Wann ist es vorteilhaft, von Float auf Double via Dezimal zu konvertieren

8

Unsere bestehende Anwendung liest einige Fließkommazahlen aus einer Datei. Die Zahlen werden dort von einer anderen Anwendung geschrieben (nennen wir es Anwendung B ). Das Format dieser Datei wurde vor langer Zeit behoben (und wir können es nicht ändern). In dieser Datei werden alle Fließkommazahlen als Gleitkommazahlen in Binärdarstellung gespeichert (4 Byte in der Datei).

Sobald wir die Daten gelesen haben, konvertieren wir in unserem Programm die floats in doubles und verwenden doubles für alle Berechnungen, weil die Berechnungen ziemlich umfangreich sind und wir uns mit der Verbreitung von Rundungsfehlern beschäftigen.

Wir haben festgestellt, dass wir bei der Konvertierung von Gleitkommazahlen über Dezimalzahlen (siehe unten stehenden Code) genauere Ergebnisse erhalten als bei der direkten Konvertierung. Hinweis: Anwendung B verwendet intern auch doubles und schreibt sie nur als Float in die Datei. Angenommen, Anwendung B hatte die Nummer 0.012 als float in die Datei geschrieben. Wenn wir es nach dem Lesen in Dezimalzahlen konvertieren und dann verdoppeln, erhalten wir genau 0,012, wenn wir es direkt konvertieren, erhalten wir 0,0120000001043081.

Dies kann ohne Lesen aus einer Datei reproduziert werden - mit nur einer Zuweisung:

%Vor%

Ist es immer vorteilhaft, von Float in Double über Dezimal zu konvertieren, und wenn nicht, unter welchen Bedingungen ist es vorteilhaft?

EDIT: Anwendung B kann die Werte, die es speichert, auf zwei Arten erhalten:

  1. Wert kann ein Ergebnis von Berechnungen sein
  2. Wert kann vom Benutzer als Dezimalbruch eingegeben werden (im obigen Beispiel hat der Benutzer also 0.012 in ein Bearbeitungsfeld eingegeben und es wurde in double umgewandelt und dann in float gespeichert)
farfareast 05.11.2012, 22:10
quelle

3 Antworten

2

So seltsam es auch erscheinen mag, die Konvertierung über Dezimalstellen (mit Convert.ToDecimal(float) ) kann unter bestimmten Umständen vorteilhaft sein.

Es verbessert die Genauigkeit, wenn bekannt ist, dass die ursprünglichen Zahlen von Benutzern in dezimaler Darstellung bereitgestellt wurden und Benutzer nicht mehr als 7 signifikante Ziffern eingegeben haben.

Um es zu beweisen, habe ich ein kleines Programm geschrieben (siehe unten). Hier ist die Erklärung:

