Modulo rechnet falsch

Für Fehler in Lazarus, um diese von anderen verifizieren zu lassen.
Antworten
Mathias
Beiträge: 6160
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Modulo rechnet falsch

Beitrag von Mathias »

Ich wollte das Modulo vom Wiki ausprobieren: http://wiki.freepascal.org/Mod
Da bin ich auf folgenden Bug gestossen. Modulo(6.6, 1.1) spuckt 1.1 anstelle von 0.0 aus. Probiert mit FPC 3.0.4.

Code: Alles auswählen

program Project1;
 
  function Modulo(Dividend, Quotient: double): double;
  begin
    Result := Dividend - Quotient * Int(Dividend / Quotient);
  end;
 
begin
  WriteLn(Modulo(8.8, 1.1): 10: 5);
  WriteLn(Modulo(7.7, 1.1): 10: 5);
  WriteLn(Modulo(6.6, 1.1): 10: 5); // Falsch !
  WriteLn(Modulo(5.5, 1.1): 10: 5);
  WriteLn(Modulo(5.9, 1.1): 10: 5);
end.


Ab FPC 3.1.1 wird Modulo in der Unit Math unterstützt.
Folgender Versuch mit FPC 3.1.1. macht einen ähnlichen Fehler, dort rechnet er mit 7.7 falsch.

Code: Alles auswählen

uses
  Math;
begin
  WriteLn(8.8 mod 1.1: 10: 5);
  WriteLn(7.7 mod 1.1: 10: 5); // Falsch !
  WriteLn(6.6 mod 1.1: 10: 5);
  WriteLn(5.5 mod 1.1: 10: 5);
end

Rechnet mein PC falsch, oder könnt ihr das auch nachvollziehen, bevor ich eine Bug-Report schreibe.
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

indianer-frank
Beiträge: 134
Registriert: So 30. Nov 2008, 21:53

Re: Modulo rechnet falsch

Beitrag von indianer-frank »

Das kommt darauf an, was Du als Bug bezeichnest. Es sind halt die Probleme, die auftreten, wenn Deine Eingaben nicht exakt als Binärzahlen darstellbar sind. Wenn Du die einzelnen Werte mal ausgibst, siehst Du mit

Code: Alles auswählen

program Project1;
uses math;
 
  function Modulo(Dividend, Quotient: double): double;
  begin
    Modulo := Dividend - Quotient * int(Dividend / Quotient);
  end;
 
var
  a,b,c,d,e: double;
begin
  WriteLn(Modulo(8.8, 1.1): 10: 5);
  WriteLn(Modulo(7.7, 1.1): 10: 5);
  WriteLn(Modulo(6.6, 1.1): 10: 5); // Falsch !
  WriteLn(Modulo(5.5, 1.1): 10: 5);
  WriteLn(Modulo(5.9, 1.1): 10: 5);
  a := 6.6;    writeln(a:30);
  b := 1.1;    writeln(b:30);
  c := a/b;    writeln(c:30);
  d := int(c); writeln(d:30);
  e := a-d*b;  writeln(e:30);
end.
 
die Ergebnisse

Code: Alles auswählen

   0.00000
   1.10000
   1.10000
   1.10000
   0.40000
       6.5999999999999996E+000
       1.1000000000000001E+000
       5.9999999999999991E+000
       5.0000000000000000E+000
       1.0999999999999992E+000

Wenn Du int durch round ersetzt ( int ist ja im Prinzip ein trunc für double ist) erhältst Du

Code: Alles auswählen

      0.00000
  -0.00000
  -0.00000
  -0.00000
   0.40000
       6.5999999999999996E+000
       1.1000000000000001E+000
       5.9999999999999991E+000
       6.0000000000000000E+000
      -8.8817841970012523E-016 

Dann kann es allerdings für andere Werte 'falsche' Ergebnisse geben.

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

Re: Modulo rechnet falsch

Beitrag von wp_xyz »

Die sind die Leute, die diese Funktion implementiert haben, zu sorglos vorgegangen. Du erwartest zu recht, dass 7.7 / 1.1 = 7.0 ist, also keinen Rest hat. Wenn du dir das Zwischenergebnis 7.7/1.1 aber ausgeben lässt sieht du auf Grund der endlichen Genauigkeit von Gleitkommazahlen den Wert 6.99999999999. Mit dem Int() wird daraus 6, und schon ist das Ergebnis massiv falsch. Um einen Divisor genaugenommen.

Da bei der Division der Rest aber immer kleiner ist als der Divisor, könnte man hieran aber auch den Fehler erkennen, indem man das Funktionsergebnis auf Gleichheit mit dem Divisor testet und dabei die Funktion SameValue verwendet, die Maschinengenauigkeit berücksichtigt:

Code: Alles auswählen

uses
  Math;
 
function Modulo(Dividend, Divisor: double): double;
begin
  Result := Dividend - Divisor * Int(Dividend / Divisor);
  if SameValue(abs(Result), abs(Divisor)) then Result := 0.0;
end

Wie das mit negativen Zahlen ausgeht, müsste man noch prüfen.

[EDIT]
Mit abs() für die beiden Zahlen unter SameValue geht es auch für negative Zahlen (oben geändert).

Ich habe einen Bug-Report geschrieben: https://bugs.freepascal.org/view.php?id=33167

indianer-frank
Beiträge: 134
Registriert: So 30. Nov 2008, 21:53

Re: Modulo rechnet falsch

Beitrag von indianer-frank »

wp_xyz hat geschrieben:

Code: Alles auswählen

