Nice programing

java.lang.Math와 java.lang.StrictMath의 차이점은 무엇입니까?

nicepro 2020. 10. 24. 11:47
반응형

java.lang.Math와 java.lang.StrictMath의 차이점은 무엇입니까?


분명히 java.lang.StrictMath포함되어 java.lang.Math있지 않은 추가 기능 (쌍곡선 등)이 포함되어 있지만 두 라이브러리에서 발견되는 기능에 차이가 있습니까?


Math클래스 용 Javadoc 은 두 클래스 간의 차이점에 대한 몇 가지 정보를 제공합니다.

class의 일부 숫자 메서드와 달리 class StrictMath의 동등한 함수에 대한 모든 구현은 Math비트 단위 동일한 결과를 반환하도록 정의되어 있지 않습니다. 이러한 완화를 통해 엄격한 재현성이 필요하지 않은 경우 더 나은 성능을 구현할 수 있습니다.

기본적으로 대부분의 Math메서드는 StrictMath구현 위해 단순히에서 동등한 메서드를 호출합니다 . 코드 생성기는 가능한 경우 플랫폼 별 네이티브 라이브러리 또는 마이크로 프로세서 명령어를 사용하여 Math메서드의 고성능 구현을 제공 하는 것이 좋습니다. 이러한 고성능 구현은 여전히 ​​.NET 용 사양을 준수해야합니다 Math.

따라서 Math클래스는 특정 작업이 무엇을해야하는지에 대한 몇 가지 규칙을 낳는,하지만 그들은 것을 요구하지 않는다 정확히 같은 결과는 라이브러리의 모든 구현에 반환.

이를 통해 라이브러리의 특정 구현이 비슷한 결과를 반환 할 수 있지만 예를 들어 Math.cos클래스가 호출 되는 경우 똑같은 결과가 아닙니다 . 이를 통해 다른 결과를 반환 할 수있는 플랫폼 별 구현 (예 : x86 부동 소수점 및 SPARC 부동 소수점 사용)이 가능합니다.

( 플랫폼 별 구현의 몇 가지 예는 Wikipedia Sine 기사에서 소프트웨어 구현 섹션을 참조하십시오 .)

그러나를 사용 StrictMath하면 다른 구현에서 반환 된 결과가 동일한 결과를 반환 해야합니다 . 이것은 다른 플랫폼에서 결과의 재현성이 필요한 경우에 바람직합니다.


소스 코드를 확인 했습니까? 의 많은 메서드 java.lang.Mathjava.lang.StrictMath.

예:

public static double cos(double a) {
    return StrictMath.cos(a); // default impl. delegates to StrictMath
}

@ntoskrnl JVM 내부 작업을하는 사람으로서, "내재 함수가 반드시 StrictMath 메소드와 동일한 방식으로 작동하지는 않는다"는 의견을 두 번째로 드리고 싶습니다. 그것을 알아 내거나 증명하기 위해 간단한 테스트를 작성할 수 있습니다.

가지고 Math.pow, 예를 들어 java.lang.Math.pow (더블 A, 더블 B)의 자바 코드를 검사, 우리는 볼 수 있습니다 :

 public static double pow(double a, double b) {
    return StrictMath.pow(a, b); // default impl. delegates to StrictMath
}

그러나 JVM은 내장 함수 또는 런타임 호출을 통해 자유롭게 구현할 수 있으므로 반환되는 결과는 예상되는 것과 다를 수 있습니다 StrictMath.pow.

그리고 다음 코드는이 호출 Math.pow()보여줍니다.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;
            }

        }
    }
}

OpenJDK 8u5-b31로이 테스트를 실행했고 결과는 아래와 같습니다.

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 

Please note that Random is used to generate the x and y values, so your mileage will vary from run to run. But good news is that at least the results of compiled version of Math.pow match those of interpreted version of Math.pow. (Off topic: even this consistency was only enforced in 2012 with a series of bug fixes from OpenJDK side.)

The reason?

Well, it's because OpenJDK uses intrinsics and runtime functions to implement Math.pow (and other math functions), instead of just executing the Java code. The main purpose is to take advantage of x87 instructions so that performance for the computation can be boosted. As a result, StrictMath.pow is never called from Math.pow at runtime (for the OpenJDK version that we just used, to be precise).

And this arragement is totally legitimate according to the Javadoc of Math class (also quoted by @coobird above):

The class Math contains methods for performing basic numeric operations such as the elementary exponential, logarithm, square root, and trigonometric functions.

Unlike some of the numeric methods of class StrictMath, all implementations of the equivalent functions of class Math are not defined to return the bit-for-bit same results. This relaxation permits better-performing implementations where strict reproducibility is not required.

By default many of the Math methods simply call the equivalent method in StrictMath for their implementation. Code generators are encouraged to use platform-specific native libraries or microprocessor instructions, where available, to provide higher-performance implementations of Math methods. Such higher-performance implementations still must conform to the specification for Math.

And the conclusion? Well, for languages with dynamic code generation such as Java, please make sure what you see from the 'static' code matches what is executed at runtime. Your eyes can sometimes really mislead you.


Quoting java.lang.Math:

Accuracy of the floating-point Math methods is measured in terms of ulps, units in the last place.

...

If a method always has an error less than 0.5 ulps, the method always returns the floating-point number nearest the exact result; such a method is correctly rounded. A correctly rounded method is generally the best a floating-point approximation can be; however, it is impractical for many floating-point methods to be correctly rounded.

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

The computed result must be within 1 ulp of the exact result.

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

So, the result returned by java.lang.Math.pow(..) may be wrong 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 up some concrete 1 ulp and 0.5 ulp code to compare. I'll speculate that quite a lot of extra work is required to get that last bit correct for the same reason that if we know two numbers A and B rounded to 52 significant figures and we wish to know A×B correct to 52 significant figures, with correct rounding, then actually we need to know a few extra bits of A and B to get the last bit of A×B right. But that means we shouldn't round intermediate results A and B by forcing them into doubles, we need, effectively, a wider type for intermediate results. (In what I've seen, most implementations of mathematical functions rely heavily on multiplications with hard-coded precomputed coefficients, so if they need to be wider than double, there's a big efficiency hit.)

참고URL : https://stackoverflow.com/questions/4232231/whats-the-difference-between-java-lang-math-and-java-lang-strictmath

반응형