Wie Sie sich vom OP erinnern, ist dies die Abfolge der Schritte:

  1. Anwendung B verfügt über Doppel aus zwei Quellen: (a) Ergebnisse von Berechnungen; (b) konvertiert von benutzerdefinierten Dezimalzahlen.
  2. Anwendung B schreibt doubles als floats in die Datei - effektiv binäre Rundung von 52 Binärziffern ( IEEE 754 single ) auf die 23 Binärziffern (IEEE 754 double ".
  3. Unsere Anwendung liest das float und konvertiert es auf eine von zwei Arten in ein double:

    (a) direkte Zuordnung zu Doppel - effektiv Auffüllen einer 23-Bit-Zahl zu einer 52-Bit-Zahl mit binären Nullen (29 Null-Bits);

    (b) über Umwandlung in Dezimal mit (double)Convert.ToDecimal(float) .

Wie Ben Voigt richtig bemerkt hat Convert.ToDecimal(float) ( siehe MSDN in der Anmerkungsteil ) rundet das Ergebnis auf 7 signifikante Dezimalziffern auf. Im Wikipedia IEEE 754 Artikel über Single können wir das lesen precision is 24 bits - equivalent to log10(pow(2,24)) ≈ 7.225 decimal digits. Also, wenn wir die Konvertierung zu machen Dezimal wir verlieren diese 0,225 einer Dezimalziffer.

Wenn also im generischen Fall keine zusätzlichen Informationen zu Doppelpunkten vorhanden sind, führt die Konvertierung in Dezimalzahlen in den meisten Fällen zu einer gewissen Genauigkeit.

Aber (!) wenn es das zusätzliche Wissen gibt, dass ursprünglich (vor dem Schreiben in eine Datei als Gleitkommazahl) die Doppelwerte Dezimalzahlen mit nicht mehr als 7 Ziffern waren, die Rundungsfehler, die in Dezimalrunden eingeführt wurden (Schritt 3 (b) oben) wird die Rundungsfehler kompensieren, die mit der binären Rundung eingeführt wurden (in Schritt 2. oben).

Im Programm zum Beweis der Aussage für den generischen Fall erzeuge ich nach dem Zufallsprinzip Doubles, werfe es dann auf float und konvertiere es dann direkt in double (a) zurück, (b) über die Dezimalstelle, dann vermesse ich den Abstand zwischen dem ursprünglichen Doppel und dem Doppel (a) und Doppel (b). Wenn das Double (a) näher am Original ist als das Double (b), inkrementiere ich den Pro-Direct-Conversion-Zähler, im umgekehrten Fall inkrementiere ich den Pro-ViaDecimal-Counter. Ich mache es in einer Schleife von 1 Million. Zyklen, dann drucke ich das Verhältnis von Pro-Direct- zu Pro-ViaDecimal-Zählern. Das Verhältnis stellt sich als ungefähr 3,7 heraus, d. H. Ungefähr in 4 Fällen von 5 wird die Umwandlung über die Dezimalzahl die Zahl verderben.

Um den Fall zu beweisen, wenn die Zahlen von Benutzern eingegeben werden Ich habe dasselbe Programm mit der einzigen Änderung verwendet, die ich Math.Round(originalDouble, N) auf die Doubles anwende. Da ich OriginalDoubles aus der Random-Klasse bekomme, liegen sie alle zwischen 0 und 1, so dass die Anzahl der signifikanten Stellen mit der Anzahl der Nachkommastellen übereinstimmt. Ich habe diese Methode in einer Schleife um N von 1 signifikanten Ziffer bis 15 signifikanten Ziffern vom Benutzer eingegeben. Dann habe ich es in der Grafik gezeichnet. Die Abhängigkeit von (wie oft direkte Konvertierung ist besser als Konvertierung über Dezimal) von der Anzahl signifikanter Ziffern, die vom Benutzer eingegeben wurden. .

Wie Sie sehen, ist die Konvertierung über Dezimal für immer 1 bis 7 Ziffern besser als die direkte Konvertierung. Um genau zu sein, für eine Million von Zufallszahlen werden nur 1 oder 2 nicht durch Umwandlung in Dezimalzahlen verbessert.

Hier ist der Code für den Vergleich:

%Vor%     
farfareast 06.11.2012, 22:29
quelle
12
  

wir bekommen genau 0.012

Nein, tust du nicht. Weder float noch double können genau 3/250 darstellen. Was Sie erhalten, ist ein Wert, der vom String-Formatierer Double.ToString() als "0.012" dargestellt wird. Dies geschieht jedoch, weil der Formatierer den genauen Wert nicht anzeigt.

Das Durchlaufen von decimal verursacht Rundung. Es ist wahrscheinlich viel schneller (um leichter zu verstehen), einfach Math.Round mit den gewünschten Rundungsparametern zu verwenden. Wenn Ihnen die Anzahl der signifikanten Ziffern wichtig ist, siehe:

Was es wert ist, ist 0.012f (was bedeutet, dass der 32-Bit IEEE-754-Wert, der 0,012 am nächsten liegt) genau

ist %Vor%

oder

%Vor%

und das ist genau als System.Decimal darstellbar. Aber Convert.ToDecimal(0.012f) gibt Ihnen nicht den genauen Wert - pro Dokumentation gibt es einen Rundungsschritt .

  

Der Wert Decimal , der von dieser Methode zurückgegeben wird, enthält maximal sieben signifikante Ziffern. Wenn der value-Parameter mehr als sieben signifikante Ziffern enthält, wird er mit der nächsten Rundung gerundet.

    
Ben Voigt 05.11.2012 22:15
quelle
1

Hier ist eine andere Antwort - ich bin mir nicht sicher, ob es besser ist als Ben (fast sicher nicht), aber es sollte die richtigen Ergebnisse bringen:

%Vor%

Solange .ToString("0.000") die "richtige" Zahl erzeugt (was leicht zu überprüfen sein sollte), erhalten Sie etwas, mit dem Sie arbeiten können, und müssen sich nicht um Rundungsfehler kümmern. Wenn Sie mehr Präzision benötigen, fügen Sie einfach weitere 0 hinzu.

Natürlich, wenn Sie tatsächlich mit 0.012f out auf die maximale Genauigkeit arbeiten müssen, dann hilft das nicht, aber wenn das der Fall ist, dann wollen Sie es nicht von einem Float in konvertieren erster Platz.

    
Bobson 06.11.2012 15:04
quelle

Tags und Links