Skip to content

Commit

Permalink
[Misc] optimize BigDecimal.divide
Browse files Browse the repository at this point in the history
Summary: Use binary search in zero stripping of BigDecimal.divide

Testing: jtreg and ci

Reviewers: zhuoren.wz, JoshuaZhuwj

Issue: #848
  • Loading branch information
weixlu committed Aug 5, 2024
1 parent 1811fb0 commit 62c2767
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/hotspot/share/runtime/arguments.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4115,6 +4115,10 @@ jint Arguments::parse(const JavaVMInitArgs* initial_cmd_args) {
DumpAppCDSWithKlassId = true;
}

if (UseBigDecimalOpt) {
PropertyList_add(&_system_properties, new SystemProperty("java.math.BigDecimal.optimization", "true", true));
}

// Set object alignment values.
set_object_alignment();

Expand Down
3 changes: 3 additions & 0 deletions src/hotspot/share/runtime/globals_ext.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@
product(bool, ReduceNMethodSize, false, \
"Move immutable data of nmethod out of code cache") \
\
product(bool, UseBigDecimalOpt, true, \
"use binary search in zero stripping of BigDecimal") \
\
//add new AJDK specific flags here


Expand Down
47 changes: 47 additions & 0 deletions src/java.base/share/classes/java/math/BigDecimal.java
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,18 @@ protected StringBuilderHelper initialValue() {
*/
private static final BigDecimal ONE_HALF = valueOf(5L, 1);

private static boolean opt = false;
static {
try {
opt = Boolean.parseBoolean(System.getProperty("java.math.BigDecimal.optimization"));
} catch (SecurityException e) {
// When user define some secure environment(like Bug6937951Test.java),
// system property here may be inaccessible. So we'd better be conservative
// and turn off optimization.
opt = false;
}
}

// Constructors

/**
Expand Down Expand Up @@ -4860,6 +4872,38 @@ private static boolean needIncrement(MutableBigInteger mdivisor, int roundingMod
return commonNeedIncrement(roundingMode, qsign, cmpFracHalf, mq.isOdd());
}

/**
* Remove insignificant trailing zeros from this
* {@code BigInteger} value until the preferred scale is reached or no
* more zeros can be removed. This is a faster version of createAndStripZerosToMatchScale
* using binary search instead of linear search.
*
* @return new {@code BigDecimal} with a scale possibly reduced
* to be closed to the preferred scale.
*/
private static BigDecimal createAndStripZerosToMatchScaleFast(BigInteger intVal, int scale, long preferredScale) {
BigInteger qr[]; // quotient-remainder pair
int scaleStep;
while (intVal.compareMagnitude(BigInteger.TEN) >= 0
&& scale > preferredScale) {
scaleStep = checkScale(intVal, Math.max(((long) scale - preferredScale) / 2, 1l));
if (intVal.getLowestSetBit() >= scaleStep) { // intVal can be divided by pow(10, scaleStep) only if intVal has more trailing zeros than scaleStep
qr = intVal.divideAndRemainder(bigTenToThe(scaleStep));
if (qr[1].signum() == 0) {
intVal = qr[0];
scale = checkScale(intVal, (long) scale - scaleStep); // could Overflow
continue;
}
}
if (scaleStep == 1) {
break;
} else {
preferredScale = scale - scaleStep;
}
}
return valueOf(intVal, scale, 0);
}

/**
* Remove insignificant trailing zeros from this
* {@code BigInteger} value until the preferred scale is reached or no
Expand All @@ -4870,6 +4914,9 @@ private static boolean needIncrement(MutableBigInteger mdivisor, int roundingMod
* to be closed to the preferred scale.
*/
private static BigDecimal createAndStripZerosToMatchScale(BigInteger intVal, int scale, long preferredScale) {
if (opt && preferredScale >= Integer.MIN_VALUE) {
return createAndStripZerosToMatchScaleFast(intVal, scale, preferredScale);
}
BigInteger qr[]; // quotient-remainder pair
while (intVal.compareMagnitude(BigInteger.TEN) >= 0
&& scale > preferredScale) {
Expand Down
82 changes: 82 additions & 0 deletions test/jdk/java/math/BigDecimal/StrippingTailZerosTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright (c) 2024, Alibaba Group Holding Limited. 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.
*/

/*
* @test
* @summary test correctness of BigDecimal.divide via optimization
* @library /test/lib
* @run main/othervm StrippingTailZerosTest
*/

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.MathContext;
import java.security.SecureRandom;
import jdk.test.lib.Asserts;

public class StrippingTailZerosTest {
private static final int TEST_ROUND = 1000000;

private static void testDivision(double dividend, Field optField) throws Exception{
/*
We intend to apply a low precision for dividend and a high precision for quotient.
Together with divisor 2, the quotient should have a long tail of zeros.
Like 67.89200 / 2 = 33.9460000000000
And then out optimization about stripping zeros should take effect.
*/
BigDecimal num1 = new BigDecimal(dividend, MathContext.DECIMAL32);
BigDecimal num2 = new BigDecimal(2);

optField.setBoolean(null, true);
BigDecimal resultOptTrue = num1.divide(num2, MathContext.DECIMAL128);

optField.setBoolean(null, false);
BigDecimal resultOptFalse = num1.divide(num2, MathContext.DECIMAL128);

Asserts.assertEQ(resultOptTrue, resultOptFalse);
}

public static void main(String[] args) {
try {
Class<BigDecimal> clazz = BigDecimal.class;
Field optField = clazz.getDeclaredField("opt");
optField.setAccessible(true);

SecureRandom random = new SecureRandom();
for (int i = 0; i < TEST_ROUND; i++) {
testDivision(random.nextDouble(), optField);
}

double[] specialCases = {0.00000000000000D, 123456789.123456789, -123456789.123456789,
1e10, 1e-010, -1e10, -1e-010,
Double.MAX_VALUE, Double.MIN_VALUE,
-Double.MAX_VALUE, -Double.MIN_VALUE};
for (double specialCase: specialCases) {
testDivision(specialCase, optField);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

0 comments on commit 62c2767

Please sign in to comment.