Added divideUnsigned and remainderUnsigned to ArithmeticUtils.

JIRA: MATH-1271
This commit is contained in:
Qualtagh 2015-09-11 18:16:35 +02:00 committed by Luc Maisonobe
parent 85d2568dea
commit 8c141a1705
4 changed files with 340 additions and 0 deletions

View File

@ -292,6 +292,9 @@
<contributor>
<name>Todd C. Parnell</name>
</contributor>
<contributor>
<name>Qualtagh</name>
</contributor>
<contributor>
<name>Andreas Rieger</name>
</contributor>

View File

@ -54,6 +54,9 @@ If the output is not quite correct, check for invisible trailing spaces!
</release>
<release version="4.0" date="XXXX-XX-XX" description="">
<action dev="luc" type="add" issue="MATH-1271" due-to="Qualtagh">
Added divideUnsigned and remainderUnsigned to ArithmeticUtils.
</action>
<action dev="luc" type="fix" issue="MATH-1272" due-to="Qualtagh"> <!-- backported to 3.6 -->
Fixed infinite loop in FastMath.pow(double, long) with Long.MIN_VALUE.
</action>

View File

@ -665,4 +665,139 @@ public final class ArithmeticUtils {
public static boolean isPowerOfTwo(long n) {
return (n > 0) && ((n & (n - 1)) == 0);
}
/**
* Returns the unsigned remainder from dividing the first argument
* by the second where each argument and the result is interpreted
* as an unsigned value.
* <p>This method doesn't use long datatype unlike it's used in
* {@link java.lang.Integer#remainderUnsigned}.
*
* @param dividend the value to be divided
* @param divisor the value doing the dividing
* @return the unsigned remainder of the first argument divided by
* the second argument
* @see java.lang.Integer#remainderUnsigned
* @since 4.0
*/
public static int remainderUnsigned(int dividend, int divisor) {
if (divisor >= 0) {
if (dividend >= 0) {
return dividend % divisor;
}
// The implementation is a Java port of algorithm described in the book
// "Hacker's Delight" (section "Unsigned short division from signed division").
int q = ((dividend >>> 1) / divisor) << 1;
dividend -= q * divisor;
if (dividend < 0 || dividend >= divisor) {
dividend -= divisor;
}
return dividend;
}
return dividend >= 0 || dividend < divisor ? dividend : dividend - divisor;
}
/**
* Returns the unsigned remainder from dividing the first argument
* by the second where each argument and the result is interpreted
* as an unsigned value.
* <p>This method doesn't use BigInteger datatype unlike it's used in
* {@link java.lang.Long#remainderUnsigned}.
*
* @param dividend the value to be divided
* @param divisor the value doing the dividing
* @return the unsigned remainder of the first argument divided by
* the second argument
* @see java.lang.Long#remainderUnsigned
* @since 4.0
*/
public static long remainderUnsigned(long dividend, long divisor) {
if (divisor >= 0L) {
if (dividend >= 0L) {
return dividend % divisor;
}
// The implementation is a Java port of algorithm described in the book
// "Hacker's Delight" (section "Unsigned short division from signed division").
long q = ((dividend >>> 1) / divisor) << 1;
dividend -= q * divisor;
if (dividend < 0L || dividend >= divisor) {
dividend -= divisor;
}
return dividend;
}
return dividend >= 0L || dividend < divisor ? dividend : dividend - divisor;
}
/**
* Returns the unsigned quotient of dividing the first argument by
* the second where each argument and the result is interpreted as
* an unsigned value.
* <p>Note that in two's complement arithmetic, the three other
* basic arithmetic operations of add, subtract, and multiply are
* bit-wise identical if the two operands are regarded as both
* being signed or both being unsigned. Therefore separate {@code
* addUnsigned}, etc. methods are not provided.
* <p>This method doesn't use long datatype unlike it's used in
* {@link java.lang.Integer#divideUnsigned}.
*
* @param dividend the value to be divided
* @param divisor the value doing the dividing
* @return the unsigned quotient of the first argument divided by
* the second argument
* @see java.lang.Integer#divideUnsigned
* @since 4.0
*/
public static int divideUnsigned(int dividend, int divisor) {
if (divisor >= 0) {
if (dividend >= 0) {
return dividend / divisor;
}
// The implementation is a Java port of algorithm described in the book
// "Hacker's Delight" (section "Unsigned short division from signed division").
int q = ((dividend >>> 1) / divisor) << 1;
dividend -= q * divisor;
if (dividend < 0L || dividend >= divisor) {
q++;
}
return q;
}
return dividend >= 0 || dividend < divisor ? 0 : 1;
}
/**
* Returns the unsigned quotient of dividing the first argument by
* the second where each argument and the result is interpreted as
* an unsigned value.
* <p>Note that in two's complement arithmetic, the three other
* basic arithmetic operations of add, subtract, and multiply are
* bit-wise identical if the two operands are regarded as both
* being signed or both being unsigned. Therefore separate {@code
* addUnsigned}, etc. methods are not provided.
* <p>This method doesn't use BigInteger datatype unlike it's used in
* {@link java.lang.Long#divideUnsigned}.
*
* @param dividend the value to be divided
* @param divisor the value doing the dividing
* @return the unsigned quotient of the first argument divided by
* the second argument
* @see java.lang.Long#divideUnsigned
* @since 4.0
*/
public static long divideUnsigned(long dividend, long divisor) {
if (divisor >= 0L) {
if (dividend >= 0L) {
return dividend / divisor;
}
// The implementation is a Java port of algorithm described in the book
// "Hacker's Delight" (section "Unsigned short division from signed division").
long q = ((dividend >>> 1) / divisor) << 1;
dividend -= q * divisor;
if (dividend < 0L || dividend >= divisor) {
q++;
}
return q;
}
return dividend >= 0L || dividend < divisor ? 0L : 1L;
}
}

