Berechnung bei double Fehler im letzten Digit.

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Antworten
Rob
Beiträge: 34
Registriert: Fr 8. Jul 2011, 10:45
OS, Lazarus, FPC: Win7, Ubuntu 64 und 32bit Lazarus (immer aktuellstes Release) FPC 2.6.4
CPU-Target: amd_64 und i386
Kontaktdaten:

Berechnung bei double Fehler im letzten Digit.

Beitrag von Rob »

Hallo Forum,

ich habe folgendes Problem:
In einem Lazarusprogramm wird ein double mehrfach multipliziert und dividiert
Das Ergebnis gebe ich als Liste aus.

Mach ich nun das gleiche in Java (ich brauche exakt das gleiche Ergebnis auf meinem Android Gerät)
unterscheidet sich das Ergebnis auf der letzten Nachkommastelle.

Lazarus:
37,5789473684211 * 97,0 = 3645,15789473684
in Java ergibt 3645,15789473684 66
In Taschenrechner 3645,15789473684 67
Hier sieht es aus als würden die überzähligen Stellen (>15 digits) abgeschnitten.

Lazarus:
33,1377990430622 * 107,0 = 3545,74449760766
in Java = 3545,74449760765 6
in Taschenrechenr = 3545,74449760765 54
Hier sieht es aus als würde die letzte Stelle aufgerundet.

Weiss jemand wie in Freepascal die Berechnung stattfindet, so das ich das Verhalten nachprogrammieren kann?
So ganz stimmig scheint es ja nicht zu sein, oder hab ich was übersehen?

Grüße
Rob

Socke
Lazarusforum e. V.
Beiträge: 3178
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: Berechnung bei double Fehler im letzten Digit.

Beitrag von Socke »

Rob hat geschrieben:Mach ich nun das gleiche in Java (ich brauche exakt das gleiche Ergebnis auf meinem Android Gerät)
Dann nimmt man Ganzzahlarithmetik; bei Fließkommazahlen hast du immer Rundungsfehler. Am besten sagst du uns auch noch, wie du zu der Ausgabe kommst; die kann das Ergebnis nämlich auch noch verfälschen.
Rob hat geschrieben:Weiss jemand wie in Freepascal die Berechnung stattfindet, so das ich das Verhalten nachprogrammieren kann?
Soweit ich weiß, verlässt sich Free Pascal auf die Gleitkommaeineinheit deines Prozessors. In Java ist das möglicherweise in der JVM versteckt. Wenn du das selbst nachpgrammieren willst, kannst du dir erste Infos unter https://de.wikipedia.org/wiki/Flie%C3%9 ... mmazahl.29 anlesen. Einfacher dürfte es sein dein Programm anständig zu entwerfen :D
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

Rob
Beiträge: 34
Registriert: Fr 8. Jul 2011, 10:45
OS, Lazarus, FPC: Win7, Ubuntu 64 und 32bit Lazarus (immer aktuellstes Release) FPC 2.6.4
CPU-Target: amd_64 und i386
Kontaktdaten:

Re: Berechnung bei double Fehler im letzten Digit.

Beitrag von Rob »

Socke hat geschrieben:Dann nimmt man Ganzzahlarithmetik; bei Fließkommazahlen hast du immer Rundungsfehler. Am besten sagst du uns auch noch, wie du zu der Ausgabe kommst; die kann das Ergebnis nämlich auch noch verfälschen.
s := FloatToStr(x);
Gibt es eine Möglichkeit in Lazarus den genauen Inhalt (alle Digits) eines Doubles auszugeben, ohne das das Ergebnis durch die Stringconvertierung verändert wird?
Socke hat geschrieben:Soweit ich weiß, verlässt sich Free Pascal auf die Gleitkommaeineinheit deines Prozessors.
Die scheint aber dann ganz schön Banane zu sein.
Ich würde erwarten, das er entweder rundet oder abschneidet, hier hab ich den Eindruck das manchmal gerundet wird und manchmal abgeschnitten.
Socke hat geschrieben:Einfacher dürfte es sein dein Programm anständig zu entwerfen :D
Leider nein. Das Lazarusprogramm ist gegeben. Ich muss nur auf meinem Android Gerät das gleiche Ergebnis hinbekommen.
Das Lazarusprogramm ist nicht als Quellcode verfügbar bzw. kann nicht verändert werden.
Ich habe mit Lazarus nur ein paar Rechnungen nachgestellt um sie mit meinem Android Programm zu vergleichen.

Lazarus auf Android geht ja nicht, oder?

Grüße
Rob

Socke
Lazarusforum e. V.
Beiträge: 3178
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: Berechnung bei double Fehler im letzten Digit.

Beitrag von Socke »

