Author: psteitz
Date: Tue Jul  4 01:09:25 2006
New Revision: 418934

URL: http://svn.apache.org/viewvc?rev=418934&view=rev
Log:
Added a nextAfter method in MathUtils to return the next
machine-representable number in a specified direction from a given
floating point number.  Used this to ensure that MathUtils.round does
not return incorrect results for numbers with bad IEEE754 
representations.
JIRA: MATH-151
Reported by Buza Zoltán
Patch submitted by Luc Maisonobe

Modified:
    jakarta/commons/proper/math/trunk/project.xml
    
jakarta/commons/proper/math/trunk/src/java/org/apache/commons/math/util/MathUtils.java
    
jakarta/commons/proper/math/trunk/src/test/org/apache/commons/math/util/MathUtilsTest.java
    jakarta/commons/proper/math/trunk/xdocs/changes.xml

Modified: jakarta/commons/proper/math/trunk/project.xml
URL: 
http://svn.apache.org/viewvc/jakarta/commons/proper/math/trunk/project.xml?rev=418934&r1=418933&r2=418934&view=diff
==============================================================================
--- jakarta/commons/proper/math/trunk/project.xml (original)
+++ jakarta/commons/proper/math/trunk/project.xml Tue Jul  4 01:09:25 2006
@@ -165,6 +165,9 @@
       <name>Piotr Kochanski</name>
     </contributor>
     <contributor>
+      <name>Luc Maisonobe</name>
+    </contributor>
+    <contributor>
       <name>Fredrik Norin</name>
     </contributor>
     <contributor>

Modified: 
jakarta/commons/proper/math/trunk/src/java/org/apache/commons/math/util/MathUtils.java
URL: 
http://svn.apache.org/viewvc/jakarta/commons/proper/math/trunk/src/java/org/apache/commons/math/util/MathUtils.java?rev=418934&r1=418933&r2=418934&view=diff
==============================================================================
--- 
jakarta/commons/proper/math/trunk/src/java/org/apache/commons/math/util/MathUtils.java
 (original)
+++ 
jakarta/commons/proper/math/trunk/src/java/org/apache/commons/math/util/MathUtils.java
 Tue Jul  4 01:09:25 2006
