Sagen wir, ich habe eine Funktion habe, der eine 64-Bit-Integer akzeptiert, und ich möchte es mit einem double
mit beliebigen numerischen Wert nennen (dh es in Größe sehr groß sein kann, oder sogar unendlich):Wie konvertiere ich ein beliebiges double in eine Integer und vermeide dabei undefiniertes Verhalten?
void DoSomething(int64_t x);
double d = [...];
DoSomething(d);
§ 1 [conv.fpint] in den Standard 11 C++, sagt dies:
A prvalue einer Gleitkomma-Typ in einen prvalue eines integer-Typ umgewandelt werden kann. Die Umwandlung verkürzt sich; das heißt, der Bruchteil wird verworfen. Das Verhalten ist nicht definiert, wenn der abgeschnittene Wert nicht im Zieltyp dargestellt werden kann.
Daher gibt es viele Werte von d
über das wird nicht definiert Verhalten führen. Ich möchte die Konvertierung zu sättigen, so dass Werte größer als std::numeric_limits<int64_t>::max()
(kint64max
unten genannt), einschließlich unendlich, werden diesen Wert, und in ähnlicher Weise mit dem minimal darstellbaren Wert. Dies scheint den natürlichen Ansatz:
double clamped = std::min(d, static_cast<double>(kint64max));
clamped = std::max(clamped, static_cast<double>(kint64min));
DoSomething(clamped);
Aber der nächste Absatz in der Norm sagt dies:
A prvalue ein Integer-Typ oder einen unscoped Aufzählungstypen kann auf eine prvalue von umgerechnet werden ein Gleitkommatyp. Das Ergebnis ist genau wenn möglich. Wenn der konvertierte Wert im Bereich der Werte liegt, der dargestellt werden kann, aber der Wert nicht exakt dargestellt werden kann, ist eine implementierungsdefinierte Auswahl entweder des nächstniedrigeren oder höheren darstellbaren Werts.
So kann clamped
noch aufzuwickeln kint64max + 1
zu sein, und das Verhalten noch undefiniert sein kann.
Was ist die einfachste Art zu tun, was ich suche? Bonuspunkte wenn es auch elegant behandelt NaN
s.
aktualisieren: Um genauer zu sein, möchte ich folgendes an alle einer int64_t SafeCast(double)
Funktion wahr sein, das dieses Problem löst:
Für Doppel
d
, ruftSafeCast(d)
undefined nicht ausführen Verhalten nach dem Standard, noch wirft es eine Ausnahme oder sonst abbrechen.Für Doppel
d
im Bereich[-2^63, 2^63)
,SafeCast(d) == static_cast<int64_t>(d)
. Das heißt,SafeCast
stimmt mit den Konvertierungsregeln von C++ überein, wo immer diese definiert sind.Für jedes Doppel
d >= 2^63
,SafeCast(d) == kint64max
.Für jedes Doppel
d < -2^63
,SafeCast(d) == kint64min
.
Ich vermute, dass die wahre Schwierigkeit, herauszufinden ist, ob d
im Bereich ist [-2^63, 2^63)
. Wie in der Frage und in Kommentaren zu anderen Antworten diskutiert, denke ich mit einem Cast von kint64max
bis double
zu testen für die oberen gebunden ist ein Nicht-Starter wegen undefiniertem Verhalten. Es kann vielversprechender sein, std::pow(2, 63)
verwenden, aber ich weiß nicht, ob dies garantiert ist genau 2^63.
'' static_cast' kint64max + 1ULL' (oder '(uint64_t) 1 '), die exakt darstellbar sein sollte, und verwenden Sie dann' std :: nextafter' erhalten die vorherigen darstellbaren Wert, und klemmen Sie sich darauf fest. –
Was @ T.C. sagte. Der Standard garantiert dies nicht, aber ganzzahlige Potenzen von 2 können ohne Verlust bis zu den Grenzen des Fließkommaformats gespeichert werden, in jedem Gleitkommasystem, das mir bekannt ist. –
Was ist mit 'NaN' und' Infinity'? –