GCC - Rundungsfehler ?

OP #4805399
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)
#4805519
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).
Gast #4805715
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.
Gast #4805771
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
#4805791
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.
OP #4806662
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....
Persönliche Seite #4806698
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
OP #4808341
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...
OP #4808343
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...

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren