Mal wieder Rechnen mit AVR

OP #2558307
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
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
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
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
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
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
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
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
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
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.
Gast #2559665
Update:

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

Antwort schreiben

Eine eigene Frage? Dieser Thread ist über 14 Jahre alt. Stell sie in einem eigenen Thread – dort lesen mehr Leute mit, und sie wird öfter und schneller beantwortet.

Neuen Thread starten

Eine Lösung, Ergänzung oder Rückmeldung zu diesem Thread? Dann antworte hier.

Formatierung
  • [c]C-Code[/c]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] – Link zu einem Artikel
  • Verweis auf einen anderen Beitrag: Rechtsklick auf dessen Beitrags-ID (#8078628), „Adresse kopieren“, in den Text einfügen

Mehr zur Formatierung

Längeren Sourcecode als Anhang, nicht in den Text. JPG nur für Fotos und Scans; Zeichnungen und Screenshots als PNG oder GIF. Siehe Bildformate.

Schon registriert? Anmelden

Wird nicht angezeigt. Wir schicken dir einen Link; sobald du damit bestätigst, erscheint dein Beitrag.

Mit dem Abschicken legst du einen kostenlosen Account an und erkennst die Nutzungsbedingungen und die Datenschutzerklärung an.