Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Programmieren allgemein (https://www.delphipraxis.net/40-programmieren-allgemein/)
-   -   Delphi Single runden (https://www.delphipraxis.net/184334-single-runden.html)

Scurra 18. Mär 2015 14:50

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:
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;
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.

Um dieses Problem zu lösen, hatte ich die Idee, die Funktion SetPrecision zunächst noch einmal rekursiv aufzurufen, etwa mit:
Delphi-Quellcode:
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;
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.

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.

Sherlock 18. Mär 2015 14:55

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

gammatester 18. Mär 2015 15:07

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.

himitsu 18. Mär 2015 15:28

AW: Single runden
 
Delphi-Referenz durchsuchenRoundTo? :stupid:


Den Datentypen Extended solltest du besser nicht verwenden.
Reicht Double denn wirklich nicht aus?


http://docwiki.embarcadero.com/RADSt...eitkommawerten
http://docwiki.embarcadero.com/RADSt...e_%28Delphi%29
http://docwiki.embarcadero.com/Libra...ystem.Extended
http://docwiki.embarcadero.com/RADSt...e_Datenformate -> Auf der 64-Bit-Intel-Plattform und ARM-Platform ist der Typ Extended ein Alias für den Typ Double ...
http://docwiki.embarcadero.com/RADSt...ommaarithmetik
http://docwiki.embarcadero.com/RADSt...d_%28Delphi%29

BadenPower 18. Mär 2015 15:33

AW: Single runden
 
Zitat:

Zitat von himitsu (Beitrag 1293945)
Delphi-Referenz durchsuchenRoundTo? :stupid:

SimpleRoundTo()

Dann braucht er nicht noch SetRoundMode, wenn er kaufmännisch runden will.

Scurra 18. Mär 2015 17:39

AW: Single runden
 
Zitat:

Exaktes Runden ist übrigens ein Klasse Oxymoron.
Ich hätte wohl besser "richtiges Runden" schreiben sollen ;)

Ok, dann ist es wohl besser, wenn ich Double verwende. Diese Genauigkeit genügt mir eigentlich auch schon. Das Runden sollte am besten für Single und Double gleiche Ergebnisse liefern. Gemäß dem obersten Link ist SimpleRoundTo genau die Funktion, die ich haben möchte.

Allerdings gibt es auch hier ein für mich unerwünschtes Verhalten (siehe die jeweiligen Ergebnisse als Kommentar):
Delphi-Quellcode:
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;
Oder liegt das nur daran, dass ich es von zu Hause aus gerade nicht mit Delphi testen kann, sondern nur mit Lazarus?

Zitat:

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.
Ich sehe zwar in meinem Code nicht so etwas wie round-to-even, aber sollte dann 49.99995 nicht erst recht auf 45 gerundet werden, wenn man auf 4 Nachkommastellen rundet (9.5 wird zur geraden Zahl, also zu 10 gerunden)?

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.

Sir Rufo 18. Mär 2015 17:47

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:
Currency
sein.

Scurra 18. Mär 2015 18:17

AW: Single runden
 
Zitat:

Der Wert der da herauskommt hat welche Bedeutung?
Ich habe ein Dokument, in dem sich Formen (Rechtecke, Bilder o. ä.) befinden. Ich möchte beispielsweise die Positionen der Formen oder die Seitenränder (z. B. in cm) o. ä. speichern und später wieder abrufen können. Da die Werte, die ich auslesen kann, aber immer x-Stellen lang sind und ich nicht so eine hohe Genauigkeit brauche, runde ich die Zahlen lieber.

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.

BadenPower 18. Mär 2015 19:15

AW: Single runden
 
Wenn Dir ein Double reicht, dann kannst Du folgendes machen, um die richtigen gerundeten Werte zu erhalten:

Delphi-Quellcode:
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;
Deine Gleitkomma-Variablen müssen allerdings vom Typ Double sein.

Scurra 18. Mär 2015 19:35

AW: Single runden
 
Funktioniert das wirklich?

Delphi-Quellcode:
ShowMessage(FloatToStr(SimpleRoundTo(x2, -4)));
Das liefert bei mir 44,9999 (anstatt 45).

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

BadenPower 18. Mär 2015 19:42

AW: Single runden
 
Zitat:

Zitat von Scurra (Beitrag 1293977)
Funktioniert das wirklich?

Ja:
getestet auf WinXP mit Delphi2007.

Nein:
getestet auf WinXP mit Lazarus.

Scurra 18. Mär 2015 20:06

AW: Single runden
 
Ok, dann bin ich froh, dass es wahrscheinlich nur an Lazarus liegt, dass es bei mir nicht funktioniert. Ich habe mir jetzt einmal genau angeschaut, was SetPrecisionMode und SetRoundMode bedeutet. Für Single funktioniert das ganze vmtl., wenn ich SetPrecisionMode(pmSingle) verwende. Gibt es eine Möglichkeit, abzufragen, ob eine Zahl Single oder Double ist. Ich habe es mit

Delphi-Quellcode:
var
  x1: Single

if x1 is Single then ...
versucht, aber führt zu einem Fehler: Class or interface type expected, but got "Single".

Ansonsten könnte ich wohl auch 2 überladene Versionen der Funktion verwenden:

Delphi-Quellcode:
function(aValue: Single; aPrecision: Integer): Single; overload;
function(aValue: Double; aPrecision: Integer); Double; overload;
Noch eine Frage zum Rundungsmodus rmTruncate: In der Hilfe steht hierzu: Truncates the value, rounding positive numbers down and negative numbers up.

Warum wird dann 44.6 (positive Zahl) auf 45 aufgerundet?

Danke für deine/eure Hilfe!

BadenPower 18. Mär 2015 20:56

AW: Single runden
 
Zitat:

Zitat von Scurra (Beitrag 1293981)
Noch eine Frage zum Rundungsmodus rmTruncate: In der Hilfe steht hierzu: Truncates the value, rounding positive numbers down and negative numbers up.

Warum wird dann 44.6 (positive Zahl) auf 45 aufgerundet?

Die deutsche Hilfe sagt da aber etwas anderes:
Code:
Schneidet den Wert ab. Positive Werte werden auf-, negative Werte abgerundet.

Sir Rufo 18. Mär 2015 21:02

AW: Single runden
 
Ich hatte da auch noch was
http://docwiki.embarcadero.com/RADSt...ositive_Zahlen

Medium 18. Mär 2015 23:42

AW: Single runden
 
Wenn einen Dinge wie 49,9999995743 statt 50 stören (also nicht nur in der Anzeige, da ist das ja lösbar, sondern in der Rechnung selbst), muss man i.A. auf Fließkommazahlen verzichten. Auch wenn die Operanden und Ergebnisse vielleicht genau darstellbar wären, heisst es noch lange nicht, dass der Rechenweg (in diesem Fall die Rundung) auch nur mit exakt darstellbaren Zwischenwerten passiert. Da gibt es auch keinen Weg, dies für den generellen Fall zu garantieren. Festpunkt Arithmetik, oder lebe damit.
Und es ist ja nicht so, als hätten wir das Thema "Ungenauigkeit von Floats" fast wöchentlich hier. Es ist eben eine gegebene Eigenschaft dieser Technik. Daher wundert mich, dass die anderen Antworten darauf bisher überhaupt nicht eingegangen sind.
Du findest also mehr Material dazu hier als einem lieb wäre. ;)

