C++ float Präzision

T_55

Lieutenant
Registriert
Feb. 2013
Beiträge
638
Hallo,

mal eine Frage zu Präzision einer 4 Byte floating, es gibt verschiedene Aussagen zur Präzision. Mir ist im Grunde das floating Thema nicht ganz fremd aber so eine richtig präzise Aussage konnte ich nicht finden.

6-stellig
http://openbook.rheinwerk-verlag.de...en_008.htm#mj357b36b759cbb526c1701f1341d99a96

6-9 significant digits, typically 7
https://www.learncpp.com/cpp-tutorial/floating-point-numbers/

float has 7 decimal digits of precision.
https://www.geeksforgeeks.org/difference-float-double-c-cpp/

Jetzt mal die Frage, wenn im Programm irgendwann mal ein float von einem double gefüttert wird, wieviele Stellen sind jetzt wirklich save?
6, 7, 8, 9 ?

Ich habe selber mal getestet und eine double einer float übergeben und geschaut ab wann die Werte nicht mehr stimmen:
(ausgegeben mit std::setprecition(20); und std::fixed)
double 0.123456789 -> float 0.1234567 (8 korrekte Stellen)
double 100.123456789 -> float 100.12345 (8 korrekte Stellen)
double 10000.123456789 -> float 10000.123 (8 korrekte Stellen)

Also beim eigenen Test bleiben 8 Stellen korrekt.
Ergänzung ()

Noch kurz der Hintergrund, ich will eine Funktion schreiben, die prüft ob ein double in ein float passt und falls nicht ein false zurück liefert. Dazu runde ich das double zunächst auf 10 Stellen (in dem Usecase wird nie mehr Präzision kommen) dann konvertiere ich es zu einem String, lösche die überflüssigen Nullen und gucke wie lang das string ist. Jetzt muss natürlich mit einem Wert verglichen werden der die maximale Anzahl an float Stellen definiert um dann ein flase oder true zurückzugeben. Nach dem eigenen Test würde ich eine 8 als max definieren aber die anderen Quellen sagen eben auch andere Werte.
 
Zuletzt bearbeitet:
Float hat eine Mantisse mit 24 bit (nach der 1), das ist die Präzision. Du kannst 0.2 schon nicht exakt darstellen mit einer float. Die Frage nach den Dezimalstellen ist ungenau, da nicht die gleiche Basis verwendet wird.

Ob eine Double rundungsfrei reinpasst:
1. Exponent prüfen (zu groß, zu klein) [-128,127] für float
2. Mantisse prüfen (hinteren 24 bit sollten 0 sein)
 
  • Gefällt mir
Reaktionen: BAGZZlash
jo so einfach ist das nicht, gibt ja noch den Exponenten.

float is a 32 bit IEEE 754 single precision Floating Point Number1 bit for the sign, (8 bits for the exponent, and 23* for the value), i.e. float has 7 decimal digits of precision.

double is a 64 bit IEEE 754 double precision Floating Point Number (1 bit for the sign, 11 bits for the exponent, and 52* bits for the value), i.e. double has 15 decimal digits of precision.

Und warum musst Du das so machen?
 
Das sieht alles andere als trivial aus. Ich frage mich auch ob das dann auch wirklich funktioniert denn zB:
Ein double was mal als 0.5 gedacht war ist gerne mal ein 0.50000000000000001 oder 0.49999999999999999.
Wenn ich das jetzt zum float gebe geht die 1 am Ende (bei zB 0.50000000000000001) verloren und der cast wirft ggf ein Fehler. Aber eigentlich ist es kein Fehler da es mal als 0.5 gedacht war. Daher die Idee mit dem Runden, dann zum String, dann Zeichen zählen und abgleichen mit Maxwert. Aber welcher Maxwert? Die Representation findet ja nicht in digits statt sondern binär daher ist meine Frage eigentlich nur bei wieviel digits ist ein float garantiert save?
 
Zuletzt bearbeitet:
Bei der Transformation Dezimal zu floating Binär hast du Fehler, genauso wie du beim Rechnen mit floats jedesmal Ungenauigkeiten hast. Ohne zu wissen was du genau machst, lassen sich keine sauberen Aussagen treffen.

Sinnvoll ist oft die Herangehensweise zu fragen wie präzise die Berechnung sein muss und daran dann festzumachen mit wie vielen Bit man rechnet.
 
T_55 schrieb:
Die Representation findet ja nicht in digits statt sondern binär daher ist meine Frage eigentlich nur bei wieviel digits ist ein float garantiert save?
Habe ich doch im ersten Beitrag direkt vom C++ Standard und den dazugehörigen Wikipedia Artikel verlinkt.
7.22​
Decimal Digits​

Ich würde aber auch zu @Piktogramm s Aussage tendieren. Sobald du eine Rechenoperation durchführst, schaut es auch wieder ganz anders aus.
 
Btw. 0.5 geht exakt.

Dir muss der Unterschied zwischen Wert und Darstellung klar sein. Jede Zahlendarstellung mit endlicher Größe kann nur endlich viele Werte annehmen.

Du hast das Problem, dass deine Zahlen, die du im Dezimalsystem genau angeben kannst, als Double gerundet werden. Das ist aber zu erwarten, da 10=2*5. Wenn du mit den Dezimalzahlen exakt rechnen MUSST, dann bleibt dir nichts übrig, als direkt mit der Dezimaldarstellung zu rechnen.

Falls dir die Präzision nicht reicht, gibt es Fixkomma-variablen Typen (z.B. der Decimal-Typ aus C#).
 
@Piktogramm Grundsätzlich werden floats zur Berechnung verwendet. Die Daten übersteigen normalerweise auch nicht diese Präzision als Input. Es kann aber sein, dass mal ein falscher Datensatz eingelesen wird (mit zu hoher Präzision) und dann soll das Programm den Datensatz verweigern, dafür die Funktion.

@new Account() Da die Daten von Tabellen kommen und dort sozusagen im Ursprung als "Zahlen in Tabelle" vorliegen, brauche ich ja ein Grenzwert als integer. 7.22 bedeutet dann wohl 7 sichere digits richtig? Dann wäre ich mit 7 save das wäre dann der Maxwert. Ich frage mich dann eben nur warum auch im Test 8 funktioniert.
 
T_55 schrieb:
Da die Daten von Tabellen kommen und dort sozusagen im Ursprung als "Zahlen in Tabelle" vorliegen, brauche ich ja ein Grenzwert als integer. 7.22 bedeutet dann wohl 7 sichere digits richtig?
100% Garantie kann ich dir leider nicht geben.
T_55 schrieb:
Ich frage mich dann eben nur warum auch im Test 8 funktioniert.
Funktioniert vermutlich nicht immer.
 
  • Gefällt mir
Reaktionen: BAGZZlash und new Account()
T_55 schrieb:
@Piktogramm Grundsätzlich werden floats zur Berechnung verwendet. Die Daten übersteigen normalerweise auch nicht diese Präzision als Input. Es kann aber sein, dass mal ein falscher Datensatz eingelesen wird (mit zu hoher Präzision)
Wenn Float an Präzission reicht, dann bereinigt der Cast auf Float doch sowieso alle unnützen Informationen. Da gibt es keinen Grund eine Sonderbehandlung einzuführen, wenn die höhere Präzision nicht gebraucht wird.
Das kann kein Grund sein da irgendwelche Sonderbehandlungen zu bauen.

Wenn es dir darum geht unplausible Datensätze heraus zu werfen, dann tu das in einer gesonderten Funktion. Wobei sich die dortige Entscheidung nicht an der Präzision der verwendeten Datentypen orientieren sollte sondern an der echten Welt (welche Werte können in der Realität überhaupt auftreten).


Für deinen Test, deine Tests sind schlicht schlecht :)
Versuch mal die 3.9999999 und 3.999999
 
