ROUND() unter Windows vs. ROUND() unter Linux

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Antworten
Joh
Lazarusforum e. V.
Beiträge: 271
Registriert: Sa 26. Mai 2012, 17:31
OS, Lazarus, FPC: Win 10 (L 2.2.6 x64 FPC 3.2.2)
CPU-Target: 64Bit

ROUND() unter Windows vs. ROUND() unter Linux

Beitrag von Joh »

Da der erste Thread zerfleddert wurde, fange ich hier noch einmal neu an:

Hier ein Miniprogramm:

Code: Alles auswählen

program project4;

var x: double;
    y: currency;

begin
  x := 2.5;
  y := 2.5;
  writeln;
  writeln(Round(x));
  writeln(Round(y));
  readln;
end.
Unter Windows ergeben die beiden Funtionen unterschiedliche Werte:
Round(x)=2,
Round(y)=3


Unter Linux wird immer die Bankers-Round-Funktion verwandt,
Hier ergeben die beiden Funtionen unterschiedliche Werte:
Round(x)=2,
Round(y)=2


unter Windows wird, sobald auch nur ein Currency-Wert innerhalb der Klammern steht, eine andere Funktion verwandt.

Diese Funktion findet man in den Sourcen scheinbar unter lazarus\fpc\3.2.2\source\rtl\inc\gencurr.inc

Das Problem tritt auf bei allen Zahlen mit ungerader Vvorkommastelle und .5 als Nachkommastelle.
just my two Beer

Benutzeravatar
m.fuchs
Lazarusforum e. V.
Beiträge: 2726
Registriert: Fr 22. Sep 2006, 19:32
OS, Lazarus, FPC: Winux (Lazarus 2.0.10, FPC 3.2.0)
CPU-Target: x86, x64, arm
Wohnort: Berlin
Kontaktdaten:

Re: ROUND() unter Windows vs. ROUND() unter Linux

Beitrag von m.fuchs »

Ok, jetzt habe ich verstanden was du meinst.

Es gibt einen Unterschied im Handling von Currency unter Linux und unter Windows.
In Windows (evtl. nicht in allen Versionen) ist es intern ein Int64 und wird einfach durch 10000 dividiert.

Dafür gibt es FPC_CURRENCY_IS_INT64 - ist es gesetzt wird die von dir genannten Rundungsmethode verwendet.
Genaueres kann vermutlich jemand aus dem FPC-Team erklären.

Wie man das Problem vermeidet, habe ich im anderen Thread geschrieben.
Software, Bibliotheken, Vorträge und mehr: https://www.ypa-software.de

Joh
Lazarusforum e. V.
Beiträge: 271
Registriert: Sa 26. Mai 2012, 17:31
OS, Lazarus, FPC: Win 10 (L 2.2.6 x64 FPC 3.2.2)
CPU-Target: 64Bit

Re: ROUND() unter Windows vs. ROUND() unter Linux

Beitrag von Joh »

Das heißt:

eine existentielle Funktion wie Round() liefert unterschiedliche Werte unter verschiedenen Betriebssystemen und das nimmt hier jeder mit einem Axelzucken hin?

Das kann doch nicht wahr sein.
just my two Beer

Benutzeravatar
m.fuchs
Lazarusforum e. V.
Beiträge: 2726
Registriert: Fr 22. Sep 2006, 19:32
OS, Lazarus, FPC: Winux (Lazarus 2.0.10, FPC 3.2.0)
CPU-Target: x86, x64, arm
Wohnort: Berlin
Kontaktdaten:

Re: ROUND() unter Windows vs. ROUND() unter Linux

Beitrag von m.fuchs »

Joh hat geschrieben: Sa 11. Jan 2025, 18:30 eine existentielle Funktion wie Round() liefert unterschiedliche Werte unter verschiedenen Betriebssystemen und das nimmt hier jeder mit einem Axelzucken hin?
Also aus meiner Sicht ist das nur ein Achselzucken wert. Ich kann auch gerne schreiben warum:
  1. Es ist eigentlich ein undokumentiertes Verhalten, dass Round überhaupt für Currency aufgerufen werden kann.
    In der [urlhttps://www.freepascal.org/docs-html/current/rt ... round.html]Doku[/url] ist die Aufgabe von Round klar beschrieben:
    Round floating point value to nearest integer number.
    Ein Currency ist keine Fließ- sondern eine Festkommazahl, also ist Round dafür gar nicht zuständig.
    Insofern hätte ich das auch nie ausprobiert und mir wäre das Problem gar nicht aufgefallen.
  2. Beim Arbeiten mit Geldbeträgen gilt eine einfache Regel:
    Wann runden wir? Niemals.
Von daher hält sich mein Ärger über den aktuellen Zustand in Grenzen. Trotzdem ist die Situation auch nicht optimal. Entweder sollte der entsprechende Quellcode ganz entfernt werden, also der Dokumentation entsprechen, oder man baut eine auf allen System gleich funktionierende Funktion ein und dokumentiert die auch entsprechend.
Software, Bibliotheken, Vorträge und mehr: https://www.ypa-software.de

Socke
Lazarusforum e. V.
Beiträge: 3171
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: ROUND() unter Windows vs. ROUND() unter Linux

Beitrag von Socke »

m.fuchs hat geschrieben: Sa 11. Jan 2025, 21:02 [*]Beim Arbeiten mit Geldbeträgen gilt eine einfache Regel:
Wann runden wir? Niemals.
[/list]
Da muss ich dir widersprechen. Sobald du die Steuer ausrechnest, rundest du, da du den Steuerbetrag selten mit 2 oder 4 Nachkommastellen abbilden kannst.

Auch hat Currency 4 Nachkommastellen, wovon wir häufig aber nur 2 Nachkommastellen im Alltag verwenden. Insofern sollte ein Programm das auch entsprechend abbilden.

Beispiel:
Du rechnest für mehrere Positionen auf einem Kassenbon die Umsatz-/Mehrwertsteuer einzeln aus, da jede Position einem anderen Steuersatz unterliegen kann und deine Buchhaltung diese pro Position verbuchen muss.
Auf dem Kassenbon möchtest du auch die gesamten Steuern ausweisen. Hier muss die ausgewiesene Steuersumme der Summe der ausgedruckten Einzelsteuern entsprechen.
Da Currency immer mit 4 Nachkommastellen rechnet, kommen unterschiedliche Summen heraus, wenn du im Programm die Steuer 4 stellig ausrechnest, aber nur 2 Stelllen ausgibst oder aber vor Ausgabe die Steuer auf 2 Nachkommastellen kürzt.

Ob man dann Rundet oder die Stellen kürzt, ist eine andere Diskussion.

Beispiel:

Code: Alles auswählen

program Project1;

{$mode objfpc}{$H+}

uses
  sysutils;

// Summe eines Currency Arrays bilden
function Sum(const a: array of currency): currency;
var
  c: Currency;
begin
  Result := 0;
  for c in a do
    Result := Result + c;
end;

// Currency-Wert auf 2 Nachkommastellen kürzen
function TruncToTwoDecimals(c: currency): Currency;
begin
  Result := frac(c);
  Result := Result * 100;
  Result := Result - frac(Result);
  Result := trunc(c) + Result / 100;
end;

var
  NettoBetrag, BruttoBetrag, Steuer, Steuer2Dec, Brutto2Dec: array[0..10] of currency;
  i: SizeInt;

begin
  randomize;

  // alle 4 Nachkommastellen in Format() ausgeben
  DefaultFormatSettings.CurrencyDecimals:=4;
  // Währungszeichen nicht ausgeben
  DefaultFormatSettings.CurrencyString:='';


  WriteLn(Format('%11S %11S %11S %11S %11S', ['Netto', 'Steuer', 'Brutto', 'Steuer2Dec', 'Brutto2Dec']));
  for i := low(NettoBetrag) to high(NettoBetrag) do
  begin
    // Für 10 Positionen einen Zufallswert mit 2 Nachkommastellen erzeugen
    NettoBetrag[i] := Random(1000)+Random(99)/100;
    // Steuer mit 4 Nachkommastellen
    Steuer[i] :=  NettoBetrag[i] * 0.19;
    // Steuer auf 2 Nachkommastellen kürzen
    Steuer2Dec[i] := TruncToTwoDecimals(Steuer[i]);
    // Bruttobetrag mit 4 Nachkommastellen
    BruttoBetrag[i] := NettoBetrag[i] + Steuer[i];
    // Bruttobetrag mit 2 Nachkommastellen
    Brutto2Dec[i] :=  NettoBetrag[i] + Steuer2Dec[i];
    WriteLn(Format('%11M %11M %11M %11M %11M', [NettoBetrag[i], Steuer[i], BruttoBetrag[i], Steuer2Dec[i], Brutto2Dec[i]]));
  end;
  WriteLn('----------- ----------- -----------');
  WriteLn(Format('%11M %11M %11M %11M %11M', [Sum(NettoBetrag), Sum(Steuer), sum(BruttoBetrag), sum(Steuer2Dec), sum(Brutto2Dec)]));

  Readln;
end.
Beispiel-Ausgabe

Code: Alles auswählen

      Netto      Steuer      Brutto  Steuer2Dec  Brutto2Dec
  575,8800    109,4172    685,2972    109,4100    685,2900
  125,3000     23,8070    149,1070     23,8000    149,1000
  484,3900     92,0341    576,4241     92,0300    576,4200
  233,6400     44,3916    278,0316     44,3900    278,0300
  367,8200     69,8858    437,7058     69,8800    437,7000
  381,1300     72,4147    453,5447     72,4100    453,5400
  916,8000    174,1920  1.090,9920    174,1900  1.090,9900
  366,4400     69,6236    436,0636     69,6200    436,0600
  763,2400    145,0156    908,2556    145,0100    908,2500
  147,3500     27,9965    175,3465     27,9900    175,3400
  645,8500    122,7115    768,5615    122,7100    768,5600
----------- ----------- -----------
5.007,8400    951,4896  5.959,3296    951,4400  5.959,2800
Ergo: Du führst 4,96 Cent mehr an das Finanzamt ab, wenn du die Steuer nicht auf 2 Nachkommastellen kürzst.
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

Benutzeravatar
Jorg3000
Lazarusforum e. V.
Beiträge: 253
Registriert: So 10. Okt 2021, 10:24
OS, Lazarus, FPC: Win64
Wohnort: NRW

Re: ROUND() unter Windows vs. ROUND() unter Linux

Beitrag von Jorg3000 »

Guten Morgen!
Ich möchte Socke beipflichten und sogar noch einen Schritt weiter gehen: Wir runden immer! (natürlich nur auf kaufmännischen Belegen)

Damit meine ich auf Rechnungen jede gedruckte Zwischensumme, mit ihrer jeweils dargestellten Anzahl an Nachkommenstellen (meistens zwei). Meiner Meinung nach, darf nur die gedruckte Zahl für weitere Berechnungen genutzt werden, d.h. auch die programminterne Variable muss in dem Moment auf zwei Nachkommastellen gerundet geändert werden, bevor sie weiterverwendet wird.

Der Hintergrund ist, dass man eine Papierrechnung nehmen kann, jede gedruckte Zeilensumme in den Taschenrechner tippt und die gleiche Summe erhalten muss (Nachprüfbarkeit). Meiner Erfahrung nach wird die einfache Nachprüfbarkeit im kaufmännischen Bereich größer geschrieben, als eine mathematische Ungenauigkeit im nicht mehr dargestellten Nachkommabereich.
Das Gleiche gilt wahrscheinlich in XML, so nehme ich stark an, d.h. für eine gerundete Zahl, die in ein ZUGFeRD-XML geschrieben wird. Diesbezüglich muss ich meine eigene ZUGFeRD-Unit noch ändern.

Deshalb hatte ich in dem anderen Thread eine Funktion vorgeschlagen, die kaufmännisch auf 2 Nachkommastellen rundet. Das Resultat kann man in eine Variable übernehmen - anstatt nur in der Format-Funktion für die Ausgabe runden zu lassen.
Grüße, Jörg

Benutzeravatar
Niesi
Lazarusforum e. V.
Beiträge: 501
Registriert: So 26. Jun 2016, 19:44
OS, Lazarus, FPC: Linux Mint Cinnamon, Laz 3.7 Fpc 3.2.3 und Laz 3.0 Fpc

Re: ROUND() unter Windows vs. ROUND() unter Linux

Beitrag von Niesi »

Socke hat geschrieben: So 12. Jan 2025, 02:25

Ob man dann Rundet oder die Stellen kürzt, ist eine andere Diskussion.

...

Ergo: Du führst 4,96 Cent mehr an das Finanzamt ab, wenn du die Steuer nicht auf 2 Nachkommastellen kürzst.
Das ist falsch, das ist keine Diskussion. Für die Berechnung der Umsatzsteuer ist kaufmännisch zu runden - das ist im Umsatzsteuergetz und in der Mehrwertsteuer-Durchführungsverordnung gesetzlich geregelt.

Also ist trunc() KEINE Option.

Und für Rechnungen, in denen die Umsatzsteuer für jeden Posten ausgewiesen wird, ist die Umsatzsteuer jeweils kaufmännisch zu runden.

Das sog. Banker-Runden (= symmetrisches Runden nach IEEE 754) ist nicht auf Rechnungen anwendbar. Es wird für Wissenschaft und Technik verwendet, um die z. B. Fehler in Statistiken zu minimieren. (Wobei das auch nicht immer sicher klappt, auch diese Methode hat Schwächen.) Siehe dazu: https://de.wikipedia.org/wiki/Rundung#S ... hes_Runden

Michael hat da absolut Recht: Auf currency darf round() nicht angewendet werden.

Mehr zum symmetrischen Runden: https://de.wikipedia.org/wiki/IEEE_754#Rundungen Kapitel 5.
Wissen ist das einzige Gut, das sich vermehrt, wenn es geteilt wird ...

Benutzeravatar
m.fuchs
Lazarusforum e. V.
Beiträge: 2726
Registriert: Fr 22. Sep 2006, 19:32
OS, Lazarus, FPC: Winux (Lazarus 2.0.10, FPC 3.2.0)
CPU-Target: x86, x64, arm
Wohnort: Berlin
Kontaktdaten:

Re: ROUND() unter Windows vs. ROUND() unter Linux

Beitrag von m.fuchs »

Socke hat geschrieben: So 12. Jan 2025, 02:25 Du rechnest für mehrere Positionen auf einem Kassenbon die Umsatz-/Mehrwertsteuer einzeln aus, da jede Position einem anderen Steuersatz unterliegen kann und deine Buchhaltung diese pro Position verbuchen muss.
Auf dem Kassenbon möchtest du auch die gesamten Steuern ausweisen. Hier muss die ausgewiesene Steuersumme der Summe der ausgedruckten Einzelsteuern entsprechen.
Aber warum sollte ich für jede einzelne Rechnungsposition die Steuer angeben?
Man kann sehr gut die Nettowerte jedes Steuersatzes zusammenfassen und davon die Steuer berechnen. Beide Steuersummen werden anschließend addiert - fertig.
Das vermeidet nämlich die Rundungsproblematik.

Das niemals runden ist sicherlich etwas überspitzt formuliert, aber es kommt wirklich gaaaaaaanz zum Schluss.

BTW: Hab gerade nochmal in meine Quellcodes geschaut, da wird nicht einmal bei Übergabe an die Fibu gerundet, selbst die nimmt vier Nachkommastellen entgegen.
Tatsächlich ist nur die Summen auf der Kundenrechnung (also Nettosumme, Steuersumme, Bruttosumme) auf zwei Stellen gekürzt.
Software, Bibliotheken, Vorträge und mehr: https://www.ypa-software.de

BeniBela
Beiträge: 319
Registriert: Sa 21. Mär 2009, 17:31
OS, Lazarus, FPC: Linux (Lazarus SVN, FPC 2.4)
CPU-Target: 64 Bit

Re: ROUND() unter Windows vs. ROUND() unter Linux

Beitrag von BeniBela »

Mit rounden habe ich mich auch gerade rumgeschlagen

Ich habe ein BigDecimal mit beliebig vielen Nachstellen programmiert, damit gibt es nie so Probleme.

Aber manchmal ist die Eingabe double, und dann muss man das double runden. Weil ich XML-Zeug nach den Spezifikationen programmiert habe, und da gibt es XML Schema und den Typ xs:double, und da hat man keine Wahl als wirklich double zu verwenden. Da ist jetzt die Frage, wie man ein double sinnvoll rundet.

Da muss man dann auch nach dem Komma und vor dem Komma und runden, also 123456,789 könnte zu 123456,8 gerundet werden aber auch zu 123000. Ich könnte die Eingabe von double zu BigDecimal oder string konvertieren und dann zurück konvertieren. Aber das ist zu langsam. Deshalb habe ich es zu extended konvertiert. Dann einfach round( eingabe / precision) * precision. Nur aus historischen Gründen nicht das round von FPC, sondern mein eigenes. Man könnte auch round( eingabe * precision) / precision nehmen, aber mathematisch sollte das äquivalent sein. Das funktioniert in den Beispielen/Testfällen unter Linux.



Und nun kommt FPC win64 und sagt, extended wird nicht mehr unterstützt. Und nun geht es da nicht mehr.

Nun könnte man denken das Runden sei falsch. Aber es hat sich rausgestellt, dass bereits das Berechnen der nötigen Stelle mit power(10, -3) falsch rundet. Das sollte 1 / power(10, 3) sein, weicht aber leicht davon ab. Unter Linux mit extended reicht power, aber unter Windows mit double nicht.

Also verwende ich power nur noch mit positiven Exponent, und sowohl round( eingabe / precision) * precision als auch round( eingabe * precision) / precision, je nachdem ob es nach dem Komma und vor dem Komma sein soll. Dann lief es bei mir.

Und dann sagen mir die Leute, bei denen geht es immer noch nicht. Dann stellt sich raus, mit FPC 3.3.1 läuft das Runden und mit FPC 3.2.2 nicht. Da habe ich noch nicht herausgefunden, wieso

PascalDragon
Beiträge: 904
Registriert: Mi 3. Jun 2020, 07:18
OS, Lazarus, FPC: L 2.0.8, FPC Trunk, OS Win/Linux
CPU-Target: Aarch64 bis Z80 ;)
Wohnort: München

