Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
4040d76
add benchmark
wenshao Nov 11, 2024
635882f
optimize toString/toPlainString
wenshao Jan 25, 2025
7ed19c8
optimize scale2
wenshao Jan 25, 2025
7b82f78
static scale2
wenshao Jan 25, 2025
edffa0f
optimize scale2 int
wenshao Jan 25, 2025
72ed1b7
bug fix
wenshao Jan 25, 2025
cb6786b
bug fix
wenshao Jan 27, 2025
57cc7b2
code style
wenshao Jan 27, 2025
0d2b3aa
use getBytes & JLA instead of StringBuilder
wenshao Jan 27, 2025
4a857cf
code style
wenshao Jan 27, 2025
057d006
code style
wenshao Jan 27, 2025
a41a34b
revert use StringBuilder
wenshao Jan 27, 2025
a734f61
from @j3graham
wenshao Jan 27, 2025
3ed3354
intCompact getValueString & int scale2
wenshao Jan 27, 2025
d99a963
remove unnecessary abs
wenshao Jan 27, 2025
ddc2a86
simplify scale2
wenshao Jan 27, 2025
d78b07c
Revert "simplify scale2"
wenshao Jan 27, 2025
4ee8837
refactor
wenshao Jan 27, 2025
36602df
bug fix
wenshao Jan 27, 2025
dc47c78
bug fix
wenshao Jan 27, 2025
3842e35
revert change
wenshao Jan 27, 2025
7d48ac1
code style
wenshao Jan 27, 2025
0a66197
simplify
wenshao Jan 28, 2025
dd14ca2
simplify
wenshao Jan 28, 2025
cb88e0e
simplify and comments
wenshao Jan 28, 2025
cf43f34
Merge remote-tracking branch 'upstream/master' into dec_to_str_202501
wenshao Jan 30, 2025
0e7b30b
copyright
wenshao Jan 30, 2025
cf67d84
remove getChars(long, int, char[])
wenshao Jan 30, 2025
f9af0b0
Merge remote-tracking branch 'upstream/master' into dec_to_str_202501
wenshao Feb 1, 2025
a5a07ce
Merge remote-tracking branch 'upstream/master' into dec_to_str_202501
wenshao Apr 22, 2025
b4c142d
Merge remote-tracking branch 'upstream/master' into dec_to_str_202501
wenshao May 25, 2025
3c3b740
Merge remote-tracking branch 'upstream/master' into dec_to_str_202501
Aug 23, 2025
d7e2934
Refactor BigDecimal string creation to use JLA.uncheckedNewStringWith…
Aug 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
274 changes: 165 additions & 109 deletions src/java.base/share/classes/java/math/BigDecimal.java
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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();
}

/**
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something to experiment with here:

        int highInt = intCompact / 100;
        int lowInt = intCompact - highInt * 100;
        short packed=DecimalDigits.pair(lowInt);

        return new StringBuilder()
                .append(highInt)
                .append('.')
                .append((char) (packed & 0xFF))
                .append((char) (packed >> 8))
                .toString();

C2 seems to be able to optimize out the SB here, so it might do as well as newStringNoRepl.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestion, but DecimalDigits no longer provides a pair method

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right - you’d need to add it back to try this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested it on a MacBook M1 Max machine and the results were different from what you said. Using StringBuilder + digitPair would degrade performance.

git remote add wenshao [email protected]:wenshao/jdk.git
git fetch wenshao

# use JLA
git checkout f9af0b0203145b95d82a2067a10bde61b0915dd7
make test TEST="micro:java.math.BigDecimals.smallScale2"

# use digitPair
git checkout 4124121933e08ab185f3f879856197ed15caafd7
make test TEST="micro:java.math.BigDecimals.smallScale2"
Benchmark                                   Mode  Cnt  Score   Error  Units (f9af0b02031)
BigDecimals.smallScale2EngineeringToString  avgt   15  9.658 ? 0.228  ns/op
BigDecimals.smallScale2LayoutCharsToString  avgt   15  9.597 ? 0.047  ns/op
BigDecimals.smallScale2PlainToString        avgt   15  9.759 ? 0.054  ns/op


Benchmark                                   Mode  Cnt   Score   Error  Units (4124121933e)
BigDecimals.smallScale2EngineeringToString  avgt   15  18.763 ? 0.332  ns/op
BigDecimals.smallScale2LayoutCharsToString  avgt   15  18.738 ? 0.214  ns/op
BigDecimals.smallScale2PlainToString        avgt   15  18.992 ? 0.226  ns/op

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}.
*
Expand Down
Loading