Home > Enterprise >  Why am I seeing a one-off error with Math.pow(11, 16)?
Why am I seeing a one-off error with Math.pow(11, 16)?

Time:06-01

I have to compute 11^16 for a project at my Uni. Somehow Math.pow(11,16) computes a solution exactly 1 less than WolframAlpha or my other computation method.

Result from WolframAlpha

My code is:

public class Test {
    public static void main(String args[]) {
        long a = 11;
        long b = 16;
        System.out.println(""   (long)Math.pow(a, b));
        System.out.println(""   squareAndMultiply(a, b));
    }

    public static long squareAndMultiply(long b, long e){
        long result = 1;
        long sq = b;
        while(e>0){
            if(e%2==1){
                result *= sq;
            }
            sq = sq * sq;
            e /= 2;
        }
        return result;
    }
}

The result from the code is:

math.pow(11,16):

45949729863572160

squareAndMultiply(11,16):

45949729863572161

CodePudding user response:

With floating-point arithmetic, you're in that gray zone where the precision of a double is less than that of a long (even if the range of a double is much bigger).

A double has 53 bits of precision, whereas a long can devote all 64 bits to precision. When you're dealing with values as high as 1116, the difference between one double value and the next one up becomes noticeable.

Java has a built-in method Math.ulp ("unit in last place") that effectively gives the difference in values between consecutive representable values. (There's a double version and a float version.)

System.out.println(Math.ulp(Math.pow(11, 16)));

8.0

That means the least possible double value greater than 45949729863572160 is 45949729863572168.

The long value 45949729863572161 is correct, but the value you're getting with Math.pow, 45949729863572160, is as close as a double can get to the true answer, given its limited (but still large) precision.

Casting to a long makes no difference, because Math.pow already computes the result as a double, so the answer is off by one already. Your long method of computing the value is correct.

If you're computing values that would overflow long, then instead of using double, you can use BigDecimal, which has its own pow method to retain a precision of 1.0.

CodePudding user response:

The root cause of this discrepancy is loss of precision because of Double precision numbers are accurate up to sixteen decimal places.

One way to demonstrate is this example.

System.out.println((double)999999999999999999L);

outputs:

1.0E18

The output of Math.pow(11, 16) is 4.594972986357216E16, which on casting to long gets converted into 45949729863572160.

If you are interested more in learning about the loss of precision, you can check this.

  • Related