提交 0ac45800 编写于 作者: B bpb

8066842: java.math.BigDecimal.divide(BigDecimal, RoundingMode) produces incorrect result

Summary: Replace divWord() with non-truncating alternatives
Reviewed-by: psandoz, darcy
上级 d80c41ff
...@@ -4801,41 +4801,61 @@ public class BigDecimal extends Number implements Comparable<BigDecimal> { ...@@ -4801,41 +4801,61 @@ public class BigDecimal extends Number implements Comparable<BigDecimal> {
if (dividendHi >= divisor) { if (dividendHi >= divisor) {
return null; return null;
} }
final int shift = Long.numberOfLeadingZeros(divisor); final int shift = Long.numberOfLeadingZeros(divisor);
divisor <<= shift; divisor <<= shift;
final long v1 = divisor >>> 32; final long v1 = divisor >>> 32;
final long v0 = divisor & LONG_MASK; final long v0 = divisor & LONG_MASK;
long q1, q0;
long r_tmp;
long tmp = dividendLo << shift; long tmp = dividendLo << shift;
long u1 = tmp >>> 32; long u1 = tmp >>> 32;
long u0 = tmp & LONG_MASK; long u0 = tmp & LONG_MASK;
tmp = (dividendHi << shift) | (dividendLo >>> 64 - shift); tmp = (dividendHi << shift) | (dividendLo >>> 64 - shift);
long u2 = tmp & LONG_MASK; long u2 = tmp & LONG_MASK;
tmp = divWord(tmp,v1); long q1, r_tmp;
q1 = tmp & LONG_MASK; if (v1 == 1) {
r_tmp = tmp >>> 32; 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];
}
while(q1 >= DIV_NUM_BASE || unsignedLongCompare(q1*v0, make64(r_tmp, u1))) { while(q1 >= DIV_NUM_BASE || unsignedLongCompare(q1*v0, make64(r_tmp, u1))) {
q1--; q1--;
r_tmp += v1; r_tmp += v1;
if (r_tmp >= DIV_NUM_BASE) if (r_tmp >= DIV_NUM_BASE)
break; break;
} }
tmp = mulsub(u2,u1,v1,v0,q1); tmp = mulsub(u2,u1,v1,v0,q1);
u1 = tmp & LONG_MASK; u1 = tmp & LONG_MASK;
tmp = divWord(tmp,v1); long q0;
q0 = tmp & LONG_MASK; if (v1 == 1) {
r_tmp = tmp >>> 32; 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];
}
while(q0 >= DIV_NUM_BASE || unsignedLongCompare(q0*v0,make64(r_tmp,u0))) { while(q0 >= DIV_NUM_BASE || unsignedLongCompare(q0*v0,make64(r_tmp,u0))) {
q0--; q0--;
r_tmp += v1; r_tmp += v1;
if (r_tmp >= DIV_NUM_BASE) if (r_tmp >= DIV_NUM_BASE)
break; break;
} }
if((int)q1 < 0) { if((int)q1 < 0) {
// result (which is positive and unsigned here) // result (which is positive and unsigned here)
// can't fit into long due to sign bit is used for value // can't fit into long due to sign bit is used for value
...@@ -4858,10 +4878,13 @@ public class BigDecimal extends Number implements Comparable<BigDecimal> { ...@@ -4858,10 +4878,13 @@ public class BigDecimal extends Number implements Comparable<BigDecimal> {
} }
} }
} }
long q = make64(q1,q0); long q = make64(q1,q0);
q*=sign; q*=sign;
if (roundingMode == ROUND_DOWN && scale == preferredScale) if (roundingMode == ROUND_DOWN && scale == preferredScale)
return valueOf(q, scale); return valueOf(q, scale);
long r = mulsub(u1, u0, v1, v0, q0) >>> shift; long r = mulsub(u1, u0, v1, v0, q0) >>> shift;
if (r != 0) { if (r != 0) {
boolean increment = needIncrement(divisor >>> shift, roundingMode, sign, q, r); boolean increment = needIncrement(divisor >>> shift, roundingMode, sign, q, r);
...@@ -4904,28 +4927,35 @@ public class BigDecimal extends Number implements Comparable<BigDecimal> { ...@@ -4904,28 +4927,35 @@ public class BigDecimal extends Number implements Comparable<BigDecimal> {
} }
} }
private static long divWord(long n, long dLong) { /**
long r; * Calculate the quotient and remainder of dividing a negative long by
long q; * another long.
if (dLong == 1) { *
q = (int)n; * @param n the numerator; must be negative
return (q & LONG_MASK); * @param d the denominator; must not be unity
} * @return a two-element {@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 // Approximate the quotient and remainder
q = (n >>> 1) / (dLong >>> 1); long q = (n >>> 1) / (d >>> 1);
r = n - q*dLong; long r = n - q * d;
// Correct the approximation // Correct the approximation
while (r < 0) { while (r < 0) {
r += dLong; r += d;
q--; q--;
} }
while (r >= dLong) { while (r >= d) {
r -= dLong; r -= d;
q++; q++;
} }
// n - q*dlong == r && 0 <= r <dLong, hence we're done.
return (r << 32) | (q & LONG_MASK); // n - q*d == r && 0 <= r < d, hence we're done.
return new long[] {r, q};
} }
private static long make64(long hi, long lo) { private static long make64(long hi, long lo) {
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
/* /*
* @test * @test
* @bug 4851776 4907265 6177836 6876282 * @bug 4851776 4907265 6177836 6876282 8066842
* @summary Some tests for the divide methods. * @summary Some tests for the divide methods.
* @author Joseph D. Darcy * @author Joseph D. Darcy
*/ */
...@@ -358,6 +358,57 @@ public class DivideTests { ...@@ -358,6 +358,57 @@ public class DivideTests {
return failures; return failures;
} }
private static int divideByOneTests() {
int failures = 0;
//problematic divisor: one with scale 17
BigDecimal one = BigDecimal.ONE.setScale(17);
RoundingMode rounding = RoundingMode.UNNECESSARY;
long[][] unscaledAndScale = new long[][] {
{ Long.MAX_VALUE, 17},
{-Long.MAX_VALUE, 17},
{ Long.MAX_VALUE, 0},
{-Long.MAX_VALUE, 0},
{ Long.MAX_VALUE, 100},
{-Long.MAX_VALUE, 100}
};
for (long[] uas : unscaledAndScale) {
long unscaled = uas[0];
int scale = (int)uas[1];
BigDecimal noRound = null;
try {
noRound = BigDecimal.valueOf(unscaled, scale).
divide(one, RoundingMode.UNNECESSARY);
} catch (ArithmeticException e) {
failures++;
System.err.println("ArithmeticException for value " + unscaled
+ " and scale " + scale + " without rounding");
}
BigDecimal roundDown = null;
try {
roundDown = BigDecimal.valueOf(unscaled, scale).
divide(one, RoundingMode.DOWN);
} catch (ArithmeticException e) {
failures++;
System.err.println("ArithmeticException for value " + unscaled
+ " and scale " + scale + " with rounding down");
}
if (noRound != null && roundDown != null
&& noRound.compareTo(roundDown) != 0) {
failures++;
System.err.println("Equality failure for value " + unscaled
+ " and scale " + scale);
}
}
return failures;
}
public static void main(String argv[]) { public static void main(String argv[]) {
int failures = 0; int failures = 0;
...@@ -366,10 +417,11 @@ public class DivideTests { ...@@ -366,10 +417,11 @@ public class DivideTests {
failures += properScaleTests(); failures += properScaleTests();
failures += trailingZeroTests(); failures += trailingZeroTests();
failures += scaledRoundedDivideTests(); failures += scaledRoundedDivideTests();
failures += divideByOneTests();
if (failures > 0) { if (failures > 0) {
throw new RuntimeException("Incurred " + failures + throw new RuntimeException("Incurred " + failures +
" failures while testing exact divide."); " failures while testing division.");
} }
} }
} }
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册