Dejan Vu 19. Mär 2015 03:27

AW: Single runden
 
Zitat:

Zitat von Medium (Beitrag 1293989)
Und es ist ja nicht so...

Langsam bin ich dafür, Floatingpoint zu verbieten. Extended, Double und Single sollte ab sofort BCD oder decimal mit der gleichen Genauigkeit, von mir aus 1024 byte groß. Speicher kostet ja nichts mehr. :stupid:

Obwohl. Dann fehlen 10% der Forumsbeiträge.

himitsu 19. Mär 2015 04:08

AW: Single runden
 
Egal auf was man rundet, in Fließkommazahlen bleiben dennoch die bekannten Rundungsfehler enthalten, welche auf Grund des Speicherformates entstehen.

Will man unbedingt in der Anzeige ein bestimmtes Format, dann sollte man auch an dieser Stelle das Format entsprechend vorgeben.
Format, FloatToStrF, Str, FloatToTextFmt, ...

Dejan Vu 19. Mär 2015 06:04

AW: Single runden
 
BCD und decimal sind jetzt aber nicht unbedingt Floating Point Zahlen. Das ist ja gerade der Witz.

Scurra 21. Mär 2015 11:52

AW: Single runden
 
Meine "Lösung" sieht jetzt so aus, dass ich ganz einfach die Rundungsfehler akzeptiere. Bei 4 Nachkommastellen, auf die ich meistens runde, macht es auch keinen so großen Unterschied. Gravierend würde es werden, wenn ich auf eine ganze Zahl runden möchte und es wird falsch gerundet.

Danke an alle, die geholfen haben!

Dejan Vu 21. Mär 2015 12:47

AW: Single runden
 
Und manchmal wirst Du einen Cent daneben liegen. Und Du wirst nichts dagegen unternehmen können.

Aber klar, wenn Du dein Privatportfolio von mehreren Hundert Mio Euro damit verwaltest, ist das eh wurscht. :lol:

Scurra 21. Mär 2015 13:11

AW: Single runden
 
Zitat:

Zitat von Dejan Vu (Beitrag 1294329)
Und manchmal wirst Du einen Cent daneben liegen. Und Du wirst nichts dagegen unternehmen können.

Aber klar, wenn Du dein Privatportfolio von mehreren Hundert Mio Euro damit verwaltest, ist das eh wurscht. :lol:

Wie gesagt, mein Problem hat nichts mit Geld zu tun, aber ja. Prinzipiell hast du recht ;)

BUG 21. Mär 2015 13:34

AW: Single runden
 
Ha, was fehlt noch: Symbolisches Rechnen!
Dann stehen da 4711,1337*sin(3*sqrt(2)*pi)^2 Cent auf der Rechnung.

Dejan Vu 22. Mär 2015 07:37

AW: Single runden
 
Zitat:

Zitat von Scurra (Beitrag 1294333)
Wie gesagt, mein Problem hat nichts mit Geld zu tun, aber ja. Prinzipiell hast du recht ;)

Wie kam ich nur darauf? Alles klar. Im normalen Leben reichen 5-6 Stellen für die Darstellung vollkommen aus, das entspricht einer Genauigkeit von 0,001%.


Alle Zeitangaben in WEZ +1. Es ist jetzt 05:41 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