181

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(",",".");?

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
Koerr
  • 15,215
  • 28
  • 78
  • 108
  • 20
    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. – Joonas Pulakka Dec 01 '10 at 11:07
  • @JoonasPulakka your suggestion works only if the current default locale uses a dot as a decimal separator. Right ? – Marco Altieri Dec 24 '16 at 00:36
  • 2
    @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. https://docs.oracle.com/javase/8/docs/api/java/lang/Double.html#valueOf-java.lang.String- – Joonas Pulakka Dec 27 '16 at 19:38
  • 5
    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: http://www.regular-expressions.info/lookaround.html – artemisian Feb 13 '17 at 17:17
  • 2
    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. – The incredible Jan Jan 31 '18 at 11:56
  • 5
    @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. – Vratislav Jindra Apr 15 '18 at 14:37

10 Answers10

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());
Marc Bannout
  • 388
  • 4
  • 15
dogbane
  • 266,786
  • 75
  • 396
  • 414
  • 16
    This only works if the current default locale happens to use a comma as a decimal separator. – Joonas Pulakka Dec 01 '10 at 11:05
  • 9
    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. – Joonas Pulakka Dec 01 '10 at 11:11
  • 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. – dogbane Dec 01 '10 at 11:14
  • 19
    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. – E-Riz Jan 17 '13 at 19:37
  • 7
    for TURKEY, you should use NumberFormat.getInstance(new Locale(tr_TR)) – Günay Gültekin Jul 27 '13 at 09:45
  • Does Java use decimal as its default for handling numbers? Is that why it fails? – TheLettuceMaster Oct 04 '14 at 13:00
  • 2
    for who uses what seperator see http://en.wikipedia.org/w/index.php?title=Decimal_mark&uselang=de#mediaviewer/File:DecimalSeparator.svg – fiffy Nov 21 '14 at 07:09
  • 1
    When the decimal separator is a dot (.) it convert in a worng way. For examples 2110.0 is converted to 21100 – John Alexander Betts Jan 07 '15 at 15:59
  • You can use "Locale.getDefault()" instead of "Locale.FRANCE". It helped me. – Volodymyr T Feb 20 '20 at 13:43
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);
Bozho
  • 588,226
  • 146
  • 1,060
  • 1,140
  • 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! – WesternGun Mar 30 '17 at 14:55
  • 1
    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. – WesternGun Mar 30 '17 at 15:11
  • Why not all countries are available there? I feel weird about using French locale for formatting Polish numbers... – Line Feb 03 '19 at 21:23
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();
}
dgolive
  • 425
  • 3
  • 8
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.

RocketBanana
  • 195
  • 1
  • 2
5

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".

user3506443
  • 81
  • 1
  • 3
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 ...
        //http://stackoverflow.com/questions/4323599/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;
        }
    }
}
Pete
  • 784
  • 8
  • 9
  • 6
    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. – Eddie Curtis Jan 27 '15 at 17:01
  • Hi @jimmycar, I've just updated my answer to use the the current version of my static method. I hope this solves your problem! Pete – Pete Sep 03 '15 at 14:36
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!

moji
  • 277
  • 1
  • 2
  • 11
2

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

Community
  • 1
  • 1
kgiannakakis
  • 103,016
  • 27
  • 158
  • 194
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.

Amr El Aswar
  • 3,395
  • 3
  • 23
  • 36
-4

This would do the job:

Double.parseDouble(p.replace(',','.')); 
Helge
  • 17
  • 1
  • 6
    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. – SuperBiasedMan Aug 05 '15 at 09:24