Skip to content

Commit 3580649

Browse files
committed
Allow DD creation from an unsigned long value.
Added to support interpolation of ordered long values in STATISTICS-94.
1 parent 647a891 commit 3580649

3 files changed

Lines changed: 67 additions & 2 deletions

File tree

  • commons-numbers-core/src
    • main/java/org/apache/commons/numbers/core
    • test/java/org/apache/commons/numbers/core
  • src/changes

commons-numbers-core/src/main/java/org/apache/commons/numbers/core/DD.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,33 @@ public static DD of(long x) {
361361
return fastTwoSum(a, b);
362362
}
363363

364+
/**
365+
* Creates the double-double number using the argument {@code x} as an unsigned
366+
* 64-bit integer.
367+
*
368+
* <p>Note this method preserves all 64-bits of precision. It can be used to
369+
* represent the positive difference between two ordered {@code long} values
370+
* without using {@link DD#subtract(DD)}.
371+
*
372+
* <p>Given two {@code long} values {@code a <= b} the following are equivalent:
373+
* <pre>
374+
* DD.of(b).subtract(DD.of(a))
375+
*
376+
* DD.ofUnsigned(b - a)
377+
* </pre>
378+
*
379+
* @param x Value.
380+
* @return the double-double
381+
*/
382+
public static DD ofUnsigned(long x) {
383+
// Similar to of(long) but the high part is composed as an unsigned double
384+
final long a = x & HIGH32_MASK;
385+
final long b = x - a;
386+
// Convert the unsigned magnitude to a double
387+
final double aa = (a >>> 1) * 2.0;
388+
return fastTwoSum(aa, b);
389+
}
390+
364391
/**
365392
* Creates the double-double number {@code (z, zz)} using the {@code double} representation
366393
* of the argument {@code x}; the low part is the {@code double} representation of the

commons-numbers-core/src/test/java/org/apache/commons/numbers/core/DDTest.java

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.apache.commons.numbers.core;
1818

1919
import java.math.BigDecimal;
20+
import java.math.BigInteger;
2021
import java.math.MathContext;
2122
import java.math.RoundingMode;
2223
import java.util.ArrayList;
@@ -26,6 +27,7 @@
2627
import java.util.function.Supplier;
2728
import java.util.function.UnaryOperator;
2829
import java.util.stream.IntStream;
30+
import java.util.stream.LongStream;
2931
import java.util.stream.Stream;
3032
import org.apache.commons.rng.UniformRandomProvider;
3133
import org.apache.commons.rng.simple.RandomSource;
@@ -131,8 +133,9 @@ void testOfInt(int x) {
131133
* to rounding to 2^53 so we have extra cases for this.
132134
*/
133135
@ParameterizedTest
134-
@ValueSource(longs = {0, 1, 42, 89545664, 8263492364L, Long.MIN_VALUE,
135-
Long.MAX_VALUE - (1L << 10), Long.MAX_VALUE - 42, Long.MAX_VALUE - 1, Long.MAX_VALUE})
136+
@ValueSource(longs = {0, 1, 42, 89545664, 8263492364L,
137+
Long.MAX_VALUE - (1L << 10), Long.MAX_VALUE - 42, Long.MAX_VALUE - 1, Long.MAX_VALUE,
138+
Long.MIN_VALUE})
136139
void testOfLong(long x) {
137140
DD dd = DD.of(x);
138141
Assertions.assertEquals(x, dd.hi(), "x hi should be (double) x");
@@ -142,6 +145,38 @@ void testOfLong(long x) {
142145
Assertions.assertEquals(BigDecimal.valueOf(-x).subtract(bd(-x)).doubleValue(), dd.lo(), "-x lo should be remaining bits");
143146
}
144147

148+
/**
149+
* Test conversion of an unsigned {@code long}.
150+
*/
151+
@ParameterizedTest
152+
@ValueSource(longs = {0, 1, 42, 89545664, 8263492364L,
153+
-1, -42, -89545664, -8263492364L,
154+
Long.MAX_VALUE - (1L << 10), Long.MAX_VALUE - 42, Long.MAX_VALUE - 1, Long.MAX_VALUE,
155+
Long.MIN_VALUE + (1L << 10), Long.MIN_VALUE + 42, Long.MIN_VALUE + 1, Long.MIN_VALUE})
156+
@MethodSource
157+
void testOfUnsignedLong(long x) {
158+
final DD dd = DD.ofUnsigned(x);
159+
Assertions.assertTrue(dd.hi() >= 0, "x hi should be positive");
160+
// Create the exact unsigned value
161+
final BigInteger xx;
162+
if (x < 0) {
163+
// 63-bits plus 2^63
164+
xx = BigInteger.valueOf(x & Long.MAX_VALUE).or(BigInteger.ONE.shiftLeft(63));
165+
} else {
166+
xx = BigInteger.valueOf(x);
167+
}
168+
final BigDecimal expected = new BigDecimal(xx);
169+
final double hi = expected.doubleValue();
170+
final double lo = expected.subtract(bd(hi)).doubleValue();
171+
Assertions.assertEquals(hi, dd.hi(), "x hi");
172+
Assertions.assertEquals(lo, dd.lo(), "x lo");
173+
}
174+
175+
static LongStream testOfUnsignedLong() {
176+
// Random
177+
return createRNG().longs(10);
178+
}
179+
145180
@ParameterizedTest
146181
@MethodSource(value = {"twoSumAddends"})
147182
void testFromBigDecimal(double x, double y) {

src/changes/changes.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ If the output is not quite correct, check for invisible trailing spaces!
5656
<release version="1.3" date="TBD" description="
5757
New features, updates and bug fixes.
5858
">
59+
<action dev="aherbert" type="add">
60+
"DD": Allow creation from an unsigned long value.
61+
</action>
5962
<action dev="aherbert" type="add" issue="NUMBERS-208">
6063
"Selection": Add support for selection from a long array.
6164
</action>

0 commit comments

Comments
 (0)