8341470: BigDecimal.stripTrailingZeros() optimization
Reviewed-by: rgiulietti
This commit is contained in:
parent
5d5d88ab9a
commit
27ef6c9df4
@ -3109,13 +3109,9 @@ public class BigDecimal extends Number implements Comparable<BigDecimal> {
|
||||
* @since 1.5
|
||||
*/
|
||||
public BigDecimal stripTrailingZeros() {
|
||||
if (intCompact == 0 || (intVal != null && intVal.signum() == 0)) {
|
||||
return BigDecimal.ZERO;
|
||||
} else if (intCompact != INFLATED) {
|
||||
return createAndStripZerosToMatchScale(intCompact, scale, Long.MIN_VALUE);
|
||||
} else {
|
||||
return createAndStripZerosToMatchScale(intVal, scale, Long.MIN_VALUE);
|
||||
}
|
||||
return intCompact == 0 || (intVal != null && intVal.signum() == 0)
|
||||
? BigDecimal.ZERO
|
||||
: stripZerosToMatchScale(intVal, intCompact, scale, Long.MIN_VALUE);
|
||||
}
|
||||
|
||||
// Comparison Operations
|
||||
@ -5219,29 +5215,116 @@ public class BigDecimal extends Number implements Comparable<BigDecimal> {
|
||||
return commonNeedIncrement(roundingMode, qsign, cmpFracHalf, mq.isOdd());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code FIVE_TO_2_TO[n] == 5^(2^n)}
|
||||
*/
|
||||
private static final BigInteger[] FIVE_TO_2_TO = new BigInteger[16 + 1];
|
||||
|
||||
static {
|
||||
BigInteger pow = FIVE_TO_2_TO[0] = BigInteger.valueOf(5L);
|
||||
for (int i = 1; i < FIVE_TO_2_TO.length; i++)
|
||||
FIVE_TO_2_TO[i] = pow = pow.multiply(pow);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param n a non-negative integer
|
||||
* @return {@code 5^(2^n)}
|
||||
*/
|
||||
private static BigInteger fiveToTwoToThe(int n) {
|
||||
int i = Math.min(n, FIVE_TO_2_TO.length - 1);
|
||||
BigInteger pow = FIVE_TO_2_TO[i];
|
||||
for (; i < n; i++)
|
||||
pow = pow.multiply(pow);
|
||||
|
||||
return pow;
|
||||
}
|
||||
|
||||
private static final double LOG_5_OF_2 = 0.43067655807339306; // double closest to log5(2)
|
||||
|
||||
/**
|
||||
* Remove insignificant trailing zeros from this
|
||||
* {@code BigInteger} value until the preferred scale is reached or no
|
||||
* more zeros can be removed. If the preferred scale is less than
|
||||
* Integer.MIN_VALUE, all the trailing zeros will be removed.
|
||||
* Assumes {@code intVal != 0}.
|
||||
*
|
||||
* @return new {@code BigDecimal} with a scale possibly reduced
|
||||
* to be closed to the preferred scale.
|
||||
* @throws ArithmeticException if scale overflows.
|
||||
*/
|
||||
private static BigDecimal createAndStripZerosToMatchScale(BigInteger intVal, int scale, long preferredScale) {
|
||||
// avoid overflow of scale - preferredScale
|
||||
preferredScale = Math.clamp(preferredScale, Integer.MIN_VALUE - 1L, Integer.MAX_VALUE);
|
||||
int powsOf2 = intVal.getLowestSetBit();
|
||||
// scale - preferredScale >= remainingZeros >= max{n : (intVal % 10^n) == 0 && n <= scale - preferredScale}
|
||||
// a multiple of 10^n must be a multiple of 2^n
|
||||
long remainingZeros = Math.min(scale - preferredScale, powsOf2);
|
||||
if (remainingZeros <= 0L)
|
||||
return valueOf(intVal, scale, 0);
|
||||
|
||||
final int sign = intVal.signum;
|
||||
if (sign < 0)
|
||||
intVal = intVal.negate(); // speed up computation of shiftRight() and bitLength()
|
||||
|
||||
intVal = intVal.shiftRight(powsOf2); // remove powers of 2
|
||||
// Let k = max{n : (intVal % 5^n) == 0}, m = max{n : 5^n <= intVal}, so m >= k.
|
||||
// Let b = intVal.bitLength(). It can be shown that
|
||||
// | b * LOG_5_OF_2 - b log5(2) | < 2^(-21) (fp viz. real arithmetic),
|
||||
// which entails m <= maxPowsOf5 <= m + 1, where maxPowsOf5 is as below.
|
||||
// Hence, maxPowsOf5 >= k.
|
||||
long maxPowsOf5 = Math.round(intVal.bitLength() * LOG_5_OF_2);
|
||||
remainingZeros = Math.min(remainingZeros, maxPowsOf5);
|
||||
|
||||
BigInteger[] qr; // quotient-remainder pair
|
||||
while (intVal.compareMagnitude(BigInteger.TEN) >= 0
|
||||
&& scale > preferredScale) {
|
||||
if (intVal.testBit(0))
|
||||
break; // odd number cannot end in 0
|
||||
qr = intVal.divideAndRemainder(BigInteger.TEN);
|
||||
if (qr[1].signum() != 0)
|
||||
break; // non-0 remainder
|
||||
intVal = qr[0];
|
||||
scale = checkScale(intVal,(long) scale - 1); // could Overflow
|
||||
// Remove 5^(2^i) from the factors of intVal, until 5^remainingZeros < 5^(2^i).
|
||||
// Let z = max{n >= 0 : ((intVal * 2^powsOf2) % 10^n) == 0 && n <= scale - preferredScale},
|
||||
// then the condition min(scale - preferredScale, powsOf2) >= remainingZeros >= z
|
||||
// and the values ((intVal * 2^powsOf2) / 10^z) and (scale - z)
|
||||
// are preserved invariants after each iteration.
|
||||
// Note that if intVal % 5^(2^i) != 0, the loop condition will become false.
|
||||
for (int i = 0; remainingZeros >= 1L << i; i++) {
|
||||
final int exp = 1 << i;
|
||||
qr = intVal.divideAndRemainder(fiveToTwoToThe(i));
|
||||
if (qr[1].signum != 0) { // non-0 remainder
|
||||
remainingZeros = exp - 1;
|
||||
} else {
|
||||
intVal = qr[0];
|
||||
scale = checkScale(intVal, (long) scale - exp); // could Overflow
|
||||
remainingZeros -= exp;
|
||||
powsOf2 -= exp;
|
||||
}
|
||||
}
|
||||
return valueOf(intVal, scale, 0);
|
||||
|
||||
// bitLength(remainingZeros) == min{n >= 0 : 5^(2^n) > 5^remainingZeros}
|
||||
// so, while the loop condition is true,
|
||||
// the invariant i == max{n : 5^(2^n) <= 5^remainingZeros},
|
||||
// which is equivalent to i == bitLength(remainingZeros) - 1,
|
||||
// is preserved at the beginning of each iteration.
|
||||
// Note that the loop stops exactly when remainingZeros == 0.
|
||||
// Using the same definition of z for the first loop, the invariants
|
||||
// min(scale - preferredScale, powsOf2) >= remainingZeros >= z,
|
||||
// ((intVal * 2^powsOf2) / 10^z) and (scale - z)
|
||||
// are preserved in this loop as well, so, when the loop ends,
|
||||
// remainingZeros == 0 implies z == 0, hence (intVal * 2^powsOf2) and scale
|
||||
// have the correct values to return.
|
||||
for (int i = BigInteger.bitLengthForLong(remainingZeros) - 1; i >= 0; i--) {
|
||||
final int exp = 1 << i;
|
||||
qr = intVal.divideAndRemainder(fiveToTwoToThe(i));
|
||||
if (qr[1].signum != 0) { // non-0 remainder
|
||||
remainingZeros = exp - 1;
|
||||
} else {
|
||||
intVal = qr[0];
|
||||
scale = checkScale(intVal, (long) scale - exp); // could Overflow
|
||||
remainingZeros -= exp;
|
||||
powsOf2 -= exp;
|
||||
|
||||
if (remainingZeros < exp >> 1) // else i == bitLength(remainingZeros) already
|
||||
i = BigInteger.bitLengthForLong(remainingZeros);
|
||||
}
|
||||
}
|
||||
|
||||
intVal = intVal.shiftLeft(powsOf2); // restore remaining powers of 2
|
||||
return valueOf(sign >= 0 ? intVal : intVal.negate(), scale, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -5249,31 +5332,27 @@ public class BigDecimal extends Number implements Comparable<BigDecimal> {
|
||||
* {@code long} value until the preferred scale is reached or no
|
||||
* more zeros can be removed. If the preferred scale is less than
|
||||
* Integer.MIN_VALUE, all the trailing zeros will be removed.
|
||||
* Assumes {@code compactVal != 0 && compactVal != INFLATED}.
|
||||
*
|
||||
* @return new {@code BigDecimal} with a scale possibly reduced
|
||||
* to be closed to the preferred scale.
|
||||
* @throws ArithmeticException if scale overflows.
|
||||
*/
|
||||
private static BigDecimal createAndStripZerosToMatchScale(long compactVal, int scale, long preferredScale) {
|
||||
while (Math.abs(compactVal) >= 10L && scale > preferredScale) {
|
||||
if ((compactVal & 1L) != 0L)
|
||||
break; // odd number cannot end in 0
|
||||
long r = compactVal % 10L;
|
||||
if (r != 0L)
|
||||
break; // non-0 remainder
|
||||
compactVal /= 10;
|
||||
scale = checkScale(compactVal, (long) scale - 1); // could Overflow
|
||||
while (compactVal % 10L == 0L && scale > preferredScale) {
|
||||
compactVal /= 10L;
|
||||
scale = checkScale(compactVal, scale - 1L); // could Overflow
|
||||
}
|
||||
return valueOf(compactVal, scale);
|
||||
}
|
||||
|
||||
private static BigDecimal stripZerosToMatchScale(BigInteger intVal, long intCompact, int scale, int preferredScale) {
|
||||
if(intCompact!=INFLATED) {
|
||||
return createAndStripZerosToMatchScale(intCompact, scale, preferredScale);
|
||||
} else {
|
||||
return createAndStripZerosToMatchScale(intVal==null ? INFLATED_BIGINT : intVal,
|
||||
scale, preferredScale);
|
||||
}
|
||||
/**
|
||||
* Assumes {@code intVal != 0 && intCompact != 0}.
|
||||
*/
|
||||
private static BigDecimal stripZerosToMatchScale(BigInteger intVal, long intCompact, int scale, long preferredScale) {
|
||||
return intCompact != INFLATED
|
||||
? createAndStripZerosToMatchScale(intCompact, scale, preferredScale)
|
||||
: createAndStripZerosToMatchScale(intVal == null ? INFLATED_BIGINT : intVal, scale, preferredScale);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -2779,6 +2779,13 @@ public class BigInteger extends Number implements Comparable<BigInteger> {
|
||||
return 32 - Integer.numberOfLeadingZeros(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Package private method to return bit length for a long.
|
||||
*/
|
||||
static int bitLengthForLong(long n) {
|
||||
return 64 - Long.numberOfLeadingZeros(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Left shift int array a up to len by n bits. Returns the array that
|
||||
* results from the shift since space may have to be reallocated.
|
||||
|
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (c) 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
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package org.openjdk.bench.java.math;
|
||||
|
||||
import org.openjdk.jmh.annotations.Benchmark;
|
||||
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||
import org.openjdk.jmh.annotations.Fork;
|
||||
import org.openjdk.jmh.annotations.Measurement;
|
||||
import org.openjdk.jmh.annotations.Mode;
|
||||
import org.openjdk.jmh.annotations.OperationsPerInvocation;
|
||||
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
||||
import org.openjdk.jmh.annotations.Scope;
|
||||
import org.openjdk.jmh.annotations.Setup;
|
||||
import org.openjdk.jmh.annotations.State;
|
||||
import org.openjdk.jmh.annotations.Param;
|
||||
import org.openjdk.jmh.annotations.Warmup;
|
||||
import org.openjdk.jmh.infra.Blackhole;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
||||
@State(Scope.Thread)
|
||||
@Warmup(iterations = 5, time = 1)
|
||||
@Measurement(iterations = 5, time = 1)
|
||||
@Fork(value = 3)
|
||||
public class BigDecimalStripTrailingZeros {
|
||||
|
||||
private BigDecimal xsPow, sPow, mPow, lPow, xlPow;
|
||||
|
||||
@Setup
|
||||
public void setup() {
|
||||
xsPow = new BigDecimal(BigInteger.TEN.pow(1 << 4));
|
||||
sPow = new BigDecimal(BigInteger.TEN.pow(1 << 5));
|
||||
mPow = new BigDecimal(BigInteger.TEN.pow(1 << 10));
|
||||
lPow = new BigDecimal(BigInteger.TEN.pow(1 << 15));
|
||||
xlPow = new BigDecimal(BigInteger.TEN.pow(1 << 20));
|
||||
}
|
||||
|
||||
/** Test BigDecimal.stripTrailingZeros() with 10^16 */
|
||||
@Benchmark
|
||||
@OperationsPerInvocation(1)
|
||||
public void testXS(Blackhole bh) {
|
||||
bh.consume(xsPow.stripTrailingZeros());
|
||||
}
|
||||
|
||||
/** Test BigDecimal.stripTrailingZeros() with 10^32 */
|
||||
@Benchmark
|
||||
@OperationsPerInvocation(1)
|
||||
public void testS(Blackhole bh) {
|
||||
bh.consume(sPow.stripTrailingZeros());
|
||||
}
|
||||
|
||||
/** Test BigDecimal.stripTrailingZeros() with 10^1024 */
|
||||
@Benchmark
|
||||
@OperationsPerInvocation(1)
|
||||
public void testM(Blackhole bh) {
|
||||
bh.consume(mPow.stripTrailingZeros());
|
||||
}
|
||||
|
||||
/** Test BigDecimal.stripTrailingZeros() with 10^32_768 */
|
||||
@Benchmark
|
||||
@OperationsPerInvocation(1)
|
||||
public void testL(Blackhole bh) {
|
||||
bh.consume(lPow.stripTrailingZeros());
|
||||
}
|
||||
|
||||
/** Test BigDecimal.stripTrailingZeros() with 10^1_048_576 */
|
||||
@Benchmark
|
||||
@OperationsPerInvocation(1)
|
||||
public void testXL(Blackhole bh) {
|
||||
bh.consume(xlPow.stripTrailingZeros());
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user