blob: 6c0c745651e3418ec9728f5b87a06f41b6c7446e [file]
/*
* Copyright (c) 2025, 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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 jdk.internal.util;
import jdk.internal.misc.Unsafe;
import jdk.internal.vm.annotation.Stable;
import static jdk.internal.misc.Unsafe.ARRAY_BYTE_BASE_OFFSET;
/**
* Digits class for decimal digits.
*
* @since 21
*/
public final class DecimalDigits {
private static final Unsafe UNSAFE = Unsafe.getUnsafe();
/**
* Each element of the array represents the packaging of two ascii characters based on little endian:<p>
* <pre>
* 00 -> '0' | ('0' << 8) -> 0x3030
* 01 -> '0' | ('1' << 8) -> 0x3130
* 02 -> '0' | ('2' << 8) -> 0x3230
*
* ...
*
* 10 -> '1' | ('0' << 8) -> 0x3031
* 11 -> '1' | ('1' << 8) -> 0x3131
* 12 -> '1' | ('2' << 8) -> 0x3231
*
* ...
*
* 97 -> '9' | ('7' << 8) -> 0x3739
* 98 -> '9' | ('8' << 8) -> 0x3839
* 99 -> '9' | ('9' << 8) -> 0x3939
* </pre>
*/
@Stable
private static final short[] DIGITS;
static {
short[] digits = new short[128];
for (int i = 0; i < 10; i++) {
short hi = (short) (i + '0');
for (int j = 0; j < 10; j++) {
short lo = (short) ((j + '0') << 8);
digits[i * 10 + j] = (short) (hi | lo);
}
}
DIGITS = digits;
}
/**
* Constructor.
*/
private DecimalDigits() {
}
/**
* Returns the string representation size for a given int value.
*
* @param x int value
* @return string size
*
* @implNote There are other ways to compute this: e.g. binary search,
* but values are biased heavily towards zero, and therefore linear search
* wins. The iteration results are also routinely inlined in the generated
* code after loop unrolling.
*/
public static int stringSize(int x) {
int d = 1;
if (x >= 0) {
d = 0;
x = -x;
}
int p = -10;
for (int i = 1; i < 10; i++) {
if (x > p)
return i + d;
p = 10 * p;
}
return 10 + d;
}
/**
* Returns the string representation size for a given long value.
*
* @param x long value
* @return string size
*
* @implNote There are other ways to compute this: e.g. binary search,
* but values are biased heavily towards zero, and therefore linear search
* wins. The iteration results are also routinely inlined in the generated
* code after loop unrolling.
*/
public static int stringSize(long x) {
int d = 1;
if (x >= 0) {
d = 0;
x = -x;
}
long p = -10;
for (int i = 1; i < 19; i++) {
if (x > p)
return i + d;
p = 10 * p;
}
return 19 + d;
}
/**
* Places characters representing the integer i into the
* character array buf. The characters are placed into
* the buffer backwards starting with the least significant
* digit at the specified index (exclusive), and working
* backwards from there.
*
* @implNote This method converts positive inputs into negative
* values, to cover the Integer.MIN_VALUE case. Converting otherwise
* (negative to positive) will expose -Integer.MIN_VALUE that overflows
* integer.
* <p>
* <b>WARNING: This method does not perform any bound checks. </b>
*
* @param i value to convert
* @param index next index, after the least significant digit
* @param buf target buffer, Latin1-encoded
* @return index of the most significant digit or minus sign, if present
*/
public static int uncheckedGetCharsLatin1(int i, int index, byte[] buf) {
int q;
int charPos = index;
boolean negative = i < 0;
if (!negative) {
i = -i;
}
// Generate two digits per iteration
while (i <= -100) {
q = i / 100;
charPos -= 2;
uncheckedPutPairLatin1(buf, charPos, (q * 100) - i);
i = q;
}
// We know there are at most two digits left at this point.
if (i <= -10) {
charPos -= 2;
uncheckedPutPairLatin1(buf, charPos, -i);
} else {
uncheckedPutCharLatin1(buf, --charPos, '0' - i);
}
if (negative) {
uncheckedPutCharLatin1(buf, --charPos, '-');
}
return charPos;
}
/**
* Places characters representing the long i into the
* character array buf. The characters are placed into
* the buffer backwards starting with the least significant
* digit at the specified index (exclusive), and working
* backwards from there.
*
* @implNote This method converts positive inputs into negative
* values, to cover the Long.MIN_VALUE case. Converting otherwise
* (negative to positive) will expose -Long.MIN_VALUE that overflows
* long.
* <p>
* <b>WARNING: This method does not perform any bound checks. </b>
*
* @param i value to convert
* @param index next index, after the least significant digit
* @param buf target buffer, Latin1-encoded
* @return index of the most significant digit or minus sign, if present
*/
public static int uncheckedGetCharsLatin1(long i, int index, byte[] 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;
uncheckedPutPairLatin1(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;
uncheckedPutPairLatin1(buf, charPos, (q2 * 100) - i2);
i2 = q2;
}
// We know there are at most two digits left at this point.
if (i2 <= -10) {
charPos -= 2;
uncheckedPutPairLatin1(buf, charPos, -i2);
} else {
uncheckedPutCharLatin1(buf, --charPos, '0' - i2);
}
if (negative) {
uncheckedPutCharLatin1(buf, --charPos, '-');
}
return charPos;
}
/**
* This is a variant of {@link DecimalDigits#uncheckedGetCharsLatin1(int, int, byte[])}, but for
* UTF-16 coder.
* <p>
* <b>WARNING: This method does not perform any bound checks.</b>
*
* @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 uncheckedGetCharsUTF16(int i, int index, byte[] buf) {
int q;
int charPos = index;
boolean negative = (i < 0);
if (!negative) {
i = -i;
}
// Get 2 digits/iteration using ints
while (i <= -100) {
q = i / 100;
charPos -= 2;
uncheckedPutPairUTF16(buf, charPos, (q * 100) - i);
i = q;
}
// We know there are at most two digits left at this point.
if (i <= -10) {
charPos -= 2;
uncheckedPutPairUTF16(buf, charPos, -i);
} else {
uncheckedPutCharUTF16(buf, --charPos, '0' - i);
}
if (negative) {
uncheckedPutCharUTF16(buf, --charPos, '-');
}
return charPos;
}
/**
* This is a variant of {@link DecimalDigits#uncheckedGetCharsLatin1(long, int, byte[])}, but for
* UTF-16 coder.
* <p>
* <b>WARNING: This method does not perform any bound checks.</b>
*
* @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 uncheckedGetCharsUTF16(long i, int index, byte[] 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;
uncheckedPutPairUTF16(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;
uncheckedPutPairUTF16(buf, charPos, (q2 * 100) - i2);
i2 = q2;
}
// We know there are at most two digits left at this point.
if (i2 <= -10) {
charPos -= 2;
uncheckedPutPairUTF16(buf, charPos, -i2);
} else {
uncheckedPutCharUTF16(buf, --charPos, '0' - i2);
}
if (negative) {
uncheckedPutCharUTF16(buf, --charPos, '-');
}
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.
* @param buf byte buffer to copy into
* @param charPos insert point
* @param v to convert
*/
public static void putPair(char[] buf, int charPos, int v) {
int packed = DIGITS[v & 0x7f];
buf[charPos ] = (char) (packed & 0xFF);
buf[charPos + 1] = (char) (packed >> 8);
}
/**
* Insert the 2-bytes integer into the buf as 2 decimal digit ASCII bytes,
* only least significant 16 bits of {@code v} are used.
* <p>
* <b>WARNING: This method does not perform any bound checks.</b>
*
* @param buf byte buffer to copy into
* @param charPos insert point
* @param v to convert
*/
public static void uncheckedPutPairLatin1(byte[] buf, int charPos, int v) {
int packed = DIGITS[v & 0x7f];
uncheckedPutCharLatin1(buf, charPos, packed & 0xFF);
uncheckedPutCharLatin1(buf, charPos + 1, packed >> 8);
}
/**
* Insert the 2-chars integer into the buf as 2 decimal digit UTF16 bytes,
* only least significant 16 bits of {@code v} are used.
* <p>
* <b>WARNING: This method does not perform any bound checks.</b>
*
* @param buf byte buffer to copy into
* @param charPos insert point
* @param v to convert
*/
public static void uncheckedPutPairUTF16(byte[] buf, int charPos, int v) {
int packed = DIGITS[v & 0x7f];
uncheckedPutCharUTF16(buf, charPos, packed & 0xFF);
uncheckedPutCharUTF16(buf, charPos + 1, packed >> 8);
}
private static void uncheckedPutCharLatin1(byte[] buf, int charPos, int c) {
assert charPos >= 0 && charPos < buf.length;
UNSAFE.putByte(buf, ARRAY_BYTE_BASE_OFFSET + charPos, (byte) c);
}
private static void uncheckedPutCharUTF16(byte[] buf, int charPos, int c) {
assert charPos >= 0 && charPos < (buf.length >> 1);
UNSAFE.putCharUnaligned(buf, ARRAY_BYTE_BASE_OFFSET + ((long) charPos << 1), (char) c);
}
}