Forum: Compiler & IDEs Addition long und double (float)


von Michael K. (michaelkorb)


Lesenswert?

Ich kämpfe mit einem Problem mit WinAvr. Wenn ich in einer Funktion 
einen double-Wert zu einem long-Wert addiere, geht die Genauigkeit 
verloren bzw. kommt was falsches raus.

konkretes Beispiel:

long    MjdMidnight;
double  FracOfDay;
int     b;
double jul = 0.0;

if (Month<=2) { Month+=12; --Year;}

if ( (10000L*Year+100L*Month+Day) <= 15821004L )
  b = -2 + ((Year+4716)/4) - 1179;     // Julianischer Kalender
else
  b = (Year/400)-(Year/100)+(Year/4);  // Gregorianischer Kalender

MjdMidnight = (365L*Year - 679004L + b + (long)(30.6001*(Month+1)) + 
Day);
FracOfDay   = Ddd(Hour,Min,Sec) / 24.0; // liefert double

jul = (double)MjdMidnight + FracOfDay;

return jul;

mit Werten:

MjdMidnight = 54376
FracOfDay = 0.54027778
jul = 54376.539

Warum ergibt die Addition so ein Zeugs?

von Jörg X. (Gast)


Lesenswert?

Weil die 'double' beim AVR-GCC nur 32 Bit lang sind? und das nur ~6 
signifikante Stellen sind? (24Bit Mantisse ink. Vorzeichen, 8Bit 
Exponent).

rtfm, Jörg

ps.: Was machst du da eigentlich? und was erwartest du ?

von Michael K. (michaelkorb)


Lesenswert?

> Was machst du da eigentlich? und was erwartest du ?
Das ist eine Umrechnung in das modifizierte julianische Datum, welches 
ich für weitere Berechnungen im astronomischen Bereich brauche. Ich 
erwarte eigentlich so was wie 54376.540278. Das dumme ist, in den 
Nachkommastellen steckt die Uhrzeit und die ist gerade wichtig für die 
weiteren Berechnungen.

von Karl H. (kbuchegg)


Lesenswert?

Michael Korb wrote:
>> Was machst du da eigentlich? und was erwartest du ?
> Das ist eine Umrechnung in das modifizierte julianische Datum, welches
> ich für weitere Berechnungen im astronomischen Bereich brauche. Ich
> erwarte eigentlich so was wie 54376.540278.

Die wirs du aber nicht kriegen.
Wie Jörg schon sagte: Mit einem double kriegst du mit WinAvr
grade mal ca. 6 signifikante Ziffern hin. Blöd bei dir ist
halt, dass von diese 6 Stellen bereits 5 auf den Teil vor
dem Komma fallen. Damit hast du gerade noch 1 Stelle nach
dem Komma, die halbwegs richtig ist, alles andere dahinter
ist mehr oder weniger gelogen.

> Nachkommastellen steckt die Uhrzeit und die ist gerade wichtig für die
> weiteren Berechnungen.

Entweder du nimmst einen anderen Compiler, einen bei dem double
eine Größe von 8 Bytes hat und nicht 4 wie beim WinAvr.(*) Das würde
sich schon alleine deshalb anbieten, weil deine astronomischen
Berechnungen auch sonst noch vor Gleitkommaarithmetik strotzen
werden und du mit WinAvr-double praktisch keine verlässlichen
Ergebnisse kriegen wirst. Die 6 signifikanten Stellen werden
nach ein paar Multiplikationen/Divisionen ganz schnell zu 5
und von dort gehts dann noch schneller zu 4, etc.

Oder aber du überlegst dir ein Fixpunktschema. Wenn du allerdings
alle 6 Nachkommastellen brauchst, wird das schwierig werden. Ein
normaler long (32-Bit Ganzzahl) wird da nicht mehr mitkommen.


(*) Mit einem 8 Byte double reden wir immerhin von ca. 15
signifikanten Stellen.

von Michael K. (michaelkorb)


Lesenswert?

Stimmt leider. Hatte ich ganz vergessen, dass der gcc WinAvr ja nur 
4Byte double kann. Bin ja schon einmal deswegen auf ARM umgestiegen. Ich 
werd es mal mit Corssworks AVR versuchen, das ist nicht ganz so teuer.
Vielleicht findet sich endlich mal jemand, der eine Lib für 8Byte double 
baut.

von Andreas K. (a-k)


Lesenswert?

Wäre jetzt nur die Frage offen, ob 64bit Integers für den Zweck nicht 
ausreichen. Denn die beherrscht er.

