TestCode nearly equal Float

Für allgemeine Fragen zur Programmierung mit PureBasic.
SMaag
Beiträge: 184
Registriert: 08.05.2022 12:58

TestCode nearly equal Float

Beitrag von SMaag »

Da das ganze Thema wohl doch nicht so einfach ist, hab ich jetzt aus verschiedenen Quellen mal einen
Testcode gebaut! Leider vergleicht der noch nicht sauber.
hier der Link zum Vorgänger-Thread
https://www.purebasic.fr/german/viewtopic.php?t=33098

TestCode für nearlyEqual

Code: Alles auswählen

EnableExplicit

  Procedure.d _CalcSinglePrecision()
      Protected.f FPr, s
  
    ; IT CARRIES OUT AN APPROXIMATION OF MACHINE PRECISION
    FPr = 1.0  
    s = FPr + 1.0
    While s > 1.0
      FPr = 0.5 * FPr
      s = FPr + 1.0
    Wend
    ProcedureReturn FPr * 2.0
  EndProcedure
  
  Global.f mem_Precsion_Single = _CalcSinglePrecision()
  Debug "Presicion = " + StrF(mem_Precsion_Single,15)
  
  Procedure.i nearlyEqual(A.f, B.f, Epsilon.f= 0.0000002)
     Protected.f eps
;     Protected *pA.Long = @A
;     Protected *pB.Long = @B
;     Debug "A = " + *pA\l
;     Debug "B = " + *pB\l
    
    If IsNAN(A) Or IsNAN(B)
      ProcedureReturn #False
    EndIf
    
    If IsInfinity(A) Or IsInfinity(B)
      ProcedureReturn #False
    EndIf
      
    If A=B
      ProcedureReturn #True             
    ElseIf A > B
      eps = A * mem_Precsion_Single  
    Else
      eps = B * mem_Precsion_Single
    EndIf
    
    If Abs(A-B) < eps
      ProcedureReturn #True
    Else
      ProcedureReturn #False
    EndIf
    
  EndProcedure

