Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Programmieren allgemein (https://www.delphipraxis.net/40-programmieren-allgemein/)
-   -   Nur x Nachkommastellen von Float (https://www.delphipraxis.net/213829-nur-x-nachkommastellen-von-float.html)

anse 2. Okt 2023 17:05

Nur x Nachkommastellen von Float
 
Nabend zusammen,

es gibt ja System.Trunc, um den Integer Teil eines Floats zu bekommen.
Gibt es quasi als Gegenstück eine Funktion die nur die ersten Ziffern des Dezimalteils liefert?
Also z.B. sowas
Code:
GetDigits(10.234567, 3); => "234"
Meine Alternative funktioniert auch aber finde ich nicht schön:
  1. Integer-Teil vom Float subtrahieren: 10.234567 - 10 => 0.234567
  2. Ergebnis mit 1000 mulitiplizieren: => 234.567
  3. System.Trunc() darauf: => 234

Uwe Raabe 2. Okt 2023 17:29

AW: Nur x Nachkommastellen von Float
 
Punkt 1 ließe sich auch durch
Delphi-Quellcode:
Frac(x)
ersetzen.
Wenn es Runden statt Abschneiden sein darf, dann wäre
Delphi-Quellcode:
RoundTo
eine Alternative. Theoretisch kann man vorher mit
Delphi-Quellcode:
SetRoundMode
einstellen, wie das erfolgen soll (damit ginge auch ein truncate), aber das ist natürlich nicht threadsicher.

mmw 2. Okt 2023 17:57

AW: Nur x Nachkommastellen von Float
 
hallo,
evtl. so (Rohversion)
Delphi-Quellcode:
function GetDigits(Value:EXTENDED;digit:integer):Extended;

var
   value1,
   value2:extended;

begin
 value1:=0;
 value2:=0;
  value1:=frac(value);
  value2:=Power(10,digit);
 result:=trunc(value1*value2);
end;
Gruß

Uwe Raabe 2. Okt 2023 21:39

AW: Nur x Nachkommastellen von Float
 
Da digit ein Integer ist, bietet sich auch IntPower an:
Delphi-Quellcode:
function GetDigits(const AValue: Double; Digits: Integer): Integer;
begin
  Result := Trunc(Frac(AValue)*IntPower(10, Digits));
end;

anse 3. Okt 2023 13:10

AW: Nur x Nachkommastellen von Float
 
Vielen Dank, das sieht schon eleganter/lesbarer aus!

Michael II 8. Okt 2023 01:40

AW: Nur x Nachkommastellen von Float
 
Double Zahlen sind etwas gefährlich. Die Mantisse bei double ist 52 Bits gross. Wenn ein Dezimalbruch binär geschrieben unendlich viele Stellen hat, dann kann die Mantisse mit ihren 52 Bits unendlich viele der 1en nicht aufnehmen.

Beispiel 0.1(10) ist binär geschrieben periodisch 0.000110011001100....(2) mit Periode 1100. D.h. die Dezimalzahl 0.1 lässt sich als double nicht genau abspeichern. Es werden nur die ersten 52 Bits NACH der ersten 1 (implizites Bit) abgespeichert.

Wenn zum Beispiel genau die ersten 52 Bits der Binärdarstellung eines solchen Bruchs abgespeichert werden, dann ist der effektiv abgespeicherte Wert etwas zu klein (Bits 53+ gehen alle verloren).

Delphi-Quellcode:
procedure testD;
var avalue, d1, d2 : double;
   a,b, res : integer;
begin
  avalue := 98.103;
  d1 := frac(avalue);
  d2 := d1 * 1000;
  res := trunc(d2);
  showmessage(res.ToString);
  showmessage(getdigits(avalue,3).ToString );
end;
wobei GetDigits die weiter oben vorgeschlagene Funktion ist.
Wenn der Code oben in Delphi ausgeführt wird, dann ist d1=0.102999999999994; der Output lautet beide Male 102; wir hätten gern 103.

Was helfen kann:
Wenn man den gespeicherten Wert um den kleinstmöglichen Wert Epsilon = 0 00 00 00 00 00 01 (hex) erhöht, dann liegt im Speicher neu eine etwas zu grosse Zahl. trunc funktioniert dann wie erwartet.

Hinweis: Der Code plusepsilon (unten) kann Plattform abhängig sein und berücksichtigt den Fall "alle 52 Mantissenbits=1" nicht.

Delphi-Quellcode:
function plusepsilon(d: Double): Double;
// addiert die kleinstmögliche zahl zu d.
// Bedingung: d>0
var
  Bits: Int64 absolute d;
begin
  Inc(Bits);
  Result := d;
end;

procedure testD;
var avalue, d1, d2 : double;
   a,b, res : integer;
begin
  avalue := plusepsilon(98.103);
....
(Es gibt natürlich weitere, weniger "mathematische" Lösungen... ;-))

Andreas13 8. Okt 2023 09:57

AW: Nur x Nachkommastellen von Float
 
Hallo Michael,
so einfach läßt sich das Problem der Rundungsfehler leider nicht lösen. Eigentlich gibt es überhaupt keine Lösung dafür. Denn es wird nicht nur abgerundet, sondern auch mal aufgerundet. Und dann müßte Dein plusepsilon ein minusepsilon sein.

Sorry, Deine Lösung ist nicht „mathematisch“, sondern willkürlich an diese eine Aufgabe zugeschnitten.

Was einzig alleine hilft, sind mehr, vieeeeeel mehr Stellen, also nicht Double, sondern Extended, quad precision, oder oft nur Multi-Precisions-Arithmetik (MPA) mit hunderten ... tausenden Nachkommastellen, wobei das Ergebnis am Ende aller Berechnungen auf das Zielformat gerundet wird.

Über diese Problematik haben sich schon viele Forscher den Kopf zerbrochen und zahlreiche gute Artikel und Fachbücher publiziert, ohne ein Patentrezept verkünden zu können...

Michael II 8. Okt 2023 13:35

AW: Nur x Nachkommastellen von Float
 
Zitat:

Zitat von Andreas13 (Beitrag 1527829)
Hallo Michael,
Denn es wird nicht nur abgerundet, sondern auch mal aufgerundet. Und dann müßte Dein plusepsilon ein minusepsilon sein

Es darf für positive Zahlen nie minusepsilon sein:
Wenn ein Dezimalbruch binär periodisch ist, dann wird das System beim Speichern von double wahrscheinlich entweder aufrunden, wenn das 53ste Bit=1 ist oder aber ab Bit 53 abschneiden (und damit abrunden). Das System speichert also entweder z1<z oder z2>z. Im Intervall ]z1,z2[ kann das Format nix speichern.
Es gibt also 2 Fälle.
1. Das System hat abgeschnitten oder abgerundet und speichert z1. Wir addieren Epsilon und landen bei z2>z und trunc() wird den erwarteten Wert ermitteln.
2. Das System hat aufgerundet und hat z2 gespeichert. Wir könnten also mit z2 weiterrechnen und trunc() wäre OK. z3:=plusepsilon(z2)=z2+Epsilon ist der nächstgrössere speicherbare Wert nach z2. Also z3>z2>z. Wenn die Stellengenauigkeit vom verwendeten Format gross genug ist für das gegebene Problem, dann fällt es nicht ins Gewicht, dass wir nicht mit z2 weiterrechnen. Wenn wir aber wie du schreibst abrunden würden, dann würden wir bei z1 := z2-Epsilon landen. Wir wissen aber z1<z und damit rechnen "wir" beim trunc() wieder falsch. Also keine gute Idee.

Michael II 8. Okt 2023 14:09

AW: Nur x Nachkommastellen von Float
 
Zitat:

Zitat von Andreas13 (Beitrag 1527829)
.

Sorry, Deine Lösung ist nicht „mathematisch“, sondern willkürlich an diese eine Aufgabe zugeschnitten.

Was einzig alleine hilft, sind mehr, vieeeeeel mehr Stellen, also nicht Double, sondern Extended, quad precision, oder oft nur Multi-Precisions-Arithmetik (MPA) mit hunderten ... tausenden Nachkommastellen, wobei das Ergebnis am Ende aller Berechnungen auf das Zielformat gerundet wird.

Über diese Problematik haben sich schon viele Forscher den Kopf zerbrochen und zahlreiche gute Artikel und Fachbücher publiziert, ohne ein Patentrezept verkünden zu können...

Du schreibst, die Lösung sei auf das Problem zugeschnitten. Dafür gebe ich dir 100 Punkte ;-). Eine Lösung sollte auf die gestellte Aufgabe zugeschnitten sein. Die vorher angegebenen Vorschläge lieferten zwischen 123.000000 und 123.999999 in mindestens 496 Fällen nicht das korrekte Resultat.

