2014-09-15 15 views
13

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:

  1. Für Doppel d, ruft SafeCast(d) undefined nicht ausführen Verhalten nach dem Standard, noch wirft es eine Ausnahme oder sonst abbrechen.

  2. 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.

  3. Für jedes Doppel d >= 2^63, SafeCast(d) == kint64max.

  4. 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.

+1

'' 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. –

+0

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. –

+0

Was ist mit 'NaN' und' Infinity'? –

Antwort

2

Es stellt sich heraus, dass dies einfacher zu tun ist, als ich dachte. Danke an Michael O'Reilly für die Grundidee dieser Lösung.

Das Herz der Sache ist herauszufinden, ob die abgeschnittene Doppel darstellbar als int64_t sein wird. Sie können dies einfach mit std::frexp:

#include <cmath> 
#include <limits> 

static constexpr int64_t kint64min = std::numeric_limits<int64_t>::min(); 
static constexpr int64_t kint64max = std::numeric_limits<int64_t>::max(); 

int64_t SafeCast(double d) { 
    // We must special-case NaN, for which the logic below doesn't work. 
    if (std::isnan(d)) { 
    return 0; 
    } 

    // Find that exponent exp such that 
    //  d == x * 2^exp 
    // for some x with abs(x) in [0.5, 1.0). Note that this implies that the 
    // magnitude of d is strictly less than 2^exp. 
    // 
    // If d is infinite, the call to std::frexp is legal but the contents of exp 
    // are unspecified. 
    int exp; 
    std::frexp(d, &exp); 

    // If the magnitude of d is strictly less than 2^63, the truncated version 
    // of d is guaranteed to be representable. The only representable integer 
    // for which this is not the case is kint64min, but it is covered by the 
    // logic below. 
    if (std::isfinite(d) && exp <= 63) { 
    return d; 
    } 

    // Handle infinities and finite numbers with magnitude >= 2^63. 
    return std::signbit(d) ? kint64min : kint64max; 
} 
-1

Wie wäre:

constexpr uint64_t weird_high_limit = (double)kint64max == (double)(kint64max-1); 
int64_t clamped = (d >= weird_high_limit + kint64max)? kint64max: (d <= kint64min)? kint64min: int64_t(d); 

ich denke, das kümmert sich um die alle Grenzfälle. Wenn d < (double)kint64max, dann (exact)d <= (exact)kint64max. Der Beweis läuft im Widerspruch zu der Tatsache, dass (double)kint64max der nächst höhere oder niedrigere darstellbare Wert ist.

+0

Wenn '(double) kint64max' zum nächst niedrigeren darstellbaren Wert und' d == (double) kint64max' geht, dann erhalten Sie 'kint64max', obwohl Sie' int64_t (d) 'erhalten sollten (was kleiner ist als' kint64max'). –

+0

Ich denke, der Standard erlaubt 'kint64max - 1', genau als Double darstellbar zu sein, aber' kint64max' nicht zu sein. Dann kann 'kint64max' im ersten Vergleich in das doppelte' kint64max - 1' umgewandelt werden. Wenn "d" also "kint64max - 1" ist, wird es falsch in "kint64max" umgewandelt. – jacobsa

+0

@ T.C .: Nun, das ist nicht schön, aber es kann explizit getestet werden. –

-1
+0

Dies war bereits eine Antwort, die zurückgezogen wurde. Die Quelle, die ich gefunden habe Googling [numeric_cast source] enthält diesen Ausdruck um zu bestimmen, ob 'arg' im Bereich ist:'! (Arg> result_traits :: max()) '. Ich glaube, das kann zu undefiniertem Verhalten führen. 'result_traits :: max()' darf das Double werden, das 2^63 darstellt, dann wird dies sagen, dass ein Wert von 2^63 für 'arg' im Bereich liegt. Aber das Konvertieren von 2^63 in ein 'int64_t' ist undefiniertes Verhalten. – jacobsa

+0

@jacobsa Ah, ich habe keine Sicht auf die zurückgezogene Antwort, um zu sehen, wie das den Fluss dieser Diskussion beeinflusst hat. –

+0

Ja, das ist nicht wirklich relevant. Ich denke, was ich sage ist: Es ist gut zu wissen, dass 'bost :: numeric_cast' existiert, aber was mich interessiert, ist, welche Technik es benutzt. Der Quellcode, den ich mir ansehe, sieht nicht korrekt aus (oder zumindest portabel), aber ich sehe vielleicht den falschen Quellcode. – jacobsa

1

Hier ist eine Lösung, die nicht alle Kriterien passen, zusammen mit Analyse für warum nicht. Eine bessere Antwort finden Sie unter the accepted answer.

// Define constants from the question. 
static constexpr int64_t kint64min = std::numeric_limits<int64_t>::min(); 
static constexpr int64_t kint64max = std::numeric_limits<int64_t>::max(); 

int64_t SafeCast(double d) { 
    // Handle NaN specially. 
    if (std::isnan(d)) return 0; 

    // Handle out of range below. 
    if (d <= kint64min) return kint64min; 

    // Handle out of range above. 
    if (d >= kint64max) return kint64max; 

    // At this point we know that d is in range. 
    return d; 
} 

Ich glaube, das nicht definiertes Verhalten vermeidet. Es gibt nichts zu beachten mit Casting Integer, um die Reichweitenprüfungen zu verdoppeln. Unter der Annahme, dass nicht darstellbare Ganzzahlen konvertiert werden (insbesondere, dass das Mapping monoton ist), können wir sicher sein, dass d in [-2^63, 2^63) ist, wie für die implizite Umwandlung erforderlich am Ende der Funktion.

Ich bin auch zuversichtlich, dass dies außer Reichweite Werte richtig einstellt.

Das Problem ist Kriterium # 2 aus dem Update auf meine Frage. Betrachten Sie eine Implementierung, wo kint64max ist nicht als ein Doppel, aber kint64max - 1 ist darstellbar. Ferner sei angenommen, dass dies eine Implementierung ist, bei der kint64max zu einem Doppel den nächst niedrigeren darstellbaren Wert , d. H. kint64max - 1, ergibt. Es sei d 2^63-2 (d. H. kint64max - 1). Dann SafeCast(d) ist kint64max, weil die Bereichsprüfung konvertiert kint64max zu eine doppelte, was einen Wert entspricht d entspricht. Aber static_cast<int64_t>(d) ist kint64max - 1.

Versuchen Sie, wie ich könnte, kann ich keinen Weg finden, um dies zu beheben. Ich kann auch keinen Komponententest schreiben, der meine Kriterien überprüft, ohne dass der Komponententest das undefinierte Verhalten ausführt.Ich habe das Gefühl, dass hier eine tiefere Lektion zu lernen ist - etwas über die Unmöglichkeit zu erkennen, ob eine Aktion in einem System zu undefiniertem Verhalten aus dem System selbst verursacht, ohne undefinierte Verhalten zu verursachen.

+0

Wenn eine Implementierung etwas verwendet, das IEEE-754-Mathematik ähnelt, ist INT64_MAX nicht genau darstellbar, aber INT64_MIN wird sein; Gibt es ein Problem mit der Überprüfung, ob d> = 0 und -d> = INT64_MIN, und falls ja, casting -d zu einem int64_t und dann das Ergebnis überprüfen? Das würde es überflüssig machen, einen Exponenten zu extrahieren. – supercat

Verwandte Themen