2017-01-24 52 views
8

我正在挖掘一些Java数学函数原生C源代码。特别是tanh(),因为我很好奇他们是如何实现这一点的。 然而,what I found出乎我的意料:Java/C:OpenJDK本地tanh()实现错误?

double tanh(double x) { 
    ... 
    if (ix < 0x40360000) {   /* |x|<22 */ 
     if (ix<0x3c800000)   /* |x|<2**-55 */ 
      return x*(one+x);  /* tanh(small) = small */ 
    ... 
} 

正如评论指出,该taylor series of tanh(x) around 0,开头:

tanh(x) = x - x^3/3 + ... 

那么为什么它看起来像他们实现它:

tanh(x) = x * (1 + x) 
     = x + x^2 

这显然不是正确的扩展,甚至比仅仅使用tanh(x) = x(这将是快),如图所示由该曲线:

enter image description here

(粗线是一个在顶部指示。另一个灰色的是log(abs(x(1+x) - tanh(x)))。该sigmoid当然是tanh(x)本身)。

所以,这是一个在实施中的错误,或者这是一个黑客来解决一些问题(如数字问题,我不能真正想到)?请注意,我期望这两种方法的结果完全相同,因为没有足够的mantisse位实际执行加法1 + x,因为x < 2 ^( - 55)。

编辑I will include a link to the version of the code at the time of writing, for future reference, as this might get fixed.

+1

这是一种导致火箭爆炸的垃圾。 –

+1

完全期望'tanh()'是一个_odd_函数,所以'y = f(x) - > y = -f(-x)'。 'x + x^2'打破了这一点。唯一的想法是在'f(-0.0)'上强制一个+符号,但是这很容易处理'tanh(x)= x + 0.0;'。国际海事组织,一个错误,可能不会显示为“| x | <2 ** - 55”......或与舍入标志有关。 – chux

+0

在目标平台上'x *(one + x)'是否会影响'x + 0.0'? – chux

回答

6

在其中执行代码的条件,并且假定,IEEE-754双精度浮点表示和运算都在使用,1.0 + x将始终评估为1.0,所以x * (1.0 + x)将始终评估为x。执行计算的唯一外部(对于函数)可观察到的效果是完成而不是仅返回x将是设置IEEE“不精确”状态标志。

尽管我不知道从Java查询FP状态标志的方法,但其他本地代码可以想象地查询它们。更可能的情况是,但是,对于落实方案的实际原因是由the Javadocs for java.StrictMath给出的这些言论:

为了确保Java程序的可移植性,一些在这个包的数值函数的定义,要求他们产生与某些公布的算法相同的结果。这些算法可以从众所周知的网络库netlib中获得,作为“自由分布式数学库”包“fdlibm”。这些用C编程语言编写的算法被理解为遵循Java浮点运算规则的所有浮点运算。

Java数学库是根据fdlibm 5.3版定义的。 fdlibm为某个功能(如acos)提供多个定义的情况下,使用“IEEE 754核心功能”版本(驻留在名称以字母e开头的文件中)。这需要fdlibm语义的方法是sincostanasinacosatanexploglog10cbrtatan2powsinhcoshtanhhypotexpm1,和log1p

(强调已添加。)您将在C源代码中注意到一个#include "fdlibm.h",它似乎将它与Javadoc注释绑定。

+1

请注意范围| x | <2 **( - 55)包含反常规(低于正常)的操作数,因此除了不精确标志之外,此代码还可能引发反常规和下溢标志。在这里不能使用'x * 1.0'的原因是因为在ISO-C的IEEE-754绑定下,这可以优化为简单地'x'(ISO C99的F.8.2节),并且这种分配不会引起任何浮动点标志。 – njuffa

+0

@njuffa我认为只有在操作结果不精确的情况下才会产生下溢标志,因此该标志永远不会被'1.0 * x'提升? –

+0

@PascalCuoq好点,你可能是对的。如果是这样,'x * 1.0'可能不适合独立于优化编译器。我不记得提升下溢旗的所有细节(其中一个是最复杂的),但我似乎回想起IEEE 754-1985(与FDLIBM代码相关)允许对造成影响的因素进行替代设计,和“精确度损失”,前者可以在舍入之前或之后检测到,后者不一定涉及不精确。 – njuffa