Rustam Issabekov
Rustam Issabekov

Reputation: 3497

BigDecimal floor rounding goes wrong

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

Answers (5)

user2758212
user2758212

Reputation: 21

According to the documentation, ROUND_FLOOR rounds towards negative infinity, thus rounding -14.000... to -15.

Upvotes: 2

assylias
assylias

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:

  1. 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.
  2. 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

Michael Borgwardt
Michael Borgwardt

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

michael667
michael667

Reputation: 3260

You should have a look at RoundingMode, especially HALF_UP.

Upvotes: 0

NPE
NPE

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

Related Questions