views:

6334

answers:

10

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?

+2  A: 

See responses to this question. Essentially what you are seeing is a natural consequence of using floating point arithmetic.

You could pick some arbitrary precision (significant digits of your inputs?) and round your result to it, if you feel comfortable doing that.

Adam Bellaire
+2  A: 

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;

Steve Klabnik
Better divide that by 100000.0 instead of just 100000; Math.round returns a long, so you'll be using integer division otherwise.
Michael Myers
+15  A: 

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.6d);
BigDecimal netToCompany = BigDecimal.valueOf(708.75d);
BigDecimal commission = premium.subtract(netToCompany);
System.out.println(commission + " = " + premium + " - " + netToCompany);

This results in the following output.

877.85 = 1586.6 - 708.75
Eric Weilnau
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).
Ates Goral
That is true. I will edit my answer to more accurately reflect the use of BigDecimal.
Eric Weilnau
+3  A: 

Save the number of cents rather than dollars, and just do the format to dollars when you output it. That way you can use an integer which doesn't suffer from the precision issues.

tloach
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.
James Schek
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)
trenton
+2  A: 

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.

toolkit
+8  A: 

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 it's 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
Johann Zacharee
+1  A: 

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.

Comments? Criticism?

A: 

So far the most elegant and most efficient way to do that in Java:

double newNum = Math.floor(num * 100 + 0.5) / 100;
Roman Kagan
A: 
double rounded = Math.rint(toround * 100) / 100;
Denis
A: 

double newNum = Math.floor(num * 100 + 0.5) / 100;

this is just the same as

double newNum = Math.round(num * 100 ) / 100;

Alexpuch