diff --git a/src/java.base/share/classes/java/math/BigDecimal.java b/src/java.base/share/classes/java/math/BigDecimal.java index c24998344c151..84238b2026910 100644 --- a/src/java.base/share/classes/java/math/BigDecimal.java +++ b/src/java.base/share/classes/java/math/BigDecimal.java @@ -1,5 +1,6 @@ /* * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 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 @@ -35,6 +36,8 @@ import java.io.ObjectInputStream; import java.io.ObjectStreamException; import java.io.StreamCorruptedException; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Objects; @@ -3429,62 +3432,113 @@ public String toEngineeringString() { * @see #toEngineeringString() */ public String toPlainString() { - if(scale==0) { - if(intCompact!=INFLATED) { - return Long.toString(intCompact); - } else { - return intVal.toString(); + int scale = this.scale; + if (scale == 0) + return unscaledString(); + + int signum = signum(); + if (scale < 0) { // No decimal point + if (signum == 0) + return "0"; + int trailingZeros = checkScaleNonZero((-(long) scale)); + String str = unscaledString(); + int len = str.length() + trailingZeros; + if (len < 0) { + throw new OutOfMemoryError("too large to fit in a String"); } + return new StringBuilder(len) + .append(str) + .repeat('0', trailingZeros) + .toString(); } - if(this.scale<0) { // No decimal point - if(signum()==0) { - return "0"; + + long intCompact = this.intCompact; + // currency fast path + if (intCompact != INFLATED) { + long intCompactAbs = Math.abs(intCompact); + if (scale == 2 & (int) intCompact == intCompactAbs) { // intCompact >= 0 && intCompact <= Integer.MAX_VALUE + return scale2((int) intCompact); } - int trailingZeros = checkScaleNonZero((-(long)scale)); - String str = intCompact != INFLATED - ? Long.toString(intCompact) - : intVal.toString(); - int len = str.length() + trailingZeros; + return getValueString(signum, intCompactAbs, DecimalDigits.stringSize(intCompactAbs), scale); + } + + return getValueString(signum, intVal.abs().toString(), scale); + } + + private static String getValueString(int signum, long intCompactAbs, int intCompactAbsSize, int scale) { + /* Insert decimal point */ + int insertionPoint = intCompactAbsSize - scale; + byte[] buf; + if (insertionPoint == 0) { /* Point goes just before intVal */ + buf = new byte[intCompactAbsSize + (signum < 0 ? 3 : 2)]; + int off = 0; + if(signum < 0) { + buf[0] = '-'; + off = 1; + } + buf[off ] = '0'; + buf[off + 1] = '.'; + DecimalDigits.uncheckedGetCharsLatin1(intCompactAbs, buf.length, buf); + } else if (insertionPoint > 0) { /* Point goes inside intVal */ + buf = new byte[intCompactAbsSize + (signum < 0 ? 2 : 1)]; + if (signum < 0) { + buf[0] = '-'; + insertionPoint++; + } + long power = LONG_TEN_POWERS_TABLE[scale]; + long highInt = intCompactAbs / power; + long small = Math.abs(intCompactAbs - highInt * power); + DecimalDigits.uncheckedGetCharsLatin1(highInt, insertionPoint, buf); + buf[insertionPoint] = '.'; + int smallStart = DecimalDigits.uncheckedGetCharsLatin1(small, buf.length, buf); + if (smallStart > insertionPoint + 1) { // fill zeros + Arrays.fill(buf, insertionPoint + 1, smallStart, (byte) '0'); + } + } else { /* We must insert zeros between point and intVal */ + int len = (signum < 0 ? 3 : 2) + scale; if (len < 0) { throw new OutOfMemoryError("too large to fit in a String"); } - StringBuilder buf = new StringBuilder(len); - buf.append(str); - buf.repeat('0', trailingZeros); - return buf.toString(); - } - String str; - if(intCompact!=INFLATED) { - str = Long.toString(Math.abs(intCompact)); - } else { - str = intVal.abs().toString(); + buf = new byte[len]; + int off = 0; + if(signum < 0) { + buf[0] = '-'; + off = 1; + } + buf[off ] = '0'; + buf[off + 1] = '.'; + Arrays.fill(buf, off + 2, off + 2 - insertionPoint, (byte) '0'); + DecimalDigits.uncheckedGetCharsLatin1(intCompactAbs, buf.length, buf); } - return getValueString(signum(), str, scale); + return JLA.uncheckedNewStringWithLatin1Bytes(buf); } /* Returns a digit.digit string */ private static String getValueString(int signum, String intString, int scale) { /* Insert decimal point */ - StringBuilder buf; int insertionPoint = intString.length() - scale; if (insertionPoint == 0) { /* Point goes just before intVal */ - return (signum<0 ? "-0." : "0.") + intString; + return (signum < 0 ? "-0." : "0.").concat(intString); } else if (insertionPoint > 0) { /* Point goes inside intVal */ - buf = new StringBuilder(intString); - buf.insert(insertionPoint, '.'); - if (signum < 0) - buf.insert(0, '-'); + StringBuilder buf = new StringBuilder(); + if (signum < 0) { + buf.append('-'); + insertionPoint++; + } + return buf.append(intString) + .insert(insertionPoint, '.') + .toString(); } else { /* We must insert zeros between point and intVal */ int len = (signum < 0 ? 3 : 2) + scale; if (len < 0) { throw new OutOfMemoryError("too large to fit in a String"); } - buf = new StringBuilder(len); - buf.append(signum<0 ? "-0." : "0."); - buf.repeat('0', -insertionPoint); // insertionPoint != MIN_VALUE - buf.append(intString); + return new StringBuilder(len) + .append(signum<0 ? "-0." : "0.") + .repeat('0', -insertionPoint) // insertionPoint != MIN_VALUE + .append(intString) + .toString(); } - return buf.toString(); } /** @@ -4131,75 +4185,65 @@ public BigDecimal ulp() { * {@code BigDecimal} */ private String layoutChars(boolean sci) { - long intCompact = this.intCompact; int scale = this.scale; if (scale == 0) // zero scale is trivial - return (intCompact != INFLATED) ? - Long.toString(intCompact): - intVal.toString(); - if (scale == 2 && - intCompact >= 0 && intCompact < Integer.MAX_VALUE) { - // currency fast path - int lowInt = (int)intCompact % 100; - int highInt = (int)intCompact / 100; - int highIntSize = DecimalDigits.stringSize(highInt); - byte[] buf = new byte[highIntSize + 3]; - DecimalDigits.uncheckedGetCharsLatin1(highInt, highIntSize, buf); - buf[highIntSize] = '.'; - DecimalDigits.uncheckedPutPairLatin1(buf, highIntSize + 1, lowInt); - return JLA.uncheckedNewStringWithLatin1Bytes(buf); - } - - char[] coeff; - int offset; // offset is the starting index for coeff array - // Get the significand as an absolute value + return unscaledString(); + + long intCompact = this.intCompact; + int signum, coeffLen; + // the significand as an absolute value + String coeff; if (intCompact != INFLATED) { - // All non negative longs can be made to fit into 19 character array. - coeff = new char[19]; - offset = DecimalDigits.getChars(Math.abs(intCompact), coeff.length, coeff); + long intCompactAbs = Math.abs(intCompact); + if (scale == 2 && (int) intCompact == intCompactAbs) { // intCompact >= 0 && intCompact <= Integer.MAX_VALUE + return scale2((int) intCompact); + } + coeffLen = DecimalDigits.stringSize(intCompactAbs); + signum = signum(); + long adjusted = -(long)scale + (coeffLen -1); + if ((scale >= 0) & (adjusted >= -6)) { // plain number + return getValueString(signum, intCompactAbs, coeffLen, scale); + } + byte[] buf = new byte[coeffLen]; + DecimalDigits.uncheckedGetCharsLatin1(intCompactAbs, buf.length, buf); + coeff = JLA.uncheckedNewStringWithLatin1Bytes(buf); } else { - offset = 0; - coeff = intVal.abs().toString().toCharArray(); + signum = signum(); + coeff = intVal.abs().toString(); + coeffLen = coeff.length(); } // Construct a buffer, with sufficient capacity for all cases. // If E-notation is needed, length will be: +1 if negative, +1 // if '.' needed, +2 for "E+", + up to 10 for adjusted exponent. // Otherwise it could have +1 if negative, plus leading "0.00000" - StringBuilder buf = new StringBuilder(32);; - if (signum() < 0) // prefix '-' if negative - buf.append('-'); - int coeffLen = coeff.length - offset; - long adjusted = -(long)scale + (coeffLen -1); + long adjusted = -(long) scale + (coeffLen - 1); if ((scale >= 0) && (adjusted >= -6)) { // plain number - int pad = scale - coeffLen; // count of padding zeros - if (pad >= 0) { // 0.xxx form - buf.append('0'); - buf.append('.'); - for (; pad>0; pad--) { - buf.append('0'); - } - buf.append(coeff, offset, coeffLen); - } else { // xx.xx form - buf.append(coeff, offset, -pad); - buf.append('.'); - buf.append(coeff, -pad + offset, scale); + return getValueString(signum, coeff, scale); + } + // E-notation is needed + return layoutCharsE(sci, coeff, coeffLen, adjusted); + } + + private String layoutCharsE(boolean sci, String coeff, int coeffLen, long adjusted) { + StringBuilder buf = new StringBuilder(coeffLen + 14); + int signum = signum(); + if (signum < 0) // prefix '-' if negative + buf.append('-'); + if (sci) { // Scientific notation + buf.append(coeff.charAt(0)); // first character + if (coeffLen > 1) { // more to come + buf.append('.') + .append(coeff, 1, coeffLen); } - } else { // E-notation is needed - if (sci) { // Scientific notation - buf.append(coeff[offset]); // first character - if (coeffLen > 1) { // more to come - buf.append('.'); - buf.append(coeff, offset + 1, coeffLen - 1); - } - } else { // Engineering notation - int sig = (int)(adjusted % 3); - if (sig < 0) - sig += 3; // [adjusted was negative] - adjusted -= sig; // now a multiple of 3 - sig++; - if (signum() == 0) { - switch (sig) { + } else { // Engineering notation + int sig = (int)(adjusted % 3); + if (sig < 0) + sig += 3; // [adjusted was negative] + adjusted -= sig; // now a multiple of 3 + sig++; + if (signum == 0) { + switch (sig) { case 1: buf.append('0'); // exponent is a multiple of three break; @@ -4213,29 +4257,41 @@ private String layoutChars(boolean sci) { break; default: throw new AssertionError("Unexpected sig value " + sig); - } - } else if (sig >= coeffLen) { // significand all in integer - buf.append(coeff, offset, coeffLen); - // may need some zeros, too - for (int i = sig - coeffLen; i > 0; i--) { - buf.append('0'); - } - } else { // xx.xxE form - buf.append(coeff, offset, sig); - buf.append('.'); - buf.append(coeff, offset + sig, coeffLen - sig); } + } else if (sig >= coeffLen) {// significand all in integer + buf.append(coeff, 0, coeffLen) + .repeat('0', sig - coeffLen); // may need some zeros, too + } else { // xx.xxE form + buf.append(coeff, 0, sig) + .append('.') + .append(coeff, sig, coeffLen); } - if (adjusted != 0) { // [!sci could have made 0] - buf.append('E'); - if (adjusted > 0) // force sign for positive - buf.append('+'); - buf.append(adjusted); - } + } + if (adjusted != 0) { // [!sci could have made 0] + buf.append('E'); + if (adjusted > 0) // force sign for positive + buf.append('+'); + buf.append(adjusted); } return buf.toString(); } + private static String scale2(int intCompact) { + int highInt = intCompact / 100; + int lowInt = intCompact - highInt * 100; + int highIntSize = DecimalDigits.stringSize(highInt); + byte[] buf = new byte[highIntSize + 3]; + DecimalDigits.uncheckedPutPairLatin1(buf, highIntSize + 1, lowInt); + buf[highIntSize] = '.'; + DecimalDigits.uncheckedGetCharsLatin1(highInt, highIntSize, buf); + return JLA.uncheckedNewStringWithLatin1Bytes(buf); + } + private String unscaledString() { + return intCompact != INFLATED + ? Long.toString(intCompact) + : intVal.toString(); + } + /** * Return 10 to the power n, as a {@code BigInteger}. * diff --git a/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java b/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java index 6c0c745651e34..2cb0ad8bc5c4c 100644 --- a/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java +++ b/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java @@ -286,7 +286,6 @@ public static int uncheckedGetCharsUTF16(int i, int index, byte[] buf) { return charPos; } - /** * This is a variant of {@link DecimalDigits#uncheckedGetCharsLatin1(long, int, byte[])}, but for * UTF-16 coder. @@ -339,56 +338,6 @@ public static int uncheckedGetCharsUTF16(long i, int index, byte[] buf) { return charPos; } - /** - * This is a variant of {@link DecimalDigits#uncheckedGetCharsUTF16(long, int, byte[])}, but for - * UTF-16 coder. - * - * @param i value to convert - * @param index next index, after the least significant digit - * @param buf target buffer, UTF16-coded. - * @return index of the most significant digit or minus sign, if present - */ - public static int getChars(long i, int index, char[] buf) { - long q; - int charPos = index; - - boolean negative = (i < 0); - if (!negative) { - i = -i; - } - - // Get 2 digits/iteration using longs until quotient fits into an int - while (i < Integer.MIN_VALUE) { - q = i / 100; - charPos -= 2; - putPair(buf, charPos, (int)((q * 100) - i)); - i = q; - } - - // Get 2 digits/iteration using ints - int q2; - int i2 = (int)i; - while (i2 <= -100) { - q2 = i2 / 100; - charPos -= 2; - putPair(buf, charPos, (q2 * 100) - i2); - i2 = q2; - } - - // We know there are at most two digits left at this point. - if (i2 <= -10) { - charPos -= 2; - putPair(buf, charPos, -i2); - } else { - buf[--charPos] = (char) ('0' - i2); - } - - if (negative) { - buf[--charPos] = '-'; - } - return charPos; - } - /** * Insert the 2-chars integer into the buf as 2 decimal digit ASCII chars, * only least significant 16 bits of {@code v} are used. diff --git a/test/micro/org/openjdk/bench/java/math/BigDecimals.java b/test/micro/org/openjdk/bench/java/math/BigDecimals.java index e9f5ba90a7b4a..38d837ce8c7a0 100644 --- a/test/micro/org/openjdk/bench/java/math/BigDecimals.java +++ b/test/micro/org/openjdk/bench/java/math/BigDecimals.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 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 @@ -35,6 +36,7 @@ import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.infra.Blackhole; +import java.lang.invoke.*; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.Random; @@ -45,7 +47,7 @@ @State(Scope.Thread) @Warmup(iterations = 5, time = 1) @Measurement(iterations = 5, time = 1) -@Fork(value = 3) +@Fork(value = 3, jvmArgs = {"--add-opens", "java.base/java.math=ALL-UNNAMED"}) public class BigDecimals { /** Make sure TEST_SIZE is used to size the arrays. We need this constant to parametrize the operations count. */ @@ -60,7 +62,9 @@ public class BigDecimals { private BigDecimal[] bigDecimals; private String[] stringInputs; private double[] doubleInputs; - private BigDecimal[] hugeArray, largeArray, smallArray; + private BigDecimal[] hugeArray; + private BigDecimal[] large2Array, small2Array; + private BigDecimal[] large3Array, small3Array; @Setup public void setup() { @@ -96,22 +100,28 @@ public void setup() { hugeArray = new BigDecimal[TEST_SIZE]; /* - * Large numbers less than MAX_LONG but larger than MAX_INT - */ - largeArray = new BigDecimal[TEST_SIZE]; + * Large numbers less than MAX_LONG but larger than MAX_INT + */ + large2Array = new BigDecimal[TEST_SIZE]; + large3Array = new BigDecimal[TEST_SIZE]; /* - * Small number less than MAX_INT - */ - smallArray = new BigDecimal[TEST_SIZE]; + * Small number less than MAX_INT + */ + small2Array = new BigDecimal[TEST_SIZE]; + small3Array = new BigDecimal[TEST_SIZE]; dummyStringArray = new String[TEST_SIZE]; for (int i = 0; i < TEST_SIZE; i++) { int value = Math.abs(r.nextInt()); hugeArray[i] = new BigDecimal("" + ((long) value + (long) Integer.MAX_VALUE) + ((long) value + (long) Integer.MAX_VALUE) + ".55"); - largeArray[i] = new BigDecimal("" + ((long) value + (long) Integer.MAX_VALUE) + ".55"); - smallArray[i] = new BigDecimal("" + ((long) value / 1000) + ".55"); + + large2Array[i] = new BigDecimal("" + ((long) value + (long) Integer.MAX_VALUE) + ".55"); + large3Array[i] = new BigDecimal("" + ((long) value + (long) Integer.MAX_VALUE) + ".555"); + + small2Array[i] = new BigDecimal("" + ((long) value / 1000) + ".55"); + small3Array[i] = new BigDecimal("" + ((long) value / 1000) + ".555"); } } @@ -185,65 +195,179 @@ public void testMultiply(Blackhole bh) { bh.consume(tmp); } - /** Test divide with huge/small numbers */ + /** Invokes the compareTo method of BigDecimal with various different values. */ + @Benchmark + @OperationsPerInvocation(TEST_SIZE - 1) + public void testCompareTo(Blackhole bh) { + BigDecimal c = bigDecimals[0]; + for (BigDecimal s : bigDecimals) { + bh.consume(c.compareTo(s)); + } + } + + /** Invokes the valueOf(double) of BigDecimal with various different values. */ + @Benchmark + @OperationsPerInvocation(TEST_SIZE) + public void testValueOfWithDouble(Blackhole bh) { + for (double s : doubleInputs) { + bh.consume(BigDecimal.valueOf(s)); + } + } + + /** Create BigDecimal from double with Double.toString on different values. */ + @Benchmark + @OperationsPerInvocation(TEST_SIZE) + public void testValueOfWithDoubleString(Blackhole bh) { + for (double s : doubleInputs) { + bh.consume(new BigDecimal(Double.toString(s))); + } + } + + /** Test BigDecimal.toString() with large numbers (scale 2) less than MAX_LONG but larger than MAX_INT */ @Benchmark - @OperationsPerInvocation(TEST_SIZE * TEST_SIZE) - public void testHugeSmallDivide(Blackhole bh) { + @OperationsPerInvocation(TEST_SIZE) + public void hugeLayoutCharsToString(Blackhole bh) throws Throwable { for (BigDecimal s : hugeArray) { - for (BigDecimal t : smallArray) { - bh.consume(s.divide(t, RoundingMode.DOWN)); - } + bh.consume((String) BigDecimalAccess.layoutChars.invokeExact(s, true)); } } - /** Test divide with large/small numbers */ + /** Test BigDecimal.toEngineeringString() with huge numbers larger than MAX_LONG */ @Benchmark - @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)); - } + @OperationsPerInvocation(TEST_SIZE) + public void hugeEngineeringToString(Blackhole bh) { + for (BigDecimal s : hugeArray) { + bh.consume(s.toEngineeringString()); } } - /** Test divide with huge/large numbers */ + /** Test BigDecimal.toEngineeringString() with huge numbers larger than MAX_LONG */ @Benchmark - @OperationsPerInvocation(TEST_SIZE * TEST_SIZE) - public void testHugeLargeDivide(Blackhole bh) { + @OperationsPerInvocation(TEST_SIZE) + public void hugePlainToString(Blackhole bh) { for (BigDecimal s : hugeArray) { - for (BigDecimal t : largeArray) { - bh.consume(s.divide(t, RoundingMode.DOWN)); - } + bh.consume(s.toPlainString()); } } - /** Invokes the compareTo method of BigDecimal with various different values. */ + /** Test BigDecimal.toString() with large numbers (scale 2) less than MAX_LONG but larger than MAX_INT */ @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) + public void largeScale2LayoutCharsToString(Blackhole bh) throws Throwable { + for (BigDecimal s : large2Array) { + bh.consume((String) BigDecimalAccess.layoutChars.invokeExact(s, true)); } } + /** Test BigDecimal.toEngineeringString() with large numbers (scale 2) less than MAX_LONG but larger than MAX_INT */ + @Benchmark + @OperationsPerInvocation(TEST_SIZE) + public void largeScale2EngineeringToString(Blackhole bh) { + for (BigDecimal s : large2Array) { + bh.consume(s.toEngineeringString()); + } + } - /** Invokes the valueOf(double) of BigDecimal with various different values. */ + /** Test BigDecimal.toPlainString() with large numbers (scale 2) less than MAX_LONG but larger than MAX_INT */ @Benchmark @OperationsPerInvocation(TEST_SIZE) - public void testValueOfWithDouble(Blackhole bh) { - for (double s : doubleInputs) { - bh.consume(BigDecimal.valueOf(s)); + public void largeScale2PlainToString(Blackhole bh) { + for (BigDecimal s : large2Array) { + bh.consume(s.toPlainString()); } } - /** Create BigDecimal from double with Double.toString on different values. */ + /** Test BigDecimal.toString() with large numbers (scale 3) less than MAX_LONG but larger than MAX_INT */ @Benchmark @OperationsPerInvocation(TEST_SIZE) - public void testValueOfWithDoubleString(Blackhole bh) { - for (double s : doubleInputs) { - bh.consume(new BigDecimal(Double.toString(s))); + public void largeScale3LayoutCharsToString(Blackhole bh) throws Throwable { + for (BigDecimal s : large3Array) { + bh.consume((String) BigDecimalAccess.layoutChars.invokeExact(s, true)); + } + } + + /** Test BigDecimal.toEngineeringString() with large numbers (scale 3) less than MAX_LONG but larger than MAX_INT */ + @Benchmark + @OperationsPerInvocation(TEST_SIZE) + public void largeScale3EngineeringToString(Blackhole bh) { + for (BigDecimal s : large3Array) { + bh.consume(s.toEngineeringString()); + } + } + + + /** Test BigDecimal.toPlainString() with large numbers (scale 3) less than MAX_LONG but larger than MAX_INT */ + @Benchmark + @OperationsPerInvocation(TEST_SIZE) + public void largeScale3PlainToString(Blackhole bh) { + for (BigDecimal s : large3Array) { + bh.consume(s.toPlainString()); + } + } + + /** Test BigDecimal.toString() with small numbers (scale 2) less than MAX_INT */ + @Benchmark + @OperationsPerInvocation(TEST_SIZE) + public void smallScale2LayoutCharsToString(Blackhole bh) throws Throwable { + for (BigDecimal s : small2Array) { + bh.consume((String) BigDecimalAccess.layoutChars.invokeExact(s, true)); + } + } + + /** Test BigDecimal.toEngineeringString() with small numbers (scale 2) less than MAX_INT */ + @Benchmark + @OperationsPerInvocation(TEST_SIZE) + public void smallScale2EngineeringToString(Blackhole bh) { + for (BigDecimal s : small2Array) { + bh.consume(s.toEngineeringString()); + } + } + + /** Test BigDecimal.toPlainString() with small numbers (scale 3) less than MAX_INT */ + @Benchmark + @OperationsPerInvocation(TEST_SIZE) + public void smallScale2PlainToString(Blackhole bh) { + for (BigDecimal s : small2Array) { + bh.consume(s.toPlainString()); + } + } + + /** Test BigDecimal.toString() with small numbers (scale 3) less than MAX_INT */ + @Benchmark + @OperationsPerInvocation(TEST_SIZE) + public void smallScale3LayoutCharsToString(Blackhole bh) throws Throwable { + for (BigDecimal s : small3Array) { + bh.consume((String) BigDecimalAccess.layoutChars.invokeExact(s, true)); + } + } + + /** Test BigDecimal.toEngineeringString() with small numbers (scale 3) less than MAX_INT */ + @Benchmark + @OperationsPerInvocation(TEST_SIZE) + public void smallScale3EngineeringToString(Blackhole bh) { + for (BigDecimal s : small3Array) { + bh.consume(s.toEngineeringString()); + } + } + + /** Test BigDecimal.toPlainString() with small numbers (scale 3) less than MAX_INT */ + @Benchmark + @OperationsPerInvocation(TEST_SIZE) + public void smallScale3PlainToString(Blackhole bh) { + for (BigDecimal s : small3Array) { + bh.consume(s.toPlainString()); + } + } + + static class BigDecimalAccess { + final static MethodHandle layoutChars; + static { + try { + MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(BigDecimal.class, MethodHandles.lookup()); + layoutChars = lookup.findVirtual(BigDecimal.class, "layoutChars", MethodType.methodType(String.class, boolean.class)); + } catch (Throwable e) { + throw new AssertionError(e); + } } } }