From f12a79bf2194fa5657d8f7a671b4195dd4f36cac Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Fri, 1 Apr 2022 14:27:21 +0200 Subject: [PATCH 01/17] poc of float/double writer --- pom.xml | 8 +- .../jackson/core/io/NumberOutput.java | 7 +- .../core/io/numberwriter/RoundingMode.java | 44 ++ .../core/io/numberwriter/RyuDouble.java | 491 ++++++++++++++++++ .../core/io/numberwriter/RyuFloat.java | 417 +++++++++++++++ 5 files changed, 961 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/fasterxml/jackson/core/io/numberwriter/RoundingMode.java create mode 100644 src/main/java/com/fasterxml/jackson/core/io/numberwriter/RyuDouble.java create mode 100644 src/main/java/com/fasterxml/jackson/core/io/numberwriter/RyuFloat.java diff --git a/pom.xml b/pom.xml index 13d68726a7..7e51e4c748 100644 --- a/pom.xml +++ b/pom.xml @@ -37,11 +37,11 @@ - 1.6 - 1.6 + 1.8 + 1.8 - 1.6 - 1.6 + 1.8 + 1.8 com.fasterxml.jackson.core;version=${project.version}, com.fasterxml.jackson.core.*;version=${project.version} diff --git a/src/main/java/com/fasterxml/jackson/core/io/NumberOutput.java b/src/main/java/com/fasterxml/jackson/core/io/NumberOutput.java index 187a0b4e4c..206afbf23c 100644 --- a/src/main/java/com/fasterxml/jackson/core/io/NumberOutput.java +++ b/src/main/java/com/fasterxml/jackson/core/io/NumberOutput.java @@ -1,5 +1,8 @@ package com.fasterxml.jackson.core.io; +import com.fasterxml.jackson.core.io.numberwriter.RyuDouble; +import com.fasterxml.jackson.core.io.numberwriter.RyuFloat; + public final class NumberOutput { private static int MILLION = 1000000; @@ -274,12 +277,12 @@ public static String toString(long v) { } public static String toString(double v) { - return Double.toString(v); + return RyuDouble.doubleToString(v); } // @since 2.6 public static String toString(float v) { - return Float.toString(v); + return RyuFloat.floatToString(v); } /* diff --git a/src/main/java/com/fasterxml/jackson/core/io/numberwriter/RoundingMode.java b/src/main/java/com/fasterxml/jackson/core/io/numberwriter/RoundingMode.java new file mode 100644 index 0000000000..e473079900 --- /dev/null +++ b/src/main/java/com/fasterxml/jackson/core/io/numberwriter/RoundingMode.java @@ -0,0 +1,44 @@ + +// Copyright 2018 Ulf Adams +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.fasterxml.jackson.core.io.numberwriter; + +enum RoundingMode { + CONSERVATIVE { + @Override + public boolean acceptUpperBound(boolean even) { + return false; + } + + @Override + public boolean acceptLowerBound(boolean even) { + return false; + } + }, + ROUND_EVEN { + @Override + public boolean acceptUpperBound(boolean even) { + return even; + } + + @Override + public boolean acceptLowerBound(boolean even) { + return even; + } + }; + + public abstract boolean acceptUpperBound(boolean even); + public abstract boolean acceptLowerBound(boolean even); +} diff --git a/src/main/java/com/fasterxml/jackson/core/io/numberwriter/RyuDouble.java b/src/main/java/com/fasterxml/jackson/core/io/numberwriter/RyuDouble.java new file mode 100644 index 0000000000..26ce85a8cc --- /dev/null +++ b/src/main/java/com/fasterxml/jackson/core/io/numberwriter/RyuDouble.java @@ -0,0 +1,491 @@ + +// Copyright 2018 Ulf Adams +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.fasterxml.jackson.core.io.numberwriter; + +import java.math.BigInteger; + +/** + * An implementation of Ryu for double. + * + * A fork from https://github.com/ulfjack/ryu/ + */ +public final class RyuDouble { + private static boolean DEBUG = false; + + private static final int DOUBLE_MANTISSA_BITS = 52; + private static final long DOUBLE_MANTISSA_MASK = (1L << DOUBLE_MANTISSA_BITS) - 1; + + private static final int DOUBLE_EXPONENT_BITS = 11; + private static final int DOUBLE_EXPONENT_MASK = (1 << DOUBLE_EXPONENT_BITS) - 1; + private static final int DOUBLE_EXPONENT_BIAS = (1 << (DOUBLE_EXPONENT_BITS - 1)) - 1; + + private static final int POS_TABLE_SIZE = 326; + private static final int NEG_TABLE_SIZE = 291; + + // Only for debugging. + private static final BigInteger[] POW5 = new BigInteger[POS_TABLE_SIZE]; + private static final BigInteger[] POW5_INV = new BigInteger[NEG_TABLE_SIZE]; + + private static final int POW5_BITCOUNT = 121; // max 3*31 = 124 + private static final int POW5_QUARTER_BITCOUNT = 31; + private static final int[][] POW5_SPLIT = new int[POS_TABLE_SIZE][4]; + + private static final int POW5_INV_BITCOUNT = 122; // max 3*31 = 124 + private static final int POW5_INV_QUARTER_BITCOUNT = 31; + private static final int[][] POW5_INV_SPLIT = new int[NEG_TABLE_SIZE][4]; + + static { + BigInteger mask = BigInteger.valueOf(1).shiftLeft(POW5_QUARTER_BITCOUNT).subtract(BigInteger.ONE); + BigInteger invMask = BigInteger.valueOf(1).shiftLeft(POW5_INV_QUARTER_BITCOUNT).subtract(BigInteger.ONE); + for (int i = 0; i < Math.max(POW5.length, POW5_INV.length); i++) { + BigInteger pow = BigInteger.valueOf(5).pow(i); + int pow5len = pow.bitLength(); + int expectedPow5Bits = pow5bits(i); + if (expectedPow5Bits != pow5len) { + throw new IllegalStateException(pow5len + " != " + expectedPow5Bits); + } + if (i < POW5.length) { + POW5[i] = pow; + } + if (i < POW5_SPLIT.length) { + for (int j = 0; j < 4; j++) { + POW5_SPLIT[i][j] = pow + .shiftRight(pow5len - POW5_BITCOUNT + (3 - j) * POW5_QUARTER_BITCOUNT) + .and(mask) + .intValueExact(); + } + } + + if (i < POW5_INV_SPLIT.length) { + // We want floor(log_2 5^q) here, which is pow5len - 1. + int j = pow5len - 1 + POW5_INV_BITCOUNT; + BigInteger inv = BigInteger.ONE.shiftLeft(j).divide(pow).add(BigInteger.ONE); + POW5_INV[i] = inv; + for (int k = 0; k < 4; k++) { + if (k == 0) { + POW5_INV_SPLIT[i][k] = inv.shiftRight((3 - k) * POW5_INV_QUARTER_BITCOUNT).intValueExact(); + } else { + POW5_INV_SPLIT[i][k] = inv.shiftRight((3 - k) * POW5_INV_QUARTER_BITCOUNT).and(invMask).intValueExact(); + } + } + } + } + } + + public static String doubleToString(double value) { + return doubleToString(value, RoundingMode.ROUND_EVEN); + } + + public static String doubleToString(double value, RoundingMode roundingMode) { + // Step 1: Decode the floating point number, and unify normalized and subnormal cases. + // First, handle all the trivial cases. + if (Double.isNaN(value)) return "NaN"; + if (value == Double.POSITIVE_INFINITY) return "Infinity"; + if (value == Double.NEGATIVE_INFINITY) return "-Infinity"; + long bits = Double.doubleToLongBits(value); + if (bits == 0) return "0.0"; + if (bits == 0x8000000000000000L) return "-0.0"; + + // Otherwise extract the mantissa and exponent bits and run the full algorithm. + int ieeeExponent = (int) ((bits >>> DOUBLE_MANTISSA_BITS) & DOUBLE_EXPONENT_MASK); + long ieeeMantissa = bits & DOUBLE_MANTISSA_MASK; + int e2; + long m2; + if (ieeeExponent == 0) { + // Denormal number - no implicit leading 1, and the exponent is 1, not 0. + e2 = 1 - DOUBLE_EXPONENT_BIAS - DOUBLE_MANTISSA_BITS; + m2 = ieeeMantissa; + } else { + // Add implicit leading 1. + e2 = ieeeExponent - DOUBLE_EXPONENT_BIAS - DOUBLE_MANTISSA_BITS; + m2 = ieeeMantissa | (1L << DOUBLE_MANTISSA_BITS); + } + + boolean sign = bits < 0; + if (DEBUG) { + System.out.println("IN=" + Long.toBinaryString(bits)); + System.out.println(" S=" + (sign ? "-" : "+") + " E=" + e2 + " M=" + m2); + } + + // Step 2: Determine the interval of legal decimal representations. + boolean even = (m2 & 1) == 0; + final long mv = 4 * m2; + final long mp = 4 * m2 + 2; + final int mmShift = ((m2 != (1L << DOUBLE_MANTISSA_BITS)) || (ieeeExponent <= 1)) ? 1 : 0; + final long mm = 4 * m2 - 1 - mmShift; + e2 -= 2; + + if (DEBUG) { + String sv, sp, sm; + int e10; + if (e2 >= 0) { + sv = BigInteger.valueOf(mv).shiftLeft(e2).toString(); + sp = BigInteger.valueOf(mp).shiftLeft(e2).toString(); + sm = BigInteger.valueOf(mm).shiftLeft(e2).toString(); + e10 = 0; + } else { + BigInteger factor = BigInteger.valueOf(5).pow(-e2); + sv = BigInteger.valueOf(mv).multiply(factor).toString(); + sp = BigInteger.valueOf(mp).multiply(factor).toString(); + sm = BigInteger.valueOf(mm).multiply(factor).toString(); + e10 = e2; + } + + e10 += sp.length() - 1; + + System.out.println("E =" + e10); + System.out.println("d+=" + sp); + System.out.println("d =" + sv); + System.out.println("d-=" + sm); + System.out.println("e2=" + e2); + } + + // Step 3: Convert to a decimal power base using 128-bit arithmetic. + // -1077 = 1 - 1023 - 53 - 2 <= e_2 - 2 <= 2046 - 1023 - 53 - 2 = 968 + long dv, dp, dm; + final int e10; + boolean dmIsTrailingZeros = false, dvIsTrailingZeros = false; + if (e2 >= 0) { + final int q = Math.max(0, ((e2 * 78913) >>> 18) - 1); + // k = constant + floor(log_2(5^q)) + final int k = POW5_INV_BITCOUNT + pow5bits(q) - 1; + final int i = -e2 + q + k; + dv = mulPow5InvDivPow2(mv, q, i); + dp = mulPow5InvDivPow2(mp, q, i); + dm = mulPow5InvDivPow2(mm, q, i); + e10 = q; + if (DEBUG) { + System.out.println(mv + " * 2^" + e2); + System.out.println("V+=" + dp); + System.out.println("V =" + dv); + System.out.println("V-=" + dm); + } + if (DEBUG) { + long exact = POW5_INV[q] + .multiply(BigInteger.valueOf(mv)) + .shiftRight(-e2 + q + k).longValueExact(); + System.out.println(exact + " " + POW5_INV[q].bitCount()); + if (dv != exact) { + throw new IllegalStateException(); + } + } + + if (q <= 21) { + if (mv % 5 == 0) { + dvIsTrailingZeros = multipleOfPowerOf5(mv, q); + } else if (roundingMode.acceptUpperBound(even)) { + dmIsTrailingZeros = multipleOfPowerOf5(mm, q); + } else if (multipleOfPowerOf5(mp, q)) { + dp--; + } + } + } else { + final int q = Math.max(0, ((-e2 * 732923) >>> 20) - 1); + final int i = -e2 - q; + final int k = pow5bits(i) - POW5_BITCOUNT; + final int j = q - k; + dv = mulPow5divPow2(mv, i, j); + dp = mulPow5divPow2(mp, i, j); + dm = mulPow5divPow2(mm, i, j); + e10 = q + e2; + if (DEBUG) { + System.out.println(mv + " * 5^" + (-e2) + " / 10^" + q); + } + if (q <= 1) { + dvIsTrailingZeros = true; + if (roundingMode.acceptUpperBound(even)) { + dmIsTrailingZeros = mmShift == 1; + } else { + dp--; + } + } else if (q < 63) { + dvIsTrailingZeros = (mv & ((1L << (q - 1)) - 1)) == 0; + } + } + if (DEBUG) { + System.out.println("d+=" + dp); + System.out.println("d =" + dv); + System.out.println("d-=" + dm); + System.out.println("e10=" + e10); + System.out.println("d-10=" + dmIsTrailingZeros); + System.out.println("d =" + dvIsTrailingZeros); + System.out.println("Accept upper=" + roundingMode.acceptUpperBound(even)); + System.out.println("Accept lower=" + roundingMode.acceptLowerBound(even)); + } + + // Step 4: Find the shortest decimal representation in the interval of legal representations. + // + // We do some extra work here in order to follow Float/Double.toString semantics. In particular, + // that requires printing in scientific format if and only if the exponent is between -3 and 7, + // and it requires printing at least two decimal digits. + // + // Above, we moved the decimal dot all the way to the right, so now we need to count digits to + // figure out the correct exponent for scientific notation. + final int vplength = decimalLength(dp); + int exp = e10 + vplength - 1; + + // Double.toString semantics requires using scientific notation if and only if outside this range. + boolean scientificNotation = !((exp >= -3) && (exp < 7)); + + int removed = 0; + + int lastRemovedDigit = 0; + long output; + if (dmIsTrailingZeros || dvIsTrailingZeros) { + while (dp / 10 > dm / 10) { + if ((dp < 100) && scientificNotation) { + // Double.toString semantics requires printing at least two digits. + break; + } + dmIsTrailingZeros &= dm % 10 == 0; + dvIsTrailingZeros &= lastRemovedDigit == 0; + lastRemovedDigit = (int) (dv % 10); + dp /= 10; + dv /= 10; + dm /= 10; + removed++; + } + if (dmIsTrailingZeros && roundingMode.acceptLowerBound(even)) { + while (dm % 10 == 0) { + if ((dp < 100) && scientificNotation) { + // Double.toString semantics requires printing at least two digits. + break; + } + dvIsTrailingZeros &= lastRemovedDigit == 0; + lastRemovedDigit = (int) (dv % 10); + dp /= 10; + dv /= 10; + dm /= 10; + removed++; + } + } + if (dvIsTrailingZeros && (lastRemovedDigit == 5) && (dv % 2 == 0)) { + // Round even if the exact numbers is .....50..0. + lastRemovedDigit = 4; + } + output = dv + + ((dv == dm && !(dmIsTrailingZeros && roundingMode.acceptLowerBound(even))) || (lastRemovedDigit >= 5) ? 1 : 0); + } else { + while (dp / 10 > dm / 10) { + if ((dp < 100) && scientificNotation) { + // Double.toString semantics requires printing at least two digits. + break; + } + lastRemovedDigit = (int) (dv % 10); + dp /= 10; + dv /= 10; + dm /= 10; + removed++; + } + output = dv + ((dv == dm || (lastRemovedDigit >= 5)) ? 1 : 0); + } + int olength = vplength - removed; + + if (DEBUG) { + System.out.println("LAST_REMOVED_DIGIT=" + lastRemovedDigit); + System.out.println("VP=" + dp); + System.out.println("VR=" + dv); + System.out.println("VM=" + dm); + System.out.println("O=" + output); + System.out.println("OLEN=" + olength); + System.out.println("EXP=" + exp); + } + + // Step 5: Print the decimal representation. + // We follow Double.toString semantics here. + char[] result = new char[24]; + int index = 0; + if (sign) { + result[index++] = '-'; + } + + // Values in the interval [1E-3, 1E7) are special. + if (scientificNotation) { + // Print in the format x.xxxxxE-yy. + for (int i = 0; i < olength - 1; i++) { + int c = (int) (output % 10); output /= 10; + result[index + olength - i] = (char) ('0' + c); + } + result[index] = (char) ('0' + output % 10); + result[index + 1] = '.'; + index += olength + 1; + if (olength == 1) { + result[index++] = '0'; + } + + // Print 'E', the exponent sign, and the exponent, which has at most three digits. + result[index++] = 'E'; + if (exp < 0) { + result[index++] = '-'; + exp = -exp; + } + if (exp >= 100) { + result[index++] = (char) ('0' + exp / 100); + exp %= 100; + result[index++] = (char) ('0' + exp / 10); + } else if (exp >= 10) { + result[index++] = (char) ('0' + exp / 10); + } + result[index++] = (char) ('0' + exp % 10); + return new String(result, 0, index); + } else { + // Otherwise follow the Java spec for values in the interval [1E-3, 1E7). + if (exp < 0) { + // Decimal dot is before any of the digits. + result[index++] = '0'; + result[index++] = '.'; + for (int i = -1; i > exp; i--) { + result[index++] = '0'; + } + int current = index; + for (int i = 0; i < olength; i++) { + result[current + olength - i - 1] = (char) ('0' + output % 10); + output /= 10; + index++; + } + } else if (exp + 1 >= olength) { + // Decimal dot is after any of the digits. + for (int i = 0; i < olength; i++) { + result[index + olength - i - 1] = (char) ('0' + output % 10); + output /= 10; + } + index += olength; + for (int i = olength; i < exp + 1; i++) { + result[index++] = '0'; + } + result[index++] = '.'; + result[index++] = '0'; + } else { + // Decimal dot is somewhere between the digits. + int current = index + 1; + for (int i = 0; i < olength; i++) { + if (olength - i - 1 == exp) { + result[current + olength - i - 1] = '.'; + current--; + } + result[current + olength - i - 1] = (char) ('0' + output % 10); + output /= 10; + } + index += olength + 1; + } + return new String(result, 0, index); + } + } + + private static int pow5bits(int e) { + return ((e * 1217359) >>> 19) + 1; + } + + private static int decimalLength(long v) { + if (v >= 1000000000000000000L) return 19; + if (v >= 100000000000000000L) return 18; + if (v >= 10000000000000000L) return 17; + if (v >= 1000000000000000L) return 16; + if (v >= 100000000000000L) return 15; + if (v >= 10000000000000L) return 14; + if (v >= 1000000000000L) return 13; + if (v >= 100000000000L) return 12; + if (v >= 10000000000L) return 11; + if (v >= 1000000000L) return 10; + if (v >= 100000000L) return 9; + if (v >= 10000000L) return 8; + if (v >= 1000000L) return 7; + if (v >= 100000L) return 6; + if (v >= 10000L) return 5; + if (v >= 1000L) return 4; + if (v >= 100L) return 3; + if (v >= 10L) return 2; + return 1; + } + + private static boolean multipleOfPowerOf5(long value, int q) { + return pow5Factor(value) >= q; + } + + private static int pow5Factor(long value) { + // We want to find the largest power of 5 that divides value. + if ((value % 5) != 0) return 0; + if ((value % 25) != 0) return 1; + if ((value % 125) != 0) return 2; + if ((value % 625) != 0) return 3; + int count = 4; + value /= 625; + while (value > 0) { + if (value % 5 != 0) { + return count; + } + value /= 5; + count++; + } + throw new IllegalArgumentException("" + value); + } + + /** + * Compute the high digits of m * 5^p / 10^q = m * 5^(p - q) / 2^q = m * 5^i / 2^j, with q chosen + * such that m * 5^i / 2^j has sufficiently many decimal digits to represent the original floating + * point number. + */ + private static long mulPow5divPow2(long m, int i, int j) { + // m has at most 55 bits. + long mHigh = m >>> 31; + long mLow = m & 0x7fffffff; + long bits13 = mHigh * POW5_SPLIT[i][0]; // 124 + long bits03 = mLow * POW5_SPLIT[i][0]; // 93 + long bits12 = mHigh * POW5_SPLIT[i][1]; // 93 + long bits02 = mLow * POW5_SPLIT[i][1]; // 62 + long bits11 = mHigh * POW5_SPLIT[i][2]; // 62 + long bits01 = mLow * POW5_SPLIT[i][2]; // 31 + long bits10 = mHigh * POW5_SPLIT[i][3]; // 31 + long bits00 = mLow * POW5_SPLIT[i][3]; // 0 + int actualShift = j - 3 * 31 - 21; + if (actualShift < 0) { + throw new IllegalArgumentException("" + actualShift); + } + return (((((( + ((bits00 >>> 31) + bits01 + bits10) >>> 31) + + bits02 + bits11) >>> 31) + + bits03 + bits12) >>> 21) + + (bits13 << 10)) >>> actualShift; + } + + /** + * Compute the high digits of m / 5^i / 2^j such that the result is accurate to at least 9 + * decimal digits. i and j are already chosen appropriately. + */ + private static long mulPow5InvDivPow2(long m, int i, int j) { + // m has at most 55 bits. + long mHigh = m >>> 31; + long mLow = m & 0x7fffffff; + long bits13 = mHigh * POW5_INV_SPLIT[i][0]; + long bits03 = mLow * POW5_INV_SPLIT[i][0]; + long bits12 = mHigh * POW5_INV_SPLIT[i][1]; + long bits02 = mLow * POW5_INV_SPLIT[i][1]; + long bits11 = mHigh * POW5_INV_SPLIT[i][2]; + long bits01 = mLow * POW5_INV_SPLIT[i][2]; + long bits10 = mHigh * POW5_INV_SPLIT[i][3]; + long bits00 = mLow * POW5_INV_SPLIT[i][3]; + + int actualShift = j - 3 * 31 - 21; + if (actualShift < 0) { + throw new IllegalArgumentException("" + actualShift); + } + return (((((( + ((bits00 >>> 31) + bits01 + bits10) >>> 31) + + bits02 + bits11) >>> 31) + + bits03 + bits12) >>> 21) + + (bits13 << 10)) >>> actualShift; + } +} diff --git a/src/main/java/com/fasterxml/jackson/core/io/numberwriter/RyuFloat.java b/src/main/java/com/fasterxml/jackson/core/io/numberwriter/RyuFloat.java new file mode 100644 index 0000000000..b249c0a5ef --- /dev/null +++ b/src/main/java/com/fasterxml/jackson/core/io/numberwriter/RyuFloat.java @@ -0,0 +1,417 @@ + +// Copyright 2018 Ulf Adams +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.fasterxml.jackson.core.io.numberwriter; + +import java.math.BigInteger; + +/** + * An implementation of Ryu for float. + * + * A fork from https://github.com/ulfjack/ryu/ + */ +public final class RyuFloat { + private static boolean DEBUG = false; + + private static final int FLOAT_MANTISSA_BITS = 23; + private static final int FLOAT_MANTISSA_MASK = (1 << FLOAT_MANTISSA_BITS) - 1; + + private static final int FLOAT_EXPONENT_BITS = 8; + private static final int FLOAT_EXPONENT_MASK = (1 << FLOAT_EXPONENT_BITS) - 1; + private static final int FLOAT_EXPONENT_BIAS = (1 << (FLOAT_EXPONENT_BITS - 1)) - 1; + + private static final long LOG10_2_DENOMINATOR = 10000000L; + private static final long LOG10_2_NUMERATOR = (long) (LOG10_2_DENOMINATOR * Math.log10(2)); + + private static final long LOG10_5_DENOMINATOR = 10000000L; + private static final long LOG10_5_NUMERATOR = (long) (LOG10_5_DENOMINATOR * Math.log10(5)); + + private static final long LOG2_5_DENOMINATOR = 10000000L; + private static final long LOG2_5_NUMERATOR = (long) (LOG2_5_DENOMINATOR * (Math.log(5)/Math.log(2))); + + private static final int POS_TABLE_SIZE = 47; + private static final int INV_TABLE_SIZE = 31; + + // Only for debugging. + private static final BigInteger[] POW5 = new BigInteger[POS_TABLE_SIZE]; + private static final BigInteger[] POW5_INV = new BigInteger[INV_TABLE_SIZE]; + + private static final int POW5_BITCOUNT = 61; + private static final int POW5_HALF_BITCOUNT = 31; + private static final int[][] POW5_SPLIT = new int[POS_TABLE_SIZE][2]; + + private static final int POW5_INV_BITCOUNT = 59; + private static final int POW5_INV_HALF_BITCOUNT = 31; + private static final int[][] POW5_INV_SPLIT = new int[INV_TABLE_SIZE][2]; + + static { + BigInteger mask = BigInteger.valueOf(1).shiftLeft(POW5_HALF_BITCOUNT).subtract(BigInteger.ONE); + BigInteger maskInv = BigInteger.valueOf(1).shiftLeft(POW5_INV_HALF_BITCOUNT).subtract(BigInteger.ONE); + for (int i = 0; i < Math.max(POW5.length, POW5_INV.length); i++) { + BigInteger pow = BigInteger.valueOf(5).pow(i); + int pow5len = pow.bitLength(); + int expectedPow5Bits = pow5bits(i); + if (expectedPow5Bits != pow5len) { + throw new IllegalStateException(pow5len + " != " + expectedPow5Bits); + } + if (i < POW5.length) { + POW5[i] = pow; + } + if (i < POW5_SPLIT.length) { + POW5_SPLIT[i][0] = pow.shiftRight(pow5len - POW5_BITCOUNT + POW5_HALF_BITCOUNT).intValueExact(); + POW5_SPLIT[i][1] = pow.shiftRight(pow5len - POW5_BITCOUNT).and(mask).intValueExact(); + } + + if (i < POW5_INV.length) { + int j = pow5len - 1 + POW5_INV_BITCOUNT; + BigInteger inv = BigInteger.ONE.shiftLeft(j).divide(pow).add(BigInteger.ONE); + POW5_INV[i] = inv; + POW5_INV_SPLIT[i][0] = inv.shiftRight(POW5_INV_HALF_BITCOUNT).intValueExact(); + POW5_INV_SPLIT[i][1] = inv.and(maskInv).intValueExact(); + } + } + } + + public static String floatToString(float value) { + return floatToString(value, RoundingMode.ROUND_EVEN); + } + + public static String floatToString(float value, RoundingMode roundingMode) { + // Step 1: Decode the floating point number, and unify normalized and subnormal cases. + // First, handle all the trivial cases. + if (Float.isNaN(value)) return "NaN"; + if (value == Float.POSITIVE_INFINITY) return "Infinity"; + if (value == Float.NEGATIVE_INFINITY) return "-Infinity"; + int bits = Float.floatToIntBits(value); + if (bits == 0) return "0.0"; + if (bits == 0x80000000) return "-0.0"; + + // Otherwise extract the mantissa and exponent bits and run the full algorithm. + int ieeeExponent = (bits >> FLOAT_MANTISSA_BITS) & FLOAT_EXPONENT_MASK; + int ieeeMantissa = bits & FLOAT_MANTISSA_MASK; + // By default, the correct mantissa starts with a 1, except for denormal numbers. + int e2; + int m2; + if (ieeeExponent == 0) { + e2 = 1 - FLOAT_EXPONENT_BIAS - FLOAT_MANTISSA_BITS; + m2 = ieeeMantissa; + } else { + e2 = ieeeExponent - FLOAT_EXPONENT_BIAS - FLOAT_MANTISSA_BITS; + m2 = ieeeMantissa | (1 << FLOAT_MANTISSA_BITS); + } + + boolean sign = bits < 0; + if (DEBUG) { + System.out.println("IN=" + Long.toBinaryString(bits)); + System.out.println(" S=" + (sign ? "-" : "+") + " E=" + e2 + " M=" + m2); + } + + // Step 2: Determine the interval of legal decimal representations. + boolean even = (m2 & 1) == 0; + int mv = 4 * m2; + int mp = 4 * m2 + 2; + int mm = 4 * m2 - ((m2 != (1L << FLOAT_MANTISSA_BITS)) || (ieeeExponent <= 1) ? 2 : 1); + e2 -= 2; + + if (DEBUG) { + String sv, sp, sm; + int e10; + if (e2 >= 0) { + sv = BigInteger.valueOf(mv).shiftLeft(e2).toString(); + sp = BigInteger.valueOf(mp).shiftLeft(e2).toString(); + sm = BigInteger.valueOf(mm).shiftLeft(e2).toString(); + e10 = 0; + } else { + BigInteger factor = BigInteger.valueOf(5).pow(-e2); + sv = BigInteger.valueOf(mv).multiply(factor).toString(); + sp = BigInteger.valueOf(mp).multiply(factor).toString(); + sm = BigInteger.valueOf(mm).multiply(factor).toString(); + e10 = e2; + } + + e10 += sp.length() - 1; + + System.out.println("Exact values"); + System.out.println(" m =" + mv); + System.out.println(" E =" + e10); + System.out.println(" d+=" + sp); + System.out.println(" d =" + sv); + System.out.println(" d-=" + sm); + System.out.println(" e2=" + e2); + } + + // Step 3: Convert to a decimal power base using 128-bit arithmetic. + // -151 = 1 - 127 - 23 - 2 <= e_2 - 2 <= 254 - 127 - 23 - 2 = 102 + int dp, dv, dm; + int e10; + boolean dpIsTrailingZeros, dvIsTrailingZeros, dmIsTrailingZeros; + int lastRemovedDigit = 0; + if (e2 >= 0) { + // Compute m * 2^e_2 / 10^q = m * 2^(e_2 - q) / 5^q + int q = (int) (e2 * LOG10_2_NUMERATOR / LOG10_2_DENOMINATOR); + int k = POW5_INV_BITCOUNT + pow5bits(q) - 1; + int i = -e2 + q + k; + dv = (int) mulPow5InvDivPow2(mv, q, i); + dp = (int) mulPow5InvDivPow2(mp, q, i); + dm = (int) mulPow5InvDivPow2(mm, q, i); + if (q != 0 && ((dp - 1) / 10 <= dm / 10)) { + // We need to know one removed digit even if we are not going to loop below. We could use + // q = X - 1 above, except that would require 33 bits for the result, and we've found that + // 32-bit arithmetic is faster even on 64-bit machines. + int l = POW5_INV_BITCOUNT + pow5bits(q - 1) - 1; + lastRemovedDigit = (int) (mulPow5InvDivPow2(mv, q - 1, -e2 + q - 1 + l) % 10); + } + e10 = q; + if (DEBUG) { + System.out.println(mv + " * 2^" + e2 + " / 10^" + q); + } + + dpIsTrailingZeros = pow5Factor(mp) >= q; + dvIsTrailingZeros = pow5Factor(mv) >= q; + dmIsTrailingZeros = pow5Factor(mm) >= q; + } else { + // Compute m * 5^(-e_2) / 10^q = m * 5^(-e_2 - q) / 2^q + int q = (int) (-e2 * LOG10_5_NUMERATOR / LOG10_5_DENOMINATOR); + int i = -e2 - q; + int k = pow5bits(i) - POW5_BITCOUNT; + int j = q - k; + dv = (int) mulPow5divPow2(mv, i, j); + dp = (int) mulPow5divPow2(mp, i, j); + dm = (int) mulPow5divPow2(mm, i, j); + if (q != 0 && ((dp - 1) / 10 <= dm / 10)) { + j = q - 1 - (pow5bits(i + 1) - POW5_BITCOUNT); + lastRemovedDigit = (int) (mulPow5divPow2(mv, i + 1, j) % 10); + } + e10 = q + e2; // Note: e2 and e10 are both negative here. + if (DEBUG) { + System.out.println(mv + " * 5^" + (-e2) + " / 10^" + q + " = " + mv + " * 5^" + (-e2 - q) + " / 2^" + q); + } + + dpIsTrailingZeros = 1 >= q; + dvIsTrailingZeros = (q < FLOAT_MANTISSA_BITS) && (mv & ((1 << (q - 1)) - 1)) == 0; + dmIsTrailingZeros = (mm % 2 == 1 ? 0 : 1) >= q; + } + if (DEBUG) { + System.out.println("Actual values"); + System.out.println(" d+=" + dp); + System.out.println(" d =" + dv); + System.out.println(" d-=" + dm); + System.out.println(" last removed=" + lastRemovedDigit); + System.out.println(" e10=" + e10); + System.out.println(" d+10=" + dpIsTrailingZeros); + System.out.println(" d =" + dvIsTrailingZeros); + System.out.println(" d-10=" + dmIsTrailingZeros); + } + + // Step 4: Find the shortest decimal representation in the interval of legal representations. + // + // We do some extra work here in order to follow Float/Double.toString semantics. In particular, + // that requires printing in scientific format if and only if the exponent is between -3 and 7, + // and it requires printing at least two decimal digits. + // + // Above, we moved the decimal dot all the way to the right, so now we need to count digits to + // figure out the correct exponent for scientific notation. + int dplength = decimalLength(dp); + int exp = e10 + dplength - 1; + + // Float.toString semantics requires using scientific notation if and only if outside this range. + boolean scientificNotation = !((exp >= -3) && (exp < 7)); + + int removed = 0; + if (dpIsTrailingZeros && !roundingMode.acceptUpperBound(even)) { + dp--; + } + + while (dp / 10 > dm / 10) { + if ((dp < 100) && scientificNotation) { + // We print at least two digits, so we might as well stop now. + break; + } + dmIsTrailingZeros &= dm % 10 == 0; + dp /= 10; + lastRemovedDigit = dv % 10; + dv /= 10; + dm /= 10; + removed++; + } + if (dmIsTrailingZeros && roundingMode.acceptLowerBound(even)) { + while (dm % 10 == 0) { + if ((dp < 100) && scientificNotation) { + // We print at least two digits, so we might as well stop now. + break; + } + dp /= 10; + lastRemovedDigit = dv % 10; + dv /= 10; + dm /= 10; + removed++; + } + } + + if (dvIsTrailingZeros && (lastRemovedDigit == 5) && (dv % 2 == 0)) { + // Round down not up if the number ends in X50000 and the number is even. + lastRemovedDigit = 4; + } + int output = dv + + ((dv == dm && !(dmIsTrailingZeros && roundingMode.acceptLowerBound(even))) || (lastRemovedDigit >= 5) ? 1 : 0); + int olength = dplength - removed; + + if (DEBUG) { + System.out.println("Actual values after loop"); + System.out.println(" d+=" + dp); + System.out.println(" d =" + dv); + System.out.println(" d-=" + dm); + System.out.println(" last removed=" + lastRemovedDigit); + System.out.println(" e10=" + e10); + System.out.println(" d+10=" + dpIsTrailingZeros); + System.out.println(" d-10=" + dmIsTrailingZeros); + System.out.println(" output=" + output); + System.out.println(" output_length=" + olength); + System.out.println(" output_exponent=" + exp); + } + + // Step 5: Print the decimal representation. + // We follow Float.toString semantics here. + char[] result = new char[15]; + int index = 0; + if (sign) { + result[index++] = '-'; + } + + if (scientificNotation) { + // Print in the format x.xxxxxE-yy. + for (int i = 0; i < olength - 1; i++) { + int c = output % 10; output /= 10; + result[index + olength - i] = (char) ('0' + c); + } + result[index] = (char) ('0' + output % 10); + result[index + 1] = '.'; + index += olength + 1; + if (olength == 1) { + result[index++] = '0'; + } + + // Print 'E', the exponent sign, and the exponent, which has at most two digits. + result[index++] = 'E'; + if (exp < 0) { + result[index++] = '-'; + exp = -exp; + } + if (exp >= 10) { + result[index++] = (char) ('0' + exp / 10); + } + result[index++] = (char) ('0' + exp % 10); + } else { + // Otherwise follow the Java spec for values in the interval [1E-3, 1E7). + if (exp < 0) { + // Decimal dot is before any of the digits. + result[index++] = '0'; + result[index++] = '.'; + for (int i = -1; i > exp; i--) { + result[index++] = '0'; + } + int current = index; + for (int i = 0; i < olength; i++) { + result[current + olength - i - 1] = (char) ('0' + output % 10); + output /= 10; + index++; + } + } else if (exp + 1 >= olength) { + // Decimal dot is after any of the digits. + for (int i = 0; i < olength; i++) { + result[index + olength - i - 1] = (char) ('0' + output % 10); + output /= 10; + } + index += olength; + for (int i = olength; i < exp + 1; i++) { + result[index++] = '0'; + } + result[index++] = '.'; + result[index++] = '0'; + } else { + // Decimal dot is somewhere between the digits. + int current = index + 1; + for (int i = 0; i < olength; i++) { + if (olength - i - 1 == exp) { + result[current + olength - i - 1] = '.'; + current--; + } + result[current + olength - i - 1] = (char) ('0' + output % 10); + output /= 10; + } + index += olength + 1; + } + } + return new String(result, 0, index); + } + + private static int pow5bits(int e) { + return e == 0 ? 1 : (int) ((e * LOG2_5_NUMERATOR + LOG2_5_DENOMINATOR - 1)/LOG2_5_DENOMINATOR); + } + + /** + * Returns the exponent of the largest power of 5 that divides the given value, i.e., returns + * i such that value = 5^i * x, where x is an integer. + */ + private static int pow5Factor(int value) { + int count = 0; + while (value > 0) { + if (value % 5 != 0) { + return count; + } + value /= 5; + count++; + } + throw new IllegalArgumentException("" + value); + } + + /** + * Compute the exact result of [m * 5^(-e_2) / 10^q] = [m * 5^(-e_2 - q) / 2^q] + * = [m * [5^(p - q)/2^k] / 2^(q - k)] = [m * POW5[i] / 2^j]. + */ + private static long mulPow5divPow2(int m, int i, int j) { + if (j - POW5_HALF_BITCOUNT < 0) { + throw new IllegalArgumentException(); + } + long bits0 = m * (long) POW5_SPLIT[i][0]; + long bits1 = m * (long) POW5_SPLIT[i][1]; + return (bits0 + (bits1 >> POW5_HALF_BITCOUNT)) >> (j - POW5_HALF_BITCOUNT); + } + + /** + * Compute the exact result of [m * 2^p / 10^q] = [m * 2^(p - q) / 5 ^ q] + * = [m * [2^k / 5^q] / 2^-(p - q - k)] = [m * POW5_INV[q] / 2^j]. + */ + private static long mulPow5InvDivPow2(int m, int q, int j) { + if (j - POW5_INV_HALF_BITCOUNT < 0) { + throw new IllegalArgumentException(); + } + long bits0 = m * (long) POW5_INV_SPLIT[q][0]; + long bits1 = m * (long) POW5_INV_SPLIT[q][1]; + return (bits0 + (bits1 >> POW5_INV_HALF_BITCOUNT)) >> (j - POW5_INV_HALF_BITCOUNT); + } + + private static int decimalLength(int v) { + int length = 10; + int factor = 1000000000; + for (; length > 0; length--) { + if (v >= factor) { + break; + } + factor /= 10; + } + return length; + } +} From 3d83cf4ecc6acc6c6066d67d3f0289e4688a3e6f Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Tue, 12 Apr 2022 15:31:38 +0200 Subject: [PATCH 02/17] copy over more tests --- .../jackson/core/io/NumberOutput.java | 4 +- .../{numberwriter => ryu}/RoundingMode.java | 7 +- .../io/{numberwriter => ryu}/RyuDouble.java | 2 +- .../io/{numberwriter => ryu}/RyuFloat.java | 2 +- .../core/io/ryu/DoubleToStringTest.java | 94 +++++++ .../jackson/core/io/ryu/DoubleUtils.java | 213 +++++++++++++++ .../jackson/core/io/ryu/EuclidMinMaxTest.java | 133 ++++++++++ .../core/io/ryu/FloatToStringTest.java | 125 +++++++++ .../jackson/core/io/ryu/JafferDoubleTest.java | 72 ++++++ .../jackson/core/io/ryu/RyuDoubleTest.java | 26 ++ .../jackson/core/io/ryu/RyuFloatTest.java | 26 ++ .../jackson/core/io/ryu/SlowDoubleTest.java | 28 ++ .../jackson/core/io/ryu/SlowFloatTest.java | 24 ++ .../ryu/analysis/ComputeRequiredBitSizes.java | 124 +++++++++ .../io/ryu/analysis/ComputeTableSizes.java | 65 +++++ .../core/io/ryu/analysis/EuclidMinMax.java | 148 +++++++++++ .../analysis/ExhaustiveFloatComparison.java | 55 ++++ .../analysis/ExtensiveDoubleComparison.java | 44 ++++ .../io/ryu/analysis/FloatingPointFormat.java | 50 ++++ .../ryu/analysis/PrintDoubleLookupTable.java | 180 +++++++++++++ .../ryu/analysis/PrintFloatLookupTable.java | 69 +++++ .../ryu/analysis/PrintGenericLookupTable.java | 242 +++++++++++++++++ .../core/io/ryu/analysis/SlowConversion.java | 243 ++++++++++++++++++ 23 files changed, 1970 insertions(+), 6 deletions(-) rename src/main/java/com/fasterxml/jackson/core/io/{numberwriter => ryu}/RoundingMode.java (89%) rename src/main/java/com/fasterxml/jackson/core/io/{numberwriter => ryu}/RyuDouble.java (99%) rename src/main/java/com/fasterxml/jackson/core/io/{numberwriter => ryu}/RyuFloat.java (99%) create mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/DoubleToStringTest.java create mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/DoubleUtils.java create mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/EuclidMinMaxTest.java create mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/FloatToStringTest.java create mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/JafferDoubleTest.java create mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/RyuDoubleTest.java create mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/RyuFloatTest.java create mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/SlowDoubleTest.java create mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/SlowFloatTest.java create mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/ComputeRequiredBitSizes.java create mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/ComputeTableSizes.java create mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/EuclidMinMax.java create mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/ExhaustiveFloatComparison.java create mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/ExtensiveDoubleComparison.java create mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/FloatingPointFormat.java create mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/PrintDoubleLookupTable.java create mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/PrintFloatLookupTable.java create mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/PrintGenericLookupTable.java create mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/SlowConversion.java diff --git a/src/main/java/com/fasterxml/jackson/core/io/NumberOutput.java b/src/main/java/com/fasterxml/jackson/core/io/NumberOutput.java index 206afbf23c..344e409a09 100644 --- a/src/main/java/com/fasterxml/jackson/core/io/NumberOutput.java +++ b/src/main/java/com/fasterxml/jackson/core/io/NumberOutput.java @@ -1,7 +1,7 @@ package com.fasterxml.jackson.core.io; -import com.fasterxml.jackson.core.io.numberwriter.RyuDouble; -import com.fasterxml.jackson.core.io.numberwriter.RyuFloat; +import com.fasterxml.jackson.core.io.ryu.RyuDouble; +import com.fasterxml.jackson.core.io.ryu.RyuFloat; public final class NumberOutput { diff --git a/src/main/java/com/fasterxml/jackson/core/io/numberwriter/RoundingMode.java b/src/main/java/com/fasterxml/jackson/core/io/ryu/RoundingMode.java similarity index 89% rename from src/main/java/com/fasterxml/jackson/core/io/numberwriter/RoundingMode.java rename to src/main/java/com/fasterxml/jackson/core/io/ryu/RoundingMode.java index e473079900..0e73e5d853 100644 --- a/src/main/java/com/fasterxml/jackson/core/io/numberwriter/RoundingMode.java +++ b/src/main/java/com/fasterxml/jackson/core/io/ryu/RoundingMode.java @@ -13,9 +13,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -package com.fasterxml.jackson.core.io.numberwriter; +package com.fasterxml.jackson.core.io.ryu; -enum RoundingMode { +/** + * This is an internal class, public only for internal testing + */ +public enum RoundingMode { CONSERVATIVE { @Override public boolean acceptUpperBound(boolean even) { diff --git a/src/main/java/com/fasterxml/jackson/core/io/numberwriter/RyuDouble.java b/src/main/java/com/fasterxml/jackson/core/io/ryu/RyuDouble.java similarity index 99% rename from src/main/java/com/fasterxml/jackson/core/io/numberwriter/RyuDouble.java rename to src/main/java/com/fasterxml/jackson/core/io/ryu/RyuDouble.java index 26ce85a8cc..65f040bad2 100644 --- a/src/main/java/com/fasterxml/jackson/core/io/numberwriter/RyuDouble.java +++ b/src/main/java/com/fasterxml/jackson/core/io/ryu/RyuDouble.java @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package com.fasterxml.jackson.core.io.numberwriter; +package com.fasterxml.jackson.core.io.ryu; import java.math.BigInteger; diff --git a/src/main/java/com/fasterxml/jackson/core/io/numberwriter/RyuFloat.java b/src/main/java/com/fasterxml/jackson/core/io/ryu/RyuFloat.java similarity index 99% rename from src/main/java/com/fasterxml/jackson/core/io/numberwriter/RyuFloat.java rename to src/main/java/com/fasterxml/jackson/core/io/ryu/RyuFloat.java index b249c0a5ef..49a02313ff 100644 --- a/src/main/java/com/fasterxml/jackson/core/io/numberwriter/RyuFloat.java +++ b/src/main/java/com/fasterxml/jackson/core/io/ryu/RyuFloat.java @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package com.fasterxml.jackson.core.io.numberwriter; +package com.fasterxml.jackson.core.io.ryu; import java.math.BigInteger; diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/DoubleToStringTest.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/DoubleToStringTest.java new file mode 100644 index 0000000000..ec18f041fc --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/io/ryu/DoubleToStringTest.java @@ -0,0 +1,94 @@ +// Copyright 2018 Ulf Adams +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.fasterxml.jackson.core.io.ryu; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public abstract class DoubleToStringTest { + abstract String f(double f, RoundingMode roundingMode); + + private void assertD2sEquals(String expected, double f) { + assertEquals(expected, f(f, RoundingMode.ROUND_EVEN)); + assertEquals(expected, f(f, RoundingMode.CONSERVATIVE)); + } + + private void assertD2sEquals(String expectedRoundEven, String expectedConservative, double f) { + assertEquals(expectedRoundEven, f(f, RoundingMode.ROUND_EVEN)); + assertEquals(expectedConservative, f(f, RoundingMode.CONSERVATIVE)); + } + + @Test + public void simpleCases() { + assertD2sEquals("0.0", 0); + assertD2sEquals("-0.0", Double.longBitsToDouble(0x8000000000000000L)); + assertD2sEquals("1.0", 1.0d); + assertD2sEquals("-1.0", -1.0d); + assertD2sEquals("NaN", Double.NaN); + assertD2sEquals("Infinity", Double.POSITIVE_INFINITY); + assertD2sEquals("-Infinity", Double.NEGATIVE_INFINITY); + } + + @Test + public void switchToSubnormal() { + assertD2sEquals("2.2250738585072014E-308", Double.longBitsToDouble(0x0010000000000000L)); + } + + /** + * Floating point values in the range 1.0E-3 <= x < 1.0E7 have to be printed + * without exponent. This test checks the values at those boundaries. + */ + @Test + public void boundaryConditions() { + // x = 1.0E7 + assertD2sEquals("1.0E7", 1.0E7d); + // x < 1.0E7 + assertD2sEquals("9999999.999999998", 9999999.999999998d); + // x = 1.0E-3 + assertD2sEquals("0.001", 0.001d); + // x < 1.0E-3 + assertD2sEquals("9.999999999999998E-4", 0.0009999999999999998d); + } + + @Test + public void minAndMax() { + assertD2sEquals("1.7976931348623157E308", Double.longBitsToDouble(0x7fefffffffffffffL)); + assertD2sEquals("4.9E-324", Double.longBitsToDouble(1)); + } + + @Test + public void roundingModeEven() { + assertD2sEquals("-2.109808898695963E16", "-2.1098088986959632E16", -2.109808898695963E16); + } + + @Test + public void regressionTest() { + assertD2sEquals("4.940656E-318", 4.940656E-318d); + assertD2sEquals("1.18575755E-316", 1.18575755E-316d); + assertD2sEquals("2.989102097996E-312", 2.989102097996E-312d); + assertD2sEquals("9.0608011534336E15", 9.0608011534336E15d); + assertD2sEquals("4.708356024711512E18", 4.708356024711512E18); + assertD2sEquals("9.409340012568248E18", 9.409340012568248E18); + // This number naively requires 65 bit for the intermediate results if we reduce the lookup + // table by half. This checks that we don't loose any information in that case. + assertD2sEquals("1.8531501765868567E21", 1.8531501765868567E21); + assertD2sEquals("-3.347727380279489E33", -3.347727380279489E33); + // Discovered by Andriy Plokhotnyuk, see #29. + assertD2sEquals("1.9430376160308388E16", 1.9430376160308388E16); + assertD2sEquals("-6.9741824662760956E19", -6.9741824662760956E19); + assertD2sEquals("4.3816050601147837E18", 4.3816050601147837E18); + } +} \ No newline at end of file diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/DoubleUtils.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/DoubleUtils.java new file mode 100644 index 0000000000..8c3cd7f13a --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/io/ryu/DoubleUtils.java @@ -0,0 +1,213 @@ +/* + * Copyright 2016 Heng Yuan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fasterxml.jackson.core.io.ryu; + +import java.math.BigInteger; + +/** + * The algorithm and code used here are from + * Jaffer, Aubrey. "Easy Accurate Reading and Writing of Floating-Point + * Numbers." arXiv preprint arXiv:1310.8121 (2013). + * (PDF) + *

+ * And here is the license given from him: + *

+ * Copyright (c) Aubrey Jaffer 2014
+ * Permission to copy this software, to modify it, to redistribute it,
+ * to distribute modified versions, and to use it for any purpose is
+ * granted, subject to the following restrictions and understandings.
+ *
+ * 1.  Any copy made of this software must include this copyright notice
+ * in full.
+ *
+ * 2.  I have made no warranty or representation that the operation of
+ * this software will be error-free, and I am under no obligation to
+ * provide any services, by way of maintenance, update, or otherwise.
+ *
+ * 3.  In conjunction with products arising from the use of this
+ * material, there shall be no use of my name in any advertising,
+ * promotional, or sales literature without prior written consent in
+ * each case.
+ * 
+ *

+ * I made minor modifications to fit my need. + * + * @author Heng Yuan + */ +class DoubleUtils +{ + private final static int dblMantDig = 53; + private final static BigInteger[] bp5a = new BigInteger[326]; + + private static final long lp5[] = + { 1L, 5L, 25L, 125L, 625L, 3125L, 15625L, 78125L, 390625L, 1953125L, + 9765625L, 48828125L, 244140625L, 1220703125L, 6103515625L, 30517578125L, + 152587890625L, 762939453125L, 3814697265625L, 19073486328125L, + 95367431640625L, 476837158203125L, 2384185791015625L, 11920928955078125L, + 59604644775390625L, 298023223876953125L, 1490116119384765625L, + 7450580596923828125L }; + + private final static BigInteger bp5 (int p) + { + BigInteger[] pa = bp5a; + if (pa[p] != null) + return pa[p]; + else if (p < lp5.length) + { + BigInteger v = BigInteger.valueOf (lp5[p]); + pa[p] = v; + return v; + } + else + { + // use divide-n-conquer strategy to compute the number + int a = p >> 1; + int b = p - a; // b has 50% chance being the same as a + BigInteger v = bp5 (a).multiply (bp5 (b)); + pa[p] = v; + return v; + } + } + + private final static double llog2 = Math.log10 (2); + + private static long rq (BigInteger num, BigInteger den) + { + BigInteger quorem[] = num.divideAndRemainder (den); + long quo = quorem[0].longValue (); + int cmpflg = quorem[1].shiftLeft (1).compareTo (den); + if ((quo & 1L) == 0L ? 1 == cmpflg : -1 < cmpflg) + return quo + 1L; + else + return quo; + } + + private static double metd (long lmant, int point) + { + BigInteger mant = BigInteger.valueOf (lmant); + if (point >= 0) + { + BigInteger num = mant.multiply (bp5 (point)); + int bex = num.bitLength () - dblMantDig; + if (bex <= 0) + return Math.scalb (num.doubleValue (), point); + long quo = rq (num, BigInteger.ONE.shiftLeft (bex)); + return Math.scalb ((double) quo, bex + point); + } + int maxpow = bp5a.length - 1; + BigInteger scl = (-point <= maxpow) ? bp5 (-point) : bp5 (maxpow).multiply (bp5 (-point - maxpow)); + int bex = mant.bitLength () - scl.bitLength () - dblMantDig; + BigInteger num = mant.shiftLeft (-bex); + long quo = rq (num, scl); + if (64 - Long.numberOfLeadingZeros (quo) > dblMantDig) + { + bex++; + quo = rq (num, scl.shiftLeft (1)); + } + return Math.scalb ((double) quo, bex + point); + } + + /** + * This function converts a double representation to a string format. + * + * @param f + * A double value. + * @return A string representation of the double value f. + */ + public static String toString (double f) + { + long lbits = Double.doubleToLongBits (f); + if (f != f) + return "NaN"; + if (f + f == f) + return (f == 0.0) ? "0" : ((f > 0) ? "Infinity" : "-Infinity"); + StringBuilder str = new StringBuilder (24); + if (f < 0) + { + str.append ('-'); + // there is a rounding bug for negative values with positive + // exp, such as -5e100. Negate the value to avoid the issue. + f = -f; + } + int ue2 = (int) (lbits >>> 52 & 0x7ff); + int e2 = ue2 - 1023 - 52 + (ue2 == 0 ? 1 : 0); + int point = (int) Math.ceil (e2 * llog2); + long lquo; + long lmant = (lbits & ((1L << 52) - 1)) + (ue2 == 0 ? 0L : 1L << 52); + BigInteger mant = BigInteger.valueOf (lmant); + if (e2 > 0) + { + BigInteger num = mant.shiftLeft (e2 - point); + lquo = rq (num, bp5 (point)); + if (metd (lquo, point) != f) + lquo = rq (num.shiftLeft (1), bp5 (--point)); + } + else + { + BigInteger num = mant.multiply (bp5 (-point)); + BigInteger den = BigInteger.ONE.shiftLeft (point - e2); + lquo = rq (num, den); + if (metd (lquo, point) != f) + { + point--; + lquo = rq (num.multiply (BigInteger.TEN), den); + } + } + String sman = Long.toString (lquo); + int len = sman.length (), lent = len; + while (sman.charAt (lent - 1) == '0') + { + lent--; + } + int exp = point + len - 1; + + if (exp >= 0 && exp < len) + { + // length string is longer than exp + // so the period is in the middle of sman + ++exp; // exp is now the period location + str.append (sman, 0, exp); + if (lent > exp) + { + str.append ('.'); + str.append (sman, exp, lent); + } + } + else if (exp < 0 && exp > -5) + { + str.append ("0."); + str.append ("0000".substring (0, -exp - 1)); + str.append (sman, 0, lent); + } + else + { + // scientific notation + + str.append (sman, 0, 1); + if (lent > 1) + { + str.append ('.'); + str.append (sman, 1, lent); + } + if (exp != 0) + { + str.append ('e'); + str.append (exp); + } + } + return str.toString (); + } +} \ No newline at end of file diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/EuclidMinMaxTest.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/EuclidMinMaxTest.java new file mode 100644 index 0000000000..502e5ab4a4 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/io/ryu/EuclidMinMaxTest.java @@ -0,0 +1,133 @@ +package com.fasterxml.jackson.core.io.ryu; + +import static org.junit.Assert.assertEquals; +import java.math.BigInteger; +import java.util.Random; +import org.junit.Test; +import com.fasterxml.jackson.core.io.ryu.analysis.EuclidMinMax; + +public class EuclidMinMaxTest { + private static int min(int a, int b, int max) { + return EuclidMinMax.min(BigInteger.valueOf(a), BigInteger.valueOf(b), BigInteger.valueOf(max)).intValueExact(); + } + + private static int max(int a, int b, int max) { + return EuclidMinMax.max(BigInteger.valueOf(a), BigInteger.valueOf(b), BigInteger.valueOf(max)).intValueExact(); + } + + private static BigInteger minSlow(BigInteger multiplier, BigInteger modulo, BigInteger maximum) { +// if (maximum.compareTo(modulo) >= 0) { +// return BigInteger.ZERO; +// } + BigInteger result = multiplier.mod(modulo); + for (long l = 2; l <= maximum.longValueExact(); l++) { + BigInteger cand = BigInteger.valueOf(l).multiply(multiplier).mod(modulo); + if (cand.compareTo(result) < 0) { + result = cand; + } + } + return result; + } + + private static BigInteger maxSlow(BigInteger multiplier, BigInteger modulo, BigInteger maximum) { +// if (maximum.compareTo(modulo) >= 0) { +// return modulo.subtract(BigInteger.ONE); +// } + BigInteger result = multiplier.mod(modulo); + for (long l = 2; l <= maximum.longValueExact(); l++) { + BigInteger cand = BigInteger.valueOf(l).multiply(multiplier).mod(modulo); + if (cand.compareTo(result) > 0) { + result = cand; + } + } + return result; + } + + @Test + public void simpleMin() { + assertEquals(2, min(2, 10, 1)); + assertEquals(2, min(2, 10, 2)); + assertEquals(2, min(2, 10, 3)); + assertEquals(2, min(2, 10, 4)); + assertEquals(0, min(2, 10, 5)); + } + + @Test + public void simpleMax() { + assertEquals(2, max(2, 10, 1)); + assertEquals(4, max(2, 10, 2)); + assertEquals(6, max(2, 10, 3)); + assertEquals(8, max(2, 10, 4)); + assertEquals(8, max(2, 10, 5)); + } + + @Test + public void minAgain() { + assertEquals(7, min(7, 10, 1)); + assertEquals(4, min(7, 10, 2)); + assertEquals(1, min(7, 10, 3)); + assertEquals(1, min(7, 10, 4)); + assertEquals(1, min(7, 10, 5)); + assertEquals(1, min(7, 10, 6)); + assertEquals(1, min(7, 10, 7)); + assertEquals(1, min(7, 10, 8)); + assertEquals(1, min(7, 10, 9)); + assertEquals(0, min(7, 10, 10)); + } + + @Test + public void maxAgain() { + assertEquals(7, max(7, 10, 1)); + assertEquals(7, max(7, 10, 2)); + assertEquals(7, max(7, 10, 3)); + assertEquals(8, max(7, 10, 4)); + assertEquals(8, max(7, 10, 5)); + assertEquals(8, max(7, 10, 6)); + assertEquals(9, max(7, 10, 7)); + assertEquals(9, max(7, 10, 8)); + assertEquals(9, max(7, 10, 9)); + assertEquals(9, max(7, 10, 10)); + } + + @Test + public void minAgain2() { + assertEquals(7, min(7, 30, 1)); + assertEquals(7, min(7, 30, 2)); + assertEquals(7, min(7, 30, 3)); + assertEquals(7, min(7, 30, 4)); + assertEquals(5, min(7, 30, 5)); + assertEquals(5, min(7, 30, 6)); + assertEquals(5, min(7, 30, 7)); + assertEquals(5, min(7, 30, 8)); + assertEquals(3, min(7, 30, 9)); + assertEquals(3, min(7, 30, 10)); + } + + @Test + public void compareMin() { + Random rand = new Random(1234L); + for (int i = 0; i < 10000; i++) { + BigInteger a = BigInteger.valueOf(rand.nextInt(500) + 2); + BigInteger b = BigInteger.valueOf(rand.nextInt(500) + 2); + BigInteger max = BigInteger.valueOf(rand.nextInt(100) + 1); +// System.out.println(a + " " + b + " " + max + " " + a.mod(b)); + BigInteger minFast = EuclidMinMax.min(a, b, max); + BigInteger minSlow = minSlow(a, b, max); + assertEquals(minSlow, minFast); + } + } + + @Test + public void compareMax() { + Random rand = new Random(1234L); + for (int i = 0; i < 10000; i++) { + BigInteger a = BigInteger.valueOf(rand.nextInt(500) + 2); + BigInteger b = BigInteger.valueOf(rand.nextInt(500) + 2); + BigInteger max = BigInteger.valueOf(rand.nextInt(100) + 1); +// System.out.println(a + " " + b + " " + max); + BigInteger maxFast = EuclidMinMax.max(a, b, max); + BigInteger maxSlow = maxSlow(a, b, max); + assertEquals(maxSlow, maxFast); + } + } +} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/FloatToStringTest.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/FloatToStringTest.java new file mode 100644 index 0000000000..5c511b9f1e --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/io/ryu/FloatToStringTest.java @@ -0,0 +1,125 @@ +// Copyright 2018 Ulf Adams +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.fasterxml.jackson.core.io.ryu; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public abstract class FloatToStringTest { + abstract String f(float f, RoundingMode roundingMode); + + private void assertF2sEquals(String expected, float f) { + assertEquals(expected, f(f, RoundingMode.ROUND_EVEN)); + assertEquals(expected, f(f, RoundingMode.CONSERVATIVE)); + } + + private void assertF2sEquals(String expectedRoundEven, String expectedConservative, float f) { + assertEquals(expectedRoundEven, f(f, RoundingMode.ROUND_EVEN)); + assertEquals(expectedConservative, f(f, RoundingMode.CONSERVATIVE)); + } + + @Test + public void simpleCases() { + assertF2sEquals("0.0", 0); + assertF2sEquals("-0.0", Float.intBitsToFloat(0x80000000)); + assertF2sEquals("1.0", 1.0f); + assertF2sEquals("-1.0", -1f); + assertF2sEquals("NaN", Float.NaN); + assertF2sEquals("Infinity", Float.POSITIVE_INFINITY); + assertF2sEquals("-Infinity", Float.NEGATIVE_INFINITY); + } + + @Test + public void switchToSubnormal() { + assertF2sEquals("1.1754944E-38", Float.intBitsToFloat(0x00800000)); + } + + /** + * Floating point values in the range 1.0E-3 <= x < 1.0E7 have to be printed + * without exponent. This test checks the values at those boundaries. + */ + @Test + public void boundaryConditions() { + // x = 1.0E7 + assertF2sEquals("1.0E7", 1.0E7f); + // x < 1.0E7 + assertF2sEquals("9999999.0", 9999999.0f); + // x = 1.0E-3 + assertF2sEquals("0.001", 0.001f); + // x < 1.0E-3 + assertF2sEquals("9.999999E-4", 0.0009999999f); + } + + @Test + public void minAndMax() { + assertF2sEquals("3.4028235E38", Float.intBitsToFloat(0x7f7fffff)); + assertF2sEquals("1.4E-45", Float.intBitsToFloat(0x00000001)); + } + + @Test + public void roundingModeEven() { + assertF2sEquals("3.355445E7", "3.3554448E7", 3.3554448E7f); + assertF2sEquals("9.0E9", "8.999999E9", 8.999999E9f); + assertF2sEquals("3.436672E10", "3.4366718E10", 3.4366717E10f); + } + + @Test + public void roundingEvenIfTied() { + assertF2sEquals("0.33007812", 0.33007812f); + } + + @Test + public void looksLikePow5() { + // These are all floating point numbers where the mantissa is a power of 5, + // and the exponent is in the range such that q = 10. + assertF2sEquals("6.7108864E17", Float.intBitsToFloat(0x5D1502F9)); + assertF2sEquals("1.3421773E18", Float.intBitsToFloat(0x5D9502F9)); + assertF2sEquals("2.6843546E18", Float.intBitsToFloat(0x5E1502F9)); + } + + @Test + public void regressionTest() { + assertF2sEquals("4.7223665E21", 4.7223665E21f); + assertF2sEquals("8388608.0", 8388608.0f); + assertF2sEquals("1.6777216E7", 1.6777216E7f); + assertF2sEquals("3.3554436E7", 3.3554436E7f); + assertF2sEquals("6.7131496E7", 6.7131496E7f); + assertF2sEquals("1.9310392E-38", 1.9310392E-38f); + assertF2sEquals("-2.47E-43", -2.47E-43f); + assertF2sEquals("1.993244E-38", 1.993244E-38f); + assertF2sEquals("4103.9004", 4103.9003f); + assertF2sEquals("5.3399997E9", 5.3399997E9f); + assertF2sEquals("6.0898E-39", 6.0898E-39f); + assertF2sEquals("0.0010310042", 0.0010310042f); + assertF2sEquals("2.882326E17", 2.8823261E17f); + assertF2sEquals("7.038531E-26", 7.038531E-26f); + assertF2sEquals("9.223404E17", 9.2234038E17f); + assertF2sEquals("6.710887E7", 6.7108872E7f); + assertF2sEquals("1.0E-44", 1.0E-44f); + assertF2sEquals("2.816025E14", 2.816025E14f); + assertF2sEquals("9.223372E18", 9.223372E18f); + assertF2sEquals("1.5846086E29", 1.5846085E29f); + assertF2sEquals("1.1811161E19", 1.1811161E19f); + assertF2sEquals("5.368709E18", 5.368709E18f); + assertF2sEquals("4.6143166E18", 4.6143165E18f); + assertF2sEquals("0.007812537", 0.007812537f); + assertF2sEquals("1.4E-45", 1.4E-45f); + assertF2sEquals("1.18697725E20", 1.18697724E20f); + assertF2sEquals("1.00014165E-36", 1.00014165E-36f); + assertF2sEquals("200.0", 200f); + assertF2sEquals("3.3554432E7", 3.3554432E7f); + } +} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/JafferDoubleTest.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/JafferDoubleTest.java new file mode 100644 index 0000000000..c6e8b9471a --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/io/ryu/JafferDoubleTest.java @@ -0,0 +1,72 @@ +// Copyright 2018 Ulf Adams +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.fasterxml.jackson.core.io.ryu; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * A few tests to check that {@link DoubleUtils} isn't completely broken. + * + *

Note that the implementation does not follow Java {@link Double#toString} semantics, so we + * can't run the existing double-to-string tests against it. + */ +@RunWith(JUnit4.class) +public class JafferDoubleTest { + private String f(double f) { + return DoubleUtils.toString(f); + } + + @Test + public void simpleCases() { + assertEquals("0", f(0)); + assertEquals("0", f(Double.longBitsToDouble(0x8000000000000000L))); + assertEquals("1", f(1.0d)); + assertEquals("-1", f(-1.0d)); + assertEquals("NaN", f(Double.NaN)); + assertEquals("Infinity", f(Double.POSITIVE_INFINITY)); + assertEquals("-Infinity", f(Double.NEGATIVE_INFINITY)); + } + + @Test + public void switchToSubnormal() { + assertEquals("2.2250738585072014e-308", f(Double.longBitsToDouble(0x0010000000000000L))); + } + + @Test + public void boundaryConditions() { + // x = 1.0E7 + assertEquals("10000000", f(1.0E7d)); + // x < 1.0E7 + assertEquals("9999999", f(9999999d)); + // x = 1.0E-3 + assertEquals("0.001", f(0.001d)); + // x < 1.0E-3 + assertEquals("0.0009", f(0.0009d)); + } + + @Test + public void regressionTest() { + assertEquals("4.940656e-318", f(4.940656E-318d)); + assertEquals("1.18575755e-316", f(1.18575755E-316d)); + assertEquals("2.989102097996e-312", f(2.989102097996E-312d)); + assertEquals("9.0608011534336e15", f(9.0608011534336E15d)); + assertEquals("4.708356024711512e18", f(4.708356024711512E18)); + assertEquals("9.409340012568248e18", f(9.409340012568248E18)); + } +} \ No newline at end of file diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/RyuDoubleTest.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/RyuDoubleTest.java new file mode 100644 index 0000000000..02a4deea62 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/io/ryu/RyuDoubleTest.java @@ -0,0 +1,26 @@ +// Copyright 2018 Ulf Adams +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.fasterxml.jackson.core.io.ryu; + +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class RyuDoubleTest extends DoubleToStringTest { + @Override + String f(double f, RoundingMode roundingMode) { + return RyuDouble.doubleToString(f, roundingMode); + } +} \ No newline at end of file diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/RyuFloatTest.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/RyuFloatTest.java new file mode 100644 index 0000000000..8259ad07f7 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/io/ryu/RyuFloatTest.java @@ -0,0 +1,26 @@ +// Copyright 2018 Ulf Adams +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.fasterxml.jackson.core.io.ryu; + +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class RyuFloatTest extends FloatToStringTest { + @Override + String f(float f, RoundingMode roundingMode) { + return RyuFloat.floatToString(f, roundingMode); + } +} \ No newline at end of file diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/SlowDoubleTest.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/SlowDoubleTest.java new file mode 100644 index 0000000000..1940f75ba8 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/io/ryu/SlowDoubleTest.java @@ -0,0 +1,28 @@ +// Copyright 2018 Ulf Adams +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.fasterxml.jackson.core.io.ryu; + +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import com.fasterxml.jackson.core.io.ryu.analysis.SlowConversion; + +@RunWith(JUnit4.class) +public class SlowDoubleTest extends DoubleToStringTest { + @Override + String f(double f, RoundingMode roundingMode) { + return SlowConversion.doubleToString(f, roundingMode); + } +} \ No newline at end of file diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/SlowFloatTest.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/SlowFloatTest.java new file mode 100644 index 0000000000..e167f2af54 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/io/ryu/SlowFloatTest.java @@ -0,0 +1,24 @@ +// Copyright 2018 Ulf Adams +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.fasterxml.jackson.core.io.ryu; + +import com.fasterxml.jackson.core.io.ryu.analysis.SlowConversion; + +public class SlowFloatTest extends FloatToStringTest { + @Override + String f(float f, RoundingMode roundingMode) { + return SlowConversion.floatToString(f, roundingMode); + } +} \ No newline at end of file diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/ComputeRequiredBitSizes.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/ComputeRequiredBitSizes.java new file mode 100644 index 0000000000..2526eae941 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/ComputeRequiredBitSizes.java @@ -0,0 +1,124 @@ +// Copyright 2018 Ulf Adams +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.fasterxml.jackson.core.io.ryu.analysis; + +import java.math.BigInteger; +import java.util.EnumSet; + +/** + * Computes appropriate values for B_0 and B_1 for a given floating point type. + */ +public final class ComputeRequiredBitSizes { + private static final BigInteger FIVE = BigInteger.valueOf(5); + private static final BigInteger TWO = BigInteger.valueOf(2); + + private static final long LOG10_5_DENOMINATOR = 10000000L; + private static final long LOG10_5_NUMERATOR = (long) (LOG10_5_DENOMINATOR * Math.log10(5)); + + private static final long LOG10_2_DENOMINATOR = 10000000L; + private static final long LOG10_2_NUMERATOR = (long) (LOG10_2_DENOMINATOR * Math.log10(2)); + + public static void main(String[] args) { + boolean verbose = false; + EnumSet formats = EnumSet.noneOf(FloatingPointFormat.class); + formats.add(FloatingPointFormat.FLOAT16); + formats.add(FloatingPointFormat.FLOAT32); + formats.add(FloatingPointFormat.FLOAT64); + for (String s : args) { + if ("-80".equals(s)) { + formats.add(FloatingPointFormat.FLOAT80); + } else if ("-128".equals(s)) { + formats.add(FloatingPointFormat.FLOAT128); + } else if ("-256".equals(s)) { + formats.add(FloatingPointFormat.FLOAT256); + } else if ("-v".equals(s)) { + verbose = true; + } + } + + for (FloatingPointFormat format : formats) { + compute(format, verbose); + } + } + + private static void compute(FloatingPointFormat format, boolean verbose) { + int mbits = format.mantissaBits() + 3; + + int minE2 = 0; + int maxE2 = -(1 - format.bias() - format.mantissaBits() - 2); + int b1 = 0; + for (int e2 = minE2; e2 < maxE2 + 1; e2++) { + int q = Math.max(0, (int) (e2 * LOG10_5_NUMERATOR / LOG10_5_DENOMINATOR) - 1); + int i = e2 - q; + BigInteger pow5 = FIVE.pow(i); + BigInteger pow2 = BigInteger.ONE.shiftLeft(q); + + // max(w) = 4 * ((1 << format.mantissaBits()) * 2 - 1) + 2 + // = (1 << (format.mantissaBits() + 3)) - 2 + BigInteger mxM = BigInteger.ONE.shiftLeft(mbits).subtract(TWO); + BigInteger min = EuclidMinMax.min(pow5, pow2, mxM.subtract(BigInteger.ONE)); +// BigInteger min2 = minSlow(pow5, pow2, mxM); +// if (!min.equals(min2)) { +// new IllegalStateException(min + " " + min2).printStackTrace(System.out); +// } + + int bits = min.divide(mxM).bitLength(); + int reqn = pow5.bitLength() - bits; + b1 = Math.max(b1, reqn); + if (verbose) { + System.out.printf("%s,%s,%s,%s,%s,%s,%.2f%%\n", + Integer.valueOf(e2), Integer.valueOf(q), Integer.valueOf(i), Integer.valueOf(bits), + Integer.valueOf(reqn), Integer.valueOf(b1), Double.valueOf((100.0 * e2) / maxE2)); + } + } + if (verbose) { + System.out.println("B_1 = " + b1); + } + + minE2 = 0; + maxE2 = ((1 << format.exponentBits()) - 2) - format.bias() - format.mantissaBits() - 2; + int b0 = 0; + for (int e2 = minE2; e2 < maxE2 + 1; e2++) { + int q = Math.max(0, (int) (e2 * LOG10_2_NUMERATOR / LOG10_2_DENOMINATOR) - 1); + BigInteger pow5 = FIVE.pow(q); + BigInteger pow2 = BigInteger.ONE.shiftLeft(e2 - q); + + // max(w) = 4 * ((1 << format.mantissaBits()) * 2 - 1) + 2 + // = (1 << (format.mantissaBits() + 3)) - 2 + BigInteger mxM = BigInteger.ONE.shiftLeft(mbits).subtract(TWO); + BigInteger max = EuclidMinMax.max(pow2, pow5, mxM.subtract(BigInteger.ONE)); +// BigInteger max2 = maxSlow(pow2, pow5, mxM); +// if (!max.equals(max2)) { +// new IllegalStateException(max + " " + max2).printStackTrace(System.out); +// } + + BigInteger num = mxM.multiply(pow5).multiply(pow2); + BigInteger den = pow5.subtract(max); + int bits = num.divide(den).bitLength(); + int reqn = bits - pow5.bitLength(); + b0 = Math.max(b0, reqn); + if (verbose) { + System.out.printf("%s,%s,%s,%s,%s,%s,%.2f%%\n", + Integer.valueOf(e2), Integer.valueOf(q), Integer.valueOf(e2 - q), Integer.valueOf(bits), + Integer.valueOf(reqn), Integer.valueOf(b0), Double.valueOf((100.0 * e2) / maxE2)); + } + } + if (verbose) { + System.out.println("B_0 = " + b0); + System.out.println(); + } + System.out.printf("%s,%d,%d\n", format, Integer.valueOf(b0), Integer.valueOf(b1)); + } +} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/ComputeTableSizes.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/ComputeTableSizes.java new file mode 100644 index 0000000000..cc311ce2dc --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/ComputeTableSizes.java @@ -0,0 +1,65 @@ +// Copyright 2018 Ulf Adams +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.fasterxml.jackson.core.io.ryu.analysis; + +/** + * Computes and outputs the lookup table sizes. + */ +public class ComputeTableSizes { + private static final long LOG10_2_DENOMINATOR = 10000000L; + private static final long LOG10_2_NUMERATOR = (long) (LOG10_2_DENOMINATOR * Math.log10(2)); + + private static final long LOG10_5_DENOMINATOR = 10000000L; + private static final long LOG10_5_NUMERATOR = (long) (LOG10_5_DENOMINATOR * Math.log10(5)); + + public static void main(String[] args) { + boolean verbose = false; + for (String s : args) { + if ("-v".equals(s)) { + verbose = true; + } + } + + if (verbose) { + System.out.println("These are the lookup table sizes required for each floating point type:"); + } else { + System.out.println("floating_point_type,table_size_1,table_size_2,total_size"); + } + for (FloatingPointFormat format : FloatingPointFormat.values()) { + int minE2 = 1 - format.bias() - format.mantissaBits() - 2; + int maxE2 = ((1 << format.exponentBits()) - 2) - format.bias() - format.mantissaBits() - 2; + long minQ, maxQ; + if (format == FloatingPointFormat.FLOAT32) { + // Float32 is special; using -1 as below is problematic as it would require 33 bit + // integers. Instead, we compute the additionally required digit separately. + minQ = (-minE2 * LOG10_5_NUMERATOR) / LOG10_5_DENOMINATOR; + maxQ = (maxE2 * LOG10_2_NUMERATOR) / LOG10_2_DENOMINATOR; + } else { + minQ = Math.max(0, (-minE2 * LOG10_5_NUMERATOR) / LOG10_5_DENOMINATOR - 1); + maxQ = Math.max(0, (maxE2 * LOG10_2_NUMERATOR) / LOG10_2_DENOMINATOR - 1); + } + long negTableSize = (-minE2 - minQ) + 1; + long posTableSize = maxQ + 1; + if (verbose) { + System.out.println(format); + System.out.println(" " + minE2 + " <= e_2 <= " + maxE2); + System.out.println(" " + (-minQ) + " <= q <= " + maxQ); + System.out.println(" Total table size = " + negTableSize + " + " + posTableSize + " = " + (negTableSize + posTableSize)); + } else { + System.out.println(format + "," + negTableSize + "," + posTableSize + "," + (negTableSize + posTableSize)); + } + } + } +} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/EuclidMinMax.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/EuclidMinMax.java new file mode 100644 index 0000000000..6092b3fdfe --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/EuclidMinMax.java @@ -0,0 +1,148 @@ +// Copyright 2018 Ulf Adams +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.fasterxml.jackson.core.io.ryu.analysis; + +import java.math.BigInteger; + +/** + * Computes the modular min and max using a modified version of Euclid's algorithm. + */ +public final class EuclidMinMax { + private static final boolean DEBUG = false; + + public static BigInteger min(BigInteger multiplier, BigInteger modulo, BigInteger maximum) { + if (DEBUG) { + System.out.println(); + } + BigInteger c = multiplier.gcd(modulo); + BigInteger b = modulo.divide(c); + BigInteger a = multiplier.divide(c).mod(b); + if (maximum.compareTo(b) >= 0) { + return BigInteger.ZERO; + } + if (DEBUG) { + System.out.println("C=" + c); + } + BigInteger s = BigInteger.ONE; + BigInteger t = BigInteger.ZERO; + BigInteger u = BigInteger.ZERO; + BigInteger v = BigInteger.ONE; + while (true) { + if (DEBUG) { + System.out.printf("A=%s\nB=%s\n", a, b); + } + while (b.compareTo(a) >= 0) { + b = b.subtract(a); + u = u.subtract(s); + v = v.subtract(t); + if (DEBUG) { + System.out.printf("U=%s (A=%s)\n", u, a); + } + if (u.negate().compareTo(maximum) >= 0) { + return a.multiply(c); + } + } + if (b.equals(BigInteger.ZERO)) { + return BigInteger.ZERO; + } + while (a.compareTo(b) >= 0) { + BigInteger oldA = a; + a = a.subtract(b); + s = s.subtract(u); + t = t.subtract(v); + if (DEBUG) { + System.out.printf("S=%s (A=%s)\n", s, a); + } + int cmp = s.compareTo(maximum); + if (cmp >= 0) { + if (cmp > 0) { + return oldA.multiply(c); + } else { + return a.multiply(c); + } + } + } + if (a.equals(BigInteger.ZERO)) { + return BigInteger.ZERO; + } + } + } + + public static BigInteger max(BigInteger multiplier, BigInteger modulo, BigInteger maximum) { + if (DEBUG) { + System.out.println(); + } + BigInteger c = multiplier.gcd(modulo); + BigInteger b = modulo.divide(c); + BigInteger a = multiplier.divide(c).mod(b); + if (DEBUG) { + System.out.printf("A=%s B=%s C=%s MAX=%s\n", a, b, c, maximum); + } + if (maximum.compareTo(b) >= 0) { + return modulo.subtract(c); + } + BigInteger s = BigInteger.ONE; + BigInteger t = BigInteger.ZERO; + BigInteger u = BigInteger.ZERO; + BigInteger v = BigInteger.ONE; + while (true) { + if (DEBUG) { + System.out.printf("A=%s\nB=%s\n", a, b); + } + while (b.compareTo(a) >= 0) { + BigInteger q = b.divide(a); + q = q.min(maximum.subtract(u.negate()).divide(s).subtract(BigInteger.ONE)); + q = q.max(BigInteger.ONE); + BigInteger oldB = b; + b = b.subtract(a.multiply(q)); + u = u.subtract(s.multiply(q)); + v = v.subtract(t.multiply(q)); + if (DEBUG) { + System.out.printf("U=%s (B=%s)\n", u, b); + } + int cmp = u.negate().compareTo(maximum); + if (cmp >= 0) { + if (cmp > 0) { + return modulo.subtract(oldB.multiply(c)); + } else { + return modulo.subtract(b.multiply(c)); + } + } + } + if (b.equals(BigInteger.ZERO)) { + return modulo.subtract(c); + } + while (a.compareTo(b) >= 0) { + BigInteger q = BigInteger.ONE; //a.divide(b); + if (!u.equals(BigInteger.ZERO)) { + q = q.min(maximum.subtract(s).divide(u.negate().add(BigInteger.ONE))); + } + q = q.max(BigInteger.ONE); + a = a.subtract(b.multiply(q)); + s = s.subtract(u.multiply(q)); + t = t.subtract(v.multiply(q)); + if (DEBUG) { + System.out.printf("S=%s (B=%s)\n", s, b); + } + if (s.compareTo(maximum) >= 0) { + return modulo.subtract(b.multiply(c)); + } + } + if (a.equals(BigInteger.ZERO)) { + return modulo.subtract(c); + } + } + } +} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/ExhaustiveFloatComparison.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/ExhaustiveFloatComparison.java new file mode 100644 index 0000000000..e73b041519 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/ExhaustiveFloatComparison.java @@ -0,0 +1,55 @@ +// Copyright 2018 Ulf Adams +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.fasterxml.jackson.core.io.ryu.analysis; + +import com.fasterxml.jackson.core.io.ryu.RyuFloat; + +/** + * Exhaustively tests the fast implementation of Ryu against the slow one. + */ +public class ExhaustiveFloatComparison { + public static void main(String[] args) { + System.out.println("This checks every possible 32-bit floating point value, which takes ~60 hours."); + System.out.println("We report progress approximately every ~10 seconds."); + long stride = 10007L; // 10007 is prime + // We go through the set of all possible values in stride-sized steps, and go through all + // possible [0,stride) offsets. This gives us good coverage across the whole range of floating + // point numbers even in the first loop, which quickly finds systematic errors. We use a prime + // to avoid bit patterns in the floating point number. + // + // We also don't check any negative numbers; we assume that the code behaves identically. + for (long base = 0; base < stride; base++) { + for (long l = base; l <= 0x7fffffffL; l += stride) { + float f = Float.intBitsToFloat((int) l); + String expected = SlowConversion.floatToString(f); + String actual = RyuFloat.floatToString(f); + if (!expected.equals(actual)) { + System.out.println(String.format("expected %s, but was %s", expected, actual)); + throw new RuntimeException(String.format("expected %s, but was %s", expected, actual)); + } + + // Also check round-trip safety. + long g = Float.floatToRawIntBits(Float.parseFloat(actual)) & 0xffffffffL; + if (!Float.isNaN(f) && g != l) { + String message = String.format("expected %d, but was %d", Long.valueOf(l), Long.valueOf(g)); + System.out.println(message); + throw new RuntimeException(message); + } + } + double frac = (base + 1) / (double) stride; + System.out.printf("(%6.2f%%)\n", Double.valueOf(100 * frac)); + } + } +} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/ExtensiveDoubleComparison.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/ExtensiveDoubleComparison.java new file mode 100644 index 0000000000..6805ac4993 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/ExtensiveDoubleComparison.java @@ -0,0 +1,44 @@ +// Copyright 2018 Ulf Adams +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.fasterxml.jackson.core.io.ryu.analysis; + +import com.fasterxml.jackson.core.io.ryu.RyuDouble; + +/** + * Extensively tests the fast implementation of Ryu against the slow one. + */ +public class ExtensiveDoubleComparison { + public static void main(String[] args) { + checkDoubleFastAgainstSlow(); + } + + private static void checkDoubleFastAgainstSlow() { + System.out.println("This checks every possible 64-bit floating point value - interrupt when you are satisfied."); + long stride = 1000000000000000L; + for (long base = 0; base < stride; base++) { + for (long l = base; l <= 0x7fffffffffffffffL - stride + 1; l += stride) { + double d = Double.longBitsToDouble(l); + String expected = RyuDouble.doubleToString(d); + String actual = SlowConversion.doubleToString(d); + if (!expected.equals(actual)) { + System.out.println(String.format("expected %s, but was %s", expected, actual)); + throw new RuntimeException(String.format("expected %s, but was %s", expected, actual)); + } + } + double frac = (base + 1) / (double) stride; + System.out.printf("(%16.12f%%)\n", Double.valueOf(100 * frac)); + } + } +} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/FloatingPointFormat.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/FloatingPointFormat.java new file mode 100644 index 0000000000..96cbf7ad8a --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/FloatingPointFormat.java @@ -0,0 +1,50 @@ +// Copyright 2018 Ulf Adams +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.fasterxml.jackson.core.io.ryu.analysis; + +enum FloatingPointFormat { + FLOAT16(16, 5, 10), + FLOAT32(32, 8, 23), + FLOAT64(64, 11, 52), + FLOAT80(80, 15, 63), + FLOAT128(128, 15, 112), + FLOAT256(256, 19, 236); + + private final int totalBits; + private final int exponentBits; + private final int mantissaBits; + + private FloatingPointFormat(int totalBits, int exponentBits, int mantissaBits) { + this.totalBits = totalBits; + this.exponentBits = exponentBits; + this.mantissaBits = mantissaBits; + } + + public int totalBits() { + return totalBits; + } + + public int exponentBits() { + return exponentBits; + } + + public int mantissaBits() { + return mantissaBits; + } + + public int bias() { + return (1 << (exponentBits - 1)) - 1; + } +} \ No newline at end of file diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/PrintDoubleLookupTable.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/PrintDoubleLookupTable.java new file mode 100644 index 0000000000..59a54429e7 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/PrintDoubleLookupTable.java @@ -0,0 +1,180 @@ +// Copyright 2018 Ulf Adams +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.fasterxml.jackson.core.io.ryu.analysis; + +import java.math.BigInteger; + +/** + * Prints a lookup table for the C version of Ryu. + */ +public final class PrintDoubleLookupTable { + private static final int POS_TABLE_SIZE = 326; + // The C version has two code paths, one of which requires an additional entry here. + private static final int NEG_TABLE_SIZE = 342; + + // We intentionally choose these to be larger than or equal to the float equivalent + 64. + private static final int POW5_BITCOUNT = 125; // max 127 + private static final int POW5_INV_BITCOUNT = 125; // max 127 + + private static final boolean PRINT_LARGE_TABLES = true; + + private static final BigInteger MASK64 = BigInteger.valueOf(1).shiftLeft(64).subtract(BigInteger.ONE); + + public static void main(String[] args) { + BigInteger[] largeInvTable = new BigInteger[NEG_TABLE_SIZE]; + for (int i = 0; i < largeInvTable.length; i++) { + largeInvTable[i] = invMultiplier(i); + } + + int mulTableSize = 26; + BigInteger[] smallInvTable = new BigInteger[(NEG_TABLE_SIZE + mulTableSize - 1) / mulTableSize + 1]; + for (int i = 0; i < smallInvTable.length; i++) { + smallInvTable[i] = invMultiplier(mulTableSize * i); + } + + int[] invErrorTable = new int[NEG_TABLE_SIZE]; + for (int i = 0; i < NEG_TABLE_SIZE; i++) { + int base = (i + mulTableSize - 1) / mulTableSize; + int base2 = base * mulTableSize; + int offset = base2 - i; + BigInteger mul = BigInteger.valueOf(5).pow(offset); + BigInteger result = smallInvTable[base].subtract(BigInteger.ONE).multiply(mul).shiftRight(pow5bits(base2) - pow5bits(i)).add(BigInteger.ONE); + BigInteger error = invMultiplier(i).subtract(result); + if ((error.signum() < 0) || (error.compareTo(BigInteger.valueOf(2)) > 0)) { + throw new IllegalStateException("That went wrong!"); + } + invErrorTable[i] = error.intValueExact(); + } + + BigInteger[] largeTable = new BigInteger[POS_TABLE_SIZE]; + for (int i = 0; i < largeTable.length; i++) { + largeTable[i] = multiplier(i); + } + BigInteger[] smallTable = new BigInteger[(POS_TABLE_SIZE + mulTableSize - 1) / mulTableSize]; + for (int i = 0; i < smallTable.length; i++) { + smallTable[i] = multiplier(mulTableSize * i); + } + + int[] errorTable = new int[POS_TABLE_SIZE]; + for (int i = 0; i < POS_TABLE_SIZE; i++) { + int base = i / mulTableSize; + int base2 = base * mulTableSize; + int offset = i - base2; + BigInteger mul = BigInteger.valueOf(5).pow(offset); + BigInteger result = smallTable[base].multiply(mul).shiftRight(pow5bits(i) - pow5bits(base2)); + BigInteger error = multiplier(i).subtract(result); + if ((error.signum() < 0) || (error.compareTo(BigInteger.valueOf(2)) > 0)) { + throw new IllegalStateException("That went wrong: " + error); + } + errorTable[i] = error.intValueExact(); + } +// System.out.println(Arrays.toString(invErrorTable)); +// System.out.println(Arrays.toString(errorTable)); + + System.out.println("#define DOUBLE_POW5_INV_BITCOUNT " + POW5_INV_BITCOUNT); + System.out.println("#define DOUBLE_POW5_BITCOUNT " + POW5_BITCOUNT); + System.out.println(); + if (PRINT_LARGE_TABLES) { + printTable("DOUBLE_POW5_INV_SPLIT", largeInvTable, 2); + System.out.println(); + printTable("DOUBLE_POW5_SPLIT", largeTable, 2); + } else { + printTable("DOUBLE_POW5_INV_SPLIT2", smallInvTable, 1); + printOffsets("POW5_INV_OFFSETS", invErrorTable, 6); + System.out.println(); + printTable("DOUBLE_POW5_SPLIT2", smallTable, 1); + printOffsets("POW5_OFFSETS", errorTable, 6); + } + } + + private static BigInteger invMultiplier(int i) { + // 5^i + BigInteger pow = BigInteger.valueOf(5).pow(i); + // length of 5^i in binary = ceil(log_2(5^i)) + int pow5len = pow.bitLength(); + // We want floor(log_2(5^i)) here, which is pow5len - 1 (no power of 5 is a power of 2). + int j = pow5len - 1 + POW5_INV_BITCOUNT; + // [2^j / 5^i] + 1 = [2^(floor(log_2(5^i)) + POW5_INV_BITCOUNT) / 5^i] + 1 + // By construction, this will have approximately POW5_INV_BITCOUNT + 1 bits. + BigInteger inv = BigInteger.ONE.shiftLeft(j).divide(pow).add(BigInteger.ONE); + if (inv.bitLength() > POW5_INV_BITCOUNT + 1) { + throw new IllegalStateException("Result is longer than expected: " + inv.bitLength() + " > " + (POW5_INV_BITCOUNT + 1)); + } + return inv; + } + + private static BigInteger multiplier(int i) { + // 5^i + BigInteger pow = BigInteger.valueOf(5).pow(i); + int pow5len = pow.bitLength(); + // [5^i / 2^j] = [5^i / 2^(ceil(log_2(5^i)) - POW5_BITCOUNT)] + // By construction, this will have exactly POW5_BITCOUNT bits. Note that this can shift left if j is negative! + BigInteger pow5DivPow2 = pow.shiftRight(pow5len - POW5_BITCOUNT); + if (pow5DivPow2.bitLength() != POW5_BITCOUNT) { + throw new IllegalStateException("Unexpected result length: " + pow5DivPow2.bitLength() + " != " + POW5_BITCOUNT); + } + return pow5DivPow2; + } + + private static int pow5bits(int e) { + return ((e * 1217359) >> 19) + 1; + } + + private static void printTable(String name, BigInteger[] table, int entriesPerLine) { + System.out.println("static const uint64_t " + name + "[" + table.length + "][2] = {"); + for (int i = 0; i < table.length; i++) { + BigInteger pow5High = table[i].shiftRight(64); + BigInteger pow5Low = table[i].and(MASK64); + if (i % entriesPerLine == 0) { + System.out.print(" "); + } else { + System.out.print(" "); + } + System.out.printf("{ %20su, %18su }", pow5Low, pow5High); + if (i != table.length - 1) { + System.out.print(","); + } + if (i % entriesPerLine == entriesPerLine - 1) { + System.out.println(); + } + } + System.out.println("};"); + } + + private static void printOffsets(String name, int[] table, int entriesPerLine) { + int length = (table.length + 15) / 16; + System.out.println("static const uint32_t " + name + "[" + length + "] = {"); + for (int i = 0; i < length; i++) { + int value = 0; + for (int j = 0; j < 16; j++) { + int offset = i * 16 + j < table.length ? table[i * 16 + j] : 0; + value |= offset << (j << 1); + } + if (i % entriesPerLine == 0) { + System.out.print(" "); + } else { + System.out.print(" "); + } + System.out.printf("0x%08x", Integer.valueOf(value)); + if (i != length - 1) { + System.out.print(","); + } + if ((i % entriesPerLine == entriesPerLine - 1) || (i == length - 1)) { + System.out.println(); + } + } + System.out.println("};"); + } +} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/PrintFloatLookupTable.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/PrintFloatLookupTable.java new file mode 100644 index 0000000000..c2188a969e --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/PrintFloatLookupTable.java @@ -0,0 +1,69 @@ +// Copyright 2018 Ulf Adams +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.fasterxml.jackson.core.io.ryu.analysis; + +import java.math.BigInteger; + +/** + * Prints a lookup table for the C version of Ryu. + */ +public final class PrintFloatLookupTable { + private static final int POS_TABLE_SIZE = 47; + private static final int INV_TABLE_SIZE = 55; + + private static final int POW5_BITCOUNT = 61; // max 63 + private static final int POW5_INV_BITCOUNT = 59; // max 63 + + public static void main(String[] args) { + System.out.println("#define POW5_INV_BITCOUNT " + POW5_INV_BITCOUNT); + System.out.println("static uint64_t FLOAT_POW5_INV_SPLIT[" + INV_TABLE_SIZE + "] = {"); + for (int i = 0; i < INV_TABLE_SIZE; i++) { + BigInteger pow = BigInteger.valueOf(5).pow(i); + int pow5len = pow.bitLength(); + int j = pow5len - 1 + POW5_INV_BITCOUNT; + BigInteger pow5inv = BigInteger.ONE.shiftLeft(j).divide(pow).add(BigInteger.ONE); + + long v = pow5inv.longValueExact(); + System.out.printf(" %19su", Long.toUnsignedString(v)); + if (i < INV_TABLE_SIZE - 1) { + System.out.print(","); + } + if (i % 4 == 3) { + System.out.println(); + } + } + System.out.println(); + System.out.println("};"); + + System.out.println("#define POW5_BITCOUNT " + POW5_BITCOUNT); + System.out.println("static uint64_t FLOAT_POW5_SPLIT[" + POS_TABLE_SIZE + "] = {"); + for (int i = 0; i < POS_TABLE_SIZE; i++) { + BigInteger pow = BigInteger.valueOf(5).pow(i); + int pow5len = pow.bitLength(); + BigInteger pow5 = pow.shiftRight(pow5len - POW5_BITCOUNT); + + long v = pow5.longValueExact(); + System.out.printf(" %19su", Long.toUnsignedString(v)); + if (i < POS_TABLE_SIZE - 1) { + System.out.print(","); + } + if (i % 4 == 3) { + System.out.println(); + } + } + System.out.println(); + System.out.println("};"); + } +} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/PrintGenericLookupTable.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/PrintGenericLookupTable.java new file mode 100644 index 0000000000..99a86bf041 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/PrintGenericLookupTable.java @@ -0,0 +1,242 @@ +// Copyright 2018 Ulf Adams +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.fasterxml.jackson.core.io.ryu.analysis; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.BitSet; + +/** + * Prints a lookup table for the C version of Ryu. + */ +public final class PrintGenericLookupTable { + // Bit sizes: FLOAT128,249,246 + // Table sizes: FLOAT128,4968,4897,9865 + // 5^55 < 2^128 + private static final int POS_TABLE_SIZE = 4968; + private static final int NEG_TABLE_SIZE = 4897; + + // We use the same size for simplicity. + private static final int POW5_BITCOUNT = 249; + private static final int POW5_INV_BITCOUNT = 249; + + public static void main(String[] args) { + BigInteger mask = BigInteger.valueOf(1).shiftLeft(64).subtract(BigInteger.ONE); + + int pow5TableSize = 56; // log_5(2^128) + 1 + System.out.println("#define POW5_INV_BITCOUNT " + POW5_INV_BITCOUNT); + System.out.println("#define POW5_BITCOUNT " + POW5_BITCOUNT); + System.out.println("#define POW5_TABLE_SIZE " + pow5TableSize); + System.out.println(); + System.out.println("static uint64_t GENERIC_POW5_TABLE[POW5_TABLE_SIZE][2] = {"); + for (int i = 0; i < pow5TableSize; i++) { + if (i != 0) { + System.out.println(","); + } + BigInteger pow = BigInteger.valueOf(5).pow(i); + System.out.print(" { "); + for (int j = 0; j < 2; j++) { + if (j != 0) { + System.out.print(", "); + } + System.out.printf("%20su", pow.and(mask)); + pow = pow.shiftRight(64); + } + if (!pow.equals(BigInteger.ZERO)) { + throw new IllegalStateException(); + } + System.out.print(" }"); + } + System.out.println(); + System.out.println("};"); + + int posPartialTableSize = (POS_TABLE_SIZE / pow5TableSize) + 1; + System.out.println("static uint64_t GENERIC_POW5_SPLIT[" + posPartialTableSize + "][4] = {"); + for (int i = 0; i < posPartialTableSize; i++) { + if (i != 0) { + System.out.println(","); + } + BigInteger pow = pos(pow5TableSize * i); + System.out.print(" { "); + for (int j = 0; j < 4; j++) { + if (j != 0) { + System.out.print(", "); + } + System.out.printf("%20su", pow.and(mask)); + pow = pow.shiftRight(64); + } + if (!pow.equals(BigInteger.ZERO)) { + throw new IllegalStateException(); + } + System.out.print(" }"); + } + System.out.println(); + System.out.println("};"); + + BitSet errorBits = new BitSet(); + for (int i = 0; i < POS_TABLE_SIZE; i++) { + int base = i / pow5TableSize; + int base2 = base * pow5TableSize; + int offset = i - base2; + BigInteger pow5base = pos(base2); + BigInteger pow5offset = BigInteger.valueOf(5).pow(offset); + BigInteger product = pow5base.multiply(pow5offset).shiftRight(pow5bits(i) - pow5bits(base2)); + + BigInteger exact = pos(i); + + int diff = exact.subtract(product).intValueExact(); +// System.out.print((diff != 0 ? diff : " ") + " "); +// if (i % pow5TableSize == pow5TableSize - 1) { +// System.out.println(); +// } + errorBits.set(2 * i + 0, (diff & 1) != 0); + errorBits.set(2 * i + 1, (diff & 2) != 0); + } + long[] error = Arrays.copyOf(errorBits.toLongArray(), POS_TABLE_SIZE / 32 + 1); + System.out.println("static uint64_t POW5_ERRORS[" + error.length + "] = {"); + for (int i = 0; i < error.length; i++) { + if (i % 4 == 0) { + if (i != 0) { + System.out.println(); + } + System.out.print(" "); + } + System.out.printf("0x%016xu, ", Long.valueOf(error[i])); + } + System.out.println(); + System.out.println("};"); + + int negPartialTableSize = (NEG_TABLE_SIZE + pow5TableSize - 1) / pow5TableSize + 1; + System.out.println("static uint64_t GENERIC_POW5_INV_SPLIT[" + negPartialTableSize + "][4] = {"); + for (int i = 0; i < negPartialTableSize; i++) { + if (i != 0) { + System.out.println(","); + } + BigInteger inv = neg(i * pow5TableSize); + System.out.print(" { "); + for (int j = 0; j < 4; j++) { + if (j != 0) { + System.out.print(", "); + } + System.out.printf("%20su", inv.and(mask)); + inv = inv.shiftRight(64); + } + if (!inv.equals(BigInteger.ZERO)) { + throw new IllegalStateException(); + } + System.out.print(" }"); + } + System.out.println(); + System.out.println("};"); + errorBits.clear(); + for (int i = 0; i < NEG_TABLE_SIZE; i++) { + int base = (i + pow5TableSize - 1) / pow5TableSize; + int base2 = base * pow5TableSize; + int offset = base2 - i; + BigInteger pow5base = neg(base2); + BigInteger pow5offset = BigInteger.valueOf(5).pow(offset); + BigInteger product = pow5base.multiply(pow5offset).shiftRight(pow5bits(base2) - pow5bits(i)); + + BigInteger exact = neg(i); + + int diff = exact.subtract(product).intValueExact(); +// System.out.print((diff != 0 ? diff : " ") + " "); +// if (i % pow5TableSize == pow5TableSize - 1) { +// System.out.println(); +// } + errorBits.set(2 * i + 0, (diff & 1) != 0); + errorBits.set(2 * i + 1, (diff & 2) != 0); + } + error = Arrays.copyOf(errorBits.toLongArray(), NEG_TABLE_SIZE / 32 + 1); + System.out.println("static uint64_t POW5_INV_ERRORS[" + error.length + "] = {"); + for (int i = 0; i < error.length; i++) { + if (i % 4 == 0) { + System.out.print(" "); + } + System.out.printf("0x%016xu, ", Long.valueOf(error[i])); + if (i % 4 == 3) { + System.out.println(); + } + } + System.out.println(); + System.out.println("};"); + + { + int[] exps = new int[] { 1, 10, 55, 56, 300, 1000, 2345, 3210, POS_TABLE_SIZE - 3, POS_TABLE_SIZE - 1 }; + System.out.println("static uint64_t EXACT_POW5[" + exps.length + "][4] = {"); + for (int i = 0; i < exps.length; i++) { + if (i != 0) { + System.out.println(","); + } + BigInteger exact = pos(exps[i]); + System.out.print(" { "); + for (int j = 0; j < 4; j++) { + if (j != 0) { + System.out.print(", "); + } + System.out.printf("%20su", exact.and(mask)); + exact = exact.shiftRight(64); + } + if (!exact.equals(BigInteger.ZERO)) { + throw new IllegalStateException(); + } + System.out.print(" }"); + } + System.out.println(); + System.out.println("};"); + } + + { + int[] exps = new int[] { 1, 10, 55, 56, 300, 1000, 2345, 3210, NEG_TABLE_SIZE - 3, NEG_TABLE_SIZE - 1 }; + System.out.println("static uint64_t EXACT_INV_POW5[" + exps.length + "][4] = {"); + for (int i = 0; i < exps.length; i++) { + if (i != 0) { + System.out.println(","); + } + BigInteger exact = neg(exps[i]).add(BigInteger.ONE); + System.out.print(" { "); + for (int j = 0; j < 4; j++) { + if (j != 0) { + System.out.print(", "); + } + System.out.printf("%20su", exact.and(mask)); + exact = exact.shiftRight(64); + } + if (!exact.equals(BigInteger.ZERO)) { + throw new IllegalStateException(); + } + System.out.print(" }"); + } + System.out.println(); + System.out.println("};"); + } + } + + private static BigInteger pos(int i) { + BigInteger pow = BigInteger.valueOf(5).pow(i); + return pow.shiftRight(pow.bitLength() - POW5_BITCOUNT); + } + + private static BigInteger neg(int i) { + BigInteger pow = BigInteger.valueOf(5).pow(i); + // We want floor(log_2 5^q) here, which is pow5len - 1. + int shift = pow.bitLength() - 1 + POW5_INV_BITCOUNT; + return BigInteger.ONE.shiftLeft(shift).divide(pow); //.add(BigInteger.ONE); + } + + private static int pow5bits(int e) { + return (int) ((e * 163391164108059L) >> 46) + 1; + } +} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/SlowConversion.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/SlowConversion.java new file mode 100644 index 0000000000..87c9db2fc6 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/SlowConversion.java @@ -0,0 +1,243 @@ +// Copyright 2018 Ulf Adams +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.fasterxml.jackson.core.io.ryu.analysis; + +import java.math.BigInteger; + +import com.fasterxml.jackson.core.io.ryu.RoundingMode; + +/** + * Implementation of Float and Double to String conversion. + */ +public final class SlowConversion { + private static final BigInteger TWO = BigInteger.valueOf(2); + + private static boolean DEBUG = false; + private static final boolean DEBUG_FLOAT = true; + + public static void main(String[] args) { + DEBUG = true; + if (DEBUG_FLOAT) { + float f = 0.33007812f; + String result = floatToString(f); + System.out.println(result + " " + f); + } else { + double f = 1.1873267205539228E-308; + String result = doubleToString(f); + System.out.println(result + " " + f); + } + } + + public static String floatToString(float value) { + return floatToString(value, RoundingMode.ROUND_EVEN); + } + + public static String floatToString(float value, RoundingMode roundingMode) { + if (DEBUG) System.out.println("VALUE="+value); + long bits = Float.floatToIntBits(value) & 0xffffffffL; + return asString(bits, FloatingPointFormat.FLOAT32, roundingMode); + } + + public static String doubleToString(double value) { + return doubleToString(value, RoundingMode.ROUND_EVEN); + } + + public static String doubleToString(double value, RoundingMode roundingMode) { + if (DEBUG) System.out.println("VALUE="+value); + long bits = Double.doubleToLongBits(value); + return asString(bits, FloatingPointFormat.FLOAT64, roundingMode); + } + + static String asString(long bits, FloatingPointFormat format, RoundingMode mode) { + // Step 1: Decode the floating point number, and unify normalized and subnormal cases. + // + // The format of all IEEE numbers is S E* M*; we obtain M by masking the lower M bits, E by + // shifting and masking, and S also by shifting and masking. + int mantissaBits = format.mantissaBits(); + int exponentBits = format.exponentBits(); + + int ieeeExponent = (int) ((bits >>> mantissaBits) & ((1 << exponentBits) - 1)); + long ieeeMantissa = bits & ((1L << mantissaBits) - 1); + boolean sign = ((bits >>> (mantissaBits + exponentBits)) & 1) != 0; + boolean even = (bits & 1) == 0; + + // Exit early if it's NaN, Infinity, or 0. + if (ieeeExponent == ((1 << exponentBits) - 1)) { + // Handle the special cases where the exponent is all 1s indicating NaN or Infinity: if the + // mantissa is non-zero, it's a NaN, otherwise it's +/-infinity. + return (ieeeMantissa != 0) ? "NaN" : sign ? "-Infinity" : "Infinity"; + } else if ((ieeeExponent == 0) && (ieeeMantissa == 0)) { + // If the mantissa is 0, the code below would end up with a lower bound that is less than 0, + // which throws off the char-by-char comparison. Instead, we exit here with the correct + // string. + return sign ? "-0.0" : "0.0"; + } + + // Compute the offset used by the IEEE format. + int offset = (1 << (exponentBits - 1)) - 1; + + // Unify normalized and subnormal cases. + int e2; + long m2; + if (ieeeExponent == 0) { + e2 = 1 - offset - mantissaBits; + m2 = ieeeMantissa; + } else { + e2 = ieeeExponent - offset - mantissaBits; + m2 = ieeeMantissa | (1L << mantissaBits); + } + + // Step 2: Determine the interval of legal decimal representations. + long mv = 4 * m2; + long mp = 4 * m2 + 2; + long mm = 4 * m2 - (((m2 != (1L << mantissaBits)) || (ieeeExponent <= 1)) ? 2 : 1); + e2 -= 2; + + // Step 3: Convert to a decimal power base using arbitrary-precision arithmetic. + BigInteger vr, vp, vm; + int e10; + if (e2 >= 0) { + vr = BigInteger.valueOf(mv).shiftLeft(e2); + vp = BigInteger.valueOf(mp).shiftLeft(e2); + vm = BigInteger.valueOf(mm).shiftLeft(e2); + e10 = 0; + } else { + BigInteger factor = BigInteger.valueOf(5).pow(-e2); + vr = BigInteger.valueOf(mv).multiply(factor); + vp = BigInteger.valueOf(mp).multiply(factor); + vm = BigInteger.valueOf(mm).multiply(factor); + e10 = e2; + } + + // Step 4: Find the shortest decimal representation in the interval of legal representations. + // + // We do some extra work here in order to follow Float/Double.toString semantics. In particular, + // that requires printing in scientific format if and only if the exponent is between -3 and 7, + // and it requires printing at least two decimal digits. + // + // Above, we moved the decimal dot all the way to the right, so now we need to count digits to + // figure out the correct exponent for scientific notation. + int vpLength = vp.toString().length(); + e10 += vpLength - 1; + boolean scientificNotation = (e10 < -3) || (e10 >= 7); + + if (DEBUG) { + System.out.println("IN=" + Long.toBinaryString(bits)); + System.out.println(" S=" + (sign ? "-" : "+") + " E=" + e2 + " M=" + m2); + System.out.println("E ="+e10); + System.out.println("V+="+vp); + System.out.println("V ="+vr); + System.out.println("V-="+vm); + } + + if (!mode.acceptUpperBound(even)) { + vp = vp.subtract(BigInteger.ONE); + } + boolean vmIsTrailingZeros = true; + // Track if vr is tailing zeroes _after_ lastRemovedDigit. + boolean vrIsTrailingZeros = true; + int removed = 0; + int lastRemovedDigit = 0; + while (!vp.divide(BigInteger.TEN).equals(vm.divide(BigInteger.TEN))) { + if (scientificNotation && vp.compareTo(BigInteger.valueOf(100)) < 0) { + // Float/Double.toString semantics requires printing at least two digits. + break; + } + vmIsTrailingZeros &= vm.mod(BigInteger.TEN).intValueExact() == 0; + vrIsTrailingZeros &= lastRemovedDigit == 0; + lastRemovedDigit = vr.mod(BigInteger.TEN).intValueExact(); + vp = vp.divide(BigInteger.TEN); + vr = vr.divide(BigInteger.TEN); + vm = vm.divide(BigInteger.TEN); + removed++; + } + if (vmIsTrailingZeros && mode.acceptLowerBound(even)) { + while (vm.mod(BigInteger.TEN).intValueExact() == 0) { + if (scientificNotation && vp.compareTo(BigInteger.valueOf(100)) < 0) { + // Float/Double.toString semantics requires printing at least two digits. + break; + } + vrIsTrailingZeros &= lastRemovedDigit == 0; + lastRemovedDigit = vr.mod(BigInteger.TEN).intValueExact(); + vp = vp.divide(BigInteger.TEN); + vr = vr.divide(BigInteger.TEN); + vm = vm.divide(BigInteger.TEN); + removed++; + } + } + if (vrIsTrailingZeros && (lastRemovedDigit == 5) && (vr.mod(TWO).intValueExact() == 0)) { + // Round down not up if the number ends in X50000 and the number is even. + lastRemovedDigit = 4; + } + String output = ((vr.compareTo(vm) > 0) ? (lastRemovedDigit >= 5 ? vr.add(BigInteger.ONE) : vr) : vp).toString(); + int olength = vpLength - removed; + + if (DEBUG) { + System.out.println("LRD=" + lastRemovedDigit); + System.out.println("VP=" + vp); + System.out.println("VR=" + vr); + System.out.println("VM=" + vm); + System.out.println("O=" + output); + System.out.println("OLEN=" + olength); + System.out.println("EXP=" + e10); + } + + // Step 5: Print the decimal representation. + // We follow Float/Double.toString semantics here. + StringBuilder result = new StringBuilder(); + // Add the minus sign if the number is negative. + if (sign) { + result.append('-'); + } + + if (scientificNotation) { + result.append(output.charAt(0)); + result.append('.'); + for (int i = 1; i < olength; i++) { + result.append(output.charAt(i)); + } + if (olength == 1) { + result.append('0'); + } + result.append('E'); + result.append(e10); + return result.toString(); + } else { + // Print leading 0s and '.' if applicable. + for (int i = 0; i > e10; i--) { + result.append('0'); + if (i == 0) { + result.append("."); + } + } + // Print number and '.' if applicable. + for (int i = 0; i < olength; i++) { + result.append(output.charAt(i)); + if (e10 == 0) { + result.append('.'); + } + e10--; + } + // Print trailing 0s and '.' if applicable. + for (; e10 >= -1; e10--) { + result.append('0'); + if (e10 == 0) { + result.append('.'); + } + } + return result.toString(); + } + } +} From 80fab12b74286ebba4a9ed95f8ec4428228b6c16 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Tue, 12 Apr 2022 15:35:37 +0200 Subject: [PATCH 03/17] remove some unnecessary classes --- .../ryu/analysis/ComputeRequiredBitSizes.java | 124 --------- .../io/ryu/analysis/ComputeTableSizes.java | 65 ----- .../analysis/ExhaustiveFloatComparison.java | 55 ---- .../analysis/ExtensiveDoubleComparison.java | 44 ---- .../ryu/analysis/PrintDoubleLookupTable.java | 180 ------------- .../ryu/analysis/PrintFloatLookupTable.java | 69 ----- .../ryu/analysis/PrintGenericLookupTable.java | 242 ------------------ 7 files changed, 779 deletions(-) delete mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/ComputeRequiredBitSizes.java delete mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/ComputeTableSizes.java delete mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/ExhaustiveFloatComparison.java delete mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/ExtensiveDoubleComparison.java delete mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/PrintDoubleLookupTable.java delete mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/PrintFloatLookupTable.java delete mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/PrintGenericLookupTable.java diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/ComputeRequiredBitSizes.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/ComputeRequiredBitSizes.java deleted file mode 100644 index 2526eae941..0000000000 --- a/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/ComputeRequiredBitSizes.java +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright 2018 Ulf Adams -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.fasterxml.jackson.core.io.ryu.analysis; - -import java.math.BigInteger; -import java.util.EnumSet; - -/** - * Computes appropriate values for B_0 and B_1 for a given floating point type. - */ -public final class ComputeRequiredBitSizes { - private static final BigInteger FIVE = BigInteger.valueOf(5); - private static final BigInteger TWO = BigInteger.valueOf(2); - - private static final long LOG10_5_DENOMINATOR = 10000000L; - private static final long LOG10_5_NUMERATOR = (long) (LOG10_5_DENOMINATOR * Math.log10(5)); - - private static final long LOG10_2_DENOMINATOR = 10000000L; - private static final long LOG10_2_NUMERATOR = (long) (LOG10_2_DENOMINATOR * Math.log10(2)); - - public static void main(String[] args) { - boolean verbose = false; - EnumSet formats = EnumSet.noneOf(FloatingPointFormat.class); - formats.add(FloatingPointFormat.FLOAT16); - formats.add(FloatingPointFormat.FLOAT32); - formats.add(FloatingPointFormat.FLOAT64); - for (String s : args) { - if ("-80".equals(s)) { - formats.add(FloatingPointFormat.FLOAT80); - } else if ("-128".equals(s)) { - formats.add(FloatingPointFormat.FLOAT128); - } else if ("-256".equals(s)) { - formats.add(FloatingPointFormat.FLOAT256); - } else if ("-v".equals(s)) { - verbose = true; - } - } - - for (FloatingPointFormat format : formats) { - compute(format, verbose); - } - } - - private static void compute(FloatingPointFormat format, boolean verbose) { - int mbits = format.mantissaBits() + 3; - - int minE2 = 0; - int maxE2 = -(1 - format.bias() - format.mantissaBits() - 2); - int b1 = 0; - for (int e2 = minE2; e2 < maxE2 + 1; e2++) { - int q = Math.max(0, (int) (e2 * LOG10_5_NUMERATOR / LOG10_5_DENOMINATOR) - 1); - int i = e2 - q; - BigInteger pow5 = FIVE.pow(i); - BigInteger pow2 = BigInteger.ONE.shiftLeft(q); - - // max(w) = 4 * ((1 << format.mantissaBits()) * 2 - 1) + 2 - // = (1 << (format.mantissaBits() + 3)) - 2 - BigInteger mxM = BigInteger.ONE.shiftLeft(mbits).subtract(TWO); - BigInteger min = EuclidMinMax.min(pow5, pow2, mxM.subtract(BigInteger.ONE)); -// BigInteger min2 = minSlow(pow5, pow2, mxM); -// if (!min.equals(min2)) { -// new IllegalStateException(min + " " + min2).printStackTrace(System.out); -// } - - int bits = min.divide(mxM).bitLength(); - int reqn = pow5.bitLength() - bits; - b1 = Math.max(b1, reqn); - if (verbose) { - System.out.printf("%s,%s,%s,%s,%s,%s,%.2f%%\n", - Integer.valueOf(e2), Integer.valueOf(q), Integer.valueOf(i), Integer.valueOf(bits), - Integer.valueOf(reqn), Integer.valueOf(b1), Double.valueOf((100.0 * e2) / maxE2)); - } - } - if (verbose) { - System.out.println("B_1 = " + b1); - } - - minE2 = 0; - maxE2 = ((1 << format.exponentBits()) - 2) - format.bias() - format.mantissaBits() - 2; - int b0 = 0; - for (int e2 = minE2; e2 < maxE2 + 1; e2++) { - int q = Math.max(0, (int) (e2 * LOG10_2_NUMERATOR / LOG10_2_DENOMINATOR) - 1); - BigInteger pow5 = FIVE.pow(q); - BigInteger pow2 = BigInteger.ONE.shiftLeft(e2 - q); - - // max(w) = 4 * ((1 << format.mantissaBits()) * 2 - 1) + 2 - // = (1 << (format.mantissaBits() + 3)) - 2 - BigInteger mxM = BigInteger.ONE.shiftLeft(mbits).subtract(TWO); - BigInteger max = EuclidMinMax.max(pow2, pow5, mxM.subtract(BigInteger.ONE)); -// BigInteger max2 = maxSlow(pow2, pow5, mxM); -// if (!max.equals(max2)) { -// new IllegalStateException(max + " " + max2).printStackTrace(System.out); -// } - - BigInteger num = mxM.multiply(pow5).multiply(pow2); - BigInteger den = pow5.subtract(max); - int bits = num.divide(den).bitLength(); - int reqn = bits - pow5.bitLength(); - b0 = Math.max(b0, reqn); - if (verbose) { - System.out.printf("%s,%s,%s,%s,%s,%s,%.2f%%\n", - Integer.valueOf(e2), Integer.valueOf(q), Integer.valueOf(e2 - q), Integer.valueOf(bits), - Integer.valueOf(reqn), Integer.valueOf(b0), Double.valueOf((100.0 * e2) / maxE2)); - } - } - if (verbose) { - System.out.println("B_0 = " + b0); - System.out.println(); - } - System.out.printf("%s,%d,%d\n", format, Integer.valueOf(b0), Integer.valueOf(b1)); - } -} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/ComputeTableSizes.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/ComputeTableSizes.java deleted file mode 100644 index cc311ce2dc..0000000000 --- a/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/ComputeTableSizes.java +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2018 Ulf Adams -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.fasterxml.jackson.core.io.ryu.analysis; - -/** - * Computes and outputs the lookup table sizes. - */ -public class ComputeTableSizes { - private static final long LOG10_2_DENOMINATOR = 10000000L; - private static final long LOG10_2_NUMERATOR = (long) (LOG10_2_DENOMINATOR * Math.log10(2)); - - private static final long LOG10_5_DENOMINATOR = 10000000L; - private static final long LOG10_5_NUMERATOR = (long) (LOG10_5_DENOMINATOR * Math.log10(5)); - - public static void main(String[] args) { - boolean verbose = false; - for (String s : args) { - if ("-v".equals(s)) { - verbose = true; - } - } - - if (verbose) { - System.out.println("These are the lookup table sizes required for each floating point type:"); - } else { - System.out.println("floating_point_type,table_size_1,table_size_2,total_size"); - } - for (FloatingPointFormat format : FloatingPointFormat.values()) { - int minE2 = 1 - format.bias() - format.mantissaBits() - 2; - int maxE2 = ((1 << format.exponentBits()) - 2) - format.bias() - format.mantissaBits() - 2; - long minQ, maxQ; - if (format == FloatingPointFormat.FLOAT32) { - // Float32 is special; using -1 as below is problematic as it would require 33 bit - // integers. Instead, we compute the additionally required digit separately. - minQ = (-minE2 * LOG10_5_NUMERATOR) / LOG10_5_DENOMINATOR; - maxQ = (maxE2 * LOG10_2_NUMERATOR) / LOG10_2_DENOMINATOR; - } else { - minQ = Math.max(0, (-minE2 * LOG10_5_NUMERATOR) / LOG10_5_DENOMINATOR - 1); - maxQ = Math.max(0, (maxE2 * LOG10_2_NUMERATOR) / LOG10_2_DENOMINATOR - 1); - } - long negTableSize = (-minE2 - minQ) + 1; - long posTableSize = maxQ + 1; - if (verbose) { - System.out.println(format); - System.out.println(" " + minE2 + " <= e_2 <= " + maxE2); - System.out.println(" " + (-minQ) + " <= q <= " + maxQ); - System.out.println(" Total table size = " + negTableSize + " + " + posTableSize + " = " + (negTableSize + posTableSize)); - } else { - System.out.println(format + "," + negTableSize + "," + posTableSize + "," + (negTableSize + posTableSize)); - } - } - } -} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/ExhaustiveFloatComparison.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/ExhaustiveFloatComparison.java deleted file mode 100644 index e73b041519..0000000000 --- a/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/ExhaustiveFloatComparison.java +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2018 Ulf Adams -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.fasterxml.jackson.core.io.ryu.analysis; - -import com.fasterxml.jackson.core.io.ryu.RyuFloat; - -/** - * Exhaustively tests the fast implementation of Ryu against the slow one. - */ -public class ExhaustiveFloatComparison { - public static void main(String[] args) { - System.out.println("This checks every possible 32-bit floating point value, which takes ~60 hours."); - System.out.println("We report progress approximately every ~10 seconds."); - long stride = 10007L; // 10007 is prime - // We go through the set of all possible values in stride-sized steps, and go through all - // possible [0,stride) offsets. This gives us good coverage across the whole range of floating - // point numbers even in the first loop, which quickly finds systematic errors. We use a prime - // to avoid bit patterns in the floating point number. - // - // We also don't check any negative numbers; we assume that the code behaves identically. - for (long base = 0; base < stride; base++) { - for (long l = base; l <= 0x7fffffffL; l += stride) { - float f = Float.intBitsToFloat((int) l); - String expected = SlowConversion.floatToString(f); - String actual = RyuFloat.floatToString(f); - if (!expected.equals(actual)) { - System.out.println(String.format("expected %s, but was %s", expected, actual)); - throw new RuntimeException(String.format("expected %s, but was %s", expected, actual)); - } - - // Also check round-trip safety. - long g = Float.floatToRawIntBits(Float.parseFloat(actual)) & 0xffffffffL; - if (!Float.isNaN(f) && g != l) { - String message = String.format("expected %d, but was %d", Long.valueOf(l), Long.valueOf(g)); - System.out.println(message); - throw new RuntimeException(message); - } - } - double frac = (base + 1) / (double) stride; - System.out.printf("(%6.2f%%)\n", Double.valueOf(100 * frac)); - } - } -} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/ExtensiveDoubleComparison.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/ExtensiveDoubleComparison.java deleted file mode 100644 index 6805ac4993..0000000000 --- a/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/ExtensiveDoubleComparison.java +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2018 Ulf Adams -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.fasterxml.jackson.core.io.ryu.analysis; - -import com.fasterxml.jackson.core.io.ryu.RyuDouble; - -/** - * Extensively tests the fast implementation of Ryu against the slow one. - */ -public class ExtensiveDoubleComparison { - public static void main(String[] args) { - checkDoubleFastAgainstSlow(); - } - - private static void checkDoubleFastAgainstSlow() { - System.out.println("This checks every possible 64-bit floating point value - interrupt when you are satisfied."); - long stride = 1000000000000000L; - for (long base = 0; base < stride; base++) { - for (long l = base; l <= 0x7fffffffffffffffL - stride + 1; l += stride) { - double d = Double.longBitsToDouble(l); - String expected = RyuDouble.doubleToString(d); - String actual = SlowConversion.doubleToString(d); - if (!expected.equals(actual)) { - System.out.println(String.format("expected %s, but was %s", expected, actual)); - throw new RuntimeException(String.format("expected %s, but was %s", expected, actual)); - } - } - double frac = (base + 1) / (double) stride; - System.out.printf("(%16.12f%%)\n", Double.valueOf(100 * frac)); - } - } -} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/PrintDoubleLookupTable.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/PrintDoubleLookupTable.java deleted file mode 100644 index 59a54429e7..0000000000 --- a/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/PrintDoubleLookupTable.java +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright 2018 Ulf Adams -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.fasterxml.jackson.core.io.ryu.analysis; - -import java.math.BigInteger; - -/** - * Prints a lookup table for the C version of Ryu. - */ -public final class PrintDoubleLookupTable { - private static final int POS_TABLE_SIZE = 326; - // The C version has two code paths, one of which requires an additional entry here. - private static final int NEG_TABLE_SIZE = 342; - - // We intentionally choose these to be larger than or equal to the float equivalent + 64. - private static final int POW5_BITCOUNT = 125; // max 127 - private static final int POW5_INV_BITCOUNT = 125; // max 127 - - private static final boolean PRINT_LARGE_TABLES = true; - - private static final BigInteger MASK64 = BigInteger.valueOf(1).shiftLeft(64).subtract(BigInteger.ONE); - - public static void main(String[] args) { - BigInteger[] largeInvTable = new BigInteger[NEG_TABLE_SIZE]; - for (int i = 0; i < largeInvTable.length; i++) { - largeInvTable[i] = invMultiplier(i); - } - - int mulTableSize = 26; - BigInteger[] smallInvTable = new BigInteger[(NEG_TABLE_SIZE + mulTableSize - 1) / mulTableSize + 1]; - for (int i = 0; i < smallInvTable.length; i++) { - smallInvTable[i] = invMultiplier(mulTableSize * i); - } - - int[] invErrorTable = new int[NEG_TABLE_SIZE]; - for (int i = 0; i < NEG_TABLE_SIZE; i++) { - int base = (i + mulTableSize - 1) / mulTableSize; - int base2 = base * mulTableSize; - int offset = base2 - i; - BigInteger mul = BigInteger.valueOf(5).pow(offset); - BigInteger result = smallInvTable[base].subtract(BigInteger.ONE).multiply(mul).shiftRight(pow5bits(base2) - pow5bits(i)).add(BigInteger.ONE); - BigInteger error = invMultiplier(i).subtract(result); - if ((error.signum() < 0) || (error.compareTo(BigInteger.valueOf(2)) > 0)) { - throw new IllegalStateException("That went wrong!"); - } - invErrorTable[i] = error.intValueExact(); - } - - BigInteger[] largeTable = new BigInteger[POS_TABLE_SIZE]; - for (int i = 0; i < largeTable.length; i++) { - largeTable[i] = multiplier(i); - } - BigInteger[] smallTable = new BigInteger[(POS_TABLE_SIZE + mulTableSize - 1) / mulTableSize]; - for (int i = 0; i < smallTable.length; i++) { - smallTable[i] = multiplier(mulTableSize * i); - } - - int[] errorTable = new int[POS_TABLE_SIZE]; - for (int i = 0; i < POS_TABLE_SIZE; i++) { - int base = i / mulTableSize; - int base2 = base * mulTableSize; - int offset = i - base2; - BigInteger mul = BigInteger.valueOf(5).pow(offset); - BigInteger result = smallTable[base].multiply(mul).shiftRight(pow5bits(i) - pow5bits(base2)); - BigInteger error = multiplier(i).subtract(result); - if ((error.signum() < 0) || (error.compareTo(BigInteger.valueOf(2)) > 0)) { - throw new IllegalStateException("That went wrong: " + error); - } - errorTable[i] = error.intValueExact(); - } -// System.out.println(Arrays.toString(invErrorTable)); -// System.out.println(Arrays.toString(errorTable)); - - System.out.println("#define DOUBLE_POW5_INV_BITCOUNT " + POW5_INV_BITCOUNT); - System.out.println("#define DOUBLE_POW5_BITCOUNT " + POW5_BITCOUNT); - System.out.println(); - if (PRINT_LARGE_TABLES) { - printTable("DOUBLE_POW5_INV_SPLIT", largeInvTable, 2); - System.out.println(); - printTable("DOUBLE_POW5_SPLIT", largeTable, 2); - } else { - printTable("DOUBLE_POW5_INV_SPLIT2", smallInvTable, 1); - printOffsets("POW5_INV_OFFSETS", invErrorTable, 6); - System.out.println(); - printTable("DOUBLE_POW5_SPLIT2", smallTable, 1); - printOffsets("POW5_OFFSETS", errorTable, 6); - } - } - - private static BigInteger invMultiplier(int i) { - // 5^i - BigInteger pow = BigInteger.valueOf(5).pow(i); - // length of 5^i in binary = ceil(log_2(5^i)) - int pow5len = pow.bitLength(); - // We want floor(log_2(5^i)) here, which is pow5len - 1 (no power of 5 is a power of 2). - int j = pow5len - 1 + POW5_INV_BITCOUNT; - // [2^j / 5^i] + 1 = [2^(floor(log_2(5^i)) + POW5_INV_BITCOUNT) / 5^i] + 1 - // By construction, this will have approximately POW5_INV_BITCOUNT + 1 bits. - BigInteger inv = BigInteger.ONE.shiftLeft(j).divide(pow).add(BigInteger.ONE); - if (inv.bitLength() > POW5_INV_BITCOUNT + 1) { - throw new IllegalStateException("Result is longer than expected: " + inv.bitLength() + " > " + (POW5_INV_BITCOUNT + 1)); - } - return inv; - } - - private static BigInteger multiplier(int i) { - // 5^i - BigInteger pow = BigInteger.valueOf(5).pow(i); - int pow5len = pow.bitLength(); - // [5^i / 2^j] = [5^i / 2^(ceil(log_2(5^i)) - POW5_BITCOUNT)] - // By construction, this will have exactly POW5_BITCOUNT bits. Note that this can shift left if j is negative! - BigInteger pow5DivPow2 = pow.shiftRight(pow5len - POW5_BITCOUNT); - if (pow5DivPow2.bitLength() != POW5_BITCOUNT) { - throw new IllegalStateException("Unexpected result length: " + pow5DivPow2.bitLength() + " != " + POW5_BITCOUNT); - } - return pow5DivPow2; - } - - private static int pow5bits(int e) { - return ((e * 1217359) >> 19) + 1; - } - - private static void printTable(String name, BigInteger[] table, int entriesPerLine) { - System.out.println("static const uint64_t " + name + "[" + table.length + "][2] = {"); - for (int i = 0; i < table.length; i++) { - BigInteger pow5High = table[i].shiftRight(64); - BigInteger pow5Low = table[i].and(MASK64); - if (i % entriesPerLine == 0) { - System.out.print(" "); - } else { - System.out.print(" "); - } - System.out.printf("{ %20su, %18su }", pow5Low, pow5High); - if (i != table.length - 1) { - System.out.print(","); - } - if (i % entriesPerLine == entriesPerLine - 1) { - System.out.println(); - } - } - System.out.println("};"); - } - - private static void printOffsets(String name, int[] table, int entriesPerLine) { - int length = (table.length + 15) / 16; - System.out.println("static const uint32_t " + name + "[" + length + "] = {"); - for (int i = 0; i < length; i++) { - int value = 0; - for (int j = 0; j < 16; j++) { - int offset = i * 16 + j < table.length ? table[i * 16 + j] : 0; - value |= offset << (j << 1); - } - if (i % entriesPerLine == 0) { - System.out.print(" "); - } else { - System.out.print(" "); - } - System.out.printf("0x%08x", Integer.valueOf(value)); - if (i != length - 1) { - System.out.print(","); - } - if ((i % entriesPerLine == entriesPerLine - 1) || (i == length - 1)) { - System.out.println(); - } - } - System.out.println("};"); - } -} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/PrintFloatLookupTable.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/PrintFloatLookupTable.java deleted file mode 100644 index c2188a969e..0000000000 --- a/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/PrintFloatLookupTable.java +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2018 Ulf Adams -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.fasterxml.jackson.core.io.ryu.analysis; - -import java.math.BigInteger; - -/** - * Prints a lookup table for the C version of Ryu. - */ -public final class PrintFloatLookupTable { - private static final int POS_TABLE_SIZE = 47; - private static final int INV_TABLE_SIZE = 55; - - private static final int POW5_BITCOUNT = 61; // max 63 - private static final int POW5_INV_BITCOUNT = 59; // max 63 - - public static void main(String[] args) { - System.out.println("#define POW5_INV_BITCOUNT " + POW5_INV_BITCOUNT); - System.out.println("static uint64_t FLOAT_POW5_INV_SPLIT[" + INV_TABLE_SIZE + "] = {"); - for (int i = 0; i < INV_TABLE_SIZE; i++) { - BigInteger pow = BigInteger.valueOf(5).pow(i); - int pow5len = pow.bitLength(); - int j = pow5len - 1 + POW5_INV_BITCOUNT; - BigInteger pow5inv = BigInteger.ONE.shiftLeft(j).divide(pow).add(BigInteger.ONE); - - long v = pow5inv.longValueExact(); - System.out.printf(" %19su", Long.toUnsignedString(v)); - if (i < INV_TABLE_SIZE - 1) { - System.out.print(","); - } - if (i % 4 == 3) { - System.out.println(); - } - } - System.out.println(); - System.out.println("};"); - - System.out.println("#define POW5_BITCOUNT " + POW5_BITCOUNT); - System.out.println("static uint64_t FLOAT_POW5_SPLIT[" + POS_TABLE_SIZE + "] = {"); - for (int i = 0; i < POS_TABLE_SIZE; i++) { - BigInteger pow = BigInteger.valueOf(5).pow(i); - int pow5len = pow.bitLength(); - BigInteger pow5 = pow.shiftRight(pow5len - POW5_BITCOUNT); - - long v = pow5.longValueExact(); - System.out.printf(" %19su", Long.toUnsignedString(v)); - if (i < POS_TABLE_SIZE - 1) { - System.out.print(","); - } - if (i % 4 == 3) { - System.out.println(); - } - } - System.out.println(); - System.out.println("};"); - } -} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/PrintGenericLookupTable.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/PrintGenericLookupTable.java deleted file mode 100644 index 99a86bf041..0000000000 --- a/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/PrintGenericLookupTable.java +++ /dev/null @@ -1,242 +0,0 @@ -// Copyright 2018 Ulf Adams -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.fasterxml.jackson.core.io.ryu.analysis; - -import java.math.BigInteger; -import java.util.Arrays; -import java.util.BitSet; - -/** - * Prints a lookup table for the C version of Ryu. - */ -public final class PrintGenericLookupTable { - // Bit sizes: FLOAT128,249,246 - // Table sizes: FLOAT128,4968,4897,9865 - // 5^55 < 2^128 - private static final int POS_TABLE_SIZE = 4968; - private static final int NEG_TABLE_SIZE = 4897; - - // We use the same size for simplicity. - private static final int POW5_BITCOUNT = 249; - private static final int POW5_INV_BITCOUNT = 249; - - public static void main(String[] args) { - BigInteger mask = BigInteger.valueOf(1).shiftLeft(64).subtract(BigInteger.ONE); - - int pow5TableSize = 56; // log_5(2^128) + 1 - System.out.println("#define POW5_INV_BITCOUNT " + POW5_INV_BITCOUNT); - System.out.println("#define POW5_BITCOUNT " + POW5_BITCOUNT); - System.out.println("#define POW5_TABLE_SIZE " + pow5TableSize); - System.out.println(); - System.out.println("static uint64_t GENERIC_POW5_TABLE[POW5_TABLE_SIZE][2] = {"); - for (int i = 0; i < pow5TableSize; i++) { - if (i != 0) { - System.out.println(","); - } - BigInteger pow = BigInteger.valueOf(5).pow(i); - System.out.print(" { "); - for (int j = 0; j < 2; j++) { - if (j != 0) { - System.out.print(", "); - } - System.out.printf("%20su", pow.and(mask)); - pow = pow.shiftRight(64); - } - if (!pow.equals(BigInteger.ZERO)) { - throw new IllegalStateException(); - } - System.out.print(" }"); - } - System.out.println(); - System.out.println("};"); - - int posPartialTableSize = (POS_TABLE_SIZE / pow5TableSize) + 1; - System.out.println("static uint64_t GENERIC_POW5_SPLIT[" + posPartialTableSize + "][4] = {"); - for (int i = 0; i < posPartialTableSize; i++) { - if (i != 0) { - System.out.println(","); - } - BigInteger pow = pos(pow5TableSize * i); - System.out.print(" { "); - for (int j = 0; j < 4; j++) { - if (j != 0) { - System.out.print(", "); - } - System.out.printf("%20su", pow.and(mask)); - pow = pow.shiftRight(64); - } - if (!pow.equals(BigInteger.ZERO)) { - throw new IllegalStateException(); - } - System.out.print(" }"); - } - System.out.println(); - System.out.println("};"); - - BitSet errorBits = new BitSet(); - for (int i = 0; i < POS_TABLE_SIZE; i++) { - int base = i / pow5TableSize; - int base2 = base * pow5TableSize; - int offset = i - base2; - BigInteger pow5base = pos(base2); - BigInteger pow5offset = BigInteger.valueOf(5).pow(offset); - BigInteger product = pow5base.multiply(pow5offset).shiftRight(pow5bits(i) - pow5bits(base2)); - - BigInteger exact = pos(i); - - int diff = exact.subtract(product).intValueExact(); -// System.out.print((diff != 0 ? diff : " ") + " "); -// if (i % pow5TableSize == pow5TableSize - 1) { -// System.out.println(); -// } - errorBits.set(2 * i + 0, (diff & 1) != 0); - errorBits.set(2 * i + 1, (diff & 2) != 0); - } - long[] error = Arrays.copyOf(errorBits.toLongArray(), POS_TABLE_SIZE / 32 + 1); - System.out.println("static uint64_t POW5_ERRORS[" + error.length + "] = {"); - for (int i = 0; i < error.length; i++) { - if (i % 4 == 0) { - if (i != 0) { - System.out.println(); - } - System.out.print(" "); - } - System.out.printf("0x%016xu, ", Long.valueOf(error[i])); - } - System.out.println(); - System.out.println("};"); - - int negPartialTableSize = (NEG_TABLE_SIZE + pow5TableSize - 1) / pow5TableSize + 1; - System.out.println("static uint64_t GENERIC_POW5_INV_SPLIT[" + negPartialTableSize + "][4] = {"); - for (int i = 0; i < negPartialTableSize; i++) { - if (i != 0) { - System.out.println(","); - } - BigInteger inv = neg(i * pow5TableSize); - System.out.print(" { "); - for (int j = 0; j < 4; j++) { - if (j != 0) { - System.out.print(", "); - } - System.out.printf("%20su", inv.and(mask)); - inv = inv.shiftRight(64); - } - if (!inv.equals(BigInteger.ZERO)) { - throw new IllegalStateException(); - } - System.out.print(" }"); - } - System.out.println(); - System.out.println("};"); - errorBits.clear(); - for (int i = 0; i < NEG_TABLE_SIZE; i++) { - int base = (i + pow5TableSize - 1) / pow5TableSize; - int base2 = base * pow5TableSize; - int offset = base2 - i; - BigInteger pow5base = neg(base2); - BigInteger pow5offset = BigInteger.valueOf(5).pow(offset); - BigInteger product = pow5base.multiply(pow5offset).shiftRight(pow5bits(base2) - pow5bits(i)); - - BigInteger exact = neg(i); - - int diff = exact.subtract(product).intValueExact(); -// System.out.print((diff != 0 ? diff : " ") + " "); -// if (i % pow5TableSize == pow5TableSize - 1) { -// System.out.println(); -// } - errorBits.set(2 * i + 0, (diff & 1) != 0); - errorBits.set(2 * i + 1, (diff & 2) != 0); - } - error = Arrays.copyOf(errorBits.toLongArray(), NEG_TABLE_SIZE / 32 + 1); - System.out.println("static uint64_t POW5_INV_ERRORS[" + error.length + "] = {"); - for (int i = 0; i < error.length; i++) { - if (i % 4 == 0) { - System.out.print(" "); - } - System.out.printf("0x%016xu, ", Long.valueOf(error[i])); - if (i % 4 == 3) { - System.out.println(); - } - } - System.out.println(); - System.out.println("};"); - - { - int[] exps = new int[] { 1, 10, 55, 56, 300, 1000, 2345, 3210, POS_TABLE_SIZE - 3, POS_TABLE_SIZE - 1 }; - System.out.println("static uint64_t EXACT_POW5[" + exps.length + "][4] = {"); - for (int i = 0; i < exps.length; i++) { - if (i != 0) { - System.out.println(","); - } - BigInteger exact = pos(exps[i]); - System.out.print(" { "); - for (int j = 0; j < 4; j++) { - if (j != 0) { - System.out.print(", "); - } - System.out.printf("%20su", exact.and(mask)); - exact = exact.shiftRight(64); - } - if (!exact.equals(BigInteger.ZERO)) { - throw new IllegalStateException(); - } - System.out.print(" }"); - } - System.out.println(); - System.out.println("};"); - } - - { - int[] exps = new int[] { 1, 10, 55, 56, 300, 1000, 2345, 3210, NEG_TABLE_SIZE - 3, NEG_TABLE_SIZE - 1 }; - System.out.println("static uint64_t EXACT_INV_POW5[" + exps.length + "][4] = {"); - for (int i = 0; i < exps.length; i++) { - if (i != 0) { - System.out.println(","); - } - BigInteger exact = neg(exps[i]).add(BigInteger.ONE); - System.out.print(" { "); - for (int j = 0; j < 4; j++) { - if (j != 0) { - System.out.print(", "); - } - System.out.printf("%20su", exact.and(mask)); - exact = exact.shiftRight(64); - } - if (!exact.equals(BigInteger.ZERO)) { - throw new IllegalStateException(); - } - System.out.print(" }"); - } - System.out.println(); - System.out.println("};"); - } - } - - private static BigInteger pos(int i) { - BigInteger pow = BigInteger.valueOf(5).pow(i); - return pow.shiftRight(pow.bitLength() - POW5_BITCOUNT); - } - - private static BigInteger neg(int i) { - BigInteger pow = BigInteger.valueOf(5).pow(i); - // We want floor(log_2 5^q) here, which is pow5len - 1. - int shift = pow.bitLength() - 1 + POW5_INV_BITCOUNT; - return BigInteger.ONE.shiftLeft(shift).divide(pow); //.add(BigInteger.ONE); - } - - private static int pow5bits(int e) { - return (int) ((e * 163391164108059L) >> 46) + 1; - } -} From 47d61eb52670c959bbe50305d8e0d6c4ca1bfb9f Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Tue, 12 Apr 2022 15:56:18 +0200 Subject: [PATCH 04/17] use schubfach instead of ryu --- .../jackson/core/io/NumberOutput.java | 8 +- .../core/io/schubfach/DoubleToDecimal.java | 649 ++++++++++++++ .../core/io/schubfach/FloatToDecimal.java | 622 +++++++++++++ .../jackson/core/io/schubfach/MathUtils.java | 823 ++++++++++++++++++ .../core/io/schubfach/DoubleToStringTest.java | 75 ++ .../core/io/schubfach/FloatToStringTest.java | 106 +++ .../io/schubfach/SchubfachDoubleTest.java | 12 + .../core/io/schubfach/SchubfachFloatTest.java | 12 + 8 files changed, 2303 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/fasterxml/jackson/core/io/schubfach/DoubleToDecimal.java create mode 100644 src/main/java/com/fasterxml/jackson/core/io/schubfach/FloatToDecimal.java create mode 100644 src/main/java/com/fasterxml/jackson/core/io/schubfach/MathUtils.java create mode 100644 src/test/java/com/fasterxml/jackson/core/io/schubfach/DoubleToStringTest.java create mode 100644 src/test/java/com/fasterxml/jackson/core/io/schubfach/FloatToStringTest.java create mode 100644 src/test/java/com/fasterxml/jackson/core/io/schubfach/SchubfachDoubleTest.java create mode 100644 src/test/java/com/fasterxml/jackson/core/io/schubfach/SchubfachFloatTest.java diff --git a/src/main/java/com/fasterxml/jackson/core/io/NumberOutput.java b/src/main/java/com/fasterxml/jackson/core/io/NumberOutput.java index 344e409a09..bfb730ce81 100644 --- a/src/main/java/com/fasterxml/jackson/core/io/NumberOutput.java +++ b/src/main/java/com/fasterxml/jackson/core/io/NumberOutput.java @@ -1,7 +1,7 @@ package com.fasterxml.jackson.core.io; -import com.fasterxml.jackson.core.io.ryu.RyuDouble; -import com.fasterxml.jackson.core.io.ryu.RyuFloat; +import com.fasterxml.jackson.core.io.schubfach.DoubleToDecimal; +import com.fasterxml.jackson.core.io.schubfach.FloatToDecimal; public final class NumberOutput { @@ -277,12 +277,12 @@ public static String toString(long v) { } public static String toString(double v) { - return RyuDouble.doubleToString(v); + return DoubleToDecimal.toString(v); } // @since 2.6 public static String toString(float v) { - return RyuFloat.floatToString(v); + return FloatToDecimal.toString(v); } /* diff --git a/src/main/java/com/fasterxml/jackson/core/io/schubfach/DoubleToDecimal.java b/src/main/java/com/fasterxml/jackson/core/io/schubfach/DoubleToDecimal.java new file mode 100644 index 0000000000..ccb5f8b2f6 --- /dev/null +++ b/src/main/java/com/fasterxml/jackson/core/io/schubfach/DoubleToDecimal.java @@ -0,0 +1,649 @@ +/* + * Copyright 2018-2020 Raffaello Giulietti + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.fasterxml.jackson.core.io.schubfach; + +import java.io.IOException; + +import static com.fasterxml.jackson.core.io.schubfach.MathUtils.flog10pow2; +import static com.fasterxml.jackson.core.io.schubfach.MathUtils.flog10threeQuartersPow2; +import static com.fasterxml.jackson.core.io.schubfach.MathUtils.flog2pow10; +import static com.fasterxml.jackson.core.io.schubfach.MathUtils.g0; +import static com.fasterxml.jackson.core.io.schubfach.MathUtils.g1; +import static com.fasterxml.jackson.core.io.schubfach.MathUtils.multiplyHigh; +import static com.fasterxml.jackson.core.io.schubfach.MathUtils.pow10; +import static java.lang.Double.doubleToRawLongBits; +import static java.lang.Long.numberOfLeadingZeros; + +/** + * This class exposes a method to render a {@code double} as a string. + * + * @author Raffaello Giulietti + */ +final public class DoubleToDecimal { + /* + For full details about this code see the following references: + + [1] Giulietti, "The Schubfach way to render doubles", + https://drive.google.com/open?id=1luHhyQF9zKlM8yJ1nebU0OgVYhfC6CBN + + [2] IEEE Computer Society, "IEEE Standard for Floating-Point Arithmetic" + + [3] Bouvier & Zimmermann, "Division-Free Binary-to-Decimal Conversion" + + Divisions are avoided altogether for the benefit of those architectures + that do not provide specific machine instructions or where they are slow. + This is discussed in section 10 of [1]. + */ + + // The precision in bits. + static final int P = 53; + + // Exponent width in bits. + private static final int W = (Double.SIZE - 1) - (P - 1); + + // Minimum value of the exponent: -(2^(W-1)) - P + 3. + static final int Q_MIN = (-1 << W - 1) - P + 3; + + // Maximum value of the exponent: 2^(W-1) - P. + static final int Q_MAX = (1 << W - 1) - P; + + // 10^(E_MIN - 1) <= MIN_VALUE < 10^E_MIN + static final int E_MIN = -323; + + // 10^(E_MAX - 1) <= MAX_VALUE < 10^E_MAX + static final int E_MAX = 309; + + // Threshold to detect tiny values, as in section 8.1.1 of [1] + static final long C_TINY = 3; + + // The minimum and maximum k, as in section 8 of [1] + static final int K_MIN = -324; + static final int K_MAX = 292; + + // H is as in section 8 of [1]. + static final int H = 17; + + // Minimum value of the significand of a normal value: 2^(P-1). + private static final long C_MIN = 1L << P - 1; + + // Mask to extract the biased exponent. + private static final int BQ_MASK = (1 << W) - 1; + + // Mask to extract the fraction bits. + private static final long T_MASK = (1L << P - 1) - 1; + + // Used in rop(). + private static final long MASK_63 = (1L << 63) - 1; + + // Used for left-to-tight digit extraction. + private static final int MASK_28 = (1 << 28) - 1; + + private static final int NON_SPECIAL = 0; + private static final int PLUS_ZERO = 1; + private static final int MINUS_ZERO = 2; + private static final int PLUS_INF = 3; + private static final int MINUS_INF = 4; + private static final int NAN = 5; + + // For thread-safety, each thread gets its own instance of this class. + private static final ThreadLocal threadLocal = + ThreadLocal.withInitial(DoubleToDecimal::new); + + /* + Room for the longer of the forms + -ddddd.dddddddddddd H + 2 characters + -0.00ddddddddddddddddd H + 5 characters + -d.ddddddddddddddddE-eee H + 7 characters + where there are H digits d + */ + public final int MAX_CHARS = H + 7; + + // Numerical results are created here... + private final byte[] bytes = new byte[MAX_CHARS]; + + // ... and copied here in appendTo() + private final char[] chars = new char[MAX_CHARS]; + + // Index into bytes of rightmost valid character. + private int index; + + private DoubleToDecimal() { + } + + /** + * Returns a string rendering of the {@code double} argument. + * + *

The characters of the result are all drawn from the ASCII set. + *

    + *
  • Any NaN, whether quiet or signaling, is rendered as + * {@code "NaN"}, regardless of the sign bit. + *
  • The infinities +∞ and -∞ are rendered as + * {@code "Infinity"} and {@code "-Infinity"}, respectively. + *
  • The positive and negative zeroes are rendered as + * {@code "0.0"} and {@code "-0.0"}, respectively. + *
  • A finite negative {@code v} is rendered as the sign + * '{@code -}' followed by the rendering of the magnitude -{@code v}. + *
  • A finite positive {@code v} is rendered in two stages: + *
      + *
    • Selection of a decimal: A well-defined + * decimal dv is selected + * to represent {@code v}. + *
    • Formatting as a string: The decimal + * dv is formatted as a string, + * either in plain or in computerized scientific notation, + * depending on its value. + *
    + *
+ * + *

A decimal is a number of the form + * d×10i + * for some (unique) integers d > 0 and i such that + * d is not a multiple of 10. + * These integers are the significand and + * the exponent, respectively, of the decimal. + * The length of the decimal is the (unique) + * integer n meeting + * 10n-1d < 10n. + * + *

The decimal dv + * for a finite positive {@code v} is defined as follows: + *

    + *
  • Let R be the set of all decimals that round to {@code v} + * according to the usual round-to-closest rule of + * IEEE 754 floating-point arithmetic. + *
  • Let m be the minimal length over all decimals in R. + *
  • When m ≥ 2, let T be the set of all decimals + * in R with length m. + * Otherwise, let T be the set of all decimals + * in R with length 1 or 2. + *
  • Define dv as + * the decimal in T that is closest to {@code v}. + * Or if there are two such decimals in T, + * select the one with the even significand (there is exactly one). + *
+ * + *

The (uniquely) selected decimal dv + * is then formatted. + * + *

Let d, i and n be the significand, exponent and + * length of dv, respectively. + * Further, let e = n + i - 1 and let + * d1dn + * be the usual decimal expansion of the significand. + * Note that d1 ≠ 0 ≠ dn. + *

    + *
  • Case -3 ≤ e < 0: + * dv is formatted as + * 0.00d1dn, + * where there are exactly -(n + i) zeroes between + * the decimal point and d1. + * For example, 123 × 10-4 is formatted as + * {@code 0.0123}. + *
  • Case 0 ≤ e < 7: + *
      + *
    • Subcase i ≥ 0: + * dv is formatted as + * d1dn00.0, + * where there are exactly i zeroes + * between dn and the decimal point. + * For example, 123 × 102 is formatted as + * {@code 12300.0}. + *
    • Subcase i < 0: + * dv is formatted as + * d1dn+i.dn+i+1dn. + * There are exactly -i digits to the right of + * the decimal point. + * For example, 123 × 10-1 is formatted as + * {@code 12.3}. + *
    + *
  • Case e < -3 or e ≥ 7: + * computerized scientific notation is used to format + * dv. + * Here e is formatted as by {@link Integer#toString(int)}. + *
      + *
    • Subcase n = 1: + * dv is formatted as + * d1.0Ee. + * For example, 1 × 1023 is formatted as + * {@code 1.0E23}. + *
    • Subcase n > 1: + * dv is formatted as + * d1.d2dnEe. + * For example, 123 × 10-21 is formatted as + * {@code 1.23E-19}. + *
    + *
+ * + * @param v the {@code double} to be rendered. + * @return a string rendering of the argument. + */ + public static String toString(double v) { + return threadLocalInstance().toDecimalString(v); + } + + /** + * Appends the rendering of the {@code v} to {@code app}. + * + *

The outcome is the same as if {@code v} were first + * {@link #toString(double) rendered} and the resulting string were then + * {@link Appendable#append(CharSequence) appended} to {@code app}. + * + * @param v the {@code double} whose rendering is appended. + * @param app the {@link Appendable} to append to. + * @throws IOException If an I/O error occurs + */ + public static Appendable appendTo(double v, Appendable app) + throws IOException { + return threadLocalInstance().appendDecimalTo(v, app); + } + + private static DoubleToDecimal threadLocalInstance() { + return threadLocal.get(); + } + + private String toDecimalString(double v) { + switch (toDecimal(v)) { + case NON_SPECIAL: return charsToString(); + case PLUS_ZERO: return "0.0"; + case MINUS_ZERO: return "-0.0"; + case PLUS_INF: return "Infinity"; + case MINUS_INF: return "-Infinity"; + default: return "NaN"; + } + } + + private Appendable appendDecimalTo(double v, Appendable app) + throws IOException { + switch (toDecimal(v)) { + case NON_SPECIAL: + for (int i = 0; i <= index; ++i) { + chars[i] = (char) bytes[i]; + } + if (app instanceof StringBuilder) { + return ((StringBuilder) app).append(chars, 0, index + 1); + } + if (app instanceof StringBuffer) { + return ((StringBuffer) app).append(chars, 0, index + 1); + } + for (int i = 0; i <= index; ++i) { + app.append(chars[i]); + } + return app; + case PLUS_ZERO: return app.append("0.0"); + case MINUS_ZERO: return app.append("-0.0"); + case PLUS_INF: return app.append("Infinity"); + case MINUS_INF: return app.append("-Infinity"); + default: return app.append("NaN"); + } + } + + /* + Returns + PLUS_ZERO iff v is 0.0 + MINUS_ZERO iff v is -0.0 + PLUS_INF iff v is POSITIVE_INFINITY + MINUS_INF iff v is NEGATIVE_INFINITY + NAN iff v is NaN + */ + private int toDecimal(double v) { + /* + For full details see references [2] and [1]. + + For finite v != 0, determine integers c and q such that + |v| = c 2^q and + Q_MIN <= q <= Q_MAX and + either 2^(P-1) <= c < 2^P (normal) + or 0 < c < 2^(P-1) and q = Q_MIN (subnormal) + */ + long bits = doubleToRawLongBits(v); + long t = bits & T_MASK; + int bq = (int) (bits >>> P - 1) & BQ_MASK; + if (bq < BQ_MASK) { + index = -1; + if (bits < 0) { + append('-'); + } + if (bq != 0) { + // normal value. Here mq = -q + int mq = -Q_MIN + 1 - bq; + long c = C_MIN | t; + // The fast path discussed in section 8.2 of [1]. + if (0 < mq & mq < P) { + long f = c >> mq; + if (f << mq == c) { + return toChars(f, 0); + } + } + return toDecimal(-mq, c, 0); + } + if (t != 0) { + // subnormal value + return t < C_TINY + ? toDecimal(Q_MIN, 10 * t, -1) + : toDecimal(Q_MIN, t, 0); + } + return bits == 0 ? PLUS_ZERO : MINUS_ZERO; + } + if (t != 0) { + return NAN; + } + return bits > 0 ? PLUS_INF : MINUS_INF; + } + + private int toDecimal(int q, long c, int dk) { + /* + The skeleton corresponds to figure 4 of [1]. + The efficient computations are those summarized in figure 7. + + Here's a correspondence between Java names and names in [1], + expressed as approximate LaTeX source code and informally. + Other names are identical. + cb: \bar{c} "c-bar" + cbr: \bar{c}_r "c-bar-r" + cbl: \bar{c}_l "c-bar-l" + + vb: \bar{v} "v-bar" + vbr: \bar{v}_r "v-bar-r" + vbl: \bar{v}_l "v-bar-l" + + rop: r_o' "r-o-prime" + */ + int out = (int) c & 0x1; + long cb = c << 2; + long cbr = cb + 2; + long cbl; + int k; + /* + flog10pow2(e) = floor(log_10(2^e)) + flog10threeQuartersPow2(e) = floor(log_10(3/4 2^e)) + flog2pow10(e) = floor(log_2(10^e)) + */ + if (c != C_MIN | q == Q_MIN) { + // regular spacing + cbl = cb - 2; + k = flog10pow2(q); + } else { + // irregular spacing + cbl = cb - 1; + k = flog10threeQuartersPow2(q); + } + int h = q + flog2pow10(-k) + 2; + + // g1 and g0 are as in section 9.9.3 of [1], so g = g1 2^63 + g0 + long g1 = g1(k); + long g0 = g0(k); + + long vb = rop(g1, g0, cb << h); + long vbl = rop(g1, g0, cbl << h); + long vbr = rop(g1, g0, cbr << h); + + long s = vb >> 2; + if (s >= 100) { + /* + For n = 17, m = 1 the table in section 10 of [1] shows + s' = floor(s / 10) = floor(s 115_292_150_460_684_698 / 2^60) + = floor(s 115_292_150_460_684_698 2^4 / 2^64) + + sp10 = 10 s' + tp10 = 10 t' + upin iff u' = sp10 10^k in Rv + wpin iff w' = tp10 10^k in Rv + See section 9.4 of [1]. + */ + long sp10 = 10 * multiplyHigh(s, 115_292_150_460_684_698L << 4); + long tp10 = sp10 + 10; + boolean upin = vbl + out <= sp10 << 2; + boolean wpin = (tp10 << 2) + out <= vbr; + if (upin != wpin) { + return toChars(upin ? sp10 : tp10, k); + } + } + + /* + 10 <= s < 100 or s >= 100 and u', w' not in Rv + uin iff u = s 10^k in Rv + win iff w = t 10^k in Rv + See section 9.4 of [1]. + */ + long t = s + 1; + boolean uin = vbl + out <= s << 2; + boolean win = (t << 2) + out <= vbr; + if (uin != win) { + // Exactly one of u or w lies in Rv. + return toChars(uin ? s : t, k + dk); + } + /* + Both u and w lie in Rv: determine the one closest to v. + See section 9.4 of [1]. + */ + long cmp = vb - (s + t << 1); + return toChars(cmp < 0 || cmp == 0 && (s & 0x1) == 0 ? s : t, k + dk); + } + + /* + Computes rop(cp g 2^(-127)), where g = g1 2^63 + g0 + See section 9.10 and figure 5 of [1]. + */ + private static long rop(long g1, long g0, long cp) { + long x1 = multiplyHigh(g0, cp); + long y0 = g1 * cp; + long y1 = multiplyHigh(g1, cp); + long z = (y0 >>> 1) + x1; + long vbp = y1 + (z >>> 63); + return vbp | (z & MASK_63) + MASK_63 >>> 63; + } + + /* + Formats the decimal f 10^e. + */ + private int toChars(long f, int e) { + /* + For details not discussed here see section 10 of [1]. + + Determine len such that + 10^(len-1) <= f < 10^len + */ + int len = flog10pow2(Long.SIZE - numberOfLeadingZeros(f)); + if (f >= pow10(len)) { + len += 1; + } + + /* + Let fp and ep be the original f and e, respectively. + Transform f and e to ensure + 10^(H-1) <= f < 10^H + fp 10^ep = f 10^(e-H) = 0.f 10^e + */ + f *= pow10(H - len); + e += len; + + /* + The toChars?() methods perform left-to-right digits extraction + using ints, provided that the arguments are limited to 8 digits. + Therefore, split the H = 17 digits of f into: + h = the most significant digit of f + m = the next 8 most significant digits of f + l = the last 8, least significant digits of f + + For n = 17, m = 8 the table in section 10 of [1] shows + floor(f / 10^8) = floor(193_428_131_138_340_668 f / 2^84) = + floor(floor(193_428_131_138_340_668 f / 2^64) / 2^20) + and for n = 9, m = 8 + floor(hm / 10^8) = floor(1_441_151_881 hm / 2^57) + */ + long hm = multiplyHigh(f, 193_428_131_138_340_668L) >>> 20; + int l = (int) (f - 100_000_000L * hm); + int h = (int) (hm * 1_441_151_881L >>> 57); + int m = (int) (hm - 100_000_000 * h); + + if (0 < e && e <= 7) { + return toChars1(h, m, l, e); + } + if (-3 < e && e <= 0) { + return toChars2(h, m, l, e); + } + return toChars3(h, m, l, e); + } + + private int toChars1(int h, int m, int l, int e) { + /* + 0 < e <= 7: plain format without leading zeroes. + Left-to-right digits extraction: + algorithm 1 in [3], with b = 10, k = 8, n = 28. + */ + appendDigit(h); + int y = y(m); + int t; + int i = 1; + for (; i < e; ++i) { + t = 10 * y; + appendDigit(t >>> 28); + y = t & MASK_28; + } + append('.'); + for (; i <= 8; ++i) { + t = 10 * y; + appendDigit(t >>> 28); + y = t & MASK_28; + } + lowDigits(l); + return NON_SPECIAL; + } + + private int toChars2(int h, int m, int l, int e) { + // -3 < e <= 0: plain format with leading zeroes. + appendDigit(0); + append('.'); + for (; e < 0; ++e) { + appendDigit(0); + } + appendDigit(h); + append8Digits(m); + lowDigits(l); + return NON_SPECIAL; + } + + private int toChars3(int h, int m, int l, int e) { + // -3 >= e | e > 7: computerized scientific notation + appendDigit(h); + append('.'); + append8Digits(m); + lowDigits(l); + exponent(e - 1); + return NON_SPECIAL; + } + + private void lowDigits(int l) { + if (l != 0) { + append8Digits(l); + } + removeTrailingZeroes(); + } + + private void append8Digits(int m) { + /* + Left-to-right digits extraction: + algorithm 1 in [3], with b = 10, k = 8, n = 28. + */ + int y = y(m); + for (int i = 0; i < 8; ++i) { + int t = 10 * y; + appendDigit(t >>> 28); + y = t & MASK_28; + } + } + + private void removeTrailingZeroes() { + while (bytes[index] == '0') { + --index; + } + // ... but do not remove the one directly to the right of '.' + if (bytes[index] == '.') { + ++index; + } + } + + private int y(int a) { + /* + Algorithm 1 in [3] needs computation of + floor((a + 1) 2^n / b^k) - 1 + with a < 10^8, b = 10, k = 8, n = 28. + Noting that + (a + 1) 2^n <= 10^8 2^28 < 10^17 + For n = 17, m = 8 the table in section 10 of [1] leads to: + */ + return (int) (multiplyHigh( + (long) (a + 1) << 28, + 193_428_131_138_340_668L) >>> 20) - 1; + } + + private void exponent(int e) { + append('E'); + if (e < 0) { + append('-'); + e = -e; + } + if (e < 10) { + appendDigit(e); + return; + } + int d; + if (e >= 100) { + /* + For n = 3, m = 2 the table in section 10 of [1] shows + floor(e / 100) = floor(1_311 e / 2^17) + */ + d = e * 1_311 >>> 17; + appendDigit(d); + e -= 100 * d; + } + /* + For n = 2, m = 1 the table in section 10 of [1] shows + floor(e / 10) = floor(103 e / 2^10) + */ + d = e * 103 >>> 10; + appendDigit(d); + appendDigit(e - 10 * d); + } + + private void append(int c) { + bytes[++index] = (byte) c; + } + + private void appendDigit(int d) { + bytes[++index] = (byte) ('0' + d); + } + + // Using the deprecated constructor enhances performance. + @SuppressWarnings("deprecation") + private String charsToString() { + return new String(bytes, 0, 0, index + 1); + } + +} diff --git a/src/main/java/com/fasterxml/jackson/core/io/schubfach/FloatToDecimal.java b/src/main/java/com/fasterxml/jackson/core/io/schubfach/FloatToDecimal.java new file mode 100644 index 0000000000..56d2c7c235 --- /dev/null +++ b/src/main/java/com/fasterxml/jackson/core/io/schubfach/FloatToDecimal.java @@ -0,0 +1,622 @@ +/* + * Copyright 2018-2020 Raffaello Giulietti + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.fasterxml.jackson.core.io.schubfach; + +import java.io.IOException; + +import static com.fasterxml.jackson.core.io.schubfach.MathUtils.flog10pow2; +import static com.fasterxml.jackson.core.io.schubfach.MathUtils.flog10threeQuartersPow2; +import static com.fasterxml.jackson.core.io.schubfach.MathUtils.flog2pow10; +import static com.fasterxml.jackson.core.io.schubfach.MathUtils.g1; +import static com.fasterxml.jackson.core.io.schubfach.MathUtils.multiplyHigh; +import static com.fasterxml.jackson.core.io.schubfach.MathUtils.pow10; +import static java.lang.Float.floatToRawIntBits; +import static java.lang.Integer.numberOfLeadingZeros; + +/** + * This class exposes a method to render a {@code float} as a string. + * + * @author Raffaello Giulietti + */ +final public class FloatToDecimal { + /* + For full details about this code see the following references: + + [1] Giulietti, "The Schubfach way to render doubles", + https://drive.google.com/open?id=1luHhyQF9zKlM8yJ1nebU0OgVYhfC6CBN + + [2] IEEE Computer Society, "IEEE Standard for Floating-Point Arithmetic" + + [3] Bouvier & Zimmermann, "Division-Free Binary-to-Decimal Conversion" + + Divisions are avoided altogether for the benefit of those architectures + that do not provide specific machine instructions or where they are slow. + This is discussed in section 10 of [1]. + */ + + // The precision in bits. + static final int P = 24; + + // Exponent width in bits. + private static final int W = (Float.SIZE - 1) - (P - 1); + + // Minimum value of the exponent: -(2^(W-1)) - P + 3. + static final int Q_MIN = (-1 << W - 1) - P + 3; + + // Maximum value of the exponent: 2^(W-1) - P. + static final int Q_MAX = (1 << W - 1) - P; + + // 10^(E_MIN - 1) <= MIN_VALUE < 10^E_MIN + static final int E_MIN = -44; + + // 10^(E_MAX - 1) <= MAX_VALUE < 10^E_MAX + static final int E_MAX = 39; + + // Threshold to detect tiny values, as in section 8.1.1 of [1] + static final int C_TINY = 8; + + // The minimum and maximum k, as in section 8 of [1] + static final int K_MIN = -45; + static final int K_MAX = 31; + + // H is as in section 8 of [1]. + static final int H = 9; + + // Minimum value of the significand of a normal value: 2^(P-1). + private static final int C_MIN = 1 << P - 1; + + // Mask to extract the biased exponent. + private static final int BQ_MASK = (1 << W) - 1; + + // Mask to extract the fraction bits. + private static final int T_MASK = (1 << P - 1) - 1; + + // Used in rop(). + private static final long MASK_32 = (1L << 32) - 1; + + // Used for left-to-tight digit extraction. + private static final int MASK_28 = (1 << 28) - 1; + + private static final int NON_SPECIAL = 0; + private static final int PLUS_ZERO = 1; + private static final int MINUS_ZERO = 2; + private static final int PLUS_INF = 3; + private static final int MINUS_INF = 4; + private static final int NAN = 5; + + // For thread-safety, each thread gets its own instance of this class. + private static final ThreadLocal threadLocal = + ThreadLocal.withInitial(FloatToDecimal::new); + + /* + Room for the longer of the forms + -ddddd.dddd H + 2 characters + -0.00ddddddddd H + 5 characters + -d.ddddddddE-ee H + 6 characters + where there are H digits d + */ + public final int MAX_CHARS = H + 6; + + // Numerical results are created here... + private final byte[] bytes = new byte[MAX_CHARS]; + + // ... and copied here in appendTo() + private final char[] chars = new char[MAX_CHARS]; + + // Index into buf of rightmost valid character. + private int index; + + private FloatToDecimal() { + } + + /** + * Returns a string rendering of the {@code float} argument. + * + *

The characters of the result are all drawn from the ASCII set. + *

    + *
  • Any NaN, whether quiet or signaling, is rendered as + * {@code "NaN"}, regardless of the sign bit. + *
  • The infinities +∞ and -∞ are rendered as + * {@code "Infinity"} and {@code "-Infinity"}, respectively. + *
  • The positive and negative zeroes are rendered as + * {@code "0.0"} and {@code "-0.0"}, respectively. + *
  • A finite negative {@code v} is rendered as the sign + * '{@code -}' followed by the rendering of the magnitude -{@code v}. + *
  • A finite positive {@code v} is rendered in two stages: + *
      + *
    • Selection of a decimal: A well-defined + * decimal dv is selected + * to represent {@code v}. + *
    • Formatting as a string: The decimal + * dv is formatted as a string, + * either in plain or in computerized scientific notation, + * depending on its value. + *
    + *
+ * + *

A decimal is a number of the form + * d×10i + * for some (unique) integers d > 0 and i such that + * d is not a multiple of 10. + * These integers are the significand and + * the exponent, respectively, of the decimal. + * The length of the decimal is the (unique) + * integer n meeting + * 10n-1d < 10n. + * + *

The decimal dv + * for a finite positive {@code v} is defined as follows: + *

    + *
  • Let R be the set of all decimals that round to {@code v} + * according to the usual round-to-closest rule of + * IEEE 754 floating-point arithmetic. + *
  • Let m be the minimal length over all decimals in R. + *
  • When m ≥ 2, let T be the set of all decimals + * in R with length m. + * Otherwise, let T be the set of all decimals + * in R with length 1 or 2. + *
  • Define dv as + * the decimal in T that is closest to {@code v}. + * Or if there are two such decimals in T, + * select the one with the even significand (there is exactly one). + *
+ * + *

The (uniquely) selected decimal dv + * is then formatted. + * + *

Let d, i and n be the significand, exponent and + * length of dv, respectively. + * Further, let e = n + i - 1 and let + * d1dn + * be the usual decimal expansion of the significand. + * Note that d1 ≠ 0 ≠ dn. + *

    + *
  • Case -3 ≤ e < 0: + * dv is formatted as + * 0.00d1dn, + * where there are exactly -(n + i) zeroes between + * the decimal point and d1. + * For example, 123 × 10-4 is formatted as + * {@code 0.0123}. + *
  • Case 0 ≤ e < 7: + *
      + *
    • Subcase i ≥ 0: + * dv is formatted as + * d1dn00.0, + * where there are exactly i zeroes + * between dn and the decimal point. + * For example, 123 × 102 is formatted as + * {@code 12300.0}. + *
    • Subcase i < 0: + * dv is formatted as + * d1dn+i.dn+i+1dn. + * There are exactly -i digits to the right of + * the decimal point. + * For example, 123 × 10-1 is formatted as + * {@code 12.3}. + *
    + *
  • Case e < -3 or e ≥ 7: + * computerized scientific notation is used to format + * dv. + * Here e is formatted as by {@link Integer#toString(int)}. + *
      + *
    • Subcase n = 1: + * dv is formatted as + * d1.0Ee. + * For example, 1 × 1023 is formatted as + * {@code 1.0E23}. + *
    • Subcase n > 1: + * dv is formatted as + * d1.d2dnEe. + * For example, 123 × 10-21 is formatted as + * {@code 1.23E-19}. + *
    + *
+ * + * @param v the {@code float} to be rendered. + * @return a string rendering of the argument. + */ + public static String toString(float v) { + return threadLocalInstance().toDecimalString(v); + } + + /** + * Appends the rendering of the {@code v} to {@code app}. + * + *

The outcome is the same as if {@code v} were first + * {@link #toString(float) rendered} and the resulting string were then + * {@link Appendable#append(CharSequence) appended} to {@code app}. + * + * @param v the {@code float} whose rendering is appended. + * @param app the {@link Appendable} to append to. + * @throws IOException If an I/O error occurs + */ + public static Appendable appendTo(float v, Appendable app) + throws IOException { + return threadLocalInstance().appendDecimalTo(v, app); + } + + private static FloatToDecimal threadLocalInstance() { + return threadLocal.get(); + } + + private String toDecimalString(float v) { + switch (toDecimal(v)) { + case NON_SPECIAL: return charsToString(); + case PLUS_ZERO: return "0.0"; + case MINUS_ZERO: return "-0.0"; + case PLUS_INF: return "Infinity"; + case MINUS_INF: return "-Infinity"; + default: return "NaN"; + } + } + + private Appendable appendDecimalTo(float v, Appendable app) + throws IOException { + switch (toDecimal(v)) { + case NON_SPECIAL: + for (int i = 0; i <= index; ++i) { + chars[i] = (char) bytes[i]; + } + if (app instanceof StringBuilder) { + return ((StringBuilder) app).append(chars, 0, index + 1); + } + if (app instanceof StringBuffer) { + return ((StringBuffer) app).append(chars, 0, index + 1); + } + for (int i = 0; i <= index; ++i) { + app.append(chars[i]); + } + return app; + case PLUS_ZERO: return app.append("0.0"); + case MINUS_ZERO: return app.append("-0.0"); + case PLUS_INF: return app.append("Infinity"); + case MINUS_INF: return app.append("-Infinity"); + default: return app.append("NaN"); + } + } + + /* + Returns + PLUS_ZERO iff v is 0.0 + MINUS_ZERO iff v is -0.0 + PLUS_INF iff v is POSITIVE_INFINITY + MINUS_INF iff v is NEGATIVE_INFINITY + NAN iff v is NaN + */ + private int toDecimal(float v) { + /* + For full details see references [2] and [1]. + + For finite v != 0, determine integers c and q such that + |v| = c 2^q and + Q_MIN <= q <= Q_MAX and + either 2^(P-1) <= c < 2^P (normal) + or 0 < c < 2^(P-1) and q = Q_MIN (subnormal) + */ + int bits = floatToRawIntBits(v); + int t = bits & T_MASK; + int bq = (bits >>> P - 1) & BQ_MASK; + if (bq < BQ_MASK) { + index = -1; + if (bits < 0) { + append('-'); + } + if (bq != 0) { + // normal value. Here mq = -q + int mq = -Q_MIN + 1 - bq; + int c = C_MIN | t; + // The fast path discussed in section 8.2 of [1]. + if (0 < mq & mq < P) { + int f = c >> mq; + if (f << mq == c) { + return toChars(f, 0); + } + } + return toDecimal(-mq, c, 0); + } + if (t != 0) { + // subnormal value + return t < C_TINY + ? toDecimal(Q_MIN, 10 * t, -1) + : toDecimal(Q_MIN, t, 0); + } + return bits == 0 ? PLUS_ZERO : MINUS_ZERO; + } + if (t != 0) { + return NAN; + } + return bits > 0 ? PLUS_INF : MINUS_INF; + } + + private int toDecimal(int q, int c, int dk) { + /* + The skeleton corresponds to figure 4 of [1]. + The efficient computations are those summarized in figure 7. + Also check the appendix. + + Here's a correspondence between Java names and names in [1], + expressed as approximate LaTeX source code and informally. + Other names are identical. + cb: \bar{c} "c-bar" + cbr: \bar{c}_r "c-bar-r" + cbl: \bar{c}_l "c-bar-l" + + vb: \bar{v} "v-bar" + vbr: \bar{v}_r "v-bar-r" + vbl: \bar{v}_l "v-bar-l" + + rop: r_o' "r-o-prime" + */ + int out = c & 0x1; + long cb = c << 2; + long cbr = cb + 2; + long cbl; + int k; + /* + flog10pow2(e) = floor(log_10(2^e)) + flog10threeQuartersPow2(e) = floor(log_10(3/4 2^e)) + flog2pow10(e) = floor(log_2(10^e)) + */ + if (c != C_MIN | q == Q_MIN) { + // regular spacing + cbl = cb - 2; + k = flog10pow2(q); + } else { + // irregular spacing0 + cbl = cb - 1; + k = flog10threeQuartersPow2(q); + } + int h = q + flog2pow10(-k) + 33; + + // g is as in the appendix + long g = g1(k) + 1; + + int vb = rop(g, cb << h); + int vbl = rop(g, cbl << h); + int vbr = rop(g, cbr << h); + + int s = vb >> 2; + if (s >= 100) { + /* + For n = 9, m = 1 the table in section 10 of [1] shows + s' = floor(s / 10) = floor(s 1_717_986_919 / 2^34) + + sp10 = 10 s' + tp10 = 10 t' + upin iff u' = sp10 10^k in Rv + wpin iff w' = tp10 10^k in Rv + See section 9.4 of [1]. + */ + int sp10 = 10 * (int) (s * 1_717_986_919L >>> 34); + int tp10 = sp10 + 10; + boolean upin = vbl + out <= sp10 << 2; + boolean wpin = (tp10 << 2) + out <= vbr; + if (upin != wpin) { + return toChars(upin ? sp10 : tp10, k); + } + } + + /* + 10 <= s < 100 or s >= 100 and u', w' not in Rv + uin iff u = s 10^k in Rv + win iff w = t 10^k in Rv + See section 9.4 of [1]. + */ + int t = s + 1; + boolean uin = vbl + out <= s << 2; + boolean win = (t << 2) + out <= vbr; + if (uin != win) { + // Exactly one of u or w lies in Rv. + return toChars(uin ? s : t, k + dk); + } + /* + Both u and w lie in Rv: determine the one closest to v. + See section 9.4 of [1]. + */ + int cmp = vb - (s + t << 1); + return toChars(cmp < 0 || cmp == 0 && (s & 0x1) == 0 ? s : t, k + dk); + } + + /* + Computes rop(cp g 2^(-95)) + See appendix and figure 8 of [1]. + */ + private static int rop(long g, long cp) { + long x1 = multiplyHigh(g, cp); + long vbp = x1 >>> 31; + return (int) (vbp | (x1 & MASK_32) + MASK_32 >>> 32); + } + + /* + Formats the decimal f 10^e. + */ + private int toChars(int f, int e) { + /* + For details not discussed here see section 10 of [1]. + + Determine len such that + 10^(len-1) <= f < 10^len + */ + int len = flog10pow2(Integer.SIZE - numberOfLeadingZeros(f)); + if (f >= pow10(len)) { + len += 1; + } + + /* + Let fp and ep be the original f and e, respectively. + Transform f and e to ensure + 10^(H-1) <= f < 10^H + fp 10^ep = f 10^(e-H) = 0.f 10^e + */ + f *= pow10(H - len); + e += len; + + /* + The toChars?() methods perform left-to-right digits extraction + using ints, provided that the arguments are limited to 8 digits. + Therefore, split the H = 9 digits of f into: + h = the most significant digit of f + l = the last 8, least significant digits of f + + For n = 9, m = 8 the table in section 10 of [1] shows + floor(f / 10^8) = floor(1_441_151_881 f / 2^57) + */ + int h = (int) (f * 1_441_151_881L >>> 57); + int l = f - 100_000_000 * h; + + if (0 < e && e <= 7) { + return toChars1(h, l, e); + } + if (-3 < e && e <= 0) { + return toChars2(h, l, e); + } + return toChars3(h, l, e); + } + + private int toChars1(int h, int l, int e) { + /* + 0 < e <= 7: plain format without leading zeroes. + Left-to-right digits extraction: + algorithm 1 in [3], with b = 10, k = 8, n = 28. + */ + appendDigit(h); + int y = y(l); + int t; + int i = 1; + for (; i < e; ++i) { + t = 10 * y; + appendDigit(t >>> 28); + y = t & MASK_28; + } + append('.'); + for (; i <= 8; ++i) { + t = 10 * y; + appendDigit(t >>> 28); + y = t & MASK_28; + } + removeTrailingZeroes(); + return NON_SPECIAL; + } + + private int toChars2(int h, int l, int e) { + // -3 < e <= 0: plain format with leading zeroes. + appendDigit(0); + append('.'); + for (; e < 0; ++e) { + appendDigit(0); + } + appendDigit(h); + append8Digits(l); + removeTrailingZeroes(); + return NON_SPECIAL; + } + + private int toChars3(int h, int l, int e) { + // -3 >= e | e > 7: computerized scientific notation + appendDigit(h); + append('.'); + append8Digits(l); + removeTrailingZeroes(); + exponent(e - 1); + return NON_SPECIAL; + } + + private void append8Digits(int m) { + /* + Left-to-right digits extraction: + algorithm 1 in [3], with b = 10, k = 8, n = 28. + */ + int y = y(m); + for (int i = 0; i < 8; ++i) { + int t = 10 * y; + appendDigit(t >>> 28); + y = t & MASK_28; + } + } + + private void removeTrailingZeroes() { + while (bytes[index] == '0') { + --index; + } + // ... but do not remove the one directly to the right of '.' + if (bytes[index] == '.') { + ++index; + } + } + + private int y(int a) { + /* + Algorithm 1 in [3] needs computation of + floor((a + 1) 2^n / b^k) - 1 + with a < 10^8, b = 10, k = 8, n = 28. + Noting that + (a + 1) 2^n <= 10^8 2^28 < 10^17 + For n = 17, m = 8 the table in section 10 of [1] leads to: + */ + return (int) (multiplyHigh( + (long) (a + 1) << 28, + 193_428_131_138_340_668L) >>> 20) - 1; + } + + private void exponent(int e) { + append('E'); + if (e < 0) { + append('-'); + e = -e; + } + if (e < 10) { + appendDigit(e); + return; + } + /* + For n = 2, m = 1 the table in section 10 of [1] shows + floor(e / 10) = floor(103 e / 2^10) + */ + int d = e * 103 >>> 10; + appendDigit(d); + appendDigit(e - 10 * d); + } + + private void append(int c) { + bytes[++index] = (byte) c; + } + + private void appendDigit(int d) { + bytes[++index] = (byte) ('0' + d); + } + + // Using the deprecated constructor enhances performance. + @SuppressWarnings("deprecation") + private String charsToString() { + return new String(bytes, 0, 0, index + 1); + } + +} + diff --git a/src/main/java/com/fasterxml/jackson/core/io/schubfach/MathUtils.java b/src/main/java/com/fasterxml/jackson/core/io/schubfach/MathUtils.java new file mode 100644 index 0000000000..3ef0410b2a --- /dev/null +++ b/src/main/java/com/fasterxml/jackson/core/io/schubfach/MathUtils.java @@ -0,0 +1,823 @@ +/* + * Copyright 2018-2020 Raffaello Giulietti + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.fasterxml.jackson.core.io.schubfach; + +/** + * This class exposes package private utilities for other classes. + * Thus, all methods are assumed to be invoked with correct arguments, + * so these are not checked at all. + * + * @author Raffaello Giulietti + */ +final class MathUtils { + /* + For full details about this code see the following reference: + + Giulietti, "The Schubfach way to render doubles", + https://drive.google.com/open?id=1luHhyQF9zKlM8yJ1nebU0OgVYhfC6CBN + */ + + /* + The boundaries for k in g0(int) and g1(int). + K_MIN must be DoubleToDecimal.K_MIN or less. + K_MAX must be DoubleToDecimal.K_MAX or more. + */ + static final int K_MIN = -324; + static final int K_MAX = 292; + + // Must be DoubleToDecimal.H or more + static final int H = 17; + + // C_10 = floor(log10(2) * 2^Q_10), A_10 = floor(log10(3/4) * 2^Q_10) + private static final int Q_10 = 41; + private static final long C_10 = 661_971_961_083L; + private static final long A_10 = -274_743_187_321L; + + // C_2 = floor(log2(10) * 2^Q_2) + private static final int Q_2 = 38; + private static final long C_2 = 913_124_641_741L; + + private MathUtils() { + } + + // The first powers of 10. The last entry must be 10^H. + private static final long[] pow10 = { + 1L, + 10L, + 100L, + 1_000L, + 10_000L, + 100_000L, + 1_000_000L, + 10_000_000L, + 100_000_000L, + 1_000_000_000L, + 10_000_000_000L, + 100_000_000_000L, + 1_000_000_000_000L, + 10_000_000_000_000L, + 100_000_000_000_000L, + 1_000_000_000_000_000L, + 10_000_000_000_000_000L, + 100_000_000_000_000_000L, + }; + + /** + * Returns 10{@code e}. + * + * @param e The exponent which must meet + * 0 ≤ {@code e} ≤ {@link #H}. + * @return 10{@code e}. + */ + static long pow10(int e) { + return pow10[e]; + } + + /** + * Returns the unique integer k such that + * 10k ≤ 2{@code e} + * < 10k+1. + *

+ * The result is correct when |{@code e}| ≤ 5_456_721. + * Otherwise the result is undefined. + * + * @param e The exponent of 2, which should meet + * |{@code e}| ≤ 5_456_721 for safe results. + * @return ⌊log102{@code e}⌋. + */ + static int flog10pow2(int e) { + return (int) (e * C_10 >> Q_10); + } + + /** + * Returns the unique integer k such that + * 10k ≤ 3/4 · 2{@code e} + * < 10k+1. + *

+ * The result is correct when + * -2_956_395 ≤ {@code e} ≤ 2_500_325. + * Otherwise the result is undefined. + * + * @param e The exponent of 2, which should meet + * -2_956_395 ≤ {@code e} ≤ 2_500_325 for safe results. + * @return ⌊log10(3/4 · + * 2{@code e})⌋. + */ + static int flog10threeQuartersPow2(int e) { + return (int) (e * C_10 + A_10 >> Q_10); + } + + /** + * Returns the unique integer k such that + * 2k ≤ 10{@code e} + * < 2k+1. + *

+ * The result is correct when |{@code e}| ≤ 1_838_394. + * Otherwise the result is undefined. + * + * @param e The exponent of 10, which should meet + * |{@code e}| ≤ 1_838_394 for safe results. + * @return ⌊log210{@code e}⌋. + */ + static int flog2pow10(int e) { + return (int) (e * C_2 >> Q_2); + } + + /** + * Let 10-{@code k} = β 2r, + * for the unique pair of integer r and real β meeting + * 2125β < 2126. + * Further, let g = ⌊β⌋ + 1. + * Split g into the higher 63 bits g1 and + * the lower 63 bits g0. Thus, + * g1 = + * ⌊g 2-63⌋ + * and + * g0 = + * g - g1 263. + *

+ * This method returns g1 while + * {@link #g0(int)} returns g0. + *

+ * If needed, the exponent r can be computed as + * r = {@code flog2pow10(-k)} - 125 (see {@link #flog2pow10(int)}). + * + * @param k The exponent of 10, which must meet + * {@link #K_MIN} ≤ {@code e} ≤ {@link #K_MAX}. + * @return g1 as described above. + */ + static long g1(int k) { + return g[k - K_MIN << 1]; + } + + /** + * Returns g0 as described in + * {@link #g1(int)}. + * + * @param k The exponent of 10, which must meet + * {@link #K_MIN} ≤ {@code e} ≤ {@link #K_MAX}. + * @return g0 as described in + * {@link #g1(int)}. + */ + static long g0(int k) { + return g[k - K_MIN << 1 | 1]; + } + + //a Java port of https://github.com/plokhotnyuk/jsoniter-scala/blob/c70a293ac802dc2eb44165471d76d7df2d4657b6/jsoniter-scala-core/native/src/main/scala/com/github/plokhotnyuk/jsoniter_scala/core/JsonWriter.scala#L2027 + static long multiplyHigh(long x, long y) { + // Karatsuba technique for two positive ints + long x2 = x & 0xFFFFFFFFL; + long y2 = y & 0xFFFFFFFFL; + long b = x2 * y2; + long x1 = x >>> 32; + long y1 = y >>> 32; + long a = x1 * y1; + return (((b >>> 32) + (x1 + x2) * (y1 + y2) - b - a) >>> 32) + a; + } + + /* + The precomputed values for g1(int) and g0(int). + The first entry must be for an exponent of K_MIN or less. + The last entry must be for an exponent of K_MAX or more. + */ + private static final long[] g = { + /* -324 */ 0x4F0C_EDC9_5A71_8DD4L, 0x5B01_E8B0_9AA0_D1B5L, + /* -323 */ 0x7E7B_160E_F71C_1621L, 0x119C_A780_F767_B5EEL, + /* -322 */ 0x652F_44D8_C5B0_11B4L, 0x0E16_EC67_2C52_F7F2L, + /* -321 */ 0x50F2_9D7A_37C0_0E29L, 0x5812_56B8_F042_5FF5L, + /* -320 */ 0x40C2_1794_F966_71BAL, 0x79A8_4560_C035_1991L, + /* -319 */ 0x679C_F287_F570_B5F7L, 0x75DA_089A_CD21_C281L, + /* -318 */ 0x52E3_F539_9126_F7F9L, 0x44AE_6D48_A41B_0201L, + /* -317 */ 0x424F_F761_40EB_F994L, 0x36F1_F106_E9AF_34CDL, + /* -316 */ 0x6A19_8BCE_CE46_5C20L, 0x57E9_81A4_A918_547BL, + /* -315 */ 0x54E1_3CA5_71D1_E34DL, 0x2CBA_CE1D_5413_76C9L, + /* -314 */ 0x43E7_63B7_8E41_82A4L, 0x23C8_A4E4_4342_C56EL, + /* -313 */ 0x6CA5_6C58_E39C_043AL, 0x060D_D4A0_6B9E_08B0L, + /* -312 */ 0x56EA_BD13_E949_9CFBL, 0x1E71_76E6_BC7E_6D59L, + /* -311 */ 0x4588_9743_2107_B0C8L, 0x7EC1_2BEB_C9FE_BDE1L, + /* -310 */ 0x6F40_F205_01A5_E7A7L, 0x7E01_DFDF_A997_9635L, + /* -309 */ 0x5900_C19D_9AEB_1FB9L, 0x4B34_B319_5479_44F7L, + /* -308 */ 0x4733_CE17_AF22_7FC7L, 0x55C3_C27A_A9FA_9D93L, + /* -307 */ 0x71EC_7CF2_B1D0_CC72L, 0x5606_03F7_765D_C8EAL, + /* -306 */ 0x5B23_9728_8E40_A38EL, 0x7804_CFF9_2B7E_3A55L, + /* -305 */ 0x48E9_45BA_0B66_E93FL, 0x1337_0CC7_55FE_9511L, + /* -304 */ 0x74A8_6F90_123E_41FEL, 0x51F1_AE0B_BCCA_881BL, + /* -303 */ 0x5D53_8C73_41CB_67FEL, 0x74C1_5809_63D5_39AFL, + /* -302 */ 0x4AA9_3D29_016F_8665L, 0x43CD_E007_8310_FAF3L, + /* -301 */ 0x7775_2EA8_024C_0A3CL, 0x0616_333F_381B_2B1EL, + /* -300 */ 0x5F90_F220_01D6_6E96L, 0x3811_C298_F9AF_55B1L, + /* -299 */ 0x4C73_F4E6_67DE_BEDEL, 0x600E_3547_2E25_DE28L, + /* -298 */ 0x7A53_2170_A631_3164L, 0x3349_EED8_49D6_303FL, + /* -297 */ 0x61DC_1AC0_84F4_2783L, 0x42A1_8BE0_3B11_C033L, + /* -296 */ 0x4E49_AF00_6A5C_EC69L, 0x1BB4_6FE6_95A7_CCF5L, + /* -295 */ 0x7D42_B19A_43C7_E0A8L, 0x2C53_E63D_BC3F_AE55L, + /* -294 */ 0x6435_5AE1_CFD3_1A20L, 0x2376_51CA_FCFF_BEAAL, + /* -293 */ 0x502A_AF1B_0CA8_E1B3L, 0x35F8_416F_30CC_9888L, + /* -292 */ 0x4022_25AF_3D53_E7C2L, 0x5E60_3458_F3D6_E06DL, + /* -291 */ 0x669D_0918_621F_D937L, 0x4A33_86F4_B957_CD7BL, + /* -290 */ 0x5217_3A79_E819_7A92L, 0x6E8F_9F2A_2DDF_D796L, + /* -289 */ 0x41AC_2EC7_ECE1_2EDBL, 0x720C_7F54_F17F_DFABL, + /* -288 */ 0x6913_7E0C_AE35_17C6L, 0x1CE0_CBBB_1BFF_CC45L, + /* -287 */ 0x540F_980A_24F7_4638L, 0x171A_3C95_AFFF_D69EL, + /* -286 */ 0x433F_ACD4_EA5F_6B60L, 0x127B_63AA_F333_1218L, + /* -285 */ 0x6B99_1487_DD65_7899L, 0x6A5F_05DE_51EB_5026L, + /* -284 */ 0x5614_106C_B11D_FA14L, 0x5518_D17E_A7EF_7352L, + /* -283 */ 0x44DC_D9F0_8DB1_94DDL, 0x2A7A_4132_1FF2_C2A8L, + /* -282 */ 0x6E2E_2980_E2B5_BAFBL, 0x5D90_6850_331E_043FL, + /* -281 */ 0x5824_EE00_B55E_2F2FL, 0x6473_86A6_8F4B_3699L, + /* -280 */ 0x4683_F19A_2AB1_BF59L, 0x36C2_D21E_D908_F87BL, + /* -279 */ 0x70D3_1C29_DDE9_3228L, 0x579E_1CFE_280E_5A5DL, + /* -278 */ 0x5A42_7CEE_4B20_F4EDL, 0x2C7E_7D98_200B_7B7EL, + /* -277 */ 0x4835_30BE_A280_C3F1L, 0x09FE_CAE0_19A2_C932L, + /* -276 */ 0x7388_4DFD_D0CE_064EL, 0x4331_4499_C29E_0EB6L, + /* -275 */ 0x5C6D_0B31_73D8_050BL, 0x4F5A_9D47_CEE4_D891L, + /* -274 */ 0x49F0_D5C1_2979_9DA2L, 0x72AE_E439_7250_AD41L, + /* -273 */ 0x764E_22CE_A8C2_95D1L, 0x377E_39F5_83B4_4868L, + /* -272 */ 0x5EA4_E8A5_53CE_DE41L, 0x12CB_6191_3629_D387L, + /* -271 */ 0x4BB7_2084_430B_E500L, 0x756F_8140_F821_7605L, + /* -270 */ 0x7925_00D3_9E79_6E67L, 0x6F18_CECE_59CF_233CL, + /* -269 */ 0x60EA_670F_B1FA_BEB9L, 0x3F47_0BD8_47D8_E8FDL, + /* -268 */ 0x4D88_5272_F4C8_9894L, 0x329F_3CAD_0647_20CAL, + /* -267 */ 0x7C0D_50B7_EE0D_C0EDL, 0x3765_2DE1_A3A5_0143L, + /* -266 */ 0x633D_DA2C_BE71_6724L, 0x2C50_F181_4FB7_3436L, + /* -265 */ 0x4F64_AE8A_31F4_5283L, 0x3D0D_8E01_0C92_902BL, + /* -264 */ 0x7F07_7DA9_E986_EA6BL, 0x7B48_E334_E0EA_8045L, + /* -263 */ 0x659F_97BB_2138_BB89L, 0x4907_1C2A_4D88_669DL, + /* -262 */ 0x514C_7962_80FA_2FA1L, 0x20D2_7CEE_A46D_1EE4L, + /* -261 */ 0x4109_FAB5_33FB_594DL, 0x670E_CA58_838A_7F1DL, + /* -260 */ 0x680F_F788_532B_C216L, 0x0B4A_DD5A_6C10_CB62L, + /* -259 */ 0x533F_F939_DC23_01ABL, 0x22A2_4AAE_BCDA_3C4EL, + /* -258 */ 0x4299_942E_49B5_9AEFL, 0x354E_A225_63E1_C9D8L, + /* -257 */ 0x6A8F_537D_42BC_2B18L, 0x554A_9D08_9FCF_A95AL, + /* -256 */ 0x553F_75FD_CEFC_EF46L, 0x776E_E406_E63F_BAAEL, + /* -255 */ 0x4432_C4CB_0BFD_8C38L, 0x5F8B_E99F_1E99_6225L, + /* -254 */ 0x6D1E_07AB_4662_79F4L, 0x3279_75CB_6428_9D08L, + /* -253 */ 0x574B_3955_D1E8_6190L, 0x2861_2B09_1CED_4A6DL, + /* -252 */ 0x45D5_C777_DB20_4E0DL, 0x06B4_226D_B0BD_D524L, + /* -251 */ 0x6FBC_7259_5E9A_167BL, 0x2453_6A49_1AC9_5506L, + /* -250 */ 0x5963_8EAD_E548_11FCL, 0x1D0F_883A_7BD4_4405L, + /* -249 */ 0x4782_D88B_1DD3_4196L, 0x4A72_D361_FCA9_D004L, + /* -248 */ 0x726A_F411_C952_028AL, 0x43EA_EBCF_FAA9_4CD3L, + /* -247 */ 0x5B88_C341_6DDB_353BL, 0x4FEF_230C_C887_70A9L, + /* -246 */ 0x493A_35CD_F17C_2A96L, 0x0CBF_4F3D_6D39_26EEL, + /* -245 */ 0x7529_EFAF_E8C6_AA89L, 0x6132_1862_485B_717CL, + /* -244 */ 0x5DBB_2626_53D2_2207L, 0x675B_46B5_06AF_8DFDL, + /* -243 */ 0x4AFC_1E85_0FDB_4E6CL, 0x52AF_6BC4_0559_3E64L, + /* -242 */ 0x77F9_CA6E_7FC5_4A47L, 0x377F_12D3_3BC1_FD6DL, + /* -241 */ 0x5FFB_0858_6637_6E9FL, 0x45FF_4242_9634_CABDL, + /* -240 */ 0x4CC8_D379_EB5F_8BB2L, 0x6B32_9B68_782A_3BCBL, + /* -239 */ 0x7ADA_EBF6_4565_AC51L, 0x2B84_2BDA_59DD_2C77L, + /* -238 */ 0x6248_BCC5_0451_56A7L, 0x3C69_BCAE_AE4A_89F9L, + /* -237 */ 0x4EA0_9704_0374_4552L, 0x6387_CA25_583B_A194L, + /* -236 */ 0x7DCD_BE6C_D253_A21EL, 0x05A6_103B_C05F_68EDL, + /* -235 */ 0x64A4_9857_0EA9_4E7EL, 0x37B8_0CFC_99E5_ED8AL, + /* -234 */ 0x5083_AD12_7221_0B98L, 0x2C93_3D96_E184_BE08L, + /* -233 */ 0x4069_5741_F4E7_3C79L, 0x7075_CADF_1AD0_9807L, + /* -232 */ 0x670E_F203_2171_FA5CL, 0x4D89_4498_2AE7_59A4L, + /* -231 */ 0x5272_5B35_B45B_2EB0L, 0x3E07_6A13_5585_E150L, + /* -230 */ 0x41F5_15C4_9048_F226L, 0x64D2_BB42_AAD1_810DL, + /* -229 */ 0x6988_22D4_1A0E_503EL, 0x07B7_9204_4482_6815L, + /* -228 */ 0x546C_E8A9_AE71_D9CBL, 0x1FC6_0E69_D068_5344L, + /* -227 */ 0x438A_53BA_F1F4_AE3CL, 0x196B_3EBB_0D20_429DL, + /* -226 */ 0x6C10_85F7_E987_7D2DL, 0x0F11_FDF8_1500_6A94L, + /* -225 */ 0x5673_9E5F_EE05_FDBDL, 0x58DB_3193_4400_5543L, + /* -224 */ 0x4529_4B7F_F19E_6497L, 0x60AF_5ADC_3666_AA9CL, + /* -223 */ 0x6EA8_78CC_B5CA_3A8CL, 0x344B_C493_8A3D_DDC7L, + /* -222 */ 0x5886_C70A_2B08_2ED6L, 0x5D09_6A0F_A1CB_17D2L, + /* -221 */ 0x46D2_38D4_EF39_BF12L, 0x173A_BB3F_B4A2_7975L, + /* -220 */ 0x7150_5AEE_4B8F_981DL, 0x0B91_2B99_2103_F588L, + /* -219 */ 0x5AA6_AF25_093F_ACE4L, 0x0940_EFAD_B403_2AD3L, + /* -218 */ 0x4885_58EA_6DCC_8A50L, 0x0767_2624_9002_88A9L, + /* -217 */ 0x7408_8E43_E2E0_DD4CL, 0x723E_A36D_B337_410EL, + /* -216 */ 0x5CD3_A503_1BE7_1770L, 0x5B65_4F8A_F5C5_CDA5L, + /* -215 */ 0x4A42_EA68_E31F_45F3L, 0x62B7_72D5_916B_0AEBL, + /* -214 */ 0x76D1_770E_3832_0986L, 0x0458_B7BC_1BDE_77DDL, + /* -213 */ 0x5F0D_F8D8_2CF4_D46BL, 0x1D13_C630_164B_9318L, + /* -212 */ 0x4C0B_2D79_BD90_A9EFL, 0x30DC_9E8C_DEA2_DC13L, + /* -211 */ 0x79AB_7BF5_FC1A_A97FL, 0x0160_FDAE_3104_9351L, + /* -210 */ 0x6155_FCC4_C9AE_EDFFL, 0x1AB3_FE24_F403_A90EL, + /* -209 */ 0x4DDE_63D0_A158_BE65L, 0x6229_981D_9002_EDA5L, + /* -208 */ 0x7C97_061A_9BC1_30A2L, 0x69DC_2695_B337_E2A1L, + /* -207 */ 0x63AC_04E2_1634_26E8L, 0x54B0_1EDE_28F9_821BL, + /* -206 */ 0x4FBC_D0B4_DE90_1F20L, 0x43C0_18B1_BA61_34E2L, + /* -205 */ 0x7F94_8121_6419_CB67L, 0x1F99_C11C_5D68_549DL, + /* -204 */ 0x6610_674D_E9AE_3C52L, 0x4C7B_00E3_7DED_107EL, + /* -203 */ 0x51A6_B90B_2158_3042L, 0x09FC_00B5_FE57_4065L, + /* -202 */ 0x4152_2DA2_8113_59CEL, 0x3B30_0091_9845_CD1DL, + /* -201 */ 0x6883_7C37_34EB_C2E3L, 0x784C_CDB5_C06F_AE95L, + /* -200 */ 0x539C_635F_5D89_68B6L, 0x2D0A_3E2B_0059_5877L, + /* -199 */ 0x42E3_82B2_B13A_BA2BL, 0x3DA1_CB55_99E1_1393L, + /* -198 */ 0x6B05_9DEA_B52A_C378L, 0x629C_7888_F634_EC1EL, + /* -197 */ 0x559E_17EE_F755_692DL, 0x3549_FA07_2B5D_89B1L, + /* -196 */ 0x447E_798B_F911_20F1L, 0x1107_FB38_EF7E_07C1L, + /* -195 */ 0x6D97_28DF_F4E8_34B5L, 0x01A6_5EC1_7F30_0C68L, + /* -194 */ 0x57AC_20B3_2A53_5D5DL, 0x4E1E_B234_65C0_09EDL, + /* -193 */ 0x4623_4D5C_21DC_4AB1L, 0x24E5_5B5D_1E33_3B24L, + /* -192 */ 0x7038_7BC6_9C93_AAB5L, 0x216E_F894_FD1E_C506L, + /* -191 */ 0x59C6_C96B_B076_222AL, 0x4DF2_6077_30E5_6A6CL, + /* -190 */ 0x47D2_3ABC_8D2B_4E88L, 0x3E5B_805F_5A51_21F0L, + /* -189 */ 0x72E9_F794_1512_1740L, 0x63C5_9A32_2A1B_697FL, + /* -188 */ 0x5BEE_5FA9_AA74_DF67L, 0x0304_7B5B_54E2_BACCL, + /* -187 */ 0x498B_7FBA_EEC3_E5ECL, 0x0269_FC49_10B5_623DL, + /* -186 */ 0x75AB_FF91_7E06_3CACL, 0x6A43_2D41_B455_69FBL, + /* -185 */ 0x5E23_32DA_CB38_308AL, 0x21CF_5767_C377_87FCL, + /* -184 */ 0x4B4F_5BE2_3C2C_F3A1L, 0x67D9_12B9_692C_6CCAL, + /* -183 */ 0x787E_F969_F9E1_85CFL, 0x595B_5128_A847_1476L, + /* -182 */ 0x6065_9454_C7E7_9E3FL, 0x6115_DA86_ED05_A9F8L, + /* -181 */ 0x4D1E_1043_D31F_B1CCL, 0x4DAB_1538_BD9E_2193L, + /* -180 */ 0x7B63_4D39_51CC_4FADL, 0x62AB_5527_95C9_CF52L, + /* -179 */ 0x62B5_D761_0E3D_0C8BL, 0x0222_AA86_116E_3F75L, + /* -178 */ 0x4EF7_DF80_D830_D6D5L, 0x4E82_2204_DABE_992AL, + /* -177 */ 0x7E59_659A_F381_57BCL, 0x1736_9CD4_9130_F510L, + /* -176 */ 0x6514_5148_C2CD_DFC9L, 0x5F5E_E3DD_40F3_F740L, + /* -175 */ 0x50DD_0DD3_CF0B_196EL, 0x1918_B64A_9A5C_C5CDL, + /* -174 */ 0x40B0_D7DC_A5A2_7ABEL, 0x4746_F83B_AEB0_9E3EL, + /* -173 */ 0x6781_5961_0903_F797L, 0x253E_59F9_1780_FD2FL, + /* -172 */ 0x52CD_E11A_6D9C_C612L, 0x50FE_AE60_DF9A_6426L, + /* -171 */ 0x423E_4DAE_BE17_04DBL, 0x5A65_584D_7FAE_B685L, + /* -170 */ 0x69FD_4917_968B_3AF9L, 0x10A2_26E2_65E4_573BL, + /* -169 */ 0x54CA_A0DF_ABA2_9594L, 0x0D4E_8581_EB1D_1295L, + /* -168 */ 0x43D5_4D7F_BC82_1143L, 0x243E_D134_BC17_4211L, + /* -167 */ 0x6C88_7BFF_9403_4ED2L, 0x06CA_E854_6025_3682L, + /* -166 */ 0x56D3_9666_1002_A574L, 0x6BD5_86A9_E684_2B9BL, + /* -165 */ 0x4576_11EB_4002_1DF7L, 0x0977_9EEE_5203_5616L, + /* -164 */ 0x6F23_4FDE_CCD0_2FF1L, 0x5BF2_97E3_B66B_BCEFL, + /* -163 */ 0x58E9_0CB2_3D73_598EL, 0x165B_ACB6_2B89_63F3L, + /* -162 */ 0x4720_D6F4_FDF5_E13EL, 0x4516_23C4_EFA1_1CC2L, + /* -161 */ 0x71CE_24BB_2FEF_CECAL, 0x3B56_9FA1_7F68_2E03L, + /* -160 */ 0x5B0B_5095_BFF3_0BD5L, 0x15DE_E61A_CC53_5803L, + /* -159 */ 0x48D5_DA11_665C_0977L, 0x2B18_B815_7042_ACCFL, + /* -158 */ 0x7489_5CE8_A3C6_758BL, 0x5E8D_F355_806A_AE18L, + /* -157 */ 0x5D3A_B0BA_1C9E_C46FL, 0x653E_5C44_66BB_BE7AL, + /* -156 */ 0x4A95_5A2E_7D4B_D059L, 0x3765_169D_1EFC_9861L, + /* -155 */ 0x7755_5D17_2EDF_B3C2L, 0x256E_8A94_FE60_F3CFL, + /* -154 */ 0x5F77_7DAC_257F_C301L, 0x6ABE_D543_FEB3_F63FL, + /* -153 */ 0x4C5F_97BC_EACC_9C01L, 0x3BCB_DDCF_FEF6_5E99L, + /* -152 */ 0x7A32_8C61_77AD_C668L, 0x5FAC_9619_97F0_975BL, + /* -151 */ 0x61C2_09E7_92F1_6B86L, 0x7FBD_44E1_465A_12AFL, + /* -150 */ 0x4E34_D4B9_425A_BC6BL, 0x7FCA_9D81_0514_DBBFL, + /* -149 */ 0x7D21_545B_9D5D_FA46L, 0x32DD_C8CE_6E87_C5FFL, + /* -148 */ 0x641A_A9E2_E44B_2E9EL, 0x5BE4_A0A5_2539_6B32L, + /* -147 */ 0x5015_54B5_836F_587EL, 0x7CB6_E6EA_842D_EF5CL, + /* -146 */ 0x4011_1091_35F2_AD32L, 0x3092_5255_368B_25E3L, + /* -145 */ 0x6681_B41B_8984_4850L, 0x4DB6_EA21_F0DE_A304L, + /* -144 */ 0x5201_5CE2_D469_D373L, 0x57C5_881B_2718_826AL, + /* -143 */ 0x419A_B0B5_76BB_0F8FL, 0x5FD1_39AF_527A_01EFL, + /* -142 */ 0x68F7_8122_5791_B27FL, 0x4C81_F5E5_50C3_364AL, + /* -141 */ 0x53F9_341B_7941_5B99L, 0x239B_2B1D_DA35_C508L, + /* -140 */ 0x432D_C349_2DCD_E2E1L, 0x02E2_88E4_AE91_6A6DL, + /* -139 */ 0x6B7C_6BA8_4949_6B01L, 0x516A_74A1_174F_10AEL, + /* -138 */ 0x55FD_22ED_076D_EF34L, 0x4121_F6E7_45D8_DA25L, + /* -137 */ 0x44CA_8257_3924_BF5DL, 0x1A81_9252_9E47_14EBL, + /* -136 */ 0x6E10_D08B_8EA1_322EL, 0x5D9C_1D50_FD3E_87DDL, + /* -135 */ 0x580D_73A2_D880_F4F2L, 0x17B0_1773_FDCB_9FE4L, + /* -134 */ 0x4671_294F_139A_5D8EL, 0x4626_7929_97D6_1984L, + /* -133 */ 0x70B5_0EE4_EC2A_2F4AL, 0x3D0A_5B75_BFBC_F59FL, + /* -132 */ 0x5A2A_7250_BCEE_8C3BL, 0x4A6E_AF91_6630_C47FL, + /* -131 */ 0x4821_F50D_63F2_09C9L, 0x21F2_260D_EB5A_36CCL, + /* -130 */ 0x7369_8815_6CB6_760EL, 0x6983_7016_455D_247AL, + /* -129 */ 0x5C54_6CDD_F091_F80BL, 0x6E02_C011_D117_5062L, + /* -128 */ 0x49DD_23E4_C074_C66FL, 0x719B_CCDB_0DAC_404EL, + /* -127 */ 0x762E_9FD4_6721_3D7FL, 0x68F9_47C4_E2AD_33B0L, + /* -126 */ 0x5E8B_B310_5280_FDFFL, 0x6D94_396A_4EF0_F627L, + /* -125 */ 0x4BA2_F5A6_A867_3199L, 0x3E10_2DEE_A58D_91B9L, + /* -124 */ 0x7904_BC3D_DA3E_B5C2L, 0x3019_E317_6F48_E927L, + /* -123 */ 0x60D0_9697_E1CB_C49BL, 0x4014_B5AC_5907_20ECL, + /* -122 */ 0x4D73_ABAC_B4A3_03AFL, 0x4CDD_5E23_7A6C_1A57L, + /* -121 */ 0x7BEC_45E1_2104_D2B2L, 0x47C8_969F_2A46_908AL, + /* -120 */ 0x6323_6B1A_80D0_A88EL, 0x6CA0_787F_5505_406FL, + /* -119 */ 0x4F4F_88E2_00A6_ED3FL, 0x0A19_F9FF_7737_66BFL, + /* -118 */ 0x7EE5_A7D0_010B_1531L, 0x5CF6_5CCB_F1F2_3DFEL, + /* -117 */ 0x6584_8640_00D5_AA8EL, 0x172B_7D6F_F4C1_CB32L, + /* -116 */ 0x5136_D1CC_CD77_BBA4L, 0x78EF_978C_C3CE_3C28L, + /* -115 */ 0x40F8_A7D7_0AC6_2FB7L, 0x13F2_DFA3_CFD8_3020L, + /* -114 */ 0x67F4_3FBE_77A3_7F8BL, 0x3984_9906_1959_E699L, + /* -113 */ 0x5329_CC98_5FB5_FFA2L, 0x6136_E0D1_ADE1_8548L, + /* -112 */ 0x4287_D6E0_4C91_994FL, 0x00F8_B3DA_F181_376DL, + /* -111 */ 0x6A72_F166_E0E8_F54BL, 0x1B27_862B_1C01_F247L, + /* -110 */ 0x5528_C11F_1A53_F76FL, 0x2F52_D1BC_1667_F506L, + /* -109 */ 0x4420_9A7F_4843_2C59L, 0x0C42_4163_451F_F738L, + /* -108 */ 0x6D00_F732_0D38_46F4L, 0x7A03_9BD2_0833_2526L, + /* -107 */ 0x5733_F8F4_D760_38C3L, 0x7B36_1641_A028_EA85L, + /* -106 */ 0x45C3_2D90_AC4C_FA36L, 0x2F5E_7834_8020_BB9EL, + /* -105 */ 0x6F9E_AF4D_E07B_29F0L, 0x4BCA_59ED_99CD_F8FCL, + /* -104 */ 0x594B_BF71_8062_87F3L, 0x563B_7B24_7B0B_2D96L, + /* -103 */ 0x476F_CC5A_CD1B_9FF6L, 0x11C9_2F50_626F_57ACL, + /* -102 */ 0x724C_7A2A_E1C5_CCBDL, 0x02DB_7EE7_03E5_5912L, + /* -101 */ 0x5B70_61BB_E7D1_7097L, 0x1BE2_CBEC_031D_E0DCL, + /* -100 */ 0x4926_B496_530D_F3ACL, 0x164F_0989_9C17_E716L, + /* -99 */ 0x750A_BA8A_1E7C_B913L, 0x3D4B_4275_C68C_A4F0L, + /* -98 */ 0x5DA2_2ED4_E530_940FL, 0x4AA2_9B91_6BA3_B726L, + /* -97 */ 0x4AE8_2577_1DC0_7672L, 0x6EE8_7C74_561C_9285L, + /* -96 */ 0x77D9_D58B_62CD_8A51L, 0x3173_FA53_BCFA_8408L, + /* -95 */ 0x5FE1_77A2_B571_3B74L, 0x278F_FB76_30C8_69A0L, + /* -94 */ 0x4CB4_5FB5_5DF4_2F90L, 0x1FA6_62C4_F3D3_87B3L, + /* -93 */ 0x7ABA_32BB_C986_B280L, 0x32A3_D13B_1FB8_D91FL, + /* -92 */ 0x622E_8EFC_A138_8ECDL, 0x0EE9_742F_4C93_E0E6L, + /* -91 */ 0x4E8B_A596_E760_723DL, 0x58BA_C359_0A0F_E71EL, + /* -90 */ 0x7DAC_3C24_A567_1D2FL, 0x412A_D228_1019_71C9L, + /* -89 */ 0x6489_C9B6_EAB8_E426L, 0x00EF_0E86_7347_8E3BL, + /* -88 */ 0x506E_3AF8_BBC7_1CEBL, 0x1A58_D86B_8F6C_71C9L, + /* -87 */ 0x4058_2F2D_6305_B0BCL, 0x1513_E056_0C56_C16EL, + /* -86 */ 0x66F3_7EAF_04D5_E793L, 0x3B53_0089_AD57_9BE2L, + /* -85 */ 0x525C_6558_D0AB_1FA9L, 0x15DC_006E_2446_164FL, + /* -84 */ 0x41E3_8447_0D55_B2EDL, 0x5E49_99F1_B69E_783FL, + /* -83 */ 0x696C_06D8_1555_EB15L, 0x7D42_8FE9_2430_C065L, + /* -82 */ 0x5456_6BE0_1111_88DEL, 0x3102_0CBA_835A_3384L, + /* -81 */ 0x4378_564C_DA74_6D7EL, 0x5A68_0A2E_CF7B_5C69L, + /* -80 */ 0x6BF3_BD47_C3ED_7BFDL, 0x770C_DD17_B25E_FA42L, + /* -79 */ 0x565C_976C_9CBD_FCCBL, 0x1270_B0DF_C1E5_9502L, + /* -78 */ 0x4516_DF8A_16FE_63D5L, 0x5B8D_5A4C_9B1E_10CEL, + /* -77 */ 0x6E8A_FF43_57FD_6C89L, 0x127B_C3AD_C4FC_E7B0L, + /* -76 */ 0x586F_329C_4664_56D4L, 0x0EC9_6957_D0CA_52F3L, + /* -75 */ 0x46BF_5BB0_3850_4576L, 0x3F07_8779_73D5_0F29L, + /* -74 */ 0x7132_2C4D_26E6_D58AL, 0x31A5_A58F_1FBB_4B75L, + /* -73 */ 0x5A8E_89D7_5252_446EL, 0x5AEA_EAD8_E62F_6F91L, + /* -72 */ 0x4872_07DF_750E_9D25L, 0x2F22_557A_51BF_8C74L, + /* -71 */ 0x73E9_A632_54E4_2EA2L, 0x1836_EF2A_1C65_AD86L, + /* -70 */ 0x5CBA_EB5B_771C_F21BL, 0x2CF8_BF54_E384_8AD2L, + /* -69 */ 0x4A2F_22AF_927D_8E7CL, 0x23FA_32AA_4F9D_3BDBL, + /* -68 */ 0x76B1_D118_EA62_7D93L, 0x5329_EAAA_18FB_92F8L, + /* -67 */ 0x5EF4_A747_21E8_6476L, 0x0F54_BBBB_472F_A8C6L, + /* -66 */ 0x4BF6_EC38_E7ED_1D2BL, 0x25DD_62FC_38F2_ED6CL, + /* -65 */ 0x798B_138E_3FE1_C845L, 0x22FB_D193_8E51_7BDFL, + /* -64 */ 0x613C_0FA4_FFE7_D36AL, 0x4F2F_DADC_71DA_C97FL, + /* -63 */ 0x4DC9_A61D_9986_42BBL, 0x58F3_157D_27E2_3ACCL, + /* -62 */ 0x7C75_D695_C270_6AC5L, 0x74B8_2261_D969_F7ADL, + /* -61 */ 0x6391_7877_CEC0_556BL, 0x1093_4EB4_ADEE_5FBEL, + /* -60 */ 0x4FA7_9393_0BCD_1122L, 0x4075_D890_8B25_1965L, + /* -59 */ 0x7F72_85B8_12E1_B504L, 0x00BC_8DB4_11D4_F56EL, + /* -58 */ 0x65F5_37C6_7581_5D9CL, 0x66FD_3E29_A7DD_9125L, + /* -57 */ 0x5190_F96B_9134_4AE3L, 0x6BFD_CB54_864A_DA84L, + /* -56 */ 0x4140_C789_40F6_A24FL, 0x6FFE_3C43_9EA2_486AL, + /* -55 */ 0x6867_A5A8_67F1_03B2L, 0x7FFD_2D38_FDD0_73DCL, + /* -54 */ 0x5386_1E20_5327_3628L, 0x6664_242D_97D9_F64AL, + /* -53 */ 0x42D1_B1B3_75B8_F820L, 0x51E9_B68A_DFE1_91D5L, + /* -52 */ 0x6AE9_1C52_55F4_C034L, 0x1CA9_2411_6635_B621L, + /* -51 */ 0x5587_49DB_77F7_0029L, 0x63BA_8341_1E91_5E81L, + /* -50 */ 0x446C_3B15_F992_6687L, 0x6962_029A_7EDA_B201L, + /* -49 */ 0x6D79_F823_28EA_3DA6L, 0x0F03_375D_97C4_5001L, + /* -48 */ 0x5794_C682_8721_CAEBL, 0x259C_2C4A_DFD0_4001L, + /* -47 */ 0x4610_9ECE_D281_6F22L, 0x5149_BD08_B30D_0001L, + /* -46 */ 0x701A_97B1_50CF_1837L, 0x3542_C80D_EB48_0001L, + /* -45 */ 0x59AE_DFC1_0D72_79C5L, 0x7768_A00B_22A0_0001L, + /* -44 */ 0x47BF_1967_3DF5_2E37L, 0x7920_8008_E880_0001L, + /* -43 */ 0x72CB_5BD8_6321_E38CL, 0x5B67_3341_7400_0001L, + /* -42 */ 0x5BD5_E313_8281_82D6L, 0x7C52_8F67_9000_0001L, + /* -41 */ 0x4977_E8DC_6867_9BDFL, 0x16A8_72B9_4000_0001L, + /* -40 */ 0x758C_A7C7_0D72_92FEL, 0x5773_EAC2_0000_0001L, + /* -39 */ 0x5E0A_1FD2_7128_7598L, 0x45F6_5568_0000_0001L, + /* -38 */ 0x4B3B_4CA8_5A86_C47AL, 0x04C5_1120_0000_0001L, + /* -37 */ 0x785E_E10D_5DA4_6D90L, 0x07A1_B500_0000_0001L, + /* -36 */ 0x604B_E73D_E483_8AD9L, 0x52E7_C400_0000_0001L, + /* -35 */ 0x4D09_85CB_1D36_08AEL, 0x0F1F_D000_0000_0001L, + /* -34 */ 0x7B42_6FAB_61F0_0DE3L, 0x31CC_8000_0000_0001L, + /* -33 */ 0x629B_8C89_1B26_7182L, 0x5B0A_0000_0000_0001L, + /* -32 */ 0x4EE2_D6D4_15B8_5ACEL, 0x7C08_0000_0000_0001L, + /* -31 */ 0x7E37_BE20_22C0_914BL, 0x1340_0000_0000_0001L, + /* -30 */ 0x64F9_64E6_8233_A76FL, 0x2900_0000_0000_0001L, + /* -29 */ 0x50C7_83EB_9B5C_85F2L, 0x5400_0000_0000_0001L, + /* -28 */ 0x409F_9CBC_7C4A_04C2L, 0x1000_0000_0000_0001L, + /* -27 */ 0x6765_C793_FA10_079DL, 0x0000_0000_0000_0001L, + /* -26 */ 0x52B7_D2DC_C80C_D2E4L, 0x0000_0000_0000_0001L, + /* -25 */ 0x422C_A8B0_A00A_4250L, 0x0000_0000_0000_0001L, + /* -24 */ 0x69E1_0DE7_6676_D080L, 0x0000_0000_0000_0001L, + /* -23 */ 0x54B4_0B1F_852B_DA00L, 0x0000_0000_0000_0001L, + /* -22 */ 0x43C3_3C19_3756_4800L, 0x0000_0000_0000_0001L, + /* -21 */ 0x6C6B_935B_8BBD_4000L, 0x0000_0000_0000_0001L, + /* -20 */ 0x56BC_75E2_D631_0000L, 0x0000_0000_0000_0001L, + /* -19 */ 0x4563_9182_44F4_0000L, 0x0000_0000_0000_0001L, + /* -18 */ 0x6F05_B59D_3B20_0000L, 0x0000_0000_0000_0001L, + /* -17 */ 0x58D1_5E17_6280_0000L, 0x0000_0000_0000_0001L, + /* -16 */ 0x470D_E4DF_8200_0000L, 0x0000_0000_0000_0001L, + /* -15 */ 0x71AF_D498_D000_0000L, 0x0000_0000_0000_0001L, + /* -14 */ 0x5AF3_107A_4000_0000L, 0x0000_0000_0000_0001L, + /* -13 */ 0x48C2_7395_0000_0000L, 0x0000_0000_0000_0001L, + /* -12 */ 0x746A_5288_0000_0000L, 0x0000_0000_0000_0001L, + /* -11 */ 0x5D21_DBA0_0000_0000L, 0x0000_0000_0000_0001L, + /* -10 */ 0x4A81_7C80_0000_0000L, 0x0000_0000_0000_0001L, + /* -9 */ 0x7735_9400_0000_0000L, 0x0000_0000_0000_0001L, + /* -8 */ 0x5F5E_1000_0000_0000L, 0x0000_0000_0000_0001L, + /* -7 */ 0x4C4B_4000_0000_0000L, 0x0000_0000_0000_0001L, + /* -6 */ 0x7A12_0000_0000_0000L, 0x0000_0000_0000_0001L, + /* -5 */ 0x61A8_0000_0000_0000L, 0x0000_0000_0000_0001L, + /* -4 */ 0x4E20_0000_0000_0000L, 0x0000_0000_0000_0001L, + /* -3 */ 0x7D00_0000_0000_0000L, 0x0000_0000_0000_0001L, + /* -2 */ 0x6400_0000_0000_0000L, 0x0000_0000_0000_0001L, + /* -1 */ 0x5000_0000_0000_0000L, 0x0000_0000_0000_0001L, + /* 0 */ 0x4000_0000_0000_0000L, 0x0000_0000_0000_0001L, + /* 1 */ 0x6666_6666_6666_6666L, 0x3333_3333_3333_3334L, + /* 2 */ 0x51EB_851E_B851_EB85L, 0x0F5C_28F5_C28F_5C29L, + /* 3 */ 0x4189_374B_C6A7_EF9DL, 0x5916_872B_020C_49BBL, + /* 4 */ 0x68DB_8BAC_710C_B295L, 0x74F0_D844_D013_A92BL, + /* 5 */ 0x53E2_D623_8DA3_C211L, 0x43F3_E037_0CDC_8755L, + /* 6 */ 0x431B_DE82_D7B6_34DAL, 0x698F_E692_70B0_6C44L, + /* 7 */ 0x6B5F_CA6A_F2BD_215EL, 0x0F4C_A41D_811A_46D4L, + /* 8 */ 0x55E6_3B88_C230_E77EL, 0x3F70_834A_CDAE_9F10L, + /* 9 */ 0x44B8_2FA0_9B5A_52CBL, 0x4C5A_02A2_3E25_4C0DL, + /* 10 */ 0x6DF3_7F67_5EF6_EADFL, 0x2D5C_D103_96A2_1347L, + /* 11 */ 0x57F5_FF85_E592_557FL, 0x3DE3_DA69_454E_75D3L, + /* 12 */ 0x465E_6604_B7A8_4465L, 0x7E4F_E1ED_D10B_9175L, + /* 13 */ 0x7097_09A1_25DA_0709L, 0x4A19_697C_81AC_1BEFL, + /* 14 */ 0x5A12_6E1A_84AE_6C07L, 0x54E1_2130_67BC_E326L, + /* 15 */ 0x480E_BE7B_9D58_566CL, 0x43E7_4DC0_52FD_8285L, + /* 16 */ 0x734A_CA5F_6226_F0ADL, 0x530B_AF9A_1E62_6A6DL, + /* 17 */ 0x5C3B_D519_1B52_5A24L, 0x426F_BFAE_7EB5_21F1L, + /* 18 */ 0x49C9_7747_490E_AE83L, 0x4EBF_CC8B_9890_E7F4L, + /* 19 */ 0x760F_253E_DB4A_B0D2L, 0x4ACC_7A78_F41B_0CBAL, + /* 20 */ 0x5E72_8432_4908_8D75L, 0x223D_2EC7_29AF_3D62L, + /* 21 */ 0x4B8E_D028_3A6D_3DF7L, 0x34FD_BF05_BAF2_9781L, + /* 22 */ 0x78E4_8040_5D7B_9658L, 0x54C9_31A2_C4B7_58CFL, + /* 23 */ 0x60B6_CD00_4AC9_4513L, 0x5D6D_C14F_03C5_E0A5L, + /* 24 */ 0x4D5F_0A66_A23A_9DA9L, 0x3124_9AA5_9C9E_4D51L, + /* 25 */ 0x7BCB_43D7_69F7_62A8L, 0x4EA0_F76F_60FD_4882L, + /* 26 */ 0x6309_0312_BB2C_4EEDL, 0x254D_92BF_80CA_A068L, + /* 27 */ 0x4F3A_68DB_C8F0_3F24L, 0x1DD7_A899_33D5_4D20L, + /* 28 */ 0x7EC3_DAF9_4180_6506L, 0x62F2_A75B_8622_1500L, + /* 29 */ 0x6569_7BFA_9ACD_1D9FL, 0x025B_B916_04E8_10CDL, + /* 30 */ 0x5121_2FFB_AF0A_7E18L, 0x6849_60DE_6A53_40A4L, + /* 31 */ 0x40E7_5996_25A1_FE7AL, 0x203A_B3E5_21DC_33B6L, + /* 32 */ 0x67D8_8F56_A29C_CA5DL, 0x19F7_863B_6960_52BDL, + /* 33 */ 0x5313_A5DE_E87D_6EB0L, 0x7B2C_6B62_BAB3_7564L, + /* 34 */ 0x4276_1E4B_ED31_255AL, 0x2F56_BC4E_FBC2_C450L, + /* 35 */ 0x6A56_96DF_E1E8_3BC3L, 0x6557_93B1_92D1_3A1AL, + /* 36 */ 0x5512_124C_B4B9_C969L, 0x3779_42F4_7574_2E7BL, + /* 37 */ 0x440E_750A_2A2E_3ABAL, 0x5F94_3590_5DF6_8B96L, + /* 38 */ 0x6CE3_EE76_A9E3_912AL, 0x65B9_EF4D_6324_1289L, + /* 39 */ 0x571C_BEC5_54B6_0DBBL, 0x6AFB_25D7_8283_4207L, + /* 40 */ 0x45B0_989D_DD5E_7163L, 0x08C8_EB12_CECF_6806L, + /* 41 */ 0x6F80_F42F_C897_1BD1L, 0x5ADB_11B7_B14B_D9A3L, + /* 42 */ 0x5933_F68C_A078_E30EL, 0x157C_0E2C_8DD6_47B5L, + /* 43 */ 0x475C_C53D_4D2D_8271L, 0x5DFC_D823_A4AB_6C91L, + /* 44 */ 0x722E_0862_1515_9D82L, 0x632E_269F_6DDF_141BL, + /* 45 */ 0x5B58_06B4_DDAA_E468L, 0x4F58_1EE5_F17F_4349L, + /* 46 */ 0x4913_3890_B155_8386L, 0x72AC_E584_C132_9C3BL, + /* 47 */ 0x74EB_8DB4_4EEF_38D7L, 0x6AAE_3C07_9B84_2D2AL, + /* 48 */ 0x5D89_3E29_D8BF_60ACL, 0x5558_3006_1603_5755L, + /* 49 */ 0x4AD4_31BB_13CC_4D56L, 0x7779_C004_DE69_12ABL, + /* 50 */ 0x77B9_E92B_52E0_7BBEL, 0x258F_99A1_63DB_5111L, + /* 51 */ 0x5FC7_EDBC_424D_2FCBL, 0x37A6_1481_1CAF_740DL, + /* 52 */ 0x4C9F_F163_683D_BFD5L, 0x7951_AA00_E3BF_900BL, + /* 53 */ 0x7A99_8238_A6C9_32EFL, 0x754F_7667_D2CC_19ABL, + /* 54 */ 0x6214_682D_523A_8F26L, 0x2AA5_F853_0F09_AE22L, + /* 55 */ 0x4E76_B9BD_DB62_0C1EL, 0x5551_9375_A5A1_581BL, + /* 56 */ 0x7D8A_C2C9_5F03_4697L, 0x3BB5_B8BC_3C35_59C5L, + /* 57 */ 0x646F_023A_B269_0545L, 0x7C91_6096_9691_149EL, + /* 58 */ 0x5058_CE95_5B87_376BL, 0x16DA_B3AB_ABA7_43B2L, + /* 59 */ 0x4047_0BAA_AF9F_5F88L, 0x78AE_F622_EFB9_02F5L, + /* 60 */ 0x66D8_12AA_B298_98DBL, 0x0DE4_BD04_B2C1_9E54L, + /* 61 */ 0x5246_7555_5BAD_4715L, 0x57EA_30D0_8F01_4B76L, + /* 62 */ 0x41D1_F777_7C8A_9F44L, 0x4654_F3DA_0C01_092CL, + /* 63 */ 0x694F_F258_C744_3207L, 0x23BB_1FC3_4668_0EACL, + /* 64 */ 0x543F_F513_D29C_F4D2L, 0x4FC8_E635_D1EC_D88AL, + /* 65 */ 0x4366_5DA9_754A_5D75L, 0x263A_51C4_A7F0_AD3BL, + /* 66 */ 0x6BD6_FC42_5543_C8BBL, 0x56C3_B607_731A_AEC4L, + /* 67 */ 0x5645_969B_7769_6D62L, 0x789C_919F_8F48_8BD0L, + /* 68 */ 0x4504_787C_5F87_8AB5L, 0x46E3_A7B2_D906_D640L, + /* 69 */ 0x6E6D_8D93_CC0C_1122L, 0x3E39_0C51_5B3E_239AL, + /* 70 */ 0x5857_A476_3CD6_741BL, 0x4B60_D6A7_7C31_B615L, + /* 71 */ 0x46AC_8391_CA45_29AFL, 0x55E7_121F_968E_2B44L, + /* 72 */ 0x7114_05B6_106E_A919L, 0x0971_B698_F0E3_786DL, + /* 73 */ 0x5A76_6AF8_0D25_5414L, 0x078E_2BAD_8D82_C6BDL, + /* 74 */ 0x485E_BBF9_A41D_DCDCL, 0x6C71_BC8A_D79B_D231L, + /* 75 */ 0x73CA_C65C_39C9_6161L, 0x2D82_C744_8C2C_8382L, + /* 76 */ 0x5CA2_3849_C7D4_4DE7L, 0x3E02_3903_A356_CF9BL, + /* 77 */ 0x4A1B_603B_0643_7185L, 0x7E68_2D9C_82AB_D949L, + /* 78 */ 0x7692_3391_A39F_1C09L, 0x4A40_48FA_6AAC_8EDBL, + /* 79 */ 0x5EDB_5C74_82E5_B007L, 0x5500_3A61_EEF0_7249L, + /* 80 */ 0x4BE2_B05D_3584_8CD2L, 0x7733_61E7_F259_F507L, + /* 81 */ 0x796A_B3C8_55A0_E151L, 0x3EB8_9CA6_508F_EE71L, + /* 82 */ 0x6122_296D_114D_810DL, 0x7EFA_16EB_73A6_585BL, + /* 83 */ 0x4DB4_EDF0_DAA4_673EL, 0x3261_ABEF_8FB8_46AFL, + /* 84 */ 0x7C54_AFE7_C43A_3ECAL, 0x1D69_1318_E5F3_A44BL, + /* 85 */ 0x6376_F31F_D02E_98A1L, 0x6454_0F47_1E5C_836FL, + /* 86 */ 0x4F92_5C19_7358_7A1BL, 0x0376_729F_4B7D_35F3L, + /* 87 */ 0x7F50_935B_EBC0_C35EL, 0x38BD_8432_1261_EFEBL, + /* 88 */ 0x65DA_0F7C_BC9A_35E5L, 0x13CA_D028_0EB4_BFEFL, + /* 89 */ 0x517B_3F96_FD48_2B1DL, 0x5CA2_4020_0BC3_CCBFL, + /* 90 */ 0x412F_6612_6439_BC17L, 0x63B5_0019_A303_0A33L, + /* 91 */ 0x684B_D683_D38F_9359L, 0x1F88_0029_04D1_A9EAL, + /* 92 */ 0x536F_DECF_DC72_DC47L, 0x32D3_3354_03DA_EE55L, + /* 93 */ 0x42BF_E573_16C2_49D2L, 0x5BDC_2910_0315_8B77L, + /* 94 */ 0x6ACC_A251_BE03_A951L, 0x12F9_DB4C_D1BC_1258L, + /* 95 */ 0x5570_81DA_FE69_5440L, 0x7594_AF70_A7C9_A847L, + /* 96 */ 0x445A_017B_FEBA_A9CDL, 0x4476_F2C0_863A_ED06L, + /* 97 */ 0x6D5C_CF2C_CAC4_42E2L, 0x3A57_EACD_A391_7B3CL, + /* 98 */ 0x577D_728A_3BD0_3581L, 0x7B79_88A4_82DA_C8FDL, + /* 99 */ 0x45FD_F53B_630C_F79BL, 0x15FA_D3B6_CF15_6D97L, + /* 100 */ 0x6FFC_BB92_3814_BF5EL, 0x565E_1F8A_E4EF_15BEL, + /* 101 */ 0x5996_FC74_F9AA_32B2L, 0x11E4_E608_B725_AAFFL, + /* 102 */ 0x47AB_FD2A_6154_F55BL, 0x27EA_51A0_9284_88CCL, + /* 103 */ 0x72AC_C843_CEEE_555EL, 0x7310_829A_8407_4146L, + /* 104 */ 0x5BBD_6D03_0BF1_DDE5L, 0x4273_9BAE_D005_CDD2L, + /* 105 */ 0x4964_5735_A327_E4B7L, 0x4EC2_E2F2_4004_A4A8L, + /* 106 */ 0x756D_5855_D1D9_6DF2L, 0x4AD1_6B1D_333A_A10CL, + /* 107 */ 0x5DF1_1377_DB14_57F5L, 0x2241_227D_C295_4DA3L, + /* 108 */ 0x4B27_42C6_48DD_132AL, 0x4E9A_81FE_3544_3E1CL, + /* 109 */ 0x783E_D13D_4161_B844L, 0x175D_9CC9_EED3_9694L, + /* 110 */ 0x6032_40FD_CDE7_C69CL, 0x7917_B0A1_8BDC_7876L, + /* 111 */ 0x4CF5_00CB_0B1F_D217L, 0x1412_F3B4_6FE3_9392L, + /* 112 */ 0x7B21_9ADE_7832_E9BEL, 0x5351_85ED_7FD2_85B6L, + /* 113 */ 0x6281_48B1_F9C2_5498L, 0x42A7_9E57_9975_37C5L, + /* 114 */ 0x4ECD_D3C1_949B_76E0L, 0x3552_E512_E12A_9304L, + /* 115 */ 0x7E16_1F9C_20F8_BE33L, 0x6EEB_081E_3510_EB39L, + /* 116 */ 0x64DE_7FB0_1A60_9829L, 0x3F22_6CE4_F740_BC2EL, + /* 117 */ 0x50B1_FFC0_151A_1354L, 0x3281_F0B7_2C33_C9BEL, + /* 118 */ 0x408E_6633_4414_DC43L, 0x4201_8D5F_568F_D498L, + /* 119 */ 0x674A_3D1E_D354_939FL, 0x1CCF_4898_8A7F_BA8DL, + /* 120 */ 0x52A1_CA7F_0F76_DC7FL, 0x30A5_D3AD_3B99_620BL, + /* 121 */ 0x421B_0865_A5F8_B065L, 0x73B7_DC8A_9614_4E6FL, + /* 122 */ 0x69C4_DA3C_3CC1_1A3CL, 0x52BF_C744_2353_B0B1L, + /* 123 */ 0x549D_7B63_63CD_AE96L, 0x7566_3903_4F76_26F4L, + /* 124 */ 0x43B1_2F82_B63E_2545L, 0x4451_C735_D92B_525DL, + /* 125 */ 0x6C4E_B26A_BD30_3BA2L, 0x3A1C_71EF_C1DE_EA2EL, + /* 126 */ 0x56A5_5B88_9759_C94EL, 0x61B0_5B26_34B2_54F2L, + /* 127 */ 0x4551_1606_DF7B_0772L, 0x1AF3_7C1E_908E_AA5BL, + /* 128 */ 0x6EE8_233E_325E_7250L, 0x2B1F_2CFD_B417_76F8L, + /* 129 */ 0x58B9_B5CB_5B7E_C1D9L, 0x6F4C_23FE_29AC_5F2DL, + /* 130 */ 0x46FA_F7D5_E2CB_CE47L, 0x72A3_4FFE_87BD_18F1L, + /* 131 */ 0x7191_8C89_6ADF_B073L, 0x0438_7FFD_A5FB_5B1BL, + /* 132 */ 0x5ADA_D6D4_557F_C05CL, 0x0360_6664_84C9_15AFL, + /* 133 */ 0x48AF_1243_7799_66B0L, 0x02B3_851D_3707_448CL, + /* 134 */ 0x744B_506B_F28F_0AB3L, 0x1DEC_082E_BE72_0746L, + /* 135 */ 0x5D09_0D23_2872_6EF5L, 0x64BC_D358_985B_3905L, + /* 136 */ 0x4A6D_A41C_205B_8BF7L, 0x6A30_A913_AD15_C738L, + /* 137 */ 0x7715_D360_33C5_ACBFL, 0x5D1A_A81F_7B56_0B8CL, + /* 138 */ 0x5F44_A919_C304_8A32L, 0x7DAE_ECE5_FC44_D609L, + /* 139 */ 0x4C36_EDAE_359D_3B5BL, 0x7E25_8A51_969D_7808L, + /* 140 */ 0x79F1_7C49_EF61_F893L, 0x16A2_76E8_F0FB_F33FL, + /* 141 */ 0x618D_FD07_F2B4_C6DCL, 0x121B_9253_F3FC_C299L, + /* 142 */ 0x4E0B_30D3_2890_9F16L, 0x41AF_A843_2997_0214L, + /* 143 */ 0x7CDE_B485_0DB4_31BDL, 0x4F7F_739E_A8F1_9CEDL, + /* 144 */ 0x63E5_5D37_3E29_C164L, 0x3F99_294B_BA5A_E3F1L, + /* 145 */ 0x4FEA_B0F8_FE87_CDE9L, 0x7FAD_BAA2_FB7B_E98DL, + /* 146 */ 0x7FDD_E7F4_CA72_E30FL, 0x7F7C_5DD1_925F_DC15L, + /* 147 */ 0x664B_1FF7_085B_E8D9L, 0x4C63_7E41_41E6_49ABL, + /* 148 */ 0x51D5_B32C_06AF_ED7AL, 0x704F_9834_34B8_3AEFL, + /* 149 */ 0x4177_C289_9EF3_2462L, 0x26A6_135C_F6F9_C8BFL, + /* 150 */ 0x68BF_9DA8_FE51_D3D0L, 0x3DD6_8561_8B29_4132L, + /* 151 */ 0x53CC_7E20_CB74_A973L, 0x4B12_044E_08ED_CDC2L, + /* 152 */ 0x4309_FE80_A2C3_BAC2L, 0x6F41_9D0B_3A57_D7CEL, + /* 153 */ 0x6B43_30CD_D139_2AD1L, 0x3202_94DE_C3BF_BFB0L, + /* 154 */ 0x55CF_5A3E_40FA_88A7L, 0x419B_AA4B_CFCC_995AL, + /* 155 */ 0x44A5_E1CB_672E_D3B9L, 0x1AE2_EEA3_0CA3_ADE1L, + /* 156 */ 0x6DD6_3612_3EB1_52C1L, 0x77D1_7DD1_ADD2_AFCFL, + /* 157 */ 0x57DE_91A8_3227_7567L, 0x7974_64A7_BE42_263FL, + /* 158 */ 0x464B_A7B9_C1B9_2AB9L, 0x4790_5086_31CE_84FFL, + /* 159 */ 0x7079_0C5C_6928_445CL, 0x0C1A_1A70_4FB0_D4CCL, + /* 160 */ 0x59FA_7049_EDB9_D049L, 0x567B_4859_D95A_43D6L, + /* 161 */ 0x47FB_8D07_F161_736EL, 0x11FC_39E1_7AAE_9CABL, + /* 162 */ 0x732C_14D9_8235_857DL, 0x032D_2968_C44A_9445L, + /* 163 */ 0x5C23_43E1_34F7_9DFDL, 0x4F57_5453_D03B_A9D1L, + /* 164 */ 0x49B5_CFE7_5D92_E4CAL, 0x72AC_4376_402F_BB0EL, + /* 165 */ 0x75EF_B30B_C8EB_07ABL, 0x0446_D256_CD19_2B49L, + /* 166 */ 0x5E59_5C09_6D88_D2EFL, 0x1D05_7512_3DAD_BC3AL, + /* 167 */ 0x4B7A_B007_8AD3_DBF2L, 0x4A6A_C40E_97BE_302FL, + /* 168 */ 0x78C4_4CD8_DE1F_C650L, 0x7711_39B0_F2C9_E6B1L, + /* 169 */ 0x609D_0A47_1819_6B73L, 0x78DA_948D_8F07_EBC1L, + /* 170 */ 0x4D4A_6E9F_467A_BC5CL, 0x60AE_DD3E_0C06_5634L, + /* 171 */ 0x7BAA_4A98_70C4_6094L, 0x344A_FB96_79A3_BD20L, + /* 172 */ 0x62EE_A213_8D69_E6DDL, 0x103B_FC78_614F_CA80L, + /* 173 */ 0x4F25_4E76_0ABB_1F17L, 0x2696_6393_810C_A200L, + /* 174 */ 0x7EA2_1723_445E_9825L, 0x2423_D285_9B47_6999L, + /* 175 */ 0x654E_78E9_037E_E01DL, 0x69B6_4204_7C39_2148L, + /* 176 */ 0x510B_93ED_9C65_8017L, 0x6E2B_6803_9694_1AA0L, + /* 177 */ 0x40D6_0FF1_49EA_CCDFL, 0x71BC_5336_1210_154DL, + /* 178 */ 0x67BC_E64E_DCAA_E166L, 0x1C60_8523_5019_BBAEL, + /* 179 */ 0x52FD_850B_E3BB_E784L, 0x7D1A_041C_4014_9625L, + /* 180 */ 0x4264_6A6F_E963_1F9DL, 0x4A7B_367D_0010_781DL, + /* 181 */ 0x6A3A_43E6_4238_3295L, 0x5D91_F0C8_001A_59C8L, + /* 182 */ 0x54FB_6985_01C6_8EDEL, 0x17A7_F3D3_3348_47D4L, + /* 183 */ 0x43FC_546A_67D2_0BE4L, 0x7953_2975_C2A0_3976L, + /* 184 */ 0x6CC6_ED77_0C83_463BL, 0x0EEB_7589_3766_C256L, + /* 185 */ 0x5705_8AC5_A39C_382FL, 0x2589_2AD4_2C52_3512L, + /* 186 */ 0x459E_089E_1C7C_F9BFL, 0x37A0_EF10_2374_F742L, + /* 187 */ 0x6F63_40FC_FA61_8F98L, 0x5901_7E80_38BB_2536L, + /* 188 */ 0x591C_33FD_951A_D946L, 0x7A67_9866_93C8_EA91L, + /* 189 */ 0x4749_C331_4415_7A9FL, 0x151F_AD1E_DCA0_BBA8L, + /* 190 */ 0x720F_9EB5_39BB_F765L, 0x0832_AE97_C767_92A5L, + /* 191 */ 0x5B3F_B22A_9496_5F84L, 0x068E_F213_05EC_7551L, + /* 192 */ 0x48FF_C1BB_AA11_E603L, 0x1ED8_C1A8_D189_F774L, + /* 193 */ 0x74CC_692C_434F_D66BL, 0x4AF4_690E_1C0F_F253L, + /* 194 */ 0x5D70_5423_690C_AB89L, 0x225D_20D8_1673_2843L, + /* 195 */ 0x4AC0_434F_873D_5607L, 0x3517_4D79_AB8F_5369L, + /* 196 */ 0x779A_054C_0B95_5672L, 0x21BE_E25C_45B2_1F0EL, + /* 197 */ 0x5FAE_6AA3_3C77_785BL, 0x3498_B516_9E28_18D8L, + /* 198 */ 0x4C8B_8882_96C5_F9E2L, 0x5D46_F745_4B53_4713L, + /* 199 */ 0x7A78_DA6A_8AD6_5C9DL, 0x7BA4_BED5_4552_0B52L, + /* 200 */ 0x61FA_4855_3BDE_B07EL, 0x2FB6_FF11_0441_A2A8L, + /* 201 */ 0x4E61_D377_6318_8D31L, 0x72F8_CC0D_9D01_4EEDL, + /* 202 */ 0x7D69_5258_9E8D_AEB6L, 0x1E5A_E015_C802_17E1L, + /* 203 */ 0x6454_41E0_7ED7_BEF8L, 0x1848_B344_A001_ACB4L, + /* 204 */ 0x5043_67E6_CBDF_CBF9L, 0x603A_2903_B334_8A2AL, + /* 205 */ 0x4035_ECB8_A319_6FFBL, 0x002E_8736_28F6_D4EEL, + /* 206 */ 0x66BC_ADF4_3828_B32BL, 0x19E4_0B89_DB24_87E3L, + /* 207 */ 0x5230_8B29_C686_F5BCL, 0x14B6_6FA1_7C1D_3983L, + /* 208 */ 0x41C0_6F54_9ED2_5E30L, 0x1091_F2E7_967D_C79CL, + /* 209 */ 0x6933_E554_3150_96B3L, 0x341C_B7D8_F0C9_3F5FL, + /* 210 */ 0x5429_8443_5AA6_DEF5L, 0x767D_5FE0_C0A0_FF80L, + /* 211 */ 0x4354_69CF_7BB8_B25EL, 0x2B97_7FE7_0080_CC66L, + /* 212 */ 0x6BBA_42E5_92C1_1D63L, 0x5F58_CCA4_CD9A_E0A3L, + /* 213 */ 0x562E_9BEA_DBCD_B11CL, 0x4C47_0A1D_7148_B3B6L, + /* 214 */ 0x44F2_1655_7CA4_8DB0L, 0x3D05_A1B1_276D_5C92L, + /* 215 */ 0x6E50_23BB_FAA0_E2B3L, 0x7B3C_35E8_3F15_60E9L, + /* 216 */ 0x5840_1C96_621A_4EF6L, 0x2F63_5E53_65AA_B3EDL, + /* 217 */ 0x4699_B078_4E7B_725EL, 0x591C_4B75_EAEE_F658L, + /* 218 */ 0x70F5_E726_E3F8_B6FDL, 0x74FA_1256_44B1_8A26L, + /* 219 */ 0x5A5E_5285_832D_5F31L, 0x43FB_41DE_9D5A_D4EBL, + /* 220 */ 0x484B_7537_9C24_4C27L, 0x4FFC_34B2_177B_DD89L, + /* 221 */ 0x73AB_EEBF_603A_1372L, 0x4CC6_BAB6_8BF9_6274L, + /* 222 */ 0x5C89_8BCC_4CFB_42C2L, 0x0A38_955E_D661_1B90L, + /* 223 */ 0x4A07_A309_D72F_689BL, 0x21C6_DDE5_784D_AFA7L, + /* 224 */ 0x7672_9E76_2518_A75EL, 0x693E_2FD5_8D49_190BL, + /* 225 */ 0x5EC2_185E_8413_B918L, 0x5431_BFDE_0AA0_E0D5L, + /* 226 */ 0x4BCE_79E5_3676_2DADL, 0x29C1_664B_3BB3_E711L, + /* 227 */ 0x794A_5CA1_F0BD_15E2L, 0x0F9B_D6DE_C5EC_A4E8L, + /* 228 */ 0x6108_4A1B_26FD_AB1BL, 0x2616_457F_04BD_50BAL, + /* 229 */ 0x4DA0_3B48_EBFE_227CL, 0x1E78_3798_D097_73C8L, + /* 230 */ 0x7C33_920E_4663_6A60L, 0x30C0_58F4_80F2_52D9L, + /* 231 */ 0x635C_74D8_384F_884DL, 0x0D66_AD90_6728_4247L, + /* 232 */ 0x4F7D_2A46_9372_D370L, 0x711E_F140_5286_9B6CL, + /* 233 */ 0x7F2E_AA0A_8584_8581L, 0x34FE_4ECD_50D7_5F14L, + /* 234 */ 0x65BE_EE6E_D136_D134L, 0x2A65_0BD7_73DF_7F43L, + /* 235 */ 0x5165_8B8B_DA92_40F6L, 0x551D_A312_C319_329CL, + /* 236 */ 0x411E_093C_AEDB_672BL, 0x5DB1_4F42_35AD_C217L, + /* 237 */ 0x6830_0EC7_7E2B_D845L, 0x7C4E_E536_BC49_368AL, + /* 238 */ 0x5359_A56C_64EF_E037L, 0x7D0B_EA92_303A_9208L, + /* 239 */ 0x42AE_1DF0_50BF_E693L, 0x173C_BBA8_2695_41A0L, + /* 240 */ 0x6AB0_2FE6_E799_70EBL, 0x3EC7_92A6_A422_029AL, + /* 241 */ 0x5559_BFEB_EC7A_C0BCL, 0x3239_421E_E9B4_CEE1L, + /* 242 */ 0x4447_CCBC_BD2F_0096L, 0x5B61_01B2_5490_A581L, + /* 243 */ 0x6D3F_ADFA_C84B_3424L, 0x2BCE_691D_541A_A268L, + /* 244 */ 0x5766_24C8_A03C_29B6L, 0x563E_BA7D_DCE2_1B87L, + /* 245 */ 0x45EB_50A0_8030_215EL, 0x7832_2ECB_171B_4939L, + /* 246 */ 0x6FDE_E767_3380_3564L, 0x59E9_E478_24F8_7527L, + /* 247 */ 0x597F_1F85_C2CC_F783L, 0x6187_E9F9_B72D_2A86L, + /* 248 */ 0x4798_E604_9BD7_2C69L, 0x346C_BB2E_2C24_2205L, + /* 249 */ 0x728E_3CD4_2C8B_7A42L, 0x20AD_F849_E039_D007L, + /* 250 */ 0x5BA4_FD76_8A09_2E9BL, 0x33BE_603B_19C7_D99FL, + /* 251 */ 0x4950_CAC5_3B3A_8BAFL, 0x42FE_B362_7B06_47B3L, + /* 252 */ 0x754E_113B_91F7_45E5L, 0x5197_856A_5E70_72B8L, + /* 253 */ 0x5DD8_0DC9_4192_9E51L, 0x27AC_6ABB_7EC0_5BC6L, + /* 254 */ 0x4B13_3E3A_9ADB_B1DAL, 0x52F0_5562_CBCD_1638L, + /* 255 */ 0x781E_C9F7_5E2C_4FC4L, 0x1E4D_556A_DFAE_89F3L, + /* 256 */ 0x6018_A192_B1BD_0C9CL, 0x7EA4_4455_7FBE_D4C3L, + /* 257 */ 0x4CE0_8142_27CA_707DL, 0x4BB6_9D11_32FF_109CL, + /* 258 */ 0x7B00_CED0_3FAA_4D95L, 0x5F8A_94E8_5198_1A93L, + /* 259 */ 0x6267_0BD9_CC88_3E11L, 0x32D5_43ED_0E13_4875L, + /* 260 */ 0x4EB8_D647_D6D3_64DAL, 0x5BDD_CFF0_D80F_6D2BL, + /* 261 */ 0x7DF4_8A0C_8AEB_D491L, 0x12FC_7FE7_C018_AEABL, + /* 262 */ 0x64C3_A1A3_A256_43A7L, 0x28C9_FFEC_99AD_5889L, + /* 263 */ 0x509C_814F_B511_CFB9L, 0x0707_FFF0_7AF1_13A1L, + /* 264 */ 0x407D_343F_C40E_3FC7L, 0x1F39_998D_2F27_42E7L, + /* 265 */ 0x672E_B9FF_A016_CC71L, 0x7EC2_8F48_4B72_04A4L, + /* 266 */ 0x528B_C7FF_B345_705BL, 0x189B_A5D3_6F8E_6A1DL, + /* 267 */ 0x4209_6CCC_8F6A_C048L, 0x7A16_1E42_BFA5_21B1L, + /* 268 */ 0x69A8_AE14_18AA_CD41L, 0x4356_96D1_32A1_CF81L, + /* 269 */ 0x5486_F1A9_AD55_7101L, 0x1C45_4574_2881_72CEL, + /* 270 */ 0x439F_27BA_F111_2734L, 0x169D_D129_BA01_28A5L, + /* 271 */ 0x6C31_D92B_1B4E_A520L, 0x242F_B50F_9001_DAA1L, + /* 272 */ 0x568E_4755_AF72_1DB3L, 0x368C_90D9_4001_7BB4L, + /* 273 */ 0x453E_9F77_BF8E_7E29L, 0x120A_0D7A_999A_C95DL, + /* 274 */ 0x6ECA_98BF_98E3_FD0EL, 0x5010_1590_F5C4_7561L, + /* 275 */ 0x58A2_13CC_7A4F_FDA5L, 0x2673_4473_F7D0_5DE8L, + /* 276 */ 0x46E8_0FD6_C83F_FE1DL, 0x6B8F_69F6_5FD9_E4B9L, + /* 277 */ 0x7173_4C8A_D9FF_FCFCL, 0x45B2_4323_CC8F_D45CL, + /* 278 */ 0x5AC2_A3A2_47FF_FD96L, 0x6AF5_0283_0A0C_A9E3L, + /* 279 */ 0x489B_B61B_6CCC_CADFL, 0x08C4_0202_6E70_87E9L, + /* 280 */ 0x742C_5692_47AE_1164L, 0x746C_D003_E3E7_3FDBL, + /* 281 */ 0x5CF0_4541_D2F1_A783L, 0x76BD_7336_4FEC_3315L, + /* 282 */ 0x4A59_D101_758E_1F9CL, 0x5EFD_F5C5_0CBC_F5ABL, + /* 283 */ 0x76F6_1B35_88E3_65C7L, 0x4B2F_EFA1_ADFB_22ABL, + /* 284 */ 0x5F2B_48F7_A0B5_EB06L, 0x08F3_261A_F195_B555L, + /* 285 */ 0x4C22_A0C6_1A2B_226BL, 0x20C2_84E2_5ADE_2AABL, + /* 286 */ 0x79D1_013C_F6AB_6A45L, 0x1AD0_D49D_5E30_4444L, + /* 287 */ 0x6174_00FD_9222_BB6AL, 0x48A7_107D_E4F3_69D0L, + /* 288 */ 0x4DF6_6731_41B5_62BBL, 0x53B8_D9FE_50C2_BB0DL, + /* 289 */ 0x7CBD_71E8_6922_3792L, 0x52C1_5CCA_1AD1_2B48L, + /* 290 */ 0x63CA_C186_BA81_C60EL, 0x7567_7D6E_7BDA_8906L, + /* 291 */ 0x4FD5_679E_FB9B_04D8L, 0x5DEC_6458_6315_3A6CL, + /* 292 */ 0x7FBB_D8FE_5F5E_6E27L, 0x497A_3A27_04EE_C3DFL, + }; + +} \ No newline at end of file diff --git a/src/test/java/com/fasterxml/jackson/core/io/schubfach/DoubleToStringTest.java b/src/test/java/com/fasterxml/jackson/core/io/schubfach/DoubleToStringTest.java new file mode 100644 index 0000000000..3d678e62ed --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/io/schubfach/DoubleToStringTest.java @@ -0,0 +1,75 @@ +package com.fasterxml.jackson.core.io.schubfach; + +import com.fasterxml.jackson.core.io.ryu.RoundingMode; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public abstract class DoubleToStringTest { + abstract String f(double f); + + private void assertD2sEquals(String expected, double f) { + assertEquals(expected, f(f)); + } + + @Test + public void simpleCases() { + assertD2sEquals("0.0", 0); + assertD2sEquals("-0.0", Double.longBitsToDouble(0x8000000000000000L)); + assertD2sEquals("1.0", 1.0d); + assertD2sEquals("-1.0", -1.0d); + assertD2sEquals("NaN", Double.NaN); + assertD2sEquals("Infinity", Double.POSITIVE_INFINITY); + assertD2sEquals("-Infinity", Double.NEGATIVE_INFINITY); + } + + @Test + public void switchToSubnormal() { + assertD2sEquals("2.2250738585072014E-308", Double.longBitsToDouble(0x0010000000000000L)); + } + + /** + * Floating point values in the range 1.0E-3 <= x < 1.0E7 have to be printed + * without exponent. This test checks the values at those boundaries. + */ + @Test + public void boundaryConditions() { + // x = 1.0E7 + assertD2sEquals("1.0E7", 1.0E7d); + // x < 1.0E7 + assertD2sEquals("9999999.999999998", 9999999.999999998d); + // x = 1.0E-3 + assertD2sEquals("0.001", 0.001d); + // x < 1.0E-3 + assertD2sEquals("9.999999999999998E-4", 0.0009999999999999998d); + } + + @Test + public void minAndMax() { + assertD2sEquals("1.7976931348623157E308", Double.longBitsToDouble(0x7fefffffffffffffL)); + assertD2sEquals("4.9E-324", Double.longBitsToDouble(1)); + } + + @Test + public void roundingModeEven() { + assertD2sEquals("-2.109808898695963E16", -2.109808898695963E16); + } + + @Test + public void regressionTest() { + assertD2sEquals("4.940656E-318", 4.940656E-318d); + assertD2sEquals("1.18575755E-316", 1.18575755E-316d); + assertD2sEquals("2.989102097996E-312", 2.989102097996E-312d); + assertD2sEquals("9.0608011534336E15", 9.0608011534336E15d); + assertD2sEquals("4.708356024711512E18", 4.708356024711512E18); + assertD2sEquals("9.409340012568248E18", 9.409340012568248E18); + // This number naively requires 65 bit for the intermediate results if we reduce the lookup + // table by half. This checks that we don't loose any information in that case. + assertD2sEquals("1.8531501765868567E21", 1.8531501765868567E21); + assertD2sEquals("-3.347727380279489E33", -3.347727380279489E33); + // Discovered by Andriy Plokhotnyuk, see #29. + assertD2sEquals("1.9430376160308388E16", 1.9430376160308388E16); + assertD2sEquals("-6.9741824662760956E19", -6.9741824662760956E19); + assertD2sEquals("4.3816050601147837E18", 4.3816050601147837E18); + } +} \ No newline at end of file diff --git a/src/test/java/com/fasterxml/jackson/core/io/schubfach/FloatToStringTest.java b/src/test/java/com/fasterxml/jackson/core/io/schubfach/FloatToStringTest.java new file mode 100644 index 0000000000..6f70811320 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/io/schubfach/FloatToStringTest.java @@ -0,0 +1,106 @@ +package com.fasterxml.jackson.core.io.schubfach; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public abstract class FloatToStringTest { + abstract String f(float f); + + private void assertF2sEquals(String expected, float f) { + assertEquals(expected, f(f)); + } + + @Test + public void simpleCases() { + assertF2sEquals("0.0", 0); + assertF2sEquals("-0.0", Float.intBitsToFloat(0x80000000)); + assertF2sEquals("1.0", 1.0f); + assertF2sEquals("-1.0", -1f); + assertF2sEquals("NaN", Float.NaN); + assertF2sEquals("Infinity", Float.POSITIVE_INFINITY); + assertF2sEquals("-Infinity", Float.NEGATIVE_INFINITY); + } + + @Test + public void switchToSubnormal() { + assertF2sEquals("1.1754944E-38", Float.intBitsToFloat(0x00800000)); + } + + /** + * Floating point values in the range 1.0E-3 <= x < 1.0E7 have to be printed + * without exponent. This test checks the values at those boundaries. + */ + @Test + public void boundaryConditions() { + // x = 1.0E7 + assertF2sEquals("1.0E7", 1.0E7f); + // x < 1.0E7 + assertF2sEquals("9999999.0", 9999999.0f); + // x = 1.0E-3 + assertF2sEquals("0.001", 0.001f); + // x < 1.0E-3 + assertF2sEquals("9.999999E-4", 0.0009999999f); + } + + @Test + public void minAndMax() { + assertF2sEquals("3.4028235E38", Float.intBitsToFloat(0x7f7fffff)); + assertF2sEquals("1.4E-45", Float.intBitsToFloat(0x00000001)); + } + + @Test + public void roundingModeEven() { + assertF2sEquals("3.355445E7", 3.3554448E7f); + assertF2sEquals("9.0E9", 8.999999E9f); + assertF2sEquals("3.436672E10", 3.4366717E10f); + } + + @Test + public void roundingEvenIfTied() { + assertF2sEquals("0.33007812", 0.33007812f); + } + + @Test + public void looksLikePow5() { + // These are all floating point numbers where the mantissa is a power of 5, + // and the exponent is in the range such that q = 10. + assertF2sEquals("6.7108864E17", Float.intBitsToFloat(0x5D1502F9)); + assertF2sEquals("1.3421773E18", Float.intBitsToFloat(0x5D9502F9)); + assertF2sEquals("2.6843546E18", Float.intBitsToFloat(0x5E1502F9)); + } + + @Test + public void regressionTest() { + assertF2sEquals("4.7223665E21", 4.7223665E21f); + assertF2sEquals("8388608.0", 8388608.0f); + assertF2sEquals("1.6777216E7", 1.6777216E7f); + assertF2sEquals("3.3554436E7", 3.3554436E7f); + assertF2sEquals("6.7131496E7", 6.7131496E7f); + assertF2sEquals("1.9310392E-38", 1.9310392E-38f); + assertF2sEquals("-2.47E-43", -2.47E-43f); + assertF2sEquals("1.993244E-38", 1.993244E-38f); + assertF2sEquals("4103.9004", 4103.9003f); + assertF2sEquals("5.3399997E9", 5.3399997E9f); + assertF2sEquals("6.0898E-39", 6.0898E-39f); + assertF2sEquals("0.0010310042", 0.0010310042f); + assertF2sEquals("2.882326E17", 2.8823261E17f); + assertF2sEquals("7.038531E-26", 7.038531E-26f); + assertF2sEquals("9.223404E17", 9.2234038E17f); + assertF2sEquals("6.710887E7", 6.7108872E7f); + //TODO investigate + assertF2sEquals("9.8E-45", 1.0E-44f); + assertF2sEquals("2.816025E14", 2.816025E14f); + assertF2sEquals("9.223372E18", 9.223372E18f); + assertF2sEquals("1.5846086E29", 1.5846085E29f); + assertF2sEquals("1.1811161E19", 1.1811161E19f); + assertF2sEquals("5.368709E18", 5.368709E18f); + assertF2sEquals("4.6143166E18", 4.6143165E18f); + assertF2sEquals("0.007812537", 0.007812537f); + assertF2sEquals("1.4E-45", 1.4E-45f); + assertF2sEquals("1.18697725E20", 1.18697724E20f); + assertF2sEquals("1.00014165E-36", 1.00014165E-36f); + assertF2sEquals("200.0", 200f); + assertF2sEquals("3.3554432E7", 3.3554432E7f); + } +} diff --git a/src/test/java/com/fasterxml/jackson/core/io/schubfach/SchubfachDoubleTest.java b/src/test/java/com/fasterxml/jackson/core/io/schubfach/SchubfachDoubleTest.java new file mode 100644 index 0000000000..99f1b36e10 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/io/schubfach/SchubfachDoubleTest.java @@ -0,0 +1,12 @@ +package com.fasterxml.jackson.core.io.schubfach; + +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class SchubfachDoubleTest extends DoubleToStringTest { + @Override + String f(double f) { + return DoubleToDecimal.toString(f); + } +} \ No newline at end of file diff --git a/src/test/java/com/fasterxml/jackson/core/io/schubfach/SchubfachFloatTest.java b/src/test/java/com/fasterxml/jackson/core/io/schubfach/SchubfachFloatTest.java new file mode 100644 index 0000000000..64e7e9d76b --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/io/schubfach/SchubfachFloatTest.java @@ -0,0 +1,12 @@ +package com.fasterxml.jackson.core.io.schubfach; + +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class SchubfachFloatTest extends FloatToStringTest { + @Override + String f(float f) { + return FloatToDecimal.toString(f); + } +} \ No newline at end of file From e88d81de8de63882bbeff1bd22d877f770349e43 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Tue, 12 Apr 2022 16:00:20 +0200 Subject: [PATCH 05/17] add some missing eols --- .../java/com/fasterxml/jackson/core/io/schubfach/MathUtils.java | 2 +- .../java/com/fasterxml/jackson/core/io/ryu/DoubleUtils.java | 2 +- .../java/com/fasterxml/jackson/core/io/ryu/RyuDoubleTest.java | 2 +- .../java/com/fasterxml/jackson/core/io/ryu/SlowDoubleTest.java | 2 +- .../java/com/fasterxml/jackson/core/io/ryu/SlowFloatTest.java | 2 +- .../fasterxml/jackson/core/io/schubfach/DoubleToStringTest.java | 2 +- .../jackson/core/io/schubfach/SchubfachDoubleTest.java | 2 +- .../fasterxml/jackson/core/io/schubfach/SchubfachFloatTest.java | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/core/io/schubfach/MathUtils.java b/src/main/java/com/fasterxml/jackson/core/io/schubfach/MathUtils.java index 3ef0410b2a..b844959aa0 100644 --- a/src/main/java/com/fasterxml/jackson/core/io/schubfach/MathUtils.java +++ b/src/main/java/com/fasterxml/jackson/core/io/schubfach/MathUtils.java @@ -820,4 +820,4 @@ The precomputed values for g1(int) and g0(int). /* 292 */ 0x7FBB_D8FE_5F5E_6E27L, 0x497A_3A27_04EE_C3DFL, }; -} \ No newline at end of file +} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/DoubleUtils.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/DoubleUtils.java index 8c3cd7f13a..36236ff4e8 100644 --- a/src/test/java/com/fasterxml/jackson/core/io/ryu/DoubleUtils.java +++ b/src/test/java/com/fasterxml/jackson/core/io/ryu/DoubleUtils.java @@ -210,4 +210,4 @@ else if (exp < 0 && exp > -5) } return str.toString (); } -} \ No newline at end of file +} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/RyuDoubleTest.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/RyuDoubleTest.java index 02a4deea62..8288280d11 100644 --- a/src/test/java/com/fasterxml/jackson/core/io/ryu/RyuDoubleTest.java +++ b/src/test/java/com/fasterxml/jackson/core/io/ryu/RyuDoubleTest.java @@ -23,4 +23,4 @@ public class RyuDoubleTest extends DoubleToStringTest { String f(double f, RoundingMode roundingMode) { return RyuDouble.doubleToString(f, roundingMode); } -} \ No newline at end of file +} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/SlowDoubleTest.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/SlowDoubleTest.java index 1940f75ba8..7720fd5001 100644 --- a/src/test/java/com/fasterxml/jackson/core/io/ryu/SlowDoubleTest.java +++ b/src/test/java/com/fasterxml/jackson/core/io/ryu/SlowDoubleTest.java @@ -25,4 +25,4 @@ public class SlowDoubleTest extends DoubleToStringTest { String f(double f, RoundingMode roundingMode) { return SlowConversion.doubleToString(f, roundingMode); } -} \ No newline at end of file +} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/SlowFloatTest.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/SlowFloatTest.java index e167f2af54..3c6fd22c14 100644 --- a/src/test/java/com/fasterxml/jackson/core/io/ryu/SlowFloatTest.java +++ b/src/test/java/com/fasterxml/jackson/core/io/ryu/SlowFloatTest.java @@ -21,4 +21,4 @@ public class SlowFloatTest extends FloatToStringTest { String f(float f, RoundingMode roundingMode) { return SlowConversion.floatToString(f, roundingMode); } -} \ No newline at end of file +} diff --git a/src/test/java/com/fasterxml/jackson/core/io/schubfach/DoubleToStringTest.java b/src/test/java/com/fasterxml/jackson/core/io/schubfach/DoubleToStringTest.java index 3d678e62ed..194cd643d2 100644 --- a/src/test/java/com/fasterxml/jackson/core/io/schubfach/DoubleToStringTest.java +++ b/src/test/java/com/fasterxml/jackson/core/io/schubfach/DoubleToStringTest.java @@ -72,4 +72,4 @@ public void regressionTest() { assertD2sEquals("-6.9741824662760956E19", -6.9741824662760956E19); assertD2sEquals("4.3816050601147837E18", 4.3816050601147837E18); } -} \ No newline at end of file +} diff --git a/src/test/java/com/fasterxml/jackson/core/io/schubfach/SchubfachDoubleTest.java b/src/test/java/com/fasterxml/jackson/core/io/schubfach/SchubfachDoubleTest.java index 99f1b36e10..13258cac1a 100644 --- a/src/test/java/com/fasterxml/jackson/core/io/schubfach/SchubfachDoubleTest.java +++ b/src/test/java/com/fasterxml/jackson/core/io/schubfach/SchubfachDoubleTest.java @@ -9,4 +9,4 @@ public class SchubfachDoubleTest extends DoubleToStringTest { String f(double f) { return DoubleToDecimal.toString(f); } -} \ No newline at end of file +} diff --git a/src/test/java/com/fasterxml/jackson/core/io/schubfach/SchubfachFloatTest.java b/src/test/java/com/fasterxml/jackson/core/io/schubfach/SchubfachFloatTest.java index 64e7e9d76b..6f1a0c04ca 100644 --- a/src/test/java/com/fasterxml/jackson/core/io/schubfach/SchubfachFloatTest.java +++ b/src/test/java/com/fasterxml/jackson/core/io/schubfach/SchubfachFloatTest.java @@ -9,4 +9,4 @@ public class SchubfachFloatTest extends FloatToStringTest { String f(float f) { return FloatToDecimal.toString(f); } -} \ No newline at end of file +} From 4a3a10b528f19acb72288a60419e21e3a7cd6ec4 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Tue, 12 Apr 2022 16:14:58 +0200 Subject: [PATCH 06/17] comments --- .../fasterxml/jackson/core/io/schubfach/DoubleToDecimal.java | 2 ++ .../com/fasterxml/jackson/core/io/schubfach/FloatToDecimal.java | 2 ++ .../java/com/fasterxml/jackson/core/io/schubfach/MathUtils.java | 2 ++ .../com/fasterxml/jackson/core/io/ryu/DoubleToStringTest.java | 2 +- .../com/fasterxml/jackson/core/io/ryu/JafferDoubleTest.java | 2 +- .../java/com/fasterxml/jackson/core/io/ryu/RyuFloatTest.java | 2 +- .../jackson/core/io/ryu/analysis/FloatingPointFormat.java | 2 +- 7 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/core/io/schubfach/DoubleToDecimal.java b/src/main/java/com/fasterxml/jackson/core/io/schubfach/DoubleToDecimal.java index ccb5f8b2f6..2b7778a316 100644 --- a/src/main/java/com/fasterxml/jackson/core/io/schubfach/DoubleToDecimal.java +++ b/src/main/java/com/fasterxml/jackson/core/io/schubfach/DoubleToDecimal.java @@ -55,6 +55,8 @@ final public class DoubleToDecimal { This is discussed in section 10 of [1]. */ + // Sources with the license are here: https://github.com/c4f7fcce9cb06515/Schubfach/blob/3c92d3c9b1fead540616c918cdfef432bca53dfa/todec/src/math/FloatToDecimal.java + // The precision in bits. static final int P = 53; diff --git a/src/main/java/com/fasterxml/jackson/core/io/schubfach/FloatToDecimal.java b/src/main/java/com/fasterxml/jackson/core/io/schubfach/FloatToDecimal.java index 56d2c7c235..9ce9cc2b34 100644 --- a/src/main/java/com/fasterxml/jackson/core/io/schubfach/FloatToDecimal.java +++ b/src/main/java/com/fasterxml/jackson/core/io/schubfach/FloatToDecimal.java @@ -54,6 +54,8 @@ final public class FloatToDecimal { This is discussed in section 10 of [1]. */ + // Sources with the license are here: https://github.com/c4f7fcce9cb06515/Schubfach/blob/3c92d3c9b1fead540616c918cdfef432bca53dfa/todec/src/math/FloatToDecimal.java + // The precision in bits. static final int P = 24; diff --git a/src/main/java/com/fasterxml/jackson/core/io/schubfach/MathUtils.java b/src/main/java/com/fasterxml/jackson/core/io/schubfach/MathUtils.java index b844959aa0..c8e89d7c8b 100644 --- a/src/main/java/com/fasterxml/jackson/core/io/schubfach/MathUtils.java +++ b/src/main/java/com/fasterxml/jackson/core/io/schubfach/MathUtils.java @@ -37,6 +37,8 @@ final class MathUtils { https://drive.google.com/open?id=1luHhyQF9zKlM8yJ1nebU0OgVYhfC6CBN */ + // Sources with the license are here: https://github.com/c4f7fcce9cb06515/Schubfach/blob/3c92d3c9b1fead540616c918cdfef432bca53dfa/todec/src/math/FloatToDecimal.java + /* The boundaries for k in g0(int) and g1(int). K_MIN must be DoubleToDecimal.K_MIN or less. diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/DoubleToStringTest.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/DoubleToStringTest.java index ec18f041fc..d0ca1e1357 100644 --- a/src/test/java/com/fasterxml/jackson/core/io/ryu/DoubleToStringTest.java +++ b/src/test/java/com/fasterxml/jackson/core/io/ryu/DoubleToStringTest.java @@ -91,4 +91,4 @@ public void regressionTest() { assertD2sEquals("-6.9741824662760956E19", -6.9741824662760956E19); assertD2sEquals("4.3816050601147837E18", 4.3816050601147837E18); } -} \ No newline at end of file +} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/JafferDoubleTest.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/JafferDoubleTest.java index c6e8b9471a..4cb00a9907 100644 --- a/src/test/java/com/fasterxml/jackson/core/io/ryu/JafferDoubleTest.java +++ b/src/test/java/com/fasterxml/jackson/core/io/ryu/JafferDoubleTest.java @@ -69,4 +69,4 @@ public void regressionTest() { assertEquals("4.708356024711512e18", f(4.708356024711512E18)); assertEquals("9.409340012568248e18", f(9.409340012568248E18)); } -} \ No newline at end of file +} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/RyuFloatTest.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/RyuFloatTest.java index 8259ad07f7..eb6d52027b 100644 --- a/src/test/java/com/fasterxml/jackson/core/io/ryu/RyuFloatTest.java +++ b/src/test/java/com/fasterxml/jackson/core/io/ryu/RyuFloatTest.java @@ -23,4 +23,4 @@ public class RyuFloatTest extends FloatToStringTest { String f(float f, RoundingMode roundingMode) { return RyuFloat.floatToString(f, roundingMode); } -} \ No newline at end of file +} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/FloatingPointFormat.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/FloatingPointFormat.java index 96cbf7ad8a..9f4b89bc6b 100644 --- a/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/FloatingPointFormat.java +++ b/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/FloatingPointFormat.java @@ -47,4 +47,4 @@ public int mantissaBits() { public int bias() { return (1 << (exponentBits - 1)) - 1; } -} \ No newline at end of file +} From 03421093eb442fb19af0bbf084b588006af0265c Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Thu, 14 Apr 2022 14:35:09 +0200 Subject: [PATCH 07/17] add tests --- .../core/io/jdk/DoubleToStringTest.java | 76 ++++++++++++ .../core/io/jdk/FloatToStringTest.java | 113 ++++++++++++++++++ .../jackson/core/io/jdk/JdkDoubleTest.java | 12 ++ .../jackson/core/io/jdk/JdkFloatTest.java | 12 ++ 4 files changed, 213 insertions(+) create mode 100644 src/test/java/com/fasterxml/jackson/core/io/jdk/DoubleToStringTest.java create mode 100644 src/test/java/com/fasterxml/jackson/core/io/jdk/FloatToStringTest.java create mode 100644 src/test/java/com/fasterxml/jackson/core/io/jdk/JdkDoubleTest.java create mode 100644 src/test/java/com/fasterxml/jackson/core/io/jdk/JdkFloatTest.java diff --git a/src/test/java/com/fasterxml/jackson/core/io/jdk/DoubleToStringTest.java b/src/test/java/com/fasterxml/jackson/core/io/jdk/DoubleToStringTest.java new file mode 100644 index 0000000000..cf5fca183b --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/io/jdk/DoubleToStringTest.java @@ -0,0 +1,76 @@ +package com.fasterxml.jackson.core.io.jdk; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public abstract class DoubleToStringTest { + abstract String f(double f); + + private void assertD2sEquals(String expected, double f) { + assertEquals(expected, f(f)); + } + + @Test + public void simpleCases() { + assertD2sEquals("0.0", 0); + assertD2sEquals("-0.0", Double.longBitsToDouble(0x8000000000000000L)); + assertD2sEquals("1.0", 1.0d); + assertD2sEquals("-1.0", -1.0d); + assertD2sEquals("NaN", Double.NaN); + assertD2sEquals("Infinity", Double.POSITIVE_INFINITY); + assertD2sEquals("-Infinity", Double.NEGATIVE_INFINITY); + } + + @Test + public void switchToSubnormal() { + assertD2sEquals("2.2250738585072014E-308", Double.longBitsToDouble(0x0010000000000000L)); + } + + /** + * Floating point values in the range 1.0E-3 <= x < 1.0E7 have to be printed + * without exponent. This test checks the values at those boundaries. + */ + @Test + public void boundaryConditions() { + // x = 1.0E7 + assertD2sEquals("1.0E7", 1.0E7d); + // x < 1.0E7 + assertD2sEquals("9999999.999999998", 9999999.999999998d); + // x = 1.0E-3 + assertD2sEquals("0.001", 0.001d); + // x < 1.0E-3 + assertD2sEquals("9.999999999999998E-4", 0.0009999999999999998d); + } + + @Test + public void minAndMax() { + assertD2sEquals("1.7976931348623157E308", Double.longBitsToDouble(0x7fefffffffffffffL)); + assertD2sEquals("4.9E-324", Double.longBitsToDouble(1)); + } + + @Test + public void roundingModeEven() { + //TODO result differs from Schubfach + assertD2sEquals("-2.1098088986959632E16", -2.109808898695963E16); + } + + @Test + public void regressionTest() { + assertD2sEquals("4.940656E-318", 4.940656E-318d); + assertD2sEquals("1.18575755E-316", 1.18575755E-316d); + assertD2sEquals("2.989102097996E-312", 2.989102097996E-312d); + assertD2sEquals("9.0608011534336E15", 9.0608011534336E15d); + //TODO next result differs from Schubfach + assertD2sEquals("4.7083560247115121E18", 4.708356024711512E18); + assertD2sEquals("9.409340012568248E18", 9.409340012568248E18); + // This number naively requires 65 bit for the intermediate results if we reduce the lookup + // table by half. This checks that we don't loose any information in that case. + assertD2sEquals("1.8531501765868567E21", 1.8531501765868567E21); + assertD2sEquals("-3.347727380279489E33", -3.347727380279489E33); + // Discovered by Andriy Plokhotnyuk, see #29. + assertD2sEquals("1.9430376160308388E16", 1.9430376160308388E16); + assertD2sEquals("-6.9741824662760956E19", -6.9741824662760956E19); + assertD2sEquals("4.3816050601147837E18", 4.3816050601147837E18); + } +} diff --git a/src/test/java/com/fasterxml/jackson/core/io/jdk/FloatToStringTest.java b/src/test/java/com/fasterxml/jackson/core/io/jdk/FloatToStringTest.java new file mode 100644 index 0000000000..a835870d24 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/io/jdk/FloatToStringTest.java @@ -0,0 +1,113 @@ +package com.fasterxml.jackson.core.io.jdk; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public abstract class FloatToStringTest { + abstract String f(float f); + + private void assertF2sEquals(String expected, float f) { + assertEquals(expected, f(f)); + } + + @Test + public void simpleCases() { + assertF2sEquals("0.0", 0); + assertF2sEquals("-0.0", Float.intBitsToFloat(0x80000000)); + assertF2sEquals("1.0", 1.0f); + assertF2sEquals("-1.0", -1f); + assertF2sEquals("NaN", Float.NaN); + assertF2sEquals("Infinity", Float.POSITIVE_INFINITY); + assertF2sEquals("-Infinity", Float.NEGATIVE_INFINITY); + } + + @Test + public void switchToSubnormal() { + //TODO next one different than Schubfach + assertF2sEquals("1.17549435E-38", Float.intBitsToFloat(0x00800000)); + } + + /** + * Floating point values in the range 1.0E-3 <= x < 1.0E7 have to be printed + * without exponent. This test checks the values at those boundaries. + */ + @Test + public void boundaryConditions() { + // x = 1.0E7 + assertF2sEquals("1.0E7", 1.0E7f); + // x < 1.0E7 + assertF2sEquals("9999999.0", 9999999.0f); + // x = 1.0E-3 + assertF2sEquals("0.001", 0.001f); + // x < 1.0E-3 + assertF2sEquals("9.999999E-4", 0.0009999999f); + } + + @Test + public void minAndMax() { + assertF2sEquals("3.4028235E38", Float.intBitsToFloat(0x7f7fffff)); + assertF2sEquals("1.4E-45", Float.intBitsToFloat(0x00000001)); + } + + @Test + public void roundingModeEven() { + //TODO all of these differ than Schubfach + assertF2sEquals("3.3554448E7", 3.3554448E7f); + assertF2sEquals("8.9999995E9", 8.999999E9f); + assertF2sEquals("3.4366718E10", 3.4366717E10f); + } + + @Test + public void roundingEvenIfTied() { + assertF2sEquals("0.33007812", 0.33007812f); + } + + @Test + public void looksLikePow5() { + // These are all floating point numbers where the mantissa is a power of 5, + // and the exponent is in the range such that q = 10. + assertF2sEquals("6.7108864E17", Float.intBitsToFloat(0x5D1502F9)); + //TODO next 2 are slightly different from Schubfach + assertF2sEquals("1.34217728E18", Float.intBitsToFloat(0x5D9502F9)); + assertF2sEquals("2.68435456E18", Float.intBitsToFloat(0x5E1502F9)); + } + + @Test + public void regressionTest() { + assertF2sEquals("4.7223665E21", 4.7223665E21f); + assertF2sEquals("8388608.0", 8388608.0f); + assertF2sEquals("1.6777216E7", 1.6777216E7f); + assertF2sEquals("3.3554436E7", 3.3554436E7f); + assertF2sEquals("6.7131496E7", 6.7131496E7f); + assertF2sEquals("1.9310392E-38", 1.9310392E-38f); + assertF2sEquals("-2.47E-43", -2.47E-43f); + assertF2sEquals("1.993244E-38", 1.993244E-38f); + assertF2sEquals("4103.9004", 4103.9003f); + assertF2sEquals("5.3399997E9", 5.3399997E9f); + assertF2sEquals("6.0898E-39", 6.0898E-39f); + assertF2sEquals("0.0010310042", 0.0010310042f); + //TODO next one is more accurate than Schubfach + assertF2sEquals("2.8823261E17", 2.8823261E17f); + assertF2sEquals("7.038531E-26", 7.038531E-26f); + //TODO next 2 are accurate than Schubfach + assertF2sEquals("9.2234038E17", 9.2234038E17f); + assertF2sEquals("6.7108872E7", 6.7108872E7f); + //TODO next one matches Schubfach but not Ryu (Ryu is more accurate) + assertF2sEquals("9.8E-45", 1.0E-44f); + //TODO next one is less accurate than Schubfach + assertF2sEquals("2.81602484E14", 2.816025E14f); + assertF2sEquals("9.223372E18", 9.223372E18f); + assertF2sEquals("1.5846086E29", 1.5846085E29f); + assertF2sEquals("1.1811161E19", 1.1811161E19f); + //TODO next one is less accurate than Schubfach + assertF2sEquals("5.3687091E18", 5.368709E18f); + assertF2sEquals("4.6143166E18", 4.6143165E18f); + assertF2sEquals("0.007812537", 0.007812537f); + assertF2sEquals("1.4E-45", 1.4E-45f); + assertF2sEquals("1.18697725E20", 1.18697724E20f); + assertF2sEquals("1.00014165E-36", 1.00014165E-36f); + assertF2sEquals("200.0", 200f); + assertF2sEquals("3.3554432E7", 3.3554432E7f); + } +} diff --git a/src/test/java/com/fasterxml/jackson/core/io/jdk/JdkDoubleTest.java b/src/test/java/com/fasterxml/jackson/core/io/jdk/JdkDoubleTest.java new file mode 100644 index 0000000000..29074565be --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/io/jdk/JdkDoubleTest.java @@ -0,0 +1,12 @@ +package com.fasterxml.jackson.core.io.jdk; + +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class JdkDoubleTest extends DoubleToStringTest { + @Override + String f(double f) { + return Double.toString(f); + } +} diff --git a/src/test/java/com/fasterxml/jackson/core/io/jdk/JdkFloatTest.java b/src/test/java/com/fasterxml/jackson/core/io/jdk/JdkFloatTest.java new file mode 100644 index 0000000000..3eb86d9269 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/io/jdk/JdkFloatTest.java @@ -0,0 +1,12 @@ +package com.fasterxml.jackson.core.io.jdk; + +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class JdkFloatTest extends FloatToStringTest { + @Override + String f(float f) { + return Float.toString(f); + } +} From edfd7cf359f78116c725fbe6f5736c6d5c2ba4ef Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Tue, 19 Apr 2022 01:15:23 +0200 Subject: [PATCH 08/17] move ryu code to test src --- .../java/com/fasterxml/jackson/core/io/ryu/RoundingMode.java | 0 .../java/com/fasterxml/jackson/core/io/ryu/RyuDouble.java | 2 ++ .../java/com/fasterxml/jackson/core/io/ryu/RyuFloat.java | 2 ++ .../fasterxml/jackson/core/io/schubfach/DoubleToStringTest.java | 1 - 4 files changed, 4 insertions(+), 1 deletion(-) rename src/{main => test}/java/com/fasterxml/jackson/core/io/ryu/RoundingMode.java (100%) rename src/{main => test}/java/com/fasterxml/jackson/core/io/ryu/RyuDouble.java (99%) rename src/{main => test}/java/com/fasterxml/jackson/core/io/ryu/RyuFloat.java (99%) diff --git a/src/main/java/com/fasterxml/jackson/core/io/ryu/RoundingMode.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/RoundingMode.java similarity index 100% rename from src/main/java/com/fasterxml/jackson/core/io/ryu/RoundingMode.java rename to src/test/java/com/fasterxml/jackson/core/io/ryu/RoundingMode.java diff --git a/src/main/java/com/fasterxml/jackson/core/io/ryu/RyuDouble.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/RyuDouble.java similarity index 99% rename from src/main/java/com/fasterxml/jackson/core/io/ryu/RyuDouble.java rename to src/test/java/com/fasterxml/jackson/core/io/ryu/RyuDouble.java index 65f040bad2..c032958280 100644 --- a/src/main/java/com/fasterxml/jackson/core/io/ryu/RyuDouble.java +++ b/src/test/java/com/fasterxml/jackson/core/io/ryu/RyuDouble.java @@ -15,6 +15,8 @@ package com.fasterxml.jackson.core.io.ryu; +import com.fasterxml.jackson.core.io.ryu.RoundingMode; + import java.math.BigInteger; /** diff --git a/src/main/java/com/fasterxml/jackson/core/io/ryu/RyuFloat.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/RyuFloat.java similarity index 99% rename from src/main/java/com/fasterxml/jackson/core/io/ryu/RyuFloat.java rename to src/test/java/com/fasterxml/jackson/core/io/ryu/RyuFloat.java index 49a02313ff..68c260b2ae 100644 --- a/src/main/java/com/fasterxml/jackson/core/io/ryu/RyuFloat.java +++ b/src/test/java/com/fasterxml/jackson/core/io/ryu/RyuFloat.java @@ -15,6 +15,8 @@ package com.fasterxml.jackson.core.io.ryu; +import com.fasterxml.jackson.core.io.ryu.RoundingMode; + import java.math.BigInteger; /** diff --git a/src/test/java/com/fasterxml/jackson/core/io/schubfach/DoubleToStringTest.java b/src/test/java/com/fasterxml/jackson/core/io/schubfach/DoubleToStringTest.java index 194cd643d2..686119a631 100644 --- a/src/test/java/com/fasterxml/jackson/core/io/schubfach/DoubleToStringTest.java +++ b/src/test/java/com/fasterxml/jackson/core/io/schubfach/DoubleToStringTest.java @@ -1,6 +1,5 @@ package com.fasterxml.jackson.core.io.schubfach; -import com.fasterxml.jackson.core.io.ryu.RoundingMode; import org.junit.Test; import static org.junit.Assert.assertEquals; From d834c66c8578763a27e5cfb889867009709b9972 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sat, 18 Jun 2022 01:14:27 +0100 Subject: [PATCH 09/17] add properties --- .../fasterxml/jackson/core/JsonGenerator.java | 9 +++++ .../jackson/core/io/NumberOutput.java | 38 ++++++++++++++++--- .../jackson/core/json/JsonWriteFeature.java | 9 +++++ .../jackson/core/json/UTF8JsonGenerator.java | 8 ++-- .../core/json/WriterBasedJsonGenerator.java | 8 ++-- 5 files changed, 59 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java b/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java index c005da4fc7..0d23394200 100644 --- a/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java +++ b/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java @@ -240,6 +240,15 @@ public enum Feature { * @since 2.5 */ IGNORE_UNKNOWN(false), + + /** + * Feature that determines whether to use standard Java code to write floats/doubles (default) or + * use the Schubfach algorithm which is faster. The latter approach may lead to small + * differences in the precision of the float/double that is written to the JSON output. + * + * @since 2.14 + */ + USE_FAST_DOUBLE_WRITER(false) ; private final boolean _defaultState; diff --git a/src/main/java/com/fasterxml/jackson/core/io/NumberOutput.java b/src/main/java/com/fasterxml/jackson/core/io/NumberOutput.java index bfb730ce81..f8db6611d5 100644 --- a/src/main/java/com/fasterxml/jackson/core/io/NumberOutput.java +++ b/src/main/java/com/fasterxml/jackson/core/io/NumberOutput.java @@ -276,13 +276,41 @@ public static String toString(long v) { return Long.toString(v); } - public static String toString(double v) { - return DoubleToDecimal.toString(v); + /** + * @param v double + * @return double as a string + */ + public static String toString(final double v) { + return toString(v, false); + } + + /** + * @param v double + * @param useFastWriter whether to use Schubfach algorithm to write output (default false) + * @return double as a string + * @since 2.14 + */ + public static String toString(final double v, final boolean useFastWriter) { + return useFastWriter ? DoubleToDecimal.toString(v) : Double.toString(v); } - // @since 2.6 - public static String toString(float v) { - return FloatToDecimal.toString(v); + /** + * @param v float + * @return float as a string + * @since 2.6 + */ + public static String toString(final float v) { + return toString(v, false); + } + + /** + * @param v float + * @param useFastWriter whether to use Schubfach algorithm to write output (default false) + * @return float as a string + * @since 2.14 + */ + public static String toString(final float v, final boolean useFastWriter) { + return useFastWriter ? FloatToDecimal.toString(v) : Float.toString(v); } /* diff --git a/src/main/java/com/fasterxml/jackson/core/json/JsonWriteFeature.java b/src/main/java/com/fasterxml/jackson/core/json/JsonWriteFeature.java index 41f6845ce5..11a0ab5759 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/JsonWriteFeature.java +++ b/src/main/java/com/fasterxml/jackson/core/json/JsonWriteFeature.java @@ -72,6 +72,15 @@ public enum JsonWriteFeature @SuppressWarnings("deprecation") ESCAPE_NON_ASCII(false, JsonGenerator.Feature.ESCAPE_NON_ASCII), + /** + * Feature that determines whether to use standard Java code to write floats/doubles (default) or + * use the Schubfach algorithm which is faster. The latter approach may lead to small + * differences in the precision of the float/double that is written to the JSON output. + * + * @since 2.14 + */ + USE_FAST_DOUBLE_WRITER(false, JsonGenerator.Feature.USE_FAST_DOUBLE_WRITER), + //23-Nov-2015, tatu: for [core#223], if and when it gets implemented /* * Feature that specifies handling of UTF-8 content that contains diff --git a/src/main/java/com/fasterxml/jackson/core/json/UTF8JsonGenerator.java b/src/main/java/com/fasterxml/jackson/core/json/UTF8JsonGenerator.java index 29ea90b972..34cf890a0c 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/UTF8JsonGenerator.java +++ b/src/main/java/com/fasterxml/jackson/core/json/UTF8JsonGenerator.java @@ -1027,12 +1027,12 @@ public void writeNumber(double d) throws IOException if (_cfgNumbersAsStrings || (NumberOutput.notFinite(d) && Feature.QUOTE_NON_NUMERIC_NUMBERS.enabledIn(_features))) { - writeString(String.valueOf(d)); + writeString(NumberOutput.toString(d, isEnabled(Feature.USE_FAST_DOUBLE_WRITER))); return; } // What is the max length for doubles? 40 chars? _verifyValueWrite(WRITE_NUMBER); - writeRaw(String.valueOf(d)); + writeRaw(NumberOutput.toString(d, isEnabled(Feature.USE_FAST_DOUBLE_WRITER))); } @SuppressWarnings("deprecation") @@ -1042,12 +1042,12 @@ public void writeNumber(float f) throws IOException if (_cfgNumbersAsStrings || (NumberOutput.notFinite(f) && Feature.QUOTE_NON_NUMERIC_NUMBERS.enabledIn(_features))) { - writeString(String.valueOf(f)); + writeString(NumberOutput.toString(f, isEnabled(Feature.USE_FAST_DOUBLE_WRITER))); return; } // What is the max length for floats? _verifyValueWrite(WRITE_NUMBER); - writeRaw(String.valueOf(f)); + writeRaw(NumberOutput.toString(f, isEnabled(Feature.USE_FAST_DOUBLE_WRITER))); } @Override diff --git a/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java b/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java index bf8c63eac7..765a7392b1 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java +++ b/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java @@ -801,12 +801,12 @@ public void writeNumber(double d) throws IOException { if (_cfgNumbersAsStrings || (NumberOutput.notFinite(d) && isEnabled(Feature.QUOTE_NON_NUMERIC_NUMBERS))) { - writeString(String.valueOf(d)); + writeString(NumberOutput.toString(d, isEnabled(Feature.USE_FAST_DOUBLE_WRITER))); return; } // What is the max length for doubles? 40 chars? _verifyValueWrite(WRITE_NUMBER); - writeRaw(String.valueOf(d)); + writeRaw(NumberOutput.toString(d, isEnabled(Feature.USE_FAST_DOUBLE_WRITER))); } @SuppressWarnings("deprecation") @@ -815,12 +815,12 @@ public void writeNumber(float f) throws IOException { if (_cfgNumbersAsStrings || (NumberOutput.notFinite(f) && isEnabled(Feature.QUOTE_NON_NUMERIC_NUMBERS))) { - writeString(String.valueOf(f)); + writeString(NumberOutput.toString(f, isEnabled(Feature.USE_FAST_DOUBLE_WRITER))); return; } // What is the max length for floats? _verifyValueWrite(WRITE_NUMBER); - writeRaw(String.valueOf(f)); + writeRaw(NumberOutput.toString(f, isEnabled(Feature.USE_FAST_DOUBLE_WRITER))); } @Override From 72c8c19acae0cbf390801e1cae5d0e9805b30e03 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sat, 18 Jun 2022 10:18:49 +0100 Subject: [PATCH 10/17] add tests --- .../core/write/ArrayGenerationTest.java | 38 ++++++++++--------- .../write/FastDoubleArrayGenerationTest.java | 12 ++++++ .../core/write/FastDoubleObjectWriteTest.java | 12 ++++++ .../jackson/core/write/ObjectWriteTest.java | 16 +++++--- 4 files changed, 56 insertions(+), 22 deletions(-) create mode 100644 src/test/java/com/fasterxml/jackson/core/write/FastDoubleArrayGenerationTest.java create mode 100644 src/test/java/com/fasterxml/jackson/core/write/FastDoubleObjectWriteTest.java diff --git a/src/test/java/com/fasterxml/jackson/core/write/ArrayGenerationTest.java b/src/test/java/com/fasterxml/jackson/core/write/ArrayGenerationTest.java index f4c7554067..9fc457cf04 100644 --- a/src/test/java/com/fasterxml/jackson/core/write/ArrayGenerationTest.java +++ b/src/test/java/com/fasterxml/jackson/core/write/ArrayGenerationTest.java @@ -13,7 +13,11 @@ public class ArrayGenerationTest extends BaseTest { private final JsonFactory FACTORY = new JsonFactory(); - + + protected JsonFactory jsonFactory() { + return FACTORY; + } + public void testIntArray() throws Exception { _testIntArray(false); @@ -124,8 +128,8 @@ private void _testIntArray(boolean useBytes, int elements, int pre, int post) th StringWriter sw = new StringWriter(); ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - JsonGenerator gen = useBytes ? FACTORY.createGenerator(bytes) - : FACTORY.createGenerator(sw); + JsonGenerator gen = useBytes ? jsonFactory().createGenerator(bytes) + : jsonFactory().createGenerator(sw); gen.writeArray(values, pre, elements); gen.close(); @@ -137,8 +141,8 @@ private void _testIntArray(boolean useBytes, int elements, int pre, int post) th json = sw.toString(); } - JsonParser p = useBytes ? FACTORY.createParser(bytes.toByteArray()) - : FACTORY.createParser(json); + JsonParser p = useBytes ? jsonFactory().createParser(bytes.toByteArray()) + : jsonFactory().createParser(json); assertToken(JsonToken.START_ARRAY, p.nextToken()); for (int i = 0; i < elements; ++i) { if ((i & 1) == 0) { // alternate @@ -165,8 +169,8 @@ private void _testLongArray(boolean useBytes, int elements, int pre, int post) t StringWriter sw = new StringWriter(); ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - JsonGenerator gen = useBytes ? FACTORY.createGenerator(bytes) - : FACTORY.createGenerator(sw); + JsonGenerator gen = useBytes ? jsonFactory().createGenerator(bytes) + : jsonFactory().createGenerator(sw); gen.writeArray(values, pre, elements); gen.close(); @@ -178,8 +182,8 @@ private void _testLongArray(boolean useBytes, int elements, int pre, int post) t json = sw.toString(); } - JsonParser p = useBytes ? FACTORY.createParser(bytes.toByteArray()) - : FACTORY.createParser(json); + JsonParser p = useBytes ? jsonFactory().createParser(bytes.toByteArray()) + : jsonFactory().createParser(json); assertToken(JsonToken.START_ARRAY, p.nextToken()); for (int i = 0; i < elements; ++i) { if ((i & 1) == 0) { // alternate @@ -206,8 +210,8 @@ private void _testDoubleArray(boolean useBytes, int elements, int pre, int post) StringWriter sw = new StringWriter(); ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - JsonGenerator gen = useBytes ? FACTORY.createGenerator(bytes) - : FACTORY.createGenerator(sw); + JsonGenerator gen = useBytes ? jsonFactory().createGenerator(bytes) + : jsonFactory().createGenerator(sw); gen.writeArray(values, pre, elements); gen.close(); @@ -219,8 +223,8 @@ private void _testDoubleArray(boolean useBytes, int elements, int pre, int post) json = sw.toString(); } - JsonParser p = useBytes ? FACTORY.createParser(bytes.toByteArray()) - : FACTORY.createParser(json); + JsonParser p = useBytes ? jsonFactory().createParser(bytes.toByteArray()) + : jsonFactory().createParser(json); assertToken(JsonToken.START_ARRAY, p.nextToken()); for (int i = 0; i < elements; ++i) { JsonToken t = p.nextToken(); @@ -248,8 +252,8 @@ private void _testStringArray(boolean useBytes, int elements, int pre, int post) StringWriter sw = new StringWriter(); ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - JsonGenerator gen = useBytes ? FACTORY.createGenerator(bytes) - : FACTORY.createGenerator(sw); + JsonGenerator gen = useBytes ? jsonFactory().createGenerator(bytes) + : jsonFactory().createGenerator(sw); gen.writeArray(values, pre, elements); gen.close(); @@ -261,8 +265,8 @@ private void _testStringArray(boolean useBytes, int elements, int pre, int post) json = sw.toString(); } - JsonParser p = useBytes ? FACTORY.createParser(bytes.toByteArray()) - : FACTORY.createParser(json); + JsonParser p = useBytes ? jsonFactory().createParser(bytes.toByteArray()) + : jsonFactory().createParser(json); assertToken(JsonToken.START_ARRAY, p.nextToken()); for (int i = 0; i < elements; ++i) { JsonToken t = p.nextToken(); diff --git a/src/test/java/com/fasterxml/jackson/core/write/FastDoubleArrayGenerationTest.java b/src/test/java/com/fasterxml/jackson/core/write/FastDoubleArrayGenerationTest.java new file mode 100644 index 0000000000..18cf389193 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/write/FastDoubleArrayGenerationTest.java @@ -0,0 +1,12 @@ +package com.fasterxml.jackson.core.write; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.json.JsonWriteFeature; + +public class FastDoubleArrayGenerationTest extends ArrayGenerationTest { + private final JsonFactory FACTORY = JsonFactory.builder().enable(JsonWriteFeature.USE_FAST_DOUBLE_WRITER).build(); + + protected JsonFactory jsonFactory() { + return FACTORY; + } +} diff --git a/src/test/java/com/fasterxml/jackson/core/write/FastDoubleObjectWriteTest.java b/src/test/java/com/fasterxml/jackson/core/write/FastDoubleObjectWriteTest.java new file mode 100644 index 0000000000..fdf8c5a6ac --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/write/FastDoubleObjectWriteTest.java @@ -0,0 +1,12 @@ +package com.fasterxml.jackson.core.write; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.json.JsonWriteFeature; + +public class FastDoubleObjectWriteTest extends ObjectWriteTest { + private final JsonFactory FACTORY = JsonFactory.builder().enable(JsonWriteFeature.USE_FAST_DOUBLE_WRITER).build(); + + protected JsonFactory jsonFactory() { + return FACTORY; + } +} diff --git a/src/test/java/com/fasterxml/jackson/core/write/ObjectWriteTest.java b/src/test/java/com/fasterxml/jackson/core/write/ObjectWriteTest.java index 15fc5f0e4e..371002149f 100644 --- a/src/test/java/com/fasterxml/jackson/core/write/ObjectWriteTest.java +++ b/src/test/java/com/fasterxml/jackson/core/write/ObjectWriteTest.java @@ -13,11 +13,17 @@ public class ObjectWriteTest extends BaseTest { + private final JsonFactory FACTORY = new JsonFactory(); + + protected JsonFactory jsonFactory() { + return FACTORY; + } + public void testEmptyObjectWrite() throws Exception { StringWriter sw = new StringWriter(); - JsonGenerator gen = new JsonFactory().createGenerator(sw); + JsonGenerator gen = jsonFactory().createGenerator(sw); JsonStreamContext ctxt = gen.getOutputContext(); assertTrue(ctxt.inRoot()); @@ -59,7 +65,7 @@ public void testInvalidObjectWrite() throws Exception { StringWriter sw = new StringWriter(); - JsonGenerator gen = new JsonFactory().createGenerator(sw); + JsonGenerator gen = jsonFactory().createGenerator(sw); gen.writeStartObject(); // Mismatch: try { @@ -75,7 +81,7 @@ public void testSimpleObjectWrite() throws Exception { StringWriter sw = new StringWriter(); - JsonGenerator gen = new JsonFactory().createGenerator(sw); + JsonGenerator gen = jsonFactory().createGenerator(sw); gen.writeStartObject(); gen.writeFieldName("first"); gen.writeNumber(-901); @@ -111,7 +117,7 @@ public void testConvenienceMethods() throws Exception { StringWriter sw = new StringWriter(); - JsonGenerator gen = new JsonFactory().createGenerator(sw); + JsonGenerator gen = jsonFactory().createGenerator(sw); gen.writeStartObject(); final String TEXT = "\"some\nString!\""; @@ -219,7 +225,7 @@ public void testConvenienceMethodsWithNulls() throws Exception { StringWriter sw = new StringWriter(); - JsonGenerator gen = new JsonFactory().createGenerator(sw); + JsonGenerator gen = jsonFactory().createGenerator(sw); gen.writeStartObject(); gen.writeStringField("str", null); From 9918e3e6c0f268c9a279005e3c2d72effbd5c600 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Tue, 21 Jun 2022 01:33:09 +0100 Subject: [PATCH 11/17] move feature --- .../com/fasterxml/jackson/core/StreamWriteFeature.java | 9 +++++++++ .../fasterxml/jackson/core/json/JsonWriteFeature.java | 9 --------- .../core/write/FastDoubleArrayGenerationTest.java | 4 ++-- .../jackson/core/write/FastDoubleObjectWriteTest.java | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/core/StreamWriteFeature.java b/src/main/java/com/fasterxml/jackson/core/StreamWriteFeature.java index 4f1998484e..7eda9d6052 100644 --- a/src/main/java/com/fasterxml/jackson/core/StreamWriteFeature.java +++ b/src/main/java/com/fasterxml/jackson/core/StreamWriteFeature.java @@ -110,6 +110,15 @@ public enum StreamWriteFeature * property will result in a {@link JsonProcessingException} */ IGNORE_UNKNOWN(JsonGenerator.Feature.IGNORE_UNKNOWN), + + /** + * Feature that determines whether to use standard Java code to write floats/doubles (default) or + * use the Schubfach algorithm which is faster. The latter approach may lead to small + * differences in the precision of the float/double that is written to the JSON output. + * + * @since 2.14 + */ + USE_FAST_DOUBLE_WRITER(JsonGenerator.Feature.USE_FAST_DOUBLE_WRITER) ; /** diff --git a/src/main/java/com/fasterxml/jackson/core/json/JsonWriteFeature.java b/src/main/java/com/fasterxml/jackson/core/json/JsonWriteFeature.java index 11a0ab5759..41f6845ce5 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/JsonWriteFeature.java +++ b/src/main/java/com/fasterxml/jackson/core/json/JsonWriteFeature.java @@ -72,15 +72,6 @@ public enum JsonWriteFeature @SuppressWarnings("deprecation") ESCAPE_NON_ASCII(false, JsonGenerator.Feature.ESCAPE_NON_ASCII), - /** - * Feature that determines whether to use standard Java code to write floats/doubles (default) or - * use the Schubfach algorithm which is faster. The latter approach may lead to small - * differences in the precision of the float/double that is written to the JSON output. - * - * @since 2.14 - */ - USE_FAST_DOUBLE_WRITER(false, JsonGenerator.Feature.USE_FAST_DOUBLE_WRITER), - //23-Nov-2015, tatu: for [core#223], if and when it gets implemented /* * Feature that specifies handling of UTF-8 content that contains diff --git a/src/test/java/com/fasterxml/jackson/core/write/FastDoubleArrayGenerationTest.java b/src/test/java/com/fasterxml/jackson/core/write/FastDoubleArrayGenerationTest.java index 18cf389193..b9ecbabd24 100644 --- a/src/test/java/com/fasterxml/jackson/core/write/FastDoubleArrayGenerationTest.java +++ b/src/test/java/com/fasterxml/jackson/core/write/FastDoubleArrayGenerationTest.java @@ -1,10 +1,10 @@ package com.fasterxml.jackson.core.write; import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.json.JsonWriteFeature; +import com.fasterxml.jackson.core.StreamWriteFeature; public class FastDoubleArrayGenerationTest extends ArrayGenerationTest { - private final JsonFactory FACTORY = JsonFactory.builder().enable(JsonWriteFeature.USE_FAST_DOUBLE_WRITER).build(); + private final JsonFactory FACTORY = JsonFactory.builder().enable(StreamWriteFeature.USE_FAST_DOUBLE_WRITER).build(); protected JsonFactory jsonFactory() { return FACTORY; diff --git a/src/test/java/com/fasterxml/jackson/core/write/FastDoubleObjectWriteTest.java b/src/test/java/com/fasterxml/jackson/core/write/FastDoubleObjectWriteTest.java index fdf8c5a6ac..4b712307df 100644 --- a/src/test/java/com/fasterxml/jackson/core/write/FastDoubleObjectWriteTest.java +++ b/src/test/java/com/fasterxml/jackson/core/write/FastDoubleObjectWriteTest.java @@ -1,10 +1,10 @@ package com.fasterxml.jackson.core.write; import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.json.JsonWriteFeature; +import com.fasterxml.jackson.core.StreamWriteFeature; public class FastDoubleObjectWriteTest extends ObjectWriteTest { - private final JsonFactory FACTORY = JsonFactory.builder().enable(JsonWriteFeature.USE_FAST_DOUBLE_WRITER).build(); + private final JsonFactory FACTORY = JsonFactory.builder().enable(StreamWriteFeature.USE_FAST_DOUBLE_WRITER).build(); protected JsonFactory jsonFactory() { return FACTORY; From 6825570228fb3032b9ad1b70c3e95f4e62bee082 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Tue, 21 Jun 2022 01:35:56 +0100 Subject: [PATCH 12/17] remove ryu code --- .../core/io/ryu/DoubleToStringTest.java | 94 ---- .../jackson/core/io/ryu/DoubleUtils.java | 213 -------- .../jackson/core/io/ryu/EuclidMinMaxTest.java | 133 ----- .../core/io/ryu/FloatToStringTest.java | 125 ----- .../jackson/core/io/ryu/JafferDoubleTest.java | 72 --- .../jackson/core/io/ryu/RoundingMode.java | 47 -- .../jackson/core/io/ryu/RyuDouble.java | 493 ------------------ .../jackson/core/io/ryu/RyuDoubleTest.java | 26 - .../jackson/core/io/ryu/RyuFloat.java | 419 --------------- .../jackson/core/io/ryu/RyuFloatTest.java | 26 - .../jackson/core/io/ryu/SlowDoubleTest.java | 28 - .../jackson/core/io/ryu/SlowFloatTest.java | 24 - .../core/io/ryu/analysis/EuclidMinMax.java | 148 ------ .../io/ryu/analysis/FloatingPointFormat.java | 50 -- .../core/io/ryu/analysis/SlowConversion.java | 243 --------- 15 files changed, 2141 deletions(-) delete mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/DoubleToStringTest.java delete mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/DoubleUtils.java delete mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/EuclidMinMaxTest.java delete mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/FloatToStringTest.java delete mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/JafferDoubleTest.java delete mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/RoundingMode.java delete mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/RyuDouble.java delete mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/RyuDoubleTest.java delete mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/RyuFloat.java delete mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/RyuFloatTest.java delete mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/SlowDoubleTest.java delete mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/SlowFloatTest.java delete mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/EuclidMinMax.java delete mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/FloatingPointFormat.java delete mode 100644 src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/SlowConversion.java diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/DoubleToStringTest.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/DoubleToStringTest.java deleted file mode 100644 index d0ca1e1357..0000000000 --- a/src/test/java/com/fasterxml/jackson/core/io/ryu/DoubleToStringTest.java +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2018 Ulf Adams -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.fasterxml.jackson.core.io.ryu; - -import static org.junit.Assert.assertEquals; - -import org.junit.Test; - -public abstract class DoubleToStringTest { - abstract String f(double f, RoundingMode roundingMode); - - private void assertD2sEquals(String expected, double f) { - assertEquals(expected, f(f, RoundingMode.ROUND_EVEN)); - assertEquals(expected, f(f, RoundingMode.CONSERVATIVE)); - } - - private void assertD2sEquals(String expectedRoundEven, String expectedConservative, double f) { - assertEquals(expectedRoundEven, f(f, RoundingMode.ROUND_EVEN)); - assertEquals(expectedConservative, f(f, RoundingMode.CONSERVATIVE)); - } - - @Test - public void simpleCases() { - assertD2sEquals("0.0", 0); - assertD2sEquals("-0.0", Double.longBitsToDouble(0x8000000000000000L)); - assertD2sEquals("1.0", 1.0d); - assertD2sEquals("-1.0", -1.0d); - assertD2sEquals("NaN", Double.NaN); - assertD2sEquals("Infinity", Double.POSITIVE_INFINITY); - assertD2sEquals("-Infinity", Double.NEGATIVE_INFINITY); - } - - @Test - public void switchToSubnormal() { - assertD2sEquals("2.2250738585072014E-308", Double.longBitsToDouble(0x0010000000000000L)); - } - - /** - * Floating point values in the range 1.0E-3 <= x < 1.0E7 have to be printed - * without exponent. This test checks the values at those boundaries. - */ - @Test - public void boundaryConditions() { - // x = 1.0E7 - assertD2sEquals("1.0E7", 1.0E7d); - // x < 1.0E7 - assertD2sEquals("9999999.999999998", 9999999.999999998d); - // x = 1.0E-3 - assertD2sEquals("0.001", 0.001d); - // x < 1.0E-3 - assertD2sEquals("9.999999999999998E-4", 0.0009999999999999998d); - } - - @Test - public void minAndMax() { - assertD2sEquals("1.7976931348623157E308", Double.longBitsToDouble(0x7fefffffffffffffL)); - assertD2sEquals("4.9E-324", Double.longBitsToDouble(1)); - } - - @Test - public void roundingModeEven() { - assertD2sEquals("-2.109808898695963E16", "-2.1098088986959632E16", -2.109808898695963E16); - } - - @Test - public void regressionTest() { - assertD2sEquals("4.940656E-318", 4.940656E-318d); - assertD2sEquals("1.18575755E-316", 1.18575755E-316d); - assertD2sEquals("2.989102097996E-312", 2.989102097996E-312d); - assertD2sEquals("9.0608011534336E15", 9.0608011534336E15d); - assertD2sEquals("4.708356024711512E18", 4.708356024711512E18); - assertD2sEquals("9.409340012568248E18", 9.409340012568248E18); - // This number naively requires 65 bit for the intermediate results if we reduce the lookup - // table by half. This checks that we don't loose any information in that case. - assertD2sEquals("1.8531501765868567E21", 1.8531501765868567E21); - assertD2sEquals("-3.347727380279489E33", -3.347727380279489E33); - // Discovered by Andriy Plokhotnyuk, see #29. - assertD2sEquals("1.9430376160308388E16", 1.9430376160308388E16); - assertD2sEquals("-6.9741824662760956E19", -6.9741824662760956E19); - assertD2sEquals("4.3816050601147837E18", 4.3816050601147837E18); - } -} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/DoubleUtils.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/DoubleUtils.java deleted file mode 100644 index 36236ff4e8..0000000000 --- a/src/test/java/com/fasterxml/jackson/core/io/ryu/DoubleUtils.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright 2016 Heng Yuan - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.fasterxml.jackson.core.io.ryu; - -import java.math.BigInteger; - -/** - * The algorithm and code used here are from - * Jaffer, Aubrey. "Easy Accurate Reading and Writing of Floating-Point - * Numbers." arXiv preprint arXiv:1310.8121 (2013). - * (PDF) - *

- * And here is the license given from him: - *

- * Copyright (c) Aubrey Jaffer 2014
- * Permission to copy this software, to modify it, to redistribute it,
- * to distribute modified versions, and to use it for any purpose is
- * granted, subject to the following restrictions and understandings.
- *
- * 1.  Any copy made of this software must include this copyright notice
- * in full.
- *
- * 2.  I have made no warranty or representation that the operation of
- * this software will be error-free, and I am under no obligation to
- * provide any services, by way of maintenance, update, or otherwise.
- *
- * 3.  In conjunction with products arising from the use of this
- * material, there shall be no use of my name in any advertising,
- * promotional, or sales literature without prior written consent in
- * each case.
- * 
- *

- * I made minor modifications to fit my need. - * - * @author Heng Yuan - */ -class DoubleUtils -{ - private final static int dblMantDig = 53; - private final static BigInteger[] bp5a = new BigInteger[326]; - - private static final long lp5[] = - { 1L, 5L, 25L, 125L, 625L, 3125L, 15625L, 78125L, 390625L, 1953125L, - 9765625L, 48828125L, 244140625L, 1220703125L, 6103515625L, 30517578125L, - 152587890625L, 762939453125L, 3814697265625L, 19073486328125L, - 95367431640625L, 476837158203125L, 2384185791015625L, 11920928955078125L, - 59604644775390625L, 298023223876953125L, 1490116119384765625L, - 7450580596923828125L }; - - private final static BigInteger bp5 (int p) - { - BigInteger[] pa = bp5a; - if (pa[p] != null) - return pa[p]; - else if (p < lp5.length) - { - BigInteger v = BigInteger.valueOf (lp5[p]); - pa[p] = v; - return v; - } - else - { - // use divide-n-conquer strategy to compute the number - int a = p >> 1; - int b = p - a; // b has 50% chance being the same as a - BigInteger v = bp5 (a).multiply (bp5 (b)); - pa[p] = v; - return v; - } - } - - private final static double llog2 = Math.log10 (2); - - private static long rq (BigInteger num, BigInteger den) - { - BigInteger quorem[] = num.divideAndRemainder (den); - long quo = quorem[0].longValue (); - int cmpflg = quorem[1].shiftLeft (1).compareTo (den); - if ((quo & 1L) == 0L ? 1 == cmpflg : -1 < cmpflg) - return quo + 1L; - else - return quo; - } - - private static double metd (long lmant, int point) - { - BigInteger mant = BigInteger.valueOf (lmant); - if (point >= 0) - { - BigInteger num = mant.multiply (bp5 (point)); - int bex = num.bitLength () - dblMantDig; - if (bex <= 0) - return Math.scalb (num.doubleValue (), point); - long quo = rq (num, BigInteger.ONE.shiftLeft (bex)); - return Math.scalb ((double) quo, bex + point); - } - int maxpow = bp5a.length - 1; - BigInteger scl = (-point <= maxpow) ? bp5 (-point) : bp5 (maxpow).multiply (bp5 (-point - maxpow)); - int bex = mant.bitLength () - scl.bitLength () - dblMantDig; - BigInteger num = mant.shiftLeft (-bex); - long quo = rq (num, scl); - if (64 - Long.numberOfLeadingZeros (quo) > dblMantDig) - { - bex++; - quo = rq (num, scl.shiftLeft (1)); - } - return Math.scalb ((double) quo, bex + point); - } - - /** - * This function converts a double representation to a string format. - * - * @param f - * A double value. - * @return A string representation of the double value f. - */ - public static String toString (double f) - { - long lbits = Double.doubleToLongBits (f); - if (f != f) - return "NaN"; - if (f + f == f) - return (f == 0.0) ? "0" : ((f > 0) ? "Infinity" : "-Infinity"); - StringBuilder str = new StringBuilder (24); - if (f < 0) - { - str.append ('-'); - // there is a rounding bug for negative values with positive - // exp, such as -5e100. Negate the value to avoid the issue. - f = -f; - } - int ue2 = (int) (lbits >>> 52 & 0x7ff); - int e2 = ue2 - 1023 - 52 + (ue2 == 0 ? 1 : 0); - int point = (int) Math.ceil (e2 * llog2); - long lquo; - long lmant = (lbits & ((1L << 52) - 1)) + (ue2 == 0 ? 0L : 1L << 52); - BigInteger mant = BigInteger.valueOf (lmant); - if (e2 > 0) - { - BigInteger num = mant.shiftLeft (e2 - point); - lquo = rq (num, bp5 (point)); - if (metd (lquo, point) != f) - lquo = rq (num.shiftLeft (1), bp5 (--point)); - } - else - { - BigInteger num = mant.multiply (bp5 (-point)); - BigInteger den = BigInteger.ONE.shiftLeft (point - e2); - lquo = rq (num, den); - if (metd (lquo, point) != f) - { - point--; - lquo = rq (num.multiply (BigInteger.TEN), den); - } - } - String sman = Long.toString (lquo); - int len = sman.length (), lent = len; - while (sman.charAt (lent - 1) == '0') - { - lent--; - } - int exp = point + len - 1; - - if (exp >= 0 && exp < len) - { - // length string is longer than exp - // so the period is in the middle of sman - ++exp; // exp is now the period location - str.append (sman, 0, exp); - if (lent > exp) - { - str.append ('.'); - str.append (sman, exp, lent); - } - } - else if (exp < 0 && exp > -5) - { - str.append ("0."); - str.append ("0000".substring (0, -exp - 1)); - str.append (sman, 0, lent); - } - else - { - // scientific notation - - str.append (sman, 0, 1); - if (lent > 1) - { - str.append ('.'); - str.append (sman, 1, lent); - } - if (exp != 0) - { - str.append ('e'); - str.append (exp); - } - } - return str.toString (); - } -} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/EuclidMinMaxTest.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/EuclidMinMaxTest.java deleted file mode 100644 index 502e5ab4a4..0000000000 --- a/src/test/java/com/fasterxml/jackson/core/io/ryu/EuclidMinMaxTest.java +++ /dev/null @@ -1,133 +0,0 @@ -package com.fasterxml.jackson.core.io.ryu; - -import static org.junit.Assert.assertEquals; -import java.math.BigInteger; -import java.util.Random; -import org.junit.Test; -import com.fasterxml.jackson.core.io.ryu.analysis.EuclidMinMax; - -public class EuclidMinMaxTest { - private static int min(int a, int b, int max) { - return EuclidMinMax.min(BigInteger.valueOf(a), BigInteger.valueOf(b), BigInteger.valueOf(max)).intValueExact(); - } - - private static int max(int a, int b, int max) { - return EuclidMinMax.max(BigInteger.valueOf(a), BigInteger.valueOf(b), BigInteger.valueOf(max)).intValueExact(); - } - - private static BigInteger minSlow(BigInteger multiplier, BigInteger modulo, BigInteger maximum) { -// if (maximum.compareTo(modulo) >= 0) { -// return BigInteger.ZERO; -// } - BigInteger result = multiplier.mod(modulo); - for (long l = 2; l <= maximum.longValueExact(); l++) { - BigInteger cand = BigInteger.valueOf(l).multiply(multiplier).mod(modulo); - if (cand.compareTo(result) < 0) { - result = cand; - } - } - return result; - } - - private static BigInteger maxSlow(BigInteger multiplier, BigInteger modulo, BigInteger maximum) { -// if (maximum.compareTo(modulo) >= 0) { -// return modulo.subtract(BigInteger.ONE); -// } - BigInteger result = multiplier.mod(modulo); - for (long l = 2; l <= maximum.longValueExact(); l++) { - BigInteger cand = BigInteger.valueOf(l).multiply(multiplier).mod(modulo); - if (cand.compareTo(result) > 0) { - result = cand; - } - } - return result; - } - - @Test - public void simpleMin() { - assertEquals(2, min(2, 10, 1)); - assertEquals(2, min(2, 10, 2)); - assertEquals(2, min(2, 10, 3)); - assertEquals(2, min(2, 10, 4)); - assertEquals(0, min(2, 10, 5)); - } - - @Test - public void simpleMax() { - assertEquals(2, max(2, 10, 1)); - assertEquals(4, max(2, 10, 2)); - assertEquals(6, max(2, 10, 3)); - assertEquals(8, max(2, 10, 4)); - assertEquals(8, max(2, 10, 5)); - } - - @Test - public void minAgain() { - assertEquals(7, min(7, 10, 1)); - assertEquals(4, min(7, 10, 2)); - assertEquals(1, min(7, 10, 3)); - assertEquals(1, min(7, 10, 4)); - assertEquals(1, min(7, 10, 5)); - assertEquals(1, min(7, 10, 6)); - assertEquals(1, min(7, 10, 7)); - assertEquals(1, min(7, 10, 8)); - assertEquals(1, min(7, 10, 9)); - assertEquals(0, min(7, 10, 10)); - } - - @Test - public void maxAgain() { - assertEquals(7, max(7, 10, 1)); - assertEquals(7, max(7, 10, 2)); - assertEquals(7, max(7, 10, 3)); - assertEquals(8, max(7, 10, 4)); - assertEquals(8, max(7, 10, 5)); - assertEquals(8, max(7, 10, 6)); - assertEquals(9, max(7, 10, 7)); - assertEquals(9, max(7, 10, 8)); - assertEquals(9, max(7, 10, 9)); - assertEquals(9, max(7, 10, 10)); - } - - @Test - public void minAgain2() { - assertEquals(7, min(7, 30, 1)); - assertEquals(7, min(7, 30, 2)); - assertEquals(7, min(7, 30, 3)); - assertEquals(7, min(7, 30, 4)); - assertEquals(5, min(7, 30, 5)); - assertEquals(5, min(7, 30, 6)); - assertEquals(5, min(7, 30, 7)); - assertEquals(5, min(7, 30, 8)); - assertEquals(3, min(7, 30, 9)); - assertEquals(3, min(7, 30, 10)); - } - - @Test - public void compareMin() { - Random rand = new Random(1234L); - for (int i = 0; i < 10000; i++) { - BigInteger a = BigInteger.valueOf(rand.nextInt(500) + 2); - BigInteger b = BigInteger.valueOf(rand.nextInt(500) + 2); - BigInteger max = BigInteger.valueOf(rand.nextInt(100) + 1); -// System.out.println(a + " " + b + " " + max + " " + a.mod(b)); - BigInteger minFast = EuclidMinMax.min(a, b, max); - BigInteger minSlow = minSlow(a, b, max); - assertEquals(minSlow, minFast); - } - } - - @Test - public void compareMax() { - Random rand = new Random(1234L); - for (int i = 0; i < 10000; i++) { - BigInteger a = BigInteger.valueOf(rand.nextInt(500) + 2); - BigInteger b = BigInteger.valueOf(rand.nextInt(500) + 2); - BigInteger max = BigInteger.valueOf(rand.nextInt(100) + 1); -// System.out.println(a + " " + b + " " + max); - BigInteger maxFast = EuclidMinMax.max(a, b, max); - BigInteger maxSlow = maxSlow(a, b, max); - assertEquals(maxSlow, maxFast); - } - } -} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/FloatToStringTest.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/FloatToStringTest.java deleted file mode 100644 index 5c511b9f1e..0000000000 --- a/src/test/java/com/fasterxml/jackson/core/io/ryu/FloatToStringTest.java +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2018 Ulf Adams -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.fasterxml.jackson.core.io.ryu; - -import static org.junit.Assert.assertEquals; - -import org.junit.Test; - -public abstract class FloatToStringTest { - abstract String f(float f, RoundingMode roundingMode); - - private void assertF2sEquals(String expected, float f) { - assertEquals(expected, f(f, RoundingMode.ROUND_EVEN)); - assertEquals(expected, f(f, RoundingMode.CONSERVATIVE)); - } - - private void assertF2sEquals(String expectedRoundEven, String expectedConservative, float f) { - assertEquals(expectedRoundEven, f(f, RoundingMode.ROUND_EVEN)); - assertEquals(expectedConservative, f(f, RoundingMode.CONSERVATIVE)); - } - - @Test - public void simpleCases() { - assertF2sEquals("0.0", 0); - assertF2sEquals("-0.0", Float.intBitsToFloat(0x80000000)); - assertF2sEquals("1.0", 1.0f); - assertF2sEquals("-1.0", -1f); - assertF2sEquals("NaN", Float.NaN); - assertF2sEquals("Infinity", Float.POSITIVE_INFINITY); - assertF2sEquals("-Infinity", Float.NEGATIVE_INFINITY); - } - - @Test - public void switchToSubnormal() { - assertF2sEquals("1.1754944E-38", Float.intBitsToFloat(0x00800000)); - } - - /** - * Floating point values in the range 1.0E-3 <= x < 1.0E7 have to be printed - * without exponent. This test checks the values at those boundaries. - */ - @Test - public void boundaryConditions() { - // x = 1.0E7 - assertF2sEquals("1.0E7", 1.0E7f); - // x < 1.0E7 - assertF2sEquals("9999999.0", 9999999.0f); - // x = 1.0E-3 - assertF2sEquals("0.001", 0.001f); - // x < 1.0E-3 - assertF2sEquals("9.999999E-4", 0.0009999999f); - } - - @Test - public void minAndMax() { - assertF2sEquals("3.4028235E38", Float.intBitsToFloat(0x7f7fffff)); - assertF2sEquals("1.4E-45", Float.intBitsToFloat(0x00000001)); - } - - @Test - public void roundingModeEven() { - assertF2sEquals("3.355445E7", "3.3554448E7", 3.3554448E7f); - assertF2sEquals("9.0E9", "8.999999E9", 8.999999E9f); - assertF2sEquals("3.436672E10", "3.4366718E10", 3.4366717E10f); - } - - @Test - public void roundingEvenIfTied() { - assertF2sEquals("0.33007812", 0.33007812f); - } - - @Test - public void looksLikePow5() { - // These are all floating point numbers where the mantissa is a power of 5, - // and the exponent is in the range such that q = 10. - assertF2sEquals("6.7108864E17", Float.intBitsToFloat(0x5D1502F9)); - assertF2sEquals("1.3421773E18", Float.intBitsToFloat(0x5D9502F9)); - assertF2sEquals("2.6843546E18", Float.intBitsToFloat(0x5E1502F9)); - } - - @Test - public void regressionTest() { - assertF2sEquals("4.7223665E21", 4.7223665E21f); - assertF2sEquals("8388608.0", 8388608.0f); - assertF2sEquals("1.6777216E7", 1.6777216E7f); - assertF2sEquals("3.3554436E7", 3.3554436E7f); - assertF2sEquals("6.7131496E7", 6.7131496E7f); - assertF2sEquals("1.9310392E-38", 1.9310392E-38f); - assertF2sEquals("-2.47E-43", -2.47E-43f); - assertF2sEquals("1.993244E-38", 1.993244E-38f); - assertF2sEquals("4103.9004", 4103.9003f); - assertF2sEquals("5.3399997E9", 5.3399997E9f); - assertF2sEquals("6.0898E-39", 6.0898E-39f); - assertF2sEquals("0.0010310042", 0.0010310042f); - assertF2sEquals("2.882326E17", 2.8823261E17f); - assertF2sEquals("7.038531E-26", 7.038531E-26f); - assertF2sEquals("9.223404E17", 9.2234038E17f); - assertF2sEquals("6.710887E7", 6.7108872E7f); - assertF2sEquals("1.0E-44", 1.0E-44f); - assertF2sEquals("2.816025E14", 2.816025E14f); - assertF2sEquals("9.223372E18", 9.223372E18f); - assertF2sEquals("1.5846086E29", 1.5846085E29f); - assertF2sEquals("1.1811161E19", 1.1811161E19f); - assertF2sEquals("5.368709E18", 5.368709E18f); - assertF2sEquals("4.6143166E18", 4.6143165E18f); - assertF2sEquals("0.007812537", 0.007812537f); - assertF2sEquals("1.4E-45", 1.4E-45f); - assertF2sEquals("1.18697725E20", 1.18697724E20f); - assertF2sEquals("1.00014165E-36", 1.00014165E-36f); - assertF2sEquals("200.0", 200f); - assertF2sEquals("3.3554432E7", 3.3554432E7f); - } -} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/JafferDoubleTest.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/JafferDoubleTest.java deleted file mode 100644 index 4cb00a9907..0000000000 --- a/src/test/java/com/fasterxml/jackson/core/io/ryu/JafferDoubleTest.java +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2018 Ulf Adams -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.fasterxml.jackson.core.io.ryu; - -import static org.junit.Assert.assertEquals; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -/** - * A few tests to check that {@link DoubleUtils} isn't completely broken. - * - *

Note that the implementation does not follow Java {@link Double#toString} semantics, so we - * can't run the existing double-to-string tests against it. - */ -@RunWith(JUnit4.class) -public class JafferDoubleTest { - private String f(double f) { - return DoubleUtils.toString(f); - } - - @Test - public void simpleCases() { - assertEquals("0", f(0)); - assertEquals("0", f(Double.longBitsToDouble(0x8000000000000000L))); - assertEquals("1", f(1.0d)); - assertEquals("-1", f(-1.0d)); - assertEquals("NaN", f(Double.NaN)); - assertEquals("Infinity", f(Double.POSITIVE_INFINITY)); - assertEquals("-Infinity", f(Double.NEGATIVE_INFINITY)); - } - - @Test - public void switchToSubnormal() { - assertEquals("2.2250738585072014e-308", f(Double.longBitsToDouble(0x0010000000000000L))); - } - - @Test - public void boundaryConditions() { - // x = 1.0E7 - assertEquals("10000000", f(1.0E7d)); - // x < 1.0E7 - assertEquals("9999999", f(9999999d)); - // x = 1.0E-3 - assertEquals("0.001", f(0.001d)); - // x < 1.0E-3 - assertEquals("0.0009", f(0.0009d)); - } - - @Test - public void regressionTest() { - assertEquals("4.940656e-318", f(4.940656E-318d)); - assertEquals("1.18575755e-316", f(1.18575755E-316d)); - assertEquals("2.989102097996e-312", f(2.989102097996E-312d)); - assertEquals("9.0608011534336e15", f(9.0608011534336E15d)); - assertEquals("4.708356024711512e18", f(4.708356024711512E18)); - assertEquals("9.409340012568248e18", f(9.409340012568248E18)); - } -} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/RoundingMode.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/RoundingMode.java deleted file mode 100644 index 0e73e5d853..0000000000 --- a/src/test/java/com/fasterxml/jackson/core/io/ryu/RoundingMode.java +++ /dev/null @@ -1,47 +0,0 @@ - -// Copyright 2018 Ulf Adams -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.fasterxml.jackson.core.io.ryu; - -/** - * This is an internal class, public only for internal testing - */ -public enum RoundingMode { - CONSERVATIVE { - @Override - public boolean acceptUpperBound(boolean even) { - return false; - } - - @Override - public boolean acceptLowerBound(boolean even) { - return false; - } - }, - ROUND_EVEN { - @Override - public boolean acceptUpperBound(boolean even) { - return even; - } - - @Override - public boolean acceptLowerBound(boolean even) { - return even; - } - }; - - public abstract boolean acceptUpperBound(boolean even); - public abstract boolean acceptLowerBound(boolean even); -} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/RyuDouble.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/RyuDouble.java deleted file mode 100644 index c032958280..0000000000 --- a/src/test/java/com/fasterxml/jackson/core/io/ryu/RyuDouble.java +++ /dev/null @@ -1,493 +0,0 @@ - -// Copyright 2018 Ulf Adams -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.fasterxml.jackson.core.io.ryu; - -import com.fasterxml.jackson.core.io.ryu.RoundingMode; - -import java.math.BigInteger; - -/** - * An implementation of Ryu for double. - * - * A fork from https://github.com/ulfjack/ryu/ - */ -public final class RyuDouble { - private static boolean DEBUG = false; - - private static final int DOUBLE_MANTISSA_BITS = 52; - private static final long DOUBLE_MANTISSA_MASK = (1L << DOUBLE_MANTISSA_BITS) - 1; - - private static final int DOUBLE_EXPONENT_BITS = 11; - private static final int DOUBLE_EXPONENT_MASK = (1 << DOUBLE_EXPONENT_BITS) - 1; - private static final int DOUBLE_EXPONENT_BIAS = (1 << (DOUBLE_EXPONENT_BITS - 1)) - 1; - - private static final int POS_TABLE_SIZE = 326; - private static final int NEG_TABLE_SIZE = 291; - - // Only for debugging. - private static final BigInteger[] POW5 = new BigInteger[POS_TABLE_SIZE]; - private static final BigInteger[] POW5_INV = new BigInteger[NEG_TABLE_SIZE]; - - private static final int POW5_BITCOUNT = 121; // max 3*31 = 124 - private static final int POW5_QUARTER_BITCOUNT = 31; - private static final int[][] POW5_SPLIT = new int[POS_TABLE_SIZE][4]; - - private static final int POW5_INV_BITCOUNT = 122; // max 3*31 = 124 - private static final int POW5_INV_QUARTER_BITCOUNT = 31; - private static final int[][] POW5_INV_SPLIT = new int[NEG_TABLE_SIZE][4]; - - static { - BigInteger mask = BigInteger.valueOf(1).shiftLeft(POW5_QUARTER_BITCOUNT).subtract(BigInteger.ONE); - BigInteger invMask = BigInteger.valueOf(1).shiftLeft(POW5_INV_QUARTER_BITCOUNT).subtract(BigInteger.ONE); - for (int i = 0; i < Math.max(POW5.length, POW5_INV.length); i++) { - BigInteger pow = BigInteger.valueOf(5).pow(i); - int pow5len = pow.bitLength(); - int expectedPow5Bits = pow5bits(i); - if (expectedPow5Bits != pow5len) { - throw new IllegalStateException(pow5len + " != " + expectedPow5Bits); - } - if (i < POW5.length) { - POW5[i] = pow; - } - if (i < POW5_SPLIT.length) { - for (int j = 0; j < 4; j++) { - POW5_SPLIT[i][j] = pow - .shiftRight(pow5len - POW5_BITCOUNT + (3 - j) * POW5_QUARTER_BITCOUNT) - .and(mask) - .intValueExact(); - } - } - - if (i < POW5_INV_SPLIT.length) { - // We want floor(log_2 5^q) here, which is pow5len - 1. - int j = pow5len - 1 + POW5_INV_BITCOUNT; - BigInteger inv = BigInteger.ONE.shiftLeft(j).divide(pow).add(BigInteger.ONE); - POW5_INV[i] = inv; - for (int k = 0; k < 4; k++) { - if (k == 0) { - POW5_INV_SPLIT[i][k] = inv.shiftRight((3 - k) * POW5_INV_QUARTER_BITCOUNT).intValueExact(); - } else { - POW5_INV_SPLIT[i][k] = inv.shiftRight((3 - k) * POW5_INV_QUARTER_BITCOUNT).and(invMask).intValueExact(); - } - } - } - } - } - - public static String doubleToString(double value) { - return doubleToString(value, RoundingMode.ROUND_EVEN); - } - - public static String doubleToString(double value, RoundingMode roundingMode) { - // Step 1: Decode the floating point number, and unify normalized and subnormal cases. - // First, handle all the trivial cases. - if (Double.isNaN(value)) return "NaN"; - if (value == Double.POSITIVE_INFINITY) return "Infinity"; - if (value == Double.NEGATIVE_INFINITY) return "-Infinity"; - long bits = Double.doubleToLongBits(value); - if (bits == 0) return "0.0"; - if (bits == 0x8000000000000000L) return "-0.0"; - - // Otherwise extract the mantissa and exponent bits and run the full algorithm. - int ieeeExponent = (int) ((bits >>> DOUBLE_MANTISSA_BITS) & DOUBLE_EXPONENT_MASK); - long ieeeMantissa = bits & DOUBLE_MANTISSA_MASK; - int e2; - long m2; - if (ieeeExponent == 0) { - // Denormal number - no implicit leading 1, and the exponent is 1, not 0. - e2 = 1 - DOUBLE_EXPONENT_BIAS - DOUBLE_MANTISSA_BITS; - m2 = ieeeMantissa; - } else { - // Add implicit leading 1. - e2 = ieeeExponent - DOUBLE_EXPONENT_BIAS - DOUBLE_MANTISSA_BITS; - m2 = ieeeMantissa | (1L << DOUBLE_MANTISSA_BITS); - } - - boolean sign = bits < 0; - if (DEBUG) { - System.out.println("IN=" + Long.toBinaryString(bits)); - System.out.println(" S=" + (sign ? "-" : "+") + " E=" + e2 + " M=" + m2); - } - - // Step 2: Determine the interval of legal decimal representations. - boolean even = (m2 & 1) == 0; - final long mv = 4 * m2; - final long mp = 4 * m2 + 2; - final int mmShift = ((m2 != (1L << DOUBLE_MANTISSA_BITS)) || (ieeeExponent <= 1)) ? 1 : 0; - final long mm = 4 * m2 - 1 - mmShift; - e2 -= 2; - - if (DEBUG) { - String sv, sp, sm; - int e10; - if (e2 >= 0) { - sv = BigInteger.valueOf(mv).shiftLeft(e2).toString(); - sp = BigInteger.valueOf(mp).shiftLeft(e2).toString(); - sm = BigInteger.valueOf(mm).shiftLeft(e2).toString(); - e10 = 0; - } else { - BigInteger factor = BigInteger.valueOf(5).pow(-e2); - sv = BigInteger.valueOf(mv).multiply(factor).toString(); - sp = BigInteger.valueOf(mp).multiply(factor).toString(); - sm = BigInteger.valueOf(mm).multiply(factor).toString(); - e10 = e2; - } - - e10 += sp.length() - 1; - - System.out.println("E =" + e10); - System.out.println("d+=" + sp); - System.out.println("d =" + sv); - System.out.println("d-=" + sm); - System.out.println("e2=" + e2); - } - - // Step 3: Convert to a decimal power base using 128-bit arithmetic. - // -1077 = 1 - 1023 - 53 - 2 <= e_2 - 2 <= 2046 - 1023 - 53 - 2 = 968 - long dv, dp, dm; - final int e10; - boolean dmIsTrailingZeros = false, dvIsTrailingZeros = false; - if (e2 >= 0) { - final int q = Math.max(0, ((e2 * 78913) >>> 18) - 1); - // k = constant + floor(log_2(5^q)) - final int k = POW5_INV_BITCOUNT + pow5bits(q) - 1; - final int i = -e2 + q + k; - dv = mulPow5InvDivPow2(mv, q, i); - dp = mulPow5InvDivPow2(mp, q, i); - dm = mulPow5InvDivPow2(mm, q, i); - e10 = q; - if (DEBUG) { - System.out.println(mv + " * 2^" + e2); - System.out.println("V+=" + dp); - System.out.println("V =" + dv); - System.out.println("V-=" + dm); - } - if (DEBUG) { - long exact = POW5_INV[q] - .multiply(BigInteger.valueOf(mv)) - .shiftRight(-e2 + q + k).longValueExact(); - System.out.println(exact + " " + POW5_INV[q].bitCount()); - if (dv != exact) { - throw new IllegalStateException(); - } - } - - if (q <= 21) { - if (mv % 5 == 0) { - dvIsTrailingZeros = multipleOfPowerOf5(mv, q); - } else if (roundingMode.acceptUpperBound(even)) { - dmIsTrailingZeros = multipleOfPowerOf5(mm, q); - } else if (multipleOfPowerOf5(mp, q)) { - dp--; - } - } - } else { - final int q = Math.max(0, ((-e2 * 732923) >>> 20) - 1); - final int i = -e2 - q; - final int k = pow5bits(i) - POW5_BITCOUNT; - final int j = q - k; - dv = mulPow5divPow2(mv, i, j); - dp = mulPow5divPow2(mp, i, j); - dm = mulPow5divPow2(mm, i, j); - e10 = q + e2; - if (DEBUG) { - System.out.println(mv + " * 5^" + (-e2) + " / 10^" + q); - } - if (q <= 1) { - dvIsTrailingZeros = true; - if (roundingMode.acceptUpperBound(even)) { - dmIsTrailingZeros = mmShift == 1; - } else { - dp--; - } - } else if (q < 63) { - dvIsTrailingZeros = (mv & ((1L << (q - 1)) - 1)) == 0; - } - } - if (DEBUG) { - System.out.println("d+=" + dp); - System.out.println("d =" + dv); - System.out.println("d-=" + dm); - System.out.println("e10=" + e10); - System.out.println("d-10=" + dmIsTrailingZeros); - System.out.println("d =" + dvIsTrailingZeros); - System.out.println("Accept upper=" + roundingMode.acceptUpperBound(even)); - System.out.println("Accept lower=" + roundingMode.acceptLowerBound(even)); - } - - // Step 4: Find the shortest decimal representation in the interval of legal representations. - // - // We do some extra work here in order to follow Float/Double.toString semantics. In particular, - // that requires printing in scientific format if and only if the exponent is between -3 and 7, - // and it requires printing at least two decimal digits. - // - // Above, we moved the decimal dot all the way to the right, so now we need to count digits to - // figure out the correct exponent for scientific notation. - final int vplength = decimalLength(dp); - int exp = e10 + vplength - 1; - - // Double.toString semantics requires using scientific notation if and only if outside this range. - boolean scientificNotation = !((exp >= -3) && (exp < 7)); - - int removed = 0; - - int lastRemovedDigit = 0; - long output; - if (dmIsTrailingZeros || dvIsTrailingZeros) { - while (dp / 10 > dm / 10) { - if ((dp < 100) && scientificNotation) { - // Double.toString semantics requires printing at least two digits. - break; - } - dmIsTrailingZeros &= dm % 10 == 0; - dvIsTrailingZeros &= lastRemovedDigit == 0; - lastRemovedDigit = (int) (dv % 10); - dp /= 10; - dv /= 10; - dm /= 10; - removed++; - } - if (dmIsTrailingZeros && roundingMode.acceptLowerBound(even)) { - while (dm % 10 == 0) { - if ((dp < 100) && scientificNotation) { - // Double.toString semantics requires printing at least two digits. - break; - } - dvIsTrailingZeros &= lastRemovedDigit == 0; - lastRemovedDigit = (int) (dv % 10); - dp /= 10; - dv /= 10; - dm /= 10; - removed++; - } - } - if (dvIsTrailingZeros && (lastRemovedDigit == 5) && (dv % 2 == 0)) { - // Round even if the exact numbers is .....50..0. - lastRemovedDigit = 4; - } - output = dv + - ((dv == dm && !(dmIsTrailingZeros && roundingMode.acceptLowerBound(even))) || (lastRemovedDigit >= 5) ? 1 : 0); - } else { - while (dp / 10 > dm / 10) { - if ((dp < 100) && scientificNotation) { - // Double.toString semantics requires printing at least two digits. - break; - } - lastRemovedDigit = (int) (dv % 10); - dp /= 10; - dv /= 10; - dm /= 10; - removed++; - } - output = dv + ((dv == dm || (lastRemovedDigit >= 5)) ? 1 : 0); - } - int olength = vplength - removed; - - if (DEBUG) { - System.out.println("LAST_REMOVED_DIGIT=" + lastRemovedDigit); - System.out.println("VP=" + dp); - System.out.println("VR=" + dv); - System.out.println("VM=" + dm); - System.out.println("O=" + output); - System.out.println("OLEN=" + olength); - System.out.println("EXP=" + exp); - } - - // Step 5: Print the decimal representation. - // We follow Double.toString semantics here. - char[] result = new char[24]; - int index = 0; - if (sign) { - result[index++] = '-'; - } - - // Values in the interval [1E-3, 1E7) are special. - if (scientificNotation) { - // Print in the format x.xxxxxE-yy. - for (int i = 0; i < olength - 1; i++) { - int c = (int) (output % 10); output /= 10; - result[index + olength - i] = (char) ('0' + c); - } - result[index] = (char) ('0' + output % 10); - result[index + 1] = '.'; - index += olength + 1; - if (olength == 1) { - result[index++] = '0'; - } - - // Print 'E', the exponent sign, and the exponent, which has at most three digits. - result[index++] = 'E'; - if (exp < 0) { - result[index++] = '-'; - exp = -exp; - } - if (exp >= 100) { - result[index++] = (char) ('0' + exp / 100); - exp %= 100; - result[index++] = (char) ('0' + exp / 10); - } else if (exp >= 10) { - result[index++] = (char) ('0' + exp / 10); - } - result[index++] = (char) ('0' + exp % 10); - return new String(result, 0, index); - } else { - // Otherwise follow the Java spec for values in the interval [1E-3, 1E7). - if (exp < 0) { - // Decimal dot is before any of the digits. - result[index++] = '0'; - result[index++] = '.'; - for (int i = -1; i > exp; i--) { - result[index++] = '0'; - } - int current = index; - for (int i = 0; i < olength; i++) { - result[current + olength - i - 1] = (char) ('0' + output % 10); - output /= 10; - index++; - } - } else if (exp + 1 >= olength) { - // Decimal dot is after any of the digits. - for (int i = 0; i < olength; i++) { - result[index + olength - i - 1] = (char) ('0' + output % 10); - output /= 10; - } - index += olength; - for (int i = olength; i < exp + 1; i++) { - result[index++] = '0'; - } - result[index++] = '.'; - result[index++] = '0'; - } else { - // Decimal dot is somewhere between the digits. - int current = index + 1; - for (int i = 0; i < olength; i++) { - if (olength - i - 1 == exp) { - result[current + olength - i - 1] = '.'; - current--; - } - result[current + olength - i - 1] = (char) ('0' + output % 10); - output /= 10; - } - index += olength + 1; - } - return new String(result, 0, index); - } - } - - private static int pow5bits(int e) { - return ((e * 1217359) >>> 19) + 1; - } - - private static int decimalLength(long v) { - if (v >= 1000000000000000000L) return 19; - if (v >= 100000000000000000L) return 18; - if (v >= 10000000000000000L) return 17; - if (v >= 1000000000000000L) return 16; - if (v >= 100000000000000L) return 15; - if (v >= 10000000000000L) return 14; - if (v >= 1000000000000L) return 13; - if (v >= 100000000000L) return 12; - if (v >= 10000000000L) return 11; - if (v >= 1000000000L) return 10; - if (v >= 100000000L) return 9; - if (v >= 10000000L) return 8; - if (v >= 1000000L) return 7; - if (v >= 100000L) return 6; - if (v >= 10000L) return 5; - if (v >= 1000L) return 4; - if (v >= 100L) return 3; - if (v >= 10L) return 2; - return 1; - } - - private static boolean multipleOfPowerOf5(long value, int q) { - return pow5Factor(value) >= q; - } - - private static int pow5Factor(long value) { - // We want to find the largest power of 5 that divides value. - if ((value % 5) != 0) return 0; - if ((value % 25) != 0) return 1; - if ((value % 125) != 0) return 2; - if ((value % 625) != 0) return 3; - int count = 4; - value /= 625; - while (value > 0) { - if (value % 5 != 0) { - return count; - } - value /= 5; - count++; - } - throw new IllegalArgumentException("" + value); - } - - /** - * Compute the high digits of m * 5^p / 10^q = m * 5^(p - q) / 2^q = m * 5^i / 2^j, with q chosen - * such that m * 5^i / 2^j has sufficiently many decimal digits to represent the original floating - * point number. - */ - private static long mulPow5divPow2(long m, int i, int j) { - // m has at most 55 bits. - long mHigh = m >>> 31; - long mLow = m & 0x7fffffff; - long bits13 = mHigh * POW5_SPLIT[i][0]; // 124 - long bits03 = mLow * POW5_SPLIT[i][0]; // 93 - long bits12 = mHigh * POW5_SPLIT[i][1]; // 93 - long bits02 = mLow * POW5_SPLIT[i][1]; // 62 - long bits11 = mHigh * POW5_SPLIT[i][2]; // 62 - long bits01 = mLow * POW5_SPLIT[i][2]; // 31 - long bits10 = mHigh * POW5_SPLIT[i][3]; // 31 - long bits00 = mLow * POW5_SPLIT[i][3]; // 0 - int actualShift = j - 3 * 31 - 21; - if (actualShift < 0) { - throw new IllegalArgumentException("" + actualShift); - } - return (((((( - ((bits00 >>> 31) + bits01 + bits10) >>> 31) - + bits02 + bits11) >>> 31) - + bits03 + bits12) >>> 21) - + (bits13 << 10)) >>> actualShift; - } - - /** - * Compute the high digits of m / 5^i / 2^j such that the result is accurate to at least 9 - * decimal digits. i and j are already chosen appropriately. - */ - private static long mulPow5InvDivPow2(long m, int i, int j) { - // m has at most 55 bits. - long mHigh = m >>> 31; - long mLow = m & 0x7fffffff; - long bits13 = mHigh * POW5_INV_SPLIT[i][0]; - long bits03 = mLow * POW5_INV_SPLIT[i][0]; - long bits12 = mHigh * POW5_INV_SPLIT[i][1]; - long bits02 = mLow * POW5_INV_SPLIT[i][1]; - long bits11 = mHigh * POW5_INV_SPLIT[i][2]; - long bits01 = mLow * POW5_INV_SPLIT[i][2]; - long bits10 = mHigh * POW5_INV_SPLIT[i][3]; - long bits00 = mLow * POW5_INV_SPLIT[i][3]; - - int actualShift = j - 3 * 31 - 21; - if (actualShift < 0) { - throw new IllegalArgumentException("" + actualShift); - } - return (((((( - ((bits00 >>> 31) + bits01 + bits10) >>> 31) - + bits02 + bits11) >>> 31) - + bits03 + bits12) >>> 21) - + (bits13 << 10)) >>> actualShift; - } -} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/RyuDoubleTest.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/RyuDoubleTest.java deleted file mode 100644 index 8288280d11..0000000000 --- a/src/test/java/com/fasterxml/jackson/core/io/ryu/RyuDoubleTest.java +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2018 Ulf Adams -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.fasterxml.jackson.core.io.ryu; - -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public class RyuDoubleTest extends DoubleToStringTest { - @Override - String f(double f, RoundingMode roundingMode) { - return RyuDouble.doubleToString(f, roundingMode); - } -} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/RyuFloat.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/RyuFloat.java deleted file mode 100644 index 68c260b2ae..0000000000 --- a/src/test/java/com/fasterxml/jackson/core/io/ryu/RyuFloat.java +++ /dev/null @@ -1,419 +0,0 @@ - -// Copyright 2018 Ulf Adams -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.fasterxml.jackson.core.io.ryu; - -import com.fasterxml.jackson.core.io.ryu.RoundingMode; - -import java.math.BigInteger; - -/** - * An implementation of Ryu for float. - * - * A fork from https://github.com/ulfjack/ryu/ - */ -public final class RyuFloat { - private static boolean DEBUG = false; - - private static final int FLOAT_MANTISSA_BITS = 23; - private static final int FLOAT_MANTISSA_MASK = (1 << FLOAT_MANTISSA_BITS) - 1; - - private static final int FLOAT_EXPONENT_BITS = 8; - private static final int FLOAT_EXPONENT_MASK = (1 << FLOAT_EXPONENT_BITS) - 1; - private static final int FLOAT_EXPONENT_BIAS = (1 << (FLOAT_EXPONENT_BITS - 1)) - 1; - - private static final long LOG10_2_DENOMINATOR = 10000000L; - private static final long LOG10_2_NUMERATOR = (long) (LOG10_2_DENOMINATOR * Math.log10(2)); - - private static final long LOG10_5_DENOMINATOR = 10000000L; - private static final long LOG10_5_NUMERATOR = (long) (LOG10_5_DENOMINATOR * Math.log10(5)); - - private static final long LOG2_5_DENOMINATOR = 10000000L; - private static final long LOG2_5_NUMERATOR = (long) (LOG2_5_DENOMINATOR * (Math.log(5)/Math.log(2))); - - private static final int POS_TABLE_SIZE = 47; - private static final int INV_TABLE_SIZE = 31; - - // Only for debugging. - private static final BigInteger[] POW5 = new BigInteger[POS_TABLE_SIZE]; - private static final BigInteger[] POW5_INV = new BigInteger[INV_TABLE_SIZE]; - - private static final int POW5_BITCOUNT = 61; - private static final int POW5_HALF_BITCOUNT = 31; - private static final int[][] POW5_SPLIT = new int[POS_TABLE_SIZE][2]; - - private static final int POW5_INV_BITCOUNT = 59; - private static final int POW5_INV_HALF_BITCOUNT = 31; - private static final int[][] POW5_INV_SPLIT = new int[INV_TABLE_SIZE][2]; - - static { - BigInteger mask = BigInteger.valueOf(1).shiftLeft(POW5_HALF_BITCOUNT).subtract(BigInteger.ONE); - BigInteger maskInv = BigInteger.valueOf(1).shiftLeft(POW5_INV_HALF_BITCOUNT).subtract(BigInteger.ONE); - for (int i = 0; i < Math.max(POW5.length, POW5_INV.length); i++) { - BigInteger pow = BigInteger.valueOf(5).pow(i); - int pow5len = pow.bitLength(); - int expectedPow5Bits = pow5bits(i); - if (expectedPow5Bits != pow5len) { - throw new IllegalStateException(pow5len + " != " + expectedPow5Bits); - } - if (i < POW5.length) { - POW5[i] = pow; - } - if (i < POW5_SPLIT.length) { - POW5_SPLIT[i][0] = pow.shiftRight(pow5len - POW5_BITCOUNT + POW5_HALF_BITCOUNT).intValueExact(); - POW5_SPLIT[i][1] = pow.shiftRight(pow5len - POW5_BITCOUNT).and(mask).intValueExact(); - } - - if (i < POW5_INV.length) { - int j = pow5len - 1 + POW5_INV_BITCOUNT; - BigInteger inv = BigInteger.ONE.shiftLeft(j).divide(pow).add(BigInteger.ONE); - POW5_INV[i] = inv; - POW5_INV_SPLIT[i][0] = inv.shiftRight(POW5_INV_HALF_BITCOUNT).intValueExact(); - POW5_INV_SPLIT[i][1] = inv.and(maskInv).intValueExact(); - } - } - } - - public static String floatToString(float value) { - return floatToString(value, RoundingMode.ROUND_EVEN); - } - - public static String floatToString(float value, RoundingMode roundingMode) { - // Step 1: Decode the floating point number, and unify normalized and subnormal cases. - // First, handle all the trivial cases. - if (Float.isNaN(value)) return "NaN"; - if (value == Float.POSITIVE_INFINITY) return "Infinity"; - if (value == Float.NEGATIVE_INFINITY) return "-Infinity"; - int bits = Float.floatToIntBits(value); - if (bits == 0) return "0.0"; - if (bits == 0x80000000) return "-0.0"; - - // Otherwise extract the mantissa and exponent bits and run the full algorithm. - int ieeeExponent = (bits >> FLOAT_MANTISSA_BITS) & FLOAT_EXPONENT_MASK; - int ieeeMantissa = bits & FLOAT_MANTISSA_MASK; - // By default, the correct mantissa starts with a 1, except for denormal numbers. - int e2; - int m2; - if (ieeeExponent == 0) { - e2 = 1 - FLOAT_EXPONENT_BIAS - FLOAT_MANTISSA_BITS; - m2 = ieeeMantissa; - } else { - e2 = ieeeExponent - FLOAT_EXPONENT_BIAS - FLOAT_MANTISSA_BITS; - m2 = ieeeMantissa | (1 << FLOAT_MANTISSA_BITS); - } - - boolean sign = bits < 0; - if (DEBUG) { - System.out.println("IN=" + Long.toBinaryString(bits)); - System.out.println(" S=" + (sign ? "-" : "+") + " E=" + e2 + " M=" + m2); - } - - // Step 2: Determine the interval of legal decimal representations. - boolean even = (m2 & 1) == 0; - int mv = 4 * m2; - int mp = 4 * m2 + 2; - int mm = 4 * m2 - ((m2 != (1L << FLOAT_MANTISSA_BITS)) || (ieeeExponent <= 1) ? 2 : 1); - e2 -= 2; - - if (DEBUG) { - String sv, sp, sm; - int e10; - if (e2 >= 0) { - sv = BigInteger.valueOf(mv).shiftLeft(e2).toString(); - sp = BigInteger.valueOf(mp).shiftLeft(e2).toString(); - sm = BigInteger.valueOf(mm).shiftLeft(e2).toString(); - e10 = 0; - } else { - BigInteger factor = BigInteger.valueOf(5).pow(-e2); - sv = BigInteger.valueOf(mv).multiply(factor).toString(); - sp = BigInteger.valueOf(mp).multiply(factor).toString(); - sm = BigInteger.valueOf(mm).multiply(factor).toString(); - e10 = e2; - } - - e10 += sp.length() - 1; - - System.out.println("Exact values"); - System.out.println(" m =" + mv); - System.out.println(" E =" + e10); - System.out.println(" d+=" + sp); - System.out.println(" d =" + sv); - System.out.println(" d-=" + sm); - System.out.println(" e2=" + e2); - } - - // Step 3: Convert to a decimal power base using 128-bit arithmetic. - // -151 = 1 - 127 - 23 - 2 <= e_2 - 2 <= 254 - 127 - 23 - 2 = 102 - int dp, dv, dm; - int e10; - boolean dpIsTrailingZeros, dvIsTrailingZeros, dmIsTrailingZeros; - int lastRemovedDigit = 0; - if (e2 >= 0) { - // Compute m * 2^e_2 / 10^q = m * 2^(e_2 - q) / 5^q - int q = (int) (e2 * LOG10_2_NUMERATOR / LOG10_2_DENOMINATOR); - int k = POW5_INV_BITCOUNT + pow5bits(q) - 1; - int i = -e2 + q + k; - dv = (int) mulPow5InvDivPow2(mv, q, i); - dp = (int) mulPow5InvDivPow2(mp, q, i); - dm = (int) mulPow5InvDivPow2(mm, q, i); - if (q != 0 && ((dp - 1) / 10 <= dm / 10)) { - // We need to know one removed digit even if we are not going to loop below. We could use - // q = X - 1 above, except that would require 33 bits for the result, and we've found that - // 32-bit arithmetic is faster even on 64-bit machines. - int l = POW5_INV_BITCOUNT + pow5bits(q - 1) - 1; - lastRemovedDigit = (int) (mulPow5InvDivPow2(mv, q - 1, -e2 + q - 1 + l) % 10); - } - e10 = q; - if (DEBUG) { - System.out.println(mv + " * 2^" + e2 + " / 10^" + q); - } - - dpIsTrailingZeros = pow5Factor(mp) >= q; - dvIsTrailingZeros = pow5Factor(mv) >= q; - dmIsTrailingZeros = pow5Factor(mm) >= q; - } else { - // Compute m * 5^(-e_2) / 10^q = m * 5^(-e_2 - q) / 2^q - int q = (int) (-e2 * LOG10_5_NUMERATOR / LOG10_5_DENOMINATOR); - int i = -e2 - q; - int k = pow5bits(i) - POW5_BITCOUNT; - int j = q - k; - dv = (int) mulPow5divPow2(mv, i, j); - dp = (int) mulPow5divPow2(mp, i, j); - dm = (int) mulPow5divPow2(mm, i, j); - if (q != 0 && ((dp - 1) / 10 <= dm / 10)) { - j = q - 1 - (pow5bits(i + 1) - POW5_BITCOUNT); - lastRemovedDigit = (int) (mulPow5divPow2(mv, i + 1, j) % 10); - } - e10 = q + e2; // Note: e2 and e10 are both negative here. - if (DEBUG) { - System.out.println(mv + " * 5^" + (-e2) + " / 10^" + q + " = " + mv + " * 5^" + (-e2 - q) + " / 2^" + q); - } - - dpIsTrailingZeros = 1 >= q; - dvIsTrailingZeros = (q < FLOAT_MANTISSA_BITS) && (mv & ((1 << (q - 1)) - 1)) == 0; - dmIsTrailingZeros = (mm % 2 == 1 ? 0 : 1) >= q; - } - if (DEBUG) { - System.out.println("Actual values"); - System.out.println(" d+=" + dp); - System.out.println(" d =" + dv); - System.out.println(" d-=" + dm); - System.out.println(" last removed=" + lastRemovedDigit); - System.out.println(" e10=" + e10); - System.out.println(" d+10=" + dpIsTrailingZeros); - System.out.println(" d =" + dvIsTrailingZeros); - System.out.println(" d-10=" + dmIsTrailingZeros); - } - - // Step 4: Find the shortest decimal representation in the interval of legal representations. - // - // We do some extra work here in order to follow Float/Double.toString semantics. In particular, - // that requires printing in scientific format if and only if the exponent is between -3 and 7, - // and it requires printing at least two decimal digits. - // - // Above, we moved the decimal dot all the way to the right, so now we need to count digits to - // figure out the correct exponent for scientific notation. - int dplength = decimalLength(dp); - int exp = e10 + dplength - 1; - - // Float.toString semantics requires using scientific notation if and only if outside this range. - boolean scientificNotation = !((exp >= -3) && (exp < 7)); - - int removed = 0; - if (dpIsTrailingZeros && !roundingMode.acceptUpperBound(even)) { - dp--; - } - - while (dp / 10 > dm / 10) { - if ((dp < 100) && scientificNotation) { - // We print at least two digits, so we might as well stop now. - break; - } - dmIsTrailingZeros &= dm % 10 == 0; - dp /= 10; - lastRemovedDigit = dv % 10; - dv /= 10; - dm /= 10; - removed++; - } - if (dmIsTrailingZeros && roundingMode.acceptLowerBound(even)) { - while (dm % 10 == 0) { - if ((dp < 100) && scientificNotation) { - // We print at least two digits, so we might as well stop now. - break; - } - dp /= 10; - lastRemovedDigit = dv % 10; - dv /= 10; - dm /= 10; - removed++; - } - } - - if (dvIsTrailingZeros && (lastRemovedDigit == 5) && (dv % 2 == 0)) { - // Round down not up if the number ends in X50000 and the number is even. - lastRemovedDigit = 4; - } - int output = dv + - ((dv == dm && !(dmIsTrailingZeros && roundingMode.acceptLowerBound(even))) || (lastRemovedDigit >= 5) ? 1 : 0); - int olength = dplength - removed; - - if (DEBUG) { - System.out.println("Actual values after loop"); - System.out.println(" d+=" + dp); - System.out.println(" d =" + dv); - System.out.println(" d-=" + dm); - System.out.println(" last removed=" + lastRemovedDigit); - System.out.println(" e10=" + e10); - System.out.println(" d+10=" + dpIsTrailingZeros); - System.out.println(" d-10=" + dmIsTrailingZeros); - System.out.println(" output=" + output); - System.out.println(" output_length=" + olength); - System.out.println(" output_exponent=" + exp); - } - - // Step 5: Print the decimal representation. - // We follow Float.toString semantics here. - char[] result = new char[15]; - int index = 0; - if (sign) { - result[index++] = '-'; - } - - if (scientificNotation) { - // Print in the format x.xxxxxE-yy. - for (int i = 0; i < olength - 1; i++) { - int c = output % 10; output /= 10; - result[index + olength - i] = (char) ('0' + c); - } - result[index] = (char) ('0' + output % 10); - result[index + 1] = '.'; - index += olength + 1; - if (olength == 1) { - result[index++] = '0'; - } - - // Print 'E', the exponent sign, and the exponent, which has at most two digits. - result[index++] = 'E'; - if (exp < 0) { - result[index++] = '-'; - exp = -exp; - } - if (exp >= 10) { - result[index++] = (char) ('0' + exp / 10); - } - result[index++] = (char) ('0' + exp % 10); - } else { - // Otherwise follow the Java spec for values in the interval [1E-3, 1E7). - if (exp < 0) { - // Decimal dot is before any of the digits. - result[index++] = '0'; - result[index++] = '.'; - for (int i = -1; i > exp; i--) { - result[index++] = '0'; - } - int current = index; - for (int i = 0; i < olength; i++) { - result[current + olength - i - 1] = (char) ('0' + output % 10); - output /= 10; - index++; - } - } else if (exp + 1 >= olength) { - // Decimal dot is after any of the digits. - for (int i = 0; i < olength; i++) { - result[index + olength - i - 1] = (char) ('0' + output % 10); - output /= 10; - } - index += olength; - for (int i = olength; i < exp + 1; i++) { - result[index++] = '0'; - } - result[index++] = '.'; - result[index++] = '0'; - } else { - // Decimal dot is somewhere between the digits. - int current = index + 1; - for (int i = 0; i < olength; i++) { - if (olength - i - 1 == exp) { - result[current + olength - i - 1] = '.'; - current--; - } - result[current + olength - i - 1] = (char) ('0' + output % 10); - output /= 10; - } - index += olength + 1; - } - } - return new String(result, 0, index); - } - - private static int pow5bits(int e) { - return e == 0 ? 1 : (int) ((e * LOG2_5_NUMERATOR + LOG2_5_DENOMINATOR - 1)/LOG2_5_DENOMINATOR); - } - - /** - * Returns the exponent of the largest power of 5 that divides the given value, i.e., returns - * i such that value = 5^i * x, where x is an integer. - */ - private static int pow5Factor(int value) { - int count = 0; - while (value > 0) { - if (value % 5 != 0) { - return count; - } - value /= 5; - count++; - } - throw new IllegalArgumentException("" + value); - } - - /** - * Compute the exact result of [m * 5^(-e_2) / 10^q] = [m * 5^(-e_2 - q) / 2^q] - * = [m * [5^(p - q)/2^k] / 2^(q - k)] = [m * POW5[i] / 2^j]. - */ - private static long mulPow5divPow2(int m, int i, int j) { - if (j - POW5_HALF_BITCOUNT < 0) { - throw new IllegalArgumentException(); - } - long bits0 = m * (long) POW5_SPLIT[i][0]; - long bits1 = m * (long) POW5_SPLIT[i][1]; - return (bits0 + (bits1 >> POW5_HALF_BITCOUNT)) >> (j - POW5_HALF_BITCOUNT); - } - - /** - * Compute the exact result of [m * 2^p / 10^q] = [m * 2^(p - q) / 5 ^ q] - * = [m * [2^k / 5^q] / 2^-(p - q - k)] = [m * POW5_INV[q] / 2^j]. - */ - private static long mulPow5InvDivPow2(int m, int q, int j) { - if (j - POW5_INV_HALF_BITCOUNT < 0) { - throw new IllegalArgumentException(); - } - long bits0 = m * (long) POW5_INV_SPLIT[q][0]; - long bits1 = m * (long) POW5_INV_SPLIT[q][1]; - return (bits0 + (bits1 >> POW5_INV_HALF_BITCOUNT)) >> (j - POW5_INV_HALF_BITCOUNT); - } - - private static int decimalLength(int v) { - int length = 10; - int factor = 1000000000; - for (; length > 0; length--) { - if (v >= factor) { - break; - } - factor /= 10; - } - return length; - } -} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/RyuFloatTest.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/RyuFloatTest.java deleted file mode 100644 index eb6d52027b..0000000000 --- a/src/test/java/com/fasterxml/jackson/core/io/ryu/RyuFloatTest.java +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2018 Ulf Adams -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.fasterxml.jackson.core.io.ryu; - -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public class RyuFloatTest extends FloatToStringTest { - @Override - String f(float f, RoundingMode roundingMode) { - return RyuFloat.floatToString(f, roundingMode); - } -} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/SlowDoubleTest.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/SlowDoubleTest.java deleted file mode 100644 index 7720fd5001..0000000000 --- a/src/test/java/com/fasterxml/jackson/core/io/ryu/SlowDoubleTest.java +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2018 Ulf Adams -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.fasterxml.jackson.core.io.ryu; - -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -import com.fasterxml.jackson.core.io.ryu.analysis.SlowConversion; - -@RunWith(JUnit4.class) -public class SlowDoubleTest extends DoubleToStringTest { - @Override - String f(double f, RoundingMode roundingMode) { - return SlowConversion.doubleToString(f, roundingMode); - } -} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/SlowFloatTest.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/SlowFloatTest.java deleted file mode 100644 index 3c6fd22c14..0000000000 --- a/src/test/java/com/fasterxml/jackson/core/io/ryu/SlowFloatTest.java +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2018 Ulf Adams -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.fasterxml.jackson.core.io.ryu; - -import com.fasterxml.jackson.core.io.ryu.analysis.SlowConversion; - -public class SlowFloatTest extends FloatToStringTest { - @Override - String f(float f, RoundingMode roundingMode) { - return SlowConversion.floatToString(f, roundingMode); - } -} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/EuclidMinMax.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/EuclidMinMax.java deleted file mode 100644 index 6092b3fdfe..0000000000 --- a/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/EuclidMinMax.java +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2018 Ulf Adams -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.fasterxml.jackson.core.io.ryu.analysis; - -import java.math.BigInteger; - -/** - * Computes the modular min and max using a modified version of Euclid's algorithm. - */ -public final class EuclidMinMax { - private static final boolean DEBUG = false; - - public static BigInteger min(BigInteger multiplier, BigInteger modulo, BigInteger maximum) { - if (DEBUG) { - System.out.println(); - } - BigInteger c = multiplier.gcd(modulo); - BigInteger b = modulo.divide(c); - BigInteger a = multiplier.divide(c).mod(b); - if (maximum.compareTo(b) >= 0) { - return BigInteger.ZERO; - } - if (DEBUG) { - System.out.println("C=" + c); - } - BigInteger s = BigInteger.ONE; - BigInteger t = BigInteger.ZERO; - BigInteger u = BigInteger.ZERO; - BigInteger v = BigInteger.ONE; - while (true) { - if (DEBUG) { - System.out.printf("A=%s\nB=%s\n", a, b); - } - while (b.compareTo(a) >= 0) { - b = b.subtract(a); - u = u.subtract(s); - v = v.subtract(t); - if (DEBUG) { - System.out.printf("U=%s (A=%s)\n", u, a); - } - if (u.negate().compareTo(maximum) >= 0) { - return a.multiply(c); - } - } - if (b.equals(BigInteger.ZERO)) { - return BigInteger.ZERO; - } - while (a.compareTo(b) >= 0) { - BigInteger oldA = a; - a = a.subtract(b); - s = s.subtract(u); - t = t.subtract(v); - if (DEBUG) { - System.out.printf("S=%s (A=%s)\n", s, a); - } - int cmp = s.compareTo(maximum); - if (cmp >= 0) { - if (cmp > 0) { - return oldA.multiply(c); - } else { - return a.multiply(c); - } - } - } - if (a.equals(BigInteger.ZERO)) { - return BigInteger.ZERO; - } - } - } - - public static BigInteger max(BigInteger multiplier, BigInteger modulo, BigInteger maximum) { - if (DEBUG) { - System.out.println(); - } - BigInteger c = multiplier.gcd(modulo); - BigInteger b = modulo.divide(c); - BigInteger a = multiplier.divide(c).mod(b); - if (DEBUG) { - System.out.printf("A=%s B=%s C=%s MAX=%s\n", a, b, c, maximum); - } - if (maximum.compareTo(b) >= 0) { - return modulo.subtract(c); - } - BigInteger s = BigInteger.ONE; - BigInteger t = BigInteger.ZERO; - BigInteger u = BigInteger.ZERO; - BigInteger v = BigInteger.ONE; - while (true) { - if (DEBUG) { - System.out.printf("A=%s\nB=%s\n", a, b); - } - while (b.compareTo(a) >= 0) { - BigInteger q = b.divide(a); - q = q.min(maximum.subtract(u.negate()).divide(s).subtract(BigInteger.ONE)); - q = q.max(BigInteger.ONE); - BigInteger oldB = b; - b = b.subtract(a.multiply(q)); - u = u.subtract(s.multiply(q)); - v = v.subtract(t.multiply(q)); - if (DEBUG) { - System.out.printf("U=%s (B=%s)\n", u, b); - } - int cmp = u.negate().compareTo(maximum); - if (cmp >= 0) { - if (cmp > 0) { - return modulo.subtract(oldB.multiply(c)); - } else { - return modulo.subtract(b.multiply(c)); - } - } - } - if (b.equals(BigInteger.ZERO)) { - return modulo.subtract(c); - } - while (a.compareTo(b) >= 0) { - BigInteger q = BigInteger.ONE; //a.divide(b); - if (!u.equals(BigInteger.ZERO)) { - q = q.min(maximum.subtract(s).divide(u.negate().add(BigInteger.ONE))); - } - q = q.max(BigInteger.ONE); - a = a.subtract(b.multiply(q)); - s = s.subtract(u.multiply(q)); - t = t.subtract(v.multiply(q)); - if (DEBUG) { - System.out.printf("S=%s (B=%s)\n", s, b); - } - if (s.compareTo(maximum) >= 0) { - return modulo.subtract(b.multiply(c)); - } - } - if (a.equals(BigInteger.ZERO)) { - return modulo.subtract(c); - } - } - } -} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/FloatingPointFormat.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/FloatingPointFormat.java deleted file mode 100644 index 9f4b89bc6b..0000000000 --- a/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/FloatingPointFormat.java +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2018 Ulf Adams -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.fasterxml.jackson.core.io.ryu.analysis; - -enum FloatingPointFormat { - FLOAT16(16, 5, 10), - FLOAT32(32, 8, 23), - FLOAT64(64, 11, 52), - FLOAT80(80, 15, 63), - FLOAT128(128, 15, 112), - FLOAT256(256, 19, 236); - - private final int totalBits; - private final int exponentBits; - private final int mantissaBits; - - private FloatingPointFormat(int totalBits, int exponentBits, int mantissaBits) { - this.totalBits = totalBits; - this.exponentBits = exponentBits; - this.mantissaBits = mantissaBits; - } - - public int totalBits() { - return totalBits; - } - - public int exponentBits() { - return exponentBits; - } - - public int mantissaBits() { - return mantissaBits; - } - - public int bias() { - return (1 << (exponentBits - 1)) - 1; - } -} diff --git a/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/SlowConversion.java b/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/SlowConversion.java deleted file mode 100644 index 87c9db2fc6..0000000000 --- a/src/test/java/com/fasterxml/jackson/core/io/ryu/analysis/SlowConversion.java +++ /dev/null @@ -1,243 +0,0 @@ -// Copyright 2018 Ulf Adams -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.fasterxml.jackson.core.io.ryu.analysis; - -import java.math.BigInteger; - -import com.fasterxml.jackson.core.io.ryu.RoundingMode; - -/** - * Implementation of Float and Double to String conversion. - */ -public final class SlowConversion { - private static final BigInteger TWO = BigInteger.valueOf(2); - - private static boolean DEBUG = false; - private static final boolean DEBUG_FLOAT = true; - - public static void main(String[] args) { - DEBUG = true; - if (DEBUG_FLOAT) { - float f = 0.33007812f; - String result = floatToString(f); - System.out.println(result + " " + f); - } else { - double f = 1.1873267205539228E-308; - String result = doubleToString(f); - System.out.println(result + " " + f); - } - } - - public static String floatToString(float value) { - return floatToString(value, RoundingMode.ROUND_EVEN); - } - - public static String floatToString(float value, RoundingMode roundingMode) { - if (DEBUG) System.out.println("VALUE="+value); - long bits = Float.floatToIntBits(value) & 0xffffffffL; - return asString(bits, FloatingPointFormat.FLOAT32, roundingMode); - } - - public static String doubleToString(double value) { - return doubleToString(value, RoundingMode.ROUND_EVEN); - } - - public static String doubleToString(double value, RoundingMode roundingMode) { - if (DEBUG) System.out.println("VALUE="+value); - long bits = Double.doubleToLongBits(value); - return asString(bits, FloatingPointFormat.FLOAT64, roundingMode); - } - - static String asString(long bits, FloatingPointFormat format, RoundingMode mode) { - // Step 1: Decode the floating point number, and unify normalized and subnormal cases. - // - // The format of all IEEE numbers is S E* M*; we obtain M by masking the lower M bits, E by - // shifting and masking, and S also by shifting and masking. - int mantissaBits = format.mantissaBits(); - int exponentBits = format.exponentBits(); - - int ieeeExponent = (int) ((bits >>> mantissaBits) & ((1 << exponentBits) - 1)); - long ieeeMantissa = bits & ((1L << mantissaBits) - 1); - boolean sign = ((bits >>> (mantissaBits + exponentBits)) & 1) != 0; - boolean even = (bits & 1) == 0; - - // Exit early if it's NaN, Infinity, or 0. - if (ieeeExponent == ((1 << exponentBits) - 1)) { - // Handle the special cases where the exponent is all 1s indicating NaN or Infinity: if the - // mantissa is non-zero, it's a NaN, otherwise it's +/-infinity. - return (ieeeMantissa != 0) ? "NaN" : sign ? "-Infinity" : "Infinity"; - } else if ((ieeeExponent == 0) && (ieeeMantissa == 0)) { - // If the mantissa is 0, the code below would end up with a lower bound that is less than 0, - // which throws off the char-by-char comparison. Instead, we exit here with the correct - // string. - return sign ? "-0.0" : "0.0"; - } - - // Compute the offset used by the IEEE format. - int offset = (1 << (exponentBits - 1)) - 1; - - // Unify normalized and subnormal cases. - int e2; - long m2; - if (ieeeExponent == 0) { - e2 = 1 - offset - mantissaBits; - m2 = ieeeMantissa; - } else { - e2 = ieeeExponent - offset - mantissaBits; - m2 = ieeeMantissa | (1L << mantissaBits); - } - - // Step 2: Determine the interval of legal decimal representations. - long mv = 4 * m2; - long mp = 4 * m2 + 2; - long mm = 4 * m2 - (((m2 != (1L << mantissaBits)) || (ieeeExponent <= 1)) ? 2 : 1); - e2 -= 2; - - // Step 3: Convert to a decimal power base using arbitrary-precision arithmetic. - BigInteger vr, vp, vm; - int e10; - if (e2 >= 0) { - vr = BigInteger.valueOf(mv).shiftLeft(e2); - vp = BigInteger.valueOf(mp).shiftLeft(e2); - vm = BigInteger.valueOf(mm).shiftLeft(e2); - e10 = 0; - } else { - BigInteger factor = BigInteger.valueOf(5).pow(-e2); - vr = BigInteger.valueOf(mv).multiply(factor); - vp = BigInteger.valueOf(mp).multiply(factor); - vm = BigInteger.valueOf(mm).multiply(factor); - e10 = e2; - } - - // Step 4: Find the shortest decimal representation in the interval of legal representations. - // - // We do some extra work here in order to follow Float/Double.toString semantics. In particular, - // that requires printing in scientific format if and only if the exponent is between -3 and 7, - // and it requires printing at least two decimal digits. - // - // Above, we moved the decimal dot all the way to the right, so now we need to count digits to - // figure out the correct exponent for scientific notation. - int vpLength = vp.toString().length(); - e10 += vpLength - 1; - boolean scientificNotation = (e10 < -3) || (e10 >= 7); - - if (DEBUG) { - System.out.println("IN=" + Long.toBinaryString(bits)); - System.out.println(" S=" + (sign ? "-" : "+") + " E=" + e2 + " M=" + m2); - System.out.println("E ="+e10); - System.out.println("V+="+vp); - System.out.println("V ="+vr); - System.out.println("V-="+vm); - } - - if (!mode.acceptUpperBound(even)) { - vp = vp.subtract(BigInteger.ONE); - } - boolean vmIsTrailingZeros = true; - // Track if vr is tailing zeroes _after_ lastRemovedDigit. - boolean vrIsTrailingZeros = true; - int removed = 0; - int lastRemovedDigit = 0; - while (!vp.divide(BigInteger.TEN).equals(vm.divide(BigInteger.TEN))) { - if (scientificNotation && vp.compareTo(BigInteger.valueOf(100)) < 0) { - // Float/Double.toString semantics requires printing at least two digits. - break; - } - vmIsTrailingZeros &= vm.mod(BigInteger.TEN).intValueExact() == 0; - vrIsTrailingZeros &= lastRemovedDigit == 0; - lastRemovedDigit = vr.mod(BigInteger.TEN).intValueExact(); - vp = vp.divide(BigInteger.TEN); - vr = vr.divide(BigInteger.TEN); - vm = vm.divide(BigInteger.TEN); - removed++; - } - if (vmIsTrailingZeros && mode.acceptLowerBound(even)) { - while (vm.mod(BigInteger.TEN).intValueExact() == 0) { - if (scientificNotation && vp.compareTo(BigInteger.valueOf(100)) < 0) { - // Float/Double.toString semantics requires printing at least two digits. - break; - } - vrIsTrailingZeros &= lastRemovedDigit == 0; - lastRemovedDigit = vr.mod(BigInteger.TEN).intValueExact(); - vp = vp.divide(BigInteger.TEN); - vr = vr.divide(BigInteger.TEN); - vm = vm.divide(BigInteger.TEN); - removed++; - } - } - if (vrIsTrailingZeros && (lastRemovedDigit == 5) && (vr.mod(TWO).intValueExact() == 0)) { - // Round down not up if the number ends in X50000 and the number is even. - lastRemovedDigit = 4; - } - String output = ((vr.compareTo(vm) > 0) ? (lastRemovedDigit >= 5 ? vr.add(BigInteger.ONE) : vr) : vp).toString(); - int olength = vpLength - removed; - - if (DEBUG) { - System.out.println("LRD=" + lastRemovedDigit); - System.out.println("VP=" + vp); - System.out.println("VR=" + vr); - System.out.println("VM=" + vm); - System.out.println("O=" + output); - System.out.println("OLEN=" + olength); - System.out.println("EXP=" + e10); - } - - // Step 5: Print the decimal representation. - // We follow Float/Double.toString semantics here. - StringBuilder result = new StringBuilder(); - // Add the minus sign if the number is negative. - if (sign) { - result.append('-'); - } - - if (scientificNotation) { - result.append(output.charAt(0)); - result.append('.'); - for (int i = 1; i < olength; i++) { - result.append(output.charAt(i)); - } - if (olength == 1) { - result.append('0'); - } - result.append('E'); - result.append(e10); - return result.toString(); - } else { - // Print leading 0s and '.' if applicable. - for (int i = 0; i > e10; i--) { - result.append('0'); - if (i == 0) { - result.append("."); - } - } - // Print number and '.' if applicable. - for (int i = 0; i < olength; i++) { - result.append(output.charAt(i)); - if (e10 == 0) { - result.append('.'); - } - e10--; - } - // Print trailing 0s and '.' if applicable. - for (; e10 >= -1; e10--) { - result.append('0'); - if (e10 == 0) { - result.append('.'); - } - } - return result.toString(); - } - } -} From c8a5fc178356be5ba143971fd65a521676ebc255 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Tue, 21 Jun 2022 10:31:50 +0100 Subject: [PATCH 13/17] update comments --- .../jackson/core/io/jdk/DoubleToStringTest.java | 6 +++--- .../jackson/core/io/jdk/FloatToStringTest.java | 16 ++++++++-------- .../core/io/schubfach/DoubleToStringTest.java | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/test/java/com/fasterxml/jackson/core/io/jdk/DoubleToStringTest.java b/src/test/java/com/fasterxml/jackson/core/io/jdk/DoubleToStringTest.java index cf5fca183b..c8bf6c5feb 100644 --- a/src/test/java/com/fasterxml/jackson/core/io/jdk/DoubleToStringTest.java +++ b/src/test/java/com/fasterxml/jackson/core/io/jdk/DoubleToStringTest.java @@ -51,7 +51,7 @@ public void minAndMax() { @Test public void roundingModeEven() { - //TODO result differs from Schubfach + //result differs to Schubfach assertD2sEquals("-2.1098088986959632E16", -2.109808898695963E16); } @@ -61,11 +61,11 @@ public void regressionTest() { assertD2sEquals("1.18575755E-316", 1.18575755E-316d); assertD2sEquals("2.989102097996E-312", 2.989102097996E-312d); assertD2sEquals("9.0608011534336E15", 9.0608011534336E15d); - //TODO next result differs from Schubfach + //next result differs yo Schubfach assertD2sEquals("4.7083560247115121E18", 4.708356024711512E18); assertD2sEquals("9.409340012568248E18", 9.409340012568248E18); // This number naively requires 65 bit for the intermediate results if we reduce the lookup - // table by half. This checks that we don't loose any information in that case. + // table by half. This checks that we don't lose any information in that case. assertD2sEquals("1.8531501765868567E21", 1.8531501765868567E21); assertD2sEquals("-3.347727380279489E33", -3.347727380279489E33); // Discovered by Andriy Plokhotnyuk, see #29. diff --git a/src/test/java/com/fasterxml/jackson/core/io/jdk/FloatToStringTest.java b/src/test/java/com/fasterxml/jackson/core/io/jdk/FloatToStringTest.java index a835870d24..6c9bd08b68 100644 --- a/src/test/java/com/fasterxml/jackson/core/io/jdk/FloatToStringTest.java +++ b/src/test/java/com/fasterxml/jackson/core/io/jdk/FloatToStringTest.java @@ -24,7 +24,7 @@ public void simpleCases() { @Test public void switchToSubnormal() { - //TODO next one different than Schubfach + //next one different to Schubfach assertF2sEquals("1.17549435E-38", Float.intBitsToFloat(0x00800000)); } @@ -52,7 +52,7 @@ public void minAndMax() { @Test public void roundingModeEven() { - //TODO all of these differ than Schubfach + //all different to Schubfach assertF2sEquals("3.3554448E7", 3.3554448E7f); assertF2sEquals("8.9999995E9", 8.999999E9f); assertF2sEquals("3.4366718E10", 3.4366717E10f); @@ -68,7 +68,7 @@ public void looksLikePow5() { // These are all floating point numbers where the mantissa is a power of 5, // and the exponent is in the range such that q = 10. assertF2sEquals("6.7108864E17", Float.intBitsToFloat(0x5D1502F9)); - //TODO next 2 are slightly different from Schubfach + //next 2 are slightly different to Schubfach assertF2sEquals("1.34217728E18", Float.intBitsToFloat(0x5D9502F9)); assertF2sEquals("2.68435456E18", Float.intBitsToFloat(0x5E1502F9)); } @@ -87,20 +87,20 @@ public void regressionTest() { assertF2sEquals("5.3399997E9", 5.3399997E9f); assertF2sEquals("6.0898E-39", 6.0898E-39f); assertF2sEquals("0.0010310042", 0.0010310042f); - //TODO next one is more accurate than Schubfach + //next one is more accurate than Schubfach assertF2sEquals("2.8823261E17", 2.8823261E17f); assertF2sEquals("7.038531E-26", 7.038531E-26f); - //TODO next 2 are accurate than Schubfach + //next 2 are more accurate than Schubfach assertF2sEquals("9.2234038E17", 9.2234038E17f); assertF2sEquals("6.7108872E7", 6.7108872E7f); - //TODO next one matches Schubfach but not Ryu (Ryu is more accurate) + //next one matches Schubfach but not Ryu (Ryu is more accurate) assertF2sEquals("9.8E-45", 1.0E-44f); - //TODO next one is less accurate than Schubfach + //next one is less accurate than Schubfach assertF2sEquals("2.81602484E14", 2.816025E14f); assertF2sEquals("9.223372E18", 9.223372E18f); assertF2sEquals("1.5846086E29", 1.5846085E29f); assertF2sEquals("1.1811161E19", 1.1811161E19f); - //TODO next one is less accurate than Schubfach + //next one is less accurate than Schubfach assertF2sEquals("5.3687091E18", 5.368709E18f); assertF2sEquals("4.6143166E18", 4.6143165E18f); assertF2sEquals("0.007812537", 0.007812537f); diff --git a/src/test/java/com/fasterxml/jackson/core/io/schubfach/DoubleToStringTest.java b/src/test/java/com/fasterxml/jackson/core/io/schubfach/DoubleToStringTest.java index 686119a631..ac920d971d 100644 --- a/src/test/java/com/fasterxml/jackson/core/io/schubfach/DoubleToStringTest.java +++ b/src/test/java/com/fasterxml/jackson/core/io/schubfach/DoubleToStringTest.java @@ -63,7 +63,7 @@ public void regressionTest() { assertD2sEquals("4.708356024711512E18", 4.708356024711512E18); assertD2sEquals("9.409340012568248E18", 9.409340012568248E18); // This number naively requires 65 bit for the intermediate results if we reduce the lookup - // table by half. This checks that we don't loose any information in that case. + // table by half. This checks that we don't lose any information in that case. assertD2sEquals("1.8531501765868567E21", 1.8531501765868567E21); assertD2sEquals("-3.347727380279489E33", -3.347727380279489E33); // Discovered by Andriy Plokhotnyuk, see #29. From d890449fba462c2ee61e153cea7089eac4aabb70 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Tue, 21 Jun 2022 11:45:03 +0100 Subject: [PATCH 14/17] add schubfach tests --- .../io/schubfach/DoubleToDecimalChecker.java | 304 +++++++++++++ .../io/schubfach/DoubleToDecimalTest.java | 129 ++++++ .../io/schubfach/FloatToDecimalChecker.java | 251 +++++++++++ .../core/io/schubfach/FloatToDecimalTest.java | 121 ++++++ .../core/io/schubfach/ToDecimalChecker.java | 407 ++++++++++++++++++ 5 files changed, 1212 insertions(+) create mode 100644 src/test/java/com/fasterxml/jackson/core/io/schubfach/DoubleToDecimalChecker.java create mode 100644 src/test/java/com/fasterxml/jackson/core/io/schubfach/DoubleToDecimalTest.java create mode 100644 src/test/java/com/fasterxml/jackson/core/io/schubfach/FloatToDecimalChecker.java create mode 100644 src/test/java/com/fasterxml/jackson/core/io/schubfach/FloatToDecimalTest.java create mode 100644 src/test/java/com/fasterxml/jackson/core/io/schubfach/ToDecimalChecker.java diff --git a/src/test/java/com/fasterxml/jackson/core/io/schubfach/DoubleToDecimalChecker.java b/src/test/java/com/fasterxml/jackson/core/io/schubfach/DoubleToDecimalChecker.java new file mode 100644 index 0000000000..ceed57290a --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/io/schubfach/DoubleToDecimalChecker.java @@ -0,0 +1,304 @@ +/* + * Copyright 2018-2020 Raffaello Giulietti + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.fasterxml.jackson.core.io.schubfach; + +import java.math.BigDecimal; +import java.util.Random; + +import static java.lang.Double.*; +import static java.lang.Long.numberOfTrailingZeros; +import static java.lang.StrictMath.scalb; +import static com.fasterxml.jackson.core.io.schubfach.MathUtils.flog10pow2; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class DoubleToDecimalChecker extends ToDecimalChecker { + + static final int P = + numberOfTrailingZeros(doubleToRawLongBits(3)) + 2; + private static final int W = (SIZE - 1) - (P - 1); + static final int Q_MIN = (-1 << W - 1) - P + 3; + static final int Q_MAX = (1 << W - 1) - P; + static final long C_MIN = 1L << P - 1; + static final long C_MAX = (1L << P) - 1; + + static final int K_MIN = flog10pow2(Q_MIN); + static final int K_MAX = flog10pow2(Q_MAX); + static final int H = flog10pow2(P) + 2; + + private static final double MIN_VALUE = scalb(1.0, Q_MIN); + private static final double MIN_NORMAL = scalb((double) C_MIN, Q_MIN); + private static final double MAX_VALUE = scalb((double) C_MAX, Q_MAX); + + static final int E_MIN = e(MIN_VALUE); + static final int E_MAX = e(MAX_VALUE); + + static final long C_TINY = cTiny(Q_MIN, K_MIN); + + private double v; + private final long originalBits; + + private DoubleToDecimalChecker(double v, String s) { + super(s); + this.v = v; + originalBits = doubleToRawLongBits(v); + } + + @Override + BigDecimal toBigDecimal() { + return new BigDecimal(v); + } + + @Override + boolean recovers(BigDecimal b) { + return b.doubleValue() == v; + } + + @Override + boolean recovers(String s) { + return parseDouble(s) == v; + } + + @Override + String hexBits() { + return String.format("0x%01X__%03X__%01X_%04X_%04X_%04X", + (int) (originalBits >>> 63) & 0x1, + (int) (originalBits >>> 52) & 0x7FF, + (int) (originalBits >>> 48) & 0xF, + (int) (originalBits >>> 32) & 0xFFFF, + (int) (originalBits >>> 16) & 0xFFFF, + (int) originalBits & 0xFFFF); + } + + @Override + int minExp() { + return E_MIN; + } + + @Override + int maxExp() { + return E_MAX; + } + + @Override + int maxLen10() { + return H; + } + + @Override + boolean isZero() { + return v == 0; + } + + @Override + boolean isInfinity() { + return v == POSITIVE_INFINITY; + } + + @Override + void negate() { + v = -v; + } + + @Override + boolean isNegative() { + return originalBits < 0; + } + + @Override + boolean isNaN() { + return Double.isNaN(v); + } + + static void toDec(double v) { +// String s = Double.toString(v); + String s = DoubleToDecimal.toString(v); + new DoubleToDecimalChecker(v, s).validate(); + } + + /* + There are tons of doubles that are rendered incorrectly by the JDK. + While the renderings correctly round back to the original value, + they are longer than needed or are not the closest decimal to the double. + Here are just a very few examples. + */ + static final String[] Anomalies = { + // JDK renders these, and others, with 18 digits! + "2.82879384806159E17", "1.387364135037754E18", + "1.45800632428665E17", + + // JDK renders these longer than needed. + "1.6E-322", "6.3E-322", + "7.3879E20", "2.0E23", "7.0E22", "9.2E22", + "9.5E21", "3.1E22", "5.63E21", "8.41E21", + + // JDK does not render these, and many others, as the closest. + "9.9E-324", "9.9E-323", + "1.9400994884341945E25", "3.6131332396758635E25", + "2.5138990223946153E25", + }; + + /* + Values are from + Paxson V, "A Program for Testing IEEE Decimal-Binary Conversion" + tables 3 and 4 + */ + static final double[] PaxsonSignificands = { + 8_511_030_020_275_656L, + 5_201_988_407_066_741L, + 6_406_892_948_269_899L, + 8_431_154_198_732_492L, + 6_475_049_196_144_587L, + 8_274_307_542_972_842L, + 5_381_065_484_265_332L, + 6_761_728_585_499_734L, + 7_976_538_478_610_756L, + 5_982_403_858_958_067L, + 5_536_995_190_630_837L, + 7_225_450_889_282_194L, + 7_225_450_889_282_194L, + 8_703_372_741_147_379L, + 8_944_262_675_275_217L, + 7_459_803_696_087_692L, + 6_080_469_016_670_379L, + 8_385_515_147_034_757L, + 7_514_216_811_389_786L, + 8_397_297_803_260_511L, + 6_733_459_239_310_543L, + 8_091_450_587_292_794L, + + 6_567_258_882_077_402L, + 6_712_731_423_444_934L, + 6_712_731_423_444_934L, + 5_298_405_411_573_037L, + 5_137_311_167_659_507L, + 6_722_280_709_661_868L, + 5_344_436_398_034_927L, + 8_369_123_604_277_281L, + 8_995_822_108_487_663L, + 8_942_832_835_564_782L, + 8_942_832_835_564_782L, + 8_942_832_835_564_782L, + 6_965_949_469_487_146L, + 6_965_949_469_487_146L, + 6_965_949_469_487_146L, + 7_487_252_720_986_826L, + 5_592_117_679_628_511L, + 8_887_055_249_355_788L, + 6_994_187_472_632_449L, + 8_797_576_579_012_143L, + 7_363_326_733_505_337L, + 8_549_497_411_294_502L, + }; + + static final int[] PaxsonExponents = { + -342, + -824, + 237, + 72, + 99, + 726, + -456, + -57, + 376, + 377, + 93, + 710, + 709, + 117, + -1, + -707, + -381, + 721, + -828, + -345, + 202, + -473, + + 952, + 535, + 534, + -957, + -144, + 363, + -169, + -853, + -780, + -383, + -384, + -385, + -249, + -250, + -251, + 548, + 164, + 665, + 690, + 588, + 272, + -448, + }; + + /* + Random doubles over the whole range + */ + private static void testRandom(int randomCount, Random r) { + for (int i = 0; i < randomCount; ++i) { + toDec(longBitsToDouble(r.nextLong())); + } + } + + /* + Random doubles over the integer range [0, 2^52). + These are all exact doubles and exercise the fast path (except 0). + */ + private static void testRandomUnit(int randomCount, Random r) { + for (int i = 0; i < randomCount; ++i) { + toDec(r.nextLong() & (1L << P - 1)); + } + } + + /* + Random doubles over the range [0, 10^15) as "multiples" of 1e-3 + */ + private static void testRandomMilli(int randomCount, Random r) { + for (int i = 0; i < randomCount; ++i) { + toDec(r.nextLong() % 1_000_000_000_000_000_000L / 1e3); + } + } + + /* + Random doubles over the range [0, 10^15) as "multiples" of 1e-6 + */ + private static void testRandomMicro(int randomCount, Random r) { + for (int i = 0; i < randomCount; ++i) { + toDec((r.nextLong() & 0x7FFF_FFFF_FFFF_FFFFL) / 1e6); + } + } + + static void randomNumberTests(int randomCount, Random r) { + testRandom(randomCount, r); + testRandomUnit(randomCount, r); + testRandomMilli(randomCount, r); + testRandomMicro(randomCount, r); + } +} diff --git a/src/test/java/com/fasterxml/jackson/core/io/schubfach/DoubleToDecimalTest.java b/src/test/java/com/fasterxml/jackson/core/io/schubfach/DoubleToDecimalTest.java new file mode 100644 index 0000000000..c3f203b651 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/io/schubfach/DoubleToDecimalTest.java @@ -0,0 +1,129 @@ +package com.fasterxml.jackson.core.io.schubfach; + +import org.junit.jupiter.api.Test; + +import java.util.Random; + +import static com.fasterxml.jackson.core.io.schubfach.DoubleToDecimalChecker.*; +import static java.lang.Double.*; +import static java.lang.StrictMath.scalb; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class DoubleToDecimalTest { + @Test + void testExtremeValues() { + toDec(NEGATIVE_INFINITY); + toDec(-MAX_VALUE); + toDec(-MIN_NORMAL); + toDec(-MIN_VALUE); + toDec(-0.0); + toDec(0.0); + toDec(MIN_VALUE); + toDec(MIN_NORMAL); + toDec(MAX_VALUE); + toDec(POSITIVE_INFINITY); + toDec(NaN); + + /* + Quiet NaNs have the most significant bit of the mantissa as 1, + while signaling NaNs have it as 0. + Exercise 4 combinations of quiet/signaling NaNs and + "positive/negative" NaNs + */ + toDec(longBitsToDouble(0x7FF8_0000_0000_0001L)); + toDec(longBitsToDouble(0x7FF0_0000_0000_0001L)); + toDec(longBitsToDouble(0xFFF8_0000_0000_0001L)); + toDec(longBitsToDouble(0xFFF0_0000_0000_0001L)); + + /* + All values treated specially by Schubfach + */ + for (int c = 1; c < C_TINY; ++c) { + toDec(c * MIN_VALUE); + } + } + + /* + A few "powers of 10" are incorrectly rendered by the JDK. + The rendering is either too long or it is not the closest decimal. + */ + @Test + void testPowersOf10() { + for (int e = E_MIN; e <= E_MAX; ++e) { + toDec(parseDouble("1e" + e)); + } + } + + /* + Many powers of 2 are incorrectly rendered by the JDK. + The rendering is either too long or it is not the closest decimal. + */ + @Test + void testPowersOf2() { + for (double v = MIN_VALUE; v <= MAX_VALUE; v *= 2) { + toDec(v); + } + } + + @Test + void testSomeAnomalies() { + for (String dec : Anomalies) { + toDec(parseDouble(dec)); + } + } + + @Test + void testPaxson() { + for (int i = 0; i < PaxsonSignificands.length; ++i) { + toDec(scalb(PaxsonSignificands[i], PaxsonExponents[i])); + } + } + + /* + Tests all integers of the form yx_xxx_000_000_000_000_000, y != 0. + These are all exact doubles. + */ + @Test + void testLongs() { + for (int i = 10_000; i < 100_000; ++i) { + toDec(i * 1e15); + } + } + + /* + Tests all integers up to 1_000_000. + These are all exact doubles and exercise a fast path. + */ + @Test + void testInts() { + for (int i = 0; i <= 1_000_000; ++i) { + toDec(i); + } + } + + @Test + void testConstants() { + assertTrue(P == DoubleToDecimal.P, "P"); + assertTrue((long) (double) C_MIN == C_MIN, "C_MIN"); + assertTrue((long) (double) C_MAX == C_MAX, "C_MAX"); + assertTrue(MIN_VALUE == Double.MIN_VALUE, "MIN_VALUE"); + assertTrue(MIN_NORMAL == Double.MIN_NORMAL, "MIN_NORMAL"); + assertTrue(MAX_VALUE == Double.MAX_VALUE, "MAX_VALUE"); + + assertTrue(Q_MIN == DoubleToDecimal.Q_MIN, "Q_MIN"); + assertTrue(Q_MAX == DoubleToDecimal.Q_MAX, "Q_MAX"); + + assertTrue(K_MIN == DoubleToDecimal.K_MIN, "K_MIN"); + assertTrue(K_MAX == DoubleToDecimal.K_MAX, "K_MAX"); + assertTrue(H == DoubleToDecimal.H, "H"); + + assertTrue(E_MIN == DoubleToDecimal.E_MIN, "E_MIN"); + assertTrue(E_MAX == DoubleToDecimal.E_MAX, "E_MAX"); + assertTrue(C_TINY == DoubleToDecimal.C_TINY, "C_TINY"); + } + + @Test + void randomNumberTests() { + DoubleToDecimalChecker.randomNumberTests(1_000_000, new Random()); + } +} diff --git a/src/test/java/com/fasterxml/jackson/core/io/schubfach/FloatToDecimalChecker.java b/src/test/java/com/fasterxml/jackson/core/io/schubfach/FloatToDecimalChecker.java new file mode 100644 index 0000000000..a39dab234e --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/io/schubfach/FloatToDecimalChecker.java @@ -0,0 +1,251 @@ +/* + * Copyright 2018-2020 Raffaello Giulietti + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.fasterxml.jackson.core.io.schubfach; + + +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.util.Random; + +import static java.lang.Float.*; +import static java.lang.Integer.numberOfTrailingZeros; +import static java.lang.StrictMath.scalb; +import static com.fasterxml.jackson.core.io.schubfach.MathUtils.flog10pow2; + +public class FloatToDecimalChecker extends ToDecimalChecker { + + static final int P = + numberOfTrailingZeros(floatToRawIntBits(3)) + 2; + private static final int W = (SIZE - 1) - (P - 1); + static final int Q_MIN = (-1 << W - 1) - P + 3; + static final int Q_MAX = (1 << W - 1) - P; + static final int C_MIN = 1 << P - 1; + static final int C_MAX = (1 << P) - 1; + + static final int K_MIN = flog10pow2(Q_MIN); + static final int K_MAX = flog10pow2(Q_MAX); + static final int H = flog10pow2(P) + 2; + + private static final float MIN_VALUE = scalb(1.0f, Q_MIN); + private static final float MIN_NORMAL = scalb((float) C_MIN, Q_MIN); + private static final float MAX_VALUE = scalb((float) C_MAX, Q_MAX); + + static final int E_MIN = e(MIN_VALUE); + static final int E_MAX = e(MAX_VALUE); + + static final long C_TINY = cTiny(Q_MIN, K_MIN); + + private float v; + private final int originalBits; + + private FloatToDecimalChecker(float v, String s) { + super(s); + this.v = v; + originalBits = floatToRawIntBits(v); + } + + @Override + BigDecimal toBigDecimal() { + return new BigDecimal(v); + } + + @Override + boolean recovers(BigDecimal b) { + return b.floatValue() == v; + } + + @Override + String hexBits() { + return String.format("0x%01X__%02X__%02X_%04X", + (originalBits >>> 31) & 0x1, + (originalBits >>> 23) & 0xFF, + (originalBits >>> 16) & 0x7F, + originalBits & 0xFFFF); + } + + @Override + boolean recovers(String s) { + return parseFloat(s) == v; + } + + @Override + int minExp() { + return E_MIN; + } + + @Override + int maxExp() { + return E_MAX; + } + + @Override + int maxLen10() { + return H; + } + + @Override + boolean isZero() { + return v == 0; + } + + @Override + boolean isInfinity() { + return v == POSITIVE_INFINITY; + } + + @Override + void negate() { + v = -v; + } + + @Override + boolean isNegative() { + return originalBits < 0; + } + + @Override + boolean isNaN() { + return Float.isNaN(v); + } + + static void toDec(float v) { +// String s = Float.toString(v); + String s = FloatToDecimal.toString(v); + new FloatToDecimalChecker(v, s).validate(); + } + + /* + There are tons of doubles that are rendered incorrectly by the JDK. + While the renderings correctly round back to the original value, + they are longer than needed or are not the closest decimal to the double. + Here are just a very few examples. + */ + static final String[] Anomalies = { + // JDK renders these longer than needed. + "1.1754944E-38", "2.2E-44", + "1.0E16", "2.0E16", "3.0E16", "5.0E16", "3.0E17", + "3.2E18", "3.7E18", "3.7E16", "3.72E17", + + // JDK does not render this as the closest. + "9.9E-44", + }; + + /* + Values are from + Paxson V, "A Program for Testing IEEE Decimal-Binary Conversion" + tables 16 and 17 + */ + static final float[] PaxsonSignificands = { + 12_676_506, + 15_445_013, + 13_734_123, + 12_428_269, + 12_676_506, + 15_334_037, + 11_518_287, + 12_584_953, + 15_961_084, + 14_915_817, + 10_845_484, + 16_431_059, + + 16_093_626, + 9_983_778, + 12_745_034, + 12_706_553, + 11_005_028, + 15_059_547, + 16_015_691, + 8_667_859, + 14_855_922, + 14_855_922, + 10_144_164, + 13_248_074, + }; + + static final int[] PaxsonExponents = { + -102, + -103, + 86, + -138, + -130, + -146, + -41, + -145, + -125, + -146, + -102, + -61, + + 69, + 25, + 104, + 72, + 45, + 71, + -99, + 56, + -82, + -83, + -110, + 95, + }; + + /* + Random floats over the whole range. + */ + private static void testRandom(int randomCount, Random r) { + for (int i = 0; i < randomCount; ++i) { + toDec(intBitsToFloat(r.nextInt())); + } + } + + /* + All, really all, 2^32 possible floats. Takes between 90 and 120 minutes. + */ + public static void testAll() { + // Avoid wrapping around Integer.MAX_VALUE + int bits = Integer.MIN_VALUE; + for (; bits < Integer.MAX_VALUE; ++bits) { + toDec(intBitsToFloat(bits)); + } + toDec(intBitsToFloat(bits)); + } + + /* + All positive 2^31 floats. + */ + public static void testPositive() { + // Avoid wrapping around Integer.MAX_VALUE + int bits = 0; + for (; bits < Integer.MAX_VALUE; ++bits) { + toDec(intBitsToFloat(bits)); + } + toDec(intBitsToFloat(bits)); + } + + public static void randomNumberTests(int randomCount, Random r) { + testRandom(randomCount, r); + } +} diff --git a/src/test/java/com/fasterxml/jackson/core/io/schubfach/FloatToDecimalTest.java b/src/test/java/com/fasterxml/jackson/core/io/schubfach/FloatToDecimalTest.java new file mode 100644 index 0000000000..e0644316b6 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/io/schubfach/FloatToDecimalTest.java @@ -0,0 +1,121 @@ +package com.fasterxml.jackson.core.io.schubfach; + +import org.junit.jupiter.api.Test; + +import java.util.Random; + +import static com.fasterxml.jackson.core.io.schubfach.FloatToDecimalChecker.*; +import static java.lang.Float.*; +import static java.lang.StrictMath.scalb; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class FloatToDecimalTest { + /* + MIN_NORMAL is incorrectly rendered by the JDK. + */ + @Test + void testExtremeValues() { + toDec(NEGATIVE_INFINITY); + toDec(-MAX_VALUE); + toDec(-MIN_NORMAL); + toDec(-MIN_VALUE); + toDec(-0.0f); + toDec(0.0f); + toDec(MIN_VALUE); + toDec(MIN_NORMAL); + toDec(MAX_VALUE); + toDec(POSITIVE_INFINITY); + toDec(NaN); + + /* + Quiet NaNs have the most significant bit of the mantissa as 1, + while signaling NaNs have it as 0. + Exercise 4 combinations of quiet/signaling NaNs and + "positive/negative" NaNs. + */ + toDec(intBitsToFloat(0x7FC0_0001)); + toDec(intBitsToFloat(0x7F80_0001)); + toDec(intBitsToFloat(0xFFC0_0001)); + toDec(intBitsToFloat(0xFF80_0001)); + + /* + All values treated specially by Schubfach + */ + for (int c = 1; c < C_TINY; ++c) { + toDec(c * MIN_VALUE); + } + } + + /* + Some "powers of 10" are incorrectly rendered by the JDK. + The rendering is either too long or it is not the closest decimal. + */ + @Test + void testPowersOf10() { + for (int e = E_MIN; e <= E_MAX; ++e) { + toDec(parseFloat("1e" + e)); + } + } + + /* + Many powers of 2 are incorrectly rendered by the JDK. + The rendering is either too long or it is not the closest decimal. + */ + @Test + void testPowersOf2() { + for (float v = MIN_VALUE; v <= MAX_VALUE; v *= 2) { + toDec(v); + } + } + + @Test + void testConstants() { + assertTrue(P == FloatToDecimal.P, "P"); + assertTrue((long) (float) C_MIN == C_MIN, "C_MIN"); + assertTrue((long) (float) C_MAX == C_MAX, "C_MAX"); + assertTrue(MIN_VALUE == Float.MIN_VALUE, "MIN_VALUE"); + assertTrue(MIN_NORMAL == Float.MIN_NORMAL, "MIN_NORMAL"); + assertTrue(MAX_VALUE == Float.MAX_VALUE, "MAX_VALUE"); + + assertTrue(Q_MIN == FloatToDecimal.Q_MIN, "Q_MIN"); + assertTrue(Q_MAX == FloatToDecimal.Q_MAX, "Q_MAX"); + + assertTrue(K_MIN == FloatToDecimal.K_MIN, "K_MIN"); + assertTrue(K_MAX == FloatToDecimal.K_MAX, "K_MAX"); + assertTrue(H == FloatToDecimal.H, "H"); + + assertTrue(E_MIN == FloatToDecimal.E_MIN, "E_MIN"); + assertTrue(E_MAX == FloatToDecimal.E_MAX, "E_MAX"); + assertTrue(C_TINY == FloatToDecimal.C_TINY, "C_TINY"); + } + + @Test + void testSomeAnomalies() { + for (String dec : Anomalies) { + toDec(parseFloat(dec)); + } + } + + @Test + void testPaxson() { + for (int i = 0; i < PaxsonSignificands.length; ++i) { + toDec(scalb(PaxsonSignificands[i], PaxsonExponents[i])); + } + } + + /* + Tests all positive integers below 2^23. + These are all exact floats and exercise the fast path. + */ + @Test + void testInts() { + for (int i = 1; i < 1 << P - 1; ++i) { + toDec(i); + } + } + + @Test + void randomNumberTests() { + FloatToDecimalChecker.randomNumberTests(1_000_000, new Random()); + } +} diff --git a/src/test/java/com/fasterxml/jackson/core/io/schubfach/ToDecimalChecker.java b/src/test/java/com/fasterxml/jackson/core/io/schubfach/ToDecimalChecker.java new file mode 100644 index 0000000000..9978186179 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/io/schubfach/ToDecimalChecker.java @@ -0,0 +1,407 @@ +/* + * Copyright 2018-2020 Raffaello Giulietti + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.fasterxml.jackson.core.io.schubfach; + +import java.io.IOException; +import java.io.StringReader; +import java.math.BigDecimal; +import java.math.BigInteger; + +import static java.math.BigInteger.*; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/* +A checker for the Javadoc specification. +It just relies on straightforward use of (expensive) BigDecimal arithmetic, +not optimized at all. + */ +abstract class ToDecimalChecker { + + // The string to check + private final String s; + + // The decimal parsed from s is c 10^q + private long c; + private int q; + + // The number of digits parsed from s: 10^(len10-1) <= c < 10^len10 + private int len10; + + ToDecimalChecker(String s) { + this.s = s; + } + + /* + Returns e be such that 10^(e-1) <= v < 10^e. + */ + static int e(double v) { + // log10(v) + 1 is a first good approximation of e + int e = (int) Math.floor(Math.log10(v)) + 1; + + // Full precision search for e such that 10^(e-1) <= c 2^q < 10^e. + BigDecimal vp = new BigDecimal(v); + BigDecimal low = new BigDecimal(BigInteger.ONE, -(e - 1)); + while (low.compareTo(vp) > 0) { + e -= 1; + low = new BigDecimal(BigInteger.ONE, -(e - 1)); + } + BigDecimal high = new BigDecimal(BigInteger.ONE, -e); + while (vp.compareTo(high) >= 0) { + e += 1; + high = new BigDecimal(BigInteger.ONE, -e); + } + return e; + } + + static long cTiny(int qMin, int kMin) { + BigInteger[] qr = ONE.shiftLeft(-qMin) + .divideAndRemainder(TEN.pow(-(kMin + 1))); + BigInteger cTiny = qr[1].signum() > 0 ? qr[0].add(ONE) : qr[0]; + assertTrue(cTiny.bitLength() < Long.SIZE, "C_TINY"); + return cTiny.longValue(); + } + + void validate() { + String msg = "toString applied to the bits " + + hexBits() + + " returns " + + "\"" + s + "\"" + + ", which is not correct according to the specification."; + assertTrue(isOK(), msg); + if (isOK()) { + return; + } + } + + /* + Returns whether s syntactically meets the expected output of + toString. It is restricted to finite positive outputs. + It is an unusually long method but rather straightforward, too. + Many conditionals could be merged, but KISS here. + */ + private boolean parse(String t) { + try { + // first determine interesting boundaries in the string + StringReader r = new StringReader(t); + int ch = r.read(); + + int i = 0; + while (ch == '0') { + ++i; + ch = r.read(); + } + // i is just after zeroes starting the integer + + int p = i; + while ('0' <= ch && ch <= '9') { + c = 10 * c + (ch - '0'); + if (c < 0) { + return false; + } + ++len10; + ++p; + ch = r.read(); + } + // p is just after digits ending the integer + + int fz = p; + if (ch == '.') { + ++fz; + ch = r.read(); + } + // fz is just after a decimal '.' + + int f = fz; + while (ch == '0') { + c = 10 * c + (ch - '0'); + if (c < 0) { + return false; + } + ++len10; + ++f; + ch = r.read(); + } + // f is just after zeroes starting the fraction + + if (c == 0) { + len10 = 0; + } + int x = f; + while ('0' <= ch && ch <= '9') { + c = 10 * c + (ch - '0'); + if (c < 0) { + return false; + } + ++len10; + ++x; + ch = r.read(); + } + // x is just after digits ending the fraction + + int g = x; + if (ch == 'E') { + ++g; + ch = r.read(); + } + // g is just after an exponent indicator 'E' + + int ez = g; + if (ch == '-') { + ++ez; + ch = r.read(); + } + // ez is just after a '-' sign in the exponent + + int e = ez; + while (ch == '0') { + ++e; + ch = r.read(); + } + // e is just after zeroes starting the exponent + + int z = e; + while ('0' <= ch && ch <= '9') { + q = 10 * q + (ch - '0'); + if (q < 0) { + return false; + } + ++z; + ch = r.read(); + } + // z is just after digits ending the exponent + + // No other char after the number + if (z != t.length()) { + return false; + } + + // The integer must be present + if (p == 0) { + return false; + } + + // The decimal '.' must be present + if (fz == p) { + return false; + } + + // The fraction must be present + if (x == fz) { + return false; + } + + // The fraction is not 0 or it consists of exactly one 0 + if (f == x && f - fz > 1) { + return false; + } + + // Plain notation, no exponent + if (x == z) { + // At most one 0 starting the integer + if (i > 1) { + return false; + } + + // If the integer is 0, at most 2 zeroes start the fraction + if (i == 1 && f - fz > 2) { + return false; + } + + // The integer cannot have more than 7 digits + if (p > 7) { + return false; + } + + q = fz - x; + + // OK for plain notation + return true; + } + + // Computerized scientific notation + + // The integer has exactly one nonzero digit + if (i != 0 || p != 1) { + return false; + } + + // + // There must be an exponent indicator + if (x == g) { + return false; + } + + // There must be an exponent + if (ez == z) { + return false; + } + + // The exponent must not start with zeroes + if (ez != e) { + return false; + } + + if (g != ez) { + q = -q; + } + + // The exponent must not lie in [-3, 7) + if (-3 <= q && q < 7) { + return false; + } + + q += fz - x; + + // OK for computerized scientific notation + return true; + } catch (IOException ex) { + // An IOException on a StringReader??? Please... + return false; + } + } + + private boolean isOK() { + if (isNaN()) { + return s.equals("NaN"); + } + String t = s; + if (isNegative()) { + if (s.isEmpty() || s.charAt(0) != '-') { + return false; + } + negate(); + t = s.substring(1); + } + if (isInfinity()) { + return t.equals("Infinity"); + } + if (isZero()) { + return t.equals("0.0"); + } + if (!parse(t)) { + return false; + } + if (len10 < 2) { + c *= 10; + q -= 1; + len10 += 1; + } + if (2 > len10 || len10 > maxLen10()) { + return false; + } + + // The exponent is bounded + if (minExp() > q + len10 || q + len10 > maxExp()) { + return false; + } + + // s must recover v + try { + if (!recovers(t)) { + return false; + } + } catch (NumberFormatException e) { + return false; + } + + // Get rid of trailing zeroes, still ensuring at least 2 digits + while (len10 > 2 && c % 10 == 0) { + c /= 10; + q += 1; + len10 -= 1; + } + + if (len10 > 2) { + // Try with a shorter number less than v... + if (recovers(BigDecimal.valueOf(c / 10, -q - 1))) { + return false; + } + + // ... and with a shorter number greater than v + if (recovers(BigDecimal.valueOf(c / 10 + 1, -q - 1))) { + return false; + } + } + + // Try with the decimal predecessor... + BigDecimal dp = c == 10 ? + BigDecimal.valueOf(99, -q + 1) : + BigDecimal.valueOf(c - 1, -q); + if (recovers(dp)) { + BigDecimal bv = toBigDecimal(); + BigDecimal deltav = bv.subtract(BigDecimal.valueOf(c, -q)); + if (deltav.signum() >= 0) { + return true; + } + BigDecimal delta = dp.subtract(bv); + if (delta.signum() >= 0) { + return false; + } + int cmp = deltav.compareTo(delta); + return cmp > 0 || cmp == 0 && (c & 0x1) == 0; + } + + // ... and with the decimal successor + BigDecimal ds = BigDecimal.valueOf(c + 1, -q); + if (recovers(ds)) { + BigDecimal bv = toBigDecimal(); + BigDecimal deltav = bv.subtract(BigDecimal.valueOf(c, -q)); + if (deltav.signum() <= 0) { + return true; + } + BigDecimal delta = ds.subtract(bv); + if (delta.signum() <= 0) { + return false; + } + int cmp = deltav.compareTo(delta); + return cmp < 0 || cmp == 0 && (c & 0x1) == 0; + } + + return true; + } + + abstract BigDecimal toBigDecimal(); + + abstract boolean recovers(BigDecimal b); + + abstract boolean recovers(String s); + + abstract String hexBits(); + + abstract int minExp(); + + abstract int maxExp(); + + abstract int maxLen10(); + + abstract boolean isZero(); + + abstract boolean isInfinity(); + + abstract void negate(); + + abstract boolean isNegative(); + + abstract boolean isNaN(); + +} From 504ae854ea771e3ab1c3d8fdbd6c568881dbc49f Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Tue, 21 Jun 2022 11:51:01 +0100 Subject: [PATCH 15/17] Create MathUtilsTest.java --- .../core/io/schubfach/MathUtilsTest.java | 467 ++++++++++++++++++ 1 file changed, 467 insertions(+) create mode 100644 src/test/java/com/fasterxml/jackson/core/io/schubfach/MathUtilsTest.java diff --git a/src/test/java/com/fasterxml/jackson/core/io/schubfach/MathUtilsTest.java b/src/test/java/com/fasterxml/jackson/core/io/schubfach/MathUtilsTest.java new file mode 100644 index 0000000000..40983ed1b1 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/io/schubfach/MathUtilsTest.java @@ -0,0 +1,467 @@ +/* + * Copyright 2018-2020 Raffaello Giulietti + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.fasterxml.jackson.core.io.schubfach; + +import org.junit.jupiter.api.Test; + +import java.math.BigInteger; + +import static java.lang.Double.*; +import static java.lang.Long.numberOfTrailingZeros; +import static java.lang.StrictMath.scalb; +import static java.math.BigInteger.*; +import static com.fasterxml.jackson.core.io.schubfach.MathUtils.*; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class MathUtilsTest { + + private static final BigInteger THREE = valueOf(3); + + // binary constants + private static final int P = + numberOfTrailingZeros(doubleToRawLongBits(3)) + 2; + private static final int W = (SIZE - 1) - (P - 1); + private static final int Q_MIN = (-1 << W - 1) - P + 3; + private static final int Q_MAX = (1 << W - 1) - P; + private static final long C_MIN = 1L << P - 1; + private static final long C_MAX = (1L << P) - 1; + + // decimal constants + private static final int K_MIN = flog10pow2(Q_MIN); + private static final int K_MAX = flog10pow2(Q_MAX); + private static final int H = flog10pow2(P) + 2; + + /* + Let + 10^(-k) = beta 2^r + for the unique integer r and real beta meeting + 2^125 <= beta < 2^126 + Further, let g = g1 2^63 + g0. + Checks that: + 2^62 <= g1 < 2^63, + 0 <= g0 < 2^63, + g - 1 <= beta < g, (that is, g = floor(beta) + 1) + The last predicate, after multiplying by 2^r, is equivalent to + (g - 1) 2^r <= 10^(-k) < g 2^r + This is the predicate that will be checked in various forms. + */ + private static void testG(int k, long g1, long g0) { + // 2^62 <= g1 < 2^63, 0 <= g0 < 2^63 + assertTrue(g1 << 1 < 0 && g1 >= 0 && g0 >= 0, "g"); + + BigInteger g = valueOf(g1).shiftLeft(63).or(valueOf(g0)); + // double check that 2^125 <= g < 2^126 + assertTrue(g.signum() > 0 && g.bitLength() == 126, "g"); + + // see javadoc of MathUtils.g1(int) + int r = flog2pow10(-k) - 125; + + /* + The predicate + (g - 1) 2^r <= 10^(-k) < g 2^r + is equivalent to + g - 1 <= 10^(-k) 2^(-r) < g + When + k <= 0 & r < 0 + all numerical subexpressions are integer-valued. This is the same as + g - 1 = 10^(-k) 2^(-r) + */ + if (k <= 0 && r < 0) { + assertTrue( + g.subtract(ONE).compareTo(TEN.pow(-k).shiftLeft(-r)) == 0, + "g"); + return; + } + + /* + The predicate + (g - 1) 2^r <= 10^(-k) < g 2^r + is equivalent to + g 10^k - 10^k <= 2^(-r) < g 10^k + When + k > 0 & r < 0 + all numerical subexpressions are integer-valued. + */ + if (k > 0 && r < 0) { + BigInteger pow5 = TEN.pow(k); + BigInteger mhs = ONE.shiftLeft(-r); + BigInteger rhs = g.multiply(pow5); + assertTrue(rhs.subtract(pow5).compareTo(mhs) <= 0 + && mhs.compareTo(rhs) < 0, + "g"); + return; + } + + /* + Finally, when + k <= 0 & r >= 0 + the predicate + (g - 1) 2^r <= 10^(-k) < g 2^r + can be used straightforwardly as all numerical subexpressions are + already integer-valued. + */ + if (k <= 0) { + BigInteger mhs = TEN.pow(-k); + assertTrue(g.subtract(ONE).shiftLeft(r).compareTo(mhs) <= 0 && + mhs.compareTo(g.shiftLeft(r)) < 0, + "g"); + return; + } + + /* + For combinatorial reasons, the only remaining case is + k > 0 & r >= 0 + which, however, cannot arise. Indeed, the predicate + (g - 1) 2^r <= 10^(-k) < g 2^r + has a positive integer left-hand side and a middle side < 1, + which cannot hold. + */ + assertTrue(false, "g"); + } + + /* + Verifies the soundness of the values returned by g1() and g0(). + */ + @Test + void testG() { + for (int k = MathUtils.K_MIN; k <= MathUtils.K_MAX; ++k) { + testG(k, g1(k), g0(k)); + } + } + + /* + Let + k = floor(log10(3/4 2^e)) + The method verifies that + k = flog10threeQuartersPow2(e), Q_MIN <= e <= Q_MAX + This range covers all binary exponents of doubles and floats. + + The first equation above is equivalent to + 10^k <= 3 2^(e-2) < 10^(k+1) + Equality never holds. Henceforth, the predicate to check is + 10^k < 3 2^(e-2) < 10^(k+1) + This will be transformed in various ways for checking purposes. + + For integer n > 0, let further + b = len2(n) + denote its length in bits. This means exactly the same as + 2^(b-1) <= n < 2^b + */ + @Test + void testFlog10threeQuartersPow2() { + // First check the case e = 1 + assertTrue(flog10threeQuartersPow2(1) == 0, + "flog10threeQuartersPow2"); + + /* + Now check the range Q_MIN <= e <= 0. + By rewriting, the predicate to check is equivalent to + 3 10^(-k-1) < 2^(2-e) < 3 10^(-k) + As e <= 0, it follows that 2^(2-e) >= 4 and the right inequality + implies k < 0, so the powers of 10 are integers. + + The left inequality is equivalent to + len2(3 10^(-k-1)) <= 2 - e + and the right inequality to + 2 - e < len2(3 10^(-k)) + The original predicate is therefore equivalent to + len2(3 10^(-k-1)) <= 2 - e < len2(3 10^(-k)) + + Starting with e = 0 and decrementing until the lower bound, the code + keeps track of the two powers of 10 to avoid recomputing them. + This is easy because at each iteration k changes at most by 1. A simple + multiplication by 10 computes the next power of 10 when needed. + */ + int e = 0; + int k0 = flog10threeQuartersPow2(e); + assertTrue(k0 < 0, "flog10threeQuartersPow2"); + BigInteger l = THREE.multiply(TEN.pow(-k0 - 1)); + BigInteger u = l.multiply(TEN); + for (;;) { + assertTrue(l.bitLength() <= 2 - e & 2 - e < u.bitLength(), + "flog10threeQuartersPow2"); + --e; + if (e < Q_MIN) { + break; + } + int kp = flog10threeQuartersPow2(e); + assertTrue(kp <= k0, "flog10threeQuartersPow2"); + if (kp < k0) { + // k changes at most by 1 at each iteration, hence: + assertTrue(k0 - kp == 1, "flog10threeQuartersPow2"); + k0 = kp; + l = u; + u = u.multiply(TEN); + } + } + + /* + Finally, check the range 2 <= e <= Q_MAX. + In predicate + 10^k < 3 2^(e-2) < 10^(k+1) + the right inequality shows that k >= 0 as soon as e >= 2. + It is equivalent to + 10^k / 3 < 2^(e-2) < 10^(k+1) / 3 + Both the powers of 10 and the powers of 2 are integers. + The left inequality is therefore equivalent to + floor(10^k / 3) < 2^(e-2) + and thus to + len2(floor(10^k / 3)) <= e - 2 + while the right inequality is equivalent to + 2^(e-2) <= floor(10^(k+1) / 3) + and hence to + e - 2 < len2(floor(10^(k+1) / 3)) + These are summarized as + len2(floor(10^k / 3)) <= e - 2 < len2(floor(10^(k+1) / 3)) + */ + e = 2; + k0 = flog10threeQuartersPow2(e); + assertTrue(k0 >= 0, "flog10threeQuartersPow2"); + BigInteger l10 = TEN.pow(k0); + BigInteger u10 = l10.multiply(TEN); + l = l10.divide(THREE); + u = u10.divide(THREE); + for (;;) { + assertTrue(l.bitLength() <= e - 2 & e - 2 < u.bitLength(), + "flog10threeQuartersPow2"); + ++e; + if (e > Q_MAX) { + break; + } + int kp = flog10threeQuartersPow2(e); + assertTrue(kp >= k0, "flog10threeQuartersPow2"); + if (kp > k0) { + // k changes at most by 1 at each iteration, hence: + assertTrue(kp - k0 == 1, "flog10threeQuartersPow2"); + k0 = kp; + u10 = u10.multiply(TEN); + l = u; + u = u10.divide(THREE); + } + } + } + + /* + Let + k = floor(log10(2^e)) + The method verifies that + k = flog10pow2(e), Q_MIN <= e <= Q_MAX + This range covers all binary exponents of doubles and floats. + + The first equation above is equivalent to + 10^k <= 2^e < 10^(k+1) + Equality holds iff e = k = 0. + Henceforth, the predicates to check are equivalent to + k = 0, if e = 0 + 10^k < 2^e < 10^(k+1), otherwise + The latter will be transformed in various ways for checking purposes. + + For integer n > 0, let further + b = len2(n) + denote its length in bits. This means exactly the same as + 2^(b-1) <= n < 2^b + */ + @Test + void testFlog10pow2() { + // First check the case e = 0 + assertTrue(flog10pow2(0) == 0, "flog10pow2"); + + /* + Now check the range F * Q_MIN <= e < 0. + By inverting all quantities, the predicate to check is equivalent to + 10^(-k-1) < 2^(-e) < 10^(-k) + As e < 0, it follows that 2^(-e) >= 2 and the right inequality + implies k < 0. + The left inequality means exactly the same as + len2(10^(-k-1)) <= -e + Similarly, the right inequality is equivalent to + -e < len2(10^(-k)) + The original predicate is therefore equivalent to + len2(10^(-k-1)) <= -e < len2(10^(-k)) + The powers of 10 are integers because k < 0. + + Starting with e = -1 and decrementing towards the lower bound, the code + keeps track of the two powers of 10 so as to avoid recomputing them. + This is easy because at each iteration k changes at most by 1. A simple + multiplication by 10 computes the next power of 10 when needed. + */ + int e = -1; + int k = flog10pow2(e); + assertTrue(k < 0, "flog10pow2"); + BigInteger l = TEN.pow(-k - 1); + BigInteger u = l.multiply(TEN); + for (;;) { + assertTrue(l.bitLength() <= -e & -e < u.bitLength(), + "flog10pow2"); + --e; + if (e < Q_MIN) { + break; + } + int kp = flog10pow2(e); + assertTrue(kp <= k, "flog10pow2"); + if (kp < k) { + // k changes at most by 1 at each iteration, hence: + assertTrue(k - kp == 1, "flog10pow2"); + k = kp; + l = u; + u = u.multiply(TEN); + } + } + + /* + Finally, in a similar vein, check the range 0 <= e <= Q_MAX. + In predicate + 10^k < 2^e < 10^(k+1) + the right inequality shows that k >= 0. + The left inequality means the same as + len2(10^k) <= e + and the right inequality holds iff + e < len2(10^(k+1)) + The original predicate is thus equivalent to + len2(10^k) <= e < len2(10^(k+1)) + As k >= 0, the powers of 10 are integers. + */ + e = 1; + k = flog10pow2(e); + assertTrue(k >= 0, "flog10pow2"); + l = TEN.pow(k); + u = l.multiply(TEN); + for (;;) { + assertTrue(l.bitLength() <= e & e < u.bitLength(), + "flog10pow2"); + ++e; + if (e > Q_MAX) { + break; + } + int kp = flog10pow2(e); + assertTrue(kp >= k, "flog10pow2"); + if (kp > k) { + // k changes at most by 1 at each iteration, hence: + assertTrue(kp - k == 1, "flog10pow2"); + k = kp; + l = u; + u = u.multiply(TEN); + } + } + } + + /* + Let + k = floor(log2(10^e)) + The method verifies that + k = flog2pow10(e), -K_MAX <= e <= -K_MIN + This range covers all decimal exponents of doubles and floats. + + The first equation above is equivalent to + 2^k <= 10^e < 2^(k+1) + Equality holds iff e = 0, implying k = 0. + Henceforth, the equivalent predicates to check are + k = 0, if e = 0 + 2^k < 10^e < 2^(k+1), otherwise + The latter will be transformed in various ways for checking purposes. + + For integer n > 0, let further + b = len2(n) + denote its length in bits. This means exactly the same as + 2^(b-1) <= n < 2^b + */ + @Test + void testFlog2pow10() { + // First check the case e = 0 + assertTrue(flog2pow10(0) == 0, "flog2pow10"); + + /* + Now check the range K_MIN <= e < 0. + By inverting all quantities, the predicate to check is equivalent to + 2^(-k-1) < 10^(-e) < 2^(-k) + As e < 0, this leads to 10^(-e) >= 10 and the right inequality implies + k <= -4. + The above means the same as + len2(10^(-e)) = -k + The powers of 10 are integer values since e < 0. + */ + int e = -1; + int k0 = flog2pow10(e); + assertTrue(k0 <= -4, "flog2pow10"); + BigInteger l = TEN; + for (;;) { + assertTrue(l.bitLength() == -k0, "flog2pow10"); + --e; + if (e < -K_MAX) { + break; + } + k0 = flog2pow10(e); + l = l.multiply(TEN); + } + + /* + Finally check the range 0 < e <= K_MAX. + From the predicate + 2^k < 10^e < 2^(k+1) + as e > 0, it follows that 10^e >= 10 and the right inequality implies + k >= 3. + The above means the same as + len2(10^e) = k + 1 + The powers of 10 are all integer valued, as e > 0. + */ + e = 1; + k0 = flog2pow10(e); + assertTrue(k0 >= 3, "flog2pow10"); + l = TEN; + for (;;) { + assertTrue(l.bitLength() == k0 + 1, "flog2pow10"); + ++e; + if (e > -K_MIN) { + break; + } + k0 = flog2pow10(e); + l = l.multiply(TEN); + } + } + + @Test + void testBinaryConstants() { + assertTrue((long) (double) C_MIN == C_MIN, "C_MIN"); + assertTrue((long) (double) C_MAX == C_MAX, "C_MAX"); + assertTrue(scalb(1.0, Q_MIN) == MIN_VALUE, "MIN_VALUE"); + assertTrue(scalb((double) C_MIN, Q_MIN) == MIN_NORMAL, "MIN_NORMAL"); + assertTrue(scalb((double) C_MAX, Q_MAX) == MAX_VALUE, "MAX_VALUE"); + } + + @Test + void testDecimalConstants() { + assertTrue(K_MIN == MathUtils.K_MIN, "K_MIN"); + assertTrue(K_MAX == MathUtils.K_MAX, "K_MAX"); + assertTrue(H == MathUtils.H, "H"); + } + + @Test + void testPow10() { + int e = 0; + long pow = 1; + for (; e <= H; e += 1, pow *= 10) { + assertTrue(pow == pow10(e), "pow10"); + } + } + +} From 3cc9191cd481662dd0f11e33cd3ebebffbf9a94c Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Tue, 21 Jun 2022 11:57:19 +0100 Subject: [PATCH 16/17] Update MathUtilsTest.java --- .../core/io/schubfach/MathUtilsTest.java | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/test/java/com/fasterxml/jackson/core/io/schubfach/MathUtilsTest.java b/src/test/java/com/fasterxml/jackson/core/io/schubfach/MathUtilsTest.java index 40983ed1b1..7622e4e358 100644 --- a/src/test/java/com/fasterxml/jackson/core/io/schubfach/MathUtilsTest.java +++ b/src/test/java/com/fasterxml/jackson/core/io/schubfach/MathUtilsTest.java @@ -31,6 +31,7 @@ import static java.lang.StrictMath.scalb; import static java.math.BigInteger.*; import static com.fasterxml.jackson.core.io.schubfach.MathUtils.*; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; public class MathUtilsTest { @@ -357,7 +358,7 @@ e < len2(10^(k+1)) assertTrue(kp >= k, "flog10pow2"); if (kp > k) { // k changes at most by 1 at each iteration, hence: - assertTrue(kp - k == 1, "flog10pow2"); + assertEquals(1, kp - k, "flog10pow2"); k = kp; l = u; u = u.multiply(TEN); @@ -388,7 +389,7 @@ e < len2(10^(k+1)) @Test void testFlog2pow10() { // First check the case e = 0 - assertTrue(flog2pow10(0) == 0, "flog2pow10"); + assertEquals(0, flog2pow10(0), "flog2pow10"); /* Now check the range K_MIN <= e < 0. @@ -405,7 +406,7 @@ void testFlog2pow10() { assertTrue(k0 <= -4, "flog2pow10"); BigInteger l = TEN; for (;;) { - assertTrue(l.bitLength() == -k0, "flog2pow10"); + assertEquals(-k0, l.bitLength(), "flog2pow10"); --e; if (e < -K_MAX) { break; @@ -429,7 +430,7 @@ void testFlog2pow10() { assertTrue(k0 >= 3, "flog2pow10"); l = TEN; for (;;) { - assertTrue(l.bitLength() == k0 + 1, "flog2pow10"); + assertEquals(k0 + 1, l.bitLength(), "flog2pow10"); ++e; if (e > -K_MIN) { break; @@ -443,16 +444,16 @@ void testFlog2pow10() { void testBinaryConstants() { assertTrue((long) (double) C_MIN == C_MIN, "C_MIN"); assertTrue((long) (double) C_MAX == C_MAX, "C_MAX"); - assertTrue(scalb(1.0, Q_MIN) == MIN_VALUE, "MIN_VALUE"); - assertTrue(scalb((double) C_MIN, Q_MIN) == MIN_NORMAL, "MIN_NORMAL"); - assertTrue(scalb((double) C_MAX, Q_MAX) == MAX_VALUE, "MAX_VALUE"); + assertEquals(MIN_VALUE, scalb(1.0, Q_MIN), "MIN_VALUE"); + assertEquals(MIN_NORMAL, scalb((double) C_MIN, Q_MIN), "MIN_NORMAL"); + assertEquals(MAX_VALUE, scalb((double) C_MAX, Q_MAX), "MAX_VALUE"); } @Test void testDecimalConstants() { - assertTrue(K_MIN == MathUtils.K_MIN, "K_MIN"); - assertTrue(K_MAX == MathUtils.K_MAX, "K_MAX"); - assertTrue(H == MathUtils.H, "H"); + assertEquals(MathUtils.K_MIN, K_MIN, "K_MIN"); + assertEquals(MathUtils.K_MAX, K_MAX, "K_MAX"); + assertEquals(MathUtils.H, H, "H"); } @Test @@ -460,7 +461,7 @@ void testPow10() { int e = 0; long pow = 1; for (; e <= H; e += 1, pow *= 10) { - assertTrue(pow == pow10(e), "pow10"); + assertEquals(pow10(e), pow, "pow10"); } } From d3e6a727bf6c641cb5e63053c54939fc6744fa87 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Tue, 21 Jun 2022 12:16:07 +0100 Subject: [PATCH 17/17] further test refactor --- .../io/schubfach/DoubleToDecimalChecker.java | 7 ++- .../io/schubfach/DoubleToDecimalTest.java | 53 ++++++++++--------- .../io/schubfach/FloatToDecimalChecker.java | 9 ++-- .../core/io/schubfach/FloatToDecimalTest.java | 53 ++++++++++--------- .../core/io/schubfach/MathUtilsTest.java | 18 ++++--- .../core/io/schubfach/ToDecimalChecker.java | 3 -- 6 files changed, 70 insertions(+), 73 deletions(-) diff --git a/src/test/java/com/fasterxml/jackson/core/io/schubfach/DoubleToDecimalChecker.java b/src/test/java/com/fasterxml/jackson/core/io/schubfach/DoubleToDecimalChecker.java index ceed57290a..52f48eadce 100644 --- a/src/test/java/com/fasterxml/jackson/core/io/schubfach/DoubleToDecimalChecker.java +++ b/src/test/java/com/fasterxml/jackson/core/io/schubfach/DoubleToDecimalChecker.java @@ -29,7 +29,6 @@ import static java.lang.Long.numberOfTrailingZeros; import static java.lang.StrictMath.scalb; import static com.fasterxml.jackson.core.io.schubfach.MathUtils.flog10pow2; -import static org.junit.jupiter.api.Assertions.assertTrue; public class DoubleToDecimalChecker extends ToDecimalChecker { @@ -45,9 +44,9 @@ public class DoubleToDecimalChecker extends ToDecimalChecker { static final int K_MAX = flog10pow2(Q_MAX); static final int H = flog10pow2(P) + 2; - private static final double MIN_VALUE = scalb(1.0, Q_MIN); - private static final double MIN_NORMAL = scalb((double) C_MIN, Q_MIN); - private static final double MAX_VALUE = scalb((double) C_MAX, Q_MAX); + static final double MIN_VALUE = scalb(1.0, Q_MIN); + static final double MIN_NORMAL = scalb((double) C_MIN, Q_MIN); + static final double MAX_VALUE = scalb((double) C_MAX, Q_MAX); static final int E_MIN = e(MIN_VALUE); static final int E_MAX = e(MAX_VALUE); diff --git a/src/test/java/com/fasterxml/jackson/core/io/schubfach/DoubleToDecimalTest.java b/src/test/java/com/fasterxml/jackson/core/io/schubfach/DoubleToDecimalTest.java index c3f203b651..abf1c489ab 100644 --- a/src/test/java/com/fasterxml/jackson/core/io/schubfach/DoubleToDecimalTest.java +++ b/src/test/java/com/fasterxml/jackson/core/io/schubfach/DoubleToDecimalTest.java @@ -5,24 +5,25 @@ import java.util.Random; import static com.fasterxml.jackson.core.io.schubfach.DoubleToDecimalChecker.*; -import static java.lang.Double.*; +import static java.lang.Double.longBitsToDouble; import static java.lang.StrictMath.scalb; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; public class DoubleToDecimalTest { @Test void testExtremeValues() { - toDec(NEGATIVE_INFINITY); - toDec(-MAX_VALUE); - toDec(-MIN_NORMAL); - toDec(-MIN_VALUE); + toDec(Double.NEGATIVE_INFINITY); + toDec(-Double.MAX_VALUE); + toDec(-Double.MIN_NORMAL); + toDec(-Double.MIN_VALUE); toDec(-0.0); toDec(0.0); - toDec(MIN_VALUE); - toDec(MIN_NORMAL); - toDec(MAX_VALUE); - toDec(POSITIVE_INFINITY); - toDec(NaN); + toDec(Double.MIN_VALUE); + toDec(Double.MIN_NORMAL); + toDec(Double.MAX_VALUE); + toDec(Double.POSITIVE_INFINITY); + toDec(Double.NaN); /* Quiet NaNs have the most significant bit of the mantissa as 1, @@ -39,7 +40,7 @@ void testExtremeValues() { All values treated specially by Schubfach */ for (int c = 1; c < C_TINY; ++c) { - toDec(c * MIN_VALUE); + toDec(c * Double.MIN_VALUE); } } @@ -50,7 +51,7 @@ void testExtremeValues() { @Test void testPowersOf10() { for (int e = E_MIN; e <= E_MAX; ++e) { - toDec(parseDouble("1e" + e)); + toDec(Double.parseDouble("1e" + e)); } } @@ -60,7 +61,7 @@ void testPowersOf10() { */ @Test void testPowersOf2() { - for (double v = MIN_VALUE; v <= MAX_VALUE; v *= 2) { + for (double v = Double.MIN_VALUE; v <= Double.MAX_VALUE; v *= 2) { toDec(v); } } @@ -68,7 +69,7 @@ void testPowersOf2() { @Test void testSomeAnomalies() { for (String dec : Anomalies) { - toDec(parseDouble(dec)); + toDec(Double.parseDouble(dec)); } } @@ -103,23 +104,23 @@ void testInts() { @Test void testConstants() { - assertTrue(P == DoubleToDecimal.P, "P"); + assertEquals(DoubleToDecimal.P, P, "P"); assertTrue((long) (double) C_MIN == C_MIN, "C_MIN"); assertTrue((long) (double) C_MAX == C_MAX, "C_MAX"); - assertTrue(MIN_VALUE == Double.MIN_VALUE, "MIN_VALUE"); - assertTrue(MIN_NORMAL == Double.MIN_NORMAL, "MIN_NORMAL"); - assertTrue(MAX_VALUE == Double.MAX_VALUE, "MAX_VALUE"); + assertEquals(Double.MIN_VALUE, MIN_VALUE, "MIN_VALUE"); + assertEquals(Double.MIN_NORMAL, MIN_NORMAL, "MIN_NORMAL"); + assertEquals(Double.MAX_VALUE, MAX_VALUE, "MAX_VALUE"); - assertTrue(Q_MIN == DoubleToDecimal.Q_MIN, "Q_MIN"); - assertTrue(Q_MAX == DoubleToDecimal.Q_MAX, "Q_MAX"); + assertEquals(DoubleToDecimal.Q_MIN, Q_MIN, "Q_MIN"); + assertEquals(DoubleToDecimal.Q_MAX, Q_MAX, "Q_MAX"); - assertTrue(K_MIN == DoubleToDecimal.K_MIN, "K_MIN"); - assertTrue(K_MAX == DoubleToDecimal.K_MAX, "K_MAX"); - assertTrue(H == DoubleToDecimal.H, "H"); + assertEquals(DoubleToDecimal.K_MIN, K_MIN, "K_MIN"); + assertEquals(DoubleToDecimal.K_MAX, K_MAX, "K_MAX"); + assertEquals(DoubleToDecimal.H, H, "H"); - assertTrue(E_MIN == DoubleToDecimal.E_MIN, "E_MIN"); - assertTrue(E_MAX == DoubleToDecimal.E_MAX, "E_MAX"); - assertTrue(C_TINY == DoubleToDecimal.C_TINY, "C_TINY"); + assertEquals(DoubleToDecimal.E_MIN, E_MIN, "E_MIN"); + assertEquals(DoubleToDecimal.E_MAX, E_MAX, "E_MAX"); + assertEquals(DoubleToDecimal.C_TINY, C_TINY, "C_TINY"); } @Test diff --git a/src/test/java/com/fasterxml/jackson/core/io/schubfach/FloatToDecimalChecker.java b/src/test/java/com/fasterxml/jackson/core/io/schubfach/FloatToDecimalChecker.java index a39dab234e..92107efcd2 100644 --- a/src/test/java/com/fasterxml/jackson/core/io/schubfach/FloatToDecimalChecker.java +++ b/src/test/java/com/fasterxml/jackson/core/io/schubfach/FloatToDecimalChecker.java @@ -22,9 +22,6 @@ package com.fasterxml.jackson.core.io.schubfach; - -import org.junit.jupiter.api.Test; - import java.math.BigDecimal; import java.util.Random; @@ -47,9 +44,9 @@ public class FloatToDecimalChecker extends ToDecimalChecker { static final int K_MAX = flog10pow2(Q_MAX); static final int H = flog10pow2(P) + 2; - private static final float MIN_VALUE = scalb(1.0f, Q_MIN); - private static final float MIN_NORMAL = scalb((float) C_MIN, Q_MIN); - private static final float MAX_VALUE = scalb((float) C_MAX, Q_MAX); + static final float MIN_VALUE = scalb(1.0f, Q_MIN); + static final float MIN_NORMAL = scalb((float) C_MIN, Q_MIN); + static final float MAX_VALUE = scalb((float) C_MAX, Q_MAX); static final int E_MIN = e(MIN_VALUE); static final int E_MAX = e(MAX_VALUE); diff --git a/src/test/java/com/fasterxml/jackson/core/io/schubfach/FloatToDecimalTest.java b/src/test/java/com/fasterxml/jackson/core/io/schubfach/FloatToDecimalTest.java index e0644316b6..326ebc8cd0 100644 --- a/src/test/java/com/fasterxml/jackson/core/io/schubfach/FloatToDecimalTest.java +++ b/src/test/java/com/fasterxml/jackson/core/io/schubfach/FloatToDecimalTest.java @@ -5,8 +5,9 @@ import java.util.Random; import static com.fasterxml.jackson.core.io.schubfach.FloatToDecimalChecker.*; -import static java.lang.Float.*; +import static java.lang.Float.intBitsToFloat; import static java.lang.StrictMath.scalb; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; public class FloatToDecimalTest { @@ -15,17 +16,17 @@ public class FloatToDecimalTest { */ @Test void testExtremeValues() { - toDec(NEGATIVE_INFINITY); - toDec(-MAX_VALUE); - toDec(-MIN_NORMAL); - toDec(-MIN_VALUE); + toDec(Float.NEGATIVE_INFINITY); + toDec(-Float.MAX_VALUE); + toDec(-Float.MIN_NORMAL); + toDec(-Float.MIN_VALUE); toDec(-0.0f); toDec(0.0f); - toDec(MIN_VALUE); - toDec(MIN_NORMAL); - toDec(MAX_VALUE); - toDec(POSITIVE_INFINITY); - toDec(NaN); + toDec(Float.MIN_VALUE); + toDec(Float.MIN_NORMAL); + toDec(Float.MAX_VALUE); + toDec(Float.POSITIVE_INFINITY); + toDec(Float.NaN); /* Quiet NaNs have the most significant bit of the mantissa as 1, @@ -42,7 +43,7 @@ void testExtremeValues() { All values treated specially by Schubfach */ for (int c = 1; c < C_TINY; ++c) { - toDec(c * MIN_VALUE); + toDec(c * Float.MIN_VALUE); } } @@ -53,7 +54,7 @@ void testExtremeValues() { @Test void testPowersOf10() { for (int e = E_MIN; e <= E_MAX; ++e) { - toDec(parseFloat("1e" + e)); + toDec(Float.parseFloat("1e" + e)); } } @@ -63,36 +64,36 @@ void testPowersOf10() { */ @Test void testPowersOf2() { - for (float v = MIN_VALUE; v <= MAX_VALUE; v *= 2) { + for (float v = Float.MIN_VALUE; v <= Float.MAX_VALUE; v *= 2) { toDec(v); } } @Test void testConstants() { - assertTrue(P == FloatToDecimal.P, "P"); + assertEquals(FloatToDecimal.P, P, "P"); assertTrue((long) (float) C_MIN == C_MIN, "C_MIN"); assertTrue((long) (float) C_MAX == C_MAX, "C_MAX"); - assertTrue(MIN_VALUE == Float.MIN_VALUE, "MIN_VALUE"); - assertTrue(MIN_NORMAL == Float.MIN_NORMAL, "MIN_NORMAL"); - assertTrue(MAX_VALUE == Float.MAX_VALUE, "MAX_VALUE"); + assertEquals(Float.MIN_VALUE, MIN_VALUE, "MIN_VALUE"); + assertEquals(Float.MIN_NORMAL, MIN_NORMAL, "MIN_NORMAL"); + assertEquals(Float.MAX_VALUE, MAX_VALUE, "MAX_VALUE"); - assertTrue(Q_MIN == FloatToDecimal.Q_MIN, "Q_MIN"); - assertTrue(Q_MAX == FloatToDecimal.Q_MAX, "Q_MAX"); + assertEquals(FloatToDecimal.Q_MIN, Q_MIN, "Q_MIN"); + assertEquals(FloatToDecimal.Q_MAX, Q_MAX, "Q_MAX"); - assertTrue(K_MIN == FloatToDecimal.K_MIN, "K_MIN"); - assertTrue(K_MAX == FloatToDecimal.K_MAX, "K_MAX"); - assertTrue(H == FloatToDecimal.H, "H"); + assertEquals(FloatToDecimal.K_MIN, K_MIN, "K_MIN"); + assertEquals(FloatToDecimal.K_MAX, K_MAX, "K_MAX"); + assertEquals(FloatToDecimal.H, H, "H"); - assertTrue(E_MIN == FloatToDecimal.E_MIN, "E_MIN"); - assertTrue(E_MAX == FloatToDecimal.E_MAX, "E_MAX"); - assertTrue(C_TINY == FloatToDecimal.C_TINY, "C_TINY"); + assertEquals(FloatToDecimal.E_MIN, E_MIN, "E_MIN"); + assertEquals(FloatToDecimal.E_MAX, E_MAX, "E_MAX"); + assertEquals(FloatToDecimal.C_TINY, C_TINY, "C_TINY"); } @Test void testSomeAnomalies() { for (String dec : Anomalies) { - toDec(parseFloat(dec)); + toDec(Float.parseFloat(dec)); } } diff --git a/src/test/java/com/fasterxml/jackson/core/io/schubfach/MathUtilsTest.java b/src/test/java/com/fasterxml/jackson/core/io/schubfach/MathUtilsTest.java index 7622e4e358..95de7c8fc7 100644 --- a/src/test/java/com/fasterxml/jackson/core/io/schubfach/MathUtilsTest.java +++ b/src/test/java/com/fasterxml/jackson/core/io/schubfach/MathUtilsTest.java @@ -33,6 +33,7 @@ import static com.fasterxml.jackson.core.io.schubfach.MathUtils.*; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; public class MathUtilsTest { @@ -88,8 +89,9 @@ private static void testG(int k, long g1, long g0) { g - 1 = 10^(-k) 2^(-r) */ if (k <= 0 && r < 0) { - assertTrue( - g.subtract(ONE).compareTo(TEN.pow(-k).shiftLeft(-r)) == 0, + assertEquals( + 0, + g.subtract(ONE).compareTo(TEN.pow(-k).shiftLeft(-r)), "g"); return; } @@ -137,7 +139,7 @@ private static void testG(int k, long g1, long g0) { has a positive integer left-hand side and a middle side < 1, which cannot hold. */ - assertTrue(false, "g"); + fail("g"); } /* @@ -171,7 +173,7 @@ void testG() { @Test void testFlog10threeQuartersPow2() { // First check the case e = 1 - assertTrue(flog10threeQuartersPow2(1) == 0, + assertEquals(0, flog10threeQuartersPow2(1), "flog10threeQuartersPow2"); /* @@ -209,7 +211,7 @@ void testFlog10threeQuartersPow2() { assertTrue(kp <= k0, "flog10threeQuartersPow2"); if (kp < k0) { // k changes at most by 1 at each iteration, hence: - assertTrue(k0 - kp == 1, "flog10threeQuartersPow2"); + assertEquals(1, k0 - kp, "flog10threeQuartersPow2"); k0 = kp; l = u; u = u.multiply(TEN); @@ -253,7 +255,7 @@ void testFlog10threeQuartersPow2() { assertTrue(kp >= k0, "flog10threeQuartersPow2"); if (kp > k0) { // k changes at most by 1 at each iteration, hence: - assertTrue(kp - k0 == 1, "flog10threeQuartersPow2"); + assertEquals(1, kp - k0, "flog10threeQuartersPow2"); k0 = kp; u10 = u10.multiply(TEN); l = u; @@ -285,7 +287,7 @@ void testFlog10threeQuartersPow2() { @Test void testFlog10pow2() { // First check the case e = 0 - assertTrue(flog10pow2(0) == 0, "flog10pow2"); + assertEquals(0, flog10pow2(0), "flog10pow2"); /* Now check the range F * Q_MIN <= e < 0. @@ -322,7 +324,7 @@ void testFlog10pow2() { assertTrue(kp <= k, "flog10pow2"); if (kp < k) { // k changes at most by 1 at each iteration, hence: - assertTrue(k - kp == 1, "flog10pow2"); + assertEquals(1, k - kp, "flog10pow2"); k = kp; l = u; u = u.multiply(TEN); diff --git a/src/test/java/com/fasterxml/jackson/core/io/schubfach/ToDecimalChecker.java b/src/test/java/com/fasterxml/jackson/core/io/schubfach/ToDecimalChecker.java index 9978186179..0852a81a45 100644 --- a/src/test/java/com/fasterxml/jackson/core/io/schubfach/ToDecimalChecker.java +++ b/src/test/java/com/fasterxml/jackson/core/io/schubfach/ToDecimalChecker.java @@ -88,9 +88,6 @@ void validate() { "\"" + s + "\"" + ", which is not correct according to the specification."; assertTrue(isOK(), msg); - if (isOK()) { - return; - } } /*