Nós todos sabemos que
exp( x ) = ∑n = 0∞xnn != 1 + x + 12x2+ …
implica que para| x | «1, temosexp( x ) ≈ 1 + x. Isso significa que, se tivermos que avaliar no ponto flutuanteexp( x ) - 1, para| x | «1cancelamento catastrófico pode ocorrer.
Isso pode ser facilmente demonstrado em 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
Os valores exatos são
exp( 10- 8) - 1exp( 10- 22) - 1= 0.000000010000000050000000166666667083333334166666668 …= 0.000000000000000000000100000000000000000000005000000 …
Em geral, uma implementação "precisa" exp
e expm1
deve estar correta para não mais que 1ULP (isto é, uma unidade do último local). No entanto, como atingir essa precisão resulta em código "lento", algumas vezes uma implementação rápida e menos precisa está disponível. Por exemplo, na CUDA, temos expf
e expm1f
, onde f
significa rápido. De acordo com o guia de programação CUDA C, app. D o expf
tem um erro de 2ULP.
Se você não se importa com erros da ordem de poucos ULPS, geralmente implementações diferentes da função exponencial são equivalentes, mas cuidado para que os erros possam estar ocultos em algum lugar ... (Lembra do bug do Pentium FDIV ?)
Portanto, é bem claro que expm1
deve ser usado para calcular exp( x ) - 1 para x pequeno . Usá-lo para o x geral não é prejudicial, pois expm1
é esperado que seja preciso em toda a sua gama:
>>> exp(200)-1 == exp(200) == expm1(200)
True
(No exemplo acima 1 1 está bem abaixo de 1ULP de exp( 200 ) , portanto, todas as três expressões retornam exatamente o mesmo número de ponto flutuante.)
Uma discussão semelhante vale para as funções inversas log
e log1p
desde registro( 1 + x ) ≈ x para | x | «1 .
log1p
você está se referindo (especialmente como ela é implementada, para que não tenhamos que adivinhar).