effizientes Rechnen mit gerundeten Werten?

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 1430
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Lazarus Fixes FPC Stable
CPU-Target: 32/64Bit
Wohnort: Echzell

effizientes Rechnen mit gerundeten Werten?

Beitrag von fliegermichl »

Hi,

ich erstelle gerade in meinem Cad Programm eine Funktion "Holzliste". Die erzeugt an dem konstruierten Dach, in bestimmten Abständen Sparren mit vorgegebener Breite und Höhe.

Die Ausgabe soll der Holzbedarf in m³ sein.
Jetzt ist es aber so, daß meine Werte Double Typen sind. Wenn ich die bei der Ausgabe auf 3 Nachkommastellen formatiere, dann stimmen die Produkte/Summen nicht mehr überein.

Gibt es eine effiziente Möglichkeit, alle Berechnungen mit auf 3 Nachkommastellen gerundeten Werten durchzuführen?

Code: Alles auswählen

qm := TryStrToFloat(Format('%.3f', [Breite])) * TryStrToFloat(Format('%.3f', [Hoehe])) * TryStrToFloat(Format('%.3f', [Laenge]));
funktioniert, ist aber sicherlich eher sehr ineffizient.

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: effizientes Rechnen mit gerundeten Werten?

Beitrag von wp_xyz »

Es ist nun mal so, dass eine Division meistens nicht ohne Rest aufgeht, und dass manche Zahlen (wie Wurzel(2) oder pi) sich nicht mit einer endlichen Zahl von Dezimalstellen genau darstellen lassen. Damit sich die Fehler nicht aufschaukeln, darfst du nie mit gerundeten Werten rechnen. Gerundete Werte nur für die Ausgabe. Und wenn sich die Prozente in der Ausgabe nicht zu 100 addieren, dann ist das halt so. Schreib unter die Tabelle eine Erklärung, dass sich das wegen Rundungsfehlern in der Darstellung so ergibt, die Rechnung selbst wurde mit größtmöglicher Genauigkeit durchgeführt.

Der Rückgabe-Wert von TryStrToFloat is übrigens boolean, du wendest die Funktion so an, als wäre das Double. Wahrscheinlich meinst du StrToFloat. Aber wiegesagt, jeden Faktor zuerst auf drei Stellen zu runden und dann zu multiplieren,. wird mit Sicherheit ein sehr falsches Ergebnis liefern. Besser ist, zuerst zu multiplieren und dann zu runden:

Code: Alles auswählen

procedure TForm1.Button1Click(Sender: TObject);
const
  Breite = 1.23451;
  Hoehe = 4.694356;
  Laenge = 3.5433453;
var
  qm: Double;
begin
  qm := StrToFloat(Format('%.3f', [Breite])) * StrToFloat(Format('%.3f', [Hoehe])) * StrToFloat(Format('%.3f', [Laenge]));
  ShowMessage(FormatFloat('0.000', qm));

  qm := Laenge * Breite * Hoehe;
  ShowMessage(FormatFloat('0.000', qm));
end; 

Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 1430
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Lazarus Fixes FPC Stable
CPU-Target: 32/64Bit
Wohnort: Echzell

Re: effizientes Rechnen mit gerundeten Werten?

Beitrag von fliegermichl »

Bei normalen Berechnungen mache ich das ja auch so. In dem Fall geht es eben nur um Additionen und Multiplikationen und meine Anwender sind Dachdecker. Da muss man froh sein, wenn die überhaupt lesen können.

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: effizientes Rechnen mit gerundeten Werten?

Beitrag von mschnell »

Und darum willst Du ihnen falsche Zahlen liefern ?!?!?!?
-Michael

Warf
Beiträge: 1908
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: effizientes Rechnen mit gerundeten Werten?

Beitrag von Warf »

Du könntest fixpunkt arithmetik verwenden

Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 1430
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Lazarus Fixes FPC Stable
CPU-Target: 32/64Bit
Wohnort: Echzell

Re: effizientes Rechnen mit gerundeten Werten?

Beitrag von fliegermichl »

Der Punkt ist, wenn ich eine Liste ausgebe, mit einer Einzelmenge und einer Anzahl und einer Gesamtmenge und man dann die ausgegebenen Werte nachrechnet, kommt ein anderes Ergebnis raus.

Klar, für die Ermittlung der Einzelmenge rechne ich exakt und runde dann nur für die Ausgabe auf 3 Nachkommastellen.

Aber diese ausgegebene Einzelmenge multipliziert mit der Anzahl sollte dann auch die ausgegebene Gesamtmenge ergeben.

