2017-01-24 5 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 * (1 + x) 
     = x + x^2 

:彼らはとしてそれを実装よう

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

そして、なぜそれが見えません明らかに正しい拡張ではなく、ちょうどtanh(x) = xを使用するよりも悪い近似さえあります速い)、このプロットで示されるように:

enter image description here

(太字の行は上部に表示するものです。他の灰色のものはlog(abs(x(1+x) - tanh(x)))です。 Sigmoidはもちろんtanh(x)です。

これは実装上のバグですか、これはいくつかの問題を解決するためのハックです(数値的な問題のように私は本当に考えることができません)。 2つのアプローチの結果は、x < 2 ^( - 55)に対して実際に加算1 + xを実行するのに十分なmantisseビットがないのとまったく同じであると期待していることに注意してください。

EDIT:そのコードが実行される条件下で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;'で簡単に行うことができます。 IMO、それ自体が '| 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からパッケージ "Freely Distributable Math Library"(fdlibm)として入手できます。 Cプログラミング言語で書かれたこれらのアルゴリズムは、Java浮動小数点演算の規則に従ってすべての浮動小数点演算で実行されるものとして理解されます。

Java数学ライブラリは、fdlibmバージョン5.3に関して定義されています。 fdlibmが関数(acosなど)に複数の定義を与える場合、 "IEEE 754 core function"バージョン(文字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)に最適化することができ、 -pointフラグ。 – njuffa

+0

@njuffa私は、アンダーフローフラグが操作の結果が小さかっただけでなく不正確な場合にのみ発生したと考えました。このフラグは '1.0 * x'によって決して上げられませんでしたか? –

+0

@ PascalCuoq良い点、あなたは正しいかもしれません。もしそうなら、 'x * 1.0'は最適化コンパイラとは無関係に適切でないかもしれません。私は、アンダーフロー・フラグを立てることの詳細を思い出さないが(それは最も複雑なものである)、IEEE 754-1985(FDLIBMコードに関連している)は、前者が丸めの前または後に検出される可能性のある「精度の喪失」、および後者が必ずしも不正確さを伴うとは限らない。 – njuffa

関連する問題