Mal wieder Rechnen mit AVR

OP #2558307
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
...
#2558315
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.
Gast #2558426
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" :-)
#2558430
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.
Gast #2558442
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?
Gast #2558466
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.
#2558533
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.
Gast #2558624
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?
#2558639
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)
Gast #2559132
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.

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