Мы все знаем, что
подразумевает, что для , мы имеем . Это означает, что если нам нужно вычислить в плавающей точке , дляможет произойти катастрофическая отмена.exp(x)=∑n=0∞xnn!=1+x+12x2+…
|x|≪1exp(x)≈1+xexp(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)−1xxexpm1
>>> exp(200)-1 == exp(200) == expm1(200)
True
(В приведенном выше примере значительно ниже 1ULP, равного , поэтому все три выражения возвращают одно и то же число с плавающей запятой.)1exp(200)
Аналогичное обсуждение справедливо для обратных функций log
и, log1p
поскольку для .log(1+x)≈x|x|≪1
log1p
вы ссылаетесь (особенно на то, как это реализовано, поэтому нам не нужно догадываться).