java.lang.Math 和 java.lang.StrictMath 有什么区别?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4232231/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 05:26:08  来源:igfitidea点击:

What's the difference between java.lang.Math and java.lang.StrictMath?

java

提问by Iain Sproat

Obviously java.lang.StrictMathcontains additional functions (hyperbolics etc.) which java.lang.Mathdoesn't, but is there a difference in the functions which are found in both libraries?

显然java.lang.StrictMath包含没有的附加函数(双曲线等)java.lang.Math,但是在两个库中找到的函数有区别吗?

采纳答案by coobird

The Javadoc for the Mathclass provides some information on the differences between the two classes:

Math该类的 Javadoc提供了有关这两个类之间差异的一些信息:

Unlike some of the numeric methods of class StrictMath, all implementations of the equivalent functions of class Mathare 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 Mathmethods simply call the equivalent method in StrictMathfor their implementation. Code generators are encouraged to use platform-specific native libraries or microprocessor instructions, where available, to provide higher-performance implementations of Mathmethods. Such higher-performance implementations still must conform to the specification for Math.

与 class 的一些数字方法不同, classStrictMath的等效函数的所有实现 Math都没有定义为返回逐位相同的结果。这种放松允许在不需要严格再现性的情况下实现更好的实现。

默认情况下,许多Math方法只是调用等效的方法来 StrictMath实现它们。鼓励代码生成器使用特定于平台的本机库或微处理器指令(如果可用)来提供更高性能的Math方法实现 。这种更高性能的实现仍然必须符合Math.

Therefore, the Mathclass lays out some rules about what certain operations should do, but they do not demand that the exactsame results be returned in all implementations of the libraries.

因此,Math该类制定了一些关于某些操作应该做什么的规则,但它们不要求在库的所有实现中返回完全相同的结果。

This allows for specific implementations of the libraries to return similiar, but not the exact same result if, for example, the Math.cosclass is called. This would allow for platform-specific implementations (such as using x86 floating point and, say, SPARC floating point) which may return different results.

这允许库的特定实现返回相似但不完全相同的结果,例如,如果Math.cos调用类。这将允许可能返回不同结果的特定于平台的实现(例如使用 x86 浮点和 SPARC 浮点)。

(Refer to the Software Implementationssection of the Sinearticle in Wikipedia for some examples of platform-specific implementations.)

(有关特定于平台的实现的一些示例,请参阅Wikipedia 中Sine文章的软件实现部分。)

However, with StrictMath, the results returned by different implementations mustreturn the same result. This would be desirable for instances where the reproducibility of results on different platforms are required.

但是,使用StrictMath,不同实现返回的结果必须返回相同的结果。对于需要在不同平台上重现结果的情况,这将是可取的。

回答by z00bs

Did you check the source code? Many methods in java.lang.Mathare delegated to java.lang.StrictMath.

你检查源代码了吗?中的许多方法java.lang.Math都委托给java.lang.StrictMath.

Example:

例子:

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

回答by Tony Guan

@ntoskrnl As somebody who is working with JVM internals, I would like to second your opinion that "intrinsics don't necessarily behave the same way as StrictMath methods". To find out (or prove) it, we can just write a simple test.

@ntoskrnl 作为使用 JVM 内部结构的人,我想支持您的观点,即“内部函数不一定与 StrictMath 方法的行为方式相同”。为了找出(或证明)它,我们可以编写一个简单的测试。

Take Math.powfor example, examining the Java code for java.lang.Math.pow(double a, double b), we will see:

就拿Math.pow例如,对于检查java.lang.Math.pow(双一,双二)Java代码中,我们将看到:

 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 with intrinsics or runtime calls, thus the returning result can be different from what we would expect from StrictMath.pow.

但是 JVM 可以自由地使用内部函数或运行时调用来实现它,因此返回的结果可能与我们期望的不同StrictMath.pow

And the following code shows this calling Math.pow()against 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;
            }

        }
    }
}

I ran this test with OpenJDK 8u5-b31 and got the result below:

我使用 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 Randomis 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.powmatch 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.)

请注意,Random它用于生成 x 和 y 值,因此您的里程会因运行而异。但好消息是,至少编译版本的结果Math.powMath.pow. (题外话:即使是这种一致性也是在 2012 年通过 OpenJDK 方面的一系列错误修复才强制执行的。)

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.powis never called from Math.powat runtime (for the OpenJDK version that we just used, to be precise).

嗯,这是因为 OpenJDK 使用内部函数和运行时函数来实现Math.pow(和其他数学函数),而不仅仅是执行 Java 代码。主要目的是利用 x87 指令来提高计算性能。因此,StrictMath.pow永远不会Math.pow在运行时调用(对于我们刚刚使用的 OpenJDK 版本,准确地说)。

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

根据Math类的Javadoc (上面也引用@coobird),这种安排是完全合法的:

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.

Math 类包含用于执行基本数值运算的方法,例如基本指数、对数、平方根和三角函数。

与类 StrictMath 的一些数值方法不同,类 Math 的等效函数的所有实现都没有定义为返回逐位相同的结果。这种放松允许在不需要严格再现性的情况下实现更好的实现。

默认情况下,许多 Math 方法只是简单地调用 StrictMath 中的等效方法来实现它们。鼓励代码生成器使用特定于平台的本机库或微处理器指令(如果可用),以提供更高性能的数学方法实现。这种更高性能的实现仍然必须符合 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.

结论呢?好吧,对于 Java 等具有动态代码生成功能的语言,请确保您从“静态”代码中看到的内容与运行时执行的内容相匹配。你的眼睛有时真的会误导你。

回答by Evgeni Sergeev

Quoting java.lang.Math:

引用java.lang.Math

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

浮点Math方法的精度是根据ulps来衡量的 ,单位在最后。

...

...

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.

如果一个方法的错误总是小于 0.5 ulps,则该方法总是返回最接近精确结果的浮点数;这种方法是正确四舍五入的。正确舍入的方法通常是浮点近似的最佳方法;但是,要正确舍入许多浮点方法是不切实际的。

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

然后我们在Math.pow(..)下看到,例如:

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

计算结果必须在精确结果的 1 ulp 以内。

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.

现在,什么是 ulp?正如预期的那样,java.lang.Math.ulp(1.0)给出 2.220446049250313e-16,即 2 -52。(也Math.ulp(8)给出与Math.ulp(10)and相同的值Math.ulp(15),但不是Math.ulp(16)。)换句话说,我们正在谈论尾数的最后一位。

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.

因此,返回的结果java.lang.Math.pow(..)可能在尾数的最后一个 52 位中是错误的,正如我们在 Tony Guan 的回答中可以确认的那样。

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.)

挖掘一些具体的 1 ulp 和 0.5 ulp 代码进行比较会很好。我推测需要相当多的额外工作才能使最后一位正确,原因与如果我们知道两个数字 A 和 B 四舍五入为 52 位有效数字并且我们希望知道 A×B 正确为 52 位有效数字的原因相同,如果四舍五入正确,那么实际上我们需要知道 A 和 B 的一些额外位才能使 A×B 的最后一位正确。但这意味着我们不应该通过将中间结果 A 和 B 强制为double 来舍入它们,我们实际上需要一种更广泛的中间结果类型。(在我所见,大多数数学函数的实现都严重依赖于与硬编码的预计算系数的乘法,因此如果它们需要比两倍宽,效率会受到很大影响。)