Dev Step
Dev Step

Reputation: 125

BigDecimal hasNextBigDecimal behavior

The following code does exactly what is expected:

import java.math.BigDecimal;
import java.util.Scanner;

public class Test2 {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        BigDecimal number = new BigDecimal("0");
        System.out.print("Enter a number: ");
        try {
            number = new BigDecimal(input.next());
        }
        catch(Exception e) {
            System.out.println("Not a number.");
        }
        System.out.println(number);
    }
}

If I remove the try catch block and replace it with a while loop, as follows, it does not do what is expected:

import java.math.BigDecimal;
import java.util.Scanner;

public class Test {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        System.out.print("Enter a number: ");
        while (!input.hasNextBigDecimal()) {
            System.out.print("Not a number, try again: ");
            input.next();
        }
        BigDecimal number = input.nextBigDecimal();
        System.out.println(number);
    }
}

Here is the output when I run: java Test

C:\>java Test
Enter a number: 1,1
1.1

Why would it accept a comma, but print a period?

If I run the following code, it prints out: en_ZA

import java.util.Locale;

public class Test3 {
    public static void main(String[] args) {
        System.out.println(Locale.getDefault());
    }
}

As you can see at the following link, the decimal separator for my locale is the period:

http://www.localeplanet.com/java/en-ZA/index.html

Please advise where I am going wrong.

Edit:

On further investigation, I found the following: https://www.sadev.co.za/content/how-correctly-format-currency-south-africa

So the locale technically does use the comma. Mind blown :)

Upvotes: 3

Views: 269

Answers (1)

guleryuz
guleryuz

Reputation: 2734

Scanner.hasNextBigDecimal() uses default locale's grouping and decimal separator (it seems '.' for grouping and ',' for decimal in your locale) to validate if next token matches decimalpattern:

...
DecimalFormat df =
    (DecimalFormat)NumberFormat.getNumberInstance(locale);
DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(locale);

// These must be literalized to avoid collision with regex
// metacharacters such as dot or parenthesis
groupSeparator =   "\\" + dfs.getGroupingSeparator();
decimalSeparator = "\\" + dfs.getDecimalSeparator();
...

this piece of code is from Scanner class and executed when initializing your scanner instance. then groupSeparator and decimalSeparator are used when call to Scanner.hasNextBigDecimal() for checking if the next token matches a decimal format.


new BigDecimal(String) uses directly '.' as the decimal separator.


So, if you want to use Scanner.hasNextBigDecimal() and '.' as decimal separator for user inputs then you should use Scanners useLocale(Locale) method. Your code shold look like:

Scanner input = new Scanner(System.in);
input.useLocale(Locale.ENGLISH);
System.out.print("Enter a number: ");
while (!input.hasNextBigDecimal()) {
    System.out.print("Not a number, try again: ");
    input.next();
}
BigDecimal number = input.nextBigDecimal();
System.out.println(number);

the program accepts a comma as valid input, but then prints a period as the decimal point.

Again in Scanner's nextBigDecimal() method replaces all groupSeparator's with "" and decimalSeparator with "." before calling 'new BigDecimal(String)'. The piece of code from Scanner class is:

private String processFloatToken(String token) {
    String result = token.replaceAll(groupSeparator, "");
    if (!decimalSeparator.equals("\\."))
        result = result.replaceAll(decimalSeparator, ".");

This is why it is accepting , and printing ..

Upvotes: 2

Related Questions