Was ist das nächste Doppel zu 1.0, das ist nicht 1.0?

88

Gibt es eine Möglichkeit, programmgesteuert das Double zu erhalten, das 1.0 am nächsten kommt, aber nicht 1.0 ist?

Ein hackiger Weg, dies zu tun, wäre, das Double auf eine gleich große Ganzzahl zu speichern und dann eine zu subtrahieren. So wie IEEE754-Gleitkommaformate funktionieren, würde dies dazu führen, dass der Exponent um eins verringert wird, während der Bruchteil von allen Nullen (1.000000000000) auf alle Einsen (1.111111111111) geändert wird. Es gibt jedoch Maschinen, auf denen Ganzzahlen als Little-Endian und Gleitkomma als Big-Endian gespeichert sind, sodass dies nicht immer funktioniert.

jorgbrown
quelle
4
Sie können nicht davon ausgehen, dass +1 der gleiche Abstand (von 1,0) wie -1 ist. Die Verschachtelung von Gleitkomma-Darstellungen der Basis 10 und der Basis 2 bedeutet, dass die Lücken ungleichmäßig sind.
Richard Critten
2
@ Richard: Du hast recht. Es ist sehr unwahrscheinlich, dass das Subtrahieren eines ULP den Wert "nextbefore" erhält, da ich denke, dass der Exponent ebenfalls angepasst werden müsste. nextafter()ist der einzig richtige Weg, um das zu erreichen, was er will.
Rudy Velthuis
1
Zu
Ihrer Information
1
@ RudyVelthuis: Es funktioniert auf jedem IEEE754-Binär-Gleitkommaformat.
Edgar Bonet
1
Ok, dann sag mir: Was "funktioniert bei jedem IEEE754-Gleitkommaformat"? Es ist einfach nicht wahr, dass Sie, wenn Sie den Signifikanten dekrementieren, den Wert "firstbefore ()" erhalten, insbesondere nicht für 1.0, der einen Signifikanten hat, der eine Zweierpotenz ist. Das bedeutet, dass 1.0000...Binär eine Dekrementierung ist 0.111111....und um sie zu normalisieren, müssen Sie sie nach links verschieben. 1.11111...Dazu müssen Sie den Exponenten dekrementieren. Und dann sind Sie 2 ulp von 1.0 entfernt. Nein, wenn Sie einen vom Integralwert abziehen, erhalten Sie NICHT das, was hier gefragt wird.
Rudy Velthuis

Antworten:

22

In C und C ++ ergibt Folgendes den Wert, der 1.0 am nächsten kommt:

#include <limits.h>

double closest_to_1 = 1.0 - DBL_EPSILON/FLT_RADIX;

Beachten Sie jedoch, dass in späteren Versionen von C ++ limits.hzugunsten von veraltet ist climits. Wenn Sie jedoch ohnehin C ++ - spezifischen Code verwenden, können Sie diesen verwenden

#include <limits>

typedef std::numeric_limits<double> lim_dbl;
double closest_to_1 = 1.0 - lim_dbl::epsilon()/lim_dbl::radix;

Und wie Jarod42 in seiner Antwort schreibt, können Sie seit C99 oder C ++ 11 auch Folgendes verwenden nextafter:

#include <math.h>

double closest_to_1 = nextafter(1.0, 0.0);

Natürlich können (und sollten für spätere C ++ - Versionen) in C ++ stattdessen eingeschlossen cmathund verwendet std::nextafterwerden.

Celtschk
quelle
143

Seit C ++ 11 können Sie verwenden nextafter, um den nächsten darstellbaren Wert in die angegebene Richtung zu erhalten:

std::nextafter(1., 0.); // 0.99999999999999989
std::nextafter(1., 2.); // 1.0000000000000002

Demo

Jarod42
quelle
11
Dies ist auch eine gute Möglichkeit, ein Double auf die nächste darstellbare Ganzzahl zu erhöhen : std::ceil(std::nextafter(1., std::numeric_limits<double>::max())).
Johannes Schaub - Litb
42
Die nächste Frage wird sein "wie wird dies in der stdlib implementiert": P
Leichtigkeitsrennen im Orbit
17
Nachdem ich den Kommentar von @ LightnessRacesinOrbit gelesen hatte, wurde ich neugierig. So implementiert glibcnextafter , und so implementiert musl es, falls jemand anderes sehen möchte, wie es gemacht wird. Grundsätzlich: rohes bisschen Twiddling.
Cornstalks
1
@Cornstalks: Ich bin nicht überrascht, dass es nur ein bisschen herumwirbelt. Die einzige andere Option wäre die CPU-Unterstützung.
Matthieu M.
5
Bit Twiddling ist der einzige Weg, es richtig zu machen, IMO. Sie könnten viele Testversuche machen und versuchen, sich langsam zu nähern, aber das könnte sehr langsam sein.
Rudy Velthuis
25

In C können Sie Folgendes verwenden:

#include <float.h>
...
double value = 1.0+DBL_EPSILON;

DBL_EPSILON ist die Differenz zwischen 1 und dem kleinsten Wert größer als 1, der darstellbar ist.

Sie müssen es auf mehrere Ziffern drucken, um den tatsächlichen Wert zu sehen.

Auf meiner Plattform printf("%.16lf",1.0+DBL_EPSILON)gibt 1.0000000000000002.

Barak Manos
quelle
10
Das funktioniert also nicht für andere Werte als 1.als 1'000'000 Demo
Jarod42
7
@ Jarod42: Du hast recht, aber OP fragt speziell nach 1.0. Übrigens gibt es auch den nächsten Wert größer als 1 und nicht den absolut nächsten Wert 1 (der möglicherweise kleiner als 1 ist). Ich stimme zu, dass dies eine teilweise Antwort ist, aber ich dachte, dass sie dennoch dazu beitragen könnte.
Barak Manos
@ LưuVĩnhPhúc: Ich gebe Präzision über die Einschränkung der Antwort und die nächstgelegene in die andere Richtung.
Jarod42
7
Dies ergibt nicht das nächstgelegene Double zu 1.0, da (unter der Annahme von Basis 2) das Double rechts vor 1.0 nur halb so weit entfernt ist wie das Double rechts nach 1.0 (das Sie berechnen).
Celtschk
@ Celtschk: Du hast recht, das habe ich im obigen Kommentar erklärt.
Barak Manos
4

In C ++ können Sie dies auch verwenden

1 + std::numeric_limits<double>::epsilon()
phuclv
quelle
1
Wie die Antwort von Barak Manos funktioniert dies nicht für einen anderen Wert als 1.
zwol
2
@zwol Tehnisch für typische binäre Gleitkomma-Implementierungen funktioniert es für jeden Wert zwischen 1 und 2 Epsilon. Aber ja, Sie haben Recht, dass es garantiert nur für 1 gilt.
Random832
7
Technisch funktioniert es nicht für 1, da die Zahl, die 1 am nächsten kommt, die Zahl direkt vor 1 ist und nicht die Zahl direkt danach. Die doppelte Genauigkeit zwischen 0,5 und 1 ist doppelt so hoch wie die Genauigkeit zwischen 1 und 2, daher endet die Zahl kurz vor 1 näher bei 1.
HelloGoodbye