Einzelnen Beitrag anzeigen

Andreas13

Registriert seit: 14. Okt 2006
Ort: Nürnberg
711 Beiträge
 
Delphi XE5 Professional
 
#30

AW: Genauigkeit von String to Single Konvertierung

  Alt 16. Apr 2020, 21:18
Ich habe absichtlich einige Zeit gewartet, um zu testen, ob die beide Jungs mit ihrer Behauptung recht hätten:
We agree with James Gosling about some things like ...
...
"95% of the folks out there are completely clueless about floating-point."
(James Gosling, 28 Feb. 1998)
(Maybe more than 95% ?: 1 March 1998, W. Kahan)
...
Das ist schon 22 Jahre her. Vielleicht wären heutzutage 99 % treffender...
Andreas
Anscheinend haben sie doch recht gehabt! Offenbar interessiert sich hier so gut wie niemand für die Genauigkeit von numerischen Berechnungen. Schade. Anderseits kann ich gut verstehen, wenn sich Integer-Programmierer nicht für Real-Zahlen interessieren. Jeder spezialisiert sich halt auf ein anderes Gebiet.
Nur für den Fall, daß sich hier zufällig auch mal ein Floating-Point-Interessierter verirrt, hier folgt die Auflösung des „Rätsels“ von # 23:
Die Lösung vo Redeamer:
Das Angebot der "Chaotic Bank Society":
Sie zahlen zunächst e - 1 $ auf Ihr Konto ein, wobei e = 2. 7182818… ist die Basis der natürlichen Logarithmen. Im ersten Jahr nehmen wir 1 $ von Ihrem Konto als Bearbeitungsgebühren. Das zweite Jahr ist besser für Sie: Wir multiplizieren Ihr Kapital mit 2, und wir nehmen 1 $ Bearbeitungsgebühren von Ihrem Konto. Das dritte Jahr ist sogar noch besser: Wir vervielfachen Ihr Kapital durch 3, und wir nehmen immer nur noch 1 $ an Bearbeitungsgebühren. Und so weiter: Im n-ten Jahr wird Ihr Kapital mit n multipliziert, und wir nehmen nur 1 Dollar Gebühren. Interessant, nicht wahr?
Wie hoch wäre Ihr Kapital nach 25 Jahren?
Exakte Lösung: 25!e-sum(i=1;25;25!/i!)=15511210043330985984000000 e - 26652630354867072870693626
Numerische Lösungen:
Currency läuft bei der Berechnung für das 18. Jahr über.
1,5511210043330986E25 Delphi Extended
1,55112100433309861E25 Delphi Double
1,55112100433655498E25 Delphi Real48
1,55112076204817268E25 Delphi Single
1.551121004333098598400000003993872967323020890367 14552103610609810902462093999452735830887464108224 0595856323132650458
… und weitere über 3500 Ziffern
ist leider keine „Erlösung“, denn Deine Formel ist falsch. Darüber hinaus hast Du gemogelt, weil Du nicht einmal Deine eigene Formel mit den verschiedenen Real-Typen berechnet hast, sondern nur Deine allerletzte Operation: 15511210043330985984000000 e - 26652630354867072870693626. Du hast dabei allerdings Deine „Zwischenergebnisse“ per Hand, mittels MatLab, Maple, Mathematica, MPA-Bibliotheken oder auf einem anderen Weg zuvor ausgerechnet und diese Zahlen als Input für die „Berechnung“ benutzt. Wenn man die Genauigkeit verschiedener Real-Typen vergleichen will, muß man schon die gesamte Berechnung damit durchführen, nicht nur einen willkürlichen Schritt. Hättest Du wenigstens Deine eigene (= falsche) Formel 25!e - sum(i=1;25;25!/i!) verwendet, dann wären folgende „Guthaben“ rausgekommen:
Guthaben Single : nach 25 Jahren = ---> Überlauf!
Guthaben Currency: nach 25 Jahren = ---> Überlauf!
Guthaben Real : nach 25 Jahren = ---> Überlauf!
Guthaben Double : nach 25 Jahren = -4.1341454764160250E+0050
Guthaben Extended: nach 25 Jahren = -4.1341454764160244E+0050
Ich denke, dieses Ergebnis brauchen wir nicht weiter zu kommentieren.
Apropos:
Die korrekte Formel für das Guthaben lautet: Guthaben nach n Jahren = n!*((e – 1) – (1/1! + 1/2! + 1/3! … + 1/n!)) .
Und das korrekte Guthaben nach 25 Jahren beträgt genau 0,03993873… $, also knapp 4 Cent.

Die Berechnung des Guthabens im vorliegenden Fall über die Formel mit den Fakultäten ist bereits ein grober Verstoß gegen das „kleine ABC“ der Gleitkomma–Arithmetik & jedweder numerischen Rechenregeln, weil es hier um die Differenzbildung zweier riesengroßer Zahlen handelt. Die Fakultät von 25, also 25! = 15511210043330985984000000 ist eine Zahl mit 26 Ziffern. Dabei verliert unser „Double“ durch arithmetische Rundung „hinten“ bereits 8 bis 9 Ziffern. Selbst bei Extended bußen wir immer noch 6 ..7 Ziffern ein. Wenn davon eine ähnlich große Zahl subtrahiert werden sollen, dann gehen uns genauso viele Nachkommastellen „hinten“ – also genau im Ergebnis – flöten. Kein Wunder, daß hierbei ein ziemlich „ungewöhnliches“ Resultat rauskommt, welches wir getrost in den Papierkorb werfen können. Daher ist dieser Weg über Fakultäten – selbst bei der Verwendung von Multipräzisionsarithmetik – nicht zu empfehlen.
Allerdings geht es bei der Beispiel-Aufgabe um die Demonstration des möglichen verheerenden Effektes winziger Rundungsfehler, die je nach verwendetem Floating-Point-Type Single, Double, Extended bei ca. 1E-8, 1E-15 und 1E-18 liegen, aber sich u. U. beachtlich aufschaukeln können.
Und vor allem, man sollte seine Ergebnisse und Formeln stets gewissenhaft überprüfen und ausgiebig testen, bevor man eine Vielzahl von falschen Ziffern raushaut… Numerische Mathematik bringt einen oft zur Verzweiflung, aber sie macht einen vor allem etwas bescheidener…
Viel einfacher und anschaulicher ist daher folgende For - Schleife:
Delphi-Quellcode:

