2017-12-21 53 views
4

Ich verstehe völlig die Probleme mit Fließkommazahlen, aber ich habe ein sehr interessantes Verhalten gesehen, das ich nicht erklären kann.Ausweichen der Ungenauigkeit einer Gleitkommazahl

float x = 1028.25478; 
long int y = 102825478; 
float z = y/(float)100000.0; 
printf("x = %f ", x); 
printf("z = %f",z); 

Der Ausgang ist:

x = 1028,254761 z = 1028,254780

Nun, wenn schwimmende Zahlen, die bestimmten Zufallswert darzustellen fehlgeschlagen (1028,25478), wenn ich das variable x zugewiesen . Warum ist es bei Variablen z nicht dasselbe?

P.S. Ich benutze pellesC IDE, um den Code zu testen (C11 Compiler).

+0

Ihr Code [druckt das gleiche für mich] (https://ideone.com/9zyYyF). – dasblinkenlight

+0

Versuchen Sie, einige weitere Dezimalstellen zu drucken, z. '"% .10f "'. –

+0

Dies ist nicht das Duplikat. Ich denke, es gibt mehrere tatsächliche Duplikate, aber sie sind schwer zu finden. Grundsätzlich führt der Compiler im zweiten Fall Mathematik als "double" aus. – dasblinkenlight

Antwort

5

Ich bin ziemlich sicher, dass was passiert hier ist, dass die letztere Gleitkomma-Variable ist elided und stattdessen in einem Register mit doppelter Genauigkeit gehalten; und dann als Argument an printf weitergegeben. Dann wird der Compiler glauben, dass es sicher ist, diese Nummer nach Standard-Argument-Promotions mit doppelter Genauigkeit zu übergeben.

I gelungen, eine ähnliche Ergebnis mit GCC 7.2.0, mit diesen Schaltern zu erzeugen:

-Wall -Werror -ffast-math -m32 -funsafe-math-optimizations -fexcess-precision=fast -O3 

Der Ausgang ist

x = 1028.254761 z = 1028.254800 

Die Zahl ist etwas anders^es.

Die description for -fexcess-precision=fast sagt:

-fexcess-precision=style

Diese Option weitere Kontrolle über überschüssige Präzision auf Maschinen ermöglicht, wo Gleitkommaoperationen in einem Format auftreten mit mehr Präzision oder der Bereich als dem IEEE-Standard und Austausch Fließkommatypen. Standardmäßig ist -fexcess-precision=fast in Wirkung; Dies bedeutet, dass Operationen in einer breiteren Präzision durchgeführt, als die Typen in der Quelle angegeben werden, wenn das wäre Ergebnis in schnellerem Code, und es ist nicht vorhersehbar, wenn sie die Typen angegeben im Quellcode erfolgt Rundung. Wenn C kompiliert wird, wenn -fexcess-precision=standard angegeben ist, dann folgt Überschreitung der Genauigkeit, die in ISO C99 angegeben ist; in Insbesondere verursachen beiden Würfe und Zuweisungen Werte zu ihren semantischen Typen gerundet werden (während -ffloat-store betrifft nur Zuweisungen). Diese Option [-fexcess-precision=standard] ist standardmäßig für C aktiviert, wenn eine strikte Konformitätsoption wie -std=c99 verwendet wird. -ffast-math aktiviert -fexcess-precision=fast standardmäßig unabhängig davon, ob eine strikte Konformitätsoption verwendet wird.

Dieses Verhalten

+0

Das macht sehr viel Sinn für mich, aber ich bin mir sicher, dass es C11 ist. –

+0

Auch die CCFlags sind: -Std: C11 -Tx86-Coff -Ot -Ob1 -fp: präzise -W1 -Gd –

+0

ja, so muss es ein Fall von schlechten Compiler sein. Kannst du die * Assembler-Ausgabe * von Pelle bekommen? –

2

Beschränkung dieser auf IEEE754 strengen Floating-Point nicht C11-kompatibel ist, die Antworten sollte gleich sein.

1028.25478 ist eigentlich 1028.2547607421875. Das ist x.

In der Auswertung von y/(float)100000.0; wird y in eine float konvertiert, durch C Regeln der Argument-Förderung. Der nächste float bis 102825478 ist 102825480. IEEE754 erfordert die Rückgabe des besten Ergebnisses einer Division, die 1028.2547607421875 (der Wert z) sein sollte: die nächste Nummer 1028.25480.

Also meine Antwort ist im Widerspruch zu Ihrem beobachteten Verhalten. Ich übertrage das auf Ihren Compiler, der Gleitkomma nicht strikt implementiert; oder vielleicht nicht IEEE754 implementieren.

+1

Und natürlich erfordert C keine Implementierungen, um strikt oder überhaupt an IEEE-754 zu halten. In der Praxis verwenden jedoch im Wesentlichen alle Implementierungen für moderne Allzweckplattformen tatsächlich IEEE-754-Formate und zumindest eine annähernde IEEE-754-Semantik. Viele * * * halten sich zumindest standardmäßig nicht streng an IEEE-754. –

2

Der Code verhält sich so, als wäre z ein double und y/(float)100000.0 ist y/100000.0.

float x = 1028.25478; 
long int y = 102825478; 
double z = y/100000.0; 

// output 
x = 1028.254761 z = 1028.254780 

Eine wichtige Überlegung ist FLT_EVAL_METHOD. Dies ermöglicht es, einen Gleitkomma-Code mit höherer Genauigkeit auszuwerten.

#include <float.h> 
#include <stdio.h> 
printf("FLT_EVAL_METHOD %d\n", FLT_EVAL_METHOD); 

Außer Zuordnung und Guss ..., die von den Betreibern ergaben Werte mit schwimmendem Operanden und Werte unterliegen den üblichen arithmetischen Umwandlungen und schwimmender Konstanten werden in ein Format, deren Reichweite und Genauigkeit ausgewertet können größer sein als vom Typ gefordert. Die Verwendung von Bewertungsformaten ist durch den implementierungsdefinierten Wert FLT_EVAL_METHOD gekennzeichnet.

-1 unbestimmbar;
0 alle Operationen und Konstanten nur auf den Bereich und die Genauigkeit des Typs auswerten;
1 bewerten ... Art float und double zur Reichweite und Präzision des double Typs bewertet long double ... auf den Bereich und die Genauigkeit des long double Typs;
2 bewerten alle ... auf den Bereich und die Präzision der long double Art.

Doch dies tut nicht gelten als z mit float z = y/(float)100000.0; sollten alle eine höhere Präzision auf die Zuordnung verlieren.


Ich stimme mit @Antti Haapala, dass Code, der eine Geschwindigkeitsoptimierung verwendet, die weniger Einhaltung der erwarteten Regeln der Gleitkommamathematik hat.