Rob hat geschrieben:s := FloatToStr(x);
Gibt es eine Möglichkeit in Lazarus den genauen Inhalt (alle Digits) eines Doubles auszugeben, ohne das das Ergebnis durch die Stringconvertierung verändert wird?
Die Binärdarstellung ;-) Unabhängig davon sollte dir bewusst sein, dass Fließkommazahlen nicht alle reellen Zahlen abdecken und man diese daher nie direkt auf Gleichheit vergleicht. Bei vergleichen wird immer ein Epsilon > 0 als Toleranzbereich verwendet.

Code: Alles auswählen

var d: Double; p: ^QWord; s: String;
begin
  p := PQword(@d);
  s := binstr(p^, 64);
end;
Rob hat geschrieben:
Socke hat geschrieben:Soweit ich weiß, verlässt sich Free Pascal auf die Gleitkommaeineinheit deines Prozessors.
Die scheint aber dann ganz schön Banane zu sein.
Ich würde erwarten, das er entweder rundet oder abschneidet, hier hab ich den Eindruck das manchmal gerundet wird und manchmal abgeschnitten.
Abschneiden ist eine Form des Runden. Ich habe gerade gesehen, dass du das Verhalten durchaus steuern kannst: http://www.freepascal.org/docs-html/rtl ... dmode.html
Rob hat geschrieben:Ich habe mit Lazarus nur ein paar Rechnungen nachgestellt um sie mit meinem Android Programm zu vergleichen.

Lazarus auf Android geht ja nicht, oder?
Dann verweise ich nochmal nach oben: bei einer Abweichung auf der letzten Nachkommastelle sind die Zahlen unter Einbeziehung der Fließkommazahlen innewohnenden Eigenschaften identisch.
Wie schon angedeutet, kann man dir vielleicht helfen dein übergeordnetes Ziel zu erreichen. Dazu müsstest du etwas genauer erklären, wozu du die exakte Gleichheit der Ergebnisse benötigst.

Lazarus auf Android geht, will aber keiner :twisted:
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

mschnell
Beiträge: 3444
Registriert: Mo 11. Sep 2006, 10:24
OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
CPU-Target: X32 / X64 / ARMv5
Wohnort: Krefeld

Re: Berechnung bei double Fehler im letzten Digit.

Beitrag von mschnell »

Rob hat geschrieben:So ganz stimmig scheint es ja nicht zu sein, oder hab ich was übersehen?
Das ist kein "Fehler", sondern genau das ist die Idee, die hinter Floating-Point steckt: "Rechne so genau wie möglich mit vorgegebenem Verbrauch an Speicher und Rechenzeit" !!!

Mathematisch gesehen gibt es bei jeder Berechnung ein definierbare Genauigkeit von +/- einem von der Hardware und Software Infrastruktur abhängigen Wert.

Werden mehrere Berechnungen hintereinander ausgeführt, wird das Ergebnis immer ungenauer. Gute Programmierer achten darauf. Es gibt jede Menge Anleitungen in informatik-Büchern und Zeitungen, wie man Ketten-Berechnungen so programmiert, dass man das Ergebnis mit einer eine möglichst gute Genauigkeit bekommt. (z-B. "Pivot-Suche" und im Extremfall iterative Methoden statt "geradeaus" zur Auflösung lineare Gleichungssysteme. )

Gleitpunkt Zahlen auf "Gleichheit" zu untersuchen ist von vorne herein Unsinn. Wer diese Aufgabenstellung erfindet hat offensichtlich keine Ahnung.

-Michael
Zuletzt geändert von mschnell am Mo 6. Mai 2013, 10:33, insgesamt 4-mal geändert.

mschnell
Beiträge: 3444
Registriert: Mo 11. Sep 2006, 10:24
OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
CPU-Target: X32 / X64 / ARMv5
Wohnort: Krefeld

Re: Berechnung bei double Fehler im letzten Digit.

Beitrag von mschnell »