Ich934
Lazarusforum e. V.
Beiträge: 316
Registriert: So 5. Mai 2019, 16:52
OS, Lazarus, FPC: ArchLinux und Windows mit FPCUPdeluxe (L: 2.0.X, FPC 3.2.0)
CPU-Target: x86_64, i386
Wohnort: Bayreuth

Re: effizientes Rechnen mit gerundeten Werten?

Beitrag von Ich934 »

Dann berechne deine Gesamtmenge doch einfach aus deinen "ungenauen" Werten. Wenn du das Ergebnis willst, dann musst du das so machen. Soll es mathematisch korrekt sein bekommst du die Abweichung.
Tipp für PostgreSQL: www.pg-forum.de

Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 1430
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Lazarus Fixes FPC Stable
CPU-Target: 32/64Bit
Wohnort: Echzell

Re: effizientes Rechnen mit gerundeten Werten?

Beitrag von fliegermichl »

Ich934 hat geschrieben:
Mi 29. Apr 2020, 22:57
Dann berechne deine Gesamtmenge doch einfach aus deinen "ungenauen" Werten. Wenn du das Ergebnis willst, dann musst du das so machen. Soll es mathematisch korrekt sein bekommst du die Abweichung.
Na genau darauf wollte ich doch hinaus. Ich habe aber eine Lösung gefunden, die zumindest den Umweg über die Umwandlung in einen String erspart.

Code: Alles auswählen

function  RundeMitXStellen(Wert : Double; Stellen : integer) : Double;
var Multi : cardinal;
    i : integer;
begin
 Multi := 1;
 for i := 1 to Stellen do Multi := Multi * 10;
 Result  := Trunc(Wert * Multi + 1/10*(Stellen +1)) / Multi; 
end;

Warf
Beiträge: 1908
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: effizientes Rechnen mit gerundeten Werten?

Beitrag von Warf »

Wenn du fixpunkt arithmetik machst, dann solltest du keine Doubles benutzen sondern Integer. Ansonsten kombinierst du Rundungsfehler von Double mit Rundungsfehler durch Fixpunktarithmetik und hast das schlechteste aus beiden welten.

Vor ner weile gabs mal einen Thread zu Fixpunkt Libraries, da hatte ich mal Fixpunktarithmetik erklärt:
viewtopic.php?p=112676#p112676
Sowie das ganze auch mal implementiert (und gebenchmarkt)
viewtopic.php?f=10&t=12742&p=112744

Benutzeravatar
Winni
Beiträge: 1577
Registriert: Mo 2. Mär 2009, 16:45
OS, Lazarus, FPC: Laz2.2.2, fpc 3.2.2
CPU-Target: 64Bit
Wohnort: Fast Dänemark

Re: effizientes Rechnen mit gerundeten Werten?

Beitrag von Winni »

Hallo!

Die Lösung ist ganz einfach:

Intern immer mit den krummen Werten rechnen. Nie runden!

Erst für die Ausgabe wird dann gerundet.
Aber mit diesen gerundeten Werten nicht weiterrechnen, sondern mit dem Original-Wert.

Mit Doubles besitzt Du dann 15-16 Ziffern Genauigkeit, was für Deine Zwecke ja ausreichend sein sollte.

Gerade für Deine Zwecke sind Fixpunktwerte viel zu ungenau, wenn Du Höhe x Breite x Tiefe rechnen musst.

Winni

Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 1430
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Lazarus Fixes FPC Stable
CPU-Target: 32/64Bit
Wohnort: Echzell

Re: effizientes Rechnen mit gerundeten Werten?

Beitrag von fliegermichl »

Winni hat geschrieben:
Mi 29. Apr 2020, 23:55

Intern immer mit den krummen Werten rechnen. Nie runden!

Erst für die Ausgabe wird dann gerundet.
Aber mit diesen gerundeten Werten nicht weiterrechnen, sondern mit dem Original-Wert.

Mit Doubles besitzt Du dann 15-16 Ziffern Genauigkeit, was für Deine Zwecke ja ausreichend sein sollte.

Gerade für Deine Zwecke sind Fixpunktwerte viel zu ungenau, wenn Du Höhe x Breite x Tiefe rechnen musst.
Das Volumen eines Balkens Breite x Höhe x Länge rechne ich ja mit ungerundeten Werten. Das Problem entsteht ja eben erst bei der Ausgabe.
Beispiel:

Code: Alles auswählen

0.11 * 0.2 * 1.06 = 0.02332
0.02332 * 2 Stück = 0.04664

