www.mikrocontroller.net

Forum: Compiler & IDEs Float Vars--Rundungsfehler


Autor: Christof Ermer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
GCC

Ich hab ebis heute keine Typendefinition für GCC gefunden..
( Wertebereiche für float usw.)
deashalb ist mir foglendes passiert. !

In einem Interrupt, den ich als Tickcounter benutze, zähle ich 0.016384
auf eine floatvaribale
gfMSTicksT2 += 0.016xxx

Ab 262144.0 + 0.016 kommen falsche Ergebnisse raus. ( 262144.031250 )
( also schon der doppelte Wert ! )

Ab  524287.0 + 0.016) immer noch ( 524287.031250 ) = FALSCH !!!

Ab 524288.0 + 0.016  aber wird der Addierwert zu NULL!!! gerundet
  524288.0 += 0.016  ( 524288.000000 )  ist FALSCH !!

Vorsicht also, bei eiem Zeitzählerverwenung mit Float...

Deshalb ist der ATMEGA16 nach 4 1/2 Tagen = >524288 Sekunden ..stehen
geblieben, weil kein Zeitvergleich in der Software mehr war...
gfMSTicksT2 + 0.16 wird nicht mehr inkrementiert.


MaAcht einfach diesen Feler nihct mehr...
Das dies so früh passiert, damit hätte ich nie gerechnet...
Ciao
Christof







gfMSTicksT2 = 0x7FFEF + 0.016//  AB hier wird
  PrintFloatCRLF( 262143.0 + 0.016);  //262143.015625      ==0x3FFFF=
262143
  PrintFloatCRLF( 262144.0 + 0.016);  //262144.031250
  PrintFloatCRLF( 524287.0 + 0.016);  //524287.031250   ==0x7FFFF
  PrintFloatCRLF( 524288.0 + 0.016);  //524288.000000

Autor: Rufus Τ. Firefly (rufus) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Für AVR-GCC ist sizeof (float) = 4, es werden also nur recht ungenaue
32-Bit-Floats verwendet.

In float.h sollten FLT_MAX und FLT_MIN zu finden sein.

Hier mal -als Beispiel- die entsprehenden Konstanten, allerdings aus
einem anderen Compiler:
#define FLT_DIG         6                       /* # of decimal digits
of precision */
#define FLT_EPSILON     1.192092896e-07F        /* smallest such that
1.0+FLT_EPSILON != 1.0 */
#define FLT_GUARD       0
#define FLT_MANT_DIG    24                      /* # of bits in
mantissa */
#define FLT_MAX         3.402823466e+38F        /* max value */
#define FLT_MAX_10_EXP  38                      /* max decimal exponent
*/
#define FLT_MAX_EXP     128                     /* max binary exponent
*/
#define FLT_MIN         1.175494351e-38F        /* min positive value
*/
#define FLT_MIN_10_EXP  (-37)                   /* min decimal exponent
*/
#define FLT_MIN_EXP     (-125)                  /* min binary exponent
*/

64-Bit-Floats hat wohl noch niemand auf diesen 8-Bit-Controller
portiert.

Könnte es sein, daß es keine besonders gute Idee ist, a)
floating-Point-Arithmetik in einer Interrupt-Routine zu betreiben und
b) eine float-Variable als Tickercounter zu verwenden?

Autor: Rufus Τ. Firefly (rufus) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Nachtrag:
Sorry für die vergurkte Formatierung. Leider gibt's hier keine
Vorschau und keine Edit-Funktion ...

Autor: Rolf Magnus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Vorsicht also, bei eiem Zeitzählerverwenung mit Float...
> Deshalb ist der ATMEGA16 nach 4 1/2 Tagen = >524288
> Sekunden ..stehen
> geblieben, weil kein Zeitvergleich in der Software mehr war...
> gfMSTicksT2 + 0.16 wird nicht mehr inkrementiert.
>
>
> MaAcht einfach diesen Feler nihct mehr...

Es gibt auch double, wenn man mehr Präzision braucht.

> Das dies so früh passiert, damit hätte ich nie gerechnet...

Dann hast du vermutlich noch nicht viel mit Fließkommaberechnungen zu
tun gehabt. In C muß die Genauigkeit nicht höher als 6 signifikanten
Dezimalstellen sein, und viel mehr wird es bei den üblichen 32 bit für
float auch nicht. Da du schon 6 Stellen vor dem Komma hast, brauchst du
dich nicht zu wundern, daß nach dem Komma nichts sinnvolles mehr
rauskommt.
Aber warum benutzt du überhaupt Fließkomma? Mir scheinen
Fixkomma-Rechnungen hier sinnvoller zu sein.

Autor: Alex (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
double = float beim avr-gcc, wie bereits oben bemerkt wurde. Eine
Anwendung die 64 Bit doubles braucht ist auf einem 8 Bitter soundso
normal verloren, es sei denn Zeit spielt keine Rolle :)

Wenn man sich jedoch überlegt, dass selbst anspruchvolle
DSP-Anwendungen mit 16-32 Bit Festkomma auskommen, liegt der Fehler
wohl meist eher beim Anwender.

