How to resolve a Java Rounding Double issue

187,809

Solution 1

To control the precision of floating point arithmetic, you should use java.math.BigDecimal. Read The need for BigDecimal by John Zukowski for more information.

Given your example, the last line would be as following using BigDecimal.

import java.math.BigDecimal;

BigDecimal premium = BigDecimal.valueOf("1586.6");
BigDecimal netToCompany = BigDecimal.valueOf("708.75");
BigDecimal commission = premium.subtract(netToCompany);
System.out.println(commission + " = " + premium + " - " + netToCompany);

This results in the following output.

877.85 = 1586.6 - 708.75

Solution 2

As the previous answers stated, this is a consequence of doing floating point arithmetic.

As a previous poster suggested, When you are doing numeric calculations, use java.math.BigDecimal.

However, there is a gotcha to using BigDecimal. When you are converting from the double value to a BigDecimal, you have a choice of using a new BigDecimal(double) constructor or the BigDecimal.valueOf(double) static factory method. Use the static factory method.

The double constructor converts the entire precision of the double to a BigDecimal while the static factory effectively converts it to a String, then converts that to a BigDecimal.

This becomes relevant when you are running into those subtle rounding errors. A number might display as .585, but internally its value is '0.58499999999999996447286321199499070644378662109375'. If you used the BigDecimal constructor, you would get the number that is NOT equal to 0.585, while the static method would give you a value equal to 0.585.

double value = 0.585;
System.out.println(new BigDecimal(value));
System.out.println(BigDecimal.valueOf(value));

on my system gives

0.58499999999999996447286321199499070644378662109375
0.585

Solution 3

Another example:

double d = 0;
for (int i = 1; i <= 10; i++) {
    d += 0.1;
}
System.out.println(d);    // prints 0.9999999999999999 not 1.0

Use BigDecimal instead.

EDIT:

Also, just to point out this isn't a 'Java' rounding issue. Other languages exhibit similar (though not necessarily consistent) behaviour. Java at least guarantees consistent behaviour in this regard.

Solution 4

I would modify the example above as follows:

import java.math.BigDecimal;

BigDecimal premium = new BigDecimal("1586.6");
BigDecimal netToCompany = new BigDecimal("708.75");
BigDecimal commission = premium.subtract(netToCompany);
System.out.println(commission + " = " + premium + " - " + netToCompany);

This way you avoid the pitfalls of using string to begin with. Another alternative:

import java.math.BigDecimal;

BigDecimal premium = BigDecimal.valueOf(158660, 2);
BigDecimal netToCompany = BigDecimal.valueOf(70875, 2);
BigDecimal commission = premium.subtract(netToCompany);
System.out.println(commission + " = " + premium + " - " + netToCompany);

I think these options are better than using doubles. In webapps numbers start out as strings anyways.

Solution 5

Any time you do calculations with doubles, this can happen. This code would give you 877.85:

double answer = Math.round(dCommission * 100000) / 100000.0;

Share:
187,809

Related videos on Youtube

Patrick
Author by

Patrick

Updated on June 05, 2020

Comments

  • Patrick
    Patrick almost 4 years

    Seems like the subtraction is triggering some kind of issue and the resulting value is wrong.

    double tempCommission = targetPremium.doubleValue()*rate.doubleValue()/100d;
    

    78.75 = 787.5 * 10.0/100d

    double netToCompany = targetPremium.doubleValue() - tempCommission;
    

    708.75 = 787.5 - 78.75

    double dCommission = request.getPremium().doubleValue() - netToCompany;
    

    877.8499999999999 = 1586.6 - 708.75

    The resulting expected value would be 877.85.

    What should be done to ensure the correct calculation?

  • Ates Goral
    Ates Goral over 15 years
    There's no way you can altogether "avoid" floating point arithmetic errors. The number of bits used in representing a number will always be finite. All you can do is to use data types with higher precision (bits).
  • palantus
    palantus over 15 years
    Better divide that by 100000.0 instead of just 100000; Math.round returns a long, so you'll be using integer division otherwise.
  • Eric Weilnau
    Eric Weilnau over 15 years
    That is true. I will edit my answer to more accurately reflect the use of BigDecimal.
  • James Schek
    James Schek over 15 years
    The only catch here is that a fraction of a cent can be lost early in the process--this might be bad for financial apps. If this is a problem, you can save the number of 1/10 cents or whatever precision you need.
  • Trenton
    Trenton over 14 years
    If I could go back in time and give my past-self one "trick", this would be it. Price as pennies! (or 1e-3 or 1e-6 -- which is still good for 2+ million dollars as an int)
  • Joshua Goldberg
    Joshua Goldberg almost 13 years
    I'll add a note that BigDecimal division needs to be treated a little bit differently than +,-,* since by default it will throw an exception if it cannot return an exact value (1 / 3, e.g.). In a similar situation I used: BigDecimal.valueOf(a).divide(BigDecimal.valueOf(b), 25, RoundingMode.HALF_UP).doubleValue(), where 25 if the maximal digits of precision (more than needed by the double result).
  • Richard
    Richard over 12 years
    I have come across this problem many times and it is really quite annoying!
  • Michael Borgwardt
    Michael Borgwardt over 12 years
    Since .5 can be exactly represented as a binary fraction, the only case where this would have an effect is when the value is legitimately different.
  • Timon
    Timon over 12 years
    @Michael: my example was flawed. However the problem remains. Therefore I added an example.
  • Michael Borgwardt
    Michael Borgwardt over 12 years
    The problem remains that your method will return the wrong result when the value really is something like 0.01499999999 - it's just fundamentally the wrong way to address problems like this.
  • Benj
    Benj almost 9 years
    This does NOT solve mantisse approximation and rounding issues ! What you see isn't what you work on !
  • Benj
    Benj almost 9 years
    Thanks for the lib ! But isn't it a bit overloaded for simple thing ?
  • Pankaj Sharma
    Pankaj Sharma almost 9 years
    @Benj you could say the same about Guava ;)
  • radekEm
    radekEm over 8 years
    Note, that the reason is, that the static method uses an UNLIMITED MathContext what practically means: new BigDecimal(value, new MathContext(0, RoundingMode.HALF_UP)) ;)
  • mortensi
    mortensi over 8 years
    If you round to 1 decimal, then you'll get 877.8. Which probably is not want you wanted.