Forum: Compiler & IDEs Mal wieder Rechnen mit AVR


von Alter F. (alter_falter)


Lesenswert?

Hallo Alle,

stehe hier gerade wie der Ochs vor'm Berg:
Wenn ich dieser Funktion einen Wert von 0.970 übergebe, dann bekomme ich 
für val auch 0.970 ausgegeben, für tmp jedoch 969.
Mega644,AVR Gcc 4.3.2, Avr Libc 1.6.6.
Woher kommt denn die Ungenauigkeit?

1
(void) foobar(double val)
2
{
3
int16_t tmp;
4
char tmpstr[10];
5
6
dtostrf(val,5,3,tmpstr);            
7
uputs(tmpstr);
8
9
tmp=(val*1000);
10
itoa(tmp,tmpstr,10);              
11
uputs(tmpstr)
12
13
14
...

von Peter II (Gast)


Lesenswert?

val*1000 ist vermutlich 969.99999999999999999999999

dann musst du schon runden, sonst wird nur abgeschnitten.

von Klaus W. (mfgkw)


Lesenswert?

Nicht jede gebrochene Zahl lässt sich exakt als Gleitkommazahl 
darstellen.

Deine 0.970 sind intern z.B. 0.969998 oder sowas.
Beim Multiplizieren mit 1000 wird dann im Beispiel 969.998 daraus, und 
beim Umwandeln in die ganze Zahl dann der Rest abgeschnitten.

Tröste dich: es sind schon mehr Leute darauf hereingefallen.

von Klaus W. (mfgkw)


Lesenswert?


von Alter Falter (Gast)


Lesenswert?

Danke Boys!

Habe mir fast schon gedacht, dass es ein Rundungsfehler ist.
Hätte nur nicht vermutet, daß sowas bei einer derart leichten 
Multiplikation bereits auftritt.

KW: danke für den Link. Wieder ein neues Studienobjekt für die 
"Einmann-Bibliothek" :-)

von Karl H. (kbuchegg)


Lesenswert?

Alter Falter schrieb:
> Danke Boys!
>
> Habe mir fast schon gedacht, dass es ein Rundungsfehler ist.
> Hätte nur nicht vermutet, daß sowas bei einer derart leichten
> Multiplikation bereits auftritt.

Das Proplem ist NICHT die Multiplikation.
Das Problem ist das Ausgangsmaterial. Wo kommen die 0.970 her und sind 
die überhaupt mit dem verwendeten Floating Point Verfahren darstellbar?

Du kannst ja hier
1
dtostrf(val,5,3,tmpstr);            
2
uputs(tmpstr);

einfach mal mehr Kommastellen anfordern. Das Ergebnis dürfte dich 
überraschen. Aus deinen 0.970 sind dann ganz schnell 0.96999998 
geworden.

(Buffergröße anpassen nicht vergessen).


Aus genau dem Grund weist man einen Float nicht einfach an einen int zu. 
Aus genau solchen Gründen werden Float niemals mittels == auf Gleichheit 
geprüft. Aus genau solchen Gründen muss man bei Float immer ein der 
Situation angepasstes Epsilon einplanen.

Arbeiten mit Floating Point ist eben ein bischen mehr als einfach nur 
den Datentyp 'float' oder 'double' zu benutzen und sich ansonsten nicht 
weiter darum zu kümmern.

von Alter Falter (Gast)


Lesenswert?

Habs gerade selbst schon gemerkt, als ich den Beitrag nochmals 
durchgelesen habe.

Mein Ausweg wäre dann vermutlich das Ergebnis der Multiplikation in 
einer float Variablen zu speichern, diese dann zu runden und erst dann 
in eine int umwandeln.

Sehe ich das richtig?

von Alter Falter (Gast)


Lesenswert?

Hmmm - es geht noch einfacher:
Der Wert von var kommt ursprünglich aus einer manuellen Eingabe.
Dort ist es ja noch ein ascii String. Da könnte ich ja einfach den 
Dezimalpunkt rausoperieren, dann habe ich ja schon mein int. Der 
Wertebereich von var liegt nämlich nur zwischen 0.100 und 1.100.

von Klaus W. (mfgkw)


Lesenswert?

... und wenn du das mit allen Gleitkommaoperationen ähnlich hinbekommst, 
ist das Programm auf einmal schnell und klein.

von Karl H. (kbuchegg)


Lesenswert?

Alter Falter schrieb:
> Habs gerade selbst schon gemerkt, als ich den Beitrag nochmals
> durchgelesen habe.
>
> Mein Ausweg wäre dann vermutlich das Ergebnis der Multiplikation in
> einer float Variablen zu speichern, diese dann zu runden und erst dann
> in eine int umwandeln.

