DecimalFormat and Double.valueOf()
Asked Answered
P

10

20

I'm trying to get rid of unnecessary symbols after decimal seperator of my double value. I'm doing it this way:

DecimalFormat format = new DecimalFormat("#.#####");
value = Double.valueOf(format.format(41251.50000000012343));

But when I run this code, it throws:

java.lang.NumberFormatException: For input string: "41251,5"
    at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1224)
    at java.lang.Double.valueOf(Double.java:447)
    at ...

As I see, Double.valueOf() works great with strings like "11.1", but it chokes on strings like "11,1". How do I work around this? Is there a more elegant way then something like

Double.valueOf(format.format(41251.50000000012343).replaceAll(",", "."));

Is there a way to override the default decimal separator value of DecimalFormat class? Any other thoughts?

Pacifically answered 22/4, 2010 at 13:9 Comment(0)
I
91

By

get rid of unnecessary symbols after decimal seperator of my double value

do you actually mean you want to round to e.g. the 5th decimal? Then just use

value = Math.round(value*1e5)/1e5;

(of course you can also Math.floor(value*1e5)/1e5 if you really want the other digits cut off)

edit

Be very careful when using this method (or any rounding of floating points). It fails for something as simple as 265.335. The intermediate result of 265.335 * 100 (precision of 2 digits) is 26533.499999999996. This means it gets rounded down to 265.33. There simply are inherent problems when converting from floating point numbers to real decimal numbers. See EJP's answer here at https://mcmap.net/q/37227/-how-to-round-a-number-to-n-decimal-places-in-java - How to round a number to n decimal places in Java

Impediment answered 22/4, 2010 at 13:25 Comment(6)
Won't this cause a problem when value*1e5 exceeds the maximum double value, effectivly reducing the maximum value value can have to Double.MAX_VALUE/1e5?Topside
@JimmyR.T. Indeed it will. However, to quote from Wikipedia on Doubles: The 11 bit width of the exponent allows the representation of numbers between 10⁻³⁰⁸ and 10³⁰⁸, with full 15–17 decimal digits precision. So you cannot store a number with 308-5=303 digits to round to anyway.Impediment
I'm aware that it won't be necessary in that case, but I think a solution should just keep the number the same if nothing is to be cut off instead of generating an overflow; or that restriction should be mentioned. Just a minor remark for completenessTopside
@JimmyR.T. Absolutely, I never thought about it. It's a corner case IMHO, but of course one could work around it. Or just refer to another answer here ;)Impediment
Why can't you store a number with 303 digits? That's less than the maximum of 308 digits.Mannes
@scrs because Double uses 52 bits for the fraction part, which corresponds to 15-17 decimal digits only, not 308 (which is the approximate maximum decimal exponent yielded by the 11 bit exponent)Impediment
T
22

The problem is that your decimal format converts your value to a localized string. I'm guessing that your default decimal separator for your locale is with a ','. This often happens with French locales or other parts of the world.

Basically what you need to do is create your formatted date with the '.' separator so Double.valueOf can read it. As indicated by the comments, you can use the same format to parse the value as well instead of using Double.valueOf.

DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance();
symbols.setDecimalSeparator('.');
DecimalFormat format = new DecimalFormat("#.#####", symbols);
value = format.parse(format.format(41251.50000000012343));
Trudi answered 22/4, 2010 at 13:25 Comment(5)
or DecimalFormat format = new DecimalFormat("#"+symbols.getDecimalSeparator+"#####", symbols; without changing the decimal separatorImpediment
If you already have the DecimalFormat, why not use it for parsing as well? Much more robust than trying to build a DecimalFormat whose formatted output can be parsed by Double.valueOf()Humanoid
@Michael Borgwardt Good point. I've updated the answer to include thatTrudi
I'm tempted to downvote this answer - not because it doesn't solve the problem, but because it supports this insane attempt at rounding - what the OP admitted to be the goal. But technically it's the answer the OP asked for :-/Impediment
@VipulPurohit For this error, yes, but for the problem behind it: NO! One should not use string operations on floats to perform MATHSImpediment
H
10

The real solution is: don't use floating-point numbers for anything that needs to be counted with precision:

  • If you are dealing with currency, don't use a double number of dollars, use an integer number of cents.
  • If you are dealing with hours of time and need to count quarter-hours and 10-minute intervals, use an integer number of minutes.

A floating point number is almost always an approximation of some real value. They are suitable for measurements and calculation of physical quantities (top a degree of precision) and for statistical artifacts.

Fooling about with rounding floating point to a number of digits is a code smell: it's wasteful and you can never really be sure that your code will work properly in all cases.

Hollenbeck answered 30/5, 2016 at 6:26 Comment(0)
D
7

The fact that your formatting string uses . as the decimal separator while the exception complains of , points to a Locale issue; i.e. DecimalFormat is using a different Locale to format the number than Double.valueOf expects.

In general, you should construct a NumberFormat based on a specific Locale.

Locale myLocale = ...;
NumberFormat f = NumberFormat.getInstance(myLocale);

From the JavaDocs of DecimalFormat:

To obtain a NumberFormat for a specific locale, including the default locale, call one of NumberFormat's factory methods, such as getInstance(). In general, do not call the DecimalFormat constructors directly, since the NumberFormat factory methods may return subclasses other than DecimalFormat.

However as BalusC points out, attempting to format a double as a String and then parse the String back to the double is a pretty bad code smell. I would suspect that you are dealing with issues where you expect a fixed-precision decimal number (such as a monetary amount) but are running into issues because double is a floating point number, which means that many values (such as 0.1) cannot be expressed precisely as a double/float. If this is the case, the correct way to handle a fixed-precision decimal number is to use a BigDecimal.

Demarche answered 22/4, 2010 at 13:20 Comment(0)
I
6

Use Locale.getDefault() to get your system's decimal separator which you can also set. You can't have two different separators at the same time since the other is then usually used as the separator for thousands: 2.001.000,23 <=> 2,001,000.23

Impediment answered 22/4, 2010 at 13:19 Comment(0)
M
4

looks like your local use a comma "," as a decimal separation.To get the "." as a decimal separator, you will have to declare:

DecimalFormat dFormat =new DecimalFormat("#.#", new DecimalFormatSymbols(Locale.ENGLISH));
Marseillaise answered 9/9, 2015 at 14:59 Comment(1)
Please avoid posting an answer which has already been posted earlier.Cruzcruzado
S
3

For the ',' instead of the '.' , you'd have to change the locale.

For the number of decimals, use setMaximumFractionDigits(int newValue).

For the rest, see the javadoc.

Spearing answered 22/4, 2010 at 13:20 Comment(0)
W
2

You can't change the internal representation of double/Double that way.

If you want to change the (human) representation, just keep it String. Thus, leave that Double#valueOf() away and use the String outcome of DecimalFormat#format() in your presentation. If you ever want to do calculations with it, you can always convert back to a real Double using DecimalFormat and Double#valueOf().

By the way, as per your complain I'm trying to get rid of unnecessary symbols after decimal seperator of my double value, are you aware of the internals of floating point numbers? It smells a bit like that you're using unformatted doubles in the presentation layer and that you didn't realize that with the average UI you can just present them using DecimalFormat without the need to convert back to Double.

Wagstaff answered 22/4, 2010 at 13:12 Comment(8)
The problem is when I convert it to String, it then won't convert back to Double, using Double.valueOf(), because DecimalFormat places ,, where valueOf() is expecting .. So is there a way to override this in DecimalFormat class (so that I don't have to place a call to replaceAll(",", ".") there)?Pacifically
Use DecimalFormat to reformat it into the format as expected by Double.Wagstaff
Yeah, but DecimalFormat places it's default decimal separator (which is ,). How do I make it place . instead?Pacifically
The separator is locale dependent. Ensure that you get the right DecimalFormat instance for the desired locale.Wagstaff
@folone See my answer, e.g. Locale.setDefault(Locale.ENGLISH)Impediment
Goldberg's paper is way too high-level to serve as a basic introduction to floating point to be thrown at newbies in the manner or RTFM. For this reason I've written floating-point-gui.deHumanoid
@Michael: now that's a nice one. I updated the link accordingly.Wagstaff
I don't actually use doubles in gui or currency presentations (here I use them for Ole Date representation). But thanks, that's a great link.Pacifically
W
2

Somewhat related to this, but not an answer to the question: try switching to BigDecimal instead of doubles and floats. I was having a lot of issue with comparisons on those types and now I'm good to go with BigDecimal.

Whistler answered 22/4, 2010 at 20:46 Comment(1)
Definitely what one should do when precision is mandatory, e.g. when money is concerned.Impediment
M
-1

My code function :

 private static double arrondi(double number){
         DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance();
         symbols.setDecimalSeparator('.');
         DecimalFormat format = new DecimalFormat("#.#####", symbols);
         return Double.valueOf(format.format(number));
     }
Menjivar answered 7/4, 2017 at 14:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.