classlib: improve accuracy of float parsing and formatting

This commit is contained in:
Alexey Andreev 2023-09-15 12:11:37 +02:00
parent e1706f242d
commit 062d4ae4e9
4 changed files with 264 additions and 257 deletions

View File

@ -21,6 +21,7 @@ public final class FloatAnalyzer {
public static final int PRECISION = 9; public static final int PRECISION = 9;
public static final int MAX_POS = 100000000; public static final int MAX_POS = 100000000;
static final int MAX_ABS_DEC_EXP = 50; static final int MAX_ABS_DEC_EXP = 50;
private static final int MAX_MANTISSA = Integer.divideUnsigned(-1, 10);
private FloatAnalyzer() { private FloatAnalyzer() {
} }
@ -54,13 +55,11 @@ public final class FloatAnalyzer {
int mantissaShift = 9 + binExponentCorrection; int mantissaShift = 9 + binExponentCorrection;
int decMantissa = mulAndShiftRight(mantissa, mantissa10Table[decExponent + 1], mantissaShift); int decMantissa = mulAndShiftRight(mantissa, mantissa10Table[decExponent + 1], mantissaShift);
if (decMantissa >= 1000000000) { if (decMantissa < MAX_MANTISSA) {
++decExponent; while (Integer.compareUnsigned(decMantissa, MAX_MANTISSA) <= 0) {
binExponentCorrection = exponent - exp10Table[decExponent + 1]; --decExponent;
mantissaShift = 9 + binExponentCorrection; decMantissa = decMantissa * 10 + 9;
decMantissa = mulAndShiftRight(mantissa, mantissa10Table[decExponent + 1], mantissaShift); }
} else if (decMantissa < 100000000) {
--decExponent;
binExponentCorrection = exponent - exp10Table[decExponent + 1]; binExponentCorrection = exponent - exp10Table[decExponent + 1];
mantissaShift = 9 + binExponentCorrection; mantissaShift = 9 + binExponentCorrection;
decMantissa = mulAndShiftRight(mantissa, mantissa10Table[decExponent + 1], mantissaShift); decMantissa = mulAndShiftRight(mantissa, mantissa10Table[decExponent + 1], mantissaShift);
@ -72,18 +71,21 @@ public final class FloatAnalyzer {
var lowerPos = findLowerDistance(decMantissa, decMantissaLow); var lowerPos = findLowerDistance(decMantissa, decMantissaLow);
var upperPos = findUpperDistance(decMantissa, decMantissaHi); var upperPos = findUpperDistance(decMantissa, decMantissaHi);
if (lowerPos > upperPos) { var posCmp = Integer.compareUnsigned(lowerPos, upperPos);
decMantissa = (decMantissa / lowerPos) * lowerPos; if (posCmp > 0) {
} else if (lowerPos < upperPos) { decMantissa = Integer.divideUnsigned(decMantissa, lowerPos) * lowerPos;
decMantissa = (decMantissa / upperPos) * upperPos + upperPos; } else if (posCmp < 0) {
decMantissa = Integer.divideUnsigned(decMantissa, upperPos) * upperPos + upperPos;
} else { } else {
decMantissa = ((decMantissa + (upperPos / 2)) / upperPos) * upperPos; decMantissa = Integer.divideUnsigned(decMantissa + (upperPos / 2), upperPos) * upperPos;
} }
if (decMantissa >= 1000000000) { if (Long.compareUnsigned(decMantissa, 1000000000) >= 0) {
decExponent++; do {
decMantissa /= 10; decExponent++;
} else if (decMantissa < 100000000) { decMantissa = Integer.divideUnsigned(decMantissa, 10);
} while (Integer.compareUnsigned(decMantissa, 1000000000) >= 0);
} else if (Integer.compareUnsigned(decMantissa, 100000000) < 0) {
decExponent--; decExponent--;
decMantissa *= 10; decMantissa *= 10;
} }
@ -94,7 +96,9 @@ public final class FloatAnalyzer {
private static int findLowerDistance(int mantissa, int lower) { private static int findLowerDistance(int mantissa, int lower) {
int pos = 1; int pos = 1;
while (mantissa / (pos * 10) > lower / (pos * 10)) { while (Integer.compareUnsigned(
Integer.divideUnsigned(mantissa, pos * 10),
Integer.divideUnsigned(lower, pos * 10)) > 0) {
pos *= 10; pos *= 10;
} }
return pos; return pos;
@ -102,7 +106,9 @@ public final class FloatAnalyzer {
private static int findUpperDistance(int mantissa, int upper) { private static int findUpperDistance(int mantissa, int upper) {
int pos = 1; int pos = 1;
while (mantissa / (pos * 10) < upper / (pos * 10)) { while (Integer.compareUnsigned(
Integer.divideUnsigned(mantissa, pos * 10),
Integer.divideUnsigned(upper, pos * 10)) < 0) {
pos *= 10; pos *= 10;
} }
return pos; return pos;
@ -110,10 +116,6 @@ public final class FloatAnalyzer {
static int mulAndShiftRight(int a, int b, int shift) { static int mulAndShiftRight(int a, int b, int shift) {
var result = (a & 0xFFFFFFFFL) * (b & 0xFFFFFFFFL); var result = (a & 0xFFFFFFFFL) * (b & 0xFFFFFFFFL);
var nextBit = ((result >> (31 - shift)) & 1) != 0;
if (nextBit) {
result += 1L << (31 - shift);
}
return (int) (result >>> (32 - shift)); return (int) (result >>> (32 - shift));
} }

View File

@ -20,26 +20,30 @@ public final class FloatSynthesizer {
} }
public static float synthesizeFloat(int mantissa, int exp, boolean negative) { public static float synthesizeFloat(int mantissa, int exp, boolean negative) {
var indexInTable = FloatAnalyzer.MAX_ABS_DEC_EXP - exp; var indexInTable = FloatAnalyzer.MAX_ABS_DEC_EXP + exp;
if (mantissa == 0 || indexInTable > mantissa10Table.length || indexInTable < 0) { if (mantissa == 0 || indexInTable > mantissa10Table.length || indexInTable < 0) {
return Float.intBitsToFloat(negative ? (1 << 31) : 0); return Float.intBitsToFloat(negative ? (1 << 31) : 0);
} }
var binMantissa = FloatAnalyzer.mulAndShiftRight(mantissa, mantissa10Table[indexInTable], 0); var binMantissa = FloatAnalyzer.mulAndShiftRight(mantissa, mantissa10Table[indexInTable], 0);
var binExp = exp10Table[indexInTable] - 1; var binExp = exp10Table[indexInTable] - 1;
while ((binMantissa & (-1L << 30L)) != 0) {
binMantissa >>>= 1; var binMantissaShift = (32 - Integer.numberOfLeadingZeros(binMantissa)) - 30;
binExp++; if (binMantissaShift >= 0) {
binMantissa >>>= binMantissaShift;
} else {
binMantissa <<= -binMantissaShift;
} }
while (binMantissa < (1L << 29)) { binExp += binMantissaShift;
binMantissa <<= 1;
binExp--;
}
binExp += 5;
if (binExp >= 255) { if (binExp >= 255) {
return negative ? Float.NEGATIVE_INFINITY : Float.POSITIVE_INFINITY; return negative ? Float.NEGATIVE_INFINITY : Float.POSITIVE_INFINITY;
} }
binMantissa += 1 << 5; binMantissa += 1 << 5;
if ((binMantissa & (-1 << 30)) != 0) {
binMantissa >>>= 1;
binExp++;
}
if (binExp <= 0) { if (binExp <= 0) {
binMantissa >>= Math.min(-binExp + 1, 32); binMantissa >>= Math.min(-binExp + 1, 32);
binExp = 0; binExp = 0;
@ -54,208 +58,208 @@ public final class FloatSynthesizer {
} }
private static final int[] mantissa10Table = { private static final int[] mantissa10Table = {
-1213479385, -1598972629,
-1829776968,
-350662770,
-1139523676,
-1770612400,
-255999462,
-1063793029,
-1710027882,
-159064234,
-986244846,
-1647989336,
-59802560,
-906835507,
-1584461865,
-2126562952,
-825520345,
-1519409735,
-2074521247,
-742253618,
-1452796353,
-2021230542,
-656988489,
-1384584251,
-1966660860,
-569676998,
-1314735058,
-1910781505,
-480270031,
-1243209484,
-1853561046,
-388717296,
-1169967296,
-1794967296,
-294967296,
-1094967296,
-1734967296,
-198967296,
-1018167296,
-1673527296,
-100663296,
-939524096,
-1610612736,
-2147483648,
-858993460,
-1546188227,
-2095944041,
-776530088,
-1480217529,
-2043167483,
-692087595,
-1412663535,
-1989124287,
-605618482,
-1343488245,
-1933784055,
-517074110,
-1272652747,
-1877115657,
-426404674,
-1200117198,
-1819087218,
-333559171,
-1125840796,
-1759666096,
-238485376,
-1049781760,
-1698818867,
-141129810,
-971897307,
-1636511305,
-41437710,
-892143627,
-1572708361,
-2117160148,
-810475859,
-1507374147,
-2064892777,
-726848065,
-1440471911,
-2011370988,
-641213203,
-1371964022,
-1956564677,
-553523105,
-1301811943,
-1900443014,
-463728444,
-1229976215,
-1842974431,
-371778712,
-1156416429,
-1784126602,
-277622186,
-1081091208,
-1723866426,
-181205903,
-1003958182,
-1662160005,
-82475630,
-924973963, -924973963,
-82475629,
-1662160004,
-1003958181,
-181205903,
-1723866425,
-1081091207,
-277622185,
-1784126602,
-1156416428,
-371778711,
-1842974431,
-1229976214,
-463728444,
-1900443013,
-1301811943,
-553523104,
-1956564676,
-1371964021,
-641213203,
-2011370988,
-1440471911,
-726848064,
-2064892776,
-1507374146,
-810475859,
-2117160148,
-1572708361,
-892143627,
-41437709,
-1636511304,
-971897307,
-141129809,
-1698818867,
-1049781759,
-238485375,
-1759666096,
-1125840795,
-333559170,
-1819087217,
-1200117198,
-426404673,
-1877115657,
-1272652747,
-517074110,
-1933784055,
-1343488244,
-605618481,
-1989124287,
-1412663534,
-692087594,
-2043167482,
-1480217529,
-776530087,
-2095944040,
-1546188227,
-858993459,
-2147483648,
-1610612736,
-939524096,
-100663296,
-1673527296,
-1018167296,
-198967296,
-1734967296,
-1094967296,
-294967296,
-1794967296,
-1169967296,
-388717296,
-1853561046,
-1243209483,
-480270030,
-1910781505,
-1314735057,
-569676998,
-1966660859,
-1384584250,
-656988489,
-2021230542,
-1452796353,
-742253617,
-2074521247,
-1519409734,
-825520344,
-2126562951,
-1584461865,
-906835507,
-59802560,
-1647989336,
-986244846,
-159064233,
-1710027882,
-1063793028,
-255999461,
-1770612399,
-1139523675,
-350662770,
-1829776967,
}; };
private static int[] exp10Table = { private static int[] exp10Table = {
292, -35,
289, -32,
285, -29,
282, -25,
279, -22,
275, -19,
272, -15,
269, -12,
265, -9,
262, -5,
259, -2,
255, 1,
252, 5,
249, 8,
246, 11,
242, 15,
239, 18,
236, 21,
232, 25,
229, 28,
226, 31,
222, 35,
219, 38,
216, 41,
212, 45,
209, 48,
206, 51,
202, 55,
199, 58,
196, 61,
192, 64,
189, 68,
186, 71,
182, 74,
179, 78,
176, 81,
172, 84,
169, 88,
166, 91,
162, 94,
159, 98,
156, 101,
153, 104,
149, 108,
146, 111,
143, 114,
139, 118,
136, 121,
133, 124,
129, 128,
126, 131,
123, 134,
119, 138,
116, 141,
113, 144,
109, 148,
106, 151,
103, 154,
99, 158,
96, 161,
93, 164,
89, 167,
86, 171,
83, 174,
79, 177,
76, 181,
73, 184,
69, 187,
66, 191,
63, 194,
59, 197,
56, 201,
53, 204,
50, 207,
46, 211,
43, 214,
40, 217,
36, 221,
33, 224,
30, 227,
26, 231,
23, 234,
20, 237,
16, 241,
13, 244,
10, 247,
6, 251,
3, 254,
0, 257,
-4, 260,
-7, 264,
-10, 267,
-14, 270,
-17, 274,
-20, 277,
-24, 280,
-27, 284,
-30, 287,
-34, 290,
-37, 294,
}; };
} }

View File

@ -24,29 +24,26 @@ public final class FloatSynthesizerGenerator {
public static void main(String[] args) { public static void main(String[] args) {
var mantissaList = new int[100]; var mantissaList = new int[100];
var expList = new int[100]; var expList = new int[100];
var shift = 57;
var exp = 0; var binOneShift = 256;
var binOne = BigInteger.ONE.shiftLeft(binOneShift);
var dec = BigInteger.valueOf(100000000); var dec = BigInteger.valueOf(100000000);
for (var i = 0; i < 50; ++i) { for (var i = 0; i <= 50; ++i) {
while (BigInteger.ONE.shiftLeft(shift + exp + 1).divide(dec).bitLength() <= 32) { var quot = binOne.divide(dec);
++exp; mantissaList[50 - i] = extractInt(quot);
} var exp = quot.bitLength() - binOneShift + 30;
mantissaList[50 + i] = BigInteger.ONE.shiftLeft(shift + exp).divide(dec).intValue(); expList[50 - i] = 127 + exp;
dec = dec.multiply(BigInteger.valueOf(10)); dec = dec.multiply(BigInteger.valueOf(10));
expList[50 + i] = 127 - exp;
} }
exp = 1; dec = BigInteger.valueOf(100000000);
dec = BigInteger.valueOf(100000000).multiply(BigInteger.ONE.shiftLeft(128)); var q = BigInteger.TEN;
var q = BigInteger.valueOf(10L); for (var i = 1; i < 50; ++i) {
for (var i = 1; i <= 50; ++i) { var quot = q.shiftLeft(binOneShift).divide(dec);
while (BigInteger.ONE.shiftLeft(shift + 128 - exp).multiply(q).divide(dec).bitLength() > 32) { mantissaList[50 + i] = extractInt(quot);
++exp; var exp = quot.bitLength() - binOneShift + 30;
} expList[50 + i] = 127 + exp;
mantissaList[50 - i] = BigInteger.ONE.shiftLeft(shift + 128 - exp).multiply(q).divide(dec).intValue(); q = q.multiply(BigInteger.TEN);
q = q.multiply(BigInteger.valueOf(10));
expList[50 - i] = 127 + exp;
} }
System.out.println("[mantissa]"); System.out.println("[mantissa]");
@ -60,4 +57,8 @@ public final class FloatSynthesizerGenerator {
System.out.println(value + ","); System.out.println(value + ",");
} }
} }
private static int extractInt(BigInteger n) {
return n.shiftRight(n.bitLength() - 33).add(BigInteger.ONE).shiftRight(1).intValue();
}
} }

View File

@ -114,17 +114,17 @@ public class TFloat extends TNumber implements TComparable<TFloat> {
@Unmanaged @Unmanaged
public static native boolean isFinite(float v); public static native boolean isFinite(float v);
public static float parseFloat(String string) throws TNumberFormatException { public static float parseFloat(String string) throws NumberFormatException {
// TODO: parse infinite and different radix // TODO: parse infinite and different radix
if (string.isEmpty()) { if (string.isEmpty()) {
throw new TNumberFormatException(); throw new NumberFormatException();
} }
int start = 0; int start = 0;
int end = string.length(); int end = string.length();
while (string.charAt(start) <= ' ') { while (string.charAt(start) <= ' ') {
if (++start == end) { if (++start == end) {
throw new TNumberFormatException(); throw new NumberFormatException();
} }
} }
while (string.charAt(end - 1) <= ' ') { while (string.charAt(end - 1) <= ' ') {
@ -140,7 +140,7 @@ public class TFloat extends TNumber implements TComparable<TFloat> {
++index; ++index;
} }
if (index == end) { if (index == end) {
throw new TNumberFormatException(); throw new NumberFormatException();
} }
char c = string.charAt(index); char c = string.charAt(index);
@ -152,7 +152,7 @@ public class TFloat extends TNumber implements TComparable<TFloat> {
if (c != '.') { if (c != '.') {
hasOneDigit = true; hasOneDigit = true;
if (c < '0' || c > '9') { if (c < '0' || c > '9') {
throw new TNumberFormatException(); throw new NumberFormatException();
} }
while (index < end && string.charAt(index) == '0') { while (index < end && string.charAt(index) == '0') {
@ -189,18 +189,18 @@ public class TFloat extends TNumber implements TComparable<TFloat> {
hasOneDigit = true; hasOneDigit = true;
} }
if (!hasOneDigit) { if (!hasOneDigit) {
throw new TNumberFormatException(); throw new NumberFormatException();
} }
} }
if (index < end) { if (index < end) {
c = string.charAt(index); c = string.charAt(index);
if (c != 'e' && c != 'E') { if (c != 'e' && c != 'E') {
throw new TNumberFormatException(); throw new NumberFormatException();
} }
++index; ++index;
boolean negativeExp = false; boolean negativeExp = false;
if (index == end) { if (index == end) {
throw new TNumberFormatException(); throw new NumberFormatException();
} }
if (string.charAt(index) == '-') { if (string.charAt(index) == '-') {
++index; ++index;
@ -220,7 +220,7 @@ public class TFloat extends TNumber implements TComparable<TFloat> {
++index; ++index;
} }
if (!hasOneDigit) { if (!hasOneDigit) {
throw new TNumberFormatException(); throw new NumberFormatException();
} }
if (negativeExp) { if (negativeExp) {
numExp = -numExp; numExp = -numExp;