[C] float-Variable inkrementiert falsch

Herakles

Profifragensteller
Moin!

Wenn ich eine float-variable inkrementiere, geschieht das ab einem bestimmten Punkt nicht sauber. Das Beweisprogramm ist denkbar einfach:

Code:
#include <stdio.h>
int main(void) {
float j;
for(j=0; j<2; j+=0.01) {
  printf("%f\n",j);
}
  exit(0);
}

Der Output gibt ab einem gewissen Punkt ein nicht erwartetes Ergebnis:

Code:
(...)
0.790000
0.800000
0.810000
0.820000
0.830000
0.839999
0.849999
0.859999
0.869999
0.879999
(...)

Noch viel lustiger wird es, wenn man die Schleife beträchlich länger laufen lässt:

Code:
(...)
1169.609863
1169.619873
1169.629883
1169.639893
(...)

Dass sich mit sowas nicht arbeiten lässt, dürfte einleuchten. Woher kommt dieser Fehler und wie kann man ihn umgehen oder beheben? Ist das ein bekanntes Phänomen?

[edit]
Wenn ich als Varibalentyp einen "double" nehme, funktioniert's...
[/edit]

Grüße
Herakles
 
Das duerfte wohl daran liegen, wie Gleitkommazahlen am Computer dargestellt werden. Diese werde gerade nicht in der dort angezeigten Dezimalschreibweise gespeichert; daher kann's sein, dass einige Zahlen nicht exakt dargestellt werden, wenn du z.B. 0,2 verwendest wird nicht genau 0,2 gespeichert, sondern halt die naechste Maschinenzahl die am naechsten an 0,2 liegt. Zudem liegen die Zahlen dichter um 0 und fuer groessere Zahlen wird dann auch der Fehler groesser. Siehe auch http://de.wikipedia.org/wiki/Maschinenzahl . Mit double werden halt hoechstens die Symptome etwas verdeckt.

Ich weiss nicht was du vorhattest, aber normalerweise schaut man halt, ob's "gut genug" ist. Ansonsten muss man halt exakt rechnen, d.h. notfalls mit rationalen Zahlen in libgmp oder sowas.
 
Man benutzt generell keine Gleitkommazahlen in Schleifen. Das kann noch ganz andere sonderbare Effekte haben. Stattdessen versuch lieber sowas wie:

Code:
int j;

for (j = 0; j < (2 * 100); ++j) {
    printf("%f\n", j / 100.0f);
}
 
Eigentlich rechnet man immer erstmal nur mit Integer, später im Code wird dann entsprechend in Float umgewandelt.
 
Interessant...

Ich schaue mir gerade libgmp an, das ist echt ein spannendes Thema. Man lernt eben nie aus.

Danke für Eure Hinweise!

Herakles
 
Moin!

Ich denke, folgende Beobachtung passt mit in die bereits genannten Beobachtungen hinein, überraschen mich dennoch.

Man nehme diesen C-Code:

Code:
#include <stdint.h>
#include <stdio.h>

int main() {
float i = 4.6;
uint16_t g= i*100;
printf("%d\n",g);
return 0;
}

Der Output ist "460". Also wie zu erwarten.

Nimmt man aber statt der float Variable eine double, also so:

Code:
#include <stdint.h>
#include <stdio.h>

int main() {
double i = 4.6;
uint16_t g= i*100;
printf("%d\n",g);
return 0;
}

, dann ist der Output "459".

Warum?

Grüße
Herakles
 
Ich hab mal versucht, das ganze nachzuvollziehen:

Code:
#include <stdint.h>
#include <stdio.h>

int main() {
  float i = 4.6;
  double j = 4.6;
  printf("%.15e %.15e\n", i, j);
  uint16_t g= i*100;
  uint16_t h= j*100;
  printf("%.15e %.15e\n", i*100, j*100);
  printf("%d %d\n", g, h);
  return 0;
}

Das liefert bei mir den Output:

Code:
4.599999904632568e+00 4.600000000000000e+00
4.600000000000000e+02 4.599999999999999e+02
460 459

Schon komisch. Double ist beim zuweisen von 4.6 noch genauer als float, nachdem multiplizieren aber ungenauer. Beim impliziten Casten zu int wird halt einfach abgeschnitten, was dann eben die Differenz erklaert.

EDIT:

Wenn ich beim Multiplizeren das hier verwende, ist das Ergebnis beide Male 459:

Code:
  uint16_t g= i*100.0;
  uint16_t h= j*100.0;
 
das haengt mit der internen repraesentierung von double und float zusammen.
im speicher steht nicht 460, sondern irgendwie so etwas wie
Code:
1.796875*2^8
verschluckt der jetzt am ende der mantisse ein bit, dann wird das ganze schnell
Code:
1.796874*2^8
oder 459.999744.
das casten hat wahrscheinlich andere regeln als das %.15e im printf befehl. da wird an einer stelle auf- an der anderen abgerundet.

(ich habs mir jetzt einfach gemacht, hab bei dem schoenen wetter gerade keine lust das mathematisch zu begruenden :p )




fazit: ganz egal ob double oder float. in sauberem, stabilem, bitgenauen code hat das ganze nichts verloren. da ist fix-punkt besser.
 
nicht jeder programmierer ist informatiker?
und nicht jeder informatiker ist programmierer.

einer von meinen ex-kollegen, dumm wie toast. der hatte mit vorliebe null-pointer an eine funktion uebergeben, 64kbyte daten auf einen 512byte heap gelegt, 16 bit variablen an einen 32 bit pointer uebergeben. fehlercodes ignoriert, sich gewundert warum sein code abschmiert. und sich dann auch noch beschwert dass ich das nicht vernuenftig getestet habe.
 
Nun gut... Dennoch hat sicherlich nicht jeder Informatik studiert der später mal ein Programm schreiben muss oder will. Zudem ist das evtl. vohandene Studium des Einen oder Anderen evtl. auch länger als zwei Jaher her und hat diese Details evtl nicht mehr so parat.
Löst euch mal davon, dass hier alles Informatiker sind.
Also kurz und knapp. Wenn einer was net weiss dann soll er fragen. Dafür is das Forum ja da. ;)
 
Zurück
Oben