![]() |
Single runden
Hallo zusammen,
ich stehe gerade vor einem Problem: Ich möchte Gleitkommazahlen (vom Typ Single, um genauer zu sein), exakt (d. h. kaufmännisch) runden. Das Runden soll für verschiedene Genauigkeiten (Nachkommastellen) möglich sein. Dazu habe ich mir folgende Funktion geschrieben (ich arbeite hier mit dem Datentyp Extended, weil ich bei der Verwendung von Single noch mehr Probleme habe, obwohl ich in meinem Programm immer Werte vom Typ Single an die Funktion übergebe):
Delphi-Quellcode:
Das funktioniert auch fast immer. Allerdings gibt es noch Probleme, wie mir mit dUnit gerade aufgefallen ist. Wenn an der entscheidenen Stelle fürs Runden eine 5 steht, dann kommt es vor, dass falsch gerundet wird, da die Zahlen intern wohl anders gespeichert werden (die Zahl 2.5 ist nicht nur 2.5, sondern so etwas wie 2.4999845). Dann wird natürlich 2.5 nicht auf 3, sondern auf 2 gerundet.
function SetPrecision(aValue: Extended; aPrecision: Integer): Extended;
var multi : Single; begin if aPrecision < 0 then Result := aValue else begin multi := IntPower(10, aPrecision); if aValue >= 0 then aValue := Trunc(aValue*multi + 0.5) else aValue := Trunc(aValue*multi - 0.5); Result := aValue/multi; end; end; Um dieses Problem zu lösen, hatte ich die Idee, die Funktion SetPrecision zunächst noch einmal rekursiv aufzurufen, etwa mit:
Delphi-Quellcode:
Das funktioniert leider immer noch nicht. Ich habe den Debugger verwendet, um nachvollziehen zu können, was schief läuft (daher z. B. auch die Zeile if Frac(aValue) <> 0 then aValue := Trunc(aValue); Genau in dieser Zeile tritt das Problem auf. Nehmen wir als Beispiel den Aufruf Aufruf von SetPrecision(x, 4) mit der Variable x = 44.99995 als Single. Laut Debugger ist im zweiten Durchlauf direkt vor Ausführen der oben genannten Zeile aValue = 450000. Intern wird da aber wohl wieder irgendein Rest mitgezogen, so dass die if-Abfrage ein true liefert und nach Trunc(aValue) habe ich 449999. Am Ende steht also der Wert 44.9999 dran, obwohl es 45 sein sollte.
function SetPrecision(aValue: Extended; aPrecision: Integer; firstTime: Boolean = True): Extended;
var multi : Single; begin if firstTime then aValue := SetPrecision(aValue, aPrecision+1, false); if aPrecision < 0 then Result := aValue else begin multi := IntPower(10, aPrecision); if aValue >= 0 then //aValue := Trunc(aValue*multi + 0.5) begin aValue := aValue*multi; aValue := aValue +0.5; if Frac(aValue) <> 0 then aValue := Trunc(aValue); end else aValue := Trunc(aValue*multi - 0.5); Result := aValue/multi; end; end; Hat jemand eine Idee, wie ich das Problem in den Griff bekommen kann? P.S.: Ja, ich habe schon im Internet nach Lösungen gesucht. Wie ich gesehen habe, gibt es schon einige Beiträge dazu, aber mein Problem konnte ich leider nicht lösen. |
AW: Single runden
An welcher Stelle versagen denn da die Bordmittel? Es gibt ja einige Rundungsmethoden in Delphi. Exaktes Runden ist übrigens ein Klasse Oxymoron.
Sherlock |
AW: Single runden
Du vermischt zwei verschiedene Aspekte beim Runden: 2.5 ist exakt als single darstellbar! Aber es wird round-to-even benutzt und deshalb nicht auf die ungerade 3 aufgerundet sondern auf die gerade 2 abgerundet. 3.5 ist auch exakt darstellbar, wird aber auf 4 gerundet.
|
AW: Single runden
![]() Den Datentypen Extended solltest du besser nicht verwenden. Reicht Double denn wirklich nicht aus? ![]() ![]() ![]() ![]() ![]() ![]() |
AW: Single runden
Zitat:
Dann braucht er nicht noch SetRoundMode, wenn er kaufmännisch runden will. |
AW: Single runden
Zitat:
Zitat:
Allerdings gibt es auch hier ein für mich unerwünschtes Verhalten (siehe die jeweiligen Ergebnisse als Kommentar):
Delphi-Quellcode:
Oder liegt das nur daran, dass ich es von zu Hause aus gerade nicht mit Delphi testen kann, sondern nur mit Lazarus?
procedure TForm1.Button1Click(Sender: TObject);
var x1: Single; x2: Double; y1: Single; y2: Double; begin x1 := 44.99995; x2 := 44.99995; ShowMessage(FloatToStr(SimpleRoundTo(x1, -4))); // liefert 45 ShowMessage(FloatToStr(SimpleRoundTo(x2, -4))); // liefert 44.9999 y1 := 4.99995; y2 := 4.99995; ShowMessage(FloatToStr(SimpleRoundTo(y1, -4))); // liefert 4.999899864 ShowMessage(FloatToStr(SimpleRoundTo(y2, -4))); // liefert 5 end; Zitat:
Danke schon einmal für eure Hilfe. Bisher waren mir nur die Rundungsfunktionen wie Trunc, Ceil und Round bekannt. Beim googlen bin ich fast nur auf Beiträge gestoßen, wo das kaufmännische Runden selbst in Funktionen implementiert wurde. Das wundert mich, wenn es doch schon bereitgestellte Funktionen dafür gibt. |
AW: Single runden
Ich stell mal die entscheidende Frage:
Der Wert der da herauskommt hat welche Bedeutung? Etwa ein Währungsbetrag? Dann bist du mit deiner Rechnerei eh auf dem Holzweg, denn der Wert sollte dann final vom Typ
Delphi-Quellcode:
sein.
Currency
|
AW: Single runden
Zitat:
Natürlich sollte immer nach der gleichen Vorschrift gerundet werden, aber solche Grenzfälle mit .5 bestehen meistens (meistens wird wohl auch bei .5 nach meiner Erwartung gerundet) meine Testcases... Dass da etwas nicht richtig funktioniert, ist mir aber erst durch Testcases (mit dUnit) aufgefallen. |
AW: Single runden
Wenn Dir ein Double reicht, dann kannst Du folgendes machen, um die richtigen gerundeten Werte zu erhalten:
Delphi-Quellcode:
Deine Gleitkomma-Variablen müssen allerdings vom Typ Double sein.
var
x2: Double; y2: Double; begin SetPrecisionMode(pmDouble); SetRoundMode(rmTruncate); x2 := 44.99995; y2 := 4.99995; ShowMessage(FloatToStr(SimpleRoundTo(x2, -4))); ShowMessage(FloatToStr(SimpleRoundTo(y2, -4))); end; |
AW: Single runden
Funktioniert das wirklich?
Delphi-Quellcode:
Das liefert bei mir 44,9999 (anstatt 45).
ShowMessage(FloatToStr(SimpleRoundTo(x2, -4)));
Was mir sonst gerade einfällt: Es ist zwar umständlich, aber falls das mit dem Runden einfach nicht klappt, muss man eben "von Hand" runden: Man frägt ab, ob die fürs Runden entscheidende Ziffer (z. B. 5. Ziffer hinter dem Punkt/Komma) eine 1, 2, 3 oder 4 ist, dann soll abgerundet werden, bei 5, 6, 7, 8 oder 9 soll aufgerundet werden und bei 0 soll die Zahl nur abgeschnitten werden. Eine einfachere und elegantere Lösung würde ich bevorzugen :D |
Alle Zeitangaben in WEZ +1. Es ist jetzt 15:51 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz