Best way to parseDouble with comma as decimal separator?
Asked Answered
V

10

182

Because of the comma used as the decimal separator, this code throws a NumberFormatException:

String p="1,234";
Double d=Double.valueOf(p); 
System.out.println(d);

Is there a better way to parse "1,234" to get 1.234 than: p = p.replaceAll(",",".");?

Vannoy answered 1/12, 2010 at 10:59 Comment(6)
In my experience, replaceAll(), as you suggested, is the best way to do this. It doesn't depend on the current locale, it's simple, and it works.Baronial
@JoonasPulakka your suggestion works only if the current default locale uses a dot as a decimal separator. Right ?Micelle
@Marco Altieri: replaceAll(",",".") replaces all commas with dots. If there are no commas, then it does nothing. Double.valueOf() works (only) with strings that use dot as decimal separator. Nothing here is affected by current default locale. docs.oracle.com/javase/8/docs/api/java/lang/…Baronial
The only problem with replaceAll(",",".") is that it'll only work if there is a single comma: ie: 1,234,567 will throw java.lang.NumberFormatException: multiple points. A regex with positive lookahead will suffice p.replaceAll(",(?=[0-9]+,)", "").replaceAll(",", ".") More at: regular-expressions.info/lookaround.htmlRetaliation
There is no problem. The NumberFormatException is good. How can you know which comma is the right one? The format is wrong and all you can do is show a better readable message than the exception to the user.Speller
@TheincredibleJan No, the format is not wrong. Some locales use comma as thousands separator, so you can have more than one of them in a number and it's technically still a valid input.Unformed
S
244

Use java.text.NumberFormat:

NumberFormat format = NumberFormat.getInstance(Locale.FRANCE);
Number number = format.parse("1,234");
double d = number.doubleValue();

Updated:

To support multi-language apps use:

NumberFormat format = NumberFormat.getInstance(Locale.getDefault());
Surbase answered 1/12, 2010 at 11:2 Comment(9)
This only works if the current default locale happens to use a comma as a decimal separator.Baronial
To further mess things up, some locales use comma as a thousands separator, in which case "1,234" would parse to 1234.0 instead of throwing an error.Baronial
Some European locales such as FRANCE, GERMANY and ITALY use comma as a decimal separator, so you could explicitly use them, rather than relying on the default.Surbase
The problem with NumberFormat is that it will silently ignore invalid characters. So if you try to parse "1,23abc" it will happily return 1.23 without indicating to you that the passed-in String contained non-parsable characters. In some situations that might actually be desirable, but I don't think it's usually the desired behavior.Ampersand
for TURKEY, you should use NumberFormat.getInstance(new Locale(tr_TR))Fugue
Does Java use decimal as its default for handling numbers? Is that why it fails?Sensitometer
for who uses what seperator see en.wikipedia.org/w/…Perspire
When the decimal separator is a dot (.) it convert in a worng way. For examples 2110.0 is converted to 21100Swinford
You can use "Locale.getDefault()" instead of "Locale.FRANCE". It helped me.Trichiasis
R
77

You can use this (the French locale has , for decimal separator)

NumberFormat nf = NumberFormat.getInstance(Locale.FRANCE);
nf.parse(p);

Or you can use java.text.DecimalFormat and set the appropriate symbols:

DecimalFormat df = new DecimalFormat();
DecimalFormatSymbols symbols = new DecimalFormatSymbols();
symbols.setDecimalSeparator(',');
symbols.setGroupingSeparator(' ');
df.setDecimalFormatSymbols(symbols);
df.parse(p);
Ramose answered 1/12, 2010 at 11:2 Comment(3)
Yes... if we don't set the thousand grouping separator and just use French format, a number in Spanish format (1.222.222,33) will be converted to "1 222 222,33", which is not what I want. So thanks!Vetter
Another thing is, Spanish locale is not listed as "default` and I cannot construct a Locale with correct format with new Locale("es", "ES") and then automatically parse the number string with NumberFormat, with , as the decimal separator and . as the thousand group separator. Only DecimalFormat works.Vetter
Why not all countries are available there? I feel weird about using French locale for formatting Polish numbers...Barns
C
22

As E-Riz points out, NumberFormat.parse(String) parse "1,23abc" as 1.23. To take the entire input we can use:

public double parseDecimal(String input) throws ParseException{
  NumberFormat numberFormat = NumberFormat.getNumberInstance(Locale.getDefault());
  ParsePosition parsePosition = new ParsePosition(0);
  Number number = numberFormat.parse(input, parsePosition);

  if(parsePosition.getIndex() != input.length()){
    throw new ParseException("Invalid input", parsePosition.getIndex());
  }

  return number.doubleValue();
}
Cusped answered 2/6, 2013 at 4:4 Comment(1)
This strategy is explained in detail here: ibm.com/developerworks/library/j-numberformatOatcake
T
17
Double.parseDouble(p.replace(',','.'))

...is very quick as it searches the underlying character array on a char-by-char basis. The string replace versions compile a RegEx to evaluate.

Basically replace(char,char) is about 10 times quicker and since you'll be doing these kind of things in low-level code it makes sense to think about this. The Hot Spot optimiser will not figure it out... Certainly doesn't on my system.

Tatia answered 3/1, 2017 at 10:31 Comment(0)
B
6

If you don't know the correct Locale and the string can have a thousand separator this could be a last resort:

    doubleStrIn = doubleStrIn.replaceAll("[^\\d,\\.]++", "");
    if (doubleStrIn.matches(".+\\.\\d+,\\d+$"))
        return Double.parseDouble(doubleStrIn.replaceAll("\\.", "").replaceAll(",", "."));
    if (doubleStrIn.matches(".+,\\d+\\.\\d+$"))
        return Double.parseDouble(doubleStrIn.replaceAll(",", ""));
    return Double.parseDouble(doubleStrIn.replaceAll(",", "."));

Be aware: this will happily parse strings like "R 1 52.43,2" to "15243.2".

Bonze answered 25/7, 2014 at 8:57 Comment(0)
A
4

This is the static method I use in my own code:

public static double sGetDecimalStringAnyLocaleAsDouble (String value) {

    if (value == null) {
        Log.e("CORE", "Null value!");
        return 0.0;
    }

    Locale theLocale = Locale.getDefault();
    NumberFormat numberFormat = DecimalFormat.getInstance(theLocale);
    Number theNumber;
    try {
        theNumber = numberFormat.parse(value);
        return theNumber.doubleValue();
    } catch (ParseException e) {
        // The string value might be either 99.99 or 99,99, depending on Locale.
        // We can deal with this safely, by forcing to be a point for the decimal separator, and then using Double.valueOf ...
        //https://mcmap.net/q/136003/-best-way-to-parsedouble-with-comma-as-decimal-separator
        String valueWithDot = value.replaceAll(",",".");

        try {
          return Double.valueOf(valueWithDot);
        } catch (NumberFormatException e2)  {
            // This happens if we're trying (say) to parse a string that isn't a number, as though it were a number!
            // If this happens, it should only be due to application logic problems.
            // In this case, the safest thing to do is return 0, having first fired-off a log warning.
            Log.w("CORE", "Warning: Value is not a number" + value);
            return 0.0;
        }
    }
}
Ailee answered 20/2, 2014 at 8:13 Comment(2)
What if the default Locale is something like German, where a comma denotes a decimal place? You could pass in, for example "1,000,000" which wouldn't parse into German Locale and would then be replaced by "1.000.000" which is not a valid Double.Aguiar
Hi @jimmycar, I've just updated my answer to use the the current version of my static method. I hope this solves your problem! PeteAilee
M
4

In Kotlin you can use extensions as below:

fun String.toDoubleEx() : Double {
   val decimalSymbol = DecimalFormatSymbols.getInstance().decimalSeparator
  return if (decimalSymbol == ',') {
      this.replace(decimalSymbol, '.').toDouble()
  } else {
      this.toDouble()
  }
}

and you can use it everywhere in your code like this:

val myNumber1 = "5,2"
val myNumber2 = "6.7"

val myNum1 = myNumber1.toDoubleEx()
val myNum2 = myNumber2.toDoubleEx()

It is easy and universal!

Meantime answered 7/9, 2020 at 9:29 Comment(0)
F
2

You of course need to use the correct locale. This question will help.

Ferdinandferdinanda answered 1/12, 2010 at 11:3 Comment(0)
B
1

In the case where you don't know the locale of the string value received and it is not necessarily the same locale as the current default locale you can use this :

private static double parseDouble(String price){
    String parsedStringDouble;
    if (price.contains(",") && price.contains(".")){
        int indexOfComma = price.indexOf(",");
        int indexOfDot = price.indexOf(".");
        String beforeDigitSeparator;
        String afterDigitSeparator;
        if (indexOfComma < indexOfDot){
            String[] splittedNumber = price.split("\\.");
            beforeDigitSeparator = splittedNumber[0];
            afterDigitSeparator = splittedNumber[1];
        }
        else {
            String[] splittedNumber = price.split(",");
            beforeDigitSeparator = splittedNumber[0];
            afterDigitSeparator = splittedNumber[1];
        }
        beforeDigitSeparator = beforeDigitSeparator.replace(",", "").replace(".", "");
        parsedStringDouble = beforeDigitSeparator+"."+afterDigitSeparator;
    }
    else {
        parsedStringDouble = price.replace(",", "");
    }

    return Double.parseDouble(parsedStringDouble);

}

It will return a double no matter what the locale of the string is. And no matter how many commas or points there are. So passing 1,000,000.54 will work so will 1.000.000,54 so you don't have to rely on the default locale for parsing the string anymore. The code isn't as optimized as it can be so any suggestions are welcome. I tried to test most of the cases to make sure it solves the problem but I am not sure it covers all. If you find a breaking value let me know.

Bellwether answered 23/5, 2020 at 20:19 Comment(1)
the problem is if the user add 10,56 it became: 1056.0 instead 10.56Pictor
F
-4

This would do the job:

Double.parseDouble(p.replace(',','.')); 
Frascati answered 5/8, 2015 at 9:4 Comment(1)
The initial question said "Is there a better way to parse "1,234" to get 1.234 than: p = p.replaceAll(",",".");", if you think replace significantly differs from using replaceAll, please explain why.Zamindar

© 2022 - 2024 — McMap. All rights reserved.