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

2022-08-31 12:31:24

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


答案 1

用于数学类的 Javadoc 提供了有关两个类之间差异的一些信息:

与类的一些数值方法不同,类的等价函数的所有实现都没有被定义为返回比特对位相同的结果。这种放宽允许在不需要严格可重复性的情况下实现性能更好。StrictMathMath

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

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

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

(请参阅维基百科中Sine文章的软件实现部分,了解特定于平台的实现的一些示例。

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


答案 2

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

例如,检查java.lang.Math.pow(double a,double b)的Java代码,我们将看到:Math.pow

 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 

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

原因何在?

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

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

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

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

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

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


推荐