2009-03-13 24 views
3
Kürzen

Dies ist wahrscheinlich eine Frage für einen x86-FPU Experten:Punkt Rundung treiben, wenn

Ich versuche, eine Funktion zu schreiben, die einen zufälligen Fließkommawert im Bereich [min, max] erzeugt. Das Problem ist, dass mein Generatoralgorithmus (der Gleitkomma-Mersenne-Twister, wenn Sie neugierig sind) nur Werte im Bereich [1,2] zurückgibt - dh ich möchte eine inklusive obere Schranke, aber meinen "source" -Erzeugungswert ist aus einer exklusiven oberen Grenze. Der Catch hier ist, dass der zugrunde liegende Generator ein 8-Byte-Double zurückgibt, aber ich möchte nur ein 4-Byte-Float, und ich verwende den Standard-FPU-Rundungsmodus von Nearest. Ich möchte wissen, ob die Kürzung selbst in diesem Fall dazu führt, dass mein Rückgabewert Max enthält, wenn der FPU interne 80-Bit-Wert ausreichend nahe ist, oder ob ich den Signifikanden meines Max-Wertes erhöhen sollte vor der Multiplikation mit dem intermediären Random in [1,2], oder ob ich FPU-Modi ändern sollte. Oder irgendwelche anderen Ideen, natürlich.

Hier ist der Code, den ich zur Zeit benutzen, und ich habe überprüfen, ob 1.0f beschließt zu 0x3f800000:

float MersenneFloat(float min, float max) 
{ 
    //genrand returns a double in [1,2) 
    const float random = (float)genrand_close1_open2(); 
    //return in desired range 
    return min + (random - 1.0f) * (max - min); 
} 

Wenn es einen Unterschied macht, muss diese sowohl auf Win32 MSVC++ und Linux gcc arbeiten. Wird die Verwendung von Versionen der SSE-Optimierungen die Antwort ändern?

Edit: Die Antwort ist ja, Trunkierung in diesem Fall von Doppel auf Float ist ausreichend, um das Ergebnis einschließlich von max zu verursachen. Weitere Informationen finden Sie in der Antwort von Crashworks.

Antwort

4

Die SSE ops wird auf subtile Weise das Verhalten dieses Algorithmus geändert werden, da sie nicht über die Zwischen 80-Bit-Darstellung - die Mathematik wirklich in 32 oder 64 Bit erfolgt. Die gute Nachricht ist, dass Sie es einfach testen können und sehen, ob es Ihre Ergebnisse ändert, indem Sie einfach die Befehlszeilenoption/ARCH: SSE2 für MSVC angeben. Dadurch werden SSE-Skalaroperationen anstelle von x87-FPU-Anweisungen für normale Gleitkommazahlen verwendet Mathematik.

Ich bin nicht sicher, was das genaue Rundungsverhalten rund um die Integer-Grenzen ist, aber Sie können testen, was passiert, wenn 1.999 .. ursprüngliches Plakat lief diesen Test und festgestellt, dass mit Abschneiden wird die 1,99999 beide mit 2 aufrunden und ohne/arch: wird von zB

static uint64 OnePointNineRepeating = 0x3FF FFFFF FFFF FFFF // exponent 0 (biased to 1023), all 1 bits in mantissa 
double asDouble = *(double *)(&OnePointNineRepeating); 
float asFloat = asDouble; 
return asFloat; 

bearbeiten, Ergebnis 64 bis 32 Bits gerundet SSE2 .

+0

Nun, warum habe ich nicht daran gedacht, diesen Test unter den anderen auszuführen? Ich habe herausgefunden, dass der 1.99999 mit truncation auf 2 mit und ohne/arch: SSE2 aufrundet. Vielen Dank! –

+0

Ich bin froh zu helfen - ich war neugierig, was das Ergebnis des Tests wäre ich selbst. – Crashworks

0

Wenn Sie die Rundung so anpassen, dass beide Enden des Bereichs enthalten sind, werden diese Extremwerte nicht nur halb so wahrscheinlich sein wie bei den nicht extremen Werten?

+0

Es scheint mir, wenn ich nur truncation verwende, ist die Antwort ja, aber wenn ich den max significand inkrementiere, wäre die Antwort nein. –

0

Mit Trunkierung, werden Sie nie die max.

Sind Sie sicher, dass Sie wirklich das Maximum brauchen? Es besteht buchstäblich eine Wahrscheinlichkeit von 0, dass Sie genau auf dem Maximum landen.

Das heißt, können Sie die Tatsache ausnutzen, dass Sie Präzision geben und etwas tun, wie folgt aus:

float MersenneFloat(float min, float max) 
{ 
    double random = 100000.0; // just a dummy value 
    while ((float)random > 65535.0) 
    { 
     //genrand returns a double in [1,2) 
     double random = genrand_close1_open2() - 1.0; // now it's [0,1) 
     random *= 65536.0; // now it's [0,65536). We try again if it's > 65535.0 
    } 
    //return in desired range 
    return min + float(random/65535.0) * (max - min); 
} 

Beachten Sie, dass, jetzt ist es eine geringe Chance mehrere Anrufe hat jedes Mal, wenn Sie anrufen, um genrand MersenneFloat. Sie haben also die mögliche Leistung für ein geschlossenes Intervall aufgegeben. Da Sie vom Double zum Floating kommen, verlieren Sie am Ende keine Präzision.

Edit: verbesserter Algorithmus

+0

Ja, ich brauche die Max inklusive (es ist ein Bibliotheksfunktionsvertrag). Wäre es von Vorteil, es auf Ihre Art zu tun, anstatt den Signifikanden meines Maximalwerts vor der Multiplikation zu erhöhen? –

+0

Das könnte auch funktionieren. Irgendwo müssen Sie entweder einen Ablehnungs-Test durchführen oder eine nicht perfekte Verteilung der Werte haben. Ein Analogon dieses Problems ist, sagen wir, eine Ganzzahl 0-256 aus einem zufälligen Int 0-65535 generieren. Es wird nicht gleichmäßig abgebildet. – rlbond

+0

Eigentlich habe ich gerade Crashworks Testvorschlag ausprobiert, und die Kürzung vervollständigt sich tatsächlich. –