Mit Willkür hat das nix zu tun. Aber du hast Recht, das ist 1. Semster Informatik Stoff und hat mit Mathe nicht viel zu tun - eher mit Rechnen.

Du schreibst Extended[/URL"]extended wäre besser. Mehr Binärstellen nützen leider nicht viel:
Dezimalbrüche, welche binär periodisch sind können in einem binären Format (wie von single, double, extended verwendet) nie genau gespeichert werden. Mit extended erhöhst du lediglich die Stellengenauigkeit (extended: floor(log(2^63)/log(10) - generell log(2^n)/log(2) = n*log(2)/log(10) - d.h. pro weiteres Bit für den fraction Teil gewinnst du ungefähr 0.301 Stellen). Wenn du für das in #1 geschilderte Problem als Input Zahlen mit vielen Stellen erwartest, dann nimm extended. Für die trunc()erei wirst du bei solchen Datentypen um das Epsilon nicht herumkommen.

Es gibt sicher Leute, welche das Speichern und das Rechnen mit solchen Daten als "Forschung" bezeichnen. Mich wundert heute nix mehr ;-)

mmw 8. Okt 2023 20:14

AW: Nur x Nachkommastellen von Float
 
Hallo,

als Korrektur zu #3, (mit DOUBLE gab's Probleme)

Delphi-Quellcode:
function GetDigits(const AValue: Extended; Digits: Integer): Integer;

var
    value:Extended;
    value1:Real;

begin
   value:=frac(AValue)*intpower(10,digits);
   value1:=value;
   Result := trunc(value1);
end;

Gruß


Alle Zeitangaben in WEZ +1. Es ist jetzt 16:43 Uhr.
Seite 1 von 2  1 2      

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz