Java/C: OpenJDK 原生 tanh() 实现错误?

Java/C: OpenJDK native tanh() implementation wrong?

我正在研究一些 Java 数学函数的原生 C 源代码。尤其是 tanh(),因为我很好奇他们是如何实施的。 然而,what I found让我吃惊:

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 更糟糕的近似值(这会更快),如下图所示:

(粗线是上面标示的。另一条灰色线是log(abs(x(1+x) - tanh(x)))。S形当然是tanh(x)本身。)

那么,这是实现中的错误,还是解决某些问题(比如数字问题,我真的想不起来)的黑客攻击?请注意,我希望这两种方法的结果完全相同,因为没有足够的尾数位来实际执行加法 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.

在执行该代码的条件下,假设使用 IEEE-754 double-precision 浮点表示和算术,1.0 + x 将始终计算为 1.0,所以 x * (1.0 + x) 的计算结果总是 x。执行计算而不只是返回 x 的唯一外部(对函数)可观察到的效果是设置 IEEE "inexact" 状态标志。

虽然我不知道如何从 Java 查询 FP 状态标志,但可以想象其他本机代码可以查询它们。然而,更有可能的是,实施的实际原因由 the Javadocs for java.StrictMath:

中的这些评论给出

To help ensure portability of Java programs, the definitions of some of the numeric functions in this package require that they produce the same results as certain published algorithms. These algorithms are available from the well-known network library netlib as the package "Freely Distributable Math Library," fdlibm. These algorithms, which are written in the C programming language, are then to be understood as executed with all floating-point operations following the rules of Java floating-point arithmetic.

The Java math library is defined with respect to fdlibm version 5.3. Where fdlibm provides more than one definition for a function (such as acos), use the "IEEE 754 core function" version (residing in a file whose name begins with the letter e). The methods which require fdlibm semantics are sin, cos, tan, asin, acos, atan, exp, log, log10, cbrt, atan2, pow, sinh, cosh, tanh, hypot, expm1, and log1p.

(强调已添加。)您会在 C 源代码中注意到一个 #include "fdlibm.h" 似乎将其与 Java 文档注释相关联。