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 expi expm1powinna 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 expfi expm1f, gdzie foznacza 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 expm1należy użyć do obliczenia exp(x)−1 dla małego x . Używanie go do ogólnego x nie jest szkodliwe, ponieważ expm1oczekuje 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 logi log1pponieważ log(1+x)≈x dla |x|≪1 .
log1pmówisz (szczególnie, jak to jest zaimplementowane, więc nie musimy zgadywać).