Nicht intuitives Ergebnis der Zuweisung einer Zahl mit doppelter Genauigkeit zu einer int-Variablen in C

Lesezeit: 7 Minuten

Benutzeravatar von GeorgiD
GeorgiD

Kann mir jemand erklären, warum ich zwei unterschiedliche Nummern bekomme, bzw. 14 und 15 als Ausgabe des folgenden Codes?

#include <stdio.h>  

int main()
{
    double Vmax = 2.9; 
    double Vmin = 1.4; 
    double step = 0.1; 

    double a =(Vmax-Vmin)/step;
    int b = (Vmax-Vmin)/step;
    int c = a;

    printf("%d  %d",b,c);  // 14 15, why?
    return 0;
}

Ich erwarte in beiden Fällen 15, aber es scheint, dass mir einige Grundlagen der Sprache fehlen.

Ich bin mir nicht sicher, ob es relevant ist, aber ich habe den Test in CodeBlocks durchgeführt. Wenn ich jedoch dieselben Codezeilen in einen Online-Compiler eingebe ( diese zum Beispiel) bekomme ich eine Antwort von 15 für die beiden gedruckten Variablen.

  • Mögliches Duplikat von Gleicher FLT_EVAL_METHOD, unterschiedliche Ergebnisse in GCC/Clang

    – Jean-Baptiste Yunes

    28. Februar 2018 um 6:42 Uhr

  • Dies ist kein Duplikat von Same FLT_EVAL_METHOD, andere Ergebnisse in GCC/Clang, da die Antworten auf diese Frage nicht auf diese zutreffen.

    – ShreevatsaR

    28. Februar 2018 um 23:52 Uhr

chux – Stellt Monicas Benutzeravatar wieder her
Chux – Wiedereinsetzung von Monica

… warum ich zwei verschiedene Nummern bekomme …

Abgesehen von den üblichen Fließkommaproblemen sind die Berechnungspfade zu b und c kommen auf unterschiedliche Weise an. c wird berechnet, indem zuerst der Wert gespeichert wird als double a.

double a =(Vmax-Vmin)/step;
int b = (Vmax-Vmin)/step;
int c = a;

C ermöglicht die Berechnung von Zwischen-Gleitkomma-Mathematik mit breiteren Typen. Überprüfen Sie den Wert von FLT_EVAL_METHOD aus <float.h>.

Abgesehen von Zuweisung und Besetzung (die alle zusätzliche Reichweite und Präzision entfernen) …

-1 unbestimmbar;

0 wertet alle Operationen und Konstanten genau auf den Bereich und die Genauigkeit des Typs aus;

1 Operationen und Konstanten des Typs auswerten float und double auf die Reichweite und Präzision der double eingeben, auswerten long double
Operationen und Konstanten auf den Bereich und die Genauigkeit der long double
Typ;

2 Bewerten Sie alle Operationen und Konstanten auf den Bereich und die Genauigkeit der
long double Typ.

C11dr §5.2.4.2.2 9

OP berichtet 2

Durch Speichern des Quotienten in double a = (Vmax-Vmin)/step;Präzision wird dazu gezwungen double wohingegen int b = (Vmax-Vmin)/step; wie berechnen könnte long double.

Dieser feine Unterschied resultiert aus (Vmax-Vmin)/step (berechnet vielleicht als long double) wird als gespeichert double gegenüber verbleibenden a long double. Einer als 15 (oder knapp darüber) und der andere knapp unter 15. int Abschneiden verstärkt diesen Unterschied auf 15 und 14.

Auf einem anderen Compiler waren die Ergebnisse aufgrund von möglicherweise beide gleich FLT_EVAL_METHOD < 2 oder andere Fließkommaeigenschaften.


Umstellung auf int von einer Gleitkommazahl ist bei Zahlen in der Nähe einer ganzen Zahl schwerwiegend. Oft besser zu round() oder lround(). Die beste Lösung ist situationsabhängig.

  • danke für deine hilfe und erklärung! Als ich das jetzt getestet habe FTL_EVAL_METHOD Auf dem Online-Compiler, wo ich “erwartete” Antworten von 15 für die tho-Variablen erhalten habe, habe ich ein Ergebnis von 0. Die Lektion ist jedoch, dass ein Noob wie ich vorsichtig sein muss, wenn er zuerst so “einfach” ist Blick, Berechnungen 🙂

    – GeorgiD

    27. Februar 2018 um 17:17 Uhr

  • @GeorgiD Mit FTL_EVAL_METHOD == 0 Ich würde für beide das gleiche Ergebnis erwarten b,caber möglicherweise nicht 15, sondern 14. Wie viele, einschließlich @Steve Summit, vorschlagen, seien Sie vorsichtig bei der Konvertierung von FP in int– das gilt für uns alle, nicht nur für Lernende.

    – chux – Wiedereinsetzung von Monica

    27. Februar 2018 um 17:23 Uhr


