Forum: Mikrocontroller und Digitale Elektronik GCC - Rundungsfehler ?


von Ralph S. (jjflash)


Lesenswert?

Weil ich nicht weiß, in welches Forum das gehört schreibe ich es mal 
hier her.

Folgendes habe ich mit (Linux) GCC, AVR-GCC und arm-none-eabi-gcc 
ausprobiert und habe jedesmal dein gleichen Fehler (die Controller waren 
ATMega644 und STM32F401 über serielle Schnittstelle angebunden). 
Original geht es um Programmierübungen für Auszubildende (2. Lektion die 
ich gerade vorbereite) und bin jetzt selbst am "verzweifeln" oder besser 
gesagt überrascht.

Die Problemstellung der Übung sollte sein, dass Displays immer mit 
Pixelangaben und den Zollangaben der Diagonalen versehen sind und man 
gern die Breite und Höhe für bspw. einen Einbau wissen würde.

Daraus sollte nun errechnet werden, wie breit das Display ist. Hieraus 
ergeben sich 2 Lösungsansätze, einmal über Trigonometrie und einmal über 
den Satz des Pythagoras:
1
    x = 128 pixel
2
   +-------------+
3
   |            /|
4
   |     d   /   |
5
   |      /      | y = 64 pixel
6
   |   /         |
7
   |/ alpha      |
8
   +-------------+
9
10
  d = 0,96 inch
