2013-08-06 12 views
5

Dies ist eine sehr einfache Frage, aber eine wichtige, da es mein ganzes Projekt enorm beeinflusst.Trunking ein Doppel zu einem Float in C

Angenommen, ich habe folgendes Codestück:

unsigned int x = 0xffffffff; 
float f = (float)((double)x * (double)2.328306436538696e-010); // x/2^32 

Ich würde erwarten, dass f etwas wie 0,99999 sein, sondern es rundet bis zu 1, da es in der Nähe float Annäherung ist. Das ist nicht gut, da ich float Werte auf dem Intervall von [0,1], nicht [0,1] brauche. Ich bin mir sicher, dass es etwas Einfaches ist, aber ich würde mich über Hilfe freuen.

Antwort

0

Meine mögliche Lösung war, nur die Größe meines konstanten Multiplikators zu verkleinern.Es war wahrscheinlich die beste Lösung, da es ohnehin keinen Sinn hatte, mit einem Doppel zu multiplizieren. Die Genauigkeit wurde nach der Konvertierung in einen Float nicht erkannt.

so wurde 2.328306436538696e-010 zu 2.3283063 geändert

3

Der Wert, über den eine double Runden auf 1 oder mehr, wenn auf float im Standard-IEEE 754-Rundungsmodus 0x1.ffffffp-1 umgewandelt (in C99 der hexadezimalen Schreibweise, da Ihre Frage „C“ markiert ist) ist.

Optionen sind verfügbar:

  1. die FPU-Rundungsmodus zu Rund nach unten vor der Konvertierung oder
  2. multiplizieren mit (0x1.ffffffp-1/0xffffffffp0) drehen (geben oder ein ULP nehmen) die vollständige Single-Precision Bereich [auszubeuten 0, 1) ohne den Wert 1.0f zu erhalten.

Methode 2 leads to use the constant0x1.ffffff01fffffp-33:

double factor = nextafter(0x1.ffffffp-1/0xffffffffp0, 0.0); 
unsigned int x = 0xffffffff; 
float f = (float)((double)x * factor); 
printf("factor:%a\nunrounded:%a\nresult:%a\n", factor, (double)x * factor, f); 

Drucke:

factor:0x1.ffffff01fffffp-33 
unrounded:0x1.fffffefffffffp-1 
result:0x1.fffffep-1 
1

Es gibt nicht viel Sie tun können - Ihre int hält 32 Bits, aber die Mantisse einer float hält nur 24 Runden wird passieren. Sie können den Rundungsmodus des Prozessors auf Abrunden statt auf den nächsten Modus einstellen, aber das wird einige Nebenwirkungen verursachen, die Sie vermeiden möchten, insbesondere wenn Sie den Rundungsmodus nicht wiederherstellen, wenn Sie fertig sind.

Es ist nichts falsch mit der Formel, die Sie verwenden, es produziert die genaueste Antwort möglich für die gegebene Eingabe. Es gibt nur einen Endfall, der eine harte Anforderung nicht erfüllt. Es ist nichts falsch mit Tests für den spezifischen Fall Ende und mit dem nächsten Wert zu ersetzen, der die Anforderung erfüllt:

if (f >= 1.0f) 
    f = 0.99999994f; 

0,999999940395355224609375 ist der nächste Wert, dass ein IEEE-754 Schwimmer ohne gleich 1,0 nehmen.

+1

Dies ist keine hilfreiche Antwort. Wie andere Antworten gezeigt haben (und sie haben gezeigt, wie), gibt es Dinge, die Sie tun können. –

+0

@EricPostpischil, wie ist es nicht hilfreich? Es bietet eine funktionierende Lösung für das Problem, ohne einen Rundungsmodus zu verlassen, der alle Zwischen- und nachfolgenden Berechnungen ändert. –

+0

Die Aussage "Es gibt nicht viel, was Sie tun können" ist irreführend und unnötig entmutigend. Die Aussage über Bits in einem "int" und einem "float" ist irrelevant; Das OP erwartet keine exakte Karte. Sie wollen nicht umrunden, nur um es zu kontrollieren. –

8

In C (seit C99), können Sie die Rundungsrichtung mit fesetround von libm

#include <stdio.h> 
#include <fenv.h> 
int main() 
{ 
    #pragma STDC FENV_ACCESS ON 
    fesetround(FE_DOWNWARD); 
    // volatile -- uncomment for GNU gcc and whoever else doesn't support FENV 
    unsigned long x = 0xffffffff; 
    float f = (float)((double)x * (double)2.328306436538696e-010); // x/2^32 
    printf("%.50f\n", f); 
} 

mit IBM XL, Sun Studio, Klirren, GNU gcc Getestet ändern. Das gibt mir 0.99999994039535522460937500000000000000000000000000 in allen Fällen

+0

Ist das eine C++ 11-Funktion? –

+0

@MarkB C99 Funktion, enthalten in C++ 11 – Cubbi

+0

@EricPostpischil danke für das Aufzeigen, umgeschrieben in C – Cubbi

1

Sie können den Wert auf maximale Genauigkeit (die 24 hohen Bits beibehalten) und durch 2^24 teilen, um den nächsten Wert zu erhalten, den ein Float darstellen kann, ohne auf 1 gerundet zu werden;

unsigned int i = 0xffffffff; 
float value = (float)(i>>8)/(1<<24); 

printf("%.20f\n", value); 
printf("%a\n", value); 

>>> 0.99999994039535522461 
>>> 0x1.fffffep-1 
+0

Dies kann ein guter Ansatz sein, wenn das Runden von jedem Wert in Richtung Null (nicht nur die in der Nähe von 1) zum OP passt. Der Hack zur Veranschaulichung ist unnötig; Wir können den Formatbezeichner '% a' verwenden, um Fließkommazahlen auf eine Weise anzuzeigen, die ihre Zusammensetzung veranschaulicht. –

+0

@EricPostpischil Danke für das '% a' Format, wusste davon nichts. –

Verwandte Themen