@@ -474,6 +474,55 @@
     }
 
     /**
+     * Get the closest machine representable number
+     * from a number in some direction.
+
+     * @param d base number
+     * @param direction (the only important thing is whether
+     * direction is greater or smaller than d)
+     * @return
+     */
+    public static double nextAfter(double d, double direction) {
+
+        // handling of some important special cases
+        if (Double.isNaN(d) || Double.isInfinite(d)) {
+                return d;
+        } else if (d == 0) {
+                return (direction < 0) ? -Double.MIN_VALUE : Double.MIN_VALUE;
+        }
+        // special cases MAX_VALUE to infinity and  MIN_VALUE to 0
+        // are handled just as normal numbers
+
+        // split the double in raw components
+        long bits     = Double.doubleToLongBits(d);
+        long sign     = bits & 0x8000000000000000L;
+        long exponent = bits & 0x7ff0000000000000L;
+        long mantissa = bits & 0x000fffffffffffffL;
+
+        if (d * (direction - d) >= 0) {
+                // we should increase the mantissa
+                if (mantissa == 0x000fffffffffffffL) {
+                        return Double.longBitsToDouble(sign |
+                                        (exponent + 0x0010000000000000L));
+                } else {
+                        return Double.longBitsToDouble(sign |
+                                        exponent | (mantissa + 1));
+                }
+        } else {
+                // we should decrease the mantissa
+                if (mantissa == 0L) {
+                        return Double.longBitsToDouble(sign |
+                                        (exponent - 0x0010000000000000L) |
+                                        0x000fffffffffffffL);
+                } else {
+                        return Double.longBitsToDouble(sign |
+                                        exponent | (mantissa - 1));
+                }
+        }
+
+    }
+
+    /**
      * Round the given value to the specified number of decimal places. The
      * value is rounded using the [EMAIL PROTECTED] BigDecimal#ROUND_HALF_UP} 
method.
      * 
@@ -499,9 +548,18 @@
      * @since 1.1
      */
     public static double round(double x, int scale, int roundingMethod) {
-        double sign = indicator(x);
-        double factor = Math.pow(10.0, scale) * sign;
-        return roundUnscaled(x * factor, sign, roundingMethod) / factor;
+        try {
+            return (new BigDecimal
+                   (new Double(x).toString())
+                   .setScale(scale, roundingMethod))
+                   .doubleValue();
+        } catch (NumberFormatException ex) {
+            if (Double.isInfinite(x)) {
+                return x;          
+            } else {
+                return Double.NaN;
+            }
+        }
     }
 
     /**
@@ -552,23 +610,24 @@
         switch (roundingMethod) {
         case BigDecimal.ROUND_CEILING :
             if (sign == -1) {
-                unscaled = Math.floor(unscaled);
+                unscaled = Math.floor(nextAfter(unscaled, 
Double.NEGATIVE_INFINITY));
             } else {
-                unscaled = Math.ceil(unscaled);
+                unscaled = Math.ceil(nextAfter(unscaled, 
Double.POSITIVE_INFINITY));
             }
             break;
         case BigDecimal.ROUND_DOWN :
-            unscaled = Math.floor(unscaled);
+            unscaled = Math.floor(nextAfter(unscaled, 
Double.NEGATIVE_INFINITY));
             break;
         case BigDecimal.ROUND_FLOOR :
             if (sign == -1) {
-                unscaled = Math.ceil(unscaled);
+                unscaled = Math.ceil(nextAfter(unscaled, 
Double.POSITIVE_INFINITY));
             } else {
-                unscaled = Math.floor(unscaled);
+                unscaled = Math.floor(nextAfter(unscaled, 
Double.NEGATIVE_INFINITY));
             }
             break;
         case BigDecimal.ROUND_HALF_DOWN : {
-            double fraction = Math.abs(unscaled - Math.floor(unscaled));
+            unscaled = nextAfter(unscaled, Double.NEGATIVE_INFINITY);
+            double fraction = unscaled - Math.floor(unscaled);
             if (fraction > 0.5) {
                 unscaled = Math.ceil(unscaled);
             } else {
@@ -577,7 +636,7 @@
             break;
         }
         case BigDecimal.ROUND_HALF_EVEN : {
-            double fraction = Math.abs(unscaled - Math.floor(unscaled));
+            double fraction = unscaled - Math.floor(unscaled);
             if (fraction > 0.5) {
                 unscaled = Math.ceil(unscaled);
             } else if (fraction < 0.5) {
@@ -593,7 +652,8 @@
             break;
         }
         case BigDecimal.ROUND_HALF_UP : {
-            double fraction = Math.abs(unscaled - Math.floor(unscaled));
+            unscaled = nextAfter(unscaled, Double.POSITIVE_INFINITY);
+            double fraction = unscaled - Math.floor(unscaled);
             if (fraction >= 0.5) {
                 unscaled = Math.ceil(unscaled);
             } else {
@@ -607,7 +667,7 @@
             }
             break;
         case BigDecimal.ROUND_UP :
-            unscaled = Math.ceil(unscaled);
+            unscaled = Math.ceil(nextAfter(unscaled,  
Double.POSITIVE_INFINITY));
             break;
         default :
             throw new IllegalArgumentException("Invalid rounding method.");

Modified: 
jakarta/commons/proper/math/trunk/src/test/org/apache/commons/math/util/MathUtilsTest.java
URL: 
http://svn.apache.org/viewvc/jakarta/commons/proper/math/trunk/src/test/org/apache/commons/math/util/MathUtilsTest.java?rev=418934&r1=418933&r2=418934&view=diff
==============================================================================
--- 
jakarta/commons/proper/math/trunk/src/test/org/apache/commons/math/util/MathUtilsTest.java
 (original)
+++ 
jakarta/commons/proper/math/trunk/src/test/org/apache/commons/math/util/MathUtilsTest.java
 Tue Jul  4 01:09:25 2006
@@ -583,15 +583,100 @@
         assertEquals(Float.NEGATIVE_INFINITY, 
MathUtils.round(Float.NEGATIVE_INFINITY, 2), 0.0f);
     }
     
+    public void testNextAfterSpecialCases() {
+        
assertTrue(Double.isInfinite(MathUtils.nextAfter(Double.NEGATIVE_INFINITY, 0)));
+        
assertTrue(Double.isInfinite(MathUtils.nextAfter(Double.POSITIVE_INFINITY, 0)));
+        assertTrue(Double.isNaN(MathUtils.nextAfter(Double.NaN, 0)));
+        assertTrue(Double.isInfinite(MathUtils.nextAfter( Double.MAX_VALUE, 
Double.POSITIVE_INFINITY)));
+        assertTrue(Double.isInfinite(MathUtils.nextAfter(-Double.MAX_VALUE, 
Double.NEGATIVE_INFINITY)));
+        assertEquals( Double.MIN_VALUE, MathUtils.nextAfter(0,  1), 0);
+        assertEquals(-Double.MIN_VALUE, MathUtils.nextAfter(0, -1), 0);
+        assertEquals(0, MathUtils.nextAfter( Double.MIN_VALUE, -1), 0);
+        assertEquals(0, MathUtils.nextAfter(-Double.MIN_VALUE,  1), 0);
+    }
+    
+    public void testNextAfter() {
+        // 0x402fffffffffffff 0x404123456789abcd -> 4030000000000000
+        assertEquals(16.0, MathUtils.nextAfter(15.999999999999998, 
34.27555555555555), 0.0);
+
+        // 0xc02fffffffffffff 0x404123456789abcd -> c02ffffffffffffe
+        assertEquals(-15.999999999999996, 
MathUtils.nextAfter(-15.999999999999998, 34.27555555555555), 0.0);
+
+        // 0x402fffffffffffff 0x400123456789abcd -> 402ffffffffffffe
+        assertEquals(15.999999999999996, 
MathUtils.nextAfter(15.999999999999998, 2.142222222222222), 0.0);
+
+        // 0xc02fffffffffffff 0x400123456789abcd -> c02ffffffffffffe
+        assertEquals(-15.999999999999996, 
MathUtils.nextAfter(-15.999999999999998, 2.142222222222222), 0.0);
+
+        // 0x4020000000000000 0x404123456789abcd -> 4020000000000001
+        assertEquals(8.000000000000002, MathUtils.nextAfter(8.0, 
34.27555555555555), 0.0);
+
+        // 0xc020000000000000 0x404123456789abcd -> c01fffffffffffff
+        assertEquals(-7.999999999999999, MathUtils.nextAfter(-8.0, 
34.27555555555555), 0.0);
+
+        // 0x4020000000000000 0x400123456789abcd -> 401fffffffffffff
+        assertEquals(7.999999999999999, MathUtils.nextAfter(8.0, 
2.142222222222222), 0.0);
+
+        // 0xc020000000000000 0x400123456789abcd -> c01fffffffffffff
+        assertEquals(-7.999999999999999, MathUtils.nextAfter(-8.0, 
2.142222222222222), 0.0);
+
+        // 0x3f2e43753d36a223 0x3f2e43753d36a224 -> 3f2e43753d36a224
+        assertEquals(2.308922399667661E-4, 
MathUtils.nextAfter(2.3089223996676606E-4, 2.308922399667661E-4), 0.0);
+
+        // 0x3f2e43753d36a223 0x3f2e43753d36a223 -> 3f2e43753d36a224
+        assertEquals(2.308922399667661E-4, 
MathUtils.nextAfter(2.3089223996676606E-4, 2.3089223996676606E-4), 0.0);
+
+        // 0x3f2e43753d36a223 0x3f2e43753d36a222 -> 3f2e43753d36a222
+        assertEquals(2.3089223996676603E-4, 
MathUtils.nextAfter(2.3089223996676606E-4, 2.3089223996676603E-4), 0.0);
+
+        // 0x3f2e43753d36a223 0xbf2e43753d36a224 -> 3f2e43753d36a222
+        assertEquals(2.3089223996676603E-4, 
MathUtils.nextAfter(2.3089223996676606E-4, -2.308922399667661E-4), 0.0);
+
+        // 0x3f2e43753d36a223 0xbf2e43753d36a223 -> 3f2e43753d36a222
+        assertEquals(2.3089223996676603E-4, 
MathUtils.nextAfter(2.3089223996676606E-4, -2.3089223996676606E-4), 0.0);
+
+        // 0x3f2e43753d36a223 0xbf2e43753d36a222 -> 3f2e43753d36a222
+        assertEquals(2.3089223996676603E-4, 
MathUtils.nextAfter(2.3089223996676606E-4, -2.3089223996676603E-4), 0.0);
+
+        // 0xbf2e43753d36a223 0x3f2e43753d36a224 -> bf2e43753d36a222
+        assertEquals(-2.3089223996676603E-4, 
MathUtils.nextAfter(-2.3089223996676606E-4, 2.308922399667661E-4), 0.0);
+
+        // 0xbf2e43753d36a223 0x3f2e43753d36a223 -> bf2e43753d36a222
+        assertEquals(-2.3089223996676603E-4, 
MathUtils.nextAfter(-2.3089223996676606E-4, 2.3089223996676606E-4), 0.0);
+
+        // 0xbf2e43753d36a223 0x3f2e43753d36a222 -> bf2e43753d36a222
+        assertEquals(-2.3089223996676603E-4, 
MathUtils.nextAfter(-2.3089223996676606E-4, 2.3089223996676603E-4), 0.0);
+
+        // 0xbf2e43753d36a223 0xbf2e43753d36a224 -> bf2e43753d36a224
+        assertEquals(-2.308922399667661E-4, 
MathUtils.nextAfter(-2.3089223996676606E-4, -2.308922399667661E-4), 0.0);
+
+        // 0xbf2e43753d36a223 0xbf2e43753d36a223 -> bf2e43753d36a224
+        assertEquals(-2.308922399667661E-4, 
MathUtils.nextAfter(-2.3089223996676606E-4, -2.3089223996676606E-4), 0.0);
+
+        // 0xbf2e43753d36a223 0xbf2e43753d36a222 -> bf2e43753d36a222
+        assertEquals(-2.3089223996676603E-4, 
MathUtils.nextAfter(-2.3089223996676606E-4, -2.3089223996676603E-4), 0.0);
+
+    }
+
     public void testRoundDouble() {
         double x = 1.234567890;
         assertEquals(1.23, MathUtils.round(x, 2), 0.0);
         assertEquals(1.235, MathUtils.round(x, 3), 0.0);
         assertEquals(1.2346, MathUtils.round(x, 4), 0.0);
         
+        // JIRA MATH-151
+        assertEquals(39.25,MathUtils.round(39.245, 2), 0.0);
+        assertEquals(39.24,MathUtils.round(39.245, 2, 
+                BigDecimal.ROUND_DOWN), 0.0);
+        double xx = 39.0;
+        xx = xx + 245d/1000d;
+        assertEquals(39.25,MathUtils.round(xx, 2), 0.0);
+        
         // BZ 35904
         assertEquals(30.1d, MathUtils.round(30.095d, 2), 0.0d);
         assertEquals(30.1d, MathUtils.round(30.095d, 1), 0.0d);
+        assertEquals(33.1d, MathUtils.round(33.095d, 1), 0.0d);
+        assertEquals(33.1d, MathUtils.round(33.095d, 2), 0.0d);
         assertEquals(50.09d,  MathUtils.round(50.085d, 2), 0.0d);
         assertEquals(50.19d,  MathUtils.round(50.185d, 2), 0.0d);
         assertEquals(50.01d,  MathUtils.round(50.005d, 2), 0.0d);
@@ -671,7 +756,10 @@
         } catch (IllegalArgumentException ex) {
             // success
         }
-        
+
+        // MATH-151
+        assertEquals(39.25, MathUtils.round(39.245, 2, 
BigDecimal.ROUND_HALF_UP), 0.0);
+     
         // special values
         TestUtils.assertEquals(Double.NaN, MathUtils.round(Double.NaN, 2), 
0.0);
         assertEquals(0.0, MathUtils.round(0.0, 2), 0.0);

Modified: jakarta/commons/proper/math/trunk/xdocs/changes.xml
URL: 
http://svn.apache.org/viewvc/jakarta/commons/proper/math/trunk/xdocs/changes.xml?rev=418934&r1=418933&r2=418934&view=diff
==============================================================================
--- jakarta/commons/proper/math/trunk/xdocs/changes.xml (original)
+++ jakarta/commons/proper/math/trunk/xdocs/changes.xml Tue Jul  4 01:09:25 2006
@@ -52,6 +52,13 @@
       <action dev="psteitz" type="fix" issue="MATH-60" due-to="Nhung Nnguyen">
         Modified ProperFractionFormat to reject embedded minus signs.
       </action>
+      <action dev="psteitz" type="fix" issue="MATH-151" due-to="Luc Maisonobe">
+        Added a nextAfter method in MathUtils to return the next
+        machine-representable number in a specified direction from a given
+        floating point number.  Used this to ensure that MathUtils.round does
+        not return incorrect results for numbers with bad IEEE754 
+        representations.
+      </action>
     </release>
     <release version="1.1" date="2005-12-17"  
  description="This is a maintenance release containing bug fixes and 
enhancements.



---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to