diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index 1e8b7319bb3..028d96520cb 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -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(); diff --git a/src/hotspot/share/runtime/globals_ext.hpp b/src/hotspot/share/runtime/globals_ext.hpp index e36fb6a27f3..1a54881c4f5 100644 --- a/src/hotspot/share/runtime/globals_ext.hpp +++ b/src/hotspot/share/runtime/globals_ext.hpp @@ -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 diff --git a/src/java.base/share/classes/java/math/BigDecimal.java b/src/java.base/share/classes/java/math/BigDecimal.java index ff9a6daf47b..1e0967fdaec 100644 --- a/src/java.base/share/classes/java/math/BigDecimal.java +++ b/src/java.base/share/classes/java/math/BigDecimal.java @@ -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 /** @@ -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 @@ -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) { diff --git a/test/jdk/java/math/BigDecimal/StrippingTailZerosTest.java b/test/jdk/java/math/BigDecimal/StrippingTailZerosTest.java new file mode 100644 index 00000000000..313f045d958 --- /dev/null +++ b/test/jdk/java/math/BigDecimal/StrippingTailZerosTest.java @@ -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 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(); + } + } +} +