0

I am puzzled by Java Bigdecimal behavior.

Let's say I have dividend = 3 and divisor = 23. first divide 3/23 = 0.130434783 Once I multiply 0.130434783 * 23 I should get 3. Bigdicimal gives a different result for a different number. Is there any way to achieve a consistent result? I have tried with multiple combinations of numbers. some times it gives correct result some time does not

public static void main(String[] args) {
    MathContext context = new MathContext(4, RoundingMode.HALF_UP);
    BigDecimal dividend = new BigDecimal(3);
    BigDecimal divisor = new BigDecimal(23);
    BigDecimal ratio = dividend.divide(divisor, context);
    System.out.println("Ratio :" + ratio);
    Double ratioInDouble = ratio.doubleValue();
    System.out.println(ratio.toEngineeringString());
    BigDecimal ans = (new BigDecimal(ratio.toEngineeringString()).multiply(divisor));
    System.out.println("Engineering String :" + ans);
    System.out.println(ratio.multiply(divisor, context));
    System.out.println(BigDecimal.valueOf(ratioInDouble).multiply(divisor, context));
    System.out.println("HALF_UP :" + BigDecimal.valueOf(ratioInDouble).multiply(divisor, new MathContext(4, RoundingMode.HALF_UP)));
    System.out.println("HALF_DOWN :" + BigDecimal.valueOf(ratioInDouble).multiply(divisor, new MathContext(4, RoundingMode.HALF_DOWN)));
    System.out.println("CEILING :" + BigDecimal.valueOf(ratioInDouble).multiply(divisor, new MathContext(4, RoundingMode.CEILING)));
    System.out.println("DOWN :" + BigDecimal.valueOf(ratioInDouble).multiply(divisor, new MathContext(4, RoundingMode.DOWN)));
    System.out.println("UP :" + BigDecimal.valueOf(ratioInDouble).multiply(divisor, new MathContext(4, RoundingMode.UP)));
}

Output

Ratio :0.1304
Multiplication  :69
0.1304
Engineering String :2.9992
2.999
2.999
HALF_UP :2.999
HALF_DOWN :2.999
CEILING :3.000
DOWN :2.999
UP :3.000
Just mul :2.9992
Turamarth
  • 2,282
  • 4
  • 25
  • 31
Varun
  • 4,342
  • 19
  • 84
  • 119
  • 1
    You'll get the same issue when you divide 1 by 3 to get 0.3333, then multiply it by 3 again. The issues is that the intermediate value is rounded. You can try to increase the scale, but it will not prevent rounding errors completely. I once had to use a scale of 15 or up just to get the same results as Excel... – Rob Spoor Feb 15 '23 at 13:53
  • 1
    You can get exact results if you use [`RoundingMode.UNNECESSARY`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/math/RoundingMode.html#UNNECESSARY). But that will throw exceptions whenever a calculation would need rounding. I'm not sure what you expect when you set up a finite MathContext. – Joachim Sauer Feb 15 '23 at 13:54
  • @RobSpoor I tried increasing the scale, But it is not consistent for all numbers. – Varun Feb 15 '23 at 13:55
  • 6
    @Varun: if you need *precise results for all numbers* then you probably need to do symbolic calculation or at the very least use a system that can store exact arbitrary fractions (i.e. it would store "3/23" instead of trying to resolve the number). Unless you can restrict "all numbers" to something more reasonable, `BigDecimal` won't be able to answer what you want. – Joachim Sauer Feb 15 '23 at 13:57
  • @JoachimSauer Do you mean java is not capable of doing that? – Varun Feb 15 '23 at 14:02
  • 5
    No, I mean that `BigDecimal` isn't capable of doing that. `BigDecimal` can store arbitrary-length decimal expansions of numbers. Some fractions (like 1/3) don't have finite expansions, so any `BigDecimal` representation of it is necessarily rounded. That rounding introduces an error. Having more digits might reduce the error but can't fully remove it. If you need exact results for any handling of fractions, then you must store the fractions in some other way. – Joachim Sauer Feb 15 '23 at 14:05
  • 3
    Java is not capable representing decimal numbers to an infinite amount of precision. It requires too much memory :-) – Stephen C Feb 15 '23 at 14:06
  • 2
    `record Fraction (int dividend, int divisor) {}` ... now `new Fraction(3, 23)` can represent 3/23 **exactly**. – Joachim Sauer Feb 15 '23 at 14:07
  • 3
    Ah ... but that wouldn't work with Pi. ([Except in Indiana ... almost](https://en.wikipedia.org/wiki/Indiana_Pi_Bill) :-) ) – Stephen C Feb 15 '23 at 14:10
  • It's not a dup but many of the same considerations apply: [is floating point math broken?](https://stackoverflow.com/q/588004/238704). BigDecimal uses a base-10 floating point representation. – President James K. Polk Feb 16 '23 at 13:57

0 Answers0