实验过程中发现Java的Math.exp()操作特别耗时间,在坐标下降求解Logistic Regression的过程中Math.exp()几乎用到了70%的时间,因此我们思考是否可以通过近似的方法计算$e^x$的值,文中整理了两种实用的近似计算Math.exp()方法。
可以看到Java的Math.exp()操作是比较复杂的,自然所消耗的时间也会比较多,然而对于一些对Math.exp()的精度要求不是特别高但操作数量巨大的应用而言,精度较高但复杂度同时较高的Math.exp算法就显得有些力不从心了。
本文整理了两种近似计算exp()的算法:
算法一 使用浮点型表示的特性
算法如下:
算法二: 使用Lookup Table直接查表
使用$e^{3.45}=e^{3}\times e^{0.4}\times e^{0.05}$的性质可以进行如下近似:
但算法二的缺点在于只能计算有限范围内$e^x$的值,如果需要计算的$e^x$的x值很大的话可能表会很大,同时其相对误差也可能会比较大,下面会详细些比较二者的差别:
近似算法比较
效率
我们对精确算法、模拟算法1、模拟算法2进行了对比,在服务器上依次运行100,000,000次三种算法,可以得到:
可以看到,近似算法跟精确解相比时间上大大缩短,可以有2-3倍的效率提升。
准确度
算法一跟算法二都具有比较好的准确度,下图是两种近似算法的准确度图,从下图可以看到二者都具有比较高的准确度,算法一的误差率在0.6%内,算法二的误差率在1%内。
当然,有理论证明,算法一最差的误差率在4%(在某些特殊的值下),算法二最差的误差率在$5\times 10^{-8}%$内(如果存在[1,0,1,…,$10^{-10}$]的lookup table),可以证明,如果存在$10^{-a}$的lookuptable,那么误差率在$[-5\times[(10^{-(a+1)}-1],5\times[(10^{-(a+1)}-1]]$内。
更大的Lookup table会导致更多的时间消耗,但会更好;但由于误差率的计算是Exp’(x)/exp(x),因此当计算x为负数时,exp(x)的准确值较小,相对误差会比较大。
总结
综上,文章整理了两种近似计算exp()的算法,各有利弊,其中算法一基于Double的表示形式,同时辅助以ExpAdjustment[]数组,绝大部分点的误差率都在0.6%以内,不过在因为ExpAdjustment[]调节的值有限,算法一的最坏误差率在4%左右;而算法二的误差率可以通过lookup Table表的大小调节,误差率比较小,缺点是适用于若干有限值的计算(lookup table的大小有限)。
同时还发现存在pow的近似算法(Optimized pow() approximation for Java, C / C++, and C#),
可以比Math.pow()快23倍(Intel Core2 Quad, Q9550, Java 1.7.0_01-b08, 64-Bit Server V)。误差率5%-12%,极端情况可以达到25%。
参考:
http://martin.ankerl.com/tag/floating-point/
https://gist.github.com/mratkovic/b273376496bb283c9dec
http://ideone.com/fork/UwNgx
http://developer.classpath.org/doc/java/lang/StrictMath-source.html