Forum: Mikrocontroller und Digitale Elektronik Verständnis Problem mit AVR und Float


von Peter (Gast)


Lesenswert?

Hallo Gemeinde,
ich habe da ein Problemchen beim Rechnen mit AVR und Float.
Ich möchte eine Zahl z.b. 56 in ihre Einzelteile zerlegen also in die 5 
und die 6.
Also dachte ich mich, ich baue aus der 56 ein Float also 56.00. Dann 
Teile ich die 56 durch 10. Erhalte da 5.6. Als int-Variable also eine 5. 
Nun ziehe auch von den 5.6 den int-Wert als Float ab. Also 5.6 - 5.0. 
Erhalte da 0.6. Diese Rechne ich nun mal 10. Erhalte da 6.0 So weit so 
gut. Nun möchte ich aber aus diesem Float wieder ein Int bilden.  Aber 
egal wie ich es probiere, ich erhalte immer eine 5? Was ist da los?
1
int Wert1;
2
int Wert2;
3
float WertZwischen;
4
float WertZwischen2;
5
6
void setup()
7
{
8
Serial.begin(9600);
9
}
10
11
void loop()
12
{
13
Wert = 56;
14
WertZwischen = (float)Wert;
15
Wert /= 10; // Wert ist nun 5
16
Serial.Println(Wert);
17
WertZwischen2 = (float)Wert;
18
WertZwischen -= WertZwischen2; // WertZwischen ist nun 0.6
19
WertZwischen += 10.00; // WertZwischen ist nun 6.00
20
Serial.Println(WertZwischen); // Kontrolle dass WertZwischen auch 6.00 ist
21
Wert2 = (int)WertZwischen; //Wert Sollte jetzt 6 sein ist aber 5!
22
Serial.Println(Wert2);
23
while(1)
24
{}
25
}

von Fabian O. (xfr)


Lesenswert?

Keine gute Idee. Bei float können ganz schnell Rundungsfehler auftreten, 
die dann zu solchen Ergebnissen führen. Außerdem kosten die Routinen auf 
dem AVR viel Programmspeicher und Rechenzeit.

Was Du brauchst ist vermutlich der Modulo-Operator (%):
1
int wert = 56;
2
int zehner = wert / 10; // 5
3
int einer  = wert % 10; // 6

von Andreas B. (andreas_b77)


Lesenswert?

Peter schrieb:
> WertZwischen -= WertZwischen2; // WertZwischen ist nun 0.6

WertZwischen ist jetzt 51. Oder da fehlt einiges an Code.

Abgesehen davon, wozu float? Das geht doch alles einfach in int.
1
int wert = 56;
2
int zehner, einer;
3
4
einer = wert % 10;
5
zehner = wert / 10;

von Peter (Gast)


Lesenswert?

Probier ich morgen gleich mal aus aber klingt ja Super :) Danke

von Falk B. (falk)


Lesenswert?


von Yalu X. (yalu) (Moderator)


Lesenswert?

1
(56.0 / 10.0 - 5.0) * 10.0

ist – in 32-Bit-FP gerechnet – leider nicht 6, sondern
1
5.99999904632568359375

In int umgewandelt, ergibt dies 5.

Abhilfe:

Runden mit der round-Funktion vor der Umwandlung in int.

Oder besser: Alle Berechnungnen in Integer durchführen, es gibt dafür 
die ganzzahlige Division '/' und die Modulofunktion '%' (zur Berechnung 
des Divsionsrests). Siehe Beiträge von Fabian und Andreas.

Oder du nutzt die Funktion itoa, die eine Integer-Zahl in einen String 
aus Ziffern umwandelt.

von Peter (Gast)


Lesenswert?

Yalu X. schrieb:
> ist – in 32-Bit-FP gerechnet – leider nicht 6, sondern
> 5.99999904632568359375


Danke für die schnelle Hilfe. Aber wie kommt man auf die Lösung oben?

von Fabian O. (xfr)


Lesenswert?

Peter schrieb:
> Yalu X. schrieb:
>> ist – in 32-Bit-FP gerechnet – leider nicht 6, sondern
>> 5.99999904632568359375
>
> Danke für die schnelle Hilfe. Aber wie kommt man auf die Lösung oben?

Kannst Du einfach am PC ausprobieren:
1
#include <stdio.h>
2
3
int main(void)
4
{
5
  float x = 56.0;
6
  x /= 10.0;
7
  x -= 5.0;
8
  x *= 10.0;
9
  printf("Ergebnis: %.20f\n", x);
10
}

Ausgabe:
1
Ergebnis: 5.99999904632568359375

Die Berechnung oben geht übrigens auch ohne Modulo:
1
int wert = 56;
2
int zehner = wert / 10;          // 5
3
int einer  = wert - zehner * 10; // 6
Merke: In den meisten Mikrocontroller-Programmen kommt man sehr gut ohne 
float aus. In diesem Fall braucht man noch nicht mal Kommazahlen.

von Peter (Gast)


Lesenswert?

So gehts auch :) Ich muss wohl mehr denken ab und an :D
DANKE

von Werner (Gast)


Lesenswert?

Fabian O. schrieb:
> Kannst Du einfach am PC ausprobieren:

Ach, kann man jetzt mit float 20 Nachkommastellen darstellen und 
anzeigen?
Selbst mit double wäre diese printf noch reiner Mumpitz.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Fabian O. schrieb:
> Peter schrieb:
>> Yalu X. schrieb:
>>> ist – in 32-Bit-FP gerechnet – leider nicht 6, sondern
>>> 5.99999904632568359375
>> Danke für die schnelle Hilfe. Aber wie kommt man auf die Lösung oben?
>
> Kannst Du einfach am PC ausprobieren:

Genau das habe ich auch gemacht.

Wer's im Detail nachrechnen will:

32-Bit-FP-Zahlen haben nach IEEE 754 eine Mantissengenauigkeit von 24 
Bit (davon werden allerdings nur 23 Bit gespeichert, da das 
höchstwertige Bit auf Grund der Normierung immer 1 und damit ohne 
Informationsgehalt ist).

Im Folgenden sind einzelnen Rechenschritte im Dualsystem dargestellt:
1
r1 = 56      = 111000.000000000000000000
2
r2 = r1 / 10 =    101.100110011001100110011       Ergebnis wird auf 24 Stellen gerundet
3
r3 = r2 - 5  =       .100110011001100110011000    Ergebnis wird auf 24 Stellen aufgefüllt
4
r4 = r3 * 10 =    101.111111111111111111110
5
6
             = 6 - 2 ** (-20) = 5.99999904632568359375

Jedes Zwischenergebnis wird entweder auf 24 Stellen gerundet oder mit 
Nullen auf 24 Stellen aufgefüllt. Der Rundungsfehler entsteht im zweiten 
Schritt, da 56/10 im Dualsystem eine nichtabbrechende periodische 
Darstellung hat.

Werner schrieb:
> Ach, kann man jetzt mit float 20 Nachkommastellen darstellen und
> anzeigen?

Klar kann man das.

> Selbst mit double wäre diese printf noch reiner Mumpitz.

Normalerweise ja. Aber hier geht es ja gerade darum, den Rundungsfehler 
aufzuzeigen. Und den sieht man erst, wenn man mehr Stellen anzeigen 
lässt, als es der Rechengenauigkeit entspricht.

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.