How to resolve a Java Rounding Double issue
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;
Related videos on Youtube
Patrick
Updated on June 05, 2020Comments
-
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 over 15 yearsThere'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 over 15 yearsBetter divide that by 100000.0 instead of just 100000; Math.round returns a long, so you'll be using integer division otherwise.
-
Eric Weilnau over 15 yearsThat is true. I will edit my answer to more accurately reflect the use of BigDecimal.
-
James Schek over 15 yearsThe 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 over 14 yearsIf 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 almost 13 yearsI'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 over 12 yearsI have come across this problem many times and it is really quite annoying!
-
Michael Borgwardt over 12 yearsSince .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 over 12 years@Michael: my example was flawed. However the problem remains. Therefore I added an example.
-
Michael Borgwardt over 12 yearsThe 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 almost 9 yearsThis does NOT solve mantisse approximation and rounding issues ! What you see isn't what you work on !
-
Benj almost 9 yearsThanks for the lib ! But isn't it a bit overloaded for simple thing ?
-
Pankaj Sharma almost 9 years@Benj you could say the same about Guava ;)
-
radekEm over 8 yearsNote, 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 over 8 yearsIf you round to 1 decimal, then you'll get 877.8. Which probably is not want you wanted.