Rob hat geschrieben:[ Das Lazarusprogramm ist gegeben. Ich muss nur auf meinem Android Gerät das gleiche Ergebnis hinbekommen.
Das geht quasi nicht. Die Aufgabenstellung ist absoluter Blödsinn: Du kannst das orginale mit Lazarsus erstellte Programm (zumindest den Rechen-Teill ohne User - Interface )auf dem Android Gerät auf zwei verschiedene Arten ans laufen bringen: mit Davlin/JVM und "nativ". Das exakte Gleitpunkt-Ergebnis wird trotzdem nicht dasselbe sein wie auf dem PC, weil die ARM Hardware im Detail anders arbeitet

Einzige Mlöglichkeit: Du programmiers die Algorithmen des PC Gleitpunktprozessors in Software nach. Viel Spaß :evil:

-Michael

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Berechnung bei double Fehler im letzten Digit.

Beitrag von mse »

Rob hat geschrieben: Ich würde erwarten, das er entweder rundet oder abschneidet, hier hab ich den Eindruck das manchmal gerundet wird und manchmal abgeschnitten.
Die MSEgui Funktion doubletostring() bringt im mode fsm_default die Werte 3645.15789473685 und 3545.74449760766 , für fsm_fix und precision = 13
3645.1578947368466 und 3545.7444976076560.
http://gitorious.org/mseide-msegui/msei ... ttostr.pas

Martin

indianer-frank
Beiträge: 134
Registriert: So 30. Nov 2008, 21:53

Re: Berechnung bei double Fehler im letzten Digit.

Beitrag von indianer-frank »

Die Diskussion ist müßig, solange nicht geklärt ist, woher die Ausgangsdaten mit Nachkommastellen stammen (97 und 107 werden korrekt dargestellt). Nehmen wird einmal an, daß die Zahlen wie angegeben dezimal korrekt sind. Dann ergeben sich folgende Rechnungen:

Der Ausganswert ist 37.5789473684211 als 4-Word Double = ($BCA8,$F286,$CA1A,$4042) = 37.57894736842109750796225853264331817626953125.

Das Produkt der exakten Werte ist 37.5789473684211*97 = 3645.1578947368467,
als Double ($35EF,$D794,$7A50,$40AC) = 3645.15789473684662880259566009044647216796875

Und das Produkt double(37.5789473684211)*97 = 3645.15789473684645827233907766640186309814453125,
als Double = ($35EF,$D794,$7A50,$40AC) = 3645.15789473684662880259566009044647216796875

In diesen Fall wäre also das Java-Ergebis 'korrekt', FPC und Taschenrechner 'falsch' !

Wenn aber zB die 37.5789473684211 'eigentlich' 37.57894736842108 sind, wäre das Freepascal/Lazuarus- Ergebnis völlig korrekt, Java und Taschenrechner falsch, denn:

37.57894736842108*97 = 3645.15789473684476,
als Double ($35EB,$D794,$7A50,$40AC) = 3645.15789473684480981319211423397064208984375

Der Taschenrechner war also beides mal 'falsch', wahrscheinlich kann man aber an den gerundeten Ausgangsdaten schrauben, bis er auch mal 'richtig' rechnet.

Gruß Frank

Rob
Beiträge: 34
Registriert: Fr 8. Jul 2011, 10:45
OS, Lazarus, FPC: Win7, Ubuntu 64 und 32bit Lazarus (immer aktuellstes Release) FPC 2.6.4
CPU-Target: amd_64 und i386
Kontaktdaten:

Re: Berechnung bei double Fehler im letzten Digit.

Beitrag von Rob »

Hallo,
indianer-frank hat geschrieben:Die Diskussion ist müßig, solange nicht geklärt ist, woher die Ausgangsdaten mit Nachkommastellen stammen
beginnend von einer Ganzzahl (30.0) werden Divisionen und Multiplikationen durchgeführt.
Dividend und Multiplikant sind jeweils Integer.

i: integer;
d: double;

d := 50.0;
i:= 230; //<- ändert sich bei jeder Berechnung im Bereich von 0 - 999
d := d * i; oder d := d / i;
Das Ganze n mal.
indianer-frank hat geschrieben: Dann ergeben sich folgende Rechnungen:

Der Ausganswert ist 37.5789473684211 als 4-Word Double = ($BCA8,$F286,$CA1A,$4042) = 37.57894736842109750796225853264331817626953125.
Wie hast du die Umrechnung von 4Byte Double in die Gleitpunktzahl durchgeführt?
Vielleicht komme ich damit weiter.

Ich weiß das es keine gute Idee war hier Double zu verwenden, ich versuche mich gerade in Schadensbegrenzung.
indianer-frank hat geschrieben: In diesen Fall wäre also das Java-Ergebis 'korrekt', FPC und Taschenrechner 'falsch' !
Ob richtig oder falsch ist mir erstmal egal. Worum es geht ist, das Ergebnis des Lazarus/Freepascal Codes ru reproduzieren, auch wenn es mathematisch falsch ist.
Den Lazarus/Freepascal code kann ich erstmal nicht ändern. (In einem nächsten Release, wenn ich dem Entwickler gut zurede bestimmt).
indianer-frank hat geschrieben: wahrscheinlich kann man aber an den gerundeten Ausgangsdaten schrauben, bis er auch mal 'richtig' rechnet.
Genau das versuche ich nun ...

Grüße
Rob

Soner
Beiträge: 733
Registriert: Do 27. Sep 2012, 00:07
OS, Lazarus, FPC: Win10Pro-64Bit, Immer letzte Lazarus Release mit SVN-Fixes
CPU-Target: x86_64-win64
Wohnort: Hamburg

Re: Berechnung bei double Fehler im letzten Digit.

Beitrag von Soner »

Für Konvertierung zu String kannst du die Funktion Format nehmen. Mit Format kann man auch nachkomma Stellen bestimmen. Dann hast du gleiche Ergebnisse wie Taschenrechner:
Format('%.16f', [(37.5789473684211 * 97.0)]) ist gleich 3645.1578947368467000
Format('%.16f', [(33.1377990430622 * 107.0)]) ist gleich 3545.7444976076554000

Wenn man die letzten Stellen bei Java-Ergebnisse schaut kommt es mir vor als ob Java falsch aufrundet.


Nachtrag:
Was merkwürdig ist dass bei mein System Freepascal und Windows-Taschenrechner gleiches Ergebnis liefern und ein Java-Programm und ein Javascript-Program (in Firefox, IE8, Chrome, Opera) anderes Ergebnis und zwar Java-Ergebnis von oben liefern.

Nachtrag 2:
Jetzt habe ich mit C-Programm gestestet, kompiliert mit gcc 4.7.2. Erste Ergebnis ist wie in Java 2.Ergebnis anders:
3645.1578947368466000
3545.7444976076558000

Code: Alles auswählen

 
// gcc -o ztest ztest.c
#include <stdio.h>
 
int main(void)
{
  double d1=(37.5789473684211 * 97.0);
  printf("%.16f %s",d1,"\n");
 
  d1=(33.1377990430622 * 107.0);
  printf("%.16f %s",d1,"\n");  
 
  return 0;
}
 
Nachtrag 3:
Jetzt habe ich selber (Hirn :) ) nachgerechnet Freepascal und Windows-Taschenrechner rechnen richtig alle anderen Falsch oder die C/C++ Biblotheken haben merkwürdige Rundungsregel.

mschnell
Beiträge: 3444
Registriert: Mo 11. Sep 2006, 10:24
OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
CPU-Target: X32 / X64 / ARMv5
Wohnort: Krefeld

Re: Berechnung bei double Fehler im letzten Digit.

Beitrag von mschnell »

Soner hat geschrieben:Freepascal und Windows-Taschenrechner rechnen richtig alle anderen Falsch oder die C/C++ Biblotheken haben merkwürdige Rundungsregel.
Wie gesagt: Bei Gleitpunktzahlen sind solche Effekte völlig normal. Da gibt es kein "richtig" oder "falsch".

-Michael

indianer-frank
Beiträge: 134
Registriert: So 30. Nov 2008, 21:53

Re: Berechnung bei double Fehler im letzten Digit.

Beitrag von indianer-frank »

Rob hat geschrieben:Wie hast du die Umrechnung von 4Byte Double in die Gleitpunktzahl durchgeführt? Vielleicht komme ich damit weiter.
Ich denke nicht, daß das weiter hilft (eigentlich macht das ja der Kompiler), aber hier ist die Rechnung:
x = 37.5789473684211 = 0.5871710526315796875*2^6, also double(x) = int(0.5871710526315796875*2^53)/2^53*2^6 = 37.57894736842109750796225853264331817626953125. Hier noch mal als Programm:

Code: Alles auswählen

program test;
uses
  math,sysutils;
var
  x: double;[code=dos]
bx: array[0..3] of word absolute x;
i: integer;
m: int64;
begin
x := 37.5789473684211;
for i:=0 to 3 do write(' $',IntToHex(bx,4));
writeln(' = ',x);
m := 5288766667668648;
x := m;
x := ldexp(x,-53)*64;
for i:=0 to 3 do write(' $',IntToHex(bx,4));
writeln(' = ',x);
end. [/code]Der Doublewert von x ist halt die zu x nächste darstellbare Doublezahl, hier die Umgebung von x:

Code: Alles auswählen

x1 = ($BCA7,$F286,$CA1A,$4042) = 37.57894736842109040253490093164145946502685546875
x2 = ($BCA8,$F286,$CA1A,$4042) = 37.57894736842109750796225853264331817626953125
x3 = ($BCA9,$F286,$CA1A,$4042) = 37.57894736842110461338961613364517688751220703125
 
x-x1 = 9.597465099E-15
x-x2 = 2.492037741E-15
x-x3 =-4.613389616E-15 
Also wie gesagt double(x)=x2.

Zu Deinem Problem: Wenn es nur Multiplikationen wären, könntest Du Dich eventuell mit skalierten Int64 retten, aber bei Divisionen ist das i.d.R. nicht möglich (bedenke daß z.B. 1/10 nicht exakt darstellbar ist). Wenn Du es allerdings unbedingt brauchst (was ich bezweifle), hilt nur Fließkomma-Multipräzisions-Arithmetik (suche nach: MPArith Pascal source code for multi precision floating point arithmetic).

Gruß Frank

Antworten