cmaster - Benutzeravatar von Monica wiederherstellen
cmaster – monica wieder einsetzen

Dies ist in der Tat eine interessante Frage, hier ist, was genau in Ihrer Hardware passiert. Diese Antwort gibt die genauen Berechnungen mit der Präzision von IEEE wieder double Precision Floats, dh 52 Bit Mantisse plus ein implizites Bit. Einzelheiten zur Vertretung finden Sie unter Wikipedia-Artikel.

Ok, also definierst du zuerst einige Variablen:

double Vmax = 2.9;
double Vmin = 1.4;
double step = 0.1;

Die jeweiligen Werte in binär werden

Vmax =    10.111001100110011001100110011001100110011001100110011
Vmin =    1.0110011001100110011001100110011001100110011001100110
step = .00011001100110011001100110011001100110011001100110011010

Wenn Sie die Bits zählen, werden Sie sehen, dass ich das erste gesetzte Bit plus 52 Bits nach rechts gegeben habe. Das ist genau die Genauigkeit, mit der Ihr Computer a speichert double. Beachten Sie, dass der Wert von step wurde aufgerundet.

Jetzt rechnen Sie mit diesen Zahlen. Die erste Operation, die Subtraktion, ergibt das genaue Ergebnis:

 10.111001100110011001100110011001100110011001100110011
- 1.0110011001100110011001100110011001100110011001100110
--------------------------------------------------------
  1.1000000000000000000000000000000000000000000000000000

Dann teilst du durch stepdie von Ihrem Compiler aufgerundet wurde:

   1.1000000000000000000000000000000000000000000000000000
 /  .00011001100110011001100110011001100110011001100110011010
--------------------------------------------------------
1110.1111111111111111111111111111111111111111111111111100001111111111111

Aufgrund der Rundung von stepdas Ergebnis ist etwas darunter 15. Anders als früher habe ich nicht sofort gerundet, denn genau hier passiert das Interessante: Ihre CPU kann tatsächlich Gleitkommazahlen mit größerer Genauigkeit speichern als a doublesodass nicht sofort gerundet wird.

Also, wenn Sie das Ergebnis von konvertieren (Vmax-Vmin)/step direkt zu einem intIhre CPU schneidet einfach die Bits nach dem Bruchpunkt ab (so wird die implizite double -> int Konvertierung wird durch die Sprachstandards definiert):

               1110.1111111111111111111111111111111111111111111111111100001111111111111
cutoff to int: 1110

Wenn Sie das Ergebnis jedoch zunächst in einer Variablen vom Typ Double speichern, wird gerundet:

               1110.1111111111111111111111111111111111111111111111111100001111111111111
rounded:       1111.0000000000000000000000000000000000000000000000000
cutoff to int: 1111

Und das ist genau das Ergebnis, das Sie erhalten haben.

  • Großartig, jede Frage zu Gleitkommazahlen sollte konkrete Beispiele wie dieses haben.

    – ShreevatsaR

    28. Februar 2018 um 20:58 Uhr

  • “Beachten Sie, dass der Wert von Schritt aufgerundet wurde. Diese Rundung wird von den Sprachstandards vorgeschrieben.”. Alle 3 Vmax, Vmin, step Rundungen entstanden sind. step: hoch. Vmax, Vmin:Nieder. Dies sind Beispiele für Runden auf den nächsten. “Diese Rundung ist von den Sprachstandards vorgeschrieben.” Hmmm, lesen Sie §5.2.4.2.2. 6. Stattdessen ist die Genauigkeit und Rundungsrichtung/-modus ein implementierungsdefiniertes Verhalten. Verschiedene FP-Standards spezifizieren Round-to-Nearest als Standard-Rundungsmodus, aber nicht C. Dennoch entsprechen viele Plattformen IEEE 754 – oder fast so.

    – chux – Wiedereinsetzung von Monica

    28. Februar 2018 um 21:27 Uhr


  • @chux Korrigiere mich, wenn ich falsch liege, aber ich hatte den Eindruck, dieser Gleitkommawert Literale müssen auf den nächsten gerundet werden. Das Runden an anderen Stellen ist in der Tat implementierungsdefiniert, wie das Runden, das passiert, wenn das Ergebnis der Berechnung gespeichert wird.

    – Cmaster – Wiedereinsetzung von Monica

    28. Februar 2018 um 21:36 Uhr

  • @cmaster Sieht so aus, als wäre es immer noch ID-Verhalten. §6.4.4.2 7 „Die Übersetzungszeit-Konvertierung von Floating-Konstanten sollte mit der Ausführungszeit-Konvertierung von Zeichenfolgen durch Bibliotheksfunktionen wie strtod übereinstimmen, sofern übereinstimmende Eingaben, die für beide Konvertierungen geeignet sind, das gleiche Ergebnisformat und die Standard-Ausführungszeit gegeben sind Rundung” und Fußnote “Die Spezifikation für die Bibliotheksfunktionen empfiehlt eine genauere Umrechnung als für Gleitkommakonstanten erforderlich (siehe 7.22.1.3).” C ist in diesen Angelegenheiten ziemlich locker schwebende Konstanten.

    – chux – Wiedereinsetzung von Monica

    28. Februar 2018 um 21:48 Uhr


  • @chux Ok, ich habe den beleidigenden Satz entfernt.

    – Cmaster – Wiedereinsetzung von Monica

    1. März 2018 um 7:19 Uhr