11
12
Mit Taschenrechner ergibt sich folgendes:
13
--------------------------------
14
Variante 1 (Satz des Pythagoras)
15
--------------------------------
16
17
 d[pixel]= sqrt( (x^2) + (y^2) =  143,108 [pixel]
18
19
 v= 128 / 143,108 = 0,8944
20
21
 x[inch] = v * d = 0,8586
22
 x[cm] = 2,18
23
24
25
--------------------------------
26
Variante 2 (Trigonometrie)
27
--------------------------------
28
29
atan(alpha)= 64/128 = 26,565
30
31
x[inch]= cos(alpha) * d = 0.8586
32
 x[cm] = 2,18


Soooooweit so gut, nun habe ich das vorab auf einem PC umgesetzt und bin 
jetzt erstmal höchst erstaunt über folgendes Programm
1
  double wi, x, y, d, v, dinch;
2
  double binch;
3
4
  x= 128.0; y= 64,0; dinch= 0.96;
5
6
  printf("\n--------------------------------------------------");
7
  printf("\n              Displaydimensionen\n");
8
9
  printf("\n  Pythagoras");
10
  printf("\n  Diagonale [cm] : %.2f",dinch*2.54);
11
  printf("\n  Breite    [cm] : %.2f",(x / sqrt((x*x)+(y*y))) * 2.54   );
12
  printf("\n");
13
14
  printf("\n  Trigonometrie");
15
  wi= atan(y/x) * (180.0 / M_PI);
16
  printf("\n  Diagonale [cm] : %.2f",dinch*2.54);
17
  printf("\n  Breite    [cm] : %.2f",( cos(wi*M_PI/180) * (dinch*2.54)) );
18
  printf("\n");
19
20
  printf("\n  nochmal Pythagros");
21
  d= sqrt((x*x)+(y*y));
22
  v= x / d;
23
  binch= v*dinch;
24
25
  printf("\n  Diagonale [cm] : %.2f",dinch*2.54);
26
  printf("\n  Breite    [cm] : %.2f",binch*2.54);
27
28
29
  printf("\n");

Und die dazugehörende Ausgabe sieht wie folgt aus:
1
--------------------------------------------------
2
              Displaydimensionen
3
4
  Pythagoras
5
  Diagonale [cm] : 2.44
6
  Breite    [cm] : 2.27
7
8
  Trigonometrie
9
  Diagonale [cm] : 2.44
10
  Breite    [cm] : 2.18
11
12
  nochmal Pythagros
13
  Diagonale [cm] : 2.44
14
  Breite    [cm] : 2.18

Woher kommt nun die Abweichung bei der ersten Berechnung über Pythagoras 
(2,27).
Ich kann irgendwie nicht glauben, dass bei einfachem Wurzelziehen ein 
größerer Rundungsfehler auftritt als bei cos / tan Berechnungen.

Vor allen Dingen bin ich deswegen erstaunt, weil die zweite Berechnung 
über Zwischenspeichern in Variablen die Abweichung nicht hat.

Dann habe ich das mit LibreOfficeCalc mal versucht, das Feld B10 
entspricht x, B11 entspricht y:

=(B10/SQRT(B10*B10+B11*B11))*2.54

das Ergebnis hier ist 2.2718450651

Wo liegt nun mein Fehler ? (an Rundungsfehler will ich nicht glauben)

von Tassilo H. (tassilo_h)


Lesenswert?

Ralph S. schrieb:
> printf("\n  Pythagoras");
>   printf("\n  Diagonale [cm] : %.2f",dinch*2.54);
>   printf("\n  Breite    [cm] : %.2f",(x / sqrt((x*x)+(y*y))) * 2.54   );
>   printf("\n");

Bei Breite fehlt doch ein *dinch, daher der Fehler.

von Ralph S. (jjflash)


Lesenswert?

Ich bin sooooooooooooooooooooooo blöd, und Entschuldigung für die 
Störung, ich hätte mir den ganzen Text hier sparen können:

printf("\n  Breite    [cm] : %.2f",(x / sqrt((x*x)+(y*y))) * 
(dinch*2.54) );

von Ralph S. (jjflash)


Lesenswert?

Tassilo H. schrieb:
> Bei Breite fehlt doch ein *dinch, daher der Fehler.

jetzt ... weiß ichs leider auch ! Aber danke !

hrmpf und schäm !

von jo (Gast)


Lesenswert?

Mal %lf im Formatstring probiert? %f ist normalerweise für float. Ob es 
was ausmacht, ist natürlich sehr compiler- und prozessorabhängig...

von M. K. (sylaina)


Lesenswert?

Noch so ein kleiner Tipp am Rande:

Man kann zwar für den Atmega (AVR-GCC)
1
double myVar = 1234.56789;

schreiben, das wird aber immer ein float sein, sprich myVar hätte 
lediglich den Wert 1234.567 (wenn ich mich recht entsinne mit 7 Stellen 
Genauigkeit bei Float).

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

M. K. schrieb:
> das wird aber immer ein float sein

Um es zu präzisieren:
AVR-GCC verwendet für float und double den gleichen Datentyp; sizeof 
float == sizeof double == 4.

Es hat sich bislang niemand die Mühe gemacht, 
64-Bit-floatingpoint-Aritmetik auf den AVR zu portieren.

von Jan W. (jannyboy)


Lesenswert?

Ralph S. schrieb:
> Wo liegt nun mein Fehler ? (an Rundungsfehler will ich nicht glauben)

Du rundes nicht.
Sonder du kürzt letztendlich das Ergebnis auf 2 Nachkommastellen.
Zum runden muss man z.b. den Befehl round() vor der Ausgabe verwenden.

von Erwin D. (Gast)


Lesenswert?

Jan W. schrieb:
> Ralph S. schrieb:
>> Wo liegt nun mein Fehler ? (an Rundungsfehler will ich nicht glauben)
>
> Du rundes nicht.

Nein, das ist nicht der Fehler. Der Fehler wurde bereits ein paar 
Minuten nach der Fragestellung (kurz nach Mitternacht) erkannt.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

M. K. schrieb:
> Noch so ein kleiner Tipp am Rande:
>
> Man kann zwar für den Atmega (AVR-GCC)
>
>
1
double myVar = 1234.56789;
>
> schreiben, das wird aber immer ein float sein,

No, es ist kein float sondern ein nicht standardkonformer double. 
Ersichtlich z.B. daran, dass avr-g++ double als "d" mangled und float 
als "f".

von Erwin D. (Gast)


Lesenswert?

Johann L. schrieb:
> No, es ist kein float sondern ein nicht standardkonformer double.
> Ersichtlich z.B. daran, dass avr-g++ double als "d" mangled und float
> als "f".

Dann hat Rufus also unrecht?
Rufus Τ. F. schrieb:
> M. K. schrieb:
>> das wird aber immer ein float sein
>
> Um es zu präzisieren:
> AVR-GCC verwendet für float und double den gleichen Datentyp; sizeof
> float == sizeof double == 4.
>
> Es hat sich bislang niemand die Mühe gemacht,
> 64-Bit-floatingpoint-Aritmetik auf den AVR zu portieren.

von M. K. (sylaina)


Lesenswert?

Johann L. schrieb:
> No, es ist kein float sondern ein nicht standardkonformer double.
> Ersichtlich z.B. daran, dass avr-g++ double als "d" mangled und float
> als "f".

Einfach mal obiges Beispiel ausprobieren und anzeigen lassen, was bei 
rum kommt ;)

von Yalu X. (yalu) (Moderator)


Lesenswert?

Beim AVR-GCC haben float und double zwar die gleiche Größe und die
gleiche interne Darstellung, es sind aber formal trotzdem zwei
verschiedene Datentypen. Entsprechendes gilt bspw. auch für int und
short, die beim AVR-GCC beide vorzeichenbehaftete 16-Bit-Ganzzahlen,
aber trotzdem verschiedene Datentypen sind.

von jo (Gast)


Lesenswert?

