@ntoskrnl As someone who works with internal JVMs, I would like to express your opinion that "intrinsics do not necessarily behave the same as StrictMath methods". To find out (or prove) this, we can simply write a simple test.
Take Math.pow , for example, by examining the Java code for java.lang.Math.pow (double a, double b), we will see:
public static double pow(double a, double b) { return StrictMath.pow(a, b);
But the JVM is free to implement it using intrinsics or runtime calls, so the return result may differ from what we expect from StrictMath.pow .
And the following code shows this call to Math.pow() against StrictMath.pow()
//Strict.java, testing StrictMath.pow against Math.pow import java.util.Random; public class Strict { static double testIt(double x, double y) { return Math.pow(x, y); } public static void main(String[] args) throws Exception{ final double[] vs = new double[100]; final double[] xs = new double[100]; final double[] ys = new double[100]; final Random random = new Random(); // compute StrictMath.pow results; for (int i = 0; i<100; i++) { xs[i] = random.nextDouble(); ys[i] = random.nextDouble(); vs[i] = StrictMath.pow(xs[i], ys[i]); } boolean printed_compiled = false; boolean ever_diff = false; long len = 1000000; long start; long elapsed; while (true) { start = System.currentTimeMillis(); double blackhole = 0; for (int i = 0; i < len; i++) { int idx = i % 100; double res = testIt(xs[idx], ys[idx]); if (i >= 0 && i<100) { //presumably interpreted if (vs[idx] != res && (!Double.isNaN(res) || !Double.isNaN(vs[idx]))) { System.out.println(idx + ":\tInterpreted:" + xs[idx] + "^" + ys[idx] + "=" + res); System.out.println(idx + ":\tStrict pow : " + xs[idx] + "^" + ys[idx] + "=" + vs[idx] + "\n"); } } if (i >= 250000 && i<250100 && !printed_compiled) { //presumably compiled at this time if (vs[idx] != res && (!Double.isNaN(res) || !Double.isNaN(vs[idx]))) { System.out.println(idx + ":\tcompiled :" + xs[idx] + "^" + ys[idx] + "=" + res); System.out.println(idx + ":\tStrict pow :" + xs[idx] + "^" + ys[idx] + "=" + vs[idx] + "\n"); ever_diff = true; } } } elapsed = System.currentTimeMillis() - start; System.out.println(elapsed + " ms "); if (!printed_compiled && ever_diff) { printed_compiled = true; return; } } } }
I tested this test with OpenJDK 8u5-b31 and got the result below:
10: Interpreted:0.1845936372497491^0.01608930867480518=0.9731817015518033 10: Strict pow : 0.1845936372497491^0.01608930867480518=0.9731817015518032 41: Interpreted:0.7281259501809544^0.9414406865385655=0.7417808233050295 41: Strict pow : 0.7281259501809544^0.9414406865385655=0.7417808233050294 49: Interpreted:0.0727813262968815^0.09866028976654662=0.7721942440239148 49: Strict pow : 0.0727813262968815^0.09866028976654662=0.7721942440239149 70: Interpreted:0.6574309575966407^0.759887845481148=0.7270872740201638 70: Strict pow : 0.6574309575966407^0.759887845481148=0.7270872740201637 82: Interpreted:0.08662340816125613^0.4216580281197062=0.3564883826345057 82: Strict pow : 0.08662340816125613^0.4216580281197062=0.3564883826345058 92: Interpreted:0.20224488115245098^0.7158182878844233=0.31851834311978916 92: Strict pow : 0.20224488115245098^0.7158182878844233=0.3185183431197892 10: compiled :0.1845936372497491^0.01608930867480518=0.9731817015518033 10: Strict pow :0.1845936372497491^0.01608930867480518=0.9731817015518032 41: compiled :0.7281259501809544^0.9414406865385655=0.7417808233050295 41: Strict pow :0.7281259501809544^0.9414406865385655=0.7417808233050294 49: compiled :0.0727813262968815^0.09866028976654662=0.7721942440239148 49: Strict pow :0.0727813262968815^0.09866028976654662=0.7721942440239149 70: compiled :0.6574309575966407^0.759887845481148=0.7270872740201638 70: Strict pow :0.6574309575966407^0.759887845481148=0.7270872740201637 82: compiled :0.08662340816125613^0.4216580281197062=0.3564883826345057 82: Strict pow :0.08662340816125613^0.4216580281197062=0.3564883826345058 92: compiled :0.20224488115245098^0.7158182878844233=0.31851834311978916 92: Strict pow :0.20224488115245098^0.7158182878844233=0.3185183431197892 290 ms
Note that Random used to generate x and y values, so your mileage will be different from the mileage to run. But the good news is that at least the results of the compiled version of Math.pow match the results of the interpreted version of Math.pow . (Off Topic: even this consistency was applied only in 2012 with a number of bug fixes by OpenJDK.)
Cause?
Well, this is because OpenJDK uses intrinsics and runtime functions to implement Math.pow (and other math functions) instead of just executing Java code. The main goal is to take advantage of x87 instructions to improve performance for computing. As a result, StrictMath.pow never called from Math.pow at run time (for the version of OpenJDK that we just used, to be precise).
And this argration is completely legal according to the Javadoc class of the Math class (also cited by @coobird above):
The Math class contains methods for performing basic numerical operations, such as elementary exponential, logarithmic, square root, and trigonometric functions.
Unlike some of the numeric methods of the StrictMath class, all implementations of the equivalent functions of the Math class are not defined to return bit-bits for the same results. This relaxation allows for more efficient implementations where strict reproducibility is not required.
By default, many of the Math methods simply call the equivalent method in StrictMath to implement them. Code generators are encouraged to use built-in libraries for specific platforms or instructions on microprocessors, where available, to provide high-performance implementations of mathematical methods. Such high-performance implementations must still meet the specifications for Math.
And the conclusion? Well, for languages ββwith dynamic code generation, such as Java, make sure that what you see from the static code matches what is executed at runtime. Your eyes can sometimes lead you astray.