Das reicht leicht für die gesamte Dauer der menschlichen Existenz in 
Mikrosekunden gerechnet aus.

von Peter D. (peda)


Lesenswert?

Ich verstehe nicht, wozu man für Datum und Uhrzeit float braucht.

Gehts mit Ganzzahlen zu schnell oder ist zuviel Flash übrig, den Du voll 
kriegen mußt ?

Beitrag "Berechnung Datum + Uhrzeit + Sommerzeit"


Peter

von Detlef _. (detlef_a)


Lesenswert?

>>Das reicht leicht für die gesamte Dauer der menschlichen Existenz in
>>Mikrosekunden gerechnet aus.

Nein. 2^64 us sind unter 600000 Jahre.

gute Nacht
Detlef

von Michael K. (michaelkorb)


Lesenswert?

Peter Dannegger wrote:
> Ich verstehe nicht, wozu man für Datum und Uhrzeit float braucht.
>
> Gehts mit Ganzzahlen zu schnell oder ist zuviel Flash übrig, den Du voll
> kriegen mußt ?
Das Problem ist, dass im astronomischen Bereich mit WInkeln in rad 
gerechnet wird. Das Datum wird dazu in ein modifiziertes julianisches 
umgerechnet (nach dem Komma ist die Zeit, vor dem Komma Tage) s. hier 
http://de.wikipedia.org/wiki/Julianisches_Datum, um dann die Sternzeit 
zu berechnen. Dabei fließen Größen ein, die in rad gegeben sind (geogr. 
Länge, geogr. Breite).
Man müßte versuchen, all diese Lehrbuchfunktionen auf ganzzahlig zu 
normieren. Ich hab auch schon versucht, die Tage aussen vor zu lassen, 
weil mich eigentlich nur die Zeit interessiert (nach dem Komma). 
Allerdings stoße ich dann bei der nächsten Funktion für die Berechnung 
der Greenwich-Sternzeit wieder auf große double-Zahlen (Konstanten) - 
das Problem verschiebt sich. Da die Endergebnisse sich immer im 
rad-bereich abspielen (also 0-2PI), ist dort die Genauigkeit wieder 
gegeben mit 5-6Stellen nach dem Komma. Weniger bringt Fehler bereits im 
Minutenbereich und das kann ich mir nicht leisten.

von tex (Gast)


Lesenswert?

>Das Problem ist, dass im astronomischen Bereich mit Winkeln in rad
>gerechnet wird. Das Datum wird dazu in ein modifiziertes julianisches
>umgerechnet (nach dem Komma ist die Zeit, vor dem Komma Tage), um dann
>die Sternzeit zu berechnen.
Hehe
Es kommt noch besser, wenn Du bei den tan und atan ankommst, die 
versalzen Dir so richtig die Suppe. Da kommt eine Grütze raus, dass Du 
graue Haare bekommst. Die verwegene Idee, es dem Compiler 
unterzuschieben und einen zu suchen der volle Double berechnet hatte ich 
auch, war aber eine Sackgasse.
Die Ober-Gemeinheit ist, dass Du die Berechnung immer mal wieder für ein 
paar Tage hingefummelt bekommst und dann am 21.06. 0der 23.12 kommt der 
grosse Heulkrampf.
Mein Tip.
Die Formel die Du hast stammt aus Zeiten, als Cäsar Gallien erobert hat. 
(das ist mal keine Metaffa sondern eine Tatsache)  Mach Dich schlau, wie 
die damals ohne Taschenrechner sowas berechnet haben und bringe diesen 
Rechnestiel Deinem uC bei, dann geht das auch mit einem handelsüblichen 
8Bit uC.

von Michael K. (michaelkorb)


Lesenswert?

tex wrote:
> Es kommt noch besser, wenn Du bei den tan und atan ankommst, die
> versalzen Dir so richtig die Suppe.
die Kandidaten kommen zum Glück nicht vor. Es beschränkt sich auf reine 
Grundrechenarten im double-Bereich
> Die Formel die Du hast stammt aus Zeiten, als Cäsar Gallien erobert hat.
> (das ist mal keine Metaffa sondern eine Tatsache)  Mach Dich schlau, wie
> die damals ohne Taschenrechner sowas berechnet haben und bringe diesen
> Rechnestiel Deinem uC bei, dann geht das auch mit einem handelsüblichen
> 8Bit uC.
Ich werd mich mal dran setzen. Wahrscheinlich kann ich dann gleich ein 
neues Buch schreiben - "Astronomische Berechnungen mit dem 
Taschenrechner" oder so

