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()}.