Wann sollten log1p und expm1 verwendet werden?

30

Ich habe eine einfache Frage, die für Google wirklich schwierig ist (neben der kanonischen, was jeder Informatiker über Gleitkomma-Arithmetik wissen sollte ).

Wann sollten Funktionen wie log1poder expm1anstelle von logund verwendet werden exp? Wann sollten sie nicht verwendet werden? Wie unterscheiden sich die verschiedenen Implementierungen dieser Funktionen hinsichtlich ihrer Verwendung?

Tim
quelle
2
Willkommen bei Scicomp.SE! Das ist eine sehr vernünftige Frage, die sich jedoch leichter beantworten lässt, wenn Sie ein wenig erläutern, auf das log1p Sie sich beziehen (insbesondere wie es implementiert ist, damit wir nicht raten müssen).
Christian Clason
4
Für reelle Argumente sollten log1p und expm1 ( x ) verwendet werden, wenn x klein ist, z. B. wenn 1 + x = 1 in der Gleitkommagenauigkeit. Siehe z. B. docs.scipy.org/doc/numpy/reference/generated/numpy.expm1.html und docs.scipy.org/doc/numpy/reference/generated/numpy.log1p.html . (x)(x)x1+x=1
GoHokies
@ChristianClason danke, ich beziehe mich hauptsächlich auf C ++ std oder R, aber wenn du fragst, beginne ich zu denken, dass es auch sehr interessant wäre, etwas über Unterschiede in den Implementierungen zu lernen.
Tim
Hier ist ein Beispiel: scicomp.stackexchange.com/questions/8371/…
Juan M. Bello-Rivas
1
@ user2186862 "wenn klein ist" ist korrekt, aber nicht nur "wenn 1 + x = 1 in Gleitkommagenauigkeit" (was für x 10 - 16 in der üblichen Arithmetik mit doppelter Genauigkeit vorkommt). Die von Ihnen verlinkten Dokumentationsseiten zeigen, dass sie beispielsweise bereits für x 10 - 10 nützlich sind . x1+x=1x1016x1010
Federico Poloni

Antworten:

25

exp(x)=n=0xnn!=1+x+12x2+
|x|1exp(x)1+xexp(x)1|x|1

Dies kann leicht in Python demonstriert werden:

>>> 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(108)1=0.000000010000000050000000166666667083333334166666668exp(1022)1=0.000000000000000000000100000000000000000000005000000

Im Allgemeinen sollte eine "genaue" Implementierung von expund expm1auf nicht mehr als 1ULP (dh eine Einheit des letzten Platzes) korrekt sein. Da das Erreichen dieser Genauigkeit jedoch zu einem "langsamen" Code führt, ist manchmal eine schnelle, weniger genaue Implementierung verfügbar. Zum Beispiel in CUDA haben wir expfund expm1f, wo fsteht für schnell. Laut dem CUDA C-Programmierhandbuch, App. D das expfhat einen Fehler von 2ULP.

Wenn Sie sich nicht um Fehler in der Größenordnung weniger ULPS kümmern, sind in der Regel unterschiedliche Implementierungen der Exponentialfunktion gleichwertig. Beachten Sie jedoch, dass Fehler möglicherweise irgendwo versteckt sind ... (Denken Sie an den Pentium FDIV-Fehler ?)

expm1exp(x)1xxexpm1

>>> exp(200)-1 == exp(200) == expm1(200)
True

1exp(200)

loglog1plog(1+x)x|x|1

Stefano M
quelle
1
Diese Antwort war bereits in den Kommentaren zur OP-Frage enthalten. Ich fand es jedoch nützlich, nur aus Gründen der Klarheit einen längeren (wenn auch einfachen) Bericht zu geben, in der Hoffnung, dass er einigen Lesern nützlich sein wird.
Stefano M
OK, aber dann kann man einfach schließen, "damit ich immer expm1 anstelle von exp verwenden kann" ...
Tim
1
@tim Ihre Schlussfolgerung ist falsch: Sie können immer expm1(x)anstelle von verwenden exp(x)-1. Gilt natürlich exp(x) == exp(x) - 1generell nicht.
Stefano M
x1
1
expm1(x)0x1exp(x) - 1x1x<ϵϵ
1

Um den Unterschied zwischen logund zu erweitern, kann log1pes hilfreich sein, den Graphen wieder aufzurufen, wenn der Logarithmus:

Logarithmus

logx0ln(x)x0ln(x)ln(1e)=1ln(1e10)=10

x0ln(x+1)0ln(1+1e)0.31ln(1+1e10)0.000045log1p

1log01log1p

sfmiller940
quelle