Benutzeravatar von Steve Summit
Steve Gipfel

Die “einfache” Antwort ist, dass diese scheinbar einfachen Zahlen 2,9, 1,4 und 0,1 alle intern als binäre Gleitkommazahl dargestellt werden, und in binärer Form wird die Zahl 1/10 als der sich unendlich wiederholende binäre Bruch 0,00011001100110011 dargestellt …[2] . (Dies ist analog zu der Art und Weise, wie 1/3 in Dezimalzahlen zu 0,333333333 wird … .) Zurück in Dezimalzahlen umgewandelt, ergeben diese ursprünglichen Zahlen Dinge wie 2,8999999999, 1,3999999999 und 0,0999999999. Und wenn Sie zusätzliche Berechnungen anstellen, neigen diese 0,0999999999 dazu, sich zu vermehren.

Und dann besteht das zusätzliche Problem darin, dass der Pfad, auf dem Sie etwas berechnen – ob Sie es in Zwischenvariablen eines bestimmten Typs speichern oder “alles auf einmal” berechnen, was bedeutet, dass der Prozessor interne Register möglicherweise mit größerer Genauigkeit als den Typ verwendet double — kann am Ende einen erheblichen Unterschied machen.

Die Quintessenz ist, dass, wenn Sie a konvertieren double zurück zu einem intSie wollen fast immer runden, nicht abschneiden. Was hier passiert ist, war, dass (tatsächlich) ein Berechnungspfad Ihnen 15,0000000001 gab, was auf 15 gekürzt wurde, während der andere Ihnen 14,999999999 gab, was ganz auf 14 gekürzt wurde.

Siehe auch Frage 14.4a in dem C FAQ-Liste.

  • Um genau zu sein, wenn 1/10 war dargestellt als der sich unendlich wiederholende binäre Bruch 0.00011001100110011... (und wenn die Arithmetik mathematisch korrekt durchgeführt würde) gäbe es kein Problem, aber tatsächlich wird es als dieser binäre Bruch dargestellt, der auf eine bestimmte Anzahl von Ziffern gekürzt wird. (So ​​wie dezimal die Zahl 0.333333… mit einer unendlichen Ziffernfolge ist genau 1/3, aber wenn wir auf eine endliche Anzahl von Ziffern kürzen, erhalten wir so etwas wie 0.33333333333333 welches ist nicht genau 1/3.)

    – ShreevatsaR

    28. Februar 2018 um 20:54 Uhr


Benutzeravatar von Jean-Baptiste Yunès
Jean-Baptiste Yunes

Ein äquivalentes Problem wird in analysiert Analyse von C-Programmen für FLT_EVAL_METHOD==2.

Wenn FLT_EVAL_METHOD==2:

double a =(Vmax-Vmin)/step;
int b = (Vmax-Vmin)/step;
int c = a;

berechnet b durch Auswertung von a long double Ausdruck, der ihn dann auf a kürzt intwährend für c es wertet aus long doublees zu kürzen double und dann zu int.

Beide Werte werden also nicht mit demselben Prozess erhalten, und dies kann zu unterschiedlichen Ergebnissen führen, da Floating-Typen keine übliche exakte Arithmetik bieten.

1409650cookie-checkNicht intuitives Ergebnis der Zuweisung einer Zahl mit doppelter Genauigkeit zu einer int-Variablen in C

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy