What is the difference between java.lang.Math and java.lang.StrictMath?

Obviously, java.lang.StrictMath contains additional functions (hyperbolics, etc.) that java.lang.Math does not have, but is there a difference in the functions that are in both libraries?

+62
java
Nov 20 '10 at 10:18
source share
4 answers

The Javadoc for the Math class provides some information about the differences between the two classes:

Unlike some class StrictMath numeric methods, 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 better implementation of reproducibility 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 platform-specific proprietary libraries or microprocessor instructions, where available to provide higher-performance implementations of Math . Such higher performance implementations must comply with the specification for Math .

Therefore, the Math class contains some rules about what certain operations should do, but they do not require the return of the same results in all library implementations.

This allows specific library implementations to return a similar, but not the same result if, for example, the Math.cos class is Math.cos . This would allow the implementation of platform-specific implementations (for example, x86 floating point and, say, SPARC floating point), which can return different results.

(Refer to the Wikipedia Software Implementations Sine section for some sample implementations for the platform.)

However, with StrictMath results returned by various implementations should return the same result. This would be desirable for cases where reproducibility of results on different platforms is required.

+60
Nov 20 '10 at 10:34
source share

Have you checked the source code? Many methods in java.lang.Math delegated to java.lang.StrictMath .

Example:

 public static double cos(double a) { return StrictMath.cos(a); // default impl. delegates to StrictMath } 
+18
Nov 20 '10 at 10:24
source share

@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); // default impl. delegates to StrictMath } 

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.

+8
May 6 '15 at 19:26
source share

Quote from java.lang.Math :

The accuracy of Math floating point methods is measured in terms of ulps, units in last place.

...

If the method always has an error of less than 0.5 ulps, the method always returns the floating point number closest to the exact result; this method is properly rounded. A properly rounded method is usually the best floating point approximation; however, for many floating point methods, it is not practical to round correctly.

And then we see in Math.pow (..) , for example:

The calculated result should be within 1 ulp of the exact result.

Now what is ulp? As expected, java.lang.Math.ulp(1.0) gives 2.220446049250313e-16, which is 2 -52 . (Also, Math.ulp(8) gives the same meaning as Math.ulp(10) and Math.ulp(15) , but not Math.ulp(16) .) In other words, we are talking about the last cue ball of the mantissa.

So, the result returned by java.lang.Math.pow(..) may be incorrect in the last of the 52 bits of the mantissa, as we can confirm in Tony Guan's answer.

It would be nice to dig out the specific code 1 ulp and 0.5 ulp for comparison. I assume that getting this last bit requires more extra work for the same reason that if we know that two numbers A and B are rounded to 52 significant digits, and we want to know that A Γ— B corresponds to 52 significant digits, with the correct rounding, then in fact we need to know a few extra bits A and B to get the last bit A Γ— B on the right. But this means that we should not round intermediate results A and B, forcing them to double, we need an effective wider type for intermediate results. (In what I saw, most implementations of mathematical functions depend heavily on multiplications with hard-coded pre-calculated coefficients, so if they should be wider than twice, there is great success).

0
Sep 08 '17 at 7:12
source share



All Articles