java.lang.Math和java.lang.StrictMath有什么区别?
显然java.lang.StrictMath
包含额外的函数(双曲线等),这是java.lang.Math
没有,但在两个库中find的function是否有所不同?
Math
类的Javadoc提供了两个类之间差异的一些信息:
与
StrictMath
类的一些数字方法不同,Math
类的等价函数的所有实现都没有定义为返回相同的结果。 这种放松允许在不需要严格再现性的情况下执行更好的执行。默认情况下,许多
Math
方法只是调用StrictMath
的等效方法来实现它们。 鼓励代码生成器在可用的情况下使用平台特定的本地库或微处理器指令来提供Math
方法的更高性能的实现。 这种更高性能的实现仍然必须符合Math
规范。
因此, Math
类为某些操作应该做什么规定了一些规则,但是他们并不要求在所有库的实现中都返回完全相同的结果。
这允许库的特定实现返回类似的结果,但是如果例如Math.cos
类被调用,则不完全相同的结果。 这将允许特定于平台的实现(例如使用x86浮点和SPARC浮点),这可能会返回不同的结果。
(请参阅维基百科Sine文章的软件实现部分,了解平台特定实现的一些示例。)
但是,对于StrictMath
,不同实现返回的结果必须返回相同的结果。 这对于需要不同平台上的结果重复性的情况将是可取的。
你检查了源代码吗? java.lang.Math
中的许多方法被委托给java.lang.StrictMath
。
例:
public static double cos(double a) { return StrictMath.cos(a); // default impl. delegates to StrictMath }
@ntoskrnl作为正在使用JVM内部工作的人员,我想继续您的意见,即“内部函数不一定与StrictMath方法的行为方式相同”。 要找出(或certificate)它,我们可以写一个简单的testing。
以Math.pow
为例,检查java.lang.Math.pow(double a,double b)的Java代码,我们将看到:
public static double pow(double a, double b) { return StrictMath.pow(a, b); // default impl. delegates to StrictMath }
但是JVM可以自由地使用intrinsics或runtime调用来实现它,因此返回的结果可能与我们期望从StrictMath.pow
得到的结果不同。
下面的代码显示了对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进行了这个testing,结果如下:
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值的,所以你的里程会因运行而异。 但好消息是,至lessMath.pow
的编译版本的结果与Math.pow
的解释版本的结果相匹配。 (偏离主题:即使这种一致性只是在2012年通过OpenJDK方面的一系列错误修复才得到执行。)
原因?
嗯,这是因为OpenJDK使用内在函数和运行时函数来实现Math.pow
(和其他math函数),而不仅仅是执行Java代码。 主要目的是利用x87指令,以便提高计算性能。 因此, StrictMath.pow
永远不会在运行时从Math.pow
中调用(对于我们刚才使用的OpenJDK版本来说,确切而言)。
根据Math
课程的Javadoc(以上引用@coobird),这种方法是完全合法的:
Math类包含执行基本数值运算的方法,如基本指数,对数,平方根和三angular函数。
与StrictMath类的一些数字方法不同,Math类的等价函数的所有实现都没有定义为返回相同的结果。 这种放松允许在不需要严格再现性的情况下执行更好的执行。
默认情况下,许多Math方法只是调用StrictMath中的等效方法来实现它们。 鼓励代码生成器在可用的情况下使用平台特定的本地库或微处理器指令来提供math方法的更高性能的实现。 这种更高性能的实现仍然必须符合math规范。
结论呢? 那么,对于像Java这样的dynamic代码生成的语言,请确保您从“静态”代码中看到的内容与运行时执行的内容相匹配。 有时你的眼睛会误导你。
引用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)
。)换句话说,我们正在讨论尾数。
所以,由于我们可以在Tony Guan的回答中确认, java.lang.Math.pow(..)
返回的结果在尾数的52位中可能是错误的。
挖掘一些具体的1 ulp和0.5 ulp的代码来比较会很好。 我会推测,需要做很多额外的工作才能得到最后一点正确的理由是,如果我们知道两个数字A和B四舍五入到52个有效数字,我们希望知道A×B正确到52个有效数字,正确舍入,那么实际上我们需要知道A和B的一些额外位来获得A×B的最后一位。 但是这意味着我们不应该把中间结果A和B强加于双打 ,我们实际上需要更广泛的中间结果。 (在我看来,math函数的大部分实现都严重依赖于硬编码预计算系数的乘法运算,所以如果它们需要宽于双倍的话,效率会大大提高。)