This is an automated email from the ASF dual-hosted git repository.

aherbert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-numbers.git


The following commit(s) were added to refs/heads/master by this push:
     new 1e98cfad DD: Fix truncation towards zero of values in (-1, 0) (#225)
1e98cfad is described below

commit 1e98cfad5931f9a024d9ad34e1a2face9c43146c
Author: 陈志谦 <[email protected]>
AuthorDate: Thu Nov 5 05:25:58 2026 +0800

    DD: Fix truncation towards zero of values in (-1, 0) (#225)
    
    DD.longValue() has an overflow check that is too strict. The sum of the high
    and low parts of a value in the interval (-1, 0) is -1 + 1, which is 0, and
    (reflected onto a positive high part) 1 - 1, which is also 0. The check
    recognised that very case as an overflow, because a sum of 0 from parts that
    are not both 0 has a different sign bit from the high part:
    
      DD.ofSum(-1.0, 1e-20).longValue()   // -1, must be 0
      DD.ofSum(1.0, -1e-20).longValue()   //  1, must be 0
    
    The check now tests whether the sum of the equal signed parts has changed
    sign, using a1 + b, as the sign of a2 matches a1 or a2 was zero. That avoids
    an overflow of the intermediate sum of a1 and b.
    
    Signed-off-by: simpleqt <[email protected]>
---
 .../java/org/apache/commons/numbers/core/DD.java   |  9 ++++++--
 .../org/apache/commons/numbers/core/DDTest.java    | 25 ++++++++++++++++++++++
 2 files changed, 32 insertions(+), 2 deletions(-)

diff --git 
a/commons-numbers-core/src/main/java/org/apache/commons/numbers/core/DD.java 
b/commons-numbers-core/src/main/java/org/apache/commons/numbers/core/DD.java
index 13f5f87c..61540490 100644
--- a/commons-numbers-core/src/main/java/org/apache/commons/numbers/core/DD.java
+++ b/commons-numbers-core/src/main/java/org/apache/commons/numbers/core/DD.java
@@ -753,11 +753,16 @@ public final class DD
             // with respect to the sign of the high part.
             final long b = (long) (a < 0 ? Math.ceil(xx) : Math.floor(xx));
 
+            // Sum the parts. A value in (-1, 0) truncates to 0, so a sum of 
zero is
+            // a result and not an overflow. An overflow occurs only when the 
sum
+            // cannot be represented as a long.
             final long sum = a1 + b + a2;
-            // Avoid overflow. If the sum has changed sign then an overflow 
occurred.
+            // Avoid overflow.
+            // If the sum of equal signed parts has changed sign then an 
overflow occurred.
             // This happens when high == 2^63 and the low part is additional 
magnitude.
             // The xor operation creates a negative if the signs are different.
-            if ((sum ^ a) >= 0) {
+            // Check the signs using a1 + b as the sign of a2 matches a1 or it 
was zero.
+            if (((sum ^ a1) & (sum ^ b)) >= 0) {
                 return sum;
             }
         }
diff --git 
a/commons-numbers-core/src/test/java/org/apache/commons/numbers/core/DDTest.java
 
b/commons-numbers-core/src/test/java/org/apache/commons/numbers/core/DDTest.java
index ae18f545..c2cd5d33 100644
--- 
a/commons-numbers-core/src/test/java/org/apache/commons/numbers/core/DDTest.java
+++ 
b/commons-numbers-core/src/test/java/org/apache/commons/numbers/core/DDTest.java
@@ -356,6 +356,31 @@ class DDTest {
         }
     }
 
+    @Test
+    void testLongValueTruncatesTowardsZeroWhenTheLowPartReversesTheSign() {
+        // A double-double with a negative high part and a positive low part 
is in the interval (-1, 0). Truncation
+        // towards zero must give 0 for both conversions.
+        final DD a = DD.ofSum(-1.0, 1e-20);
+        final BigDecimal bd = a.bigDecimalValue();
+        Assertions.assertEquals(-1, bd.signum(), "sign");
+        Assertions.assertTrue(bd.abs().compareTo(BigDecimal.ONE) < 0, "value 
is in (-1, 0)");
+        Assertions.assertEquals(0, a.longValue(), "longValue");
+        Assertions.assertEquals(0, a.intValue(), "intValue");
+        // The mirror value is unchanged
+        Assertions.assertEquals(0, DD.ofSum(1.0, -1e-20).longValue(), 
"mirror");
+        // A high part away from zero is unchanged
+        Assertions.assertEquals(-2, DD.ofSum(-3.0, 1e-20).longValue(), "value 
in (-3, -2)");
+        // A low part of the opposite sign to a high part just above 
Integer.MIN_VALUE truncates towards zero
+        final DD c = DD.ofSum(Integer.MIN_VALUE, 1e-7);
+        
Assertions.assertTrue(c.bigDecimalValue().compareTo(BigDecimal.valueOf(Integer.MIN_VALUE))
 > 0, "value above min");
+        Assertions.assertEquals(Integer.MIN_VALUE + 1, c.longValue(), "above 
min longValue");
+        Assertions.assertEquals(Integer.MIN_VALUE + 1, c.intValue(), "above 
min intValue");
+        // A value below the minimum int retains the clipped limit
+        final DD d = DD.ofSum(-0x1.00000001p31, 1e-7);
+        
Assertions.assertTrue(d.bigDecimalValue().compareTo(BigDecimal.valueOf(Integer.MIN_VALUE))
 < 0, "value below min");
+        Assertions.assertEquals(Integer.MIN_VALUE, d.intValue(), "clipped 
intValue");
+    }
+
     /**
      * Test the value is exact after a sum of longs.
      * This exercises {@link DD#of(long)}, {@link DD#bigDecimalValue()} and 
{@link DD#longValue()}.

Reply via email to