Skip to content

Commit

Permalink
8331907: BigInteger and BigDecimal should use optimized division
Browse files Browse the repository at this point in the history
Reviewed-by: rgiulietti, bpb
  • Loading branch information
djelinski committed May 14, 2024
1 parent 440782e commit beea530
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 180 deletions.
61 changes: 5 additions & 56 deletions src/java.base/share/classes/java/math/BigDecimal.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1996, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -5680,18 +5680,8 @@ private static BigDecimal divideAndRound128(final long dividendHi, final long di

tmp = (dividendHi << shift) | (dividendLo >>> 64 - shift);
long u2 = tmp & LONG_MASK;
long q1, r_tmp;
if (v1 == 1) {
q1 = tmp;
r_tmp = 0;
} else if (tmp >= 0) {
q1 = tmp / v1;
r_tmp = tmp - q1 * v1;
} else {
long[] rq = divRemNegativeLong(tmp, v1);
q1 = rq[1];
r_tmp = rq[0];
}
long q1 = Long.divideUnsigned(tmp, v1);
long r_tmp = Long.remainderUnsigned(tmp, v1);

while(q1 >= DIV_NUM_BASE || unsignedLongCompare(q1*v0, make64(r_tmp, u1))) {
q1--;
Expand All @@ -5702,18 +5692,8 @@ private static BigDecimal divideAndRound128(final long dividendHi, final long di

tmp = mulsub(u2,u1,v1,v0,q1);
u1 = tmp & LONG_MASK;
long q0;
if (v1 == 1) {
q0 = tmp;
r_tmp = 0;
} else if (tmp >= 0) {
q0 = tmp / v1;
r_tmp = tmp - q0 * v1;
} else {
long[] rq = divRemNegativeLong(tmp, v1);
q0 = rq[1];
r_tmp = rq[0];
}
long q0 = Long.divideUnsigned(tmp, v1);
r_tmp = Long.remainderUnsigned(tmp, v1);

while(q0 >= DIV_NUM_BASE || unsignedLongCompare(q0*v0,make64(r_tmp,u0))) {
q0--;
Expand Down Expand Up @@ -5793,37 +5773,6 @@ static BigDecimal scaledTenPow(int n, int sign, int scale) {
}
}

/**
* Calculate the quotient and remainder of dividing a negative long by
* another long.
*
* @param n the numerator; must be negative
* @param d the denominator; must not be unity
* @return a two-element {@code long} array with the remainder and quotient in
* the initial and final elements, respectively
*/
private static long[] divRemNegativeLong(long n, long d) {
assert n < 0 : "Non-negative numerator " + n;
assert d != 1 : "Unity denominator";

// Approximate the quotient and remainder
long q = (n >>> 1) / (d >>> 1);
long r = n - q * d;

// Correct the approximation
while (r < 0) {
r += d;
q--;
}
while (r >= d) {
r -= d;
q++;
}

// n - q*d == r && 0 <= r < d, hence we're done.
return new long[] {r, q};
}

private static long make64(long hi, long lo) {
return hi<<32 | lo;
}
Expand Down
108 changes: 16 additions & 92 deletions src/java.base/share/classes/java/math/MutableBigInteger.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -1092,9 +1092,9 @@ int divideOneWord(int divisor, MutableBigInteger quotient) {

// Special case of one word dividend
if (intLen == 1) {
long dividendValue = value[offset] & LONG_MASK;
int q = (int) (dividendValue / divisorLong);
int r = (int) (dividendValue - q * divisorLong);
int dividendValue = value[offset];
int q = Integer.divideUnsigned(dividendValue, divisor);
int r = Integer.remainderUnsigned(dividendValue, divisor);
quotient.value[0] = q;
quotient.intLen = (q == 0) ? 0 : 1;
quotient.offset = 0;
Expand All @@ -1106,41 +1106,17 @@ int divideOneWord(int divisor, MutableBigInteger quotient) {
quotient.offset = 0;
quotient.intLen = intLen;

// Normalize the divisor
int shift = Integer.numberOfLeadingZeros(divisor);

int rem = value[offset];
long remLong = rem & LONG_MASK;
if (remLong < divisorLong) {
quotient.value[0] = 0;
} else {
quotient.value[0] = (int)(remLong / divisorLong);
rem = (int) (remLong - (quotient.value[0] * divisorLong));
remLong = rem & LONG_MASK;
}
int xlen = intLen;
while (--xlen > 0) {
long dividendEstimate = (remLong << 32) |
long rem = 0;
for (int xlen = intLen; xlen > 0; xlen--) {
long dividendEstimate = (rem << 32) |
(value[offset + intLen - xlen] & LONG_MASK);
int q;
if (dividendEstimate >= 0) {
q = (int) (dividendEstimate / divisorLong);
rem = (int) (dividendEstimate - q * divisorLong);
} else {
long tmp = divWord(dividendEstimate, divisor);
q = (int) (tmp & LONG_MASK);
rem = (int) (tmp >>> 32);
}
int q = (int) Long.divideUnsigned(dividendEstimate, divisorLong);
rem = Long.remainderUnsigned(dividendEstimate, divisorLong);
quotient.value[intLen - xlen] = q;
remLong = rem & LONG_MASK;
}

quotient.normalize();
// Unnormalize
if (shift > 0)
return rem % divisor;
else
return rem;
return (int)rem;
}

/**
Expand Down Expand Up @@ -1557,14 +1533,8 @@ private MutableBigInteger divideMagnitude(MutableBigInteger div,
skipCorrection = qrem + 0x80000000 < nh2;
} else {
long nChunk = (((long)nh) << 32) | (nm & LONG_MASK);
if (nChunk >= 0) {
qhat = (int) (nChunk / dhLong);
qrem = (int) (nChunk - (qhat * dhLong));
} else {
long tmp = divWord(nChunk, dh);
qhat = (int) (tmp & LONG_MASK);
qrem = (int) (tmp >>> 32);
}
qhat = (int) Long.divideUnsigned(nChunk, dhLong);
qrem = (int) Long.remainderUnsigned(nChunk, dhLong);
}

if (qhat == 0)
Expand Down Expand Up @@ -1616,14 +1586,8 @@ private MutableBigInteger divideMagnitude(MutableBigInteger div,
skipCorrection = qrem + 0x80000000 < nh2;
} else {
long nChunk = (((long) nh) << 32) | (nm & LONG_MASK);
if (nChunk >= 0) {
qhat = (int) (nChunk / dhLong);
qrem = (int) (nChunk - (qhat * dhLong));
} else {
long tmp = divWord(nChunk, dh);
qhat = (int) (tmp & LONG_MASK);
qrem = (int) (tmp >>> 32);
}
qhat = (int) Long.divideUnsigned(nChunk, dhLong);
qrem = (int) Long.remainderUnsigned(nChunk, dhLong);
}
if (qhat != 0) {
if (!skipCorrection) { // Correct qhat
Expand Down Expand Up @@ -1732,14 +1696,8 @@ private MutableBigInteger divideLongMagnitude(long ldivisor, MutableBigInteger q
skipCorrection = qrem + 0x80000000 < nh2;
} else {
long nChunk = (((long) nh) << 32) | (nm & LONG_MASK);
if (nChunk >= 0) {
qhat = (int) (nChunk / dhLong);
qrem = (int) (nChunk - (qhat * dhLong));
} else {
long tmp = divWord(nChunk, dh);
qhat =(int)(tmp & LONG_MASK);
qrem = (int)(tmp>>>32);
}
qhat = (int) Long.divideUnsigned(nChunk, dhLong);
qrem = (int) Long.remainderUnsigned(nChunk, dhLong);
}

if (qhat == 0)
Expand Down Expand Up @@ -1834,40 +1792,6 @@ private boolean unsignedLongCompare(long one, long two) {
return (one+Long.MIN_VALUE) > (two+Long.MIN_VALUE);
}

/**
* This method divides a long quantity by an int to estimate
* qhat for two multi precision numbers. It is used when
* the signed value of n is less than zero.
* Returns long value where high 32 bits contain remainder value and
* low 32 bits contain quotient value.
*/
static long divWord(long n, int d) {
long dLong = d & LONG_MASK;
long r;
long q;
if (dLong == 1) {
q = (int)n;
r = 0;
return (r << 32) | (q & LONG_MASK);
}

// Approximate the quotient and remainder
q = (n >>> 1) / (dLong >>> 1);
r = n - q*dLong;

// Correct the approximation
while (r < 0) {
r += dLong;
q--;
}
while (r >= dLong) {
r -= dLong;
q++;
}
// n - q*dlong == r && 0 <= r <dLong, hence we're done.
return (r << 32) | (q & LONG_MASK);
}

/**
* Calculate the integer square root {@code floor(sqrt(this))} where
* {@code sqrt(.)} denotes the mathematical square root. The contents of
Expand Down
59 changes: 28 additions & 31 deletions test/micro/org/openjdk/bench/java/math/BigDecimals.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -133,15 +133,6 @@ public void testConstructorWithDouble(Blackhole bh) {
}
}

/** Invokes the toString method of BigDecimal with various different values. */
@Benchmark
@OperationsPerInvocation(TEST_SIZE)
public void testToString(Blackhole bh) {
for (BigDecimal s : bigDecimals) {
bh.consume(s.toString());
}
}

/**
* Invokes the setScale method of BigDecimal with various different values.
*/
Expand Down Expand Up @@ -194,40 +185,46 @@ public void testMultiply(Blackhole bh) {
bh.consume(tmp);
}

/** Invokes the compareTo method of BigDecimal with various different values. */
/** Test divide with huge/small numbers */
@Benchmark
@OperationsPerInvocation(TEST_SIZE - 1)
public void testCompareTo(Blackhole bh) {
BigDecimal c = bigDecimals[0];
for (BigDecimal s : bigDecimals) {
bh.consume(c.compareTo(s));
@OperationsPerInvocation(TEST_SIZE * TEST_SIZE)
public void testHugeSmallDivide(Blackhole bh) {
for (BigDecimal s : hugeArray) {
for (BigDecimal t : smallArray) {
bh.consume(s.divide(t, RoundingMode.DOWN));
}
}
}

/** Test BigDecimal.toString() with huge numbers larger than MAX_LONG */
/** Test divide with large/small numbers */
@Benchmark
@OperationsPerInvocation(TEST_SIZE)
public void testHugeToString(Blackhole bh) {
for (BigDecimal s : hugeArray) {
bh.consume(s.toString());
@OperationsPerInvocation(TEST_SIZE * TEST_SIZE)
public void testLargeSmallDivide(Blackhole bh) {
for (BigDecimal s : largeArray) {
for (BigDecimal t : smallArray) {
bh.consume(s.divide(t, RoundingMode.DOWN));
}
}
}

/** Test BigDecimal.toString() with large numbers less than MAX_LONG but larger than MAX_INT */
/** Test divide with huge/large numbers */
@Benchmark
@OperationsPerInvocation(TEST_SIZE)
public void testLargeToString(Blackhole bh) {
for (BigDecimal s : largeArray) {
bh.consume(s.toString());
@OperationsPerInvocation(TEST_SIZE * TEST_SIZE)
public void testHugeLargeDivide(Blackhole bh) {
for (BigDecimal s : hugeArray) {
for (BigDecimal t : largeArray) {
bh.consume(s.divide(t, RoundingMode.DOWN));
}
}
}

/** Test BigDecimal.toString() with small numbers less than MAX_INT */
/** Invokes the compareTo method of BigDecimal with various different values. */
@Benchmark
@OperationsPerInvocation(TEST_SIZE)
public void testSmallToString(Blackhole bh) {
for (BigDecimal s : smallArray) {
bh.consume(s.toString());
@OperationsPerInvocation(TEST_SIZE - 1)
public void testCompareTo(Blackhole bh) {
BigDecimal c = bigDecimals[0];
for (BigDecimal s : bigDecimals) {
bh.consume(c.compareTo(s));
}
}
}
35 changes: 34 additions & 1 deletion test/micro/org/openjdk/bench/java/math/BigIntegers.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -132,6 +132,39 @@ public void testMultiply(Blackhole bh) {
bh.consume(tmp);
}

/** Test divide with huge/small numbers */
@Benchmark
@OperationsPerInvocation(TESTSIZE * TESTSIZE)
public void testHugeSmallDivide(Blackhole bh) {
for (BigInteger s : hugeArray) {
for (BigInteger t : smallArray) {
bh.consume(s.divide(t));
}
}
}

/** Test divide with large/small numbers */
@Benchmark
@OperationsPerInvocation(TESTSIZE * TESTSIZE)
public void testLargeSmallDivide(Blackhole bh) {
for (BigInteger s : largeArray) {
for (BigInteger t : smallArray) {
bh.consume(s.divide(t));
}
}
}

/** Test divide with huge/large numbers */
@Benchmark
@OperationsPerInvocation(TESTSIZE * TESTSIZE)
public void testHugeLargeDivide(Blackhole bh) {
for (BigInteger s : hugeArray) {
for (BigInteger t : largeArray) {
bh.consume(s.divide(t));
}
}
}

/** Invokes the multiply method of BigInteger with various different values. */
@Benchmark
@OperationsPerInvocation(TESTSIZE)
Expand Down

1 comment on commit beea530

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.