mrGreenBrown
mrGreenBrown

Reputation: 596

Java BigDecimal - explanation needed

I was using BigDecimal, but I am still getting different results for two different (mathematically identical) expressions:

First Expression: PI - (10^(-14)/PI)

Second Expression: (PI^2 - 10^(-14))/PI

To put it more simply, here is the equation: Equation with PI

package set1;

import java.math.BigDecimal;
import java.math.RoundingMode;

public class FloatingLaws {
    final static BigDecimal PI = BigDecimal.valueOf(Math.PI);
    public static void main(String[] args) {        
        System.out.println(firstExpression());
        System.out.println(secondExpression());

    }

    private static BigDecimal secondExpression() {
        return PI.subtract((BigDecimal.valueOf(Math.pow(10, -14)).divide(PI,50,RoundingMode.CEILING)));

    }

    private static BigDecimal firstExpression() {
        return (PI.multiply(PI).subtract(BigDecimal.valueOf(Math.pow(10, -14)))).divide(PI, 50,RoundingMode.CEILING);
    }

}

After executing this code, no matter how big is rounding, last digit is always different. In my case I get these two results:

3.14159265358978981690113816209304300915191180404867
3.14159265358978981690113816209304300915191180404866

My question is why is this happening and is it solvable?

Upvotes: 3

Views: 441

Answers (3)

Krzysztof Cichocki
Krzysztof Cichocki

Reputation: 6414

It is because You are doing this:

pi - ((10^-4)/pi)<- only the part in bracket is ceiled,

which is different from

((pi^2-10^-14)/pi)<- where the whole expression is ceiled.

You use BigDecimal and you have rounding mode CEILING with precision 50. In both of your expressions the ceiling is applied when you divide by PI number. So if you divide by PI eager like in first expression, then you could get less accurate results - because you CEIL intermediate value, before your formula fully execute, so you loose the CEILED part from the divide by PI operation and this in further computation creates the "error" effect. When you divide by PI last, like in second expression, you use more accurate formula, which is ceiling only the result, not intermediate value like in first expression, so it calculates more preciselly, rounding only the result and not intermediate value.

Upvotes: 5

Eashi
Eashi

Reputation: 347

I modified your program to try all rounding modes java knows. Running under oracle jdk 8.72, i get equal results for the rounding modes HALF_UP, HALF_DOWN and HALF_EVEN. But Krzysztof is right, since you are not rounding in the same places, errors are bound to pop up.

public class FloatingLaws {
    final static BigDecimal PI = BigDecimal.valueOf(Math.PI);

    public static void main(String[] args) {
        for (RoundingMode roundingMode : RoundingMode.values()) {
            System.out.println(roundingMode);
            System.out.println("Equal? "+firstExpression(roundingMode).equals(secondExpression(roundingMode)));
            System.out.println(firstExpression(roundingMode));
            System.out.println(secondExpression(roundingMode));
        }

    }

    private static BigDecimal secondExpression(RoundingMode roundingMode) {
    return PI.subtract((BigDecimal.valueOf(Math.pow(10, -14)).divide(PI, 50, roundingMode)));

    }

    private static BigDecimal firstExpression(RoundingMode roundingMode) {
    return (PI.multiply(PI).subtract(BigDecimal.valueOf(Math.pow(10, -14)))).divide(PI, 50, roundingMode);
    }

}

Upvotes: 0

Tagir Valeev
Tagir Valeev

Reputation: 100209

The BigDecimal.subtract method always produces exact difference between two BigDecimal numbers, without rounding. On the other hand BigDecimal.divide usually rounds the result. In your case you are use CEILING rounding mode which rounds up (towards +infinity). When you calculate a-ceil(b/a), you are essentially rounding down the whole result (assuming that a is already rounded), while calculating ceil((a*a-b)/a) you're rounding up. That's why firstExpression() is bigger. Were you using HALF_EVEN rounding, the result would be the same. Were you using FLOOR mode, the result would be opposite.

Also take a look to what is BigDecimal.valueOf(Math.PI);:

System.out.println(BigDecimal.valueOf(Math.PI));
> 3.141592653589793

It's not even close to the actual PI number (given the fact that you need 50 digits). You should define PI explicitly like this:

final static BigDecimal PI = new BigDecimal("3.14159265358979323846264338327950288419716939937510");

Now the result is the following:

3.14159265358979005536378154537278750652190194908786
3.14159265358979005536378154537278750652190194908785

Which is quite different from yours one.

Upvotes: 3

Related Questions