Wszyscy wiemy, że
exp(x)=∑n=0∞xnn!=1+x+12x2+…
oznacza, że dla|x|≪1, mamyexp(x)≈1+x. Oznacza to, że jeśli musimy oceniać w zmiennoprzecinkowymexp(x)−1, dla|x|≪1może wystąpić katastrofalne anulowanie.
Można to łatwo zademonstrować w Pythonie:
>>> from math import (exp, expm1)
>>> x = 1e-8
>>> exp(x) - 1
9.99999993922529e-09
>>> expm1(x)
1.0000000050000001e-08
>>> x = 1e-22
>>> exp(x) - 1
0.0
>>> expm1(x)
1e-22
Dokładne wartości
exp(10−8)−1exp(10−22)−1=0.000000010000000050000000166666667083333334166666668…=0.000000000000000000000100000000000000000000005000000…
Zasadniczo „dokładna” implementacja exp
i expm1
powinna być poprawna do nie więcej niż 1ULP (tj. Jedna jednostka ostatniego miejsca). Ponieważ jednak osiągnięcie tej dokładności powoduje powstanie „wolnego” kodu, czasami dostępna jest szybka, mniej dokładna implementacja. Na przykład w CUDA mamy expf
i expm1f
, gdzie f
oznacza szybko. Według przewodnika programowania CUDA C, aplikacja. Dexpf
ma błędu 2ULP.
Jeśli nie przejmujesz się błędami w kolejności kilku ULPS, zwykle różne implementacje funkcji wykładniczej są równoważne, ale uważaj, że błędy mogą być gdzieś ukryte ... (Pamiętasz błąd Pentium FDIV ?)
Jest więc całkiem jasne, że expm1
należy użyć do obliczenia exp(x)−1 dla małego x . Używanie go do ogólnego x nie jest szkodliwe, ponieważ expm1
oczekuje się, że będzie dokładny w całym zakresie:
>>> exp(200)-1 == exp(200) == expm1(200)
True
(W powyższym przykładzie 1 jest znacznie poniżej 1ULP exp(200) , więc wszystkie trzy wyrażenia zwracają dokładnie tę samą liczbę zmiennoprzecinkową.)
Podobna dyskusja dotyczy funkcji odwrotnych log
i log1p
ponieważ log(1+x)≈x dla |x|≪1 .
log1p
mówisz (szczególnie, jak to jest zaimplementowane, więc nie musimy zgadywać).