我正在挖掘一些Java Math函数本机C源代码.特别是tanh()
,我很想知道他们是如何实现那一个的.但是,我发现让我感到惊讶的是:
double tanh(double x) { ... if (ix < 0x40360000) { /* |x|<22 */ if (ix<0x3c800000) /* |x|<2**-55 */ return x*(one+x); /* tanh(small) = small */ ... }
正如评论所指出的那样,泰勒系列的tanh(x)大约为0,开头是:
tanh(x) = x - x^3/3 + ...
那么为什么它们看起来像是这样实现的:
tanh(x) = x * (1 + x) = x + x^2
这显然不是正确的扩展,甚至比仅仅使用更差的近似tanh(x) = x
(更快),如此图所示:
(粗线是顶部显示的那个.另一个灰色的是log(abs(x(1+x) - tanh(x)))
.Sigmoid当然是tanh(x)
它本身.)
那么,这是实施中的一个错误,还是修复一些问题的黑客(比如数字问题,我真的无法想到)?请注意,我希望两种方法的结果完全相同,因为没有足够的mantisse位来实际执行加法1 + x,对于x <2 ^( - 55).
编辑:我将在撰写本文时包含指向代码版本的链接,以供将来参考,因为这可能会得到修复.
在执行该代码的条件下,假设正在使用IEEE-754双精度浮点表示和算术,1.0 + x
将始终求值1.0
,因此x * (1.0 + x)
将始终求值x
.执行计算的唯一外部(到函数)可观察的效果而不仅仅是返回x
将是设置IEEE"不精确"状态标志.
虽然我不知道从Java查询FP状态标志,但其他本机代码可以想象它们.然而,更有可能的是,实施的实际原因是由Javadocs中的java.StrictMath
这些评论给出的:
为了帮助确保Java程序的可移植性,此程序包中某些数字函数的定义要求它们产生与某些已发布算法相同的结果.这些算法可以从众所周知的网络库netlib获得,作为"Freely Distributable Math Library"包fdlibm.然后,这些以C编程语言编写的算法应理解为遵循Java浮点算法规则的所有浮点运算.
Java数学库是根据fdlibm 5.3版定义的.其中fdlibm为函数提供了多个定义(例如acos),请使用"IEEE 754核心函数"版本(驻留在名称以字母e开头的文件中).这需要fdlibm语义的方法是
sin
,cos
,tan
,asin
,acos
,atan
,exp
,log
,log10
,cbrt
,atan2
,pow
,sinh
,cosh
,tanh
,hypot
,expm1
,和log1p
.
(重点补充.)您将在C源代码中注意到#include "fdlibm.h"
它似乎与Javadoc注释相关联.