uses
  Math;
 
function Modulo(Dividend, Divisor: double): double;
begin
  Result := Dividend - Divisor * Int(Dividend / Divisor);
  if SameValue(Result, Divisor) then Result := 0.0;
end

Wie das mit negativen Zahlen ausgeht, müsste man noch prüfen.

Es geht schief. Die Ergebnisse für die entsprechenden negativen Dividenden sind

Code: Alles auswählen

   0.00000
  -1.10000
  -1.10000
  -1.10000
  -0.40000

IMO arbeitet die brauchbarste Lösung mit der floor-Funktion (wie es mathematisch korekt ist) und SameValue:

Code: Alles auswählen

program Project1;
{$mode delphi}
 
uses math;
 
function Modulo(Dividend, Divisor: double): double;
begin
  Result := Dividend - Divisor * floor(Dividend / Divisor);
  if SameValue(Result, Divisor) then Result := 0.0;
end;
 
begin
  WriteLn(Modulo(8.8, 1.1): 10: 5);
  WriteLn(Modulo(7.7, 1.1): 10: 5);
  WriteLn(Modulo(6.6, 1.1): 10: 5);
  WriteLn(Modulo(5.5, 1.1): 10: 5);
  WriteLn(Modulo(5.9, 1.1): 10: 5);
  WriteLn(Modulo(-8.8, 1.1): 10: 5);
  WriteLn(Modulo(-7.7, 1.1): 10: 5);
  WriteLn(Modulo(-6.6, 1.1): 10: 5);
  WriteLn(Modulo(-5.5, 1.1): 10: 5);
  WriteLn(Modulo(-5.9, 1.1): 10: 5);
end.
 

Die Ausgabe ist dann,

Code: Alles auswählen

   0.00000
   0.00000
   0.00000
   0.00000
   0.40000
   0.00000
   0.00000
   0.00000
   0.00000
   0.70000 

Man beachte, daß bei auch 5.9 mod 1.1 der Rest die Relation 0 <= Rest < Divisor erfüllt. Allerdings muß der Quotient in ein Integer passen.

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

Re: Modulo rechnet falsch

Beitrag von wp_xyz »

Ja, mit Floor() erhalte ich auch die Vorzeichen, die Wolfram Alpha ausspuckt.

Mathias
Beiträge: 6160
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Modulo rechnet falsch

Beitrag von Mathias »

So wie ich sehe, gibt dies ein Bug-Report.
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Mathias
Beiträge: 6160
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Modulo rechnet falsch

Beitrag von Mathias »

Ich habe es gerade nochmals mit folgendem Code probiert.

Code: Alles auswählen

uses
  Math;
begin
  WriteLn(8.8 mod 1.1: 10: 5);
  WriteLn(7.7 mod 1.1: 10: 5); // Falsch !
  WriteLn(6.6 mod 1.1: 10: 5);
  WriteLn(5.5 mod 1.1: 10: 5);
end.

Mit einer fast neuen Trunk, kommt jetzt bei allen 4 Varianten 0.0.

Anscheinend wurde es jetzt gemacht, wie wp_xyz er vorgeschlagen hat.

Code: Alles auswählen

operator mod(const a,b:float) c:float;inline;
begin
  c:= a-b * Int(a/b);
  if SameValue(abs(c),abs(b)) then
    c:=0.0;
end
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

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

Re: Modulo rechnet falsch

Beitrag von wp_xyz »

Ja, das wurde behoben, hat aber noch den Fehler, dass es nur mit float und extended funktioniert, nicht aber mit Double und Single (https://bugs.freepascal.org/view.php?id=33167). Leider wird der Bugtracker zur Zeit überhäuft mit Bugs zum FpReport, und daher lohnt es sich momentan nicht, den Report nach oben zu holen.

Mathias
Beiträge: 6160
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Modulo rechnet falsch

Beitrag von Mathias »

Ja, das wurde behoben, hat aber noch den Fehler, dass es nur mit float und extended funktioniert, nicht aber mit Double und Single

Ist Float und Single nicht das gleiche ?

Was nimmt FPC als Default bei ?

Code: Alles auswählen

WriteLn(8.8 mod 1.1: 10: 5);

float oder extended ?
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

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

Re: Modulo rechnet falsch

Beitrag von wp_xyz »

Mathias hat geschrieben:
Ja, das wurde behoben, hat aber noch den Fehler, dass es nur mit float und extended funktioniert, nicht aber mit Double und Single

Ist Float und Single nicht das gleiche ?

Was nimmt FPC als Default bei ?

Code: Alles auswählen

WriteLn(8.8 mod 1.1: 10: 5);

float oder extended ?

Kann man pauschal nicht sagen: siehe Antwort von Thaddy de Konig in dem oben zitierten Bugtracker-Link. Ansonsten "math" zu "uses" hinzufügen, eine Variable als "float" deklarieren und auf "float" mit gedrückter CTRL-Taste klicken -> die IDE springt zu der Stelle, wo "float" deklariert ist. Bei mir, Win 10, Laz 32-bit, steht da: "float = extended".

Mathias
Beiträge: 6160
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Modulo rechnet falsch

Beitrag von Mathias »

Bei mir, Win 10, Laz 32-bit, steht da: "float = extended".

Bei mir auch Linux 64Bit.
Somit ist man besser bedient, wen man Single nimmt, dieser ist immer 32Bit gross.

In OpenGL gibt es noch einen glFloat, dieser ist auch 32Bit. Dies muss garantiert sein, ansonsten würde man Probleme bekommen.
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Antworten