Re: ROUND() unter Windows vs. ROUND() unter Linux

Beitrag von PascalDragon »

BeniBela hat geschrieben: Mi 15. Jan 2025, 14:11 Und nun kommt FPC win64 und sagt, extended wird nicht mehr unterstützt. Und nun geht es da nicht mehr.
Und Überraschung, Extended wird auch auf jeglicher anderen nicht-x86 Plattform nicht unterstützt.
BeniBela hat geschrieben: Mi 15. Jan 2025, 14:11 Und dann sagen mir die Leute, bei denen geht es immer noch nicht. Dann stellt sich raus, mit FPC 3.3.1 läuft das Runden und mit FPC 3.2.2 nicht. Da habe ich noch nicht herausgefunden, wieso
Weil das schlicht und ergreifend behoben wurde.
FPC Compiler Entwickler

BeniBela
Beiträge: 319
Registriert: Sa 21. Mär 2009, 17:31
OS, Lazarus, FPC: Linux (Lazarus SVN, FPC 2.4)
CPU-Target: 64 Bit

Re: ROUND() unter Windows vs. ROUND() unter Linux

Beitrag von BeniBela »

PascalDragon hat geschrieben: Do 16. Jan 2025, 22:33 Weil das schlicht und ergreifend behoben wurde.
nein, ich verwende doch extended und nicht currency

Antworten