VAR
  i : Integer;
  Guthaben_s: Single;
  Guthaben_c: Currency;
  Guthaben_r: Real48;
  Guthaben_d: Double;
  Guthaben_x: Extended;

CONST
  Br = 25;
  St = 20;

      Guthaben_s:= exp(1) - 1;
      Guthaben_c:= exp(1) - 1;
      Guthaben_r:= exp(1) - 1;
      Guthaben_d:= exp(1) - 1;
      Guthaben_x:= exp(1) - 1;

      WriteLn('StartGuthaben: e - 1 => Single : ', Guthaben_s:Br:St);
      WriteLn('StartGuthaben: e - 1 => Currency: ', Guthaben_c:Br:St);
      WriteLn('StartGuthaben: e - 1 => Real : ', Guthaben_r:Br:St);
      WriteLn('StartGuthaben: e - 1 => Double : ', Guthaben_d:Br:St);
      WriteLn('StartGuthaben: e - 1 => Extended: ', Guthaben_x:Br:St);
      WriteLn;
            
      For i := 1 To 25 Do
      Begin
        Guthaben_s := Guthaben_s*i - 1;
        // Guthaben_c := Guthaben_c*i - 1; // Überlauf nach 21 Jahren!
        Guthaben_r:= Guthaben_r*i - 1;
        Guthaben_d := Guthaben_d*i - 1;
        Guthaben_x := Guthaben_x*i - 1;
        // WriteLn('Guthaben Double : nach ' + (i).ToString + ' Jahren = ', Guthaben_d:Br:St);
        // WriteLn('Guthaben Extended: nach ' + (i).ToString + ' Jahren = ', Guthaben_x:Br:St);
      End;

      WriteLn;
      WriteLn('Guthaben Single : nach ' + (i-1).ToString + ' Jahren = ', Guthaben_s:Br:St);
      // WriteLn('Guthaben Currency: nach ' + (i-1).ToString + ' Jahren = ', Guthaben_c:Br:St);
      WriteLn('Guthaben Currency: nach ' + (i-1).ToString + ' Jahren = ','---> Überlauf nach 21 Jahren!');
      WriteLn('Guthaben Real : nach ' + (i-1).ToString + ' Jahren = ', Guthaben_r:Br:St);
      WriteLn('Guthaben Double : nach ' + (i-1).ToString + ' Jahren = ', Guthaben_d:Br:St);
      WriteLn('Guthaben Extended: nach ' + (i-1).ToString + ' Jahren = ', Guthaben_x:Br:St);
      WriteLn;
      WriteLn('Guthaben KORREKT : nach 25 Jahren = 0.0399387...');
Obige Berechnung liefert folgende Ergebnisse:

Guthaben Single : nach 25 Jahren = 5.68654735142289E+0017 (= 569 tausend Billionen!)
Guthaben Currency: nach 25 Jahren = ---> Überlauf nach 21 Jahren!
Guthaben Real : nach 25 Jahren = -1.30694632129600E+0013 (= -13 Billionen SCHULDEN!)
Guthaben Double : nach 25 Jahren = 1.20180724741045E+0009 (= 1,20 Milliarden!)
Guthaben Extended: nach 25 Jahren = 1.05291085275662E+0006 (= 1,05 Millionen!)

Guthaben KORREKT : nach 25 Jahren = 0.0399387...

Auch Excel rechnet uns arm:
Guthaben Excel Professional (2016): nach 25 Jahren = -2242373258,57016 (= -2,24 Milliarden SCHULDEN!)
Und je nach verwendetem Taschenrechner bekommen wir weitere recht „interessante“ Resultate.

Für die halbwegs korrekte Berechnung des Guthabens im 25-ten Jahr brauchen wir mindestens 30 wertvolle Ziffern, die Delphi von Haus nicht bietet. Interessant ist dabei, daß alle (korrekten) Zwischenergebnisse im Bereich 1,72 … 0 liegen: Es geht also um ganz „normale“ Alltags-Zahlen. Leider versagen hier alle „eingebauten“ Typen von Delphi. Excel – ein tolles Programm! – rechnet hier u .a. wegen eines winzigen Rundungsfehlers in der 15-ten Stelle bei der Zahl e) etwas falscher als Delphi‘s Double und bekommt für den Kontoinhaber anstelle des „erwarteten“ Milliarden-Guthabens einen beinahe doppelt so hohen Schuldenberg raus...
Dieses einfache Beispiel sollte uns zeigen, daß bei Gleitkomma-Berechnungen stets Vorsicht geboten ist. Selbst zuverlässige, fehlertolerante Algorithmen, Multipräzisions-Arithmetik und ausgiebige Tests sind keine Garantie vor gelegentlichen bösen Überraschungen.

Aber ich denke, es genügt: Schließlich interessiert es hier eh niemanden.
Gruß, Andreas
Grüße, Andreas
Wenn man seinem Nächsten einen steilen Berg hinaufhilft, kommt man selbst dem Gipfel näher. (John C. Cornelius)
  Mit Zitat antworten Zitat