#MAX_VALUE = 3.4028235e38
#MIN_VALUE = 1.4e-45

  ; https://floating-point-gui.de/errors/comparison/
  Procedure.i nearlyEqual_1(A.f, B.f, Epsilon.f=0.0000002)
    Protected.f absA, absB, diff, absSum, min
    
    absA = Abs(A)
    absB = Abs(B)
    diff = Abs(absA-absB)
    absSum = absA + absB 
    
    If IsNAN(A) Or IsNAN(B)
      ProcedureReturn #False
    EndIf
    
    If IsInfinity(A) Or IsInfinity(B)
      ProcedureReturn #False
    EndIf

    If A=B
      ProcedureReturn #True
      
    ElseIf Bool (A=0.0) | Bool(B=0.0) | Bool(absSum < #MIN_VALUE)
      ; a Or b is zero Or both are extremely close To it
      ; relative error is less meaningful here
      ProcedureReturn Bool(diff < (Epsilon * #MIN_VALUE))
      
    Else  ; use relative error
              
      If absSum < #MAX_VALUE
        ProcedureReturn Bool((diff / absSum) < epsilon)
      Else
        ProcedureReturn Bool((diff / #MAX_VALUE) < epsilon)   
      EndIf
    EndIf
    
  EndProcedure
  
  Macro DoubleQuote
    "
  EndMacro
  
  Macro assertTrue (Expression)
    CompilerIf #PB_Compiler_Debugger ; ’ Assert ’ ( Erklärung ) nur im Debug - Modus aktivieren
      If Not Expression
        Debug "Error assertTrue ( Line " + #PB_Compiler_Line + " ) : " +  DoubleQuote#Expression#DoubleQuote
      EndIf
    CompilerEndIf
  EndMacro
  
  Macro assertFalse (Expression)
    CompilerIf #PB_Compiler_Debugger ; ’ Assert ’ ( Erklärung ) nur im Debug - Modus aktivieren
      If Expression
        Debug "Error assertFalse ( Line " + #PB_Compiler_Line + " ) : " +  DoubleQuote#Expression#DoubleQuote
      EndIf
    CompilerEndIf
  EndMacro
  
;https://floating-point-gui.de/errors/NearlyEqualsTest.java
Debug " "
Debug "Line : " + #PB_Compiler_Line + " : Regular large numbers - generally Not problematic"
assertTrue(nearlyEqual(1000000, 1000001))
assertTrue(nearlyEqual(1000001, 1000000))
assertFalse(nearlyEqual(10000, 10001))
assertFalse(nearlyEqual(10001, 10000))

Debug " "
Debug "Line : " + #PB_Compiler_Line + " : Negative large numbers"
assertTrue(nearlyEqual(-1000000, -1000001))
assertTrue(nearlyEqual(-1000001, -1000000))
assertFalse(nearlyEqual(-10000, -10001))
assertFalse(nearlyEqual(-10001, -10000))

Debug " "
Debug "Line : " + #PB_Compiler_Line + " : Numbers around 1"
assertTrue(nearlyEqual(1.0000001, 1.0000002))
assertTrue(nearlyEqual(1.0000002, 1.0000001))
assertFalse(nearlyEqual(1.0002, 1.0001))
assertFalse(nearlyEqual(1.0001, 1.0002))

Debug " "
Debug "Line : " + #PB_Compiler_Line + " : Numbers around -1"
assertTrue(nearlyEqual(-1.000001, -1.000002))
assertTrue(nearlyEqual(-1.000002, -1.000001))
assertFalse(nearlyEqual(-1.0001, -1.0002))
assertFalse(nearlyEqual(-1.0002, -1.0001))

Debug " "
Debug "Line : " + #PB_Compiler_Line + " : Numbers between 1 And 0"
assertTrue(nearlyEqual(0.000000001000001, 0.000000001000002))
assertTrue(nearlyEqual(0.000000001000002, 0.000000001000001))
assertFalse(nearlyEqual(0.000000000001002, 0.000000000001001))
assertFalse(nearlyEqual(0.000000000001001, 0.000000000001002))

Debug " "
Debug "Line : " + #PB_Compiler_Line + " : Numbers between -1 And 0"
assertTrue(nearlyEqual(-0.000000001000001, -0.000000001000002))
assertTrue(nearlyEqual(-0.000000001000002, -0.000000001000001))
assertFalse(nearlyEqual(-0.000000000001002, -0.000000000001001))
assertFalse(nearlyEqual(-0.000000000001001, -0.000000000001002))

Debug " "
Debug "Line : " + #PB_Compiler_Line + " : Small differences away from zero"
assertTrue(nearlyEqual(0.3, 0.30000003))
assertTrue(nearlyEqual(-0.3, -0.30000003))

Debug " "
Debug "Line : " + #PB_Compiler_Line + " : Comparisons involving zero"
assertTrue(nearlyEqual(0.0, 0.0))
assertTrue(nearlyEqual(0.0, -0.0))
assertTrue(nearlyEqual(-0.0, -0.0))
assertFalse(nearlyEqual(0.00000001, 0.0))
assertFalse(nearlyEqual(0.0, 0.00000001))
assertFalse(nearlyEqual(-0.00000001, 0.0))
assertFalse(nearlyEqual(0.0, -0.00000001))

assertTrue(nearlyEqual(0.0, 1e-40, 0.01))
assertTrue(nearlyEqual(1e-40, 0.0, 0.01))
assertFalse(nearlyEqual(1e-40, 0.0, 0.000001))
assertFalse(nearlyEqual(0.0, 1e-40, 0.000001))

assertTrue(nearlyEqual(0.0, -1e-40, 0.1))
assertTrue(nearlyEqual(-1e-40, 0.0, 0.1))
assertFalse(nearlyEqual(-1e-40, 0.0, 0.00000001))
assertFalse(nearlyEqual(0.0, -1e-40, 0.00000001))

Debug " "
Debug "Line : " + #PB_Compiler_Line + " : Comparisons involving extreme values (overflow potential)"
assertTrue(nearlyEqual(#MAX_VALUE, #MAX_VALUE))
assertFalse(nearlyEqual(#MAX_VALUE, -#MAX_VALUE))
assertFalse(nearlyEqual(-#MAX_VALUE, #MAX_VALUE))
assertFalse(nearlyEqual(#MAX_VALUE, #MAX_VALUE / 2))
assertFalse(nearlyEqual(#MAX_VALUE, -#MAX_VALUE / 2))
assertFalse(nearlyEqual(-#MAX_VALUE, #MAX_VALUE / 2))

Debug " "
Debug "Line : " + #PB_Compiler_Line + " : Comparisons involving infinities"
assertFalse(nearlyEqual(Infinity(), Infinity()))
assertFalse(nearlyEqual(-Infinity(), -Infinity()))
assertFalse(nearlyEqual(-Infinity(), Infinity()))
assertFalse(nearlyEqual(Infinity(), #MAX_VALUE))
assertFalse(nearlyEqual(-Infinity(), -#MAX_VALUE))

Debug " "
Debug "Line : " + #PB_Compiler_Line + " : Comparisons involving NaN values"
assertFalse(nearlyEqual( NaN(),  NaN()))
assertFalse(nearlyEqual( NaN(), 0.0))
assertFalse(nearlyEqual(-0.0,  NaN()))
assertFalse(nearlyEqual( NaN(), -0.0))
assertFalse(nearlyEqual(0.0,  NaN()))
assertFalse(nearlyEqual( NaN(), Infinity()))
assertFalse(nearlyEqual(Infinity(),  NaN()))
assertFalse(nearlyEqual( NaN(), -Infinity()))
assertFalse(nearlyEqual(-Infinity(),  NaN()))
assertFalse(nearlyEqual( NaN(), #MAX_VALUE))
assertFalse(nearlyEqual(#MAX_VALUE,  NaN()))
assertFalse(nearlyEqual( NaN(), -#MAX_VALUE))
assertFalse(nearlyEqual(-#MAX_VALUE,  NaN()))
assertFalse(nearlyEqual( NaN(), #MIN_VALUE))
assertFalse(nearlyEqual(#MIN_VALUE,  NaN()))
assertFalse(nearlyEqual( NaN(), -#MIN_VALUE))
assertFalse(nearlyEqual(-#MIN_VALUE,  NaN()))

Debug " "
Debug "Line : " + #PB_Compiler_Line + " : Comparisons of numbers on opposite sides of 0"
assertFalse(nearlyEqual(1.000000001, -1.0))
assertFalse(nearlyEqual(-1.0, 1.000000001))
assertFalse(nearlyEqual(-1.000000001, 1.0))
assertFalse(nearlyEqual(1.0, -1.000000001))
assertTrue(nearlyEqual(10 * #MIN_VALUE, 10 * -#MIN_VALUE))
assertFalse(nearlyEqual(10000 * #MIN_VALUE, 10000 * -#MIN_VALUE))

Debug " "
Debug "Line : " + #PB_Compiler_Line + " : The really tricky part - comparisons of numbers very close To zero."
assertTrue(nearlyEqual(#MIN_VALUE, #MIN_VALUE))
assertTrue(nearlyEqual(#MIN_VALUE, -#MIN_VALUE))
assertTrue(nearlyEqual(-#MIN_VALUE, #MIN_VALUE))
assertTrue(nearlyEqual(#MIN_VALUE, 0))
assertTrue(nearlyEqual(0, #MIN_VALUE))
assertTrue(nearlyEqual(-#MIN_VALUE, 0))
assertTrue(nearlyEqual(0, -#MIN_VALUE))

assertFalse(nearlyEqual(0.000000001, -#MIN_VALUE))
assertFalse(nearlyEqual(0.000000001, #MIN_VALUE))
assertFalse(nearlyEqual(#MIN_VALUE, 0.000000001))
assertFalse(nearlyEqual(-#MIN_VALUE, 0.000000001))
DarkDragon
Beiträge: 6291
Registriert: 29.08.2004 08:37
Computerausstattung: Hoffentlich bald keine mehr
Kontaktdaten:

Re: TestCode nearly equal Float

Beitrag von DarkDragon »

Bin grad nur am Smartphone, aber sehe ich das richtig, du versuchst die größere Zahl (max(A, B)) mit der Präzision bei 1 zu multiplizieren, um dann die Präzision um den Wert max(A, B) herum zu bekommen? In meinem Code hatte ich Bitroutinen, damit könntest du die Mantisse entsprechend verändern, dann hättest du direkt die Präzision am Zielwert. Setze einfach die Mantisse auf 1 dann.
Angenommen es gäbe einen Algorithmus mit imaginärer Laufzeit O(i * n), dann gilt O((i * n)^2) = O(-1 * n^2) d.h. wenn man diesen Algorithmus verschachtelt ist er fertig, bevor er angefangen hat.
SMaag
Beiträge: 184
Registriert: 08.05.2022 12:58

Re: TestCode nearly equal Float

Beitrag von SMaag »

Zahl (max(A, B)) mit der Präzision bei 1 zu multiplizieren, um dann die Präzision um den Wert max(A, B) herum zu bekommen?
ja genau!

Ich bin jetzt soweit, dass ich glaube, dass der besser Weg auf Bitebene ist, indem man die LSBs weglässt bzw. rundet.
Der Code von STARGATE ist glaub ich der richtige Weg.
DarkDragon
Beiträge: 6291
Registriert: 29.08.2004 08:37
Computerausstattung: Hoffentlich bald keine mehr
Kontaktdaten:

Re: TestCode nearly equal Float

Beitrag von DarkDragon »

SMaag hat geschrieben: 02.01.2024 23:12
Zahl (max(A, B)) mit der Präzision bei 1 zu multiplizieren, um dann die Präzision um den Wert max(A, B) herum zu bekommen?
ja genau!

Ich bin jetzt soweit, dass ich glaube, dass der besser Weg auf Bitebene ist, indem man die LSBs weglässt bzw. rundet.
Der Code von STARGATE ist glaub ich der richtige Weg.
Wie schon im anderen Thread erwähnt, der Fehler nach Rechenoperationen könnte durchaus höher sein als das LSB bzw. die Präzision die du so raus bekommst. STARGATE hat dafür aber wunderbar eine einstellbare Grenze drin mit seinem Accuracy Parameter, damit kannst du die Anzahl MSBs in der Mantisse bestimmen, die gleich sein sollen.

Und max(A, B) ist halt oft keine Zweierpotenz, d.h. es ist nicht einfach ein Fließkommashift was man damit wohl erreichen wollte. Korrekter wäre wohl Pow(2.0, Ceil(Log2(max(A,B)))).
Angenommen es gäbe einen Algorithmus mit imaginärer Laufzeit O(i * n), dann gilt O((i * n)^2) = O(-1 * n^2) d.h. wenn man diesen Algorithmus verschachtelt ist er fertig, bevor er angefangen hat.
Antworten