From 572688e736f22e5b23e2b3b19ba9d37770e8508c Mon Sep 17 00:00:00 2001
From: Sven Göthel <sgothel@jausoft.com>
Date: Wed, 14 Feb 2024 18:40:46 +0100
Subject: FloatUtil Epsilon ops: Generalize all epsilon operations by allowing
 passing a zero epsilon value to reduce complexity in upper math layers

---
 src/jogl/classes/com/jogamp/math/FloatUtil.java    |  32 ++++--
 .../test/junit/math/TestFloatUtil01NOUI.java       | 119 +++++++++++++++++----
 2 files changed, 127 insertions(+), 24 deletions(-)

(limited to 'src')

diff --git a/src/jogl/classes/com/jogamp/math/FloatUtil.java b/src/jogl/classes/com/jogamp/math/FloatUtil.java
index e75e86b7f..94182baec 100644
--- a/src/jogl/classes/com/jogamp/math/FloatUtil.java
+++ b/src/jogl/classes/com/jogamp/math/FloatUtil.java
@@ -1428,7 +1428,11 @@ public final class FloatUtil {
   }
 
   /**
-   * Return true if both values are equal, i.e. their absolute delta < <code>epsilon</code>.
+   * Returns true if both values are equal, i.e. their absolute delta < {@code epsilon} if 0 != {@code epsilon},
+   * otherwise == {@code 0}.
+   * <p>
+   * {@code epsilon} is allowed to be {@code 0}.
+   * </p>
    * <p>
    * Implementation considers following corner cases:
    * <ul>
@@ -1440,7 +1444,8 @@ public final class FloatUtil {
    * @see #EPSILON
    */
   public static boolean isEqual(final float a, final float b, final float epsilon) {
-      if ( Math.abs(a - b) < epsilon ) {
+      if( 0 == epsilon && Math.abs(a - b) == 0 ||
+          0 != epsilon && Math.abs(a - b) < epsilon ) {
           return true;
       } else {
           // Values are equal (Inf, Nan .. )
@@ -1513,7 +1518,11 @@ public final class FloatUtil {
   }
 
   /**
-   * Return true if both values are equal, i.e. their absolute delta < <code>epsilon</code>.
+   * Returns {@code -1}, {@code 0} or {@code 1} if {@code a} is less, equal or greater than {@code b},
+   * taking {@code epsilon} into account for equality.
+   * <p>
+   * {@code epsilon} is allowed to be {@code 0}.
+   * </p>
    * <p>
    * Implementation considers following corner cases:
    * <ul>
@@ -1527,7 +1536,8 @@ public final class FloatUtil {
    * @see #EPSILON
    */
   public static int compare(final float a, final float b, final float epsilon) {
-      if ( Math.abs(a - b) < epsilon ) {
+      if( 0 == epsilon && Math.abs(a - b) == 0 ||
+          0 != epsilon && Math.abs(a - b) < epsilon ) {
           return 0;
       } else {
           return compare(a, b);
@@ -1535,11 +1545,21 @@ public final class FloatUtil {
   }
 
   /**
-   * Return true if value is zero, i.e. it's absolute value < <code>epsilon</code>.
+   * Returns true if value is zero, i.e. it's absolute value < {@code epsilon} if 0 != {@code epsilon},
+   * otherwise {@code 0 == a}.
+   * <p>
+   * {@code epsilon} is allowed to be {@code 0}.
+   * </p>
+   * <pre>
+   *    return 0 == epsilon && 0 == a || 0 != epsilon && Math.abs(a) < epsilon
+   * </pre>
+   * @param a value to test
+   * @param epsilon optional positive epsilon value, maybe {@code 0}
    * @see #EPSILON
    */
   public static boolean isZero(final float a, final float epsilon) {
-      return Math.abs(a) < epsilon;
+      return 0 == epsilon && a == 0 ||
+             0 != epsilon && Math.abs(a) < epsilon;
   }
 
   /**
diff --git a/src/test/com/jogamp/opengl/test/junit/math/TestFloatUtil01NOUI.java b/src/test/com/jogamp/opengl/test/junit/math/TestFloatUtil01NOUI.java
index aae2b02e0..c67db9f1d 100644
--- a/src/test/com/jogamp/opengl/test/junit/math/TestFloatUtil01NOUI.java
+++ b/src/test/com/jogamp/opengl/test/junit/math/TestFloatUtil01NOUI.java
@@ -79,36 +79,83 @@ public class TestFloatUtil01NOUI extends JunitTracer {
     }
 
     @Test
-    public void test01aZeroWithFixedEpsilon() {
-        testZeroWithEpsilon(10, FloatUtil.EPSILON);
+    public void test01aZeroWithFixedEpsilon0() {
+        testZeroWithEpsilon0(100);
+    }
+    @Test
+    public void test01bZeroWithFixedEpsilon1() {
+        testZeroWithEpsilon1(200, FloatUtil.EPSILON);
     }
     @Test
     public void test01bZeroWithMachEpsilon() {
-        testZeroWithEpsilon(100, MACH_EPSILON);
+        testZeroWithEpsilon1(300, MACH_EPSILON);
+    }
+    @Test
+    public void test01cZeroWithZeroEpsilon() {
+        final float EPSILON = 0.0f;
+        int i = 400;
+        System.err.println();
+        testZeroWithEpsilon1(i++, true, 0f, EPSILON);
+        testZeroWithEpsilon1(i++, true, 0f-EPSILON/2f, EPSILON);
+        testZeroWithEpsilon1(i++, true, 0f+EPSILON/2f, EPSILON);
+        testZeroWithEpsilon1(i++, false, 0f-Float.MIN_VALUE, EPSILON);
+        testZeroWithEpsilon1(i++, false, 0f+Float.MIN_VALUE, EPSILON);
+        testZeroWithEpsilon1(i++, true, -0f, EPSILON);
+        testZeroWithEpsilon1(i++, true, +0f, EPSILON);
+
+        testZeroWithEpsilon1(i++, false, 0f+EPSILON+Float.MIN_VALUE, EPSILON);
+        testZeroWithEpsilon1(i++, false, 0f-EPSILON-Float.MIN_VALUE, EPSILON);
+
+        // Unpredicted .. accuracy beyond epsilon, or deltaMachEpsLEQEpsilon or deltaFixedEpsLEQEpsilon;
+        dumpTestWE(i++, 1, 0f, 0f+EPSILON-Float.MIN_VALUE, EPSILON);
+        dumpTestWE(i++, 1, 0f, 0f-EPSILON+Float.MIN_VALUE, EPSILON);
     }
-    private void testZeroWithEpsilon(int i, final float EPSILON) {
+    private void testZeroWithEpsilon1(int i, final float EPSILON) {
         System.err.println();
-        testZeroWithEpsilon(i++, true, 0f, EPSILON);
-        testZeroWithEpsilon(i++, true, 0f-EPSILON/2f, EPSILON);
-        testZeroWithEpsilon(i++, true, 0f+EPSILON/2f, EPSILON);
-        testZeroWithEpsilon(i++, true, 0f-Float.MIN_VALUE, EPSILON);
-        testZeroWithEpsilon(i++, true, 0f+Float.MIN_VALUE, EPSILON);
-        testZeroWithEpsilon(i++, true, -0f, EPSILON);
-        testZeroWithEpsilon(i++, true, +0f, EPSILON);
+        testZeroWithEpsilon1(i++, true, 0f, EPSILON);
+        testZeroWithEpsilon1(i++, true, 0f-EPSILON/2f, EPSILON);
+        testZeroWithEpsilon1(i++, true, 0f+EPSILON/2f, EPSILON);
+        testZeroWithEpsilon1(i++, true, 0f-Float.MIN_VALUE, EPSILON);
+        testZeroWithEpsilon1(i++, true, 0f+Float.MIN_VALUE, EPSILON);
+        testZeroWithEpsilon1(i++, true, -0f, EPSILON);
+        testZeroWithEpsilon1(i++, true, +0f, EPSILON);
 
-        testZeroWithEpsilon(i++, false, 0f+EPSILON+Float.MIN_VALUE, EPSILON);
-        testZeroWithEpsilon(i++, false, 0f-EPSILON-Float.MIN_VALUE, EPSILON);
+        testZeroWithEpsilon1(i++, false, 0f+EPSILON+Float.MIN_VALUE, EPSILON);
+        testZeroWithEpsilon1(i++, false, 0f-EPSILON-Float.MIN_VALUE, EPSILON);
 
         // Unpredicted .. accuracy beyond epsilon, or deltaMachEpsLEQEpsilon or deltaFixedEpsLEQEpsilon;
         dumpTestWE(i++, 1, 0f, 0f+EPSILON-Float.MIN_VALUE, EPSILON);
         dumpTestWE(i++, 1, 0f, 0f-EPSILON+Float.MIN_VALUE, EPSILON);
     }
-    private void testZeroWithEpsilon(final int tstNum, final boolean exp, final float a, final float EPSILON) {
+    private void testZeroWithEpsilon1(final int tstNum, final boolean exp, final float a, final float EPSILON) {
         final boolean zero =  FloatUtil.isZero(a, EPSILON);
         final float delta = a-0f;
         System.err.println("Zero."+tstNum+": a: "+a+", -> d "+delta+", exp "+exp+", zero "+zero+", epsilon "+EPSILON);
         Assert.assertEquals("Zero failed a: "+a+" within "+EPSILON, exp, zero);
     }
+    private void testZeroWithEpsilon0(int i) {
+        System.err.println();
+        testZeroWithEpsilon0(i++, true, 0f);
+        testZeroWithEpsilon0(i++, true, 0f-FloatUtil.EPSILON/2f);
+        testZeroWithEpsilon0(i++, true, 0f+FloatUtil.EPSILON/2f);
+        testZeroWithEpsilon0(i++, true, 0f-Float.MIN_VALUE);
+        testZeroWithEpsilon0(i++, true, 0f+Float.MIN_VALUE);
+        testZeroWithEpsilon0(i++, true, -0f);
+        testZeroWithEpsilon0(i++, true, +0f);
+
+        testZeroWithEpsilon0(i++, false, 0f+FloatUtil.EPSILON+Float.MIN_VALUE);
+        testZeroWithEpsilon0(i++, false, 0f-FloatUtil.EPSILON-Float.MIN_VALUE);
+
+        // Unpredicted .. accuracy beyond epsilon, or deltaMachEpsLEQEpsilon or deltaFixedEpsLEQEpsilon;
+        dumpTestWE(i++, 1, 0f, 0f+FloatUtil.EPSILON-Float.MIN_VALUE, FloatUtil.EPSILON);
+        dumpTestWE(i++, 1, 0f, 0f-FloatUtil.EPSILON+Float.MIN_VALUE, FloatUtil.EPSILON);
+    }
+    private void testZeroWithEpsilon0(final int tstNum, final boolean exp, final float a) {
+        final boolean zero =  FloatUtil.isZero(a);
+        final float delta = a-0f;
+        System.err.println("Zero."+tstNum+": a: "+a+", -> d "+delta+", exp "+exp+", zero "+zero+", build-in epsilon "+FloatUtil.EPSILON);
+        Assert.assertEquals("Zero failed a: "+a+" within build-in "+FloatUtil.EPSILON, exp, zero);
+    }
 
     @Test
     public void test02EqualsNoEpsilon() {
@@ -146,11 +193,42 @@ public class TestFloatUtil01NOUI extends JunitTracer {
 
     @Test
     public void test03aEqualsWithFixedEpsilon() {
-        testEqualsWithEpsilon(10, FloatUtil.EPSILON);
+        testEqualsWithEpsilon(100, FloatUtil.EPSILON);
     }
     @Test
     public void test03bEqualsWithMachEpsilon() {
-        testEqualsWithEpsilon(50, MACH_EPSILON);
+        testEqualsWithEpsilon(200, MACH_EPSILON);
+    }
+    @Test
+    public void test03cEqualsWithZeroEpsilon() {
+        // testEqualsWithEpsilon(300, 0.0f);
+        //
+        // Results marked with (***) are actually true epsilon artifacts
+        // reversing the result due to no-epsilon comparison
+        final float EPSILON = 0.0f;
+        int i=400;
+        System.err.println();
+        testEqualsWithEpsilon(i++, true, 0f, 0f, EPSILON);
+        testEqualsWithEpsilon(i++, true, 1f, 1f-EPSILON/2f, EPSILON);
+        testEqualsWithEpsilon(i++, true, 1f, 1f+EPSILON/2f, EPSILON);
+        testEqualsWithEpsilon(i++, true, 1f, 1f-Float.MIN_VALUE, EPSILON);  // ***
+        testEqualsWithEpsilon(i++, true, 1f, 1f-Float.MIN_NORMAL, EPSILON); // ***
+        testEqualsWithEpsilon(i++, true, 1f, 1f+Float.MIN_VALUE, EPSILON);  // ***
+        testEqualsWithEpsilon(i++, true, Float.MAX_VALUE, Float.MAX_VALUE, EPSILON);
+        testEqualsWithEpsilon(i++, true, Float.MIN_VALUE, Float.MIN_VALUE, EPSILON);
+        testEqualsWithEpsilon(i++, true, Float.MIN_NORMAL, Float.MIN_NORMAL, EPSILON);
+        testEqualsWithEpsilon(i++, true, Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, EPSILON);
+        testEqualsWithEpsilon(i++, true, Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, EPSILON);
+        testEqualsWithEpsilon(i++, true, Float.NaN, Float.NaN, EPSILON);
+        testEqualsWithEpsilon(i++, true, -0f, 0f, EPSILON);
+        testEqualsWithEpsilon(i++, true, 0f, -0f, EPSILON);
+
+        testEqualsWithEpsilon(i++, true, 1f, 1f+EPSILON+Float.MIN_VALUE, EPSILON); // ***
+        testEqualsWithEpsilon(i++, true, 1f, 1f-EPSILON-Float.MIN_VALUE, EPSILON); // ***
+
+        // Unpredicted .. accuracy beyond epsilon, or deltaMachEpsLEQEpsilon or deltaFixedEpsLEQEpsilon;
+        dumpTestWE(i++, 1, 1f, 1f+EPSILON-Float.MIN_VALUE, EPSILON);
+        dumpTestWE(i++, 1, 1f, 1f-EPSILON+Float.MIN_VALUE, EPSILON);
     }
     private void testEqualsWithEpsilon(int i, final float EPSILON) {
         System.err.println();
@@ -158,6 +236,7 @@ public class TestFloatUtil01NOUI extends JunitTracer {
         testEqualsWithEpsilon(i++, true, 1f, 1f-EPSILON/2f, EPSILON);
         testEqualsWithEpsilon(i++, true, 1f, 1f+EPSILON/2f, EPSILON);
         testEqualsWithEpsilon(i++, true, 1f, 1f-Float.MIN_VALUE, EPSILON);
+        testEqualsWithEpsilon(i++, true, 1f, 1f-Float.MIN_NORMAL, EPSILON);
         testEqualsWithEpsilon(i++, true, 1f, 1f+Float.MIN_VALUE, EPSILON);
         testEqualsWithEpsilon(i++, true, Float.MAX_VALUE, Float.MAX_VALUE, EPSILON);
         testEqualsWithEpsilon(i++, true, Float.MIN_VALUE, Float.MIN_VALUE, EPSILON);
@@ -232,11 +311,15 @@ public class TestFloatUtil01NOUI extends JunitTracer {
 
     @Test
     public void test05aCompareWithFixedEpsilon() {
-        test05CompareWithEpsilon(10, FloatUtil.EPSILON);
+        test05CompareWithEpsilon(100, FloatUtil.EPSILON);
     }
     @Test
     public void test05bCompareWithMachEpsilon() {
-        test05CompareWithEpsilon(50, MACH_EPSILON);
+        test05CompareWithEpsilon(200, MACH_EPSILON);
+    }
+    @Test
+    public void test05cCompareWithZeroEpsilon() {
+        test05CompareWithEpsilon(300, 0.0f);
     }
     private void test05CompareWithEpsilon(int i, final float epsilon) {
         System.err.println();
-- 
cgit v1.2.3