>Beim AVR-GCC haben float und double zwar die gleiche Größe und die
>gleiche interne Darstellung, es sind aber formal trotzdem zwei
>verschiedene Datentypen. Entsprechendes gilt bspw. auch für int und
>short, die beim AVR-GCC beide vorzeichenbehaftete 16-Bit-Ganzzahlen,
>aber trotzdem verschiedene Datentypen sind.

Interessant.

Gruß J

von M. K. (sylaina)


Lesenswert?

Yalu X. schrieb:
> Beim AVR-GCC haben float und double zwar die gleiche Größe und die
> gleiche interne Darstellung, es sind aber formal trotzdem zwei
> verschiedene Datentypen. Entsprechendes gilt bspw. auch für int und
> short, die beim AVR-GCC beide vorzeichenbehaftete 16-Bit-Ganzzahlen,
> aber trotzdem verschiedene Datentypen sind.

So kenn ich es auch. Es sind zwar unterschiedliche Datentypen, haben 
aber die gleiche Größe was unterm Strich bedeutet, dass man damit das 
gleiche Ergebniss erhält. Ich wollte nur drauf hinweisen da ja z.B. 
Double eigentlich bedeutet, dass das ein Float mit doppelter Genauigkeit 
ist, im Falle des AVR-GCC ist dem aber nicht so, da ist ein Double eben 
nicht genauer als ein Float.

von Ralph S. (jjflash)


Lesenswert?

Schmunzeln muß, ich wollte jetzt keine Fiskussion über Datentypen 
anwerfen. Ursprünglich war das eh alles float und ich hatte die 
Datenrypen verändert.

Ich hatte einfach nur schlicht einen Programmierfehler den ich vor 
Blindheit nicht sah. Erfreulich für mich war, dass der Codeschnippsel 
für alle 3 Plattformen ohne Änderung übersetzt wurde....

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Ralph S. schrieb:
> Hieraus ergeben sich 2 Lösungsansätze, einmal über Trigonometrie und
> einmal über den Satz des Pythagoras:

Der Ansatz über "Trigonometrie" ist allerdings deutlich aufwändiger, 
sowohl was Rechenzeit als auch Codegröße angeht, und schlechter 
konditioniert ist er auch.

Offenbar gilt cos(atan(x)) = 1/(1+x²) so dass die 2. Formel ohne atan 
und cos geschrieben werden kann, und die Umrechnungen zwischen Altrgrad 
und Radians fällt auch weg.

Zur Berechnung der Hypothenusenlänge gibt's seit C99 übrigens hypot:

https://linux.die.net/man/3/hypot

von Rolf M. (rmagnus)


Lesenswert?

jo schrieb:
> Mal %lf im Formatstring probiert? %f ist normalerweise für float. Ob es
> was ausmacht, ist natürlich sehr compiler- und prozessorabhängig...

Nein. %f ist für double. float kann man an printf gar nicht übergeben, 
da bei variadischen Parametern float immer erst in double konvertiert 
wird.

von M. K. (sylaina)


Lesenswert?

Ralph S. schrieb:
> Ursprünglich war das eh alles float und ich hatte die
> Datenrypen verändert.

Warum hast du sie denn verändert, hilft doch beim AVR eh nicht.

von Ralph S. (jjflash)


Lesenswert?

M. K. schrieb:
> Warum hast du sie denn verändert, hilft doch beim AVR eh nicht.

Weil ich so dämlich war und den logischen Fehler nicht gesehen hatte, 
also hab ich geguckt wie es sich mit unterschiedlichen Datentypen 
verhält.

Letztlich ist es halt schlicht MEIN Programmierfehler gewesen, mehr 
nicht !
noch mal lächeln muss : Verrückt, dass hieraus ein längerer Thread 
entsteht als es muss...

von Ralph S. (jjflash)


Lesenswert?

Johann L. schrieb:
> Der Ansatz über "Trigonometrie" ist allerdings deutlich aufwändiger,
> sowohl was Rechenzeit als auch Codegröße angeht, und schlechter
> konditioniert ist er auch.

Das ... wäre das Ziel der Lerneinheit gewesen... die ich nun doch nicht 
mache, irgendwie ist mir mein Fehler schlicht peinlich, dass ich ein 
anderes Beispiel verwenden werde...

von (prx) A. K. (prx)


Lesenswert?

Ralph S. schrieb:
> x= 128.0; y= 64,0; dinch= 0.96;
                 ^
Glück gehabt, weil da eine 0 folgt.
Wäre mit Warnings wohl aufgefallen.

von Jacko (Gast)


Lesenswert?

Da hier alle nur dusslig am schnell gefundenen Ergebnis
vorbei labern, müsste ich natürlich noch eine platzsparende
ASM-Festkomma-Alternative anzubieten!

Mach ich aber nicht. Ätsch.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.