Piktogramm schrieb:
Wenn es dir darum geht unplausible Datensätze heraus zu werfen
Genau das ist der Grund, es ist nur eine Sicherheitsmaßnahme wenn die Daten zu "lang" sind zB weil sie in einem falschen Faktor vorliegen was sie "normal" nicht sollten aber schon mal passieren kann zB statt 7.15631 kommt dann 0.0715631 (Faktor 0.01) und schon liegt eine nötige Stelle an Digit 8 und es wird bei floats Murks gerechnet. Aktuell haben wir nicht mehr als 32GB RAM daher im Programm derzeit noch float-Zwang im Datenbereithaltungsteil (Berechnungen selbst als double).

Danke für alle Antworten, mit etwas Abstand hab ich nun auch bemerkt, dass ich zu kompliziert gedacht habe, denn ich habe an einem Punkt angesetzt wo die Daten temporär beim Tabelle iterieren bereits als double vorliegen und wollte von dort das Problem lösen, es ist aber natürlich viel einfacher schon davor, also bevor die Zeile per std::stod reingezogen wurde, einfach die String-Länge des entsprechenden Spalten-Eintrags zu checken. Das geht dann erstmal soweit ok aber die langfristige Lösung wird irgendwann sein ein Umstieg komplett auf double und damit neue Hardware mit mehr RAM.

Die korrekte Antwort auf die digit-Frage ist wohl: 6 Stellen sind in einer float sicher aufgehoben:
https://www.exploringbinary.com/decimal-precision-of-binary-floating-point-numbers/
If you care about the minimum precision you can get from a float, or equivalently, the maximum number of digits guaranteed to round-trip through a float, then 6 digits is your answer. (It’s unfortunate we have to be that conservative; only a small percentage of the float range is limited to 6 digits.) If you want to know the range of precision, or equivalently, the range of the number of digits that can round-trip through a float (excluding “coincidental” conversions), then 6-8 digits is your answer. If you want to know how much precision you’ll get on average, then your answer is slightly more than 7 digits.
If you could give only one answer, the safe answer is 6 digits. That way there will be no surprises (print precision notwithstanding).
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: new Account()
Da wäre die nächste Frage, musst du wirklich alle Daten im Speicher behalten? In sehr vielen Fällen kann man Daten ja geschickt aufteilen oder gar in Datenbanken werfen. Damit kann man Daten dann auch mal auf der Platte lassen.

Speichern als Single und Rechnen als Double... Ich werde immer neugieriger ;)
 
Piktogramm schrieb:
Da wäre die nächste Frage, musst du wirklich alle Daten im Speicher behalten?
Ja wird sonst zu langsam, sind riesen Mengen Daten und stark parallele Zugriffe per Multithreading (aufm RAM läuft ne Simulation manchmal schon 4 Tage).
Piktogramm schrieb:
Speichern als Single und Rechnen als Double... Ich werde immer neugieriger ;)
Hehe finde ich gar nicht mal so unlogisch, das passiert wenn man RAM sparen will und die vectoren auf float reduziert und den Rest auf double lässt, aber ich rede mich mal raus, ich habe double als Endziel schon vorausschauend eingeplant ;)
Keine Angst es ist nicht die Steuerungssoftware der Boeing-737-Max :D
 
Zuletzt bearbeitet:
T_55 schrieb:
Die korrekte Antwort auf die digit-Frage ist wohl: 6 Stellen sind in einer float sicher aufgehoben:
Deine Schlussfolgerung ist falsch.
Du kannst dich nicht darauf verlassen, dass eine Zahl mit 6 Nachkommastellen im 10er System in einem float oder in einem double abbildbar ist:
https://www.exploringbinary.com/why-0-point-1-does-not-exist-in-floating-point/

Daher auch meine Frage: Wieso denkst du, dass die Daten in einem gültigen Format vorliegen? Ich wette, dass das nicht der Fall ist bei dir. Du wirst garantiert Daten einlesen, die nicht fehlerfrei in einen float oder in einen double reinpassen.

Ein HW Upgrade auf beliebig viel Ram und die Umstellung auf double löst dieses Problem nicht. Selbst wenn ihr von float auf double_256 oder double_1024 wechselt passt nichtmal 0,1 in eure Variablen.

Man muss seine SW so schreiben, dass sie damit klarkommt.
0,1 ist im 2er System genau wie 1/3 im 10er System eine Zahl, die sich nie mit einer endlichen Zahl von Nachkommastellen darstellen lässt.
 
T_55 schrieb:
7.15631 kommt dann 0.0715631 (Faktor 0.01) und schon liegt eine nötige Stelle an Digit 8 und es wird bei floats Murks gerechnet.
Ahhm, nein, da das eine 7.15631e0 und das andere 7.15631e-2 ist. Das ist die Idee hinter FLIESSkomma. So lange dein Exponent nicht überläuft -> Unendlich oder unterläuft (-> +-0) hast du immer 24 bit Nachkommastellen.

Bei Floats hat man daher die meisten Probleme bei der Addition von Zahlen mit großen Wertunterschieden. Multiplikation/Division sind da "robuster".
 
  • Gefällt mir
Reaktionen: Piktogramm
kuddlmuddl schrieb:
Wieso denkst du, dass die Daten in einem gültigen Format vorliegen?
Denk ich nicht (weil nicht alle von uns sind) deswegen will ich die Sicherheitsfunktion doch einbauen. Auch damit ich die manuelle Prüfung nicht machen muss abgesehen davon, dass Tabellensoftware oder Texteditoren abschmieren bei so riesen Dateien daher will ich es beim Einlesen abfangen.
kuddlmuddl schrieb:
Du kannst dich nicht darauf verlassen, dass eine Zahl mit 6 Nachkommastellen im 10er System in einem float oder in einem double abbildbar ist:
Will ich auch nicht ich hab geschrieben "6 Stellen" nicht 6 Nachkommastellen ;) Wenn ich es richtig verstanden habe kann man bei der Betrachtung "maximum number of digits guaranteed to round-trip through a float" das Komma ignorieren und von links nach rechts gelten einfach die "signifikanten Stellen" und bei float sind wohl 6 Stück garantiert.
https://de.wikipedia.org/wiki/Signi...kante_Stellen_einer_Zahl_mit_Nachkommastellen
Mit 6-stelliger Genauigkeit sind die signifikanten Stellen von links nach rechts gemeint.
http://openbook.rheinwerk-verlag.de...en_008.htm#mj84b7f33e0d8a8a4a2c05bcf0b04744f3
Hancock schrieb:
Ahhm, nein, da das eine 7.15631e0 und das andere 7.15631e-2 ist. Das ist die Idee hinter FLIESSkomma. So lange dein Exponent nicht überläuft -> Unendlich oder unterläuft (-> +-0) hast du immer 24 bit Nachkommastellen.
Wie gesagt ich will nicht die möglichen Nachkommastellen prüfen sondern die möglichen Stellen.
Das bei 12345 ein float nur noch eine "sichere" signifikante Nachkommastellen übrig bleibt ist klar und auch das mit 123456 gar keine übrig bleibt.

7.15631 -> Messwert hat 6 signifikanten Stellen -> float geht ok
0.0715631 -> Messwert hat 8 signifikanten Stellen -> float reicht nicht
 
Nein so funktioniert das nich, wie gesagt

0,1 hätte nach deiner Rechnung nur 1 oder 2 signifikante Stellen - passt aber trotzdem nicht in float oder double.

Nichtmal mit 1000 Stellen und auch nicht, wenn du doubles nutzt und auch nicht wenn du größere doubles hättest, die nicht 64bit sondern zb 1024*1024 bit haben

Stell dir 1/3 im 10er System vor, das klappt auch niemals (0,3333333333..)
1/10 ist eben so eine Zahl im 2er System
 
  • Gefällt mir
Reaktionen: new Account()

Ähnliche Themen

Zurück
Oben