Forum: Compiler & IDEs Float Vars--Rundungsfehler


von Christof Ermer (Gast)


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

von Rufus Τ. F. (rufus) Benutzerseite


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:
1
#define FLT_DIG         6                       /* # of decimal digits
2
of precision */
3
#define FLT_EPSILON     1.192092896e-07F        /* smallest such that
4
1.0+FLT_EPSILON != 1.0 */
5
#define FLT_GUARD       0
6
#define FLT_MANT_DIG    24                      /* # of bits in
7
mantissa */
8
#define FLT_MAX         3.402823466e+38F        /* max value */
9
#define FLT_MAX_10_EXP  38                      /* max decimal exponent
10
*/
11
#define FLT_MAX_EXP     128                     /* max binary exponent
12
*/
13
#define FLT_MIN         1.175494351e-38F        /* min positive value
14
*/
15
#define FLT_MIN_10_EXP  (-37)                   /* min decimal exponent
16
*/
17
#define FLT_MIN_EXP     (-125)                  /* min binary exponent
18
*/

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?

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

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

von Rolf Magnus (Gast)


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.

von Alex (Gast)


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.

von Rolf Magnus (Gast)


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.

von Falk W. (dl3daz) Benutzerseite


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

von Rolf Magnus (Gast)


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.

von Falk W. (dl3daz) Benutzerseite


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

von peter dannegger (Gast)


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

von Christof Ermer (Gast)


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
  };
};

von peter dannegger (Gast)


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

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.