Ist aber ein bischen umständlich. Was soll dir die Zwischenvariable 
bringen, was du nicht auch gleich mit dem Ergebnis der Berechnung machen 
kannst?

Man macht sich eine Funktion round(), die die Rundung durchführt.
Wenn deine Zahlen sowieso nur positiv sein können ist das leicht (unter 
der Annahme, dass das Ergebnis in den Zieldatentyp passt)

uint16_t round_u16( float value )
{
  return (uint16_t)( value + 0.5 );
}

Wenns auch negativ sein kann

int16_t round_i16( float value )
{
  if( value < 0.0 )
    return (int16_t)( value - 0.5 );

  return (int16_t)( value + 0.5 );
}

und damit schreibt es sich dann als

  tmp = round16_t(val*1000);



Aber deine andere Lösung, unter Verzicht auf Floating Point, ist 
sicherlich die bessere Lösung.

von Alter Falter (Gast)


Lesenswert?

Wie? So einfach geht das?
Da fallen einem ja alle Schuppen von den Augen.

Wenn man die Gesetzmässigkeiten erst mal durchschaut hat wird alles 
logisch.

Wie verhält es sich bei Deinem Beispiel, wenn das Ergebnis auf genau 
x.500 fällt?
Wird bei der Typenumwandlung immer nur abgerundet?

von Karl H. (kbuchegg)


Lesenswert?

Alter Falter schrieb:

> Wie verhält es sich bei Deinem Beispiel, wenn das Ergebnis auf genau
> x.500 fällt?

Dann hast du dort genau das gleiche Problem. Ist 24.499999 nun ein 
Rechenfehler und sollte 25 sein, oder stimmt das schon und das Ergebnis 
sollte 24 sein.
Aber interessanterweise ist dieses Problem weit weniger häufig.

> Wird bei der Typenumwandlung immer nur abgerundet?

abgeschnitten! nicht gerundet.
(OK, man kann abschneiden als abrunden in Richtung 0 auffassen)

von numerischMathematik (Gast)


Lesenswert?

Klaus Wachtler schrieb:
> http://docs.oracle.com/cd/E19957-01/806-3568/ncg_g...

Vorsicht! Es sind schon Leute für das runterladen solcher allgemein 
bekannter Informationen von Larry verklagt wurden. Da hat doch mal 
jemand erfahren, wie die Schaltjahrs-Berechnung geht, und schon 
verknackt.
Lieber Wikkipedia fragen. Da wirst Du frei geholfen.

von Alter F. (alter_falter)


Lesenswert?

Karl-Heinz: Vielen Dank für Deine unendliche Geduld und Deine 
hervorragenden Erklärungen.

Das Ganze scheint mir ähnlich schwer gelagert zu sein, wie die Existenz 
der schwarzen Materie.

Werde also Plan B und die Ascii Lösung angreifen.


Bye

von Klaus W. (mfgkw)


Lesenswert?

numerischMathematik schrieb:
> Klaus Wachtler schrieb:
>> http://docs.oracle.com/cd/E19957-01/806-3568/ncg_g...
>
> Vorsicht! Es sind schon Leute für das runterladen solcher allgemein
> bekannter Informationen von Larry verklagt wurden.

Beispiel?

von Matthias (Gast)


Lesenswert?

Alter Falter schrieb:
> Werde also Plan B und die Ascii Lösung angreifen.

Auch dann hast du das Problem, dass du 0.2399999 vielleicht als 240 
wiedersehen möchtest. Wenn du ein ganz winziges bisschen, i.A. epsilon 
genannt, dass dem halben Wert deiner kleinsten Ausgabestelle entsprich, 
hinzuaddierst, passiert dir nicht, dass wie in dem Beispiel 239 
herauskommt.

von EGS_TI (Gast)


Lesenswert?

Wie genau ist denn überhaupt dein "val"-Wert?

von Klaus W. (mfgkw)


Lesenswert?

Matthias schrieb:
> Auch dann hast du das Problem, dass du 0.2399999 vielleicht als 240
> wiedersehen möchtest.

Er hat doch einen String mit der Eingabe.
Wenn er darin das Komma manipuliert bzw. weglässt, hat er das Problem 
doch gar nicht.

von Alter Falter (Gast)


Lesenswert?

Update:

Die Ascii Behandlung des Dezimalpunktes und spätere Weiterbehandlung des 
daraus entstandenen Integer Wertes funktioniert wunderbar.
Problem halbwegs elegant gelöst :-)

von Klaus W. (mfgkw)


Lesenswert?

Alter Falter schrieb:
> Problem halbwegs elegant gelöst

Nicht immer untertreiben.

Du hast den Standard "ACD" (ASCII coded decimal) eingeführt.

von Alter Falter (Gast)


Lesenswert?

;-)  THX!

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.