Skip to content

Commit 285adff

Browse files
committed
8362448: Make use of the Double.toString(double) algorithm in java.text.DecimalFormat
Reviewed-by: naoto, jlu
1 parent c9ecedd commit 285adff

File tree

7 files changed

+183
-50
lines changed

7 files changed

+183
-50
lines changed

src/java.base/share/classes/java/text/DecimalFormat.java

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -652,17 +652,10 @@ StringBuf format(double number, StringBuf result,
652652
return result;
653653
}
654654

655-
/* Detecting whether a double is negative is easy with the exception of
656-
* the value -0.0. This is a double which has a zero mantissa (and
657-
* exponent), but a negative sign bit. It is semantically distinct from
658-
* a zero with a positive sign bit, and this distinction is important
659-
* to certain kinds of computations. However, it's a little tricky to
660-
* detect, since (-0.0 == 0.0) and !(-0.0 < 0.0). How then, you may
661-
* ask, does it behave distinctly from +0.0? Well, 1/(-0.0) ==
662-
* -Infinity. Proper detection of -0.0 is needed to deal with the
655+
/* Proper detection of -0.0 is needed to deal with the
663656
* issues raised by bugs 4106658, 4106667, and 4147706. Liu 7/6/98.
664657
*/
665-
boolean isNegative = ((number < 0.0) || (number == 0.0 && 1/number < 0.0)) ^ (multiplier < 0);
658+
boolean isNegative = Double.doubleToRawLongBits(number) < 0 ^ multiplier < 0;
666659

