Всі ми знаємо, що
exp(x)=∑n=0∞xnn!=1+x+12x2+…
означає, що для|x|≪1, маємоexp(x)≈1+x. Це означає, що якщо нам доведеться оцінювати в плаваючій точціexp(x)−1, для|x|≪1може статися катастрофічне скасування.
Це можна легко продемонструвати на python:
>>> 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
Точні значення є
exp(10−8)−1exp(10−22)−1=0.000000010000000050000000166666667083333334166666668…=0.000000000000000000000100000000000000000000005000000…
Загалом "точна" реалізація exp
та expm1
повинна бути правильною не більше 1ULP (тобто однієї одиниці останнього місця). Однак, оскільки досягнення цієї точності призводить до "повільного" коду, іноді доступна швидка, менш точна реалізація. Наприклад, у CUDA у нас є expf
і expm1f
, де f
означає швидкість. Відповідно до посібника з програмування CUDA C, додаток. Дexpf
має похибку 2ULP.
Якщо вас не хвилюють помилки в порядку декількох ULPS, зазвичай різні реалізації експоненціальної функції рівноцінні, але будьте обережні, що помилки можуть бути десь заховані ... (Пам'ятаєте помилку Pentium FDIV ?)
Тож цілком зрозуміло, що expm1
слід використовувати для обчислення exp(x)−1 для малих x . Використання його для загального x не є шкідливим, оскільки, expm1
як очікується, буде точним за весь його діапазон:
>>> exp(200)-1 == exp(200) == expm1(200)
True
(У наведеному вище прикладі 1 набагато нижче 1ULP exp(200) , тому всі три вирази повертають точно таке ж число з плаваючою точкою.)
Аналогічне обговорення стосується і зворотних функцій, log
і log1p
оскільки log(1+x)≈x для |x|≪1 .
log1p
ви посилаєтесь (особливо, як це реалізовано, тому нам не доведеться здогадуватися).