View File

@ -587,4 +587,203 @@ public class ArithmeticUtilsTest {
// success
}
}
/**
* Testing helper method.
* @return an array of int numbers containing corner cases:<ul>
* <li>values near the beginning of int range,</li>
* <li>values near the end of int range,</li>
* <li>values near zero</li>
* <li>and some randomly distributed values.</li>
* </ul>
*/
private static int[] getIntSpecialCases() {
int ints[] = new int[100];
int i = 0;
ints[i++] = Integer.MAX_VALUE;
ints[i++] = Integer.MAX_VALUE - 1;
ints[i++] = 100;
ints[i++] = 101;
ints[i++] = 102;
ints[i++] = 300;
ints[i++] = 567;
for (int j = 0; j < 20; j++) {
ints[i++] = j;
}
for (int j = i - 1; j >= 0; j--) {
ints[i++] = ints[j] > 0 ? -ints[j] : Integer.MIN_VALUE;
}
java.util.Random r = new java.util.Random(System.nanoTime());
for (; i < ints.length;) {
ints[i++] = r.nextInt();
}
return ints;
}
/**
* Testing helper method.
* @return an array of long numbers containing corner cases:<ul>
* <li>values near the beginning of long range,</li>
* <li>values near the end of long range,</li>
* <li>values near the beginning of int range,</li>
* <li>values near the end of int range,</li>
* <li>values near zero</li>
* <li>and some randomly distributed values.</li>
* </ul>
*/
private static long[] getLongSpecialCases() {
long longs[] = new long[100];
int i = 0;
longs[i++] = Long.MAX_VALUE;
longs[i++] = Long.MAX_VALUE - 1L;
longs[i++] = (long) Integer.MAX_VALUE + 1L;
longs[i++] = Integer.MAX_VALUE;
longs[i++] = Integer.MAX_VALUE - 1;
longs[i++] = 100L;
longs[i++] = 101L;
longs[i++] = 102L;
longs[i++] = 300L;
longs[i++] = 567L;
for (int j = 0; j < 20; j++) {
longs[i++] = j;
}
for (int j = i - 1; j >= 0; j--) {
longs[i++] = longs[j] > 0L ? -longs[j] : Long.MIN_VALUE;
}
java.util.Random r = new java.util.Random(System.nanoTime());
for (; i < longs.length;) {
longs[i++] = r.nextLong();
}
return longs;
}
private static long toUnsignedLong(int number) {
return number < 0 ? 0x100000000L + (long)number : (long)number;
}
private static int remainderUnsignedExpected(int dividend, int divisor) {
return (int)remainderUnsignedExpected(toUnsignedLong(dividend), toUnsignedLong(divisor));
}
private static int divideUnsignedExpected(int dividend, int divisor) {
return (int)divideUnsignedExpected(toUnsignedLong(dividend), toUnsignedLong(divisor));
}
private static BigInteger toUnsignedBigInteger(long number) {
return number < 0L ? BigInteger.ONE.shiftLeft(64).add(BigInteger.valueOf(number)) : BigInteger.valueOf(number);
}
private static long remainderUnsignedExpected(long dividend, long divisor) {
return toUnsignedBigInteger(dividend).remainder(toUnsignedBigInteger(divisor)).longValue();
}
private static long divideUnsignedExpected(long dividend, long divisor) {
return toUnsignedBigInteger(dividend).divide(toUnsignedBigInteger(divisor)).longValue();
}
@Test(timeout=5000L)
public void testRemainderUnsignedInt() {
Assert.assertEquals(36, ArithmeticUtils.remainderUnsigned(-2147479015, 63));
Assert.assertEquals(6, ArithmeticUtils.remainderUnsigned(-2147479015, 25));
}
@Test(timeout=5000L)
public void testRemainderUnsignedIntSpecialCases() {
int ints[] = getIntSpecialCases();
for (int dividend : ints) {
for (int divisor : ints) {
if (divisor == 0) {
try {
ArithmeticUtils.remainderUnsigned(dividend, divisor);
Assert.fail("Should have failed with ArithmeticException: division by zero");
} catch (ArithmeticException e) {
// Success.
}
} else {
Assert.assertEquals(remainderUnsignedExpected(dividend, divisor), ArithmeticUtils.remainderUnsigned(dividend, divisor));
}
}
}
}
@Test(timeout=5000L)
public void testRemainderUnsignedLong() {
Assert.assertEquals(48L, ArithmeticUtils.remainderUnsigned(-2147479015L, 63L));
}
@Test//(timeout=5000L)
public void testRemainderUnsignedLongSpecialCases() {
long longs[] = getLongSpecialCases();
for (long dividend : longs) {
for (long divisor : longs) {
if (divisor == 0L) {
try {
ArithmeticUtils.remainderUnsigned(dividend, divisor);
Assert.fail("Should have failed with ArithmeticException: division by zero");
} catch (ArithmeticException e) {
// Success.
}
} else {
Assert.assertEquals(remainderUnsignedExpected(dividend, divisor), ArithmeticUtils.remainderUnsigned(dividend, divisor));
}
}
}
}
@Test(timeout=5000L)
public void testDivideUnsignedInt() {
Assert.assertEquals(34087115, ArithmeticUtils.divideUnsigned(-2147479015, 63));
Assert.assertEquals(85899531, ArithmeticUtils.divideUnsigned(-2147479015, 25));
Assert.assertEquals(2147483646, ArithmeticUtils.divideUnsigned(-3, 2));
Assert.assertEquals(330382098, ArithmeticUtils.divideUnsigned(-16, 13));
Assert.assertEquals(306783377, ArithmeticUtils.divideUnsigned(-16, 14));
Assert.assertEquals(2, ArithmeticUtils.divideUnsigned(-1, 2147483647));
Assert.assertEquals(2, ArithmeticUtils.divideUnsigned(-2, 2147483647));
Assert.assertEquals(1, ArithmeticUtils.divideUnsigned(-3, 2147483647));
Assert.assertEquals(1, ArithmeticUtils.divideUnsigned(-16, 2147483647));
Assert.assertEquals(1, ArithmeticUtils.divideUnsigned(-16, 2147483646));
}
@Test(timeout=5000L)
public void testDivideUnsignedIntSpecialCases() {
int ints[] = getIntSpecialCases();
for (int dividend : ints) {
for (int divisor : ints) {
if (divisor == 0) {
try {
ArithmeticUtils.divideUnsigned(dividend, divisor);
Assert.fail("Should have failed with ArithmeticException: division by zero");
} catch (ArithmeticException e) {
// Success.
}
} else {
Assert.assertEquals(divideUnsignedExpected(dividend, divisor), ArithmeticUtils.divideUnsigned(dividend, divisor));
}
}
}
}
@Test(timeout=5000L)
public void testDivideUnsignedLong() {
Assert.assertEquals(292805461453366231L, ArithmeticUtils.divideUnsigned(-2147479015L, 63L));
}
@Test(timeout=5000L)
public void testDivideUnsignedLongSpecialCases() {
long longs[] = getLongSpecialCases();
for (long dividend : longs) {
for (long divisor : longs) {
if (divisor == 0L) {
try {
ArithmeticUtils.divideUnsigned(dividend, divisor);
Assert.fail("Should have failed with ArithmeticException: division by zero");
} catch (ArithmeticException e) {
// Success.
}
} else {
Assert.assertEquals(divideUnsignedExpected(dividend, divisor), ArithmeticUtils.divideUnsigned(dividend, divisor));
}
}
}
}
}