Autor: Rolf Magnus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich würde hier auch nicht jedesmal eine Fließkommazahl um irgendeinen
komischen Wert erhöhen, sondern stattdessen einfahch einen Integer
inkrementieren. Den rechne ich dann nachher da, wo ich das brauche, in
den Zielwert um (möglichst Fixkomma oder Integer). Das dürfte deutlich
schneller und kompakter sein, und die Genauigkeit bleibt nicht ab
irgendeinem Wert auf einmal auf der Stecke. Den Integer kann ich durch
Kaskadierung auch "beliebig" groß machen, je nachdem, wie weit
gezählt werden muß.
Fließkomma ist eine heikle Sache, vor allem, wenn man inkrementell
rechnen muß. Fehler pflanzen sich fort, die Genauigeit reicht vorne und
hinten nicht, es wird gerundet, wo man's nicht will. Man kann keine
exakten Vergleiche machen, ...
Und beim Mikrocontroller kommt noch dazu, daß es einen gewaltigen
Overhead hat.

Autor: Falk Willberg (dl3daz) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Mhhh, die 0.016384 sehen irgendwie nach 1/61 aus.
Wie wäre es damit:

uint32_t gfMSTicksT2;
uint8_t  tmpticks;

if (++tmpticks==61) { gfMSTicksT2++; tmpticks=0; }

War es nicht sogar so, daß manche unverdächtige Dezimalzahl (1/10) sich
als unendliche Gleitkommazahl entpuppt?


Falk

Autor: Rolf Magnus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Mhhh, die 0.016384 sehen irgendwie nach 1/61 aus.

Für mich sieht's eher aus wie 2 hoch 14 geteilt durch eine Million.
Das wäre das gleiche wie 256/15625.

> War es nicht sogar so, daß manche unverdächtige Dezimalzahl (1/10)
> sich als unendliche Gleitkommazahl entpuppt?

Nicht als unendliche, aber als periodische. Genauso wie 1/3 im
Dezimalsystem nicht darstellbar ist, ist 1/5 (und damit auch 1/10) im
Dualsystem nicht darstellbar.

Autor: Falk Willberg (dl3daz) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@Rolf Magnus:
So oder so: Wir meinten etwa das Gleiche.

Mein Lieblingsbeispiel für die "Macht des Integers":

uint32_t r,n;
r=n*(125.000.000 * 4.294.967.296);

in 80 Zeilen AVR_Assembler.

Falk

Autor: peter dannegger (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Ab 524288.0 + 0.016  aber wird der Addierwert zu NULL!!! gerundet
>   524288.0 += 0.016  ( 524288.000000 )  ist FALSCH !!


Ja ja, sowas kommt dabei raus, wenn Mathematiker in C programmieren
wollen.

In C gibt es keine unendlich genauen Zahlen, daher ist das Ergebnis
unter C RICHTIG !!

Wie ja schon gesagt wurde, nimm Ganzzahlen, Float in Interrupts nehmen
nur Ignoranten (bloß nicht zuviel Rechenzeit fürs Main übriglassen).

Ganzzahlen lassen sich auch prima kaskadieren:

unsigned long time0, time1, time2;

if( ++time0 == 0 )
  if( ++time1 == 0 )
    ++time2;

Und schon hast Du einen 96 Bit Zeitstempel. Ist zwar immer noch nicht
unendlich genau, sollte in der Praxis aber reichen.

Da rauft sich dann zwar der Mathematiker die Haare (positive Zahl kann
nach Increment nie Null werden), in C geht das aber.


Peter

Autor: Christof Ermer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ihr seit ja richtig gut und nett.
ich dachte einfach den 8 Bit Timer als Tickcounter zu missbrauchen wäre
eine gute Idee, und so genaue Sekunden und schnelle mS Zeitabschnitte zu
bekommen...

um eben genaue Sekunden zu bekommen müßte ich ja

0.016384 ms mit dem Faktor 1 000 000 multpliziren...
und dann
//unsigned long ulMSTicks;
ulMSTicks += 16384;

und auf ein ULONG aufaddieren....!!!
Phuuu, dass klingt aber auch nicht so toll. !!

Und dann muss ich auch den Zähler VOR den Überlauf zurückstellen, damit
kein Zeitvergleich ( ZEitdistanz) auf einmal NEGATIV werden
kann.....Wichtig z.B. bei Entprellroutinen für Tastaturen etc..


Aber allen Vielen Dank für eure Gedanken


//Bis jetzt war es so implementiert !!!!

#define TIMER2_INTPERIODE 0.016384 //mS ad

volatile SIGNAL(SIG_OVERFLOW2)
// *******************************************************************
{
sei();  //INTERRUPT ENABLE
// 16MhZ / 1024 1024  256  = 61,03515625 Hz
// T= 1/f = 0.016384 ms / Interrupt
gfMSTicksT2 += TIMER2_INTPERIODE;

// JETZT STELLE ICH EINFACH  DEN ZÄHLER ZURÜCK

//Um einen floaTrundungsfehler zu verhindern !
if(gfMSTicksT2 > HALF_FLOAT_ROUNDERRORPOINT ) // --->> 0x3FFFF
  {
  ResetCountVals();  //Wegen Ausfallgefahr
  };
};

Autor: peter dannegger (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
"Und dann muss ich auch den Zähler VOR den Überlauf zurückstellen,
damit kein Zeitvergleich ( ZEitdistanz) auf einmal NEGATIV werden
kann"


Nein !!!

Mußt Du eben nicht, wenn Du Ganzzahlen nimmst.

Es ist egal, welche Zahl kleiner oder größer ist, die Differenz ist
immer richtig, wenn Dein Zeitinterval nie länger ist als der
Zählbereich der Zeitstempelvariable.

Und das ist auch genau der Grund, warum alle Welt Ganzzahlen für die
Timerticks nimmt.

Selbst auf dem PC ist das so, da gibt es ja diese ominöse Konstante
18,2 mit der man dann die genauen Sekunden rauskriegt.


Peter

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.