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:
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:
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%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
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.
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.