BigDecimal divide returning wrong result for some calculations

XtremeBaumer :

I have read a few questions about flaoting point numbers and their math. To me it looks like the issues only happen at greater scales (i.e. 10+ decimals). Now my issue happens at 2 decimals already and its quite big off in a few cases:

Following code:

System.out.println(
        String.format("%.2f", BigDecimal.valueOf(2.8).divide(BigDecimal.valueOf(3.87), 2).doubleValue()));
System.out.println(
        String.format("%.2f", BigDecimal.valueOf(2.41).divide(BigDecimal.valueOf(2.73), 2).doubleValue()));

generates this output respectively:

0.80
0.89

If I perform the calculations by hand (using google calculations), I get these results:

0.72351421188
0.88278388278

for the first calculations the results are really big (~0.08 off) while for the second one its very low (~0.01 off).

Is there some sane explanation as to why thie first result is that big? Or any way to get the right result using BigDecimal?

Note that

System.out.println(2.8/3.87);

actually returns the right result (0.7235142118863048).

Please also note that the String.format stuff is only used to check if the result was changed by it which is not the case. BigDecimal.valueOf(2.8).divide(BigDecimal.valueOf(3.87), 2).doubleValue() yields exactly the same result.

Kevin Cruijssen :

The problem is that you're using the wrong BigDecimal#divide for your rounding to 2 decimals. Here are the available arguments for the BigDecimal#divide methods:

  • divide(BigDecimal divisor)
  • divide(BigDecimal divisor, int roundingMode)
  • divide(BigDecimal divisor, MathContext mc)
  • divide(BigDecimal divisor, RoundingMode roundingMode)
  • divide(BigDecimal divisor, int scale, int roundingMode)
  • divide(BigDecimal divisor, int scale, RoundingMode roundingMode)

Since you're using a divide with a BigDecimal and int argument, it therefore uses the divide(BigDecimal divisor, int roundingMode), where your 2 is the rounding mode and NOT the scale. In this case, 2 is actually ROUND_CEILING, and scale is unspecified.

Instead, you'll have to use either the divide(BigDecimal divisor, int scale, int roundingMode) or divide(BigDecimal divisor, int scale, RoundingMode roundingMode). So change your calls to:

System.out.println(BigDecimal.valueOf(2.8).divide(BigDecimal.valueOf(3.87), 2, 
  BigDecimal.ROUND_HALF_UP));
System.out.println(BigDecimal.valueOf(2.41).divide(BigDecimal.valueOf(2.73), 2, 
  BigDecimal.ROUND_HALF_UP));

(Feel free to use a rounding mode other than ROUND_HALF_UP.)

Try it online.

I'm not sure what scale it uses by default, but the ROUND_CEILING you've specified with your 2 caused the issues in your calculations.


As for the mentioned comments, there are three possible ways to create the BigDecimal with the specified values:

  • BigDecimal.valueOf(2.8)
  • new BigDecimal(2.8)
  • new BigDecimal("2.8")

The new BigDecimal(2.8) still gives floating point errors, so I would advice to use either BigDecimal.valueOf(2.8) as you already did, or the String constructor new BigDecimal("2.8").

Try it online.

This is all a bit irrelevant for your rounding issues however, since using the correct divide method will give the correct results regardless of the BigDecimal initialization you've used:

Try it online.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=127688&siteId=1