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?
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.
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" :-)
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.
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?
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.
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.
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?
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)
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.
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
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.
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.
Update:
Die Ascii Behandlung des Dezimalpunktes und spätere Weiterbehandlung des
daraus entstandenen Integer Wertes funktioniert wunderbar.
Problem halbwegs elegant gelöst :-)