von Falk B. (falk)


Lesenswert?

@ Michael Korb (michaelkorb)

>das Problem verschiebt sich. Da die Endergebnisse sich immer im
>rad-bereich abspielen (also 0-2PI), ist dort die Genauigkeit wieder
>gegeben mit 5-6Stellen nach dem Komma. Weniger bringt Fehler bereits im
>Minutenbereich und das kann ich mir nicht leisten.

Festkommaarithmetik

MFG
Falk

von tex (Gast)


Lesenswert?

@Michael
Das Buch brauchst Du nicht zu schreiben, das gibt es schon.
Wenn Du mir sagst, was Du berechnen willst, kann ich mal schauen, ob ich 
Dir da helfen kann.

von Michael K. (michaelkorb)


Lesenswert?

tex wrote:
> Das Buch brauchst Du nicht zu schreiben, das gibt es schon.
Due meinst sicher den Mountenbr. bzw. das vo Meus o.ä.
> Wenn Du mir sagst, was Du berechnen willst, kann ich mal schauen, ob ich
> Dir da helfen kann.
Also was ich tun will:
- von einem GPS-Empfänger bekomme ich Datum und Zeit (zum Glück 
wenigstens schon als UT) und geogr. Länge / Breite
- daraus will ich die lokale Sternzeit berechnen (erst GMST, dann LST)
- später will ich dann noch für Objekte (ausgewählt aus Katalog auf 
SD-Card) mit gegebener RA und DEC den Stundenwinkel, Azimut und Höhe 
berechnen.
Das ist eigentlich schon alles. Bisher hab ich die Gleichungen aus 
genannten Büchern in Klassen verpackt und gut. Allerdings unter 
Compilern, die mit echten double (8Byte) umgehen können. Man müßte nun 
die wichtigen Funktionen entsprechend so bauen, dass die relevante 
Anzahl der Stellen nicht überschritten wird oder im long-Bereich rechnen 
und entsprechend normieren.

von tex (Gast)


Lesenswert?

>- später will ich dann noch für Objekte (ausgewählt aus Katalog auf
>SD-Card) mit gegebener RA und DEC den Stundenwinkel, Azimut und Höhe
>berechnen.
>Das ist eigentlich schon alles.

Das Beste daran ist der letzte Satz.
Also bei Azimut unf Höhe kommst Du um den Tan nicht mehr drum herum.
Wie schon oben geschriebn, bring dem uC bei, zu fuss zu rechnen, und 
benutze die Ziffern als Zeichen.
Also für eine einfache Addition also zuerst die Position der 
Kommastellen der beiden Summanden ermitteln, dann Zeichen für Zeichen 
mit übertrag addieren und Ergebnis Zeichen für Zeichen in einen String 
schreiben.
Wichitg ist auch dass Du Dir mal exemplarisch und zu Fuss ein paar 
Zahlen anschaust, wie sie sich mit den Jahren ändern. Viele sind über 
hunderte Jahre hinweg konstant.

von Michael K. (michaelkorb)


Lesenswert?

tex wrote:
> Also bei Azimut unf Höhe kommst Du um den Tan nicht mehr drum herum.
Ab dem Punkt ist es aber kein Problem ehr, da sich alles im Bereich 
0-2PI abspielt. Da reichen die Stellen der Mantisse aus. Das Problem ist 
bei großen Zahlen wie dem julianischen modifizierten Datum und der 
Berechnung der Greenwich-Sternzeit, die dann bereits in rad vorliegt. 
Danach komme ich sicher mit der Anzahl der Stellen aus.
Ich hab die Funktionen für jul. Datum und GMST mal zusammengefasst und 
die kritischen Stellen anders gelöst. Das sieht dann so aus:

// Berechnung der mittleren Greenwich-Sternzeit
double calcGMST( int jahr, int monat, int tag, int stunde, int minute, 
int sekunde)
{
  //
  // Konstanten
  //
  const double Secs = 86400.0;        // Anzahl der Sekunden je Tag

  //
  // Variablen
  //
  long    MjdMidnight = 0L;
  double  FracOfDay = 0.0;
  int     b = 0;

  double  MJD_0 = 0.0, UT = 0.0, T_0 = 0.0;
  double  T = 0.0, gmst = 0.0, result = 0.0;

  /// mjd berechnen
  if (monat<=2) { monat+=12; --jahr;}

  if ( (10000L*jahr+100L*monat+tag) <= 15821004L )
    b = -2 + ((jahr+4716)/4) - 1179;     // Julianischer Kalender
  else
    b = (jahr/400)-(jahr/100)+(jahr/4);  // Gregorianischer Kalender

  // Anzahl Tage
  MjdMidnight = (365L*jahr - 679004L + b + 
(long)(30.6001*(monat+1))+tag);
  // Zeit als Nachkommawert
  FracOfDay   = Ddd(stunde, minute, sekunde) / 24.0;

  // gmst berechnen
  MJD_0 = MjdMidnight;      // ganzzahliger Anteil
  UT    = Secs*(FracOfDay);     // [s]
  T_0   = (MJD_0-51544.5)/36525.0;
  T      = ((MjdMidnight + FracOfDay) - 1544.5)/36525.0;

  gmst  = 24110.54841 + 8640184.812866*T_0 + 1.0027379093*UT
          + (0.093104-6.2e-6*T)*T*T;      // [sec]

  result = Modulo(gmst,Secs);
  result = (M_PI/2.0/Secs)*result;   // [Rad]

  return result;
}

Das Problem mit dem großen julianischen Datum konnte ich lösen, da es 
bei der GMST-Berechnung eh in seinen Bestandteilen verarbeitet wird. Ich 
bin nur nicht sicher, ob bei der Zeile gmst= die Konstanten 
funktionieren. Das Ergebnis ist bisher noch nicht korrekt.

Hier mal noch die Original GMST-Funktion, in die das jul. Datum 
einfließt.

double GMST (double MJD)
{
  //
  // Konstanten
  //
  const double Secs = 86400.0;        // Anzahl der Sekunden je Tag


  //
  // Variablen
  //
  double MJD_0, UT, T_0, T, gmst;


  MJD_0 = floor ( MJD );
  UT    = Secs*(MJD-MJD_0);     // [s]
  T_0   = (MJD_0-51544.5)/36525.0;
  T     = (MJD  -51544.5)/36525.0;

  gmst  = 24110.54841 + 8640184.812866*T_0 + 1.0027379093*UT
          + (0.093104-6.2e-6*T)*T*T;      // [sec]

  return  (M_PI/2.0/Secs)*Modulo(gmst,Secs);   // [Rad]
}

MJD_0 ist ja eigentlich eh nur der Vorkommateil des jul. Datums (floor) 
und UT berechnet sich nur aus dem Nachkommateil des jul. Datums.
Momentan bin ich eh betriebsblind und finde den Fehler nicht. Vielleicht 
hat ja jemand eine Idee.

von Karl H. (kbuchegg)


Lesenswert?

Michael Korb wrote:

> bin nur nicht sicher, ob bei der Zeile gmst= die Konstanten
> funktionieren.

Das wird mit Sicherheit nicht gehen. Der Dynamikumfang von
4-Byte double reicht da bei weitem nicht aus, um ein
zuverlässiges Ergebnis zu erhalten.

> Momentan bin ich eh betriebsblind und finde den Fehler nicht.

Der Fehler liegt darin, dass 4 Byte double einfach zuwenig
Reserven haben um Zahlen mit Größenordnungsunterschieden von
10 hoch 13 in einer Berechnung zu verwurschten.

> Vielleicht
> hat ja jemand eine Idee.

Entweder suchst du dir am Netz eine Floating Point Library,
die mit 8 Byte double umgehen kann. Oder du suchst nach
einer "Multi Precission Library", der du die Stellenzahl
vorgeben kannst oder du schreibst dir selber was, so wie
tex das schon angedeutet hat. Solange es nur um Additionen,
Subtraktionen, Multiplikationen und Divisionen geht, ist das
nicht weiter schwer.

von Michael K. (michaelkorb)


Lesenswert?

Ich hab das Problem gelöst. Ich hab mir eine etwas einfachere Funktion 
zur Berechnung der Greenwich-Sternzeit rausgesucht, die aber trotzdem 
genau ist. Innerhalb dieser hab ich die Konstanten um 10000 normiert, im 
long-Bereich gerechnet und dann wieder in double gewandelt. Die 
Genauigkeit liegt im Minutenbereich und das reicht mir. Die 
nachfolgenden Funktionen können im double-Bereich bleiben, da es sich 
nur um WInkel im Bereich 0-2PI handelt. Da reichen die signifikanten 
Stellen aus. Nochmals danke an alle Helferlein.

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.