Reputation: 3497
I recently ask a question about weird java double floor rounding, and got answers to use BigDecimals instead, so tried the following code:
BigDecimal velocity = new BigDecimal(-0.07);
BigDecimal afterMultiplyingBy200 = velocity.multiply( new BigDecimal(200.0) );
BigDecimal floored = afterMultiplyingBy200.setScale(0, RoundingMode.FLOOR);
System.out.println("After multiplication " + afterMultiplyingBy200);
System.out.println("floored value is " + floored);
And I'm getting following results
After multiplication -14.000000000000001332267629550187848508358001708984375000
floored value is -15
It seems that even using BigDecimal I can't get correct value for multiplying -0.07 by 200, is there anything that I can do to get exactly -14.0?
Upvotes: 2
Views: 15240
Reputation: 21
According to the documentation, ROUND_FLOOR
rounds towards negative infinity, thus rounding -14.000...
to -15
.
Upvotes: 2
Reputation: 328568
You should use the string constructor to avoid rounding errors due to the use of doubles:
BigDecimal velocity = new BigDecimal("-0.07");
BigDecimal afterMultiplyingBy200 = velocity.multiply(new BigDecimal("200"));
Javadoc extract for the constructor using doubles - emphasis mine:
- The results of this constructor can be somewhat unpredictable. One might assume that writing new BigDecimal(0.1) in Java creates a BigDecimal which is exactly equal to 0.1 (an unscaled value of 1, with a scale of 1), but it is actually equal to 0.1000000000000000055511151231257827021181583404541015625. This is because 0.1 cannot be represented exactly as a double (or, for that matter, as a binary fraction of any finite length). Thus, the value that is being passed in to the constructor is not exactly equal to 0.1, appearances notwithstanding.
- The String constructor, on the other hand, is perfectly predictable: writing new BigDecimal("0.1") creates a BigDecimal which is exactly equal to 0.1, as one would expect. Therefore, it is generally recommended that the String constructor be used in preference to this one.
Upvotes: 5
Reputation: 346240
From my answer to that question:
When the code is compiled or interpreted, your “0.1” is already rounded to the nearest number in that format, which results in a small rounding error even before the calculation happens.
The problem is that new BigDecimal(-0.07);
uses a double
literal to initialize the BigDecimal
- so the error still happens. Use the BigDecimal
constructor that takes a String
instead.
Upvotes: 7
Reputation: 500167
The problem is right here:
BigDecimal velocity = new BigDecimal(-0.07);
-0.07
cannot be represented exactly as a double
, so the literal value passed to the BigDecimal
constructor ends up being slightly different than -0.07
. BigDecimal
just takes that approximate value and runs with it, producing the results you're seeing.
Try:
BigDecimal velocity = new BigDecimal(-7, 2);
BigDecimal afterMultiplyingBy200 = velocity.multiply( new BigDecimal(2, -2) );
Upvotes: 3