From deb7edb151fd5940fdf9fdb2a6356080ee94e222 Mon Sep 17 00:00:00 2001 From: Raffaello Giulietti Date: Mon, 3 Nov 2025 09:48:55 +0000 Subject: [PATCH] 8366017: Extend the set of inputs handled by fast paths in FloatingDecimal Reviewed-by: darcy --- .../share/classes/java/text/DigitList.java | 2 +- .../jdk/internal/math/FDBigInteger.java | 1232 ++++--------- .../jdk/internal/math/FloatingDecimal.java | 1561 +++++++---------- .../classes/jdk/internal/math/MathUtils.java | 24 +- test/jdk/java/lang/Double/ParseDouble.java | 41 +- test/jdk/java/lang/Float/ParseFloat.java | 28 +- .../FloatingDecimal/TestFDBigInteger.java | 410 +---- .../internal/math/FDBigIntegerChecker.java | 339 ++++ .../bench/java/lang/FloatingPointParse.java | 193 ++ 9 files changed, 1610 insertions(+), 2220 deletions(-) create mode 100644 test/jdk/jdk/internal/math/FloatingDecimal/java.base/jdk/internal/math/FDBigIntegerChecker.java create mode 100644 test/micro/org/openjdk/bench/java/lang/FloatingPointParse.java diff --git a/src/java.base/share/classes/java/text/DigitList.java b/src/java.base/share/classes/java/text/DigitList.java index 9ad1203fb62..140a5827860 100644 --- a/src/java.base/share/classes/java/text/DigitList.java +++ b/src/java.base/share/classes/java/text/DigitList.java @@ -322,7 +322,7 @@ final class DigitList implements Cloneable { void set(boolean isNegative, double source, int maximumDigits, boolean fixedPoint) { assert Double.isFinite(source); - FloatingDecimal.BinaryToASCIIConverter fdConverter = + FloatingDecimal.BinaryToASCIIBuffer fdConverter = FloatingDecimal.getBinaryToASCIIConverter(source, COMPAT); boolean hasBeenRoundedUp = fdConverter.digitsRoundedUp(); boolean valueExactAsDecimal = fdConverter.decimalDigitsExact(); diff --git a/src/java.base/share/classes/jdk/internal/math/FDBigInteger.java b/src/java.base/share/classes/jdk/internal/math/FDBigInteger.java index 90113a4a271..1ef9dee3a8a 100644 --- a/src/java.base/share/classes/jdk/internal/math/FDBigInteger.java +++ b/src/java.base/share/classes/jdk/internal/math/FDBigInteger.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 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 @@ -22,62 +22,31 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ + package jdk.internal.math; import jdk.internal.misc.CDS; +import jdk.internal.vm.annotation.Stable; -import java.math.BigInteger; import java.util.Arrays; -//@ model import org.jmlspecs.models.JMLMath; /** - * A simple big integer package specifically for floating point base conversion. + * A simple big integer class specifically for floating point base conversion. */ -public /*@ spec_bigint_math @*/ class FDBigInteger { - - // - // This class contains many comments that start with "/*@" mark. - // They are behavourial specification in - // the Java Modelling Language (JML): - // http://www.eecs.ucf.edu/~leavens/JML//index.shtml - // - - /*@ - @ public pure model static \bigint UNSIGNED(int v) { - @ return v >= 0 ? v : v + (((\bigint)1) << 32); - @ } - @ - @ public pure model static \bigint UNSIGNED(long v) { - @ return v >= 0 ? v : v + (((\bigint)1) << 64); - @ } - @ - @ public pure model static \bigint AP(int[] data, int len) { - @ return (\sum int i; 0 <= 0 && i < len; UNSIGNED(data[i]) << (i*32)); - @ } - @ - @ public pure model static \bigint pow52(int p5, int p2) { - @ ghost \bigint v = 1; - @ for (int i = 0; i < p5; i++) v *= 5; - @ return v << p2; - @ } - @ - @ public pure model static \bigint pow10(int p10) { - @ return pow52(p10, p10); - @ } - @*/ +final class FDBigInteger { + @Stable static final int[] SMALL_5_POW; + @Stable static final long[] LONG_5_POW; - // Maximum size of cache of powers of 5 as FDBigIntegers. + // Size of full cache of powers of 5 as FDBigIntegers. private static final int MAX_FIVE_POW = 340; // Cache of big powers of 5 as FDBigIntegers. - private static final FDBigInteger POW_5_CACHE[]; - - // Zero as an FDBigInteger. - public static final FDBigInteger ZERO; + @Stable + private static final FDBigInteger[] POW_5_CACHE; // Archive proxy private static Object[] archivedCaches; @@ -87,114 +56,63 @@ public /*@ spec_bigint_math @*/ class FDBigInteger { CDS.initializeFromArchive(FDBigInteger.class); Object[] caches = archivedCaches; if (caches == null) { - long[] long5pow = { - 1L, - 5L, - 5L * 5, - 5L * 5 * 5, - 5L * 5 * 5 * 5, - 5L * 5 * 5 * 5 * 5, - 5L * 5 * 5 * 5 * 5 * 5, - 5L * 5 * 5 * 5 * 5 * 5 * 5, - 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5, - 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, - 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, - 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, - 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, - 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, - 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, - 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, - 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, - 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, - 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, - 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, - 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, - 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, - 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, - 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, - 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, - 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, - 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, - }; - int[] small5pow = { - 1, - 5, - 5 * 5, - 5 * 5 * 5, - 5 * 5 * 5 * 5, - 5 * 5 * 5 * 5 * 5, - 5 * 5 * 5 * 5 * 5 * 5, - 5 * 5 * 5 * 5 * 5 * 5 * 5, - 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, - 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, - 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, - 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, - 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, - 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 - }; + int[] small5pow = new int[13 + 1]; // 5^13 fits in an int, 5^14 does not + small5pow[0] = 1; + for (int i = 1; i < small5pow.length; ++i) { + small5pow[i] = 5 * small5pow[i - 1]; + } + + long[] long5pow = new long[27 + 1]; // 5^27 fits in a long, 5^28 does not + long5pow[0] = 1; + for (int i = 1; i < long5pow.length; ++i) { + long5pow[i] = 5 * long5pow[i - 1]; + } + FDBigInteger[] pow5cache = new FDBigInteger[MAX_FIVE_POW]; int i = 0; - while (i < small5pow.length) { - FDBigInteger pow5 = new FDBigInteger(new int[] { small5pow[i] }, 0); - pow5.makeImmutable(); - pow5cache[i] = pow5; - i++; + for (; i < long5pow.length; ++i) { + pow5cache[i] = new FDBigInteger(long5pow[i]).makeImmutable(); } FDBigInteger prev = pow5cache[i - 1]; - while (i < MAX_FIVE_POW) { - pow5cache[i] = prev = prev.mult(5); - prev.makeImmutable(); - i++; + for (; i < MAX_FIVE_POW; ++i) { + pow5cache[i] = prev = prev.mult(5).makeImmutable(); } - FDBigInteger zero = new FDBigInteger(new int[0], 0); - zero.makeImmutable(); - archivedCaches = caches = new Object[] {small5pow, long5pow, pow5cache, zero}; + + /* Here prev is 5^(MAX_FIVE_POW-1). */ + FDBigInteger[] largePow5cache = + new FDBigInteger[(2 - DoubleToDecimal.Q_MIN) - MAX_FIVE_POW + 1]; + largePow5cache[2 * (MAX_FIVE_POW - 1) - MAX_FIVE_POW] = + prev = prev.mult(prev).makeImmutable(); + largePow5cache[3 * (MAX_FIVE_POW - 1) - MAX_FIVE_POW] = + pow5cache[MAX_FIVE_POW - 1].mult(prev).makeImmutable(); + archivedCaches = caches = new Object[] {small5pow, long5pow, pow5cache, largePow5cache}; } - SMALL_5_POW = (int[])caches[0]; - LONG_5_POW = (long[])caches[1]; - POW_5_CACHE = (FDBigInteger[])caches[2]; - ZERO = (FDBigInteger)caches[3]; + SMALL_5_POW = (int[]) caches[0]; + LONG_5_POW = (long[]) caches[1]; + POW_5_CACHE = (FDBigInteger[]) caches[2]; + LARGE_POW_5_CACHE = (FDBigInteger[]) caches[3]; } // Constant for casting an int to a long via bitwise AND. - private static final long LONG_MASK = 0xffffffffL; + private static final long LONG_MASK = 0xffff_ffffL; - //@ spec_public non_null; - private int data[]; // value: data[0] is least significant - //@ spec_public; + private int[] data; // value: data[0] is least significant private int offset; // number of least significant zero padding ints - //@ spec_public; private int nWords; // data[nWords-1]!=0, all values above are zero // if nWords==0 -> this FDBigInteger is zero - //@ spec_public; private boolean isImmutable = false; - /*@ - @ public invariant 0 <= nWords && nWords <= data.length && offset >= 0; - @ public invariant nWords == 0 ==> offset == 0; - @ public invariant nWords > 0 ==> data[nWords - 1] != 0; - @ public invariant (\forall int i; nWords <= i && i < data.length; data[i] == 0); - @ public pure model \bigint value() { - @ return AP(data, nWords) << (offset*32); - @ } - @*/ - /** - * Constructs an FDBigInteger from data and padding. The - * data parameter has the least significant int at - * the zeroth index. The offset parameter gives the number of - * zero ints to be inferred below the least significant element - * of data. + * Constructs an {@link FDBigInteger} from data and padding. The + * {@code data} parameter has the least significant {@code int} at + * the zeroth index. The {@code offset} parameter gives the number of + * zero {@code int}s to be inferred below the least significant element + * of {@code data}. * - * @param data An array containing all non-zero ints of the value. - * @param offset An offset indicating the number of zero ints to pad - * below the least significant element of data. + * @param data An array containing all non-zero {@code int}s of the value. + * @param offset An offset indicating the number of zero {@code int}s to pad + * below the least significant element of {@code data}. */ - /*@ - @ requires data != null && offset >= 0; - @ ensures this.value() == \old(AP(data, data.length) << (offset*32)); - @ ensures this.data == \old(data); - @*/ private FDBigInteger(int[] data, int offset) { this.data = data; this.offset = offset; @@ -203,198 +121,156 @@ public /*@ spec_bigint_math @*/ class FDBigInteger { } /** - * Constructs an FDBigInteger from a starting value and some + * Constructs an {@link FDBigInteger} from a starting value and some * decimal digits. * * @param lValue The starting value. * @param digits The decimal digits. - * @param kDigits The initial index into digits. - * @param nDigits The final index into digits. + * @param i The initial index into {@code digits}. + * @param nDigits The final index into {@code digits}. */ - /*@ - @ requires digits != null; - @ requires 0 <= kDigits && kDigits <= nDigits && nDigits <= digits.length; - @ requires (\forall int i; 0 <= i && i < nDigits; '0' <= digits[i] && digits[i] <= '9'); - @ ensures this.value() == \old(lValue * pow10(nDigits - kDigits) + (\sum int i; kDigits <= i && i < nDigits; (digits[i] - '0') * pow10(nDigits - i - 1))); - @*/ - public FDBigInteger(long lValue, byte[] digits, int kDigits, int nDigits) { - int n = Math.max((nDigits + 8) / 9, 2); // estimate size needed. - data = new int[n]; // allocate enough space - data[0] = (int) lValue; // starting value + public FDBigInteger(long lValue, byte[] digits, int i, int nDigits) { + int n = (nDigits + 8) / 9; // estimate size needed: ⌈nDigits / 9⌉ + data = new int[Math.max(n, 2)]; + data[0] = (int) lValue; // starting value data[1] = (int) (lValue >>> 32); offset = 0; nWords = 2; - int i = kDigits; - int limit = nDigits - 5; // slurp digits 5 at a time. - int v; + int limit = nDigits - 9; while (i < limit) { - int ilim = i + 5; - v = (int) digits[i++] - (int) '0'; + int v = 0; + int ilim = i + 9; while (i < ilim) { - v = 10 * v + (int) digits[i++] - (int) '0'; + v = 10 * v + digits[i++] - '0'; } - multAddMe(100000, v); // ... where 100000 is 10^5. + multAdd(1_000_000_000, v); // 10^9 } - int factor = 1; - v = 0; - while (i < nDigits) { - v = 10 * v + (int) digits[i++] - (int) '0'; - factor *= 10; - } - if (factor != 1) { - multAddMe(factor, v); + if (i < nDigits) { + int factor = (int) MathUtils.pow10(nDigits - i); + int v = 0; + while (i < nDigits) { + v = 10 * v + digits[i++] - '0'; + } + multAdd(factor, v); } trimLeadingZeros(); } - /** - * Returns an FDBigInteger with the numerical value - * 5p5 * 2p2. - * - * @param p5 The exponent of the power-of-five factor. - * @param p2 The exponent of the power-of-two factor. - * @return 5p5 * 2p2 - */ - /*@ - @ requires p5 >= 0 && p2 >= 0; - @ assignable \nothing; - @ ensures \result.value() == \old(pow52(p5, p2)); - @*/ - public static FDBigInteger valueOfPow52(int p5, int p2) { - if (p5 != 0) { - if (p2 == 0) { - return big5pow(p5); - } else if (p5 < SMALL_5_POW.length) { - int pow5 = SMALL_5_POW[p5]; - int wordcount = p2 >> 5; - int bitcount = p2 & 0x1f; - if (bitcount == 0) { - return new FDBigInteger(new int[]{pow5}, wordcount); - } else { - return new FDBigInteger(new int[]{ - pow5 << bitcount, - pow5 >>> (32 - bitcount) - }, wordcount); - } - } else { - return big5pow(p5).leftShift(p2); - } - } else { - return valueOfPow2(p2); - } + public FDBigInteger(long v) { + this(new int[] {(int) v, (int) (v >>> 32)}, 0); } /** - * Returns an FDBigInteger with the numerical value - * value * 5p5 * 2p2. + * Returns an {@link FDBigInteger} with the numerical value + * 5{@code e5} * 2{@code e2}. + * + * @param e5 The exponent of the power-of-five factor. + * @param e2 The exponent of the power-of-two factor. + * @return 5{@code e5} * 2{@code e2} + */ + public static FDBigInteger valueOfPow52(int e5, int e2) { + if (e5 == 0) { + return valueOfPow2(e2); + } + if (e2 == 0) { + return pow5(e5); + } + if (e5 >= SMALL_5_POW.length) { + return pow5(e5).leftShift(e2); + } + int pow5 = SMALL_5_POW[e5]; + int offset = e2 >> 5; + int bitcount = e2 & 0x1f; + if (bitcount == 0) { + return new FDBigInteger(new int[] {pow5}, offset); + } + return new FDBigInteger(new int[] { + pow5 << bitcount, + pow5 >>> -bitcount + }, offset); + } + + /** + * Returns an {@link FDBigInteger} with the numerical value: + * value * 5{@code e5} * 2{@code e2}. * * @param value The constant factor. - * @param p5 The exponent of the power-of-five factor. - * @param p2 The exponent of the power-of-two factor. - * @return value * 5p5 * 2p2 + * @param e5 The exponent of the power-of-five factor. + * @param e2 The exponent of the power-of-two factor. + * @return value * 5{@code e5} * 2{@code e2} */ - /*@ - @ requires p5 >= 0 && p2 >= 0; - @ assignable \nothing; - @ ensures \result.value() == \old(UNSIGNED(value) * pow52(p5, p2)); - @*/ - public static FDBigInteger valueOfMulPow52(long value, int p5, int p2) { - assert p5 >= 0 : p5; - assert p2 >= 0 : p2; + public static FDBigInteger valueOfMulPow52(long value, int e5, int e2) { int v0 = (int) value; int v1 = (int) (value >>> 32); - int wordcount = p2 >> 5; - int bitcount = p2 & 0x1f; - if (p5 != 0) { - if (p5 < SMALL_5_POW.length) { - long pow5 = SMALL_5_POW[p5] & LONG_MASK; + int offset = e2 >> 5; + int bitcount = e2 & 0x1f; + if (e5 != 0) { + if (e5 < SMALL_5_POW.length) { + long pow5 = SMALL_5_POW[e5] & LONG_MASK; long carry = (v0 & LONG_MASK) * pow5; v0 = (int) carry; carry >>>= 32; carry = (v1 & LONG_MASK) * pow5 + carry; v1 = (int) carry; int v2 = (int) (carry >>> 32); - if (bitcount == 0) { - return new FDBigInteger(new int[]{v0, v1, v2}, wordcount); - } else { - return new FDBigInteger(new int[]{ - v0 << bitcount, - (v1 << bitcount) | (v0 >>> (32 - bitcount)), - (v2 << bitcount) | (v1 >>> (32 - bitcount)), - v2 >>> (32 - bitcount) - }, wordcount); - } - } else { - FDBigInteger pow5 = big5pow(p5); - int[] r; - if (v1 == 0) { - r = new int[pow5.nWords + 1 + ((p2 != 0) ? 1 : 0)]; - mult(pow5.data, pow5.nWords, v0, r); - } else { - r = new int[pow5.nWords + 2 + ((p2 != 0) ? 1 : 0)]; - mult(pow5.data, pow5.nWords, v0, v1, r); - } - return (new FDBigInteger(r, pow5.offset)).leftShift(p2); + return bitcount == 0 + ? new FDBigInteger(new int[] {v0, v1, v2}, offset) + : new FDBigInteger(new int[] { + v0 << bitcount, + (v1 << bitcount) | (v0 >>> -bitcount), + (v2 << bitcount) | (v1 >>> -bitcount), + v2 >>> -bitcount + }, offset); } - } else if (p2 != 0) { - if (bitcount == 0) { - return new FDBigInteger(new int[]{v0, v1}, wordcount); + FDBigInteger pow5 = pow5(e5); + int[] r; + if (v1 == 0) { + r = new int[pow5.nWords + 1 + ((e2 != 0) ? 1 : 0)]; + mult(pow5.data, pow5.nWords, v0, r); } else { - return new FDBigInteger(new int[]{ - v0 << bitcount, - (v1 << bitcount) | (v0 >>> (32 - bitcount)), - v1 >>> (32 - bitcount) - }, wordcount); + r = new int[pow5.nWords + 2 + ((e2 != 0) ? 1 : 0)]; + mult(pow5.data, pow5.nWords, v0, v1, r); } + return (new FDBigInteger(r, 0)).leftShift(e2); } - return new FDBigInteger(new int[]{v0, v1}, 0); + if (e2 != 0) { + return bitcount == 0 + ? new FDBigInteger(new int[] {v0, v1}, offset) + : new FDBigInteger(new int[] { + v0 << bitcount, + (v1 << bitcount) | (v0 >>> -bitcount), + v1 >>> -bitcount + }, offset); + } + return new FDBigInteger(new int[] {v0, v1}, 0); } /** - * Returns an FDBigInteger with the numerical value - * 2p2. + * Returns an {@link FDBigInteger} with the numerical value + * 2{@code e}. * - * @param p2 The exponent of 2. - * @return 2p2 + * @param e The exponent of 2. + * @return 2{@code e} */ - /*@ - @ requires p2 >= 0; - @ assignable \nothing; - @ ensures \result.value() == pow52(0, p2); - @*/ - private static FDBigInteger valueOfPow2(int p2) { - int wordcount = p2 >> 5; - int bitcount = p2 & 0x1f; - return new FDBigInteger(new int[]{1 << bitcount}, wordcount); + private static FDBigInteger valueOfPow2(int e) { + return new FDBigInteger(new int[] {1 << (e & 0x1f)}, e >> 5); } /** - * Removes all leading zeros from this FDBigInteger adjusting + * Removes all leading zeros from this {@link FDBigInteger} adjusting * the offset and number of non-zero leading words accordingly. */ - /*@ - @ requires data != null; - @ requires 0 <= nWords && nWords <= data.length && offset >= 0; - @ requires nWords == 0 ==> offset == 0; - @ ensures nWords == 0 ==> offset == 0; - @ ensures nWords > 0 ==> data[nWords - 1] != 0; - @*/ - private /*@ helper @*/ void trimLeadingZeros() { - int i = nWords; - if (i > 0 && (data[--i] == 0)) { - //for (; i > 0 && data[i - 1] == 0; i--) ; - while(i > 0 && data[i - 1] == 0) { - i--; - } - this.nWords = i; - if (i == 0) { // all words are zero - this.offset = 0; - } + private void trimLeadingZeros() { + int i = nWords - 1; + for (; i >= 0 && data[i] == 0; --i); // empty body + nWords = i + 1; + if (i < 0) { + offset = 0; } } /** - * Retrieves the normalization bias of the FDBigIntger. The + * Retrieves the normalization bias of the {@link FDBigInteger}. The * normalization bias is a left shift such that after it the highest word * of the value will have the 4 highest bits equal to zero: * {@code (highestWord & 0xf0000000) == 0}, but the next bit should be 1 @@ -402,10 +278,7 @@ public /*@ spec_bigint_math @*/ class FDBigInteger { * * @return The normalization bias. */ - /*@ - @ requires this.value() > 0; - @*/ - public /*@ pure @*/ int getNormalizationBias() { + public int getNormalizationBias() { if (nWords == 0) { throw new IllegalArgumentException("Zero value cannot be normalized"); } @@ -413,7 +286,6 @@ public /*@ spec_bigint_math @*/ class FDBigInteger { return (zeros < 4) ? 28 + zeros : zeros - 4; } - // TODO: Why is anticount param needed if it is always 32 - bitcount? /** * Left shifts the contents of one int array into another. * @@ -421,20 +293,13 @@ public /*@ spec_bigint_math @*/ class FDBigInteger { * @param idx The initial index of the source array. * @param result The destination array. * @param bitcount The left shift. - * @param anticount The left anti-shift, e.g., 32-bitcount. * @param prev The prior source value. */ - /*@ - @ requires 0 < bitcount && bitcount < 32 && anticount == 32 - bitcount; - @ requires src.length >= idx && result.length > idx; - @ assignable result[*]; - @ ensures AP(result, \old(idx + 1)) == \old((AP(src, idx) + UNSIGNED(prev) << (idx*32)) << bitcount); - @*/ - private static void leftShift(int[] src, int idx, int result[], int bitcount, int anticount, int prev){ + private static void leftShift(int[] src, int idx, int[] result, int bitcount, int prev) { for (; idx > 0; idx--) { - int v = (prev << bitcount); + int v = prev << bitcount; prev = src[idx - 1]; - v |= (prev >>> anticount); + v |= prev >>> -bitcount; result[idx] = v; } int v = prev << bitcount; @@ -442,111 +307,88 @@ public /*@ spec_bigint_math @*/ class FDBigInteger { } /** - * Shifts this FDBigInteger to the left. The shift is performed - * in-place unless the FDBigInteger is immutable in which case - * a new instance of FDBigInteger is returned. + * Shifts this {@link FDBigInteger} to the left. The shift is performed + * in-place unless the {@link FDBigInteger} is immutable in which case + * a new instance of {@link FDBigInteger} is returned. * * @param shift The number of bits to shift left. - * @return The shifted FDBigInteger. + * @return The shifted {@link FDBigInteger}. */ - /*@ - @ requires this.value() == 0 || shift == 0; - @ assignable \nothing; - @ ensures \result == this; - @ - @ also - @ - @ requires this.value() > 0 && shift > 0 && this.isImmutable; - @ assignable \nothing; - @ ensures \result.value() == \old(this.value() << shift); - @ - @ also - @ - @ requires this.value() > 0 && shift > 0 && this.isImmutable; - @ assignable \nothing; - @ ensures \result == this; - @ ensures \result.value() == \old(this.value() << shift); - @*/ public FDBigInteger leftShift(int shift) { if (shift == 0 || nWords == 0) { return this; } int wordcount = shift >> 5; int bitcount = shift & 0x1f; - if (this.isImmutable) { + if (isImmutable) { if (bitcount == 0) { return new FDBigInteger(Arrays.copyOf(data, nWords), offset + wordcount); + } + int idx = nWords - 1; + int prev = data[idx]; + int hi = prev >>> -bitcount; + int[] result; + if (hi != 0) { + result = new int[nWords + 1]; + result[nWords] = hi; + } else { + result = new int[nWords]; + } + leftShift(data, idx, result, bitcount, prev); + return new FDBigInteger(result, offset + wordcount); + } + if (bitcount != 0) { + if (data[0] << bitcount == 0) { + int idx = 0; + int prev = data[idx]; + for (; idx < nWords - 1; idx++) { + int v = prev >>> -bitcount; + prev = data[idx + 1]; + v |= prev << bitcount; + data[idx] = v; + } + int v = prev >>> -bitcount; + data[idx] = v; + if (v == 0) { + nWords--; + } + offset++; } else { - int anticount = 32 - bitcount; int idx = nWords - 1; int prev = data[idx]; - int hi = prev >>> anticount; - int[] result; + int hi = prev >>> -bitcount; + int[] result = data; + int[] src = data; if (hi != 0) { - result = new int[nWords + 1]; - result[nWords] = hi; - } else { - result = new int[nWords]; + if (nWords == data.length) { + data = result = new int[nWords + 1]; + } + result[nWords++] = hi; } - leftShift(data,idx,result,bitcount,anticount,prev); - return new FDBigInteger(result, offset + wordcount); + leftShift(src, idx, result, bitcount, prev); } - } else { - if (bitcount != 0) { - int anticount = 32 - bitcount; - if ((data[0] << bitcount) == 0) { - int idx = 0; - int prev = data[idx]; - for (; idx < nWords - 1; idx++) { - int v = (prev >>> anticount); - prev = data[idx + 1]; - v |= (prev << bitcount); - data[idx] = v; - } - int v = prev >>> anticount; - data[idx] = v; - if(v==0) { - nWords--; - } - offset++; - } else { - int idx = nWords - 1; - int prev = data[idx]; - int hi = prev >>> anticount; - int[] result = data; - int[] src = data; - if (hi != 0) { - if(nWords == data.length) { - data = result = new int[nWords + 1]; - } - result[nWords++] = hi; - } - leftShift(src,idx,result,bitcount,anticount,prev); - } - } - offset += wordcount; - return this; } + offset += wordcount; + return this; } /** - * Returns the number of ints this FDBigInteger represents. + * Returns the number of {@code int}s this {@link FDBigInteger} represents. * - * @return Number of ints required to represent this FDBigInteger. + * @return Number of {@code int}s required to represent this {@link FDBigInteger}. */ - /*@ - @ requires this.value() == 0; - @ ensures \result == 0; - @ - @ also - @ - @ requires this.value() > 0; - @ ensures ((\bigint)1) << (\result - 1) <= this.value() && this.value() <= ((\bigint)1) << \result; - @*/ - private /*@ pure @*/ int size() { + private int size() { return nWords + offset; } + /** + * Returns whether this {@link FDBigInteger} is zero. + * + * @return {@code this} is zero. + */ + public boolean isZero() { + return nWords == 0; + } /** * Computes @@ -561,18 +403,9 @@ public /*@ spec_bigint_math @*/ class FDBigInteger { * Also assumed, of course, is that the result, q, can be expressed * as an integer, {@code 0 <= q < 10}. * - * @param S The divisor of this FDBigInteger. - * @return q = (int)(this / S). + * @param S The divisor of this {@link FDBigInteger}. + * @return {@code q = (int)(this / S)}. */ - /*@ - @ requires !this.isImmutable; - @ requires this.size() <= S.size(); - @ requires this.data.length + this.offset >= S.size(); - @ requires S.value() >= ((\bigint)1) << (S.size()*32 - 4); - @ assignable this.nWords, this.offset, this.data, this.data[*]; - @ ensures \result == \old(this.value() / S.value()); - @ ensures this.value() == \old(10 * (this.value() % S.value())); - @*/ public int quoRemIteration(FDBigInteger S) throws IllegalArgumentException { assert !this.isImmutable : "cannot modify immutable value"; // ensure that this and S have the same number of @@ -597,7 +430,7 @@ public /*@ spec_bigint_math @*/ class FDBigInteger { // right. If not, then we're only off by a little and // will re-add. long q = (this.data[this.nWords - 1] & LONG_MASK) / (S.data[S.nWords - 1] & LONG_MASK); - long diff = multDiffMe(q, S); + long diff = multSub(q, S); if (diff != 0L) { //@ assert q != 0; //@ assert this.offset == \old(Math.min(this.offset, S.offset)); @@ -639,30 +472,12 @@ public /*@ spec_bigint_math @*/ class FDBigInteger { } /** - * Multiplies this FDBigInteger by 10. The operation will be - * performed in place unless the FDBigInteger is immutable in - * which case a new FDBigInteger will be returned. + * Multiplies this {@link FDBigInteger} by 10. The operation will be + * performed in place unless the {@link FDBigInteger} is immutable in + * which case a new {@link FDBigInteger} will be returned. * - * @return The FDBigInteger multiplied by 10. + * @return The {@link FDBigInteger} multiplied by 10. */ - /*@ - @ requires this.value() == 0; - @ assignable \nothing; - @ ensures \result == this; - @ - @ also - @ - @ requires this.value() > 0 && this.isImmutable; - @ assignable \nothing; - @ ensures \result.value() == \old(this.value() * 10); - @ - @ also - @ - @ requires this.value() > 0 && !this.isImmutable; - @ assignable this.nWords, this.data, this.data[*]; - @ ensures \result == this; - @ ensures \result.value() == \old(this.value() * 10); - @*/ public FDBigInteger multBy10() { if (nWords == 0) { return this; @@ -691,71 +506,55 @@ public /*@ spec_bigint_math @*/ class FDBigInteger { } /** - * Multiplies this FDBigInteger by - * 5p5 * 2p2. The operation will be - * performed in place if possible, otherwise a new FDBigInteger + * Multiplies this {@link FDBigInteger} by + * 5{@code e5} * 2{@code e2}. The operation will be + * performed in place if possible, otherwise a new {@link FDBigInteger} * will be returned. * - * @param p5 The exponent of the power-of-five factor. - * @param p2 The exponent of the power-of-two factor. + * @param e5 The exponent of the power-of-five factor. + * @param e2 The exponent of the power-of-two factor. * @return The multiplication result. */ - /*@ - @ requires this.value() == 0 || p5 == 0 && p2 == 0; - @ assignable \nothing; - @ ensures \result == this; - @ - @ also - @ - @ requires this.value() > 0 && (p5 > 0 && p2 >= 0 || p5 == 0 && p2 > 0 && this.isImmutable); - @ assignable \nothing; - @ ensures \result.value() == \old(this.value() * pow52(p5, p2)); - @ - @ also - @ - @ requires this.value() > 0 && p5 == 0 && p2 > 0 && !this.isImmutable; - @ assignable this.nWords, this.data, this.data[*]; - @ ensures \result == this; - @ ensures \result.value() == \old(this.value() * pow52(p5, p2)); - @*/ - public FDBigInteger multByPow52(int p5, int p2) { - if (this.nWords == 0) { + public FDBigInteger multByPow52(int e5, int e2) { + if (nWords == 0) { return this; } FDBigInteger res = this; - if (p5 != 0) { + if (e5 != 0) { int[] r; - int extraSize = (p2 != 0) ? 1 : 0; - if (p5 < SMALL_5_POW.length) { - r = new int[this.nWords + 1 + extraSize]; - mult(this.data, this.nWords, SMALL_5_POW[p5], r); - res = new FDBigInteger(r, this.offset); + int extraSize = e2 != 0 ? 1 : 0; // accounts for e2 % 32 shift bits + if (e5 < SMALL_5_POW.length) { + r = new int[nWords + 1 + extraSize]; + mult(data, nWords, SMALL_5_POW[e5], r); + } else if (e5 < LONG_5_POW.length) { + long pow5 = LONG_5_POW[e5]; + r = new int[nWords + 2 + extraSize]; + mult(data, nWords, (int) pow5, (int) (pow5 >>> 32), r); } else { - FDBigInteger pow5 = big5pow(p5); - r = new int[this.nWords + pow5.size() + extraSize]; - mult(this.data, this.nWords, pow5.data, pow5.nWords, r); - res = new FDBigInteger(r, this.offset + pow5.offset); + FDBigInteger pow5 = pow5(e5); + r = new int[nWords + pow5.nWords + extraSize]; + mult(data, nWords, pow5.data, pow5.nWords, r); } + res = new FDBigInteger(r, offset); } - return res.leftShift(p2); + return res.leftShift(e2); } /** * Multiplies two big integers represented as int arrays. * * @param s1 The first array factor. - * @param s1Len The number of elements of s1 to use. + * @param s1Len The number of elements of {@code s1} to use. * @param s2 The second array factor. - * @param s2Len The number of elements of s2 to use. + * @param s2Len The number of elements of {@code s2} to use. * @param dst The product array. */ - /*@ - @ requires s1 != dst && s2 != dst; - @ requires s1.length >= s1Len && s2.length >= s2Len && dst.length >= s1Len + s2Len; - @ assignable dst[0 .. s1Len + s2Len - 1]; - @ ensures AP(dst, s1Len + s2Len) == \old(AP(s1, s1Len) * AP(s2, s2Len)); - @*/ private static void mult(int[] s1, int s1Len, int[] s2, int s2Len, int[] dst) { + if (s1Len > s2Len) { + /* Swap ensures that inner loop is longest. */ + int l = s1Len; s1Len = s2Len; s2Len = l; + int[] s = s1; s1 = s2; s2 = s; + } for (int i = 0; i < s1Len; i++) { long v = s1[i] & LONG_MASK; long p = 0L; @@ -768,158 +567,6 @@ public /*@ spec_bigint_math @*/ class FDBigInteger { } } - /** - * Subtracts the supplied FDBigInteger subtrahend from this - * FDBigInteger. Assert that the result is positive. - * If the subtrahend is immutable, store the result in this(minuend). - * If this(minuend) is immutable a new FDBigInteger is created. - * - * @param subtrahend The FDBigInteger to be subtracted. - * @return This FDBigInteger less the subtrahend. - */ - /*@ - @ requires this.isImmutable; - @ requires this.value() >= subtrahend.value(); - @ assignable \nothing; - @ ensures \result.value() == \old(this.value() - subtrahend.value()); - @ - @ also - @ - @ requires !subtrahend.isImmutable; - @ requires this.value() >= subtrahend.value(); - @ assignable this.nWords, this.offset, this.data, this.data[*]; - @ ensures \result == this; - @ ensures \result.value() == \old(this.value() - subtrahend.value()); - @*/ - public FDBigInteger leftInplaceSub(FDBigInteger subtrahend) { - assert this.size() >= subtrahend.size() : "result should be positive"; - FDBigInteger minuend; - if (this.isImmutable) { - minuend = new FDBigInteger(this.data.clone(), this.offset); - } else { - minuend = this; - } - int offsetDiff = subtrahend.offset - minuend.offset; - int[] sData = subtrahend.data; - int[] mData = minuend.data; - int subLen = subtrahend.nWords; - int minLen = minuend.nWords; - if (offsetDiff < 0) { - // need to expand minuend - int rLen = minLen - offsetDiff; - if (rLen < mData.length) { - System.arraycopy(mData, 0, mData, -offsetDiff, minLen); - Arrays.fill(mData, 0, -offsetDiff, 0); - } else { - int[] r = new int[rLen]; - System.arraycopy(mData, 0, r, -offsetDiff, minLen); - minuend.data = mData = r; - } - minuend.offset = subtrahend.offset; - minuend.nWords = minLen = rLen; - offsetDiff = 0; - } - long borrow = 0L; - int mIndex = offsetDiff; - for (int sIndex = 0; sIndex < subLen && mIndex < minLen; sIndex++, mIndex++) { - long diff = (mData[mIndex] & LONG_MASK) - (sData[sIndex] & LONG_MASK) + borrow; - mData[mIndex] = (int) diff; - borrow = diff >> 32; // signed shift - } - for (; borrow != 0 && mIndex < minLen; mIndex++) { - long diff = (mData[mIndex] & LONG_MASK) + borrow; - mData[mIndex] = (int) diff; - borrow = diff >> 32; // signed shift - } - assert borrow == 0L : borrow; // borrow out of subtract, - // result should be positive - minuend.trimLeadingZeros(); - return minuend; - } - - /** - * Subtracts the supplied FDBigInteger subtrahend from this - * FDBigInteger. Assert that the result is positive. - * If the this(minuend) is immutable, store the result in subtrahend. - * If subtrahend is immutable a new FDBigInteger is created. - * - * @param subtrahend The FDBigInteger to be subtracted. - * @return This FDBigInteger less the subtrahend. - */ - /*@ - @ requires subtrahend.isImmutable; - @ requires this.value() >= subtrahend.value(); - @ assignable \nothing; - @ ensures \result.value() == \old(this.value() - subtrahend.value()); - @ - @ also - @ - @ requires !subtrahend.isImmutable; - @ requires this.value() >= subtrahend.value(); - @ assignable subtrahend.nWords, subtrahend.offset, subtrahend.data, subtrahend.data[*]; - @ ensures \result == subtrahend; - @ ensures \result.value() == \old(this.value() - subtrahend.value()); - @*/ - public FDBigInteger rightInplaceSub(FDBigInteger subtrahend) { - assert this.size() >= subtrahend.size() : "result should be positive"; - FDBigInteger minuend = this; - if (subtrahend.isImmutable) { - subtrahend = new FDBigInteger(subtrahend.data.clone(), subtrahend.offset); - } - int offsetDiff = minuend.offset - subtrahend.offset; - int[] sData = subtrahend.data; - int[] mData = minuend.data; - int subLen = subtrahend.nWords; - int minLen = minuend.nWords; - if (offsetDiff < 0) { - int rLen = minLen; - if (rLen < sData.length) { - System.arraycopy(sData, 0, sData, -offsetDiff, subLen); - Arrays.fill(sData, 0, -offsetDiff, 0); - } else { - int[] r = new int[rLen]; - System.arraycopy(sData, 0, r, -offsetDiff, subLen); - subtrahend.data = sData = r; - } - subtrahend.offset = minuend.offset; - subLen -= offsetDiff; - offsetDiff = 0; - } else { - int rLen = minLen + offsetDiff; - if (rLen >= sData.length) { - subtrahend.data = sData = Arrays.copyOf(sData, rLen); - } - } - //@ assert minuend == this && minuend.value() == \old(this.value()); - //@ assert mData == minuend.data && minLen == minuend.nWords; - //@ assert subtrahend.offset + subtrahend.data.length >= minuend.size(); - //@ assert sData == subtrahend.data; - //@ assert AP(subtrahend.data, subtrahend.data.length) << subtrahend.offset == \old(subtrahend.value()); - //@ assert subtrahend.offset == Math.min(\old(this.offset), minuend.offset); - //@ assert offsetDiff == minuend.offset - subtrahend.offset; - //@ assert 0 <= offsetDiff && offsetDiff + minLen <= sData.length; - int sIndex = 0; - long borrow = 0L; - for (; sIndex < offsetDiff; sIndex++) { - long diff = 0L - (sData[sIndex] & LONG_MASK) + borrow; - sData[sIndex] = (int) diff; - borrow = diff >> 32; // signed shift - } - //@ assert sIndex == offsetDiff; - for (int mIndex = 0; mIndex < minLen; sIndex++, mIndex++) { - //@ assert sIndex == offsetDiff + mIndex; - long diff = (mData[mIndex] & LONG_MASK) - (sData[sIndex] & LONG_MASK) + borrow; - sData[sIndex] = (int) diff; - borrow = diff >> 32; // signed shift - } - assert borrow == 0L : borrow; // borrow out of subtract, - // result should be positive - subtrahend.nWords = sIndex; - subtrahend.trimLeadingZeros(); - return subtrahend; - - } - /** * Determines whether all elements of an array are zero for all indices less * than a given index. @@ -928,11 +575,7 @@ public /*@ spec_bigint_math @*/ class FDBigInteger { * @param from The index strictly below which elements are to be examined. * @return Zero if all elements in range are zero, 1 otherwise. */ - /*@ - @ requires 0 <= from && from <= a.length; - @ ensures \result == (AP(a, from) == 0 ? 0 : 1); - @*/ - private /*@ pure @*/ static int checkZeroTail(int[] a, int from) { + private static int checkZeroTail(int[] a, int from) { while (from > 0) { if (a[--from] != 0) { return 1; @@ -942,7 +585,7 @@ public /*@ spec_bigint_math @*/ class FDBigInteger { } /** - * Compares the parameter with this FDBigInteger. Returns an + * Compares the parameter with this {@link FDBigInteger}. Returns an * integer accordingly as: *
{@code
      * > 0: this > other
@@ -950,14 +593,11 @@ public /*@ spec_bigint_math @*/ class FDBigInteger {
      * < 0: this < other
      * }
* - * @param other The FDBigInteger to compare. + * @param other The {@link FDBigInteger} to compare. * @return A negative value, zero, or a positive value according to the * result of the comparison. */ - /*@ - @ ensures \result == (this.value() < other.value() ? -1 : this.value() > other.value() ? +1 : 0); - @*/ - public /*@ pure @*/ int cmp(FDBigInteger other) { + public int cmp(FDBigInteger other) { int aSize = nWords + offset; int bSize = other.nWords + other.offset; if (aSize > bSize) { @@ -968,10 +608,9 @@ public /*@ spec_bigint_math @*/ class FDBigInteger { int aLen = nWords; int bLen = other.nWords; while (aLen > 0 && bLen > 0) { - int a = data[--aLen]; - int b = other.data[--bLen]; - if (a != b) { - return ((a & LONG_MASK) < (b & LONG_MASK)) ? -1 : 1; + int cmp = Integer.compareUnsigned(data[--aLen], other.data[--bLen]); + if (cmp != 0) { + return cmp; } } if (aLen > 0) { @@ -984,45 +623,7 @@ public /*@ spec_bigint_math @*/ class FDBigInteger { } /** - * Compares this FDBigInteger with - * 5p5 * 2p2. - * Returns an integer accordingly as: - *
{@code
-     * > 0: this > other
-     *   0: this == other
-     * < 0: this < other
-     * }
- * @param p5 The exponent of the power-of-five factor. - * @param p2 The exponent of the power-of-two factor. - * @return A negative value, zero, or a positive value according to the - * result of the comparison. - */ - /*@ - @ requires p5 >= 0 && p2 >= 0; - @ ensures \result == (this.value() < pow52(p5, p2) ? -1 : this.value() > pow52(p5, p2) ? +1 : 0); - @*/ - public /*@ pure @*/ int cmpPow52(int p5, int p2) { - if (p5 == 0) { - int wordcount = p2 >> 5; - int bitcount = p2 & 0x1f; - int size = this.nWords + this.offset; - if (size > wordcount + 1) { - return 1; - } else if (size < wordcount + 1) { - return -1; - } - int a = this.data[this.nWords -1]; - int b = 1 << bitcount; - if (a != b) { - return ( (a & LONG_MASK) < (b & LONG_MASK)) ? -1 : 1; - } - return checkZeroTail(this.data, this.nWords - 1); - } - return this.cmp(big5pow(p5).leftShift(p2)); - } - - /** - * Compares this FDBigInteger with x + y. Returns a + * Compares this {@link FDBigInteger} with {@code x + y}. Returns a * value according to the comparison as: *
{@code
      * -1: this <  x + y
@@ -1033,10 +634,7 @@ public /*@ spec_bigint_math @*/ class FDBigInteger {
      * @param y The second addend of the sum to compare.
      * @return -1, 0, or 1 according to the result of the comparison.
      */
-    /*@
-     @ ensures \result == (this.value() < x.value() + y.value() ? -1 : this.value() > x.value() + y.value() ? +1 : 0);
-     @*/
-    public /*@ pure @*/ int addAndCmp(FDBigInteger x, FDBigInteger y) {
+    public int addAndCmp(FDBigInteger x, FDBigInteger y) {
         FDBigInteger big;
         FDBigInteger small;
         int xSize = x.size();
@@ -1104,93 +702,48 @@ public /*@ spec_bigint_math @*/ class FDBigInteger {
     }
 
     /**
-     * Makes this FDBigInteger immutable.
+     * Makes this {@link FDBigInteger} immutable.
+     *
+     * @return {@code this}
      */
-    /*@
-     @ assignable this.isImmutable;
-     @ ensures this.isImmutable;
-     @*/
-    public void makeImmutable() {
-        this.isImmutable = true;
+    public FDBigInteger makeImmutable() {
+        isImmutable = true;
+        return this;
     }
 
     /**
-     * Multiplies this FDBigInteger by an integer.
+     * Multiplies this {@link FDBigInteger} by an integer.
      *
-     * @param i The factor by which to multiply this FDBigInteger.
-     * @return This FDBigInteger multiplied by an integer.
+     * @param v The factor by which to multiply this {@link FDBigInteger}.
+     * @return This {@link FDBigInteger} multiplied by an integer.
      */
-    /*@
-     @ requires this.value() == 0;
-     @ assignable \nothing;
-     @ ensures \result == this;
-     @
-     @  also
-     @
-     @ requires this.value() != 0;
-     @ assignable \nothing;
-     @ ensures \result.value() == \old(this.value() * UNSIGNED(i));
-     @*/
-    private FDBigInteger mult(int i) {
-        if (this.nWords == 0) {
+    public FDBigInteger mult(int v) {
+        if (nWords == 0 || v == 0) {
             return this;
         }
         int[] r = new int[nWords + 1];
-        mult(data, nWords, i, r);
+        mult(data, nWords, v, r);
         return new FDBigInteger(r, offset);
     }
 
     /**
-     * Multiplies this FDBigInteger by another FDBigInteger.
+     * Multiplies this {@link FDBigInteger} by another {@link FDBigInteger}.
      *
-     * @param other The FDBigInteger factor by which to multiply.
-     * @return The product of this and the parameter FDBigIntegers.
+     * @param other The {@link FDBigInteger} factor by which to multiply.
+     * @return The product of this and the parameter {@link FDBigInteger}s.
      */
-    /*@
-     @ requires this.value() == 0;
-     @ assignable \nothing;
-     @ ensures \result == this;
-     @
-     @  also
-     @
-     @ requires this.value() != 0 && other.value() == 0;
-     @ assignable \nothing;
-     @ ensures \result == other;
-     @
-     @  also
-     @
-     @ requires this.value() != 0 && other.value() != 0;
-     @ assignable \nothing;
-     @ ensures \result.value() == \old(this.value() * other.value());
-     @*/
     private FDBigInteger mult(FDBigInteger other) {
-        if (this.nWords == 0) {
-            return this;
-        }
-        if (this.size() == 1) {
-            return other.mult(data[0]);
-        }
-        if (other.nWords == 0) {
-            return other;
-        }
-        if (other.size() == 1) {
-            return this.mult(other.data[0]);
-        }
         int[] r = new int[nWords + other.nWords];
-        mult(this.data, this.nWords, other.data, other.nWords, r);
-        return new FDBigInteger(r, this.offset + other.offset);
+        mult(data, nWords, other.data, other.nWords, r);
+        return new FDBigInteger(r, offset + other.offset);
     }
 
     /**
-     * Adds another FDBigInteger to this FDBigInteger.
+     * Adds another {@link FDBigInteger} to this {@link FDBigInteger}.
      *
-     * @param other The FDBigInteger to add.
-     * @return The sum of the FDBigIntegers.
+     * @param other The {@link FDBigInteger} to add.
+     * @return The sum of the {@link FDBigInteger}s.
      */
-    /*@
-     @ assignable \nothing;
-     @ ensures \result.value() == \old(this.value() + other.value());
-     @*/
     private FDBigInteger add(FDBigInteger other) {
         FDBigInteger big, small;
         int bigLen, smallLen;
@@ -1227,23 +780,15 @@ public /*@ spec_bigint_math @*/ class FDBigInteger {
 
 
     /**
-     * Multiplies a FDBigInteger by an int and adds another int. The
+     * Multiplies a {@link FDBigInteger} by an int and adds another int. The
      * result is computed in place. This method is intended only to be invoked
-     * from
-     * 
-     * FDBigInteger(long lValue, char[] digits, int kDigits, int nDigits)
-     * .
+     * from {@link FDBigInteger(long,char[],int,int)}.
      *
-     * @param iv The factor by which to multiply this FDBigInteger.
+     * @param iv The factor by which to multiply this {@link FDBigInteger}.
      * @param addend The value to add to the product of this
-     * FDBigInteger and iv.
+     * {@link FDBigInteger} and {@code iv}.
      */
-    /*@
-     @ requires this.value()*UNSIGNED(iv) + UNSIGNED(addend) < ((\bigint)1) << ((this.data.length + this.offset)*32);
-     @ assignable this.data[*];
-     @ ensures this.value() == \old(this.value()*UNSIGNED(iv) + UNSIGNED(addend));
-     @*/
-    private /*@ helper @*/ void multAddMe(int iv, int addend) {
+    private void multAdd(int iv, int addend) {
         long v = iv & LONG_MASK;
         // unroll 0th iteration, doing addition.
         long p = v * (data[0] & LONG_MASK) + (addend & LONG_MASK);
@@ -1255,51 +800,18 @@ public /*@ spec_bigint_math @*/ class FDBigInteger {
             p >>>= 32;
         }
         if (p != 0L) {
-            data[nWords++] = (int) p; // will fail noisily if illegal!
+            data[nWords++] = (int) p;
         }
     }
 
-    //
-    // original doc:
-    //
-    // do this -=q*S
-    // returns borrow
-    //
     /**
-     * Multiplies the parameters and subtracts them from this
-     * FDBigInteger.
+     * Multiplies the parameters and subtracts them from this {@link FDBigInteger}.
      *
      * @param q The integer parameter.
-     * @param S The FDBigInteger parameter.
-     * @return this - q*S.
+     * @param S The {@link FDBigInteger} parameter.
+     * @return {@code this - q*S}.
      */
-    /*@
-     @ ensures nWords == 0 ==> offset == 0;
-     @ ensures nWords > 0 ==> data[nWords - 1] != 0;
-     @*/
-    /*@
-     @ requires 0 < q && q <= (1L << 31);
-     @ requires data != null;
-     @ requires 0 <= nWords && nWords <= data.length && offset >= 0;
-     @ requires !this.isImmutable;
-     @ requires this.size() == S.size();
-     @ requires this != S;
-     @ assignable this.nWords, this.offset, this.data, this.data[*];
-     @ ensures -q <= \result && \result <= 0;
-     @ ensures this.size() == \old(this.size());
-     @ ensures this.value() + (\result << (this.size()*32)) == \old(this.value() - q*S.value());
-     @ ensures this.offset == \old(Math.min(this.offset, S.offset));
-     @ ensures \old(this.offset <= S.offset) ==> this.nWords == \old(this.nWords);
-     @ ensures \old(this.offset <= S.offset) ==> this.offset == \old(this.offset);
-     @ ensures \old(this.offset <= S.offset) ==> this.data == \old(this.data);
-     @
-     @  also
-     @
-     @ requires q == 0;
-     @ assignable \nothing;
-     @ ensures \result == 0;
-     @*/
-    private /*@ helper @*/ long multDiffMe(long q, FDBigInteger S) {
+    private long multSub(long q, FDBigInteger S) {
         long diff = 0L;
         if (q != 0) {
             int deltaSize = S.offset - this.offset;
@@ -1337,22 +849,15 @@ public /*@ spec_bigint_math @*/ class FDBigInteger {
         return diff;
     }
 
-
     /**
      * Multiplies by 10 a big integer represented as an array. The final carry
      * is returned.
      *
      * @param src The array representation of the big integer.
-     * @param srcLen The number of elements of src to use.
+     * @param srcLen The number of elements of {@code src} to use.
      * @param dst The product array.
      * @return The final carry of the multiplication.
      */
-    /*@
-     @ requires src.length >= srcLen && dst.length >= srcLen;
-     @ assignable dst[0 .. srcLen - 1];
-     @ ensures 0 <= \result && \result < 10;
-     @ ensures AP(dst, srcLen) + (\result << (srcLen*32)) == \old(AP(src, srcLen) * 10);
-     @*/
     private static int multAndCarryBy10(int[] src, int srcLen, int[] dst) {
         long carry = 0;
         for (int i = 0; i < srcLen; i++) {
@@ -1365,157 +870,138 @@ public /*@ spec_bigint_math @*/ class FDBigInteger {
 
     /**
      * Multiplies by a constant value a big integer represented as an array.
-     * The constant factor is an int.
+     * The constant factor is an {@code int}.
      *
      * @param src The array representation of the big integer.
-     * @param srcLen The number of elements of src to use.
+     * @param srcLen The number of elements of {@code src} to use.
      * @param value The constant factor by which to multiply.
      * @param dst The product array.
      */
-    /*@
-     @ requires src.length >= srcLen && dst.length >= srcLen + 1;
-     @ assignable dst[0 .. srcLen];
-     @ ensures AP(dst, srcLen + 1) == \old(AP(src, srcLen) * UNSIGNED(value));
-     @*/
     private static void mult(int[] src, int srcLen, int value, int[] dst) {
-        long val = value & LONG_MASK;
+        long v = value & LONG_MASK;
         long carry = 0;
-        for (int i = 0; i < srcLen; i++) {
-            long product = (src[i] & LONG_MASK) * val + carry;
+        int i = 0;
+        for (; i < srcLen; i++) {
+            long product = v * (src[i] & LONG_MASK) + carry;
             dst[i] = (int) product;
             carry = product >>> 32;
         }
-        dst[srcLen] = (int) carry;
+        dst[i] = (int) carry;
     }
 
     /**
      * Multiplies by a constant value a big integer represented as an array.
-     * The constant factor is a long represent as two ints.
+     * The constant factor is a long represent as two {@code int}s.
      *
      * @param src The array representation of the big integer.
-     * @param srcLen The number of elements of src to use.
+     * @param srcLen The number of elements of {@code src} to use.
      * @param v0 The lower 32 bits of the long factor.
      * @param v1 The upper 32 bits of the long factor.
      * @param dst The product array.
      */
-    /*@
-     @ requires src != dst;
-     @ requires src.length >= srcLen && dst.length >= srcLen + 2;
-     @ assignable dst[0 .. srcLen + 1];
-     @ ensures AP(dst, srcLen + 2) == \old(AP(src, srcLen) * (UNSIGNED(v0) + (UNSIGNED(v1) << 32)));
-     @*/
     private static void mult(int[] src, int srcLen, int v0, int v1, int[] dst) {
         long v = v0 & LONG_MASK;
         long carry = 0;
-        for (int j = 0; j < srcLen; j++) {
+        int j = 0;
+        for (; j < srcLen; j++) {
             long product = v * (src[j] & LONG_MASK) + carry;
             dst[j] = (int) product;
             carry = product >>> 32;
         }
-        dst[srcLen] = (int) carry;
+        dst[j] = (int) carry;
+
         v = v1 & LONG_MASK;
         carry = 0;
-        for (int j = 0; j < srcLen; j++) {
-            long product = (dst[j + 1] & LONG_MASK) + v * (src[j] & LONG_MASK) + carry;
-            dst[j + 1] = (int) product;
+        j = 1;
+        for (; j <= srcLen; j++) {
+            long product = (dst[j] & LONG_MASK) + v * (src[j - 1] & LONG_MASK) + carry;
+            dst[j] = (int) product;
             carry = product >>> 32;
         }
-        dst[srcLen + 1] = (int) carry;
+        dst[j] = (int) carry;
     }
 
-    // Fails assertion for negative exponent.
-    /**
-     * Computes 5 raised to a given power.
+    /*
+     * Lookup table of powers of 5 starting with 5^MAX_FIVE_POW.
+     * The size just serves for the conversions.
+     * It is filled lazily, except for the entries with exponent
+     * 2 (MAX_FIVE_POW - 1) and 3 (MAX_FIVE_POW - 1).
      *
-     * @param p The exponent of 5.
-     * @return 5p.
+     * Access needs not be synchronized for thread-safety, since races would
+     * produce the same non-null value (although not the same instance).
      */
-    private static FDBigInteger big5pow(int p) {
-        assert p >= 0 : p; // negative power of 5
-        if (p < MAX_FIVE_POW) {
-            return POW_5_CACHE[p];
-        }
-        return big5powRec(p);
-    }
+    @Stable
+    private static final FDBigInteger[] LARGE_POW_5_CACHE;
 
-    // slow path
     /**
-     * Computes 5 raised to a given power.
+     * Computes 5{@code e}.
      *
-     * @param p The exponent of 5.
-     * @return 5p.
+     * @param e The exponent of 5.
+     * @return 5{@code e}.
+     * @throws IllegalArgumentException if e > 2 - DoubleToDecimal.Q_MIN = 1076
      */
-    private static FDBigInteger big5powRec(int p) {
-        if (p < MAX_FIVE_POW) {
-            return POW_5_CACHE[p];
+    private static FDBigInteger pow5(int e) {
+        if (e < MAX_FIVE_POW) {
+            return POW_5_CACHE[e];
         }
-        // construct the value.
-        // recursively.
-        int q, r;
-        // in order to compute 5^p,
-        // compute its square root, 5^(p/2) and square.
-        // or, let q = p / 2, r = p -q, then
-        // 5^p = 5^(q+r) = 5^q * 5^r
-        q = p >> 1;
-        r = p - q;
-        FDBigInteger bigq = big5powRec(q);
-        if (r < SMALL_5_POW.length) {
-            return bigq.mult(SMALL_5_POW[r]);
-        } else {
-            return bigq.mult(big5powRec(r));
+        if (e > 2 - DoubleToDecimal.Q_MIN) {
+            throw new IllegalArgumentException("exponent too large: " + e);
         }
+        FDBigInteger p5 = LARGE_POW_5_CACHE[e - MAX_FIVE_POW];
+        if (p5 == null) {
+            int ep = (e - 1) - (e - 1) % (MAX_FIVE_POW - 1);
+            p5 = (ep < MAX_FIVE_POW
+                    ? POW_5_CACHE[ep]
+                    : LARGE_POW_5_CACHE[ep - MAX_FIVE_POW])
+                    .mult(POW_5_CACHE[e - ep]);
+            LARGE_POW_5_CACHE[e - MAX_FIVE_POW] = p5.makeImmutable();
+        }
+        return p5;
     }
 
     // for debugging ...
     /**
-     * Converts this FDBigInteger to a hexadecimal string.
+     * Converts this {@link FDBigInteger} to a hexadecimal string.
      *
      * @return The hexadecimal string representation.
      */
-    public String toHexString(){
-        if(nWords ==0) {
+    @Override
+    public String toString() {
+        if (nWords == 0) {
             return "0";
         }
-        StringBuilder sb = new StringBuilder((nWords +offset)*8);
-        for(int i= nWords -1; i>=0; i--) {
-            String subStr = Integer.toHexString(data[i]);
-            for(int j = subStr.length(); j<8; j++) {
-                sb.append('0');
-            }
-            sb.append(subStr);
+        StringBuilder sb = new StringBuilder(8 * (size()));
+        int i = nWords - 1;
+        sb.append(Integer.toHexString(data[i--]));
+        while (i >= 0) {
+            String subStr = Integer.toHexString(data[i--]);
+            sb.repeat('0', 8 - subStr.length()).append(subStr);
         }
-        for(int i=offset; i>0; i--) {
-            sb.append("00000000");
-        }
-        return sb.toString();
+        return sb.repeat('0', 8 * offset).toString();
     }
 
     // for debugging ...
     /**
-     * Converts this FDBigInteger to a BigInteger.
+     * Converts this {@link FDBigInteger} to a {@code byte[]} suitable
+     * for use with {@link java.math.BigInteger#BigInteger(byte[])}.
      *
-     * @return The BigInteger representation.
+     * @return The {@code byte[]} representation.
      */
-    public BigInteger toBigInteger() {
-        byte[] magnitude = new byte[nWords * 4 + 1];
-        for (int i = 0; i < nWords; i++) {
+    /*
+     * A toBigInteger() method would be more convenient, but it would introduce
+     * a dependency on java.math, which is not desirable in such a basic layer
+     * like this one used in components as java.lang, javac, and others.
+     */
+    public byte[] toByteArray() {
+        byte[] magnitude = new byte[4 * size() + 1];  // +1 for the "sign" byte
+        for (int i = 0, j = magnitude.length - 4 * offset; i < nWords; i += 1, j -= 4) {
             int w = data[i];
-            magnitude[magnitude.length - 4 * i - 1] = (byte) w;
-            magnitude[magnitude.length - 4 * i - 2] = (byte) (w >> 8);
-            magnitude[magnitude.length - 4 * i - 3] = (byte) (w >> 16);
-            magnitude[magnitude.length - 4 * i - 4] = (byte) (w >> 24);
+            magnitude[j - 1] = (byte) w;
+            magnitude[j - 2] = (byte) (w >> 8);
+            magnitude[j - 3] = (byte) (w >> 16);
+            magnitude[j - 4] = (byte) (w >> 24);
         }
-        return new BigInteger(magnitude).shiftLeft(offset * 32);
+        return magnitude;
     }
 
-    // for debugging ...
-    /**
-     * Converts this FDBigInteger to a string.
-     *
-     * @return The string representation.
-     */
-    @Override
-    public String toString(){
-        return toBigInteger().toString();
-    }
 }
diff --git a/src/java.base/share/classes/jdk/internal/math/FloatingDecimal.java b/src/java.base/share/classes/jdk/internal/math/FloatingDecimal.java
index e90ca4d75f1..067ca9a6406 100644
--- a/src/java.base/share/classes/jdk/internal/math/FloatingDecimal.java
+++ b/src/java.base/share/classes/jdk/internal/math/FloatingDecimal.java
@@ -27,59 +27,50 @@ package jdk.internal.math;
 
 import jdk.internal.vm.annotation.Stable;
 
+import static jdk.internal.math.FDBigInteger.valueOfMulPow52;
+import static jdk.internal.math.FDBigInteger.valueOfPow52;
+
 /**
  * A class for converting between ASCII and decimal representations of a single
  * or double precision floating point number. Most conversions are provided via
- * static convenience methods, although a BinaryToASCIIConverter
- * instance may be obtained and reused.
+ * static convenience methods, although a {@link BinaryToASCIIBuffer}
+ * instance may be obtained.
  */
-public class FloatingDecimal{
+public final class FloatingDecimal {
     //
     // Constants of the implementation;
     // most are IEEE-754 related.
-    // (There are more really boring constants at the end.)
     //
-    static final int    EXP_SHIFT = DoubleConsts.SIGNIFICAND_WIDTH - 1;
-    static final long   FRACT_HOB = ( 1L<String to a double precision floating point value.
+     * Converts a {@link String} to a double precision floating point value.
      *
-     * @param s The String to convert.
+     * @param s The {@link String} to convert.
      * @return The double precision value.
-     * @throws NumberFormatException If the String does not
+     * @throws NumberFormatException If the {@link String} does not
      * represent a properly formatted double precision value.
      */
     public static double parseDouble(String s) throws NumberFormatException {
-        return readJavaFormatString(s, BINARY_64_IX).doubleValue();
+        return readJavaFormatString(s, BINARY_64_IX);
     }
 
     /**
-     * Converts a String to a single precision floating point value.
+     * Converts a {@link String} to a single precision floating point value.
      *
-     * @param s The String to convert.
+     * @param s The {@link String} to convert.
      * @return The single precision value.
-     * @throws NumberFormatException If the String does not
+     * @throws NumberFormatException If the {@link String} does not
      * represent a properly formatted single precision value.
      */
     public static float parseFloat(String s) throws NumberFormatException {
-        return readJavaFormatString(s, BINARY_32_IX).floatValue();
+        return (float) readJavaFormatString(s, BINARY_32_IX);
     }
 
     /**
@@ -87,60 +78,23 @@ public class FloatingDecimal{
      * double value
      *
      * @param decExp The decimal exponent of the value to generate
-     * @param digits The digits of the significand.
+     * @param d The digits of the significand.
      * @param length Number of digits to use
      * @return The double-precision value of the conversion
      */
-    public static double parseDoubleSignlessDigits(int decExp, byte[] digits, int length) {
-        return readDoubleSignlessDigits(decExp, digits, length).doubleValue();
-    }
-
-    /**
-     * A converter which can process single or double precision floating point
-     * values into an ASCII String representation.
-     */
-    public interface BinaryToASCIIConverter {
-
-        /**
-         * Retrieves the decimal exponent most closely corresponding to this value.
-         * @return The decimal exponent.
-         */
-        int getDecimalExponent();
-
-        /**
-         * Retrieves the value as an array of digits.
-         * @param digits The digit array.
-         * @return The number of valid digits copied into the array.
-         */
-        int getDigits(byte[] digits);
-
-        /**
-         * Indicates whether the value was rounded up during the binary to ASCII
-         * conversion.
-         *
-         * @return true if and only if the value was rounded up.
-         */
-        boolean digitsRoundedUp();
-
-        /**
-         * Indicates whether the binary to ASCII conversion was exact.
-         *
-         * @return true if any only if the conversion was exact.
-         */
-        boolean decimalDigitsExact();
+    public static double parseDoubleSignlessDigits(int decExp, byte[] d, int length) {
+        return new ASCIIToBinaryBuffer(false, decExp, d, length).doubleValue();
     }
 
     private static final String INFINITY_REP = "Infinity";
     private static final String NAN_REP = "NaN";
 
-    private static final BinaryToASCIIConverter B2AC_POSITIVE_ZERO = new BinaryToASCIIBuffer(false, new byte[]{'0'});
-    private static final BinaryToASCIIConverter B2AC_NEGATIVE_ZERO = new BinaryToASCIIBuffer(true,  new byte[]{'0'});
+    private static final BinaryToASCIIBuffer B2AC_POSITIVE_ZERO =
+            new BinaryToASCIIBuffer(new byte[] {'0'});
+    private static final BinaryToASCIIBuffer B2AC_NEGATIVE_ZERO =
+            new BinaryToASCIIBuffer(new byte[] {'0'});
 
-    /**
-     * A buffered implementation of BinaryToASCIIConverter.
-     */
-    static class BinaryToASCIIBuffer implements BinaryToASCIIConverter {
-        private boolean isNegative;
+    public static final class BinaryToASCIIBuffer {
         private int decExponent;
         private int firstDigitIndex;
         private int nDigits;
@@ -161,48 +115,39 @@ public class FloatingDecimal{
 
         /**
          * Default constructor; used for non-zero values,
-         * BinaryToASCIIBuffer may be thread-local and reused
+         * {@link BinaryToASCIIBuffer} may be thread-local and reused
          */
-        BinaryToASCIIBuffer(){
+        private BinaryToASCIIBuffer() {
             this.digits = new byte[20];
         }
 
         /**
          * Creates a specialized value (positive and negative zeros).
          */
-        BinaryToASCIIBuffer(boolean isNegative, byte[] digits){
-            this.isNegative = isNegative;
+        private BinaryToASCIIBuffer(byte[] digits){
             this.decExponent  = 0;
             this.digits = digits;
             this.firstDigitIndex = 0;
             this.nDigits = digits.length;
         }
 
-        @Override
         public int getDecimalExponent() {
             return decExponent;
         }
 
-        @Override
         public int getDigits(byte[] digits) {
             System.arraycopy(this.digits, firstDigitIndex, digits, 0, this.nDigits);
             return this.nDigits;
         }
 
-        @Override
         public boolean digitsRoundedUp() {
             return decimalDigitsRoundedUp;
         }
 
-        @Override
         public boolean decimalDigitsExact() {
             return exactDecimalConversion;
         }
 
-        private void setSign(boolean isNegative) {
-            this.isNegative = isNegative;
-        }
-
         /**
          * This is the easy subcase --
          * all the significant bits, after scaling, are held in lvalue.
@@ -212,13 +157,14 @@ public class FloatingDecimal{
          * In particular:
          * lvalue is a finite number (not Inf, nor NaN)
          * lvalue > 0L (not zero, nor negative).
-         *
+         *

* The only reason that we develop the digits here, rather than * calling on Long.toString() is that we can do it a little faster, * and besides want to treat trailing 0s specially. If Long.toString * changes, we should re-evaluate this strategy! */ - private void developLongDigits( int decExponent, long lvalue, int insignificantDigits ){ + private void developLongDigits( long lvalue, int insignificantDigits ){ + int decExponent = 0; if ( insignificantDigits != 0 ){ // Discard non-significant low-order bits, while rounding, // up to insignificant value. @@ -275,7 +221,7 @@ public class FloatingDecimal{ this.nDigits = this.digits.length - digitno; } - private void dtoa( int binExp, long fractBits, int nSignificantBits, boolean isCompatibleFormat) + private void dtoa( int binExp, long fractBits, int nSignificantBits) { assert fractBits > 0 ; // fractBits here can't be zero or negative assert (fractBits & FRACT_HOB)!=0 ; // Hi-order bit should be set @@ -327,7 +273,7 @@ public class FloatingDecimal{ } else { fractBits >>>= (EXP_SHIFT-binExp) ; } - developLongDigits( 0, fractBits, insignificant ); + developLongDigits( fractBits, insignificant ); return; } // @@ -421,7 +367,7 @@ public class FloatingDecimal{ // 26 Sept 96 is not that day. // So we use a symmetric test. // - int ndigit = 0; + int ndigit; boolean low, high; long lowDigitDifference; int q; @@ -477,7 +423,7 @@ public class FloatingDecimal{ // Thus we will need more than one digit if we're using // E-form // - if ( !isCompatibleFormat ||decExp < -3 || decExp >= 8 ){ + if (decExp < -3 || decExp >= 8){ high = low = false; } while( ! low && ! high ){ @@ -531,7 +477,7 @@ public class FloatingDecimal{ // Thus we will need more than one digit if we're using // E-form // - if ( !isCompatibleFormat || decExp < -3 || decExp >= 8 ){ + if (decExp < -3 || decExp >= 8){ high = low = false; } while( ! low && ! high ){ @@ -561,14 +507,14 @@ public class FloatingDecimal{ // We really must do FDBigInteger arithmetic. // Fist, construct our FDBigInteger initial values. // - FDBigInteger Sval = FDBigInteger.valueOfPow52(S5, S2); + FDBigInteger Sval = valueOfPow52(S5, S2); int shiftBias = Sval.getNormalizationBias(); Sval = Sval.leftShift(shiftBias); // normalize so that division works better - FDBigInteger Bval = FDBigInteger.valueOfMulPow52(fractBits, B5, B2 + shiftBias); - FDBigInteger Mval = FDBigInteger.valueOfPow52(M5 + 1, M2 + shiftBias + 1); + FDBigInteger Bval = valueOfMulPow52(fractBits, B5, B2 + shiftBias); + FDBigInteger Mval = valueOfPow52(M5 + 1, M2 + shiftBias + 1); - FDBigInteger tenSval = FDBigInteger.valueOfPow52(S5 + 1, S2 + shiftBias + 1); //Sval.mult( 10 ); + FDBigInteger tenSval = valueOfPow52(S5 + 1, S2 + shiftBias + 1); //Sval.mult( 10 ); // // Unroll the first iteration. If our decExp estimate // was too high, our first quotient will be zero. In this @@ -592,7 +538,7 @@ public class FloatingDecimal{ // Thus we will need more than one digit if we're using // E-form // - if (!isCompatibleFormat || decExp < -3 || decExp >= 8 ){ + if (decExp < -3 || decExp >= 8){ high = low = false; } while( ! low && ! high ){ @@ -609,7 +555,7 @@ public class FloatingDecimal{ } else { lowDigitDifference = 0L; // this here only for flow analysis! } - exactDecimalConversion = (Bval.cmp( FDBigInteger.ZERO ) == 0); + exactDecimalConversion = Bval.isZero(); } this.decExponent = decExp+1; this.firstDigitIndex = 0; @@ -662,7 +608,7 @@ public class FloatingDecimal{ /** * Estimate decimal exponent. (If it is small-ish, * we could double-check.) - * + *

* First, scale the mantissa bits such that 1 <= d2 < 2. * We are then going to estimate * log10(d2) ~=~ (d2-1.5)/1.5 + log(1.5) @@ -670,7 +616,7 @@ public class FloatingDecimal{ * log10(d) ~=~ log10(d2) + binExp * log10(2) * take the floor and call it decExp. */ - static int estimateDecExp(long fractBits, int binExp) { + private static int estimateDecExp(long fractBits, int binExp) { double d2 = Double.longBitsToDouble( EXP_ONE | ( fractBits & DoubleConsts.SIGNIF_BIT_MASK ) ); double d = (d2-1.5D)*0.289529654D + 0.176091259 + (double)binExp * 0.301029995663981; long dBits = Double.doubleToRawLongBits(d); //can't be NaN here so use raw @@ -705,16 +651,18 @@ public class FloatingDecimal{ * for ( i = 0; insignificant >= 10L; i++ ) * insignificant /= 10L; */ + @Stable private static final int[] insignificantDigitsNumber = { 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 17, 17, 17, - 18, 18, 18, 19 + 18, 18, 18, 19, }; // approximately ceil( log2( long5pow[i] ) ) + @Stable private static final int[] N_5_BITS = { 0, 3, @@ -743,760 +691,508 @@ public class FloatingDecimal{ 56, 59, 61, + 63, }; } private static final ThreadLocal threadLocalBinaryToASCIIBuffer = - new ThreadLocal() { - @Override - protected BinaryToASCIIBuffer initialValue() { - return new BinaryToASCIIBuffer(); - } - }; + ThreadLocal.withInitial(BinaryToASCIIBuffer::new); private static BinaryToASCIIBuffer getBinaryToASCIIBuffer() { return threadLocalBinaryToASCIIBuffer.get(); } - /** - * A converter which can process an ASCII String representation - * of a single or double precision floating point value into a - * float or a double. + /* + * The mathematical value x of an instance is + * ±<0.d_1...d_n> 10^e + * where d_i = d[i-1] - '0' (0 < i ≤ n) is the i-th digit. + * It is assumed that d_1 > 0. + * isNegative denotes the - sign. */ - interface ASCIIToBinaryConverter { + private static final class ASCIIToBinaryBuffer { - double doubleValue(); + private final boolean isNegative; + private final int e; + private final int n; + private final byte[] d; - float floatValue(); - - } - - /** - * A ASCIIToBinaryConverter container for a double. - */ - static class PreparedASCIIToBinaryBuffer implements ASCIIToBinaryConverter { - private final double doubleVal; - private final float floatVal; - - public PreparedASCIIToBinaryBuffer(double doubleVal, float floatVal) { - this.doubleVal = doubleVal; - this.floatVal = floatVal; + private ASCIIToBinaryBuffer(boolean isNegative, int e, byte[] d, int n) { + this.isNegative = isNegative; + this.e = e; + this.d = d; + this.n = n; } - @Override - public double doubleValue() { - return doubleVal; + /* Assumes n ≤ 19 and returns a decimal prefix of f as an unsigned long. */ + private long toLong(int n) { + long f = 0; + for (int i = 0; i < n; ++i) { + f = 10 * f + (d[i] - '0'); + } + return f; } - @Override - public float floatValue() { - return floatVal; - } - } + private double doubleValue() { + /* + * As described above, the magnitude of the mathematical value is + * x = <0.d_1...d_n> 10^e = 10^(e-n) = f 10^ep + * where f = and ep = e - n are integers. + * + * Let r_e denote the roundTiesToEven rounding. + * This method returns ±r_e(x). + */ - static final ASCIIToBinaryConverter A2BC_POSITIVE_INFINITY = new PreparedASCIIToBinaryBuffer(Double.POSITIVE_INFINITY, Float.POSITIVE_INFINITY); - static final ASCIIToBinaryConverter A2BC_NEGATIVE_INFINITY = new PreparedASCIIToBinaryBuffer(Double.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY); - static final ASCIIToBinaryConverter A2BC_NOT_A_NUMBER = new PreparedASCIIToBinaryBuffer(Double.NaN, Float.NaN); - static final ASCIIToBinaryConverter A2BC_POSITIVE_ZERO = new PreparedASCIIToBinaryBuffer(0.0d, 0.0f); - static final ASCIIToBinaryConverter A2BC_NEGATIVE_ZERO = new PreparedASCIIToBinaryBuffer(-0.0d, -0.0f); + /* Filter out extremely small or extremely large x. */ + if (e <= DoubleToDecimal.E_THR_Z) { + /* Test cases: "0.9e-324", "3e-500" */ + return signed(0.0); + } + if (e >= DoubleToDecimal.E_THR_I) { + /* Test cases: "0.1e310", "4e500" */ + return signed(Double.POSITIVE_INFINITY); + } - /** - * A buffered implementation of ASCIIToBinaryConverter. - */ - static class ASCIIToBinaryBuffer implements ASCIIToBinaryConverter { - final boolean isNegative; - final int decExponent; - final byte[] digits; - int nDigits; + /* + * Attempt some fast paths before resorting to higher precision. + * Here, let P = Double.PRECISION = 53. + * + * Below, fl is an unsigned long, thus we require n ≤ 19 because + * 10^19 < 2^64 < 10^20. + */ + int n = this.n; + int ep = e - n; + double v; + int m = Math.min(n, MathUtils.N); + long fl = toLong(m); // unsigned + if (n <= MathUtils.N && 0 <= ep && e <= MathUtils.N) { + /* + * Here, n ≤ 19, hence f = fl < 10^19. + * Since e = n + ep and 0 ≤ ep ∧ n + ep ≤ 19 we see that + * x = f 10^ep < 10^n 10^ep = 10^(n+ep) ≤ 10^19. + * Thus, x = fl 10^ep fits in an unsigned long as well. + * If its most significant bit is 0, the long is non-negative. + * Otherwise, fl ≥ 2^63, so there's room for P precision bits, + * +1 rounding bit, +1 sticky bit. + * In both cases, correct rounding is achieved as below. + * All integer x < 10^19 are covered here. + */ - ASCIIToBinaryBuffer( boolean negSign, int decExponent, byte[] digits, int n) - { - this.isNegative = negSign; - this.decExponent = decExponent; - this.digits = digits; - this.nDigits = n; + /* + * Test cases: + * for fl < 2^63: "1", "2.34000e2", "9.223e18"; + * for fl ≥ 2^63: "9.876e18", "9223372036854776833" (this + * is 2^63 + 2^10 + 1, rounding up due to sticky bit), + * "9223372036854776832" (this is 2^63 + 2^10, halfway + * value rounding down to even); + */ + fl *= MathUtils.pow10(ep); // 0 ≤ ep < 19 + v = fl >= 0 ? fl : 2.0 * (fl >>> 1 | fl & 0b1); + return signed(v); + } + + if (n <= FLOG_10_MAX_LONG && -MAX_SMALL_TEN <= ep) { + v = fl; + /* + * Here, -22 ≤ ep. + * Further, fl < 10^18, so fl is an exact double iff + * (long) v == fl holds. + * If fl is not an exact double, resort to higher precision. + */ + boolean isExact = (long) v == fl; + if (isExact && ep <= MAX_SMALL_TEN) { + /* + * Here, -22 ≤ ep ≤ 22, so 10^|ep| is an exact double. + * The product or quotient below operate on exact doubles, + * so the result is correctly rounded. + */ + + /* + * Test cases: + * for ep < 0: "1.23", "0.000234"; + * for ep > 0: "3.45e23", "576460752303423616e20" (the + * significand is 2^59 + 2^7, an exact double); + */ + v = ep >= 0 ? v * SMALL_10_POW[ep] : v / SMALL_10_POW[-ep]; + return signed(v); + } + + /* + * Here, fl < 10^18 is not an exact double, or ep > 22. + * If fl is not an exact double, resort to higher precision. + */ + if (isExact) { // v and fl are mathematically equal. + /* + * Here, ep > 22. + * We have f = fl = v. + * Note that 2^P = 9007199254740992 has 16 digits. + * If f does not start with 9 let ef = 16 - n, otherwise + * let ef = 15 - n. + * If ef < 0 then resort to higher precision. + * Otherwise, if f does not start with 9 we have n ≤ 16, + * so f 10^ef < 9 10^(n-1) 10^ef = 9 10^15 < 2^P. + * If f starts with 9 we have n ≤ 15, hence f 10^ef < + * 10^n 10^ef = 10^15 < 2^P. + * + * Hence, when ef ≥ 0 and ep - ef ≤ 22 we know that + * fl 10^ep = (fl 10^ef) 10^(ep-ef), with fl, (fl 10^ef), + * and 10^(ep-ef) all exact doubles. + */ + int ef = (d[0] < '9' ? MAX_DEC_DIGITS + 1 : MAX_DEC_DIGITS) - n; + if (ef >= 0 && ep - ef <= MAX_SMALL_TEN) { + /* + * Test cases: + * f does not start with 9: "1e37", "8999e34"; + * f starts with 9: "0.9999e36", "0.9876e37"; + */ + + /* Rely on left-to-right evaluation. */ + v = v * SMALL_10_POW[ef] * SMALL_10_POW[ep - ef]; + return signed(v); + } + } + } + + /* + * Here, the above fast paths have failed to return. + * Force ll, lh in [10^(N-1), 10^N] to have more high order bits. + */ + long ll = fl; // unsigned + long lh; // unsigned + if (n <= MathUtils.N) { // ll = f + ll *= MathUtils.pow10(MathUtils.N - n); + lh = ll; + } else { // ll is an N digits long prefix of f + lh = ll + 1; + } + int el = e - MathUtils.N; + /* + * We now have + * x = f 10^ep + * ll 10^el ≤ x ≤ lh 10^el + * 2^59 < 10^(N-1) ≤ ll ≤ lh ≤ 10^N < 2^64 + * + * Rather than rounding x directly, which requires full precision + * arithmetic, approximate x as follows. + * Let integers g and r such that (see comments in MathUtils) + * (g - 1) 2^r ≤ 10^el < g 2^r + * and split g into the lower 63 bits g0 and the higher bits g1: + * g = g1 2^63 + g0 + * where + * 2^62 < g1 + 1 < 2^63, 0 < g0 < 2^63 + * We have + * g - 1 = g1 2^63 + g0 - 1 ≥ g1 2^63 + * g = g1 2^63 + g0 < g1 2^63 + 2^63 = (g1 + 1) 2^63 + * Let + * nl = ll g1 nh = lh (g1 + 1) + * These lead to + * nl 2^(r+63) ≤ x < nh 2^(r+63) + * Let + * v = r_e(nl 2^(r+63)) vh = r_e(nh 2^(r+63)) + * If v = vh then r_e(x) = v. + * + * We also have + * 2^121 = 2^59 2^62 < nl < nh < 2^64 2^63 = 2^127 + * Therefore, each of nl and nh fits in two longs. + * Split them into the lower 64 bits and the higher bits. + * nl = nl1 2^64 + nl0 2^57 ≤ nl1 < 2^63 + * nh = nh1 2^64 + nh0 2^57 ≤ nh1 < 2^63 + * Let bl and bh be the bitlength of nl1 and nh1, resp. + * Both bl and bh lie in the interval [58, 63], and all of nl1, nh1, + * nl, and nh are in the normal range of double. + * As nl ≤ nh ≤ nl + 2 ll, and as ll < 2^64, then either bh = bl, + * or more rarely bh = bl + 1. + * + * As mentioned above, if v = vh then r_e(x) = v. + * Rather than rounding nl 2^(r+63), nh 2^(r+63) boundaries directly, + * first round nl and nh to obtain doubles wl and wh, resp. + * wl = r_e(nl) wh = r_e(nh) + * Note that both wl and wh are normal doubles. + * + * Assume wl = wh. + * There's a good chance that v = scalb(wl, r + 63) holds. + * In fact, if x ≥ MIN_NORMAL then it can be (tediously) shown that + * v = scalb(wl, r + 63) holds, even when v overflows. + * If x < MIN_NORMAL, and since wl is normal and v ≤ MIN_NORMAL, + * the precision might be lowered, so scalb(wl, r + 63) might incur + * two rounding errors and could slightly differ from v. + * + * It is costly to precisely determine whether x ≥ MIN_NORMAL. + * However, bl + r > MIN_EXPONENT - 127 implies x ≥ MIN_NORMAL, + * and bh + r ≤ MIN_EXPONENT - 127 entails x < MIN_NORMAL. + * Finally, when bl + r ≤ MIN_EXPONENT - 127 < bh + r we see that + * bl + r = MIN_EXPONENT - 127 and bh = bl + 1 must hold. + * + * As noted, nh ≤ nl + 2 ll. + * This means + * nh1 ≤ nh 2^(-64) ≤ (nl + 2 ll) 2^(-64) < (nl1 + 1) + 2 + * and thus + * nh1 ≤ nl1 + 2 + */ + int rp = MathUtils.flog2pow10(el) + 2; // r + 127 + long g1 = MathUtils.g1(el); + long nl1 = Math.unsignedMultiplyHigh(ll, g1); + long nl0 = ll * g1; + long nh1 = Math.unsignedMultiplyHigh(lh, g1 + 1); + long nh0 = lh * (g1 + 1); + int bl = Long.SIZE - Long.numberOfLeadingZeros(nl1); + if (bl + rp > Double.MIN_EXPONENT) { // implies x is normal + /* + * To round nl we need its most significant P bits, the rounding + * bit immediately to the right, and an indication (sticky bit) + * of whether there are "1" bits following the rounding bit. + * The sticky bit can be placed anywhere after the rounding bit. + * Since bl ≥ 58, the P = 53 bits, the rounding bit, and space + * for the sticky bit are all located in nl1. + * + * When nl0 = 0, the indication of whether there are "1" bits + * to the right of the rounding bit is already contained in nl1. + * Rounding nl to wl is the same as rounding nl1 to ul and then + * multiplying this by 2^64. + * that is, given wl = r_e(nl), ul = r_e(nl1), we get + * wl = scalb(ul, 64). + * The same holds for nh, wh, nh1, and uh. + * So, if ul = uh then wl = wh, thus v = scalb(ul, r + 127). + * + * When nl1 ≠ 0, there are indeed "1" bits to the right of the + * rounding bit. + * We force the rightmost bit of nl1 to 1, obtaining nl1'. + * Then, again, rounding nl to wl is the same as rounding nl1' + * to ul and multiplying this by 2^64. + * Analogously for nh, wh, nh1, and uh. + * Again, if ul = uh then wl = wh, thus v = scalb(ul, r + 127). + * + * Since nh1 ≤ nl1 + 2, then either uh = ul or uh = nextUp(ul). + * This means that when ul ≠ uh then + * v ≤ r_e(x) ≤ nextUp(v) + */ + double ul = nl1 | (nl0 != 0 ? 1 : 0); + double uh = nh1 | (nh0 != 0 ? 1 : 0); + v = Math.scalb(ul, rp); + if (ul == uh || v == Double.POSITIVE_INFINITY) { + /* + * Test cases: + * for ll = lh ∧ ul = uh: "1.2e-200", "2.3e100"; + * for ll ≠ lh ∧ ul = uh: "1.2000000000000000003e-200", + * "2.3000000000000000004e100"; + * for ll = lh ∧ v = ∞: "5.249320425370670463e308"; + * for ll ≠ lh ∧ v = ∞: "5.2493204253706704633e308"; + */ + return signed(v); + } + } else { + int bh = Long.SIZE - Long.numberOfLeadingZeros(nh1); + if (bh + rp <= Double.MIN_EXPONENT) { // implies x is subnormal + /* + * We need to reduce the precision to avoid double rounding + * issues. + * Shifting to the right while keeping room for the rounding + * and the sticky bit is one way to go. + * Other than that, the reasoning is similar to the above case. + */ + int sh = DoubleToDecimal.Q_MIN - rp; // shift distance + long sbMask = -1L >>> 1 - sh; + + long nl1p = nl1 >>> sh; + long rb = nl1 >>> sh - 1; + long sb = (nl1 & sbMask | nl0) != 0 ? 1 : 0; + long corr = rb & (sb | nl1p) & 0b1; + double ul = nl1p + corr; + + long nh1p = nh1 >>> sh; + rb = nh1 >>> sh - 1; + sb = (nh1 & sbMask | nh0) != 0 ? 1 : 0; + corr = rb & (sb | nh1p) & 0b1; + double uh = nh1p + corr; + v = Math.scalb(ul, rp + sh); + if (ul == uh) { + /* + * Test cases: + * for ll = lh: "1.2e-320"; + * for ll ≠ lh: "1.2000000000000000003e-320"; + */ + return signed(v); + } + } else { + /* + * Here, bl + r ≤ MIN_EXPONENT - 127 < bh + r. + * As mentioned before, this means bh = bl + 1 and + * rp = MIN_EXPONENT - bl. + * As nh1 ≤ nl1 + 2, nl1 ≥ 2^57, bh = bl + 1 happens only if + * the most significant P + 2 bits in nl1 are all "1" bits, + * so wl = r_e(nl) = r_e(nh) = wh = 2^(bl+64), and + * thus v = vh = 2^(bl+127) = 2^MIN_EXPONENT = MIN_NORMAL. + */ + + /* + * Test cases: + * for ll = lh: "2.225073858507201383e-308" + * for ll ≠ lh: "2.2250738585072013831e-308" + */ + return signed(Double.MIN_NORMAL); + } + } + + /* + * Measurements show that the failure rate of the above fast paths + * on the outcomes of Double.toString() on uniformly distributed + * double bit patterns is around 0.04%. + * + * Here, v ≤ r_e(x) ≤ nextUp(v), with v = c 2^q (c, q are as in + * IEEE-754 2019). + * + * Let vr = v + ulp(v)/2 = (c + 1/2) 2^q, the number halfway between + * v and nextUp(v). + * With cr = (2 c + 1), qr = q - 1 we get vr = cr 2^qr. + */ + long bits = Double.doubleToRawLongBits(v); + int be = (int) ((bits & DoubleConsts.EXP_BIT_MASK) >>> DoubleConsts.SIGNIFICAND_WIDTH - 1); + int qr = be - (DoubleConsts.EXP_BIAS + DoubleConsts.SIGNIFICAND_WIDTH - 1) + - (be != 0 ? 1 : 0); + long cr = 2 * (bits & DoubleConsts.SIGNIF_BIT_MASK | (be != 0 ? DoubleToDecimal.C_MIN : 0)) + 1; + + /* + * The test vr ⋚ x is equivalent to cr 2^qr ⋚ f 10^ep. + * This is in turn equivalent to one of 4 cases, where all exponents + * are non-negative: + * ep ≥ 0 ∧ ep ≥ qr: cr ⋚ f 5^ep 2^(ep-qr) + * ep ≥ 0 ∧ ep < qr: cr 2^(qr-ep) ⋚ f 5^ep + * ep < 0 ∧ ep ≥ qr: cr 5^(-ep) ⋚ f 2^(ep-qr) + * ep < 0 ∧ ep < qr: cr 5^(-ep) 2^(qr-ep) ⋚ f + */ + FDBigInteger lhs = valueOfMulPow52(cr, Math.max(-ep, 0), Math.max(qr - ep, 0)); + FDBigInteger rhs = new FDBigInteger(fl, d, m, n) + .multByPow52(Math.max(ep, 0), Math.max(ep - qr, 0)); + int cmp = lhs.cmp(rhs); + v = Double.longBitsToDouble(cmp < 0 + ? bits + 1 + : cmp > 0 + ? bits + : bits + (bits & 0b1)); + return signed(v); } - /** - * Takes a FloatingDecimal, which we presumably just scanned in, - * and finds out what its value is, as a double. - * - * AS A SIDE EFFECT, SET roundDir TO INDICATE PREFERRED - * ROUNDING DIRECTION in case the result is really destined - * for a single-precision float. - */ - @Override - public double doubleValue() { - int kDigits = Math.min(nDigits, MAX_DECIMAL_DIGITS + 1); - // - // convert the lead kDigits to a long integer. - // - // (special performance hack: start to do it using int) - int iValue = (int) digits[0] - (int) '0'; - int iDigits = Math.min(kDigits, INT_DECIMAL_DIGITS); - for (int i = 1; i < iDigits; i++) { - iValue = iValue * 10 + (int) digits[i] - (int) '0'; - } - long lValue = (long) iValue; - for (int i = iDigits; i < kDigits; i++) { - lValue = lValue * 10L + (long) ((int) digits[i] - (int) '0'); - } - double dValue = (double) lValue; - int exp = decExponent - kDigits; - // - // lValue now contains a long integer with the value of - // the first kDigits digits of the number. - // dValue contains the (double) of the same. - // - - if (nDigits <= MAX_DECIMAL_DIGITS) { - // - // possibly an easy case. - // We know that the digits can be represented - // exactly. And if the exponent isn't too outrageous, - // the whole thing can be done with one operation, - // thus one rounding error. - // Note that all our constructors trim all leading and - // trailing zeros, so simple values (including zero) - // will always end up here - // - if (exp == 0 || dValue == 0.0) { - return (isNegative) ? -dValue : dValue; // small floating integer - } - else if (exp >= 0) { - if (exp <= MAX_SMALL_TEN) { - // - // Can get the answer with one operation, - // thus one roundoff. - // - double rValue = dValue * SMALL_10_POW[exp]; - return (isNegative) ? -rValue : rValue; - } - int slop = MAX_DECIMAL_DIGITS - kDigits; - if (exp <= MAX_SMALL_TEN + slop) { - // - // We can multiply dValue by 10^(slop) - // and it is still "small" and exact. - // Then we can multiply by 10^(exp-slop) - // with one rounding. - // - dValue *= SMALL_10_POW[slop]; - double rValue = dValue * SMALL_10_POW[exp - slop]; - return (isNegative) ? -rValue : rValue; - } - // - // Else we have a hard case with a positive exp. - // - } else { - if (exp >= -MAX_SMALL_TEN) { - // - // Can get the answer in one division. - // - double rValue = dValue / SMALL_10_POW[-exp]; - return (isNegative) ? -rValue : rValue; - } - // - // Else we have a hard case with a negative exp. - // - } - } - - // - // Harder cases: - // The sum of digits plus exponent is greater than - // what we think we can do with one error. - // - // Start by approximating the right answer by, - // naively, scaling by powers of 10. - // - if (exp > 0) { - if (decExponent > MAX_DECIMAL_EXPONENT + 1) { - // - // Lets face it. This is going to be - // Infinity. Cut to the chase. - // - return (isNegative) ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY; - } - if ((exp & 15) != 0) { - dValue *= SMALL_10_POW[exp & 15]; - } - if ((exp >>= 4) != 0) { - int j; - for (j = 0; exp > 1; j++, exp >>= 1) { - if ((exp & 1) != 0) { - dValue *= BIG_10_POW[j]; - } - } - // - // The reason for the weird exp > 1 condition - // in the above loop was so that the last multiply - // would get unrolled. We handle it here. - // It could overflow. - // - double t = dValue * BIG_10_POW[j]; - if (Double.isInfinite(t)) { - // - // It did overflow. - // Look more closely at the result. - // If the exponent is just one too large, - // then use the maximum finite as our estimate - // value. Else call the result infinity - // and punt it. - // ( I presume this could happen because - // rounding forces the result here to be - // an ULP or two larger than - // Double.MAX_VALUE ). - // - t = dValue / 2.0; - t *= BIG_10_POW[j]; - if (Double.isInfinite(t)) { - return (isNegative) ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY; - } - t = Double.MAX_VALUE; - } - dValue = t; - } - } else if (exp < 0) { - exp = -exp; - if (decExponent < MIN_DECIMAL_EXPONENT - 1) { - // - // Lets face it. This is going to be - // zero. Cut to the chase. - // - return (isNegative) ? -0.0 : 0.0; - } - if ((exp & 15) != 0) { - dValue /= SMALL_10_POW[exp & 15]; - } - if ((exp >>= 4) != 0) { - int j; - for (j = 0; exp > 1; j++, exp >>= 1) { - if ((exp & 1) != 0) { - dValue *= TINY_10_POW[j]; - } - } - // - // The reason for the weird exp > 1 condition - // in the above loop was so that the last multiply - // would get unrolled. We handle it here. - // It could underflow. - // - double t = dValue * TINY_10_POW[j]; - if (t == 0.0) { - // - // It did underflow. - // Look more closely at the result. - // If the exponent is just one too small, - // then use the minimum finite as our estimate - // value. Else call the result 0.0 - // and punt it. - // ( I presume this could happen because - // rounding forces the result here to be - // an ULP or two less than - // Double.MIN_VALUE ). - // - t = dValue * 2.0; - t *= TINY_10_POW[j]; - if (t == 0.0) { - return (isNegative) ? -0.0 : 0.0; - } - t = Double.MIN_VALUE; - } - dValue = t; - } - } - - // - // dValue is now approximately the result. - // The hard part is adjusting it, by comparison - // with FDBigInteger arithmetic. - // Formulate the EXACT big-number result as - // bigD0 * 10^exp - // - if (nDigits > MAX_NDIGITS) { - nDigits = MAX_NDIGITS + 1; - digits[MAX_NDIGITS] = '1'; - } - FDBigInteger bigD0 = new FDBigInteger(lValue, digits, kDigits, nDigits); - exp = decExponent - nDigits; - - long ieeeBits = Double.doubleToRawLongBits(dValue); // IEEE-754 bits of double candidate - final int B5 = Math.max(0, -exp); // powers of 5 in bigB, value is not modified inside correctionLoop - final int D5 = Math.max(0, exp); // powers of 5 in bigD, value is not modified inside correctionLoop - bigD0 = bigD0.multByPow52(D5, 0); - bigD0.makeImmutable(); // prevent bigD0 modification inside correctionLoop - FDBigInteger bigD = null; - int prevD2 = 0; - - correctionLoop: - while (true) { - // here ieeeBits can't be NaN, Infinity or zero - int binexp = (int) (ieeeBits >>> EXP_SHIFT); - long bigBbits = ieeeBits & DoubleConsts.SIGNIF_BIT_MASK; - if (binexp > 0) { - bigBbits |= FRACT_HOB; - } else { // Normalize denormalized numbers. - assert bigBbits != 0L : bigBbits; // doubleToBigInt(0.0) - int leadingZeros = Long.numberOfLeadingZeros(bigBbits); - int shift = leadingZeros - (63 - EXP_SHIFT); - bigBbits <<= shift; - binexp = 1 - shift; - } - binexp -= DoubleConsts.EXP_BIAS; - int lowOrderZeros = Long.numberOfTrailingZeros(bigBbits); - bigBbits >>>= lowOrderZeros; - final int bigIntExp = binexp - EXP_SHIFT + lowOrderZeros; - final int bigIntNBits = EXP_SHIFT + 1 - lowOrderZeros; - - // - // Scale bigD, bigB appropriately for - // big-integer operations. - // Naively, we multiply by powers of ten - // and powers of two. What we actually do - // is keep track of the powers of 5 and - // powers of 2 we would use, then factor out - // common divisors before doing the work. - // - int B2 = B5; // powers of 2 in bigB - int D2 = D5; // powers of 2 in bigD - int Ulp2; // powers of 2 in halfUlp. - if (bigIntExp >= 0) { - B2 += bigIntExp; - } else { - D2 -= bigIntExp; - } - Ulp2 = B2; - // shift bigB and bigD left by a number s. t. - // halfUlp is still an integer. - int hulpbias; - if (binexp <= -DoubleConsts.EXP_BIAS) { - // This is going to be a denormalized number - // (if not actually zero). - // half an ULP is at 2^-(DoubleConsts.EXP_BIAS+EXP_SHIFT+1) - hulpbias = binexp + lowOrderZeros + DoubleConsts.EXP_BIAS; - } else { - hulpbias = 1 + lowOrderZeros; - } - B2 += hulpbias; - D2 += hulpbias; - // if there are common factors of 2, we might just as well - // factor them out, as they add nothing useful. - int common2 = Math.min(B2, Math.min(D2, Ulp2)); - B2 -= common2; - D2 -= common2; - Ulp2 -= common2; - // do multiplications by powers of 5 and 2 - FDBigInteger bigB = FDBigInteger.valueOfMulPow52(bigBbits, B5, B2); - if (bigD == null || prevD2 != D2) { - bigD = bigD0.leftShift(D2); - prevD2 = D2; - } - // - // to recap: - // bigB is the scaled-big-int version of our floating-point - // candidate. - // bigD is the scaled-big-int version of the exact value - // as we understand it. - // halfUlp is 1/2 an ulp of bigB, except for special cases - // of exact powers of 2 - // - // the plan is to compare bigB with bigD, and if the difference - // is less than halfUlp, then we're satisfied. Otherwise, - // use the ratio of difference to halfUlp to calculate a fudge - // factor to add to the floating value, then go 'round again. - // - FDBigInteger diff; - int cmpResult; - boolean overvalue; - if ((cmpResult = bigB.cmp(bigD)) > 0) { - overvalue = true; // our candidate is too big. - diff = bigB.leftInplaceSub(bigD); // bigB is not user further - reuse - if ((bigIntNBits == 1) && (bigIntExp > -DoubleConsts.EXP_BIAS + 1)) { - // candidate is a normalized exact power of 2 and - // is too big (larger than Double.MIN_NORMAL). We will be subtracting. - // For our purposes, ulp is the ulp of the - // next smaller range. - Ulp2 -= 1; - if (Ulp2 < 0) { - // rats. Cannot de-scale ulp this far. - // must scale diff in other direction. - Ulp2 = 0; - diff = diff.leftShift(1); - } - } - } else if (cmpResult < 0) { - overvalue = false; // our candidate is too small. - diff = bigD.rightInplaceSub(bigB); // bigB is not user further - reuse - } else { - // the candidate is exactly right! - // this happens with surprising frequency - break correctionLoop; - } - cmpResult = diff.cmpPow52(B5, Ulp2); - if ((cmpResult) < 0) { - // difference is small. - // this is close enough - break correctionLoop; - } else if (cmpResult == 0) { - // difference is exactly half an ULP - // round to some other value maybe, then finish - if ((ieeeBits & 1) != 0) { // half ties to even - ieeeBits += overvalue ? -1 : 1; // nextDown or nextUp - } - break correctionLoop; - } else { - // difference is non-trivial. - // could scale addend by ratio of difference to - // halfUlp here, if we bothered to compute that difference. - // Most of the time ( I hope ) it is about 1 anyway. - ieeeBits += overvalue ? -1 : 1; // nextDown or nextUp - if (ieeeBits == 0 || ieeeBits == DoubleConsts.EXP_BIT_MASK) { // 0.0 or Double.POSITIVE_INFINITY - break correctionLoop; // oops. Fell off end of range. - } - continue; // try again. - } - - } - if (isNegative) { - ieeeBits |= DoubleConsts.SIGN_BIT_MASK; - } - return Double.longBitsToDouble(ieeeBits); + private double signed(double v) { + return isNegative ? -v : v; } - /** - * Takes a FloatingDecimal, which we presumably just scanned in, - * and finds out what its value is, as a float. - * This is distinct from doubleValue() to avoid the extremely - * unlikely case of a double rounding error, wherein the conversion - * to double has one rounding error, and the conversion of that double - * to a float has another rounding error, IN THE WRONG DIRECTION, - * ( because of the preference to a zero low-order bit ). - */ - @Override - public float floatValue() { - int kDigits = Math.min(nDigits, SINGLE_MAX_DECIMAL_DIGITS + 1); - // - // convert the lead kDigits to an integer. - // - int iValue = (int) digits[0] - (int) '0'; - for (int i = 1; i < kDigits; i++) { - iValue = iValue * 10 + (int) digits[i] - (int) '0'; + private float floatValue() { + /* For details not covered here, see comments in doubleValue(). */ + if (e <= E_THR_Z[BINARY_32_IX]) { + return signed(0.0f); } - float fValue = (float) iValue; - int exp = decExponent - kDigits; - // - // iValue now contains an integer with the value of - // the first kDigits digits of the number. - // fValue contains the (float) of the same. - // + if (e >= E_THR_I[BINARY_32_IX]) { + return signed(Float.POSITIVE_INFINITY); + } + int n = this.n; + int ep = e - n; + float v; + int m = Math.min(n, MathUtils.N); + long fl = toLong(m); + if (n <= MathUtils.N && 0 <= ep && e <= MathUtils.N) { + fl *= MathUtils.pow10(ep); // 0 ≤ ep < 19 + v = fl >= 0 ? fl : 2.0f * (fl >>> 1 | fl & 0b1); + return signed(v); + } + if (n <= FLOG_10_MAX_LONG && -SINGLE_MAX_SMALL_TEN <= ep) { + v = fl; + boolean isExact = (long) v == fl; + if (isExact && ep <= SINGLE_MAX_SMALL_TEN) { + v = ep >= 0 ? v * SINGLE_SMALL_10_POW[ep] : v / SINGLE_SMALL_10_POW[-ep]; + return signed(v); + } + /* + * The similar case in doubleValue() where fl is exact and + * ep is somewhat larger than MAX_SMALL_TEN is already covered + * above for float. + */ + } + long ll = fl; + long lh; + if (n <= MathUtils.N) { + ll *= MathUtils.pow10(MathUtils.N - n); + lh = ll; + } else { + lh = ll + 1; + } + int el = e - MathUtils.N; + int rp = MathUtils.flog2pow10(el) + 2; + long g1 = MathUtils.g1(el); + long nl1 = Math.unsignedMultiplyHigh(ll, g1); + long nl0 = ll * g1; + long nh1 = Math.unsignedMultiplyHigh(lh, g1 + 1); + long nh0 = lh * (g1 + 1); + int bl = Long.SIZE - Long.numberOfLeadingZeros(nl1); + if (bl + rp > Float.MIN_EXPONENT) { + float ul = nl1 | (nl0 != 0 ? 1 : 0); + float uh = nh1 | (nh0 != 0 ? 1 : 0); + v = Math.scalb(ul, rp); + if (ul == uh || v == Float.POSITIVE_INFINITY) { + return signed(v); + } + } else { + int bh = Long.SIZE - Long.numberOfLeadingZeros(nh1); + if (bh + rp <= Float.MIN_EXPONENT) { + int sh = FloatToDecimal.Q_MIN - rp; + long sbMask = -1L >>> 1 - sh; - if (nDigits <= SINGLE_MAX_DECIMAL_DIGITS) { - // - // possibly an easy case. - // We know that the digits can be represented - // exactly. And if the exponent isn't too outrageous, - // the whole thing can be done with one operation, - // thus one rounding error. - // Note that all our constructors trim all leading and - // trailing zeros, so simple values (including zero) - // will always end up here. - // - if (exp == 0 || fValue == 0.0f) { - return (isNegative) ? -fValue : fValue; // small floating integer - } else if (exp >= 0) { - if (exp <= SINGLE_MAX_SMALL_TEN) { - // - // Can get the answer with one operation, - // thus one roundoff. - // - fValue *= SINGLE_SMALL_10_POW[exp]; - return (isNegative) ? -fValue : fValue; + long nl1p = nl1 >>> sh; + long rb = nl1 >>> sh - 1; + long sb = (nl1 & sbMask | nl0) != 0 ? 1 : 0; + long corr = rb & (sb | nl1p) & 0b1; + float ul = nl1p + corr; + + long nh1p = nh1 >>> sh; + rb = nh1 >>> sh - 1; + sb = (nh1 & sbMask | nh0) != 0 ? 1 : 0; + corr = rb & (sb | nh1p) & 0b1; + float uh = nh1p + corr; + v = Math.scalb(ul, rp + sh); + if (ul == uh) { + return signed(v); } - int slop = SINGLE_MAX_DECIMAL_DIGITS - kDigits; - if (exp <= SINGLE_MAX_SMALL_TEN + slop) { - // - // We can multiply fValue by 10^(slop) - // and it is still "small" and exact. - // Then we can multiply by 10^(exp-slop) - // with one rounding. - // - fValue *= SINGLE_SMALL_10_POW[slop]; - fValue *= SINGLE_SMALL_10_POW[exp - slop]; - return (isNegative) ? -fValue : fValue; - } - // - // Else we have a hard case with a positive exp. - // } else { - if (exp >= -SINGLE_MAX_SMALL_TEN) { - // - // Can get the answer in one division. - // - fValue /= SINGLE_SMALL_10_POW[-exp]; - return (isNegative) ? -fValue : fValue; - } - // - // Else we have a hard case with a negative exp. - // - } - } else if ((decExponent >= nDigits) && (nDigits + decExponent <= MAX_DECIMAL_DIGITS)) { - // - // In double-precision, this is an exact floating integer. - // So we can compute to double, then shorten to float - // with one round, and get the right answer. - // - // First, finish accumulating digits. - // Then convert that integer to a double, multiply - // by the appropriate power of ten, and convert to float. - // - long lValue = (long) iValue; - for (int i = kDigits; i < nDigits; i++) { - lValue = lValue * 10L + (long) ((int) digits[i] - (int) '0'); - } - double dValue = (double) lValue; - exp = decExponent - nDigits; - dValue *= SMALL_10_POW[exp]; - fValue = (float) dValue; - return (isNegative) ? -fValue : fValue; - - } - // - // Harder cases: - // The sum of digits plus exponent is greater than - // what we think we can do with one error. - // - // Start by approximating the right answer by, - // naively, scaling by powers of 10. - // Scaling uses doubles to avoid overflow/underflow. - // - double dValue = fValue; - if (exp > 0) { - if (decExponent > SINGLE_MAX_DECIMAL_EXPONENT + 1) { - // - // Lets face it. This is going to be - // Infinity. Cut to the chase. - // - return (isNegative) ? Float.NEGATIVE_INFINITY : Float.POSITIVE_INFINITY; - } - if ((exp & 15) != 0) { - dValue *= SMALL_10_POW[exp & 15]; - } - if ((exp >>= 4) != 0) { - int j; - for (j = 0; exp > 0; j++, exp >>= 1) { - if ((exp & 1) != 0) { - dValue *= BIG_10_POW[j]; - } - } - } - } else if (exp < 0) { - exp = -exp; - if (decExponent < SINGLE_MIN_DECIMAL_EXPONENT - 1) { - // - // Lets face it. This is going to be - // zero. Cut to the chase. - // - return (isNegative) ? -0.0f : 0.0f; - } - if ((exp & 15) != 0) { - dValue /= SMALL_10_POW[exp & 15]; - } - if ((exp >>= 4) != 0) { - int j; - for (j = 0; exp > 0; j++, exp >>= 1) { - if ((exp & 1) != 0) { - dValue *= TINY_10_POW[j]; - } - } + return signed(Float.MIN_NORMAL); } } - fValue = Math.clamp((float) dValue, Float.MIN_VALUE, Float.MAX_VALUE); - - // - // fValue is now approximately the result. - // The hard part is adjusting it, by comparison - // with FDBigInteger arithmetic. - // Formulate the EXACT big-number result as - // bigD0 * 10^exp - // - if (nDigits > SINGLE_MAX_NDIGITS) { - nDigits = SINGLE_MAX_NDIGITS + 1; - digits[SINGLE_MAX_NDIGITS] = '1'; - } - FDBigInteger bigD0 = new FDBigInteger(iValue, digits, kDigits, nDigits); - exp = decExponent - nDigits; - - int ieeeBits = Float.floatToRawIntBits(fValue); // IEEE-754 bits of float candidate - final int B5 = Math.max(0, -exp); // powers of 5 in bigB, value is not modified inside correctionLoop - final int D5 = Math.max(0, exp); // powers of 5 in bigD, value is not modified inside correctionLoop - bigD0 = bigD0.multByPow52(D5, 0); - bigD0.makeImmutable(); // prevent bigD0 modification inside correctionLoop - FDBigInteger bigD = null; - int prevD2 = 0; - - correctionLoop: - while (true) { - // here ieeeBits can't be NaN, Infinity or zero - int binexp = ieeeBits >>> SINGLE_EXP_SHIFT; - int bigBbits = ieeeBits & FloatConsts.SIGNIF_BIT_MASK; - if (binexp > 0) { - bigBbits |= SINGLE_FRACT_HOB; - } else { // Normalize denormalized numbers. - assert bigBbits != 0 : bigBbits; // floatToBigInt(0.0) - int leadingZeros = Integer.numberOfLeadingZeros(bigBbits); - int shift = leadingZeros - (31 - SINGLE_EXP_SHIFT); - bigBbits <<= shift; - binexp = 1 - shift; - } - binexp -= FloatConsts.EXP_BIAS; - int lowOrderZeros = Integer.numberOfTrailingZeros(bigBbits); - bigBbits >>>= lowOrderZeros; - final int bigIntExp = binexp - SINGLE_EXP_SHIFT + lowOrderZeros; - final int bigIntNBits = SINGLE_EXP_SHIFT + 1 - lowOrderZeros; - - // - // Scale bigD, bigB appropriately for - // big-integer operations. - // Naively, we multiply by powers of ten - // and powers of two. What we actually do - // is keep track of the powers of 5 and - // powers of 2 we would use, then factor out - // common divisors before doing the work. - // - int B2 = B5; // powers of 2 in bigB - int D2 = D5; // powers of 2 in bigD - int Ulp2; // powers of 2 in halfUlp. - if (bigIntExp >= 0) { - B2 += bigIntExp; - } else { - D2 -= bigIntExp; - } - Ulp2 = B2; - // shift bigB and bigD left by a number s. t. - // halfUlp is still an integer. - int hulpbias; - if (binexp <= -FloatConsts.EXP_BIAS) { - // This is going to be a denormalized number - // (if not actually zero). - // half an ULP is at 2^-(FloatConsts.EXP_BIAS+SINGLE_EXP_SHIFT+1) - hulpbias = binexp + lowOrderZeros + FloatConsts.EXP_BIAS; - } else { - hulpbias = 1 + lowOrderZeros; - } - B2 += hulpbias; - D2 += hulpbias; - // if there are common factors of 2, we might just as well - // factor them out, as they add nothing useful. - int common2 = Math.min(B2, Math.min(D2, Ulp2)); - B2 -= common2; - D2 -= common2; - Ulp2 -= common2; - // do multiplications by powers of 5 and 2 - FDBigInteger bigB = FDBigInteger.valueOfMulPow52(bigBbits, B5, B2); - if (bigD == null || prevD2 != D2) { - bigD = bigD0.leftShift(D2); - prevD2 = D2; - } - // - // to recap: - // bigB is the scaled-big-int version of our floating-point - // candidate. - // bigD is the scaled-big-int version of the exact value - // as we understand it. - // halfUlp is 1/2 an ulp of bigB, except for special cases - // of exact powers of 2 - // - // the plan is to compare bigB with bigD, and if the difference - // is less than halfUlp, then we're satisfied. Otherwise, - // use the ratio of difference to halfUlp to calculate a fudge - // factor to add to the floating value, then go 'round again. - // - FDBigInteger diff; - int cmpResult; - boolean overvalue; - if ((cmpResult = bigB.cmp(bigD)) > 0) { - overvalue = true; // our candidate is too big. - diff = bigB.leftInplaceSub(bigD); // bigB is not user further - reuse - if ((bigIntNBits == 1) && (bigIntExp > -FloatConsts.EXP_BIAS + 1)) { - // candidate is a normalized exact power of 2 and - // is too big (larger than Float.MIN_NORMAL). We will be subtracting. - // For our purposes, ulp is the ulp of the - // next smaller range. - Ulp2 -= 1; - if (Ulp2 < 0) { - // rats. Cannot de-scale ulp this far. - // must scale diff in other direction. - Ulp2 = 0; - diff = diff.leftShift(1); - } - } - } else if (cmpResult < 0) { - overvalue = false; // our candidate is too small. - diff = bigD.rightInplaceSub(bigB); // bigB is not user further - reuse - } else { - // the candidate is exactly right! - // this happens with surprising frequency - break correctionLoop; - } - cmpResult = diff.cmpPow52(B5, Ulp2); - if ((cmpResult) < 0) { - // difference is small. - // this is close enough - break correctionLoop; - } else if (cmpResult == 0) { - // difference is exactly half an ULP - // round to some other value maybe, then finish - if ((ieeeBits & 1) != 0) { // half ties to even - ieeeBits += overvalue ? -1 : 1; // nextDown or nextUp - } - break correctionLoop; - } else { - // difference is non-trivial. - // could scale addend by ratio of difference to - // halfUlp here, if we bothered to compute that difference. - // Most of the time ( I hope ) it is about 1 anyway. - ieeeBits += overvalue ? -1 : 1; // nextDown or nextUp - if (ieeeBits == 0 || ieeeBits == FloatConsts.EXP_BIT_MASK) { // 0.0 or Float.POSITIVE_INFINITY - break correctionLoop; // oops. Fell off end of range. - } - continue; // try again. - } - - } - if (isNegative) { - ieeeBits |= FloatConsts.SIGN_BIT_MASK; - } - return Float.intBitsToFloat(ieeeBits); + int bits = Float.floatToRawIntBits(v); + int be = (bits & FloatConsts.EXP_BIT_MASK) >>> FloatConsts.SIGNIFICAND_WIDTH - 1; + int qr = be - (FloatConsts.EXP_BIAS + FloatConsts.SIGNIFICAND_WIDTH - 1) + - (be != 0 ? 1 : 0); + int cr = 2 * (bits & FloatConsts.SIGNIF_BIT_MASK | (be != 0 ? FloatToDecimal.C_MIN : 0)) + 1; + FDBigInteger lhs = valueOfMulPow52(cr, Math.max(-ep, 0), Math.max(qr - ep, 0)); + FDBigInteger rhs = new FDBigInteger(fl, d, m, n) + .multByPow52(Math.max(ep, 0), Math.max(ep - qr, 0)); + int cmp = lhs.cmp(rhs); + v = Float.intBitsToFloat(cmp < 0 + ? bits + 1 + : cmp > 0 + ? bits + : bits + (bits & 0b1)); + return signed(v); } + private float signed(float v) { + return isNegative ? -v : v; + } - /** - * All the positive powers of 10 that can be - * represented exactly in double/float. - */ + /* All the powers of 10 that can be represented exactly in double. */ + @Stable private static final double[] SMALL_10_POW = { - 1.0e0, - 1.0e1, 1.0e2, 1.0e3, 1.0e4, 1.0e5, - 1.0e6, 1.0e7, 1.0e8, 1.0e9, 1.0e10, - 1.0e11, 1.0e12, 1.0e13, 1.0e14, 1.0e15, - 1.0e16, 1.0e17, 1.0e18, 1.0e19, 1.0e20, - 1.0e21, 1.0e22 + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, + 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, + 1e20, 1e21, 1e22, }; + /* All the powers of 10 that can be represented exactly in float. */ + @Stable private static final float[] SINGLE_SMALL_10_POW = { - 1.0e0f, - 1.0e1f, 1.0e2f, 1.0e3f, 1.0e4f, 1.0e5f, - 1.0e6f, 1.0e7f, 1.0e8f, 1.0e9f, 1.0e10f + 1e0f, 1e1f, 1e2f, 1e3f, 1e4f, 1e5f, 1e6f, 1e7f, 1e8f, 1e9f, + 1e10f, }; - private static final double[] BIG_10_POW = { - 1e16, 1e32, 1e64, 1e128, 1e256 }; - private static final double[] TINY_10_POW = { - 1e-16, 1e-32, 1e-64, 1e-128, 1e-256 }; - - private static final int MAX_SMALL_TEN = SMALL_10_POW.length-1; - private static final int SINGLE_MAX_SMALL_TEN = SINGLE_SMALL_10_POW.length-1; + private static final int MAX_SMALL_TEN = SMALL_10_POW.length - 1; + private static final int SINGLE_MAX_SMALL_TEN = SINGLE_SMALL_10_POW.length - 1; } @@ -1508,13 +1204,13 @@ public class FloatingDecimal{ * @param compat compatibility with releases < JDK 21 * @return The converter. */ - public static BinaryToASCIIConverter getBinaryToASCIIConverter(double d, boolean compat) { + public static BinaryToASCIIBuffer getBinaryToASCIIConverter(double d, boolean compat) { return compat - ? getCompatBinaryToASCIIConverter(d, true) + ? getCompatBinaryToASCIIConverter(d) : getBinaryToASCIIConverter(d); } - private static BinaryToASCIIConverter getBinaryToASCIIConverter(double d) { + private static BinaryToASCIIBuffer getBinaryToASCIIConverter(double d) { assert Double.isFinite(d); FormattedFPDecimal dec = FormattedFPDecimal.split(d); @@ -1541,7 +1237,7 @@ public class FloatingDecimal{ * Should be removed in the future, along with its dependent methods and * fields (> 550 lines). */ - private static BinaryToASCIIConverter getCompatBinaryToASCIIConverter(double d, boolean isCompatibleFormat) { + private static BinaryToASCIIBuffer getCompatBinaryToASCIIConverter(double d) { long dBits = Double.doubleToRawLongBits(d); boolean isNegative = (dBits&DoubleConsts.SIGN_BIT_MASK) != 0; // discover sign long fractBits = dBits & DoubleConsts.SIGNIF_BIT_MASK; @@ -1575,34 +1271,23 @@ public class FloatingDecimal{ } binExp -= DoubleConsts.EXP_BIAS; BinaryToASCIIBuffer buf = getBinaryToASCIIBuffer(); - buf.setSign(isNegative); // call the routine that actually does all the hard work. - buf.dtoa(binExp, fractBits, nSignificantBits, isCompatibleFormat); + buf.dtoa(binExp, fractBits, nSignificantBits); return buf; } - static ASCIIToBinaryConverter readDoubleSignlessDigits(int decExp, byte[] digits, int length) { - - // Prevent an extreme negative exponent from causing overflow issues in doubleValue(). - // Large positive values are handled within doubleValue(); - if (decExp < MIN_DECIMAL_EXPONENT) { - return A2BC_POSITIVE_ZERO; - } - return new ASCIIToBinaryBuffer(false, decExp, digits, length); - } - /** * The input must match the {@link Double#valueOf(String) rules described here}, * about leading and trailing whitespaces, and the grammar. * * @param in the non-null input * @param ix one of the {@code BINARY__IX} constants, where {@code } - * is one of 16, 32, 64 + * is one of 16, 32, 64 * @return an appropriate binary converter - * @throws NullPointerException if the input is null + * @throws NullPointerException if the input is null * @throws NumberFormatException if the input is malformed */ - static ASCIIToBinaryConverter readJavaFormatString(String in, int ix) { + private static double readJavaFormatString(String in, int ix) { /* * The scanning proper does not allocate any object, * nor does it perform any costly computation. @@ -1624,6 +1309,7 @@ public class FloatingDecimal{ * 23 for BINARY_16_IX (Float16, once integrated in java.base) * 114 for BINARY_32_IX (float) * 769 for BINARY_64_IX (double) + * but is much shorter in common cases. */ int len = in.length(); // fail fast on null @@ -1647,11 +1333,11 @@ public class FloatingDecimal{ ch = in.charAt(i); if (ch == 'I') { scanSymbolic(in, i, INFINITY_REP); - return ssign != '-' ? A2BC_POSITIVE_INFINITY : A2BC_NEGATIVE_INFINITY; + return ssign != '-' ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY; } if (ch == 'N') { scanSymbolic(in, i, NAN_REP); - return A2BC_NOT_A_NUMBER; // ignore sign + return Double.NaN; // ignore sign } if (ch == '0' && i + 1 < len && toLowerCase(in.charAt(i + 1)) == 'x') { isDec = false; @@ -1730,7 +1416,7 @@ public class FloatingDecimal{ /* By now, the input is syntactically correct. */ if (tnz == 0) { // all zero digits, so ignore ep and point - return ssign != '-' ? A2BC_POSITIVE_ZERO : A2BC_NEGATIVE_ZERO; + return ssign != '-' ? 0.0 : -0.0; } /* @@ -1785,7 +1471,7 @@ public class FloatingDecimal{ ep += emult * (pt - tnz); if (pt > tnz) { // '.' was counted as a position, adjust ep ep -= emult; - } else if (lz < pt) { // lz < pt <= tnz + } else if (lz < pt) { // lz < pt ≤ tnz n -= 1; } } @@ -1797,8 +1483,8 @@ public class FloatingDecimal{ * The magnitude x of the input meets * x = f 10^ep (decimal) * x = f 2^ep (hexadecimal) - * Integer f = consists of the n decimal or hexadecimal - * digits found in part [lz, tnz) of the input, and f_1 != 0, f_n != 0. + * Integer f = consists of the n decimal or hexadecimal + * digits found in part [lz, tnz) of the input, and d_1 > 0, d_n > 0. */ if (!isDec) { // hexadecimal conversion is performed entirely here @@ -1824,19 +1510,19 @@ public class FloatingDecimal{ int bl = Long.SIZE - Long.numberOfLeadingZeros(c); // bitlength /* - * Let x = c 2^ep, so 2^(ep+bl-1) <= x < 2^(ep+bl) + * Let x = c 2^ep, so 2^(ep+bl-1) ≤ x < 2^(ep+bl) * When ep + bl < Q_MIN then x certainly rounds to zero. * When ep + bl > QE_MAX then x surely rounds to infinity. */ if (ep < Q_MIN[ix] - bl) { - return ssign != '-' ? A2BC_POSITIVE_ZERO : A2BC_NEGATIVE_ZERO; + return ssign != '-' ? 0.0 : -0.0; } - if (ep > QE_MAX[ix] - bl) { - return ssign != '-' ? A2BC_POSITIVE_INFINITY : A2BC_NEGATIVE_INFINITY; + if (ep > QP_MAX[ix] - bl) { + return ssign != '-' ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY; } int q = (int) ep; // narrowing conversion is safe int shr; // (sh)ift to (r)ight iff shr > 0 - if (q >= QE_MIN[ix] - bl) { + if (q >= QP_MIN[ix] - bl) { shr = bl - P[ix]; q += shr; } else { @@ -1860,10 +1546,8 @@ public class FloatingDecimal{ /* For now throw on BINARY_16_IX, until Float16 is integrated in java.base. */ return switch (ix) { - case BINARY_32_IX -> - new PreparedASCIIToBinaryBuffer(Double.NaN, buildFloat(ssign, q, c)); - case BINARY_64_IX -> - new PreparedASCIIToBinaryBuffer(buildDouble(ssign, q, c), Float.NaN); + case BINARY_32_IX -> buildFloat(ssign, q, c); + case BINARY_64_IX -> buildDouble(ssign, q, c); default -> throw new AssertionError("unexpected"); }; } @@ -1873,27 +1557,27 @@ public class FloatingDecimal{ * rely on another method to do the (sometimes intensive) math conversion. * * Define e = n + ep, which leads to - * x = 0.d_1 ... d_n 10^e, 10^(e-1) <= x < 10^e - * If e <= E_THR_Z then x rounds to zero. - * Similarly, if e >= E_THR_I then x rounds to infinity. + * x = 0.d_1 ... d_n 10^e, 10^(e-1) ≤ x < 10^e + * If e ≤ E_THR_Z then x rounds to zero. + * Similarly, if e ≥ E_THR_I then x rounds to infinity. * We return immediately in these cases. * Otherwise, e fits in an int, aptly named e as well. */ int e = Math.clamp(ep + n, E_THR_Z[ix], E_THR_I[ix]); - if (e == E_THR_Z[ix]) { // true e <= E_THR_Z - return ssign != '-' ? A2BC_POSITIVE_ZERO : A2BC_NEGATIVE_ZERO; + if (e == E_THR_Z[ix]) { // the true mathematical e ≤ E_THR_Z + return ssign != '-' ? 0.0 : -0.0; } - if (e == E_THR_I[ix]) { // true e >= E_THR_I - return ssign != '-' ? A2BC_POSITIVE_INFINITY : A2BC_NEGATIVE_INFINITY; + if (e == E_THR_I[ix]) { // the true mathematical e ≥ E_THR_I + return ssign != '-' ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY; } /* * For further considerations, x also needs to be seen as * x = beta 2^q * with real beta and integer q meeting - * q >= Q_MIN + * q ≥ Q_MIN * and - * either 2^(P-1) <= beta < 2^P + * either 2^(P-1) ≤ beta < 2^P * or 0 < beta < 2^(P-1) and q = Q_MIN * The (unique) solution is * q = max(floor(log2(x)) - (P-1), Q_MIN), beta = x 2^(-q) @@ -1904,15 +1588,15 @@ public class FloatingDecimal{ * ql = max(floor((e-1) log2(10)) - (P-1), Q_MIN) * qh = max(floor(e log2(10)) - (P-1), Q_MIN) * then the following hold - * ql <= q <= qh, and qh - ql <= 4 + * ql ≤ q ≤ qh, and qh - ql ≤ 4 * Since by now e is relatively small, we can leverage flog2pow10(). * * Consider the half-open interval [ 2^(P-1+q), 2^(P+q) ). * It contains all floating-point values of the form - * c 2^q, c integer, 2^(P-1) <= c < 2^P (normal values) + * c 2^q, c integer, 2^(P-1) ≤ c < 2^P (normal values) * When q = Q_MIN also consider the interval half-open [0, 2^(P-1+q) ), * which contains all floating-point values of the form - * c 2^q, c integer, 0 <= c < 2^(P-1) (subnormal values and zero) + * c 2^q, c integer, 0 ≤ c < 2^(P-1) (subnormal values and zero) * For these c values, all numbers of the form * (c + 1/2) 2^q * also belong to the intervals. @@ -1927,12 +1611,12 @@ public class FloatingDecimal{ * (Well, the sticky digit is only needed when the integer part * coincides with a boundary, but that's hard to detect at this stage. * Adding the sticky digit is always safe.) - * If n > e we pass the digits d_1...d_e 3 (3 is as good as any other + * If n > e we pass the digits (8 is as good as any other * non-zero sticky digit) and the exponent e to the conversion routine. - * If n <= e we pass all the digits d_1...d_n (no sticky digit, + * If n ≤ e we pass all the digits (no sticky digit, * as the fractional part is empty) and the exponent e to the converter. * - * Now assume qh <= 0, so q <= 0. + * Now assume qh ≤ 0, so q ≤ 0. * The boundaries (c + 1/2) 2^q = (2c + 1) 2^(q-1) have a fractional part * of 1 - q digits: some (or zero) leading zeros, the rightmost is 5. * A correct rounding needs to retain the integer part of x (if any), @@ -1943,25 +1627,31 @@ public class FloatingDecimal{ * But let's keep it simple for now.) * However, q is unknown, so use the conservative ql instead. * More precisely, if n > e + 1 - ql we pass the leftmost e + 1 - ql - * digits of f, sticky 3, and e. - * Otherwise, n <= e + 1 - ql. + * digits of f, sticky 8 (the "most even" digit), and e. + * Otherwise, n ≤ e + 1 - ql. * We pass all n digits of f, no sticky digit, and e to the converter. * - * Otherwise, ql <= 0 < qh, so -4 < q <= 4. + * Otherwise, ql ≤ 0 < qh, so -4 < q ≤ 4. * Again, since q is not known exactly, we proceed as in the previous * case, with ql as a safe replacement for q. */ - // TODO insert logic for small n: 9 for float, 18 for double int ql = Math.max(MathUtils.flog2pow10(e - 1) - (P[ix] - 1), Q_MIN[ix]); int np = e + Math.max(2 - ql, 1); - byte[] digits = new byte[Math.min(n, np)]; + byte[] d = new byte[Math.min(n, np)]; if (n >= np) { - copyDigits(in, digits, np - 1, lz); - digits[np - 1] = '3'; // append any non-zero sticky digit + copyDigits(in, d, np - 1, lz); + d[np - 1] = '8'; // append the "most even" non-zero sticky digit } else { - copyDigits(in, digits, n, lz); + copyDigits(in, d, n, lz); } - return new ASCIIToBinaryBuffer(ssign == '-', e, digits, digits.length); + /* For now throw on BINARY_16_IX, until Float16 is integrated in java.base. */ + return switch (ix) { + case BINARY_32_IX -> + new ASCIIToBinaryBuffer(ssign == '-', e, d, d.length).floatValue(); + case BINARY_64_IX -> + new ASCIIToBinaryBuffer(ssign == '-', e, d, d.length).doubleValue(); + default -> throw new AssertionError("unexpected"); + }; } private static int toLowerCase(int ch) { @@ -1988,17 +1678,17 @@ public class FloatingDecimal{ return Float.intBitsToFloat(bits); } - private static void copyDigits(String in, byte[] digits, int len, int i) { + private static void copyDigits(String in, byte[] d, int len, int i) { int ch; int j = 0; while (j < len) { if ((ch = in.charAt(i++)) != '.') { - digits[j++] = (byte) ch; + d[j++] = (byte) ch; } } } - /* Arithmetically "appends the dec digit" ch to v >= 0, clamping at 10^10. */ + /* Arithmetically "appends the dec digit" ch to v ≥ 0, clamping at 10^10. */ private static long appendDecDigit(long v, int ch) { return v < 10_000_000_000L / 10 ? 10 * v + (ch - '0') : 10_000_000_000L; } @@ -2067,21 +1757,21 @@ public class FloatingDecimal{ * value of precision P is (uniquely) expressed as * c 2^q * where integers c and q meet - * Q_MIN <= q <= Q_MAX - * either 2^(P-1) <= c < 2^P (normal) + * Q_MIN ≤ q ≤ Q_MAX + * either 2^(P-1) ≤ c < 2^P (normal) * or 0 < c < 2^(P-1) & q = Q_MIN (subnormal) - * c = b_1...b_P (b_i in [0, 2)) + * c = (b_i in [0, 2)), b_1 > 0 iff normal * * Equivalently, the floating-point value can be (uniquely) expressed as - * m 2^ep - * where integer qe and real f meet - * qe = q + P + * m 2^qp + * where integer qp and real m meet + * qp = q + P * m = c 2^(-P) * Hence, - * QE_MIN = Q_MIN + P, QE_MAX = Q_MAX + P, - * 2^(-1) <= m < 1 (normal) + * QP_MIN = Q_MIN + P, QP_MAX = Q_MAX + P, + * 2^(-1) ≤ m < 1 (normal) * m < 2^(-1) (subnormal) - * m = 0.b_1...b_P + * m = <0.b_1...b_P> */ /* @@ -2091,71 +1781,78 @@ public class FloatingDecimal{ private static final int BINARY_16_IX = 0; private static final int BINARY_32_IX = 1; private static final int BINARY_64_IX = 2; -// private static final int BINARY_128_IX = 3; -// private static final int BINARY_256_IX = 4; + // private static final int BINARY_128_IX = 3; + // private static final int BINARY_256_IX = 4; @Stable private static final int[] P = { - 11, - FloatToDecimal.P, - DoubleToDecimal.P, + 11, // 11 + FloatToDecimal.P, // 24 + DoubleToDecimal.P, // 53 // 113, // 237, }; - @Stable - private static final int[] W = { - 5, - FloatToDecimal.W, - DoubleToDecimal.W, -// (1 << 4 + BINARY_128_IX) - P[BINARY_128_IX], -// (1 << 4 + BINARY_256_IX) - P[BINARY_256_IX], - }; - - /* Minimum exponent in the m 2^e representation. */ /* Minimum exponent in the c 2^q representation. */ @Stable private static final int[] Q_MIN = { - -24, // Float16ToDecimal.Q_MIN, - FloatToDecimal.Q_MIN, - DoubleToDecimal.Q_MIN, -// QE_MIN[BINARY_128_IX] - (P[BINARY_128_IX] - 1), -// QE_MIN[BINARY_256_IX] - (P[BINARY_256_IX] - 1), + -24, // Float16ToDecimal.Q_MIN, // -24 + FloatToDecimal.Q_MIN, // -149 + DoubleToDecimal.Q_MIN, // -1_074 + // -16_494, + // -262_378, }; + /* Minimum exponent in the m 2^qp representation. */ @Stable - private static final int[] QE_MIN = { - Q_MIN[BINARY_16_IX] + P[BINARY_16_IX], - FloatToDecimal.Q_MIN + FloatToDecimal.P, - DoubleToDecimal.Q_MIN + DoubleToDecimal.P, -// Q_MIN[BINARY_128_IX] + P[BINARY_128_IX], -// Q_MIN[BINARY_256_IX] + P[BINARY_256_IX], + private static final int[] QP_MIN = { + Q_MIN[BINARY_16_IX] + P[BINARY_16_IX], // -13 + Q_MIN[BINARY_32_IX] + P[BINARY_32_IX], // -125 + Q_MIN[BINARY_64_IX] + P[BINARY_64_IX], // -1_021 + // Q_MIN[BINARY_128_IX] + P[BINARY_128_IX], // -16_381 + // Q_MIN[BINARY_256_IX] + P[BINARY_256_IX], // -262_141 }; - /* Maximum exponent in the m 2^e representation. */ + /* Maximum exponent in the m 2^qp representation. */ @Stable - private static final int[] QE_MAX = { - 3 - QE_MIN[BINARY_16_IX], - FloatToDecimal.Q_MAX + FloatToDecimal.P, - DoubleToDecimal.Q_MAX + DoubleToDecimal.P, -// 3 - QE_MIN[BINARY_128_IX], -// 3 - QE_MIN[BINARY_256_IX], + private static final int[] QP_MAX = { + 3 - QP_MIN[BINARY_16_IX], // 16 + 3 - QP_MIN[BINARY_32_IX], // 128 + 3 - QP_MIN[BINARY_64_IX], // 1_024 + // 3 - QP_MIN[BINARY_128_IX], // 16_384 + // 3 - QP_MIN[BINARY_256_IX], // 262_144 }; + /* + * For each binary floating-point format, let + * THR_Z = ulp(0.0) / 2 = MIN_VALUE / 2 + * THR_Z is the zero threshold. + * Real x rounds to 0 by roundTiesToEven iff |x| ≤ THR_Z. + * + * E_THR_Z = max{e : 10^e ≤ THR_Z}. + */ @Stable private static final int[] E_THR_Z = { - -8, - FloatToDecimal.E_THR_Z, - DoubleToDecimal.E_THR_Z, + -8, // -8 + FloatToDecimal.E_THR_Z, // -46 + DoubleToDecimal.E_THR_Z, // -324 // -4_966, // -78_985, }; + /* + * For each binary floating-point format, let + * THR_I = MAX_VALUE + ulp(MAX_VALUE) / 2 + * THR_I is the infinity threshold. + * Real x rounds to infinity by roundTiesToEven iff |x| ≥ THR_I. + * + * E_THR_I = min{e : THR_I ≤ 10^(e-1)}. + */ @Stable private static final int[] E_THR_I = { - 6, - FloatToDecimal.E_THR_I, - DoubleToDecimal.E_THR_I, + 6, // 6 + FloatToDecimal.E_THR_I, // 40 + DoubleToDecimal.E_THR_I, // 310 // 4_934, // 78_915, }; @@ -2166,11 +1863,11 @@ public class FloatingDecimal{ */ @Stable private static final int[] HEX_COUNT = { - P[BINARY_16_IX] / 4 + 2, - P[BINARY_32_IX] / 4 + 2, - P[BINARY_64_IX] / 4 + 2, -// P[BINARY_128_IX] / 4 + 2, -// P[BINARY_256_IX] / 4 + 2, + P[BINARY_16_IX] / 4 + 2, // 4 + P[BINARY_32_IX] / 4 + 2, // 8 + P[BINARY_64_IX] / 4 + 2, // 15 + // P[BINARY_128_IX] / 4 + 2, // 30 + // P[BINARY_256_IX] / 4 + 2, // 61 }; } diff --git a/src/java.base/share/classes/jdk/internal/math/MathUtils.java b/src/java.base/share/classes/jdk/internal/math/MathUtils.java index 172090facc0..259ab5daf9e 100644 --- a/src/java.base/share/classes/jdk/internal/math/MathUtils.java +++ b/src/java.base/share/classes/jdk/internal/math/MathUtils.java @@ -43,7 +43,7 @@ final class MathUtils { */ /* - * N = max{n : 10^n - 1 <= 2^Long.SIZE - 1} + * N = max{n : 10^n - 1 ≤ 2^Long.SIZE - 1} * Every positive integer up to N decimal digits fits in an unsigned long, * but there are positive integers of N + 1 digits that do not. */ @@ -52,16 +52,16 @@ final class MathUtils { /* * Given integer e, let 10^e = beta 2^r for the unique integer r and * real beta meeting - * 2^125 <= beta < 2^126. + * 2^125 ≤ beta < 2^126. * The unique r and beta are * r = floor(log2(10^e)) - 125, beta = 10^e 2^(-r) * Let g = floor(beta) + 1. * Thus, - * (g - 1) 2^r <= 10^e < g 2^r + * (g - 1) 2^r ≤ 10^e < g 2^r * Further, let * g1 = floor(g 2^(-63)), g0 = g - g1 2^63 * - * For e with GE_MIN <= e <= GE_MAX, the values g1 and g0 are available + * For e with GE_MIN ≤ e ≤ GE_MAX, the values g1 and g0 are available * by invoking methods g1(e) and g0(e). * In addition, r = flog2pow10(e) - 125. * @@ -73,17 +73,27 @@ final class MathUtils { * discussed in [1]. * (See DoubleToDecimal.K_MIN and DoubleToDecimal.K_MAX.) * - * Let x = f 10^ep, where integers f and ep meet 10^(n-1) <= f < 10^n + * Let x = f 10^ep, where integers f and ep meet 10^(n-1) ≤ f < 10^n * (that is, f has n digits) and E_THR_Z < ep + n < E_THR_I. * (See DoubleToDecimal.E_THR_Z and DoubleToDecimal.E_THR_I.) * - * The decimal->double fast paths assume n <= N. - * Thus, E_THR_Z - (N - 1) <= ep <= E_THR_I - 2, which means that + * The decimal->double fast paths assume n ≤ N. + * Thus, E_THR_Z - (N - 1) ≤ ep ≤ E_THR_I - 2, which means that * the interval [GE_MIN, GE_MAX] must include the interval * [E_THR_Z - (N - 1), E_THR_I - 2]. * That is, * GE_MIN = min(-K_MAX, E_THR_Z - (N - 1)) * GE_MAX = max(-K_MIN, E_THR_I - 2) + * + * For the record, while the definition of g allows the case g = 2^126, + * the maximal g1 in the lookup table is 0x7FDD_E7F4_CA72_E30FL (e = -146), + * the minimal g1 is 0x4000_0000_0000_0000L (for e = 0), + * and the minimal g0 is 1. + * Hence, as easily verified, we always have + * 2^62 < g1 + 1 < 2^31 (2^32 - 1) < 2^63 + * 2^125 < g = g1 2^63 + g0 < (g1 + 1) 2^63 < 2^94 (2^32 - 1) < 2^126 + * We will assume these bounds observed by glancing at the lookup table, + * and use them liberally when so needed. */ static final int GE_MIN = -342; static final int GE_MAX = 324; diff --git a/test/jdk/java/lang/Double/ParseDouble.java b/test/jdk/java/lang/Double/ParseDouble.java index c8f3443d772..b3c7c8717c1 100644 --- a/test/jdk/java/lang/Double/ParseDouble.java +++ b/test/jdk/java/lang/Double/ParseDouble.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 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 @@ -23,7 +23,8 @@ /* * @test - * @bug 4160406 4705734 4707389 4826774 4895911 4421494 6358355 7021568 7039369 4396272 + * @bug 4160406 4705734 4707389 4826774 4895911 4421494 6358355 7021568 + * 7039369 4396272 8366017 * @summary Test for Double.parseDouble method and acceptance regex */ @@ -759,6 +760,39 @@ public class ParseDouble { throw new RuntimeException("Inconsistent conversion"); } + private static void testFastPaths() { + /* Exercises the fast paths in jdk.internal.math.FloatingDecimal. */ + check("1", 0x1.0p0); // 1.0 + check("2.34000e2", 0x1.d4p7); // 234.0 + check("9.223e18", 0x1.fffab689adb6p62); // 9.223e18 + check("9.876e18", 0x1.121d33597384p63); // 9.876e18 + check("9223372036854776833", 0x1.0000000000001p63); // 9.223372036854778E18 + check("9223372036854776832", 0x1.0p63); // 9.223372036854776E18 + + check("1.23", 0x1.3ae147ae147aep0); // 1.23 + check("0.000234", 0x1.eabbcb1cc9646p-13); // 2.34E-4 + check("3.45e23", 0x1.2439f32cbea41p78); // 3.45E23 + check("576460752303423616e20", 0x1.5af1d78b58c41p125); // 5.764607523034236E37 + + check("1e37", 0x1.e17b84357691bp122); // 1.0E37 + check("8999e34", 0x1.0ecdc63717fbdp126); // 8.999E37 + check("0.9999e36", 0x1.8125c09cb78b7p119); // 9.999E35 + check("0.9876e37", 0x1.db831933296cep122); // 9.876E36 + + check("1.2e-200", 0x1.d64af4cc52935p-665); // 1.2E-200 + check("2.3e100", 0x1.507ed84d17a69p333); // 2.3E100 + check("1.2000000000000000003e-200", 0x1.d64af4cc52935p-665); // 1.2E-200 + check("2.3000000000000000004e100", 0x1.507ed84d17a69p333); // 2.3E100 + check("5.249320425370670463e308", Double.POSITIVE_INFINITY); + check("5.2493204253706704633e308", Double.POSITIVE_INFINITY); + + check("1.2e-320", 0x0.000000000097dp-1022); // 1.2E-320 + check("1.2000000000000000003e-320", 0x0.000000000097dp-1022); // 1.2E-320 + + check("2.225073858507201383e-308", Double.MIN_NORMAL); + check("2.2250738585072013831e-308", Double.MIN_NORMAL); + } + public static void main(String[] args) throws Exception { rudimentaryTest(); @@ -775,5 +809,8 @@ public class ParseDouble { testSubnormalPowers(); testPowers(); testStrictness(); + + testFastPaths(); } + } diff --git a/test/jdk/java/lang/Float/ParseFloat.java b/test/jdk/java/lang/Float/ParseFloat.java index 2828db3ee84..04f03407113 100644 --- a/test/jdk/java/lang/Float/ParseFloat.java +++ b/test/jdk/java/lang/Float/ParseFloat.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 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 @@ -23,8 +23,10 @@ /* * @test - * @bug 4160406 4705734 4707389 6358355 7032154 - * @summary Tests for Float.parseFloat method + * @bug 4160406 4705734 4707389 6358355 7032154 8366017 + * @summary Tests for Float.parseFloat method (use -DallRoundtrips=true + * to additionally check all non-negative, non-NaN float->String->float + * roundtrips) */ import java.math.BigDecimal; @@ -315,6 +317,22 @@ public class ParseFloat { check(new BigDecimal(Float.MAX_VALUE).add(new BigDecimal(Math.ulp(Float.MAX_VALUE)).multiply(HALF)).toString()); } + private static final int N = Float.floatToIntBits(Float.POSITIVE_INFINITY); + + /* Tests all non-negative, non-NaN float->String->float roundtrips. */ + private static void testAllRoundtrips() { + for (int i = 0; i <= N; ++i) { + float v = Float.intBitsToFloat(i); + String s = Float.toString(v); + float v0 = Float.parseFloat(s); + if (v != v0) { + fail(s, v0); + } + } + } + + private static final String ALL_ROUNDTRIPS_PROP = "allRoundtrips"; + public static void main(String[] args) throws Exception { rudimentaryTest(); @@ -324,5 +342,9 @@ public class ParseFloat { testParsing(paddedBadStrings, true); testPowers(); + + if (Boolean.getBoolean(ALL_ROUNDTRIPS_PROP)) { + testAllRoundtrips(); + } } } diff --git a/test/jdk/jdk/internal/math/FloatingDecimal/TestFDBigInteger.java b/test/jdk/jdk/internal/math/FloatingDecimal/TestFDBigInteger.java index 04be8efa38c..d2cdf9365e0 100644 --- a/test/jdk/jdk/internal/math/FloatingDecimal/TestFDBigInteger.java +++ b/test/jdk/jdk/internal/math/FloatingDecimal/TestFDBigInteger.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 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 @@ -21,415 +21,21 @@ * questions. */ -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; -import jdk.internal.math.FDBigInteger; +import jdk.internal.math.FDBigIntegerChecker; -/** +/* * @test * @bug 7032154 8342693 * @summary unit tests of FDBigInteger * @modules java.base/jdk.internal.math - * @author Dmitry Nadezhin + * @library java.base + * @build java.base/jdk.internal.math.* + * @run main TestFDBigInteger */ public class TestFDBigInteger { - private static final int MAX_P5 = 413; - private static final int MAX_P2 = 65; - private static final long LONG_SIGN_MASK = (1L << 63); - private static final BigInteger FIVE = BigInteger.valueOf(5); - private static final FDBigInteger MUTABLE_ZERO = FDBigInteger.valueOfPow52(0, 0).leftInplaceSub(FDBigInteger.valueOfPow52(0, 0)); - private static final FDBigInteger IMMUTABLE_ZERO = FDBigInteger.valueOfPow52(0, 0).leftInplaceSub(FDBigInteger.valueOfPow52(0, 0)); - private static final FDBigInteger IMMUTABLE_MILLION = genMillion1(); - private static final FDBigInteger IMMUTABLE_BILLION = genBillion1(); - private static final FDBigInteger IMMUTABLE_TEN18 = genTen18(); - - static { - IMMUTABLE_ZERO.makeImmutable(); - IMMUTABLE_MILLION.makeImmutable(); - IMMUTABLE_BILLION.makeImmutable(); - IMMUTABLE_TEN18.makeImmutable(); - } - - private static FDBigInteger mutable(String hex, int offset) { - byte[] chars = new BigInteger(hex, 16).toString().getBytes(StandardCharsets.US_ASCII); - return new FDBigInteger(0, chars, 0, chars.length).multByPow52(0, offset * 32); - } - - private static FDBigInteger immutable(String hex, int offset) { - FDBigInteger fd = mutable(hex, offset); - fd.makeImmutable(); - return fd; - } - - private static BigInteger biPow52(int p5, int p2) { - return FIVE.pow(p5).shiftLeft(p2); - } - - // data.length == 1, nWords == 1, offset == 0 - private static FDBigInteger genMillion1() { - return FDBigInteger.valueOfPow52(6, 0).leftShift(6); - } - - // data.length == 2, nWords == 1, offset == 0 - private static FDBigInteger genMillion2() { - return FDBigInteger.valueOfMulPow52(1000000L, 0, 0); - } - - // data.length == 1, nWords == 1, offset == 0 - private static FDBigInteger genBillion1() { - return FDBigInteger.valueOfPow52(9, 0).leftShift(9); - } - - // data.length == 2, nWords == 2, offset == 0 - private static FDBigInteger genTen18() { - return FDBigInteger.valueOfPow52(18, 0).leftShift(18); - } - - private static void check(BigInteger expected, FDBigInteger actual, String message) throws Exception { - if (!expected.equals(actual.toBigInteger())) { - throw new Exception(message + " result " + actual.toHexString() + " expected " + expected.toString(16)); - } - } - - private static void testValueOfPow52(int p5, int p2) throws Exception { - check(biPow52(p5, p2), FDBigInteger.valueOfPow52(p5, p2), - "valueOfPow52(" + p5 + "," + p2 + ")"); - } - - private static void testValueOfPow52() throws Exception { - for (int p5 = 0; p5 <= MAX_P5; p5++) { - for (int p2 = 0; p2 <= MAX_P2; p2++) { - testValueOfPow52(p5, p2); - } - } - } - - private static void testValueOfMulPow52(long value, int p5, int p2) throws Exception { - BigInteger bi = BigInteger.valueOf(value & ~LONG_SIGN_MASK); - if (value < 0) { - bi = bi.setBit(63); - } - check(biPow52(p5, p2).multiply(bi), FDBigInteger.valueOfMulPow52(value, p5, p2), - "valueOfMulPow52(" + Long.toHexString(value) + "." + p5 + "," + p2 + ")"); - } - - private static void testValueOfMulPow52(long value, int p5) throws Exception { - testValueOfMulPow52(value, p5, 0); - testValueOfMulPow52(value, p5, 1); - testValueOfMulPow52(value, p5, 30); - testValueOfMulPow52(value, p5, 31); - testValueOfMulPow52(value, p5, 33); - testValueOfMulPow52(value, p5, 63); - } - - private static void testValueOfMulPow52() throws Exception { - for (int p5 = 0; p5 <= MAX_P5; p5++) { - testValueOfMulPow52(0xFFFFFFFFL, p5); - testValueOfMulPow52(0x123456789AL, p5); - testValueOfMulPow52(0x7FFFFFFFFFFFFFFFL, p5); - testValueOfMulPow52(0xFFFFFFFFFFF54321L, p5); - } - } - - private static void testLeftShift(FDBigInteger t, int shift, boolean isImmutable) throws Exception { - BigInteger bt = t.toBigInteger(); - FDBigInteger r = t.leftShift(shift); - if ((bt.signum() == 0 || shift == 0 || !isImmutable) && r != t) { - throw new Exception("leftShift doesn't reuse its argument"); - } - if (isImmutable) { - check(bt, t, "leftShift corrupts its argument"); - } - check(bt.shiftLeft(shift), r, "leftShift returns wrong result"); - } - - private static void testLeftShift() throws Exception { - testLeftShift(IMMUTABLE_ZERO, 0, true); - testLeftShift(IMMUTABLE_ZERO, 10, true); - testLeftShift(MUTABLE_ZERO, 0, false); - testLeftShift(MUTABLE_ZERO, 10, false); - - testLeftShift(IMMUTABLE_MILLION, 0, true); - testLeftShift(IMMUTABLE_MILLION, 1, true); - testLeftShift(IMMUTABLE_MILLION, 12, true); - testLeftShift(IMMUTABLE_MILLION, 13, true); - testLeftShift(IMMUTABLE_MILLION, 32, true); - testLeftShift(IMMUTABLE_MILLION, 33, true); - testLeftShift(IMMUTABLE_MILLION, 44, true); - testLeftShift(IMMUTABLE_MILLION, 45, true); - - testLeftShift(genMillion1(), 0, false); - testLeftShift(genMillion1(), 1, false); - testLeftShift(genMillion1(), 12, false); - testLeftShift(genMillion1(), 13, false); - testLeftShift(genMillion1(), 25, false); - testLeftShift(genMillion1(), 26, false); - testLeftShift(genMillion1(), 32, false); - testLeftShift(genMillion1(), 33, false); - testLeftShift(genMillion1(), 44, false); - testLeftShift(genMillion1(), 45, false); - - testLeftShift(genMillion2(), 0, false); - testLeftShift(genMillion2(), 1, false); - testLeftShift(genMillion2(), 12, false); - testLeftShift(genMillion2(), 13, false); - testLeftShift(genMillion2(), 25, false); - testLeftShift(genMillion2(), 26, false); - testLeftShift(genMillion2(), 32, false); - testLeftShift(genMillion2(), 33, false); - testLeftShift(genMillion2(), 44, false); - testLeftShift(genMillion2(), 45, false); - } - - private static void testQuoRemIteration(FDBigInteger t, FDBigInteger s) throws Exception { - BigInteger bt = t.toBigInteger(); - BigInteger bs = s.toBigInteger(); - int q = t.quoRemIteration(s); - BigInteger[] qr = bt.divideAndRemainder(bs); - if (!BigInteger.valueOf(q).equals(qr[0])) { - throw new Exception("quoRemIteration returns incorrect quo"); - } - check(qr[1].multiply(BigInteger.TEN), t, "quoRemIteration returns incorrect rem"); - } - - private static void testQuoRemIteration() throws Exception { - // IMMUTABLE_TEN18 == 0de0b6b3a7640000 - // q = 0 - testQuoRemIteration(mutable("00000001", 0), IMMUTABLE_TEN18); - testQuoRemIteration(mutable("00000001", 1), IMMUTABLE_TEN18); - testQuoRemIteration(mutable("0de0b6b2", 1), IMMUTABLE_TEN18); - // q = 1 -> q = 0 - testQuoRemIteration(mutable("0de0b6b3", 1), IMMUTABLE_TEN18); - testQuoRemIteration(mutable("0de0b6b3a763FFFF", 0), IMMUTABLE_TEN18); - // q = 1 - testQuoRemIteration(mutable("0de0b6b3a7640000", 0), IMMUTABLE_TEN18); - testQuoRemIteration(mutable("0de0b6b3FFFFFFFF", 0), IMMUTABLE_TEN18); - testQuoRemIteration(mutable("8ac72304", 1), IMMUTABLE_TEN18); - testQuoRemIteration(mutable("0de0b6b400000000", 0), IMMUTABLE_TEN18); - testQuoRemIteration(mutable("8ac72305", 1), IMMUTABLE_TEN18); - // q = 18 - testQuoRemIteration(mutable("FFFFFFFF", 1), IMMUTABLE_TEN18); - } - - private static void testCmp(FDBigInteger t, FDBigInteger o) throws Exception { - BigInteger bt = t.toBigInteger(); - BigInteger bo = o.toBigInteger(); - int cmp = t.cmp(o); - int bcmp = bt.compareTo(bo); - if (bcmp != cmp) { - throw new Exception("cmp returns " + cmp + " expected " + bcmp); - } - check(bt, t, "cmp corrupts this"); - check(bo, o, "cmp corrupts other"); - if (o.cmp(t) != -cmp) { - throw new Exception("asymmetrical cmp"); - } - check(bt, t, "cmp corrupts this"); - check(bo, o, "cmp corrupts other"); - } - - private static void testCmp() throws Exception { - testCmp(mutable("FFFFFFFF", 0), mutable("100000000", 0)); - testCmp(mutable("FFFFFFFF", 0), mutable("1", 1)); - testCmp(mutable("5", 0), mutable("6", 0)); - testCmp(mutable("5", 0), mutable("5", 0)); - testCmp(mutable("5000000001", 0), mutable("500000001", 0)); - testCmp(mutable("5000000001", 0), mutable("6", 1)); - testCmp(mutable("5000000001", 0), mutable("5", 1)); - testCmp(mutable("5000000000", 0), mutable("5", 1)); - } - - private static void testCmpPow52(FDBigInteger t, int p5, int p2) throws Exception { - FDBigInteger o = FDBigInteger.valueOfPow52(p5, p2); - BigInteger bt = t.toBigInteger(); - BigInteger bo = biPow52(p5, p2); - int cmp = t.cmp(o); - int bcmp = bt.compareTo(bo); - if (bcmp != cmp) { - throw new Exception("cmpPow52 returns " + cmp + " expected " + bcmp); - } - check(bt, t, "cmpPow52 corrupts this"); - check(bo, o, "cmpPow5 corrupts other"); - } - - private static void testCmpPow52() throws Exception { - testCmpPow52(mutable("00000002", 1), 0, 31); - testCmpPow52(mutable("00000002", 1), 0, 32); - testCmpPow52(mutable("00000002", 1), 0, 33); - testCmpPow52(mutable("00000002", 1), 0, 34); - testCmpPow52(mutable("00000002", 1), 0, 64); - testCmpPow52(mutable("00000003", 1), 0, 32); - testCmpPow52(mutable("00000003", 1), 0, 33); - testCmpPow52(mutable("00000003", 1), 0, 34); - } - - private static void testAddAndCmp(FDBigInteger t, FDBigInteger x, FDBigInteger y) throws Exception { - BigInteger bt = t.toBigInteger(); - BigInteger bx = x.toBigInteger(); - BigInteger by = y.toBigInteger(); - int cmp = t.addAndCmp(x, y); - int bcmp = bt.compareTo(bx.add(by)); - if (bcmp != cmp) { - throw new Exception("addAndCmp returns " + cmp + " expected " + bcmp); - } - check(bt, t, "addAndCmp corrupts this"); - check(bx, x, "addAndCmp corrupts x"); - check(by, y, "addAndCmp corrupts y"); - } - - private static void testAddAndCmp() throws Exception { - testAddAndCmp(MUTABLE_ZERO, MUTABLE_ZERO, MUTABLE_ZERO); - testAddAndCmp(mutable("00000001", 0), MUTABLE_ZERO, MUTABLE_ZERO); - testAddAndCmp(mutable("00000001", 0), mutable("00000001", 0), MUTABLE_ZERO); - testAddAndCmp(mutable("00000001", 0), MUTABLE_ZERO, mutable("00000001", 0)); - testAddAndCmp(mutable("00000001", 0), mutable("00000002", 0), MUTABLE_ZERO); - testAddAndCmp(mutable("00000001", 0), MUTABLE_ZERO, mutable("00000002", 0)); - testAddAndCmp(mutable("00000001", 2), mutable("FFFFFFFF", 0), mutable("FFFFFFFF", 0)); - testAddAndCmp(mutable("00000001", 0), mutable("00000001", 1), mutable("00000001", 0)); - - testAddAndCmp(mutable("00000001", 2), mutable("0F0F0F0F80000000", 1), mutable("F0F0F0F080000000", 1)); - testAddAndCmp(mutable("00000001", 2), mutable("0F0F0F0E80000000", 1), mutable("F0F0F0F080000000", 1)); - - testAddAndCmp(mutable("00000002", 1), mutable("0000000180000000", 1), mutable("0000000280000000", 1)); - testAddAndCmp(mutable("00000003", 1), mutable("0000000180000000", 1), mutable("0000000280000000", 1)); - testAddAndCmp(mutable("00000004", 1), mutable("0000000180000000", 1), mutable("0000000280000000", 1)); - testAddAndCmp(mutable("00000005", 1), mutable("0000000180000000", 1), mutable("0000000280000000", 1)); - - testAddAndCmp(mutable("00000001", 2), mutable("8000000000000000", 0), mutable("8000000000000000", 0)); - testAddAndCmp(mutable("00000001", 2), mutable("8000000000000000", 0), mutable("8000000000000001", 0)); - testAddAndCmp(mutable("00000002", 2), mutable("8000000000000000", 0), mutable("8000000000000000", 0)); - testAddAndCmp(mutable("00000003", 2), mutable("8000000000000000", 0), mutable("8000000000000000", 0)); - } - - private static void testMultBy10(FDBigInteger t, boolean isImmutable) throws Exception { - BigInteger bt = t.toBigInteger(); - FDBigInteger r = t.multBy10(); - if ((bt.signum() == 0 || !isImmutable) && r != t) { - throw new Exception("multBy10 of doesn't reuse its argument"); - } - if (isImmutable) { - check(bt, t, "multBy10 corrupts its argument"); - } - check(bt.multiply(BigInteger.TEN), r, "multBy10 returns wrong result"); - } - - private static void testMultBy10() throws Exception { - for (int p5 = 0; p5 <= MAX_P5; p5++) { - for (int p2 = 0; p2 <= MAX_P2; p2++) { - // This strange way of creating a value ensures that it is mutable. - FDBigInteger value = FDBigInteger.valueOfPow52(0, 0).multByPow52(p5, p2); - testMultBy10(value, false); - value.makeImmutable(); - testMultBy10(value, true); - } - } - } - - private static void testMultByPow52(FDBigInteger t, int p5, int p2) throws Exception { - BigInteger bt = t.toBigInteger(); - FDBigInteger r = t.multByPow52(p5, p2); - if (bt.signum() == 0 && r != t) { - throw new Exception("multByPow52 of doesn't reuse its argument"); - } - check(bt.multiply(biPow52(p5, p2)), r, "multByPow52 returns wrong result"); - } - - private static void testMultByPow52() throws Exception { - for (int p5 = 0; p5 <= MAX_P5; p5++) { - for (int p2 = 0; p2 <= MAX_P2; p2++) { - // This strange way of creating a value ensures that it is mutable. - FDBigInteger value = FDBigInteger.valueOfPow52(0, 0).multByPow52(p5, p2); - testMultByPow52(value, p5, p2); - } - } - } - - private static void testLeftInplaceSub(FDBigInteger left, FDBigInteger right, boolean isImmutable) throws Exception { - BigInteger biLeft = left.toBigInteger(); - BigInteger biRight = right.toBigInteger(); - FDBigInteger diff = left.leftInplaceSub(right); - if (!isImmutable && diff != left) { - throw new Exception("leftInplaceSub of doesn't reuse its argument"); - } - if (isImmutable) { - check(biLeft, left, "leftInplaceSub corrupts its left immutable argument"); - } - check(biRight, right, "leftInplaceSub corrupts its right argument"); - check(biLeft.subtract(biRight), diff, "leftInplaceSub returns wrong result"); - } - - private static void testLeftInplaceSub() throws Exception { - for (int p5 = 0; p5 <= MAX_P5; p5++) { - for (int p2 = 0; p2 <= MAX_P2; p2++) { -// for (int p5r = 0; p5r <= p5; p5r += 10) { -// for (int p2r = 0; p2r <= p2; p2r += 10) { - for (int p5r = 0; p5r <= p5; p5r++) { - for (int p2r = 0; p2r <= p2; p2r++) { - // This strange way of creating a value ensures that it is mutable. - FDBigInteger left = FDBigInteger.valueOfPow52(0, 0).multByPow52(p5, p2); - FDBigInteger right = FDBigInteger.valueOfPow52(0, 0).multByPow52(p5r, p2r); - testLeftInplaceSub(left, right, false); - left = FDBigInteger.valueOfPow52(0, 0).multByPow52(p5, p2); - left.makeImmutable(); - testLeftInplaceSub(left, right, true); - } - } - } - } - } - - private static void testRightInplaceSub(FDBigInteger left, FDBigInteger right, boolean isImmutable) throws Exception { - BigInteger biLeft = left.toBigInteger(); - BigInteger biRight = right.toBigInteger(); - FDBigInteger diff = left.rightInplaceSub(right); - if (!isImmutable && diff != right) { - throw new Exception("rightInplaceSub of doesn't reuse its argument"); - } - check(biLeft, left, "leftInplaceSub corrupts its left argument"); - if (isImmutable) { - check(biRight, right, "leftInplaceSub corrupts its right immutable argument"); - } - try { - check(biLeft.subtract(biRight), diff, "rightInplaceSub returns wrong result"); - } catch (Exception e) { - System.out.println(biLeft+" - "+biRight+" = "+biLeft.subtract(biRight)); - throw e; - } - } - - private static void testRightInplaceSub() throws Exception { - for (int p5 = 0; p5 <= MAX_P5; p5++) { - for (int p2 = 0; p2 <= MAX_P2; p2++) { -// for (int p5r = 0; p5r <= p5; p5r += 10) { -// for (int p2r = 0; p2r <= p2; p2r += 10) { - for (int p5r = 0; p5r <= p5; p5r++) { - for (int p2r = 0; p2r <= p2; p2r++) { - // This strange way of creating a value ensures that it is mutable. - FDBigInteger left = FDBigInteger.valueOfPow52(0, 0).multByPow52(p5, p2); - FDBigInteger right = FDBigInteger.valueOfPow52(0, 0).multByPow52(p5r, p2r); - testRightInplaceSub(left, right, false); - right = FDBigInteger.valueOfPow52(0, 0).multByPow52(p5r, p2r); - right.makeImmutable(); - testRightInplaceSub(left, right, true); - } - } - } - } - } - public static void main(String[] args) throws Exception { - testValueOfPow52(); - testValueOfMulPow52(); - testLeftShift(); - testQuoRemIteration(); - testCmp(); - testCmpPow52(); - testAddAndCmp(); - // Uncomment the following for more comprehensize but slow testing. - // testLeftInplaceSub(); - // testMultBy10(); - // testMultByPow52(); - // testRightInplaceSub(); + FDBigIntegerChecker.main(args); } + } diff --git a/test/jdk/jdk/internal/math/FloatingDecimal/java.base/jdk/internal/math/FDBigIntegerChecker.java b/test/jdk/jdk/internal/math/FloatingDecimal/java.base/jdk/internal/math/FDBigIntegerChecker.java new file mode 100644 index 00000000000..3a52e597ead --- /dev/null +++ b/test/jdk/jdk/internal/math/FloatingDecimal/java.base/jdk/internal/math/FDBigIntegerChecker.java @@ -0,0 +1,339 @@ +/* + * Copyright (c) 2013, 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. + * + * 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 java.math.BigInteger; +import java.nio.charset.StandardCharsets; + +public class FDBigIntegerChecker { + + private static final int MAX_P5 = 413; + private static final int MAX_P2 = 65; + private static final long LONG_SIGN_MASK = (1L << 63); + private static final BigInteger FIVE = BigInteger.valueOf(5); + private static final FDBigInteger MUTABLE_ZERO = new FDBigInteger(0); + private static final FDBigInteger IMMUTABLE_ZERO = new FDBigInteger(0).makeImmutable(); + private static final FDBigInteger IMMUTABLE_MILLION = genMillion1().makeImmutable(); + private static final FDBigInteger IMMUTABLE_TEN18 = genTen18().makeImmutable(); + + private static BigInteger toBigInteger(FDBigInteger v) { + return new BigInteger(v.toByteArray()); + } + + private static FDBigInteger mutable(String hex, int offset) { + byte[] chars = new BigInteger(hex, 16).toString().getBytes(StandardCharsets.US_ASCII); + return new FDBigInteger(0, chars, 0, chars.length).multByPow52(0, offset * 32); + } + + private static BigInteger biPow52(int p5, int p2) { + return FIVE.pow(p5).shiftLeft(p2); + } + + // data.length == 1, nWords == 1, offset == 0 + private static FDBigInteger genMillion1() { + return FDBigInteger.valueOfPow52(6, 0).leftShift(6); + } + + // data.length == 2, nWords == 1, offset == 0 + private static FDBigInteger genMillion2() { + return FDBigInteger.valueOfMulPow52(1000000L, 0, 0); + } + + // data.length == 2, nWords == 2, offset == 0 + private static FDBigInteger genTen18() { + return FDBigInteger.valueOfPow52(18, 0).leftShift(18); + } + + private static void check(BigInteger expected, FDBigInteger actual, String message) throws Exception { + if (!expected.equals(toBigInteger(actual))) { + throw new Exception(message + " result " + actual + " expected " + expected.toString(16)); + } + } + + private static void testValueOfPow52(int p5, int p2) throws Exception { + check(biPow52(p5, p2), FDBigInteger.valueOfPow52(p5, p2), + "valueOfPow52(" + p5 + "," + p2 + ")"); + } + + private static void testValueOfPow52() throws Exception { + for (int p5 = 0; p5 <= MAX_P5; p5++) { + for (int p2 = 0; p2 <= MAX_P2; p2++) { + testValueOfPow52(p5, p2); + } + } + } + + private static void testValueOfMulPow52(long value, int p5, int p2) throws Exception { + BigInteger bi = BigInteger.valueOf(value & ~LONG_SIGN_MASK); + if (value < 0) { + bi = bi.setBit(63); + } + check(biPow52(p5, p2).multiply(bi), FDBigInteger.valueOfMulPow52(value, p5, p2), + "valueOfMulPow52(" + Long.toHexString(value) + "." + p5 + "," + p2 + ")"); + } + + private static void testValueOfMulPow52(long value, int p5) throws Exception { + testValueOfMulPow52(value, p5, 0); + testValueOfMulPow52(value, p5, 1); + testValueOfMulPow52(value, p5, 30); + testValueOfMulPow52(value, p5, 31); + testValueOfMulPow52(value, p5, 33); + testValueOfMulPow52(value, p5, 63); + } + + private static void testValueOfMulPow52() throws Exception { + for (int p5 = 0; p5 <= MAX_P5; p5++) { + testValueOfMulPow52(0xFFFFFFFFL, p5); + testValueOfMulPow52(0x123456789AL, p5); + testValueOfMulPow52(0x7FFFFFFFFFFFFFFFL, p5); + testValueOfMulPow52(0xFFFFFFFFFFF54321L, p5); + } + } + + private static void testLeftShift(FDBigInteger t, int shift, boolean isImmutable) throws Exception { + BigInteger bt = toBigInteger(t); + FDBigInteger r = t.leftShift(shift); + if ((bt.signum() == 0 || shift == 0 || !isImmutable) && r != t) { + throw new Exception("leftShift doesn't reuse its argument"); + } + if (isImmutable) { + check(bt, t, "leftShift corrupts its argument"); + } + check(bt.shiftLeft(shift), r, "leftShift returns wrong result"); + } + + private static void testLeftShift() throws Exception { + testLeftShift(IMMUTABLE_ZERO, 0, true); + testLeftShift(IMMUTABLE_ZERO, 10, true); + testLeftShift(MUTABLE_ZERO, 0, false); + testLeftShift(MUTABLE_ZERO, 10, false); + + testLeftShift(IMMUTABLE_MILLION, 0, true); + testLeftShift(IMMUTABLE_MILLION, 1, true); + testLeftShift(IMMUTABLE_MILLION, 12, true); + testLeftShift(IMMUTABLE_MILLION, 13, true); + testLeftShift(IMMUTABLE_MILLION, 32, true); + testLeftShift(IMMUTABLE_MILLION, 33, true); + testLeftShift(IMMUTABLE_MILLION, 44, true); + testLeftShift(IMMUTABLE_MILLION, 45, true); + + testLeftShift(genMillion1(), 0, false); + testLeftShift(genMillion1(), 1, false); + testLeftShift(genMillion1(), 12, false); + testLeftShift(genMillion1(), 13, false); + testLeftShift(genMillion1(), 25, false); + testLeftShift(genMillion1(), 26, false); + testLeftShift(genMillion1(), 32, false); + testLeftShift(genMillion1(), 33, false); + testLeftShift(genMillion1(), 44, false); + testLeftShift(genMillion1(), 45, false); + + testLeftShift(genMillion2(), 0, false); + testLeftShift(genMillion2(), 1, false); + testLeftShift(genMillion2(), 12, false); + testLeftShift(genMillion2(), 13, false); + testLeftShift(genMillion2(), 25, false); + testLeftShift(genMillion2(), 26, false); + testLeftShift(genMillion2(), 32, false); + testLeftShift(genMillion2(), 33, false); + testLeftShift(genMillion2(), 44, false); + testLeftShift(genMillion2(), 45, false); + } + + private static void testQuoRemIteration(FDBigInteger t, FDBigInteger s) throws Exception { + BigInteger bt = toBigInteger(t); + BigInteger bs = toBigInteger(s); + int q = t.quoRemIteration(s); + BigInteger[] qr = bt.divideAndRemainder(bs); + if (!BigInteger.valueOf(q).equals(qr[0])) { + throw new Exception("quoRemIteration returns incorrect quo"); + } + check(qr[1].multiply(BigInteger.TEN), t, "quoRemIteration returns incorrect rem"); + } + + private static void testQuoRemIteration() throws Exception { + // IMMUTABLE_TEN18 == 0de0b6b3a7640000 + // q = 0 + testQuoRemIteration(mutable("00000001", 0), IMMUTABLE_TEN18); + testQuoRemIteration(mutable("00000001", 1), IMMUTABLE_TEN18); + testQuoRemIteration(mutable("0de0b6b2", 1), IMMUTABLE_TEN18); + // q = 1 -> q = 0 + testQuoRemIteration(mutable("0de0b6b3", 1), IMMUTABLE_TEN18); + testQuoRemIteration(mutable("0de0b6b3a763FFFF", 0), IMMUTABLE_TEN18); + // q = 1 + testQuoRemIteration(mutable("0de0b6b3a7640000", 0), IMMUTABLE_TEN18); + testQuoRemIteration(mutable("0de0b6b3FFFFFFFF", 0), IMMUTABLE_TEN18); + testQuoRemIteration(mutable("8ac72304", 1), IMMUTABLE_TEN18); + testQuoRemIteration(mutable("0de0b6b400000000", 0), IMMUTABLE_TEN18); + testQuoRemIteration(mutable("8ac72305", 1), IMMUTABLE_TEN18); + // q = 18 + testQuoRemIteration(mutable("FFFFFFFF", 1), IMMUTABLE_TEN18); + } + + private static void testCmp(FDBigInteger t, FDBigInteger o) throws Exception { + BigInteger bt = toBigInteger(t); + BigInteger bo = toBigInteger(o); + int cmp = t.cmp(o); + int bcmp = bt.compareTo(bo); + if (bcmp != cmp) { + throw new Exception("cmp returns " + cmp + " expected " + bcmp); + } + check(bt, t, "cmp corrupts this"); + check(bo, o, "cmp corrupts other"); + if (o.cmp(t) != -cmp) { + throw new Exception("asymmetrical cmp"); + } + check(bt, t, "cmp corrupts this"); + check(bo, o, "cmp corrupts other"); + } + + private static void testCmp() throws Exception { + testCmp(mutable("FFFFFFFF", 0), mutable("100000000", 0)); + testCmp(mutable("FFFFFFFF", 0), mutable("1", 1)); + testCmp(mutable("5", 0), mutable("6", 0)); + testCmp(mutable("5", 0), mutable("5", 0)); + testCmp(mutable("5000000001", 0), mutable("500000001", 0)); + testCmp(mutable("5000000001", 0), mutable("6", 1)); + testCmp(mutable("5000000001", 0), mutable("5", 1)); + testCmp(mutable("5000000000", 0), mutable("5", 1)); + } + + private static void testCmpPow52(FDBigInteger t, int p5, int p2) throws Exception { + FDBigInteger o = FDBigInteger.valueOfPow52(p5, p2); + BigInteger bt = toBigInteger(t); + BigInteger bo = biPow52(p5, p2); + int cmp = t.cmp(o); + int bcmp = bt.compareTo(bo); + if (bcmp != cmp) { + throw new Exception("cmp returns " + cmp + " expected " + bcmp); + } + check(bt, t, "cmp corrupts this"); + check(bo, o, "cmpPow5 corrupts other"); + } + + private static void testCmpPow52() throws Exception { + testCmpPow52(mutable("00000002", 1), 0, 31); + testCmpPow52(mutable("00000002", 1), 0, 32); + testCmpPow52(mutable("00000002", 1), 0, 33); + testCmpPow52(mutable("00000002", 1), 0, 34); + testCmpPow52(mutable("00000002", 1), 0, 64); + testCmpPow52(mutable("00000003", 1), 0, 32); + testCmpPow52(mutable("00000003", 1), 0, 33); + testCmpPow52(mutable("00000003", 1), 0, 34); + } + + private static void testAddAndCmp(FDBigInteger t, FDBigInteger x, FDBigInteger y) throws Exception { + BigInteger bt = toBigInteger(t); + BigInteger bx = toBigInteger(x); + BigInteger by = toBigInteger(y); + int cmp = t.addAndCmp(x, y); + int bcmp = bt.compareTo(bx.add(by)); + if (bcmp != cmp) { + throw new Exception("addAndCmp returns " + cmp + " expected " + bcmp); + } + check(bt, t, "addAndCmp corrupts this"); + check(bx, x, "addAndCmp corrupts x"); + check(by, y, "addAndCmp corrupts y"); + } + + private static void testAddAndCmp() throws Exception { + testAddAndCmp(MUTABLE_ZERO, MUTABLE_ZERO, MUTABLE_ZERO); + testAddAndCmp(mutable("00000001", 0), MUTABLE_ZERO, MUTABLE_ZERO); + testAddAndCmp(mutable("00000001", 0), mutable("00000001", 0), MUTABLE_ZERO); + testAddAndCmp(mutable("00000001", 0), MUTABLE_ZERO, mutable("00000001", 0)); + testAddAndCmp(mutable("00000001", 0), mutable("00000002", 0), MUTABLE_ZERO); + testAddAndCmp(mutable("00000001", 0), MUTABLE_ZERO, mutable("00000002", 0)); + testAddAndCmp(mutable("00000001", 2), mutable("FFFFFFFF", 0), mutable("FFFFFFFF", 0)); + testAddAndCmp(mutable("00000001", 0), mutable("00000001", 1), mutable("00000001", 0)); + + testAddAndCmp(mutable("00000001", 2), mutable("0F0F0F0F80000000", 1), mutable("F0F0F0F080000000", 1)); + testAddAndCmp(mutable("00000001", 2), mutable("0F0F0F0E80000000", 1), mutable("F0F0F0F080000000", 1)); + + testAddAndCmp(mutable("00000002", 1), mutable("0000000180000000", 1), mutable("0000000280000000", 1)); + testAddAndCmp(mutable("00000003", 1), mutable("0000000180000000", 1), mutable("0000000280000000", 1)); + testAddAndCmp(mutable("00000004", 1), mutable("0000000180000000", 1), mutable("0000000280000000", 1)); + testAddAndCmp(mutable("00000005", 1), mutable("0000000180000000", 1), mutable("0000000280000000", 1)); + + testAddAndCmp(mutable("00000001", 2), mutable("8000000000000000", 0), mutable("8000000000000000", 0)); + testAddAndCmp(mutable("00000001", 2), mutable("8000000000000000", 0), mutable("8000000000000001", 0)); + testAddAndCmp(mutable("00000002", 2), mutable("8000000000000000", 0), mutable("8000000000000000", 0)); + testAddAndCmp(mutable("00000003", 2), mutable("8000000000000000", 0), mutable("8000000000000000", 0)); + } + + private static void testMultBy10(FDBigInteger t, boolean isImmutable) throws Exception { + BigInteger bt = toBigInteger(t); + FDBigInteger r = t.multBy10(); + if ((bt.signum() == 0 || !isImmutable) && r != t) { + throw new Exception("multBy10 of doesn't reuse its argument"); + } + if (isImmutable) { + check(bt, t, "multBy10 corrupts its argument"); + } + check(bt.multiply(BigInteger.TEN), r, "multBy10 returns wrong result"); + } + + private static void testMultBy10() throws Exception { + for (int p5 = 0; p5 <= MAX_P5; p5++) { + for (int p2 = 0; p2 <= MAX_P2; p2++) { + // This strange way of creating a value ensures that it is mutable. + FDBigInteger value = FDBigInteger.valueOfPow52(0, 0).multByPow52(p5, p2); + testMultBy10(value, false); + value.makeImmutable(); + testMultBy10(value, true); + } + } + } + + private static void testMultByPow52(FDBigInteger t, int p5, int p2) throws Exception { + BigInteger bt = toBigInteger(t); + FDBigInteger r = t.multByPow52(p5, p2); + if (bt.signum() == 0 && r != t) { + throw new Exception("multByPow52 of doesn't reuse its argument"); + } + check(bt.multiply(biPow52(p5, p2)), r, "multByPow52 returns wrong result"); + } + + private static void testMultByPow52() throws Exception { + for (int p5 = 0; p5 <= MAX_P5; p5++) { + for (int p2 = 0; p2 <= MAX_P2; p2++) { + // This strange way of creating a value ensures that it is mutable. + FDBigInteger value = FDBigInteger.valueOfPow52(0, 0).multByPow52(p5, p2); + testMultByPow52(value, p5, p2); + } + } + } + + public static void main(String[] args) throws Exception { + testValueOfPow52(); + testValueOfMulPow52(); + testLeftShift(); + testQuoRemIteration(); + testCmp(); + testCmpPow52(); + testAddAndCmp(); + // Uncomment the following for more comprehensize but slow testing. + // testMultBy10(); + // testMultByPow52(); + } +} diff --git a/test/micro/org/openjdk/bench/java/lang/FloatingPointParse.java b/test/micro/org/openjdk/bench/java/lang/FloatingPointParse.java new file mode 100644 index 00000000000..735f2760d3e --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/FloatingPointParse.java @@ -0,0 +1,193 @@ +/* + * 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. + * + * 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 org.openjdk.bench.java.lang; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +import java.util.Random; +import java.util.concurrent.TimeUnit; + +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@State(Scope.Thread) +@Warmup(iterations = 3, time = 3) +@Measurement(iterations = 3, time = 3) +@Fork(value = 3) +public class FloatingPointParse { + + private static final int N = 1_000_000; + private static final int M = 100_000; + + private String[] doubleToString, floatToString, s1, s2, s4, s10; + + @Setup + public void setup() { + Random r = new Random(); + + doubleToString = new String[N]; + for (int i = 0; i < doubleToString.length;) { + double v = Double.longBitsToDouble(r.nextLong()); + if (Double.isFinite(v)) { + doubleToString[i++] = Double.toString(v); + } + } + + floatToString = new String[N]; + for (int i = 0; i < floatToString.length;) { + float v = Float.intBitsToFloat(r.nextInt()); + if (Float.isFinite(v)) { + floatToString[i++] = Float.toString(v); + } + } + + s1 = new String[M]; + for (int i = 0; i < s1.length; ++i) { + String f = "0." + (r.nextLong() & 0x7fff_ffff_ffff_ffffL); + s1[i] = f + "e" + (r.nextInt(600) - 300); + } + + s2 = new String[M]; + for (int i = 0; i < s2.length; ++i) { + String f = "0." + (r.nextLong() & 0x7fff_ffff_ffff_ffffL) + + (r.nextLong() & 0x7fff_ffff_ffff_ffffL); + s2[i] = f + "e" + (r.nextInt(600) - 300); + } + + s4 = new String[M]; + for (int i = 0; i < s4.length; ++i) { + String f = "0." + (r.nextLong() & 0x7fff_ffff_ffff_ffffL) + + (r.nextLong() & 0x7fff_ffff_ffff_ffffL) + + (r.nextLong() & 0x7fff_ffff_ffff_ffffL) + + (r.nextLong() & 0x7fff_ffff_ffff_ffffL); + s4[i] = f + "e" + (r.nextInt(600) - 300); + } + + s10 = new String[M]; + for (int i = 0; i < s10.length; ++i) { + String f = "0." + (r.nextLong() & 0x7fff_ffff_ffff_ffffL) + + (r.nextLong() & 0x7fff_ffff_ffff_ffffL) + + (r.nextLong() & 0x7fff_ffff_ffff_ffffL) + + (r.nextLong() & 0x7fff_ffff_ffff_ffffL) + + (r.nextLong() & 0x7fff_ffff_ffff_ffffL) + + (r.nextLong() & 0x7fff_ffff_ffff_ffffL) + + (r.nextLong() & 0x7fff_ffff_ffff_ffffL) + + (r.nextLong() & 0x7fff_ffff_ffff_ffffL) + + (r.nextLong() & 0x7fff_ffff_ffff_ffffL) + + (r.nextLong() & 0x7fff_ffff_ffff_ffffL); + s10[i] = f + "e" + (r.nextInt(600) - 300); + } + + } + + @Benchmark + @OperationsPerInvocation(N) + public void parseDoubleToString(Blackhole bh) { + for (String s : doubleToString) { + bh.consume(Double.parseDouble(s)); + } + } + + @Benchmark + @OperationsPerInvocation(M) + public void parseDoubleS1(Blackhole bh) { + for (String s : s1) { + bh.consume(Double.parseDouble(s)); + } + } + + @Benchmark + @OperationsPerInvocation(M) + public void parseDoubleS2(Blackhole bh) { + for (String s : s2) { + bh.consume(Double.parseDouble(s)); + } + } + + @Benchmark + @OperationsPerInvocation(M) + public void parseDoubleS4(Blackhole bh) { + for (String s : s4) { + bh.consume(Double.parseDouble(s)); + } + } + + @Benchmark + @OperationsPerInvocation(M) + public void parseDoubleS10(Blackhole bh) { + for (String s : s10) { + bh.consume(Double.parseDouble(s)); + } + } + + @Benchmark + @OperationsPerInvocation(N) + public void parseFloatToString(Blackhole bh) { + for (String s : floatToString) { + bh.consume(Float.parseFloat(s)); + } + } + + @Benchmark + @OperationsPerInvocation(M) + public void parseFloatS1(Blackhole bh) { + for (String s : s1) { + bh.consume(Float.parseFloat(s)); + } + } + + @Benchmark + @OperationsPerInvocation(M) + public void parseFloatS2(Blackhole bh) { + for (String s : s2) { + bh.consume(Float.parseFloat(s)); + } + } + + @Benchmark + @OperationsPerInvocation(M) + public void parseFloatS4(Blackhole bh) { + for (String s : s4) { + bh.consume(Float.parseFloat(s)); + } + } + + @Benchmark + @OperationsPerInvocation(M) + public void parseFloatS10(Blackhole bh) { + for (String s : s10) { + bh.consume(Float.parseFloat(s)); + } + } + +}