Przeszukałem niektóre z natywnych kodu źródłowego funkcji języka Java Math. Zwłaszcza tanh()
, ponieważ byłem ciekawy, jak go zaimplementowali. Jednak what I found zaskoczył mnie:Java/C: Zła implementacja OpenJDK native tanh()?
double tanh(double x) {
...
if (ix < 0x40360000) { /* |x|<22 */
if (ix<0x3c800000) /* |x|<2**-55 */
return x*(one+x); /* tanh(small) = small */
...
}
Jako komentarz wskazuje, taylor series of tanh(x) around 0, rozpoczyna się:
tanh(x) = x - x^3/3 + ...
to dlaczego to wygląda one realizowane jako:
tanh(x) = x * (1 + x)
= x + x^2
Co najwyraźniej nie jest prawidłową ekspansją, a nawet gorszym przybliżeniem niż użycie tylko tanh(x) = x
(co byłoby szybsze), co wskazuje na tym wykresie:
(Linia pogrubiona jedną wskazane na szczycie. Drugi szary to log(abs(x(1+x) - tanh(x)))
. Sigmoid to oczywiście sama tanh(x)
).
Czy jest to błąd w implementacji, czy jest to hack, aby naprawić jakiś problem (np. Problemy numeryczne, o których naprawdę nie myślę)? Zauważ, że spodziewam się, że wynik obu podejść będzie dokładnie taki sam, jak nie ma wystarczającej liczby bitów mantisse, aby faktycznie wykonać dodawanie 1 + x, dla x < 2^(- 55).
Ten rodzaj gówna powoduje eksplozje rakiet. –
W pełni oczekuj, że 'tanh()' będzie funkcją _odd_, więc 'y = f (x) -> y = -f (-x)'. 'x + x^2' to powoduje. Jedyna myśl polega na tym, że może wymusić znak + na 'f (-0.0)', ale to jest łatwe do zrobienia z 'tanh (x) = x + 0.0;'. IMO, błąd, który mógł nie objawiać się jako '| x | <2 ** - 55' ... lub coś związanego z zaokrąglaniem flag. – chux
Czy 'x * (jeden + x)' może być trudnym sposobem na uzyskanie 'x + 0.0' na docelowej platformie? – chux