667660
if (multiplier != 1) {
668661
number *= multiplier;

src/java.base/share/classes/java/text/DigitList.java

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,11 @@
5252
* DecimalFormat is that DigitList handles the radix 10 representation
5353
* issues; DecimalFormat handles the locale-specific issues such as
5454
* positive/negative, grouping, decimal point, currency, and so on.
55-
*
55+
* <p>
5656
* A DigitList is really a representation of a floating point value.
5757
* It may be an integer value; we assume that a double has sufficient
5858
* precision to represent all digits of a long.
59-
*
59+
* <p>
6060
* The DigitList representation consists of a string of characters,
6161
* which are the digits radix 10, from '0' to '9'. It also has a radix
6262
* 10 exponent associated with it. The value represented by a DigitList
@@ -82,22 +82,22 @@ final class DigitList implements Cloneable {
8282

8383
/**
8484
* These data members are intentionally public and can be set directly.
85-
*
85+
* <p>
8686
* The value represented is given by placing the decimal point before
8787
* digits[decimalAt]. If decimalAt is < 0, then leading zeros between
8888
* the decimal point and the first nonzero digit are implied. If decimalAt
8989
* is > count, then trailing zeros between the digits[count-1] and the
9090
* decimal point are implied.
91-
*
91+
* <p>
9292
* Equivalently, the represented value is given by f * 10^decimalAt. Here
9393
* f is a value 0.1 <= f < 1 arrived at by placing the digits in Digits to
9494
* the right of the decimal.
95-
*
95+
* <p>
9696
* DigitList is normalized, so if it is non-zero, digits[0] is non-zero. We
9797
* don't allow denormalized numbers because our exponent is effectively of
9898
* unlimited magnitude. The count value contains the number of significant
9999
* digits present in digits[].
100-
*
100+
* <p>
101101
* Zero is represented by any DigitList with count == 0 or with each digits[i]
102102
* for all i <= count == '0'.
103103
*/
@@ -166,7 +166,7 @@ public void append(char digit) {
166166
* If (count == 0) this returns 0.0,
167167
* unlike Double.parseDouble("") which throws NumberFormatException.
168168
*/
169-
public final double getDouble() {
169+
public double getDouble() {
170170
if (count == 0) {
171171
return 0.0;
172172
}
@@ -178,7 +178,7 @@ public final double getDouble() {
178178
* If (count == 0) this returns 0,
179179
* unlike Long.parseLong("") which throws NumberFormatException.
180180
*/
181-
public final long getLong() {
181+
public long getLong() {
182182
// for now, simple implementation; later, do proper IEEE native stuff
183183

184184
if (count == 0) {
@@ -208,7 +208,7 @@ public final long getLong() {
208208
* If (count == 0) this does not throw a NumberFormatException,
209209
* unlike BigDecimal("").
210210
*/
211-
public final BigDecimal getBigDecimal() {
211+
public BigDecimal getBigDecimal() {
212212
if (count == 0) {
213213
return BigDecimal.valueOf(0, -decimalAt);
214214
}
@@ -280,10 +280,26 @@ boolean fitsIntoLong(boolean isPositive, boolean ignoreNegativeZero) {
280280
* @param maximumFractionDigits The most fractional digits which should
281281
* be converted.
282282
*/
283-
final void set(boolean isNegative, double source, int maximumFractionDigits) {
283+
void set(boolean isNegative, double source, int maximumFractionDigits) {
284284
set(isNegative, source, maximumFractionDigits, true);
285285
}
286286

287+
/*
288+
* This compatibility option will only be available for a *very* limited
289+
* number of releases.
290+
* It restores the original behavior to help migrating to the new one,
291+
* and is used by adding
292+
* -Djdk.compat.DecimalFormat=true
293+
* to the launcher's command line.
294+
*
295+
* The new behavior differs from the old one only in very rare cases,
296+
* so migration should be painless.
297+
*
298+
* When this option is removed, the old behavior, including relevant
299+
* fields and methods, will be removed as well.
300+
*/
301+
private static final boolean COMPAT = Boolean.getBoolean("jdk.compat.DecimalFormat");
302+
287303
/**
288304
* Set the digit list to a representation of the given double value.
289305
* This method supports both fixed-point and exponential notation.
@@ -295,9 +311,9 @@ final void set(boolean isNegative, double source, int maximumFractionDigits) {
295311
* @param fixedPoint If true, then maximumDigits is the maximum
296312
* fractional digits to be converted. If false, total digits.
297313
*/
298-
final void set(boolean isNegative, double source, int maximumDigits, boolean fixedPoint) {
299-
300-
FloatingDecimal.BinaryToASCIIConverter fdConverter = FloatingDecimal.getBinaryToASCIIConverter(source);
314+
void set(boolean isNegative, double source, int maximumDigits, boolean fixedPoint) {
315+
FloatingDecimal.BinaryToASCIIConverter fdConverter =
316+
FloatingDecimal.getBinaryToASCIIConverter(source, COMPAT);
301317
boolean hasBeenRoundedUp = fdConverter.digitsRoundedUp();
302318
boolean valueExactAsDecimal = fdConverter.decimalDigitsExact();
303319
assert !fdConverter.isExceptional();
@@ -415,7 +431,7 @@ private void roundInt(int maximumDigits) {
415431
*
416432
* Upon return, count will be less than or equal to maximumDigits.
417433
*/
418-
private final void round(int maximumDigits,
434+
private void round(int maximumDigits,
419435
boolean alreadyRounded,
420436
boolean valueExactAsDecimal) {
421437
// Eliminate digits beyond maximum digits to be displayed.
@@ -582,7 +598,7 @@ private int roundUp(int maximumDigits) {
582598
/**
583599
* Utility routine to set the value of the digit list from a long
584600
*/
585-
final void set(boolean isNegative, long source) {
601+
void set(boolean isNegative, long source) {
586602
set(isNegative, source, 0);
587603
}
588604

@@ -595,7 +611,7 @@ final void set(boolean isNegative, long source) {
595611
* If maximumDigits is lower than the number of significant digits
596612
* in source, the representation will be rounded. Ignored if <= 0.
597613
*/
598-
final void set(boolean isNegative, long source, int maximumDigits) {
614+
void set(boolean isNegative, long source, int maximumDigits) {
599615
this.isNegative = isNegative;
600616

601617
// This method does not expect a negative number. However,
@@ -645,7 +661,7 @@ final void set(boolean isNegative, long source, int maximumDigits) {
645661
* @param fixedPoint If true, then maximumDigits is the maximum
646662
* fractional digits to be converted. If false, total digits.
647663
*/
648-
final void set(boolean isNegative, BigDecimal source, int maximumDigits, boolean fixedPoint) {
664+
void set(boolean isNegative, BigDecimal source, int maximumDigits, boolean fixedPoint) {
649665
String s = source.toString();
650666
extendDigits(s.length());
651667

@@ -662,7 +678,7 @@ final void set(boolean isNegative, BigDecimal source, int maximumDigits, boolean
662678
* If maximumDigits is lower than the number of significant digits
663679
* in source, the representation will be rounded. Ignored if <= 0.
664680
*/
665-
final void set(boolean isNegative, BigInteger source, int maximumDigits) {
681+
void set(boolean isNegative, BigInteger source, int maximumDigits) {
666682
this.isNegative = isNegative;
667683
String s = source.toString();
668684
int len = s.length();
@@ -772,7 +788,7 @@ private void extendDigits(int len) {
772788
}
773789
}
774790

775-
private final char[] getDataChars(int length) {
791+
private char[] getDataChars(int length) {
776792
if (data == null || data.length < length) {
777793
data = new char[length];
778794
}

src/java.base/share/classes/jdk/internal/math/DoubleToDecimal.java

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ private int toDecimal(byte[] str, int index, double v, FormattedFPDecimal fd) {
246246
if (0 < mq & mq < P) {
247247
long f = c >> mq;
248248
if (f << mq == c) {
249-
return toChars(str, index, f, 0, fd) - start;
249+
return toChars(str, index, f, 0, fd, true, false) - start;
250250
}
251251
}
252252
return toDecimal(str, index, -mq, c, 0, fd) - start;
@@ -324,13 +324,17 @@ private int toDecimal(byte[] str, int index, int q, long c, int dk, FormattedFPD
324324
* upin iff u' = sp10 10^k in Rv
325325
* wpin iff w' = tp10 10^k in Rv
326326
* See section 9.3 of [1].
327+
*
328+
* Also,
329+
* d_v = v iff 4 sp10 = vb
327330
*/
328331
long sp10 = 10 * multiplyHigh(s, 115_292_150_460_684_698L << 4);
329332
long tp10 = sp10 + 10;
330333
boolean upin = vbl + out <= sp10 << 2;
331334
boolean wpin = (tp10 << 2) + out <= vbr;
332335
if (upin != wpin) {
333-
return toChars(str, index, upin ? sp10 : tp10, k, fd);
336+
/* Exactly one of u' or w' lies in Rv */
337+
return toChars(str, index, upin ? sp10 : tp10, k, fd, sp10 << 2 == vb, wpin);
334338
}
335339
}
336340

@@ -339,20 +343,24 @@ private int toDecimal(byte[] str, int index, int q, long c, int dk, FormattedFPD
339343
* uin iff u = s 10^k in Rv
340344
* win iff w = t 10^k in Rv
341345
* See section 9.3 of [1].
346+
*
347+
* Also,
348+
* d_v = v iff 4 s = vb
342349
*/
343350
long t = s + 1;
344351
boolean uin = vbl + out <= s << 2;
345352
boolean win = (t << 2) + out <= vbr;
346353
if (uin != win) {
347354
/* Exactly one of u or w lies in Rv */
348-
return toChars(str, index, uin ? s : t, k + dk, fd);
355+
return toChars(str, index, uin ? s : t, k + dk, fd, s << 2 == vb, win);
349356
}
350357
/*
351358
* Both u and w lie in Rv: determine the one closest to v.
352359
* See section 9.3 of [1].
353360
*/
354361
long cmp = vb - (s + t << 1);
355-
return toChars(str, index, cmp < 0 || cmp == 0 && (s & 0x1) == 0 ? s : t, k + dk, fd);
362+
boolean away = cmp > 0 || cmp == 0 && (s & 0x1) != 0;
363+
return toChars(str, index, away ? t : s, k + dk, fd, s << 2 == vb, away);
356364
}
357365

358366
/*
@@ -371,7 +379,8 @@ private static long rop(long g1, long g0, long cp) {
371379
/*
372380
* Formats the decimal f 10^e.
373381
*/
374-
private int toChars(byte[] str, int index, long f, int e, FormattedFPDecimal fd) {
382+
private int toChars(byte[] str, int index, long f, int e,
383+
FormattedFPDecimal fd, boolean exact, boolean away) {
375384
/*
376385
* For details not discussed here see section 10 of [1].
377386
*
@@ -383,7 +392,7 @@ private int toChars(byte[] str, int index, long f, int e, FormattedFPDecimal fd)
383392
len += 1;
384393
}
385394
if (fd != null) {
386-
fd.set(f, e, len);
395+
fd.set(f, e, len, exact, away);
387396
return index;
388397
}
389398

src/java.base/share/classes/jdk/internal/math/FloatToDecimal.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@ private int toDecimal(byte[] str, int index, int q, int c, int dk) {
304304
boolean upin = vbl + out <= sp10 << 2;
305305
boolean wpin = (tp10 << 2) + out <= vbr;
306306
if (upin != wpin) {
307+
/* Exactly one of u' or w' lies in Rv */
307308
return toChars(str, index, upin ? sp10 : tp10, k);
308309
}
309310
}
@@ -326,7 +327,8 @@ private int toDecimal(byte[] str, int index, int q, int c, int dk) {
326327
* See section 9.3 of [1].
327328
*/
328329
int cmp = vb - (s + t << 1);
329-
return toChars(str, index, cmp < 0 || cmp == 0 && (s & 0x1) == 0 ? s : t, k + dk);
330+
boolean away = cmp > 0 || cmp == 0 && (s & 0x1) != 0;
331+
return toChars(str, index, away ? t : s, k + dk);
330332
}
331333

332334
/*

src/java.base/share/classes/jdk/internal/math/FloatingDecimal.java

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1742,22 +1742,44 @@ public float floatValue() {
17421742
* Returns a <code>BinaryToASCIIConverter</code> for a <code>double</code>.
17431743
* The returned object is a <code>ThreadLocal</code> variable of this class.
17441744
*
1745-
* @param d The double precision value to convert.
1745+
* @param d The double precision value to convert.
1746+
* @param compat compatibility with releases < JDK 21
17461747
* @return The converter.
17471748
*/
1748-
public static BinaryToASCIIConverter getBinaryToASCIIConverter(double d) {
1749-
return getBinaryToASCIIConverter(d, true);
1749+
public static BinaryToASCIIConverter getBinaryToASCIIConverter(double d, boolean compat) {
1750+
return compat
1751+
? getCompatBinaryToASCIIConverter(d, true)
1752+
: getBinaryToASCIIConverter(d);
17501753
}
17511754

1752-
/**
1753-
* Returns a <code>BinaryToASCIIConverter</code> for a <code>double</code>.
1754-
* The returned object is a <code>ThreadLocal</code> variable of this class.
1755-
*
1756-
* @param d The double precision value to convert.
1757-
* @param isCompatibleFormat
1758-
* @return The converter.
1755+
private static BinaryToASCIIConverter getBinaryToASCIIConverter(double d) {
1756+
assert Double.isFinite(d);
1757+
1758+
FormattedFPDecimal dec = FormattedFPDecimal.split(d);
1759+
BinaryToASCIIBuffer buf = getBinaryToASCIIBuffer();
1760+
1761+
buf.nDigits = dec.getPrecision();
1762+
buf.decExponent = dec.getExp() + buf.nDigits;
1763+
buf.firstDigitIndex = 0;
1764+
buf.exactDecimalConversion = dec.getExact();
1765+
buf.decimalDigitsRoundedUp = dec.getAway();
1766+
1767+
long f = dec.getSignificand();
1768+
char[] digits = buf.digits;
1769+
for (int i = buf.nDigits - 1; i >= 0; --i) {
1770+
long q = f / 10;
1771+
digits[i] = (char) ((f - 10 * q) + '0');
1772+
f = q;
1773+
}
1774+
return buf;
1775+
}
1776+
1777+
/*
1778+
* The old implementation of getBinaryToASCIIConverter().
1779+
* Should be removed in the future, along with its dependent methods and
1780+
* fields (> 550 lines).
17591781
*/
1760-
static BinaryToASCIIConverter getBinaryToASCIIConverter(double d, boolean isCompatibleFormat) {
1782+
private static BinaryToASCIIConverter getCompatBinaryToASCIIConverter(double d, boolean isCompatibleFormat) {
17611783
long dBits = Double.doubleToRawLongBits(d);
17621784
boolean isNegative = (dBits&DoubleConsts.SIGN_BIT_MASK) != 0; // discover sign
17631785
long fractBits = dBits & DoubleConsts.SIGNIF_BIT_MASK;

src/java.base/share/classes/jdk/internal/math/FormattedFPDecimal.java

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,16 @@ public final class FormattedFPDecimal {
4747
public static final char PLAIN = 'f';
4848
public static final char GENERAL = 'g';
4949

50-
private long f;
50+
/* Whether the decimal exactly represents the double */
51+
private boolean exact;
52+
/*
53+
* When not exact, whether the magnitude of the decimal is larger than
54+
* the magnitude of the double. Aka "away from zero".
55+
*/
56+
private boolean away;
5157
private int e; // normalized to 0 when f = 0
5258
private int n;
59+
private long f;
5360
private char[] digits; // ... and often the decimal separator as well
5461
private char[] exp; // [+-][e]ee, that is, sign and minimum 2 digits
5562

@@ -68,7 +75,7 @@ public static FormattedFPDecimal valueOf(double v, int prec, char form) {
6875
};
6976
}
7077

71-
private static FormattedFPDecimal split(double v) {
78+
static FormattedFPDecimal split(double v) {
7279
FormattedFPDecimal fd = new FormattedFPDecimal();
7380
DoubleToDecimal.split(v, fd);
7481
return fd;
@@ -119,7 +126,7 @@ public static FormattedFPDecimal valueForDoubleToString(double v) {
119126

120127
// Calculate new e based on updated precision
121128
final int eNew = expR - prec + 1; // expR is defined as prec + e - 1
122-
fd.set(s, eNew, prec);
129+
fd.set(s, eNew, prec, fd.exact, fd.away);
123130

124131
return fd;
125132
}
@@ -132,15 +139,25 @@ public int getPrecision() {
132139
return n;
133140
}
134141

142+
public boolean getAway() {
143+
return away;
144+
}
145+
146+
public boolean getExact() {
147+
return exact;
148+
}
149+
135150
public int getExp() {
136151
return e;
137152
}
138153

139-
public void set(long f, int e, int n) {
154+
public void set(long f, int e, int n, boolean exact, boolean away) {
140155
/* Initially, n = 0 if f = 0, and 10^{n-1} <= f < 10^n if f != 0 */
141156
this.f = f;
142157
this.e = e;
143158
this.n = n;
159+
this.exact = exact;
160+
this.away = away;
144161
}
145162

146163
public char[] getExponent() {

0 commit comments

Comments
 (0)