sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1539858 - in /sis/branches/JDK7/core/sis-utility/src: main/java/org/apache/sis/math/DecimalFunctions.java test/java/org/apache/sis/math/DecimalFunctionsTest.java
Date Thu, 07 Nov 2013 23:01:35 GMT
Author: desruisseaux
Date: Thu Nov  7 23:01:35 2013
New Revision: 1539858

URL: http://svn.apache.org/r1539858
Log:
More tests.

Modified:
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/math/DecimalFunctions.java
    sis/branches/JDK7/core/sis-utility/src/test/java/org/apache/sis/math/DecimalFunctionsTest.java

Modified: sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/math/DecimalFunctions.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/math/DecimalFunctions.java?rev=1539858&r1=1539857&r2=1539858&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/math/DecimalFunctions.java
[UTF-8] (original)
+++ sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/math/DecimalFunctions.java
[UTF-8] Thu Nov  7 23:01:35 2013
@@ -30,13 +30,13 @@ import static org.apache.sis.internal.ut
  * <ul>
  *   <li>Post-parsing methods {@link #floatToDouble(float)} and {@link #deltaForDoubleToDecimal(double)}:
  *     <ul>
- *       <li>for compensating error if the base 10 representation was <cite>definitive</cite>.</li>
+ *       <li>for compensating error when the base 10 representation is considered <cite>definitive</cite>.</li>
  *     </ul>
  *   </li>
  *   <li>Pre-formatting methods {@link #fractionDigitsForValue(double)} and
  *       {@link #fractionDigitsForDelta(double, boolean)}:
  *     <ul>
- *       <li>for formatting the exact amount of significant digits for a given precision.</li>
+ *       <li>for formatting numbers using the exact amount of significant digits for
a given precision.</li>
  *     </ul>
  *   </li>
  * </ul>
@@ -178,8 +178,8 @@ public final class DecimalFunctions exte
 
     /**
      * Returns the difference between the given {@code double} value and the representation
of that value in base 10.
-     * This method is equivalent to the following code, except that it is potentially faster
since the actual
-     * implementation avoid the creation of {@link java.math.BigDecimal} objects:
+     * This method is <em>approximatively</em> equivalent to the following code,
except that it is potentially faster
+     * since the actual implementation avoid the creation of {@link java.math.BigDecimal}
objects:
      *
      * {@preformat java
      *   BigDecimal base2  = new BigDecimal(value);     // Exact same value as stored in
IEEE 754 format.
@@ -244,33 +244,49 @@ public final class DecimalFunctions exte
          * 56 lower bits because of the scaling discussed in previous comment). In integer
arithmetic, the low
          * bits are always valid even if the multiplication overflow.
          */
-        long mc = m * ((long) cs);
-        mc &= (1L << PRECISION) - 1;
+        final long ci = (long) cs;
+        long mc = (m * ci) & ((1L << PRECISION) - 1);
         /*
          * Because we used c/10 instead than c,  the first (leftmost) decimal digit is potentially
the last
          * (rightmost) decimal digit of 'value'. Whether it is really the last 'value' digit
or not depends
-         * on the magnitude of last decimal digit compared to 1 ULP.
-         */
-        long lastDigit = (long) (Math.scalb(mc, -PRECISION) * 10); // [0 … 9] range.
-        if (lastDigit >= 5) lastDigit -= 10;    // Wraparound in the [-5 … 4] range.
-        /*
-         * Redo exactly the same calculation than above, but now using the real 'c' conversion
factor.
-         * The fraction digits extracted here are guaranteed to be smaller (after unscaling)
than 1 ULP.
+         * on the magnitude of last decimal digit compared to 1 ULP. The last digit is:
+         *
+         *    lastDigit = (mc * 10) >>> PRECISION;
+         *
+         * The above digit is guaranteed to be in the [0 … 9] range. We will wraparound
in the [-5 … 4] range
+         * because the delta must be no more than ±0.5 ULP of the 'value' argument.
          */
-        cs = Math.scalb(pow10(e10), e + PRECISION); // Equivalent to cs *= 10, but sometime
more accurate.
-        mc = m * ((long) cs);
-        mc &= (1L << PRECISION) - 1;
+        if (mc >= (5L << PRECISION) / 10) { // The  0.5 × 2^56  threshold.
+            mc -= (1L << PRECISION);        // Shift [5 … 9] digits to [-5 … -1].
+        }
         /*
-         * The 'lastDigit' that we computed above may be a significant digit of 'value',
or may be an artefact.
-         * Both cases can occur because 52 binary digits do not correspond to an integer
number of decimal digits.
-         * If it was not a significant digit of 'value', then we need to add it ourself.
-         */
-        // TODO: compare with 1 ULP for determining if it was a significant digit.
-        mc += lastDigit << PRECISION;
+         * At this point, 'mc' is less than 0.5 ULP if the last decimal digits were zero.
+         * But if the 'value' argument uses the full precision range of the 'double' type,
+         * then we still have 'mc' greater than 0.5 ULP is some cases. This difficulty happens
+         * because 52 binary digits do not correspond to an integer number of decimal digits.
+         *
+         * For now, I didn't found a better strategy than choosing the last decimal digit
in such
+         * a way that 'mc' is as small as possible. We don't really care what this last digit
is;
+         * we care only about the remainder after we removed the effet of that last digit.
+         */
+        if (Math.abs(mc) >= ci/2) {
+            mc %= (1L << PRECISION) / 10; // Remove the effect of last decimal digit.
+            if (mc >= 0) {                // Check if changing the sign would make it
smaller.
+                if (mc >= (1L << PRECISION) / 20) {
+                    mc -= (1L << PRECISION) / 10;
+                }
+            } else {
+                if (mc < (-1L << PRECISION) / 20) {
+                    mc += (1L << PRECISION) / 10;
+                }
+            }
+        }
         /*
          * We are done: unscale and return.
          */
-        return -Math.scalb(mc / cs, e);
+        final double delta = -Math.scalb(mc / cs, e);
+        assert Math.abs(delta) <= Math.ulp(value) / 2 : value;
+        return delta;
     }
 
     /**

Modified: sis/branches/JDK7/core/sis-utility/src/test/java/org/apache/sis/math/DecimalFunctionsTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-utility/src/test/java/org/apache/sis/math/DecimalFunctionsTest.java?rev=1539858&r1=1539857&r2=1539858&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-utility/src/test/java/org/apache/sis/math/DecimalFunctionsTest.java
[UTF-8] (original)
+++ sis/branches/JDK7/core/sis-utility/src/test/java/org/apache/sis/math/DecimalFunctionsTest.java
[UTF-8] Thu Nov  7 23:01:35 2013
@@ -96,6 +96,7 @@ public final strictfp class DecimalFunct
 
     /**
      * Tests {@link DecimalFunctions#deltaForDoubleToDecimal(double)}.
+     * This method uses {@link BigDecimal} as the reference implementation.
      */
     @Test
     @DependsOnMethod("testPow10")
@@ -121,6 +122,8 @@ public final strictfp class DecimalFunct
         assertEquals(-1.3471890270011499E-15, deltaForDoubleToDecimal(20.1168),  STRICT);
// Chain to metres
         /*
          * Tests random value that do not use the full 'double' accuracy.
+         * This is a simpler case than the next one after this one, because the
+         * final adjustment at the end of deltaForDoubleToDecimal is not needed.
          */
         final Random random = TestUtilities.createRandomNumberGenerator();
         for (int fractionDigits=0; fractionDigits<=9; fractionDigits++) {
@@ -132,14 +135,27 @@ public final strictfp class DecimalFunct
             }
         }
         /*
-         * Tests random values that do use the full 'double' accuracy.
+         * Tests random values that do use the full 'double' accuracy. First, tests a few
values which
+         * were known to fail in an earlier version of deltaForDoubleToDecimal, then uses
random values.
+         * The expected values were computed with BigDecimal. The tolerance thresholds were
determined
+         * empirically. Comments on the right side give the tolerance thresholds in ULP of
the delta.
+         * The later are sometime hight, but it does not really matter. What matter is the
tolerance
+         * relative to the given value, not to the returned delta.
          */
-        for (int i=0; i<0; i++) { // TODO: disabled for now
+        assertEquals(-1.9216378778219224E-23, deltaForDoubleToDecimal(3.3446045755169960E-7),
STRICT);
+        assertEquals(-4.1861088853329420E-24, deltaForDoubleToDecimal(3.5496578465465944E-7),
3E-39); //        4 ULP
+        assertEquals(-4.1997787803848041E-17, deltaForDoubleToDecimal(0.7714013208272988),
   2E-32); //        3 ULP
+        assertEquals( 4.0373325589462183E-18, deltaForDoubleToDecimal(0.37197394704138476),
  4E-33); //        4 ULP
+        assertEquals(-2.3295945035351907E-18, deltaForDoubleToDecimal(0.25380700796141886),
  4E-33); //        9 ULP
+        assertEquals(-4.1729149110324215E-18, deltaForDoubleToDecimal(0.6546245266605436),
   4E-32); //       43 ULP
+        assertEquals( 4.8633955884724856E-23, deltaForDoubleToDecimal(0.8234936921177336),
   4E-32); //  5666840 ULP
+        assertEquals(-2.1507730707526207E-25, deltaForDoubleToDecimal(0.19920566694813302),
  2E-33); // 36267774 ULP
+        for (int i=0; i<50; i++) {
             final double     ieee  = random.nextDouble();
             final String     text  = String.valueOf(ieee);
             final BigDecimal value = new BigDecimal(text);
-            final BigDecimal delta = value.subtract(new BigDecimal(ieee));
-            assertEquals(text, delta.doubleValue(), deltaForDoubleToDecimal(ieee), STRICT);
+            final double     delta = value.subtract(new BigDecimal(ieee)).doubleValue();
+            assertEquals(text, delta, deltaForDoubleToDecimal(ieee), StrictMath.ulp(ieee)
* 1E-12);
         }
     }
 



Mime
View raw message