Ausgabe: Menge 0.023	Anzahl 2 Gesamt 0.047
Da schaut der Dachdecker drauf und sagt mir, daß mein Programm nicht rechnen kann. 0,023 * 2 ist 0,046 und nicht 0,047.
Der schert sich einen Dreck um Rundungsfehler an der dritten Nachkommastelle. Da geht es darum, ein Angebot zu erstellen und da sollte Einzelmenge * Anzahl = Gesamtmenge stimmen.

Und deshalb rechne ich die Gesamtmenge mit der gerundeten Einzelmenge.

Gruß
Michael

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: effizientes Rechnen mit gerundeten Werten?

Beitrag von mschnell »

Winni hat geschrieben:
Mi 29. Apr 2020, 23:55
Gerade für Deine Zwecke sind Fixpunktwerte viel zu ungenau, wenn Du Höhe x Breite x Tiefe rechnen musst.
Nicht wenn die Einheit µm, Ångström, pm, oder Plank-Länge ist.
-Michael

Ich934
Lazarusforum e. V.
Beiträge: 316
Registriert: So 5. Mai 2019, 16:52
OS, Lazarus, FPC: ArchLinux und Windows mit FPCUPdeluxe (L: 2.0.X, FPC 3.2.0)
CPU-Target: x86_64, i386
Wohnort: Bayreuth

Re: effizientes Rechnen mit gerundeten Werten?

Beitrag von Ich934 »

Und warum rechnest du dann nicht auch oben mit gerundeten Werten? Spielt es da überhaupt eine Rolle das du auf mehr als zwei Nachkommastellen gehst?
Tipp für PostgreSQL: www.pg-forum.de

Michl
Beiträge: 2505
Registriert: Di 19. Jun 2012, 12:54

Re: effizientes Rechnen mit gerundeten Werten?

Beitrag von Michl »

fliegermichl hat geschrieben:
Mi 29. Apr 2020, 23:10
Na genau darauf wollte ich doch hinaus. Ich habe aber eine Lösung gefunden, die zumindest den Umweg über die Umwandlung in einen String erspart.

Code: Alles auswählen

function  RundeMitXStellen(Wert : Double; Stellen : integer) : Double;
...
Nur als Hinweis. Dafür gibt es schon die fertige Funktion in der Unit Math RoundTo. Z.B.:

Code: Alles auswählen

  d := 0.02332;
  WriteLn(d:1:5);
  d := RoundTo(d, -3);
  WriteLn(d:1:5);

Code: Alles auswählen

type
  TLiveSelection = (lsMoney, lsChilds, lsTime);
  TLive = Array[0..1] of TLiveSelection; 

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: effizientes Rechnen mit gerundeten Werten?

Beitrag von wp_xyz »

fliegermichl hat geschrieben:
Do 30. Apr 2020, 07:10
Das Volumen eines Balkens Breite x Höhe x Länge rechne ich ja mit ungerundeten Werten. Das Problem entsteht ja eben erst bei der Ausgabe.
Beispiel:

Code: Alles auswählen

0.11 * 0.2 * 1.06 = 0.02332
0.02332 * 2 Stück = 0.04664

Ausgabe: Menge 0.023	Anzahl 2 Gesamt 0.047
Da schaut der Dachdecker drauf und sagt mir, daß mein Programm nicht rechnen kann. 0,023 * 2 ist 0,046 und nicht 0,047.
Der schert sich einen Dreck um Rundungsfehler an der dritten Nachkommastelle. Da geht es darum, ein Angebot zu erstellen und da sollte Einzelmenge * Anzahl = Gesamtmenge stimmen.

Und deshalb rechne ich die Gesamtmenge mit der gerundeten Einzelmenge.
Mit dem Beispiel oben verstehe ich dein Problem.

0.11 ist ein Messwert. Dass du ihn auf zwei Stellen angibst, heißt, dass du über die dritte Stelle nichts aussagen kannst. Genauso bei den anderen Faktoren. Wenn man nun diese drei fehlerbehafteten Größen miteinander multipliziert, ist hat das Produkt soviele gültige Dezimalstellen wie die einzelnen Faktoren zusammen, also 2+1+2 = 5 Dezimalstellen (wahrscheinlich sogar 6, denn mit "0.2" ist wahrscheinlich "0.20" gemeint - es gibt keinen Grund, dass diese Dimension ungenauer zu messen ist als die anderen). Daher ist es meiner Meinung nach falsch, wenn du das Ergebnis auf drei Stellen rundest. Wenn der Dachdecker damit ein Problem hat, weil ihm das zuviele Stellen sind, dann muss er aber auch die Konsequenz akzeptieren, dass das gerundete Ergebnis ungenauer ist.

Antworten