Todos sabemos que
implica que para , tenemos . Esto significa que si tenemos que evaluar en coma flotante , para puede producir una cancelación catastrófica.exp(x)=∑n=0∞xnn!=1+x+12x2+…
|x|≪1exp(x)≈1+xexp(x)−1|x|≪1
Esto se puede demostrar fácilmente en 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
Los valores exactos son
exp(10−8)−1exp(10−22)−1=0.000000010000000050000000166666667083333334166666668…=0.000000000000000000000100000000000000000000005000000…
En general, una implementación "precisa" de exp
y expm1
debe ser correcta a no más de 1ULP (es decir, una unidad del último lugar). Sin embargo, dado que lograr esta precisión da como resultado un código "lento", a veces hay disponible una implementación rápida y menos precisa. Por ejemplo en CUDA tenemos expf
y expm1f
, donde f
significa rápido. De acuerdo con la guía de programación CUDA C, aplicación. D el expf
tiene un error de 2ULP.
Si no le importan los errores en el orden de pocas ULPS, generalmente las implementaciones diferentes de la función exponencial son equivalentes, pero tenga en cuenta que los errores pueden estar ocultos en alguna parte ... (¿Recuerda el error Pentium FDIV ?)
Por lo tanto, está bastante claro que expm1
debería usarse para calcular para pequeña . Usarlo para general no es dañino, ya que se espera que sea preciso en todo su rango:exp(x)−1xxexpm1
>>> exp(200)-1 == exp(200) == expm1(200)
True
(En el ejemplo anterior está muy por debajo de 1ULP de , por lo que las tres expresiones devuelven exactamente el mismo número de coma flotante).1exp(200)
Una discusión similar es válida para las funciones inversas log
y log1p
desde para .log(1+x)≈x|x|≪1
log1p
se refiere (especialmente cómo se implementa, por lo que no tenemos que adivinar).