8334328: Reduce object allocation for FloatToDecimal and DoubleToDecimal
Reviewed-by: redestad, rgiulietti
This commit is contained in:
parent
9bb675f89d
commit
9d20b58f40
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2003, 2024, 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
|
||||
@ -885,13 +885,10 @@ abstract sealed class AbstractStringBuilder implements Appendable, CharSequence
|
||||
* @return a reference to this object.
|
||||
*/
|
||||
public AbstractStringBuilder append(float f) {
|
||||
try {
|
||||
FloatToDecimal.appendTo(f, this);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
ensureCapacityInternal(count + FloatToDecimal.MAX_CHARS);
|
||||
FloatToDecimal toDecimal = isLatin1() ? FloatToDecimal.LATIN1 : FloatToDecimal.UTF16;
|
||||
count = toDecimal.putDecimal(value, count, f);
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -907,11 +904,9 @@ abstract sealed class AbstractStringBuilder implements Appendable, CharSequence
|
||||
* @return a reference to this object.
|
||||
*/
|
||||
public AbstractStringBuilder append(double d) {
|
||||
try {
|
||||
DoubleToDecimal.appendTo(d, this);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
ensureCapacityInternal(count + DoubleToDecimal.MAX_CHARS);
|
||||
DoubleToDecimal toDecimal = isLatin1() ? DoubleToDecimal.LATIN1 : DoubleToDecimal.UTF16;
|
||||
count = toDecimal.putDecimal(value, count, d);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* 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
|
||||
@ -32,10 +33,24 @@ import static java.lang.Long.*;
|
||||
import static java.lang.Math.multiplyHigh;
|
||||
import static jdk.internal.math.MathUtils.*;
|
||||
|
||||
import sun.nio.cs.ISO_8859_1;
|
||||
|
||||
/**
|
||||
* This class exposes a method to render a {@code double} as a string.
|
||||
*/
|
||||
public final class DoubleToDecimal {
|
||||
public final class DoubleToDecimal extends ToDecimal {
|
||||
/**
|
||||
* Use LATIN1 encoding to process the in-out byte[] str
|
||||
*
|
||||
*/
|
||||
public static final DoubleToDecimal LATIN1 = new DoubleToDecimal(true);
|
||||
|
||||
/**
|
||||
* Use UTF16 encoding to process the in-out byte[] str
|
||||
*
|
||||
*/
|
||||
public static final DoubleToDecimal UTF16 = new DoubleToDecimal(false);
|
||||
|
||||
/*
|
||||
* For full details about this code see the following references:
|
||||
*
|
||||
@ -91,16 +106,6 @@ public final class DoubleToDecimal {
|
||||
/* Used in rop() */
|
||||
private static final long MASK_63 = (1L << 63) - 1;
|
||||
|
||||
/* Used for left-to-tight digit extraction */
|
||||
private static final int MASK_28 = (1 << 28) - 1;
|
||||
|
||||
private static final int NON_SPECIAL = 0;
|
||||
private static final int PLUS_ZERO = 1;
|
||||
private static final int MINUS_ZERO = 2;
|
||||
private static final int PLUS_INF = 3;
|
||||
private static final int MINUS_INF = 4;
|
||||
private static final int NAN = 5;
|
||||
|
||||
/*
|
||||
* Room for the longer of the forms
|
||||
* -ddddd.dddddddddddd H + 2 characters
|
||||
@ -110,13 +115,8 @@ public final class DoubleToDecimal {
|
||||
*/
|
||||
public static final int MAX_CHARS = H + 7;
|
||||
|
||||
private final byte[] bytes;
|
||||
|
||||
/* Index into bytes of rightmost valid character */
|
||||
private int index;
|
||||
|
||||
private DoubleToDecimal(boolean noChars) {
|
||||
bytes = noChars ? null : new byte[MAX_CHARS];
|
||||
private DoubleToDecimal(boolean latin1) {
|
||||
super(latin1);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -128,7 +128,14 @@ public final class DoubleToDecimal {
|
||||
* @see Double#toString(double)
|
||||
*/
|
||||
public static String toString(double v) {
|
||||
return new DoubleToDecimal(false).toDecimalString(v);
|
||||
byte[] str = new byte[MAX_CHARS];
|
||||
int pair = LATIN1.toDecimal(str, 0, v, null);
|
||||
int type = pair & 0xFF00;
|
||||
if (type == NON_SPECIAL) {
|
||||
int size = pair & 0xFF;
|
||||
return new String(str, 0, size, ISO_8859_1.INSTANCE);
|
||||
}
|
||||
return special(type);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -149,71 +156,42 @@ public final class DoubleToDecimal {
|
||||
* @param fd the object that will carry <i>f</i>, <i>e</i>, and <i>n</i>.
|
||||
*/
|
||||
public static void split(double v, FormattedFPDecimal fd) {
|
||||
new DoubleToDecimal(true).toDecimal(v, fd);
|
||||
byte[] str = new byte[MAX_CHARS];
|
||||
LATIN1.toDecimal(str, 0, v, fd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the rendering of the {@code v} to {@code app}.
|
||||
* Appends the rendering of the {@code v} to {@code str}.
|
||||
*
|
||||
* <p>The outcome is the same as if {@code v} were first
|
||||
* {@link #toString(double) rendered} and the resulting string were then
|
||||
* {@link Appendable#append(CharSequence) appended} to {@code app}.
|
||||
*
|
||||
* @param v the {@code double} whose rendering is appended.
|
||||
* @param app the {@link Appendable} to append to.
|
||||
* @param str the String byte array to append to
|
||||
* @param index the index into str
|
||||
* @param v the {@code double} whose rendering is into str.
|
||||
* @throws IOException If an I/O error occurs
|
||||
*/
|
||||
public static Appendable appendTo(double v, Appendable app)
|
||||
throws IOException {
|
||||
return new DoubleToDecimal(false).appendDecimalTo(v, app);
|
||||
}
|
||||
public int putDecimal(byte[] str, int index, double v) {
|
||||
assert 0 <= index && index <= length(str) - MAX_CHARS : "Trusted caller missed bounds check";
|
||||
|
||||
private String toDecimalString(double v) {
|
||||
return switch (toDecimal(v, null)) {
|
||||
case NON_SPECIAL -> charsToString();
|
||||
case PLUS_ZERO -> "0.0";
|
||||
case MINUS_ZERO -> "-0.0";
|
||||
case PLUS_INF -> "Infinity";
|
||||
case MINUS_INF -> "-Infinity";
|
||||
default -> "NaN";
|
||||
};
|
||||
}
|
||||
|
||||
private Appendable appendDecimalTo(double v, Appendable app)
|
||||
throws IOException {
|
||||
switch (toDecimal(v, null)) {
|
||||
case NON_SPECIAL:
|
||||
char[] chars = new char[index + 1];
|
||||
for (int i = 0; i < chars.length; ++i) {
|
||||
chars[i] = (char) bytes[i];
|
||||
}
|
||||
if (app instanceof StringBuilder builder) {
|
||||
return builder.append(chars);
|
||||
}
|
||||
if (app instanceof StringBuffer buffer) {
|
||||
return buffer.append(chars);
|
||||
}
|
||||
for (char c : chars) {
|
||||
app.append(c);
|
||||
}
|
||||
return app;
|
||||
case PLUS_ZERO: return app.append("0.0");
|
||||
case MINUS_ZERO: return app.append("-0.0");
|
||||
case PLUS_INF: return app.append("Infinity");
|
||||
case MINUS_INF: return app.append("-Infinity");
|
||||
default: return app.append("NaN");
|
||||
int pair = toDecimal(str, index, v, null);
|
||||
int type = pair & 0xFF00;
|
||||
if (type == NON_SPECIAL) {
|
||||
return index + (pair & 0xFF);
|
||||
}
|
||||
return putSpecial(str, index, type);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns
|
||||
* Returns size in the lower byte, type in the high byte, where type is
|
||||
* PLUS_ZERO iff v is 0.0
|
||||
* MINUS_ZERO iff v is -0.0
|
||||
* PLUS_INF iff v is POSITIVE_INFINITY
|
||||
* MINUS_INF iff v is NEGATIVE_INFINITY
|
||||
* NAN iff v is NaN
|
||||
* otherwise NON_SPECIAL
|
||||
*/
|
||||
private int toDecimal(double v, FormattedFPDecimal fd) {
|
||||
private int toDecimal(byte[] str, int index, double v, FormattedFPDecimal fd) {
|
||||
/*
|
||||
* For full details see references [2] and [1].
|
||||
*
|
||||
@ -227,13 +205,13 @@ public final class DoubleToDecimal {
|
||||
long t = bits & T_MASK;
|
||||
int bq = (int) (bits >>> P - 1) & BQ_MASK;
|
||||
if (bq < BQ_MASK) {
|
||||
index = -1;
|
||||
int start = index;
|
||||
if (bits < 0) {
|
||||
/*
|
||||
* fd != null implies bytes == null and bits >= 0
|
||||
* fd != null implies str == null and bits >= 0
|
||||
* Thus, when fd != null, control never reaches here.
|
||||
*/
|
||||
append('-');
|
||||
index = putChar(str, index, '-');
|
||||
}
|
||||
if (bq != 0) {
|
||||
/* normal value. Here mq = -q */
|
||||
@ -243,16 +221,16 @@ public final class DoubleToDecimal {
|
||||
if (0 < mq & mq < P) {
|
||||
long f = c >> mq;
|
||||
if (f << mq == c) {
|
||||
return toChars(f, 0, fd);
|
||||
return toChars(str, index, f, 0, fd) - start;
|
||||
}
|
||||
}
|
||||
return toDecimal(-mq, c, 0, fd);
|
||||
return toDecimal(str, index, -mq, c, 0, fd) - start;
|
||||
}
|
||||
if (t != 0) {
|
||||
/* subnormal value */
|
||||
return t < C_TINY
|
||||
? toDecimal(Q_MIN, 10 * t, -1, fd)
|
||||
: toDecimal(Q_MIN, t, 0, fd);
|
||||
return (t < C_TINY
|
||||
? toDecimal(str, index, Q_MIN, 10 * t, -1, fd)
|
||||
: toDecimal(str, index, Q_MIN, t, 0, fd)) - start;
|
||||
}
|
||||
return bits == 0 ? PLUS_ZERO : MINUS_ZERO;
|
||||
}
|
||||
@ -262,7 +240,7 @@ public final class DoubleToDecimal {
|
||||
return bits > 0 ? PLUS_INF : MINUS_INF;
|
||||
}
|
||||
|
||||
private int toDecimal(int q, long c, int dk, FormattedFPDecimal fd) {
|
||||
private int toDecimal(byte[] str, int index, int q, long c, int dk, FormattedFPDecimal fd) {
|
||||
/*
|
||||
* The skeleton corresponds to figure 7 of [1].
|
||||
* The efficient computations are those summarized in figure 9.
|
||||
@ -327,7 +305,7 @@ public final class DoubleToDecimal {
|
||||
boolean upin = vbl + out <= sp10 << 2;
|
||||
boolean wpin = (tp10 << 2) + out <= vbr;
|
||||
if (upin != wpin) {
|
||||
return toChars(upin ? sp10 : tp10, k, fd);
|
||||
return toChars(str, index, upin ? sp10 : tp10, k, fd);
|
||||
}
|
||||
}
|
||||
|
||||
@ -342,14 +320,14 @@ public final class DoubleToDecimal {
|
||||
boolean win = (t << 2) + out <= vbr;
|
||||
if (uin != win) {
|
||||
/* Exactly one of u or w lies in Rv */
|
||||
return toChars(uin ? s : t, k + dk, fd);
|
||||
return toChars(str, index, uin ? s : t, k + dk, fd);
|
||||
}
|
||||
/*
|
||||
* Both u and w lie in Rv: determine the one closest to v.
|
||||
* See section 9.3 of [1].
|
||||
*/
|
||||
long cmp = vb - (s + t << 1);
|
||||
return toChars(cmp < 0 || cmp == 0 && (s & 0x1) == 0 ? s : t, k + dk, fd);
|
||||
return toChars(str, index, cmp < 0 || cmp == 0 && (s & 0x1) == 0 ? s : t, k + dk, fd);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -368,7 +346,7 @@ public final class DoubleToDecimal {
|
||||
/*
|
||||
* Formats the decimal f 10^e.
|
||||
*/
|
||||
private int toChars(long f, int e, FormattedFPDecimal fd) {
|
||||
private int toChars(byte[] str, int index, long f, int e, FormattedFPDecimal fd) {
|
||||
/*
|
||||
* For details not discussed here see section 10 of [1].
|
||||
*
|
||||
@ -381,7 +359,7 @@ public final class DoubleToDecimal {
|
||||
}
|
||||
if (fd != null) {
|
||||
fd.set(f, e, len);
|
||||
return NON_SPECIAL;
|
||||
return index;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -413,115 +391,74 @@ public final class DoubleToDecimal {
|
||||
int m = (int) (hm - 100_000_000 * h);
|
||||
|
||||
if (0 < e && e <= 7) {
|
||||
return toChars1(h, m, l, e);
|
||||
return toChars1(str, index, h, m, l, e);
|
||||
}
|
||||
if (-3 < e && e <= 0) {
|
||||
return toChars2(h, m, l, e);
|
||||
return toChars2(str, index, h, m, l, e);
|
||||
}
|
||||
return toChars3(h, m, l, e);
|
||||
return toChars3(str, index, h, m, l, e);
|
||||
}
|
||||
|
||||
private int toChars1(int h, int m, int l, int e) {
|
||||
private int toChars1(byte[] str, int index, int h, int m, int l, int e) {
|
||||
/*
|
||||
* 0 < e <= 7: plain format without leading zeroes.
|
||||
* Left-to-right digits extraction:
|
||||
* algorithm 1 in [3], with b = 10, k = 8, n = 28.
|
||||
*/
|
||||
appendDigit(h);
|
||||
index = putDigit(str, index, h);
|
||||
int y = y(m);
|
||||
int t;
|
||||
int i = 1;
|
||||
for (; i < e; ++i) {
|
||||
t = 10 * y;
|
||||
appendDigit(t >>> 28);
|
||||
index = putDigit(str, index, t >>> 28);
|
||||
y = t & MASK_28;
|
||||
}
|
||||
append('.');
|
||||
index = putChar(str, index, '.');
|
||||
for (; i <= 8; ++i) {
|
||||
t = 10 * y;
|
||||
appendDigit(t >>> 28);
|
||||
index = putDigit(str, index, t >>> 28);
|
||||
y = t & MASK_28;
|
||||
}
|
||||
lowDigits(l);
|
||||
return NON_SPECIAL;
|
||||
return lowDigits(str, index, l);
|
||||
}
|
||||
|
||||
private int toChars2(int h, int m, int l, int e) {
|
||||
private int toChars2(byte[] str, int index, int h, int m, int l, int e) {
|
||||
/* -3 < e <= 0: plain format with leading zeroes */
|
||||
appendDigit(0);
|
||||
append('.');
|
||||
index = putDigit(str, index, 0);
|
||||
index = putChar(str, index, '.');
|
||||
for (; e < 0; ++e) {
|
||||
appendDigit(0);
|
||||
index = putDigit(str, index, 0);
|
||||
}
|
||||
appendDigit(h);
|
||||
append8Digits(m);
|
||||
lowDigits(l);
|
||||
return NON_SPECIAL;
|
||||
index = putDigit(str, index, h);
|
||||
index = put8Digits(str, index, m);
|
||||
return lowDigits(str, index, l);
|
||||
}
|
||||
|
||||
private int toChars3(int h, int m, int l, int e) {
|
||||
private int toChars3(byte[] str, int index, int h, int m, int l, int e) {
|
||||
/* -3 >= e | e > 7: computerized scientific notation */
|
||||
appendDigit(h);
|
||||
append('.');
|
||||
append8Digits(m);
|
||||
lowDigits(l);
|
||||
exponent(e - 1);
|
||||
return NON_SPECIAL;
|
||||
index = putDigit(str, index, h);
|
||||
index = putChar(str, index, '.');
|
||||
index = put8Digits(str, index, m);
|
||||
index = lowDigits(str, index, l);
|
||||
return exponent(str, index, e - 1);
|
||||
}
|
||||
|
||||
private void lowDigits(int l) {
|
||||
private int lowDigits(byte[] str, int index, int l) {
|
||||
if (l != 0) {
|
||||
append8Digits(l);
|
||||
index = put8Digits(str, index, l);
|
||||
}
|
||||
removeTrailingZeroes();
|
||||
return removeTrailingZeroes(str, index);
|
||||
}
|
||||
|
||||
private void append8Digits(int m) {
|
||||
/*
|
||||
* Left-to-right digits extraction:
|
||||
* algorithm 1 in [3], with b = 10, k = 8, n = 28.
|
||||
*/
|
||||
int y = y(m);
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
int t = 10 * y;
|
||||
appendDigit(t >>> 28);
|
||||
y = t & MASK_28;
|
||||
}
|
||||
}
|
||||
|
||||
private void removeTrailingZeroes() {
|
||||
while (bytes[index] == '0') {
|
||||
--index;
|
||||
}
|
||||
/* ... but do not remove the one directly to the right of '.' */
|
||||
if (bytes[index] == '.') {
|
||||
++index;
|
||||
}
|
||||
}
|
||||
|
||||
private int y(int a) {
|
||||
/*
|
||||
* Algorithm 1 in [3] needs computation of
|
||||
* floor((a + 1) 2^n / b^k) - 1
|
||||
* with a < 10^8, b = 10, k = 8, n = 28.
|
||||
* Noting that
|
||||
* (a + 1) 2^n <= 10^8 2^28 < 10^17
|
||||
* For n = 17, m = 8 the table in section 10 of [1] leads to:
|
||||
*/
|
||||
return (int) (multiplyHigh(
|
||||
(long) (a + 1) << 28,
|
||||
193_428_131_138_340_668L) >>> 20) - 1;
|
||||
}
|
||||
|
||||
private void exponent(int e) {
|
||||
append('E');
|
||||
private int exponent(byte[] str, int index, int e) {
|
||||
index = putChar(str, index, 'E');
|
||||
if (e < 0) {
|
||||
append('-');
|
||||
index = putChar(str, index, '-');
|
||||
e = -e;
|
||||
}
|
||||
if (e < 10) {
|
||||
appendDigit(e);
|
||||
return;
|
||||
return putDigit(str, index, e);
|
||||
}
|
||||
int d;
|
||||
if (e >= 100) {
|
||||
@ -530,7 +467,7 @@ public final class DoubleToDecimal {
|
||||
* floor(e / 100) = floor(1_311 e / 2^17)
|
||||
*/
|
||||
d = e * 1_311 >>> 17;
|
||||
appendDigit(d);
|
||||
index = putDigit(str, index, d);
|
||||
e -= 100 * d;
|
||||
}
|
||||
/*
|
||||
@ -538,22 +475,7 @@ public final class DoubleToDecimal {
|
||||
* floor(e / 10) = floor(103 e / 2^10)
|
||||
*/
|
||||
d = e * 103 >>> 10;
|
||||
appendDigit(d);
|
||||
appendDigit(e - 10 * d);
|
||||
index = putDigit(str, index, d);
|
||||
return putDigit(str, index, e - 10 * d);
|
||||
}
|
||||
|
||||
private void append(int c) {
|
||||
bytes[++index] = (byte) c;
|
||||
}
|
||||
|
||||
private void appendDigit(int d) {
|
||||
bytes[++index] = (byte) ('0' + d);
|
||||
}
|
||||
|
||||
/* Using the deprecated constructor enhances performance */
|
||||
@SuppressWarnings("deprecation")
|
||||
private String charsToString() {
|
||||
return new String(bytes, 0, 0, index + 1);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* 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
|
||||
@ -32,10 +33,24 @@ import static java.lang.Integer.*;
|
||||
import static java.lang.Math.multiplyHigh;
|
||||
import static jdk.internal.math.MathUtils.*;
|
||||
|
||||
import sun.nio.cs.ISO_8859_1;
|
||||
|
||||
/**
|
||||
* This class exposes a method to render a {@code float} as a string.
|
||||
*/
|
||||
public final class FloatToDecimal {
|
||||
public final class FloatToDecimal extends ToDecimal {
|
||||
/**
|
||||
* Use LATIN1 encoding to process the in-out byte[] str
|
||||
*
|
||||
*/
|
||||
public static final FloatToDecimal LATIN1 = new FloatToDecimal(true);
|
||||
|
||||
/**
|
||||
* Use UTF16 encoding to process the in-out byte[] str
|
||||
*
|
||||
*/
|
||||
public static final FloatToDecimal UTF16 = new FloatToDecimal(false);
|
||||
|
||||
/*
|
||||
* For full details about this code see the following references:
|
||||
*
|
||||
@ -91,16 +106,6 @@ public final class FloatToDecimal {
|
||||
/* Used in rop() */
|
||||
private static final long MASK_32 = (1L << 32) - 1;
|
||||
|
||||
/* Used for left-to-tight digit extraction */
|
||||
private static final int MASK_28 = (1 << 28) - 1;
|
||||
|
||||
private static final int NON_SPECIAL = 0;
|
||||
private static final int PLUS_ZERO = 1;
|
||||
private static final int MINUS_ZERO = 2;
|
||||
private static final int PLUS_INF = 3;
|
||||
private static final int MINUS_INF = 4;
|
||||
private static final int NAN = 5;
|
||||
|
||||
/*
|
||||
* Room for the longer of the forms
|
||||
* -ddddd.dddd H + 2 characters
|
||||
@ -110,12 +115,8 @@ public final class FloatToDecimal {
|
||||
*/
|
||||
public static final int MAX_CHARS = H + 6;
|
||||
|
||||
private final byte[] bytes = new byte[MAX_CHARS];
|
||||
|
||||
/* Index into bytes of rightmost valid character */
|
||||
private int index;
|
||||
|
||||
private FloatToDecimal() {
|
||||
private FloatToDecimal(boolean latin1) {
|
||||
super(latin1);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -127,71 +128,49 @@ public final class FloatToDecimal {
|
||||
* @see Float#toString(float)
|
||||
*/
|
||||
public static String toString(float v) {
|
||||
return new FloatToDecimal().toDecimalString(v);
|
||||
byte[] str = new byte[MAX_CHARS];
|
||||
int pair = LATIN1.toDecimal(str, 0, v);
|
||||
int type = pair & 0xFF00;
|
||||
if (type == NON_SPECIAL) {
|
||||
int size = pair & 0xFF;
|
||||
return new String(str, 0, size, ISO_8859_1.INSTANCE);
|
||||
}
|
||||
return special(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the rendering of the {@code v} to {@code app}.
|
||||
* Appends the rendering of the {@code v} to {@code str}.
|
||||
*
|
||||
* <p>The outcome is the same as if {@code v} were first
|
||||
* {@link #toString(float) rendered} and the resulting string were then
|
||||
* {@link Appendable#append(CharSequence) appended} to {@code app}.
|
||||
* {@link #toString(double) rendered} and the resulting string were then
|
||||
*
|
||||
* @param v the {@code float} whose rendering is appended.
|
||||
* @param app the {@link Appendable} to append to.
|
||||
* @param str the String byte array to append to
|
||||
* @param index the index into str
|
||||
* @param v the {@code float} whose rendering is into str.
|
||||
* @throws IOException If an I/O error occurs
|
||||
*/
|
||||
public static Appendable appendTo(float v, Appendable app)
|
||||
throws IOException {
|
||||
return new FloatToDecimal().appendDecimalTo(v, app);
|
||||
}
|
||||
public int putDecimal(byte[] str, int index, float v) {
|
||||
assert 0 <= index && index <= length(str) - MAX_CHARS : "Trusted caller missed bounds check";
|
||||
|
||||
private String toDecimalString(float v) {
|
||||
return switch (toDecimal(v)) {
|
||||
case NON_SPECIAL -> charsToString();
|
||||
case PLUS_ZERO -> "0.0";
|
||||
case MINUS_ZERO -> "-0.0";
|
||||
case PLUS_INF -> "Infinity";
|
||||
case MINUS_INF -> "-Infinity";
|
||||
default -> "NaN";
|
||||
};
|
||||
}
|
||||
|
||||
private Appendable appendDecimalTo(float v, Appendable app)
|
||||
throws IOException {
|
||||
switch (toDecimal(v)) {
|
||||
case NON_SPECIAL:
|
||||
char[] chars = new char[index + 1];
|
||||
for (int i = 0; i < chars.length; ++i) {
|
||||
chars[i] = (char) bytes[i];
|
||||
}
|
||||
if (app instanceof StringBuilder builder) {
|
||||
return builder.append(chars);
|
||||
}
|
||||
if (app instanceof StringBuffer buffer) {
|
||||
return buffer.append(chars);
|
||||
}
|
||||
for (char c : chars) {
|
||||
app.append(c);
|
||||
}
|
||||
return app;
|
||||
case PLUS_ZERO: return app.append("0.0");
|
||||
case MINUS_ZERO: return app.append("-0.0");
|
||||
case PLUS_INF: return app.append("Infinity");
|
||||
case MINUS_INF: return app.append("-Infinity");
|
||||
default: return app.append("NaN");
|
||||
int pair = toDecimal(str, index, v);
|
||||
int type = pair & 0xFF00;
|
||||
if (type == NON_SPECIAL) {
|
||||
return index + (pair & 0xFF);
|
||||
}
|
||||
return putSpecial(str, index, type);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns
|
||||
* Combine type and size, the first byte is size, the second byte is type
|
||||
*
|
||||
* PLUS_ZERO iff v is 0.0
|
||||
* MINUS_ZERO iff v is -0.0
|
||||
* PLUS_INF iff v is POSITIVE_INFINITY
|
||||
* MINUS_INF iff v is NEGATIVE_INFINITY
|
||||
* NAN iff v is NaN
|
||||
*/
|
||||
private int toDecimal(float v) {
|
||||
private int toDecimal(byte[] str, int index, float v) {
|
||||
/*
|
||||
* For full details see references [2] and [1].
|
||||
*
|
||||
@ -205,9 +184,9 @@ public final class FloatToDecimal {
|
||||
int t = bits & T_MASK;
|
||||
int bq = (bits >>> P - 1) & BQ_MASK;
|
||||
if (bq < BQ_MASK) {
|
||||
index = -1;
|
||||
int start = index;
|
||||
if (bits < 0) {
|
||||
append('-');
|
||||
index = putChar(str, index, '-');
|
||||
}
|
||||
if (bq != 0) {
|
||||
/* normal value. Here mq = -q */
|
||||
@ -217,16 +196,16 @@ public final class FloatToDecimal {
|
||||
if (0 < mq & mq < P) {
|
||||
int f = c >> mq;
|
||||
if (f << mq == c) {
|
||||
return toChars(f, 0);
|
||||
return toChars(str, index, f, 0) - start;
|
||||
}
|
||||
}
|
||||
return toDecimal(-mq, c, 0);
|
||||
return toDecimal(str, index, -mq, c, 0) - start;
|
||||
}
|
||||
if (t != 0) {
|
||||
/* subnormal value */
|
||||
return t < C_TINY
|
||||
? toDecimal(Q_MIN, 10 * t, -1)
|
||||
: toDecimal(Q_MIN, t, 0);
|
||||
return (t < C_TINY
|
||||
? toDecimal(str, index, Q_MIN, 10 * t, -1)
|
||||
: toDecimal(str, index, Q_MIN, t, 0)) - start;
|
||||
}
|
||||
return bits == 0 ? PLUS_ZERO : MINUS_ZERO;
|
||||
}
|
||||
@ -236,7 +215,7 @@ public final class FloatToDecimal {
|
||||
return bits > 0 ? PLUS_INF : MINUS_INF;
|
||||
}
|
||||
|
||||
private int toDecimal(int q, int c, int dk) {
|
||||
private int toDecimal(byte[] str, int index, int q, int c, int dk) {
|
||||
/*
|
||||
* The skeleton corresponds to figure 7 of [1].
|
||||
* The efficient computations are those summarized in figure 9.
|
||||
@ -300,7 +279,7 @@ public final class FloatToDecimal {
|
||||
boolean upin = vbl + out <= sp10 << 2;
|
||||
boolean wpin = (tp10 << 2) + out <= vbr;
|
||||
if (upin != wpin) {
|
||||
return toChars(upin ? sp10 : tp10, k);
|
||||
return toChars(str, index, upin ? sp10 : tp10, k);
|
||||
}
|
||||
}
|
||||
|
||||
@ -315,14 +294,14 @@ public final class FloatToDecimal {
|
||||
boolean win = (t << 2) + out <= vbr;
|
||||
if (uin != win) {
|
||||
/* Exactly one of u or w lies in Rv */
|
||||
return toChars(uin ? s : t, k + dk);
|
||||
return toChars(str, index, uin ? s : t, k + dk);
|
||||
}
|
||||
/*
|
||||
* Both u and w lie in Rv: determine the one closest to v.
|
||||
* See section 9.3 of [1].
|
||||
*/
|
||||
int cmp = vb - (s + t << 1);
|
||||
return toChars(cmp < 0 || cmp == 0 && (s & 0x1) == 0 ? s : t, k + dk);
|
||||
return toChars(str, index, cmp < 0 || cmp == 0 && (s & 0x1) == 0 ? s : t, k + dk);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -338,7 +317,7 @@ public final class FloatToDecimal {
|
||||
/*
|
||||
* Formats the decimal f 10^e.
|
||||
*/
|
||||
private int toChars(int f, int e) {
|
||||
private int toChars(byte[] str, int index, int f, int e) {
|
||||
/*
|
||||
* For details not discussed here see section 10 of [1].
|
||||
*
|
||||
@ -373,130 +352,74 @@ public final class FloatToDecimal {
|
||||
int l = f - 100_000_000 * h;
|
||||
|
||||
if (0 < e && e <= 7) {
|
||||
return toChars1(h, l, e);
|
||||
return toChars1(str, index, h, l, e);
|
||||
}
|
||||
if (-3 < e && e <= 0) {
|
||||
return toChars2(h, l, e);
|
||||
return toChars2(str, index, h, l, e);
|
||||
}
|
||||
return toChars3(h, l, e);
|
||||
return toChars3(str, index, h, l, e);
|
||||
}
|
||||
|
||||
private int toChars1(int h, int l, int e) {
|
||||
private int toChars1(byte[] str, int index, int h, int l, int e) {
|
||||
/*
|
||||
* 0 < e <= 7: plain format without leading zeroes.
|
||||
* Left-to-right digits extraction:
|
||||
* algorithm 1 in [3], with b = 10, k = 8, n = 28.
|
||||
*/
|
||||
appendDigit(h);
|
||||
index = putDigit(str, index, h);
|
||||
int y = y(l);
|
||||
int t;
|
||||
int i = 1;
|
||||
for (; i < e; ++i) {
|
||||
t = 10 * y;
|
||||
appendDigit(t >>> 28);
|
||||
index = putDigit(str, index, t >>> 28);
|
||||
y = t & MASK_28;
|
||||
}
|
||||
append('.');
|
||||
index = putChar(str, index, '.');
|
||||
for (; i <= 8; ++i) {
|
||||
t = 10 * y;
|
||||
appendDigit(t >>> 28);
|
||||
index = putDigit(str, index, t >>> 28);
|
||||
y = t & MASK_28;
|
||||
}
|
||||
removeTrailingZeroes();
|
||||
return NON_SPECIAL;
|
||||
return removeTrailingZeroes(str, index);
|
||||
}
|
||||
|
||||
private int toChars2(int h, int l, int e) {
|
||||
private int toChars2(byte[] str, int index, int h, int l, int e) {
|
||||
/* -3 < e <= 0: plain format with leading zeroes */
|
||||
appendDigit(0);
|
||||
append('.');
|
||||
index = putDigit(str, index, 0);
|
||||
index = putChar(str, index, '.');
|
||||
for (; e < 0; ++e) {
|
||||
appendDigit(0);
|
||||
index = putDigit(str, index, 0);
|
||||
}
|
||||
appendDigit(h);
|
||||
append8Digits(l);
|
||||
removeTrailingZeroes();
|
||||
return NON_SPECIAL;
|
||||
index = putDigit(str, index, h);
|
||||
index = put8Digits(str, index, l);
|
||||
return removeTrailingZeroes(str, index);
|
||||
}
|
||||
|
||||
private int toChars3(int h, int l, int e) {
|
||||
private int toChars3(byte[] str, int index, int h, int l, int e) {
|
||||
/* -3 >= e | e > 7: computerized scientific notation */
|
||||
appendDigit(h);
|
||||
append('.');
|
||||
append8Digits(l);
|
||||
removeTrailingZeroes();
|
||||
exponent(e - 1);
|
||||
return NON_SPECIAL;
|
||||
index = putDigit(str, index, h);
|
||||
index = putChar(str, index, '.');
|
||||
index = put8Digits(str, index, l);
|
||||
index = removeTrailingZeroes(str, index);
|
||||
return exponent(str, index, e - 1);
|
||||
}
|
||||
|
||||
private void append8Digits(int m) {
|
||||
/*
|
||||
* Left-to-right digits extraction:
|
||||
* algorithm 1 in [3], with b = 10, k = 8, n = 28.
|
||||
*/
|
||||
int y = y(m);
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
int t = 10 * y;
|
||||
appendDigit(t >>> 28);
|
||||
y = t & MASK_28;
|
||||
}
|
||||
}
|
||||
|
||||
private void removeTrailingZeroes() {
|
||||
while (bytes[index] == '0') {
|
||||
--index;
|
||||
}
|
||||
/* ... but do not remove the one directly to the right of '.' */
|
||||
if (bytes[index] == '.') {
|
||||
++index;
|
||||
}
|
||||
}
|
||||
|
||||
private int y(int a) {
|
||||
/*
|
||||
* Algorithm 1 in [3] needs computation of
|
||||
* floor((a + 1) 2^n / b^k) - 1
|
||||
* with a < 10^8, b = 10, k = 8, n = 28.
|
||||
* Noting that
|
||||
* (a + 1) 2^n <= 10^8 2^28 < 10^17
|
||||
* For n = 17, m = 8 the table in section 10 of [1] leads to:
|
||||
*/
|
||||
return (int) (multiplyHigh(
|
||||
(long) (a + 1) << 28,
|
||||
193_428_131_138_340_668L) >>> 20) - 1;
|
||||
}
|
||||
|
||||
private void exponent(int e) {
|
||||
append('E');
|
||||
private int exponent(byte[] str, int index, int e) {
|
||||
index = putChar(str, index, 'E');
|
||||
if (e < 0) {
|
||||
append('-');
|
||||
index = putChar(str, index, '-');
|
||||
e = -e;
|
||||
}
|
||||
if (e < 10) {
|
||||
appendDigit(e);
|
||||
return;
|
||||
return putDigit(str, index, e);
|
||||
}
|
||||
/*
|
||||
* For n = 2, m = 1 the table in section 10 of [1] shows
|
||||
* floor(e / 10) = floor(103 e / 2^10)
|
||||
*/
|
||||
int d = e * 103 >>> 10;
|
||||
appendDigit(d);
|
||||
appendDigit(e - 10 * d);
|
||||
index = putDigit(str, index, d);
|
||||
return putDigit(str, index, e - 10 * d);
|
||||
}
|
||||
|
||||
private void append(int c) {
|
||||
bytes[++index] = (byte) c;
|
||||
}
|
||||
|
||||
private void appendDigit(int d) {
|
||||
bytes[++index] = (byte) ('0' + d);
|
||||
}
|
||||
|
||||
/* Using the deprecated constructor enhances performance */
|
||||
@SuppressWarnings("deprecation")
|
||||
private String charsToString() {
|
||||
return new String(bytes, 0, 0, index + 1);
|
||||
}
|
||||
|
||||
}
|
||||
|
163
src/java.base/share/classes/jdk/internal/math/ToDecimal.java
Normal file
163
src/java.base/share/classes/jdk/internal/math/ToDecimal.java
Normal file
@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* 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. 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.math;
|
||||
|
||||
import jdk.internal.access.JavaLangAccess;
|
||||
import jdk.internal.access.SharedSecrets;
|
||||
|
||||
import static java.lang.Math.multiplyHigh;
|
||||
|
||||
abstract sealed class ToDecimal permits DoubleToDecimal, FloatToDecimal {
|
||||
private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
|
||||
|
||||
/* Used for left-to-tight digit extraction */
|
||||
static final int MASK_28 = (1 << 28) - 1;
|
||||
|
||||
static final int NON_SPECIAL = 0 << 8;
|
||||
static final int PLUS_ZERO = 1 << 8;
|
||||
static final int MINUS_ZERO = 2 << 8;
|
||||
static final int PLUS_INF = 3 << 8;
|
||||
static final int MINUS_INF = 4 << 8;
|
||||
static final int NAN = 5 << 8;
|
||||
|
||||
/**
|
||||
* The identifier of the encoding used to encode the bytes. If latin1 is true, the encoding is LATIN1, false is UTF16
|
||||
*
|
||||
*/
|
||||
private final boolean latin1;
|
||||
|
||||
ToDecimal(boolean latin1) {
|
||||
this.latin1 = latin1;
|
||||
}
|
||||
|
||||
final int putChar(byte[] str, int index, int c) {
|
||||
if (latin1) {
|
||||
str[index] = (byte) c;
|
||||
} else {
|
||||
JLA.putCharUTF16(str, index, (char) c);
|
||||
}
|
||||
return index + 1;
|
||||
}
|
||||
|
||||
final int putDigit(byte[] str, int index, int d) {
|
||||
return putChar(str, index, (byte) ('0' + d));
|
||||
}
|
||||
|
||||
final int put8Digits(byte[] str, int index, int m) {
|
||||
/*
|
||||
* Left-to-right digits extraction:
|
||||
* algorithm 1 in [3], with b = 10, k = 8, n = 28.
|
||||
*/
|
||||
if (latin1) {
|
||||
put8DigitsLatin1(str, index, m);
|
||||
} else {
|
||||
put8DigitsUTF16 (str, index, m);
|
||||
}
|
||||
return index + 8;
|
||||
}
|
||||
|
||||
private static void put8DigitsLatin1(byte[] str, int index, int m) {
|
||||
int y = y(m);
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
int t = 10 * y;
|
||||
str[index + i] = (byte) ('0' + (t >>> 28));
|
||||
y = t & MASK_28;
|
||||
}
|
||||
}
|
||||
|
||||
private static void put8DigitsUTF16(byte[] str, int index, int m) {
|
||||
int y = y(m);
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
int t = 10 * y;
|
||||
JLA.putCharUTF16(str, index + i, '0' + (t >>> 28));
|
||||
y = t & MASK_28;
|
||||
}
|
||||
}
|
||||
|
||||
static int y(int a) {
|
||||
/*
|
||||
* Algorithm 1 in [3] needs computation of
|
||||
* floor((a + 1) 2^n / b^k) - 1
|
||||
* with a < 10^8, b = 10, k = 8, n = 28.
|
||||
* Noting that
|
||||
* (a + 1) 2^n <= 10^8 2^28 < 10^17
|
||||
* For n = 17, m = 8 the table in section 10 of [1] leads to:
|
||||
*/
|
||||
return (int) (multiplyHigh(
|
||||
(long) (a + 1) << 28,
|
||||
193_428_131_138_340_668L) >>> 20) - 1;
|
||||
}
|
||||
|
||||
final int removeTrailingZeroes(byte[] str, int index) {
|
||||
if (latin1) {
|
||||
while (str[index - 1] == '0') {
|
||||
--index;
|
||||
}
|
||||
/* ... but do not remove the one directly to the right of '.' */
|
||||
if (str[index - 1] == '.') {
|
||||
++index;
|
||||
}
|
||||
} else {
|
||||
while (JLA.getUTF16Char(str, index - 1) == '0') {
|
||||
--index;
|
||||
}
|
||||
/* ... but do not remove the one directly to the right of '.' */
|
||||
if (JLA.getUTF16Char(str, index - 1) == '.') {
|
||||
++index;
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
final int putSpecial(byte[] str, int index, int type) {
|
||||
String s = special(type);
|
||||
int length = s.length();
|
||||
if (latin1) {
|
||||
s.getBytes(0, length, str, index);
|
||||
} else {
|
||||
for (int i = 0; i < length; ++i) {
|
||||
putChar(str, index + i, s.charAt(i));
|
||||
}
|
||||
}
|
||||
return index + length;
|
||||
}
|
||||
|
||||
final int length(byte[] str) {
|
||||
return str.length >> (latin1 ? 0 : 1);
|
||||
}
|
||||
|
||||
static String special(int type) {
|
||||
return switch (type) {
|
||||
case PLUS_ZERO -> "0.0";
|
||||
case MINUS_ZERO -> "-0.0";
|
||||
case PLUS_INF -> "Infinity";
|
||||
case MINUS_INF -> "-Infinity";
|
||||
default -> "NaN";
|
||||
};
|
||||
}
|
||||
}
|
@ -241,17 +241,66 @@ public class StringBuilders {
|
||||
|
||||
|
||||
@Benchmark
|
||||
public String toStringCharWithFloat8() {
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(113.110F);
|
||||
result.append(156456.36435637F);
|
||||
result.append(65436434.64632F);
|
||||
result.append(42654634.64540F);
|
||||
result.append(63464351.64537F);
|
||||
result.append(634564.645711F);
|
||||
result.append(64547.64311F);
|
||||
result.append(4763456341.64531F);
|
||||
return result.toString();
|
||||
public int appendWithFloat8Latin1() {
|
||||
StringBuilder buf = sbLatin1;
|
||||
buf.setLength(0);
|
||||
buf.append(113.110F);
|
||||
buf.append(156456.36435637F);
|
||||
buf.append(65436434.64632F);
|
||||
buf.append(42654634.64540F);
|
||||
buf.append(63464351.64537F);
|
||||
buf.append(634564.645711F);
|
||||
buf.append(64547.64311F);
|
||||
buf.append(4763456341.64531F);
|
||||
return buf.length();
|
||||
}
|
||||
|
||||
|
||||
@Benchmark
|
||||
public int appendWithFloat8Utf16() {
|
||||
StringBuilder buf = sbUtf16;
|
||||
buf.setLength(0);
|
||||
buf.append(113.110F);
|
||||
buf.append(156456.36435637F);
|
||||
buf.append(65436434.64632F);
|
||||
buf.append(42654634.64540F);
|
||||
buf.append(63464351.64537F);
|
||||
buf.append(634564.645711F);
|
||||
buf.append(64547.64311F);
|
||||
buf.append(4763456341.64531F);
|
||||
return buf.length();
|
||||
}
|
||||
|
||||
|
||||
@Benchmark
|
||||
public int appendWithDouble8Latin1() {
|
||||
StringBuilder buf = sbLatin1;
|
||||
buf.setLength(0);
|
||||
buf.append(0.3005216476500575D);
|
||||
buf.append(0.39727691577802204D);
|
||||
buf.append(0.9869700323149287D);
|
||||
buf.append(42654634.645403256D);
|
||||
buf.append(63464351.645371353D);
|
||||
buf.append(634564.645711246D);
|
||||
buf.append(64547.6431172363D);
|
||||
buf.append(4763456341.64531675D);
|
||||
return buf.length();
|
||||
}
|
||||
|
||||
|
||||
@Benchmark
|
||||
public int appendWithDouble8Utf16() {
|
||||
StringBuilder buf = sbUtf16;
|
||||
buf.setLength(0);
|
||||
buf.append(0.3005216476500575D);
|
||||
buf.append(0.39727691577802204D);
|
||||
buf.append(0.9869700323149287D);
|
||||
buf.append(42654634.645403256D);
|
||||
buf.append(63464351.645371353D);
|
||||
buf.append(634564.645711246D);
|
||||
buf.append(64547.6431172363D);
|
||||
buf.append(4763456341.64531675D);
|
||||
return buf.length();
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
|
Loading…
x
Reference in New Issue
Block a user