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

时间:2010-11-20 10:18:36

标签: java

显然java.lang.StrictMath包含java.lang.Math没有的附加功能(双曲线等),但两个库中的功能是否存在差异?

4 个答案:

答案 0 :(得分:67)

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

  

与某些数字方法不同   类StrictMath,所有实现   类的等价函数   Math未定义为返回   逐位相同的结果。这个   放松允许更好的表现   实现严格的   不需要再现性。

     

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

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

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

(有关特定于平台的实施的一些示例,请参阅维基百科中Software Implementations文章的Sine部分。)

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

答案 1 :(得分:18)

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

示例:

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

答案 2 :(得分:14)

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

Math.pow为例,检查Java代码 java.lang.Math.pow(double a,double 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 

请注意,Random用于生成x和y值,因此您的里程数会因运行而异。但好消息是,至少Math.pow的编译版本的结果与Math.pow的解释版本的结果相匹配。 (非主题:即使这种一致性仅在2012年通过OpenJDK方面的一系列错误修复实施。)

原因?

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

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

  

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

     

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

     

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

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

答案 3 :(得分:0)

引用java.lang.Math

  

浮点Math方法的准确性用以下方式衡量    ulps ,最后一个单位。

...

  

如果方法的误差总是小于0.5 ulps,则该方法始终为   返回最接近确切结果的浮点数;这样的   方法正确舍入。正确舍入的方法通常是浮点近似的最佳方法;但是,对于许多浮点方法来说,正确舍入是不切实际的。

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

  

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

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

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

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