classlib: improve precision of float/double parsing and formatting

This commit is contained in:
Alexey Andreev 2023-09-10 21:22:07 +02:00
parent e6c71fa106
commit e2ee9f1dbb
22 changed files with 3808 additions and 291 deletions

View File

@ -64,3 +64,4 @@ tasks {
teavmPublish {
artifactId = "teavm-classlib"
}

View File

@ -0,0 +1,56 @@
/*
* Copyright 2023 Alexey Andreev.
*
* 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 org.teavm.classlib.impl.text;
import java.math.BigInteger;
public final class DoubleAnalyzerGenerator {
private DoubleAnalyzerGenerator() {
}
public static void main(String[] args) {
var mantissaList = new long[660];
var expList = new long[660];
var dec = BigInteger.valueOf(1000000000000000000L).shiftLeft(1024 + 64);
for (var i = 0; i < 330; ++i) {
var shift = dec.bitLength() - 64;
mantissaList[330 + i] = dec.shiftRight(shift).longValue();
dec = dec.divide(BigInteger.valueOf(10));
var exp = 1024 + 64 - shift;
expList[330 + i] = 1023 + exp;
}
dec = BigInteger.valueOf(1000000000000000000L);
for (var i = 1; i <= 330; ++i) {
dec = dec.multiply(BigInteger.valueOf(10));
var shift = dec.bitLength() - 64;
mantissaList[330 - i] = dec.shiftRight(shift).longValue();
expList[330 - i] = 1023 - shift;
}
System.out.println("[mantissa]");
for (var value : mantissaList) {
System.out.println(value + "L,");
}
System.out.println();
System.out.println("[exponent]");
for (var value : expList) {
System.out.println(value + ",");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,63 @@
/*
* Copyright 2023 Alexey Andreev.
*
* 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 org.teavm.classlib.impl.text;
import java.math.BigInteger;
public final class DoubleSynthesizerGenerator {
private DoubleSynthesizerGenerator() {
}
public static void main(String[] args) {
var mantissaList = new long[660];
var expList = new long[660];
var shift = 122;
var exp = 0;
var dec = BigInteger.valueOf(1000000000000000000L);
for (var i = 0; i < 330; ++i) {
while (BigInteger.ONE.shiftLeft(shift + exp + 1).divide(dec).bitLength() <= 64) {
++exp;
}
mantissaList[330 + i] = BigInteger.ONE.shiftLeft(shift + exp).divide(dec).longValue();
dec = dec.multiply(BigInteger.valueOf(10));
expList[330 + i] = 1023 - exp;
}
exp = 1;
dec = BigInteger.valueOf(1000000000000000000L).multiply(BigInteger.ONE.shiftLeft(1024));
var q = BigInteger.valueOf(10L);
for (var i = 1; i <= 330; ++i) {
while (BigInteger.ONE.shiftLeft(shift + 1024 - exp).multiply(q).divide(dec).bitLength() > 64) {
++exp;
}
mantissaList[330 - i] = BigInteger.ONE.shiftLeft(shift + 1024 - exp).multiply(q).divide(dec).longValue();
q = q.multiply(BigInteger.valueOf(10));
expList[330 - i] = 1023 + exp;
}
System.out.println("[mantissa]");
for (var value : mantissaList) {
System.out.println(value + "L,");
}
System.out.println();
System.out.println("[exponent]");
for (var value : expList) {
System.out.println(value + ",");
}
}
}

View File

@ -20,58 +20,11 @@ import java.util.Arrays;
public final class FloatAnalyzer {
public static final int PRECISION = 9;
public static final int MAX_POS = 100000000;
private static final int MAX_ABS_DEC_EXP = 50;
private static final int[] mantissa10Table = new int[MAX_ABS_DEC_EXP * 2];
private static final int[] exp10Table = new int[MAX_ABS_DEC_EXP * 2];
static final int MAX_ABS_DEC_EXP = 50;
private FloatAnalyzer() {
}
static {
int decMantissaOne = 2000000000;
int mantissa = decMantissaOne;
int exponent = 127;
for (int i = 0; i < MAX_ABS_DEC_EXP; ++i) {
mantissa10Table[i + MAX_ABS_DEC_EXP] = Integer.divideUnsigned(mantissa, 20);
exp10Table[i + MAX_ABS_DEC_EXP] = exponent;
mantissa = Integer.divideUnsigned(mantissa, 10);
int remainder = Integer.remainderUnsigned(mantissa, 10);
while (mantissa <= decMantissaOne && (mantissa & (1 << 31)) == 0) {
mantissa <<= 1;
exponent++;
remainder <<= 1;
}
mantissa += remainder / 10;
}
int maxMantissa = Integer.MAX_VALUE / 10;
mantissa = decMantissaOne;
exponent = 127;
for (int i = 0; i < MAX_ABS_DEC_EXP; ++i) {
int nextMantissa = mantissa;
int shift = 0;
while (nextMantissa > maxMantissa) {
nextMantissa >>= 1;
shift++;
exponent--;
}
nextMantissa *= 10;
if (shift > 0) {
long shiftedOffPart = mantissa & ((1 << shift) - 1);
nextMantissa += (shiftedOffPart * 10) >> shift;
}
mantissa = nextMantissa;
mantissa10Table[MAX_ABS_DEC_EXP - i - 1] = Integer.divideUnsigned(mantissa, 20);
exp10Table[MAX_ABS_DEC_EXP - i - 1] = exponent;
}
}
public static void analyze(float d, Result result) {
int bits = Float.floatToIntBits(d);
result.sign = (bits & (1 << 31)) != 0;
@ -83,13 +36,11 @@ public final class FloatAnalyzer {
return;
}
int errorShift = 0;
if (exponent == 0) {
mantissa <<= 1;
while ((mantissa & (1L << 23)) == 0) {
mantissa <<= 1;
exponent--;
++errorShift;
}
} else {
mantissa |= 1 << 23;
@ -97,37 +48,36 @@ public final class FloatAnalyzer {
int decExponent = Arrays.binarySearch(exp10Table, exponent);
if (decExponent < 0) {
decExponent = -decExponent - 2;
decExponent = -decExponent;
}
int binExponentCorrection = exponent - exp10Table[decExponent];
int binExponentCorrection = exponent - exp10Table[decExponent + 1];
int mantissaShift = 9 + binExponentCorrection;
int decMantissa = (int) (((long) mantissa * mantissa10Table[decExponent]) >>> (32 - mantissaShift));
int decMantissa = mulAndShiftRight(mantissa, mantissa10Table[decExponent + 1], mantissaShift);
if (decMantissa >= 1000000000) {
++decExponent;
binExponentCorrection = exponent - exp10Table[decExponent];
binExponentCorrection = exponent - exp10Table[decExponent + 1];
mantissaShift = 9 + binExponentCorrection;
decMantissa = (int) (((long) mantissa * mantissa10Table[decExponent]) >>> (32 - mantissaShift));
decMantissa = mulAndShiftRight(mantissa, mantissa10Table[decExponent + 1], mantissaShift);
} else if (decMantissa < 100000000) {
--decExponent;
binExponentCorrection = exponent - exp10Table[decExponent + 1];
mantissaShift = 9 + binExponentCorrection;
decMantissa = mulAndShiftRight(mantissa, mantissa10Table[decExponent + 1], mantissaShift);
}
var decMantissaHi = mulAndShiftRight((mantissa << 1) + 1, mantissa10Table[decExponent + 1],
mantissaShift - 1);
var decMantissaLow = mulAndShiftRight((mantissa << 1) - 1, mantissa10Table[decExponent + 1],
mantissaShift - 1);
errorShift = 31 - mantissaShift - errorShift;
int error = errorShift >= 0
? mantissa10Table[decExponent] >>> errorShift
: mantissa10Table[decExponent] << (-errorShift);
int upError = (error + 1) >> 1;
int downError = error >> 1;
if (mantissa == (1 << 22)) {
downError >>= 2;
}
int lowerPos = findLowerDistanceToZero(decMantissa, downError);
int upperPos = findUpperDistanceToZero(decMantissa, upError);
var lowerPos = findLowerDistance(decMantissa, decMantissaLow);
var upperPos = findUpperDistance(decMantissa, decMantissaHi);
if (lowerPos > upperPos) {
decMantissa = (decMantissa / lowerPos) * lowerPos;
} else if (lowerPos < upperPos) {
decMantissa = (decMantissa / upperPos) * upperPos + upperPos;
} else {
decMantissa = ((decMantissa + upperPos / 2) / upperPos) * upperPos;
decMantissa = ((decMantissa + (upperPos / 2)) / upperPos) * upperPos;
}
if (decMantissa >= 1000000000) {
@ -142,33 +92,242 @@ public final class FloatAnalyzer {
result.exponent = decExponent - MAX_ABS_DEC_EXP;
}
private static int findLowerDistanceToZero(int mantissa, int error) {
int pos = 10;
while (pos <= error) {
private static int findLowerDistance(int mantissa, int lower) {
int pos = 1;
while (mantissa / (pos * 10) > lower / (pos * 10)) {
pos *= 10;
}
int mantissaRight = mantissa % pos;
if (mantissaRight >= error / 2) {
pos /= 10;
}
return pos;
}
private static int findUpperDistanceToZero(int mantissa, int error) {
int pos = 10;
while (pos <= error) {
private static int findUpperDistance(int mantissa, int upper) {
int pos = 1;
while (mantissa / (pos * 10) < upper / (pos * 10)) {
pos *= 10;
}
int mantissaRight = mantissa % pos;
if (pos - mantissaRight > error / 2) {
pos /= 10;
}
return pos;
}
static int mulAndShiftRight(int a, int b, int shift) {
var result = (a & 0xFFFFFFFFL) * (b & 0xFFFFFFFFL);
var nextBit = ((result >> (31 - shift)) & 1) != 0;
if (nextBit) {
result += 1L << (31 - shift);
}
return (int) (result >>> (32 - shift));
}
public static class Result {
public int mantissa;
public int exponent;
public boolean sign;
}
// The code below was generated by FloatAnalyzerGenerator
private static final int[] mantissa10Table = {
-18543760,
-873828468,
-1558056233,
-2105438446,
-791721136,
-1492370368,
-2052889754,
-707643228,
-1425108042,
-1999079893,
-621547450,
-1356231419,
-1943978595,
-533385374,
-1285701758,
-1887554866,
-443107408,
-1213479385,
-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,
-1956564688,
};
private static final int[] exp10Table = {
-37,
-34,
-31,
-28,
-24,
-21,
-18,
-14,
-11,
-8,
-4,
-1,
2,
6,
9,
12,
16,
19,
22,
26,
29,
32,
36,
39,
42,
46,
49,
52,
56,
59,
62,
65,
69,
72,
75,
79,
82,
85,
89,
92,
95,
99,
102,
105,
109,
112,
115,
119,
122,
125,
129,
132,
135,
139,
142,
145,
149,
152,
155,
158,
162,
165,
168,
172,
175,
178,
182,
185,
188,
192,
195,
198,
202,
205,
208,
212,
215,
218,
222,
225,
228,
232,
235,
238,
242,
245,
248,
252,
255,
258,
261,
265,
268,
271,
275,
278,
281,
285,
288,
291,
};
}

View File

@ -0,0 +1,56 @@
/*
* Copyright 2023 konsoletyper.
*
* 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 org.teavm.classlib.impl.text;
import java.math.BigInteger;
public final class FloatAnalyzerGenerator {
private FloatAnalyzerGenerator() {
}
public static void main(String[] args) {
var mantissaList = new int[100];
var expList = new int[100];
var dec = BigInteger.valueOf(1000000000).shiftLeft(128 + 32);
for (var i = 0; i < 50; ++i) {
var shift = dec.bitLength() - 32;
mantissaList[50 + i] = dec.shiftRight(shift).intValue();
dec = dec.divide(BigInteger.valueOf(10));
var exp = 128 + 32 - shift;
expList[50 + i] = 127 + exp;
}
dec = BigInteger.valueOf(1000000000);
for (var i = 1; i <= 50; ++i) {
dec = dec.multiply(BigInteger.valueOf(10));
var shift = dec.bitLength() - 32;
mantissaList[50 - i] = dec.shiftRight(shift).intValue();
expList[50 - i] = 127 - shift;
}
System.out.println("[mantissa]");
for (var value : mantissaList) {
System.out.println(value + ",");
}
System.out.println();
System.out.println("[exponent]");
for (var value : expList) {
System.out.println(value + ",");
}
}
}

View File

@ -0,0 +1,261 @@
/*
* Copyright 2023 Alexey Andreev.
*
* 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 org.teavm.classlib.impl.text;
public final class FloatSynthesizer {
private FloatSynthesizer() {
}
public static float synthesizeFloat(int mantissa, int exp, boolean negative) {
var indexInTable = FloatAnalyzer.MAX_ABS_DEC_EXP - exp;
if (mantissa == 0 || indexInTable > mantissa10Table.length || indexInTable < 0) {
return Float.intBitsToFloat(negative ? (1 << 31) : 0);
}
var binMantissa = FloatAnalyzer.mulAndShiftRight(mantissa, mantissa10Table[indexInTable], 0);
var binExp = exp10Table[indexInTable] - 1;
while ((binMantissa & (-1L << 30L)) != 0) {
binMantissa >>>= 1;
binExp++;
}
while (binMantissa < (1L << 29)) {
binMantissa <<= 1;
binExp--;
}
binExp += 5;
if (binExp >= 255) {
return negative ? Float.NEGATIVE_INFINITY : Float.POSITIVE_INFINITY;
}
binMantissa += 1 << 5;
if (binExp <= 0) {
binMantissa >>= Math.min(-binExp + 1, 32);
binExp = 0;
}
binMantissa = (binMantissa >>> 6) & (-1 << 9 >>> 9);
var iee754 = binMantissa | (binExp << 23);
if (negative) {
iee754 ^= 1 << 31;
}
return Float.intBitsToFloat(iee754);
}
private static final int[] mantissa10Table = {
-1213479385,
-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,
};
private static int[] exp10Table = {
292,
289,
285,
282,
279,
275,
272,
269,
265,
262,
259,
255,
252,
249,
246,
242,
239,
236,
232,
229,
226,
222,
219,
216,
212,
209,
206,
202,
199,
196,
192,
189,
186,
182,
179,
176,
172,
169,
166,
162,
159,
156,
153,
149,
146,
143,
139,
136,
133,
129,
126,
123,
119,
116,
113,
109,
106,
103,
99,
96,
93,
89,
86,
83,
79,
76,
73,
69,
66,
63,
59,
56,
53,
50,
46,
43,
40,
36,
33,
30,
26,
23,
20,
16,
13,
10,
6,
3,
0,
-4,
-7,
-10,
-14,
-17,
-20,
-24,
-27,
-30,
-34,
-37,
};
}

View File

@ -0,0 +1,63 @@
/*
* Copyright 2023 Alexey Andreev.
*
* 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 org.teavm.classlib.impl.text;
import java.math.BigInteger;
public final class FloatSynthesizerGenerator {
private FloatSynthesizerGenerator() {
}
public static void main(String[] args) {
var mantissaList = new int[100];
var expList = new int[100];
var shift = 57;
var exp = 0;
var dec = BigInteger.valueOf(100000000);
for (var i = 0; i < 50; ++i) {
while (BigInteger.ONE.shiftLeft(shift + exp + 1).divide(dec).bitLength() <= 32) {
++exp;
}
mantissaList[50 + i] = BigInteger.ONE.shiftLeft(shift + exp).divide(dec).intValue();
dec = dec.multiply(BigInteger.valueOf(10));
expList[50 + i] = 127 - exp;
}
exp = 1;
dec = BigInteger.valueOf(100000000).multiply(BigInteger.ONE.shiftLeft(128));
var q = BigInteger.valueOf(10L);
for (var i = 1; i <= 50; ++i) {
while (BigInteger.ONE.shiftLeft(shift + 128 - exp).multiply(q).divide(dec).bitLength() > 32) {
++exp;
}
mantissaList[50 - i] = BigInteger.ONE.shiftLeft(shift + 128 - exp).multiply(q).divide(dec).intValue();
q = q.multiply(BigInteger.valueOf(10));
expList[50 - i] = 127 + exp;
}
System.out.println("[mantissa]");
for (var value : mantissaList) {
System.out.println(value + ",");
}
System.out.println();
System.out.println("[exponent]");
for (var value : expList) {
System.out.println(value + ",");
}
}
}

View File

@ -206,13 +206,13 @@ class TAbstractStringBuilder implements TSerializable, TCharSequence {
buffer[target++] = '.';
buffer[target++] = '0';
return this;
} else if (TFloat.isNaN(value)) {
} else if (Float.isNaN(value)) {
insertSpace(target, target + 3);
buffer[target++] = 'N';
buffer[target++] = 'a';
buffer[target++] = 'N';
return this;
} else if (TFloat.isInfinite(value)) {
} else if (Float.isInfinite(value)) {
if (value > 0) {
insertSpace(target, target + 8);
} else {
@ -230,7 +230,7 @@ class TAbstractStringBuilder implements TSerializable, TCharSequence {
return this;
}
FloatAnalyzer.Result number = Constants.floatAnalysisResult;
var number = Constants.floatAnalysisResult;
FloatAnalyzer.analyze(value, number);
int mantissa = number.mantissa;
int exp = number.exponent;
@ -247,6 +247,8 @@ class TAbstractStringBuilder implements TSerializable, TCharSequence {
if (zeros > 0) {
digits -= zeros;
}
var leadingZeros = 0;
var leadingZero = false;
// Handle special case of exponent close to 0
if (exp < 7 && exp >= -3) {
@ -255,8 +257,10 @@ class TAbstractStringBuilder implements TSerializable, TCharSequence {
digits = Math.max(digits, intPart + 1);
exp = 0;
} else {
mantissa /= Constants.intPowersOfTen[-exp];
digits -= exp;
intPart = 0;
leadingZeros = -exp - 1;
leadingZero = true;
sz++;
exp = 0;
}
}
@ -275,7 +279,7 @@ class TAbstractStringBuilder implements TSerializable, TCharSequence {
if (exp != 0 && digits == intPart) {
digits++;
}
sz += digits;
sz += digits + leadingZeros;
// Print mantissa
insertSpace(target, target + sz);
@ -283,6 +287,13 @@ class TAbstractStringBuilder implements TSerializable, TCharSequence {
buffer[target++] = '-';
}
int pos = FloatAnalyzer.MAX_POS;
if (leadingZero) {
buffer[target++] = '0';
buffer[target++] = '.';
while (leadingZeros-- > 0) {
buffer[target++] = '0';
}
}
for (int i = 0; i < digits; ++i) {
int intDigit;
if (pos > 0) {
@ -331,13 +342,13 @@ class TAbstractStringBuilder implements TSerializable, TCharSequence {
buffer[target++] = '.';
buffer[target++] = '0';
return this;
} else if (TDouble.isNaN(value)) {
} else if (Double.isNaN(value)) {
insertSpace(target, target + 3);
buffer[target++] = 'N';
buffer[target++] = 'a';
buffer[target++] = 'N';
return this;
} else if (TDouble.isInfinite(value)) {
} else if (Double.isInfinite(value)) {
if (value > 0) {
insertSpace(target, target + 8);
} else {
@ -355,7 +366,7 @@ class TAbstractStringBuilder implements TSerializable, TCharSequence {
return this;
}
DoubleAnalyzer.Result number = Constants.doubleAnalysisResult;
var number = Constants.doubleAnalysisResult;
DoubleAnalyzer.analyze(value, number);
long mantissa = number.mantissa;
int exp = number.exponent;
@ -374,6 +385,8 @@ class TAbstractStringBuilder implements TSerializable, TCharSequence {
if (zeros > 0) {
digits -= zeros;
}
var leadingZeros = 0;
var leadingZero = false;
// Handle special case of exponent close to 0
if (exp < 7 && exp >= -3) {
@ -382,8 +395,10 @@ class TAbstractStringBuilder implements TSerializable, TCharSequence {
digits = Math.max(digits, intPart + 1);
exp = 0;
} else {
mantissa /= Constants.longPowersOfTen[-exp];
digits -= exp;
intPart = 0;
leadingZeros = -exp - 1;
leadingZero = true;
sz++;
exp = 0;
}
}
@ -405,7 +420,7 @@ class TAbstractStringBuilder implements TSerializable, TCharSequence {
if (exp != 0 && digits == intPart) {
digits++;
}
sz += digits;
sz += digits + leadingZeros;
// Print mantissa
insertSpace(target, target + sz);
@ -413,6 +428,13 @@ class TAbstractStringBuilder implements TSerializable, TCharSequence {
buffer[target++] = '-';
}
long pos = DoubleAnalyzer.DOUBLE_MAX_POS;
if (leadingZero) {
buffer[target++] = '0';
buffer[target++] = '.';
while (leadingZeros-- > 0) {
buffer[target++] = '0';
}
}
for (int i = 0; i < digits; ++i) {
int intDigit;
if (pos > 0) {

View File

@ -16,6 +16,7 @@
package org.teavm.classlib.java.lang;
import org.teavm.backend.javascript.spi.InjectedBy;
import org.teavm.classlib.impl.text.DoubleSynthesizer;
import org.teavm.interop.Import;
import org.teavm.interop.NoSideEffects;
import org.teavm.interop.Unmanaged;
@ -25,7 +26,7 @@ import org.teavm.jso.JSBody;
public class TDouble extends TNumber implements TComparable<TDouble> {
public static final double POSITIVE_INFINITY = 1 / 0.0;
public static final double NEGATIVE_INFINITY = -POSITIVE_INFINITY;
public static final double NaN = getNaN();
public static final double NaN = 0 / 0.0;
public static final double MAX_VALUE = 0x1.FFFFFFFFFFFFFP+1023;
public static final double MIN_NORMAL = -0x1.0P+1022;
public static final double MIN_VALUE = 0x0.0000000000001P-1022;
@ -39,7 +40,7 @@ public class TDouble extends TNumber implements TComparable<TDouble> {
this.value = value;
}
public TDouble(TString value) throws TNumberFormatException {
public TDouble(String value) throws TNumberFormatException {
this.value = parseDouble(value);
}
@ -71,21 +72,21 @@ public class TDouble extends TNumber implements TComparable<TDouble> {
return new TStringBuilder().append(d).toString();
}
public static TDouble valueOf(TString string) {
public static TDouble valueOf(String string) {
return valueOf(parseDouble(string));
}
public static double parseDouble(TString string) throws TNumberFormatException {
public static double parseDouble(String string) throws NumberFormatException {
// TODO: parse infinite and different radix
if (string.isEmpty()) {
throw new TNumberFormatException();
throw new NumberFormatException();
}
int start = 0;
int end = string.length();
while (string.charAt(start) <= ' ') {
if (++start == end) {
throw new TNumberFormatException();
throw new NumberFormatException();
}
}
while (string.charAt(end - 1) <= ' ') {
@ -101,17 +102,18 @@ public class TDouble extends TNumber implements TComparable<TDouble> {
++index;
}
if (index == end) {
throw new TNumberFormatException();
throw new NumberFormatException();
}
char c = string.charAt(index);
long mantissa = 0;
int exp = 0;
int exp = -1;
boolean hasOneDigit = false;
long mantissaPos = 1000000000000000000L;
if (c != '.') {
hasOneDigit = true;
if (c < '0' || c > '9') {
throw new TNumberFormatException();
throw new NumberFormatException();
}
while (index < end && string.charAt(index) == '0') {
++index;
@ -121,11 +123,11 @@ public class TDouble extends TNumber implements TComparable<TDouble> {
if (c < '0' || c > '9') {
break;
}
if (mantissa < TLong.MAX_VALUE / 10 - 9) {
mantissa = mantissa * 10 + (c - '0');
} else {
++exp;
if (mantissaPos > 0) {
mantissa = mantissa + (mantissaPos * (c - '0'));
mantissaPos = Long.divideUnsigned(mantissaPos, 10);
}
++exp;
++index;
}
}
@ -136,26 +138,28 @@ public class TDouble extends TNumber implements TComparable<TDouble> {
if (c < '0' || c > '9') {
break;
}
if (mantissa < TLong.MAX_VALUE / 10 - 9) {
mantissa = mantissa * 10 + (c - '0');
--exp;
if (mantissa == 0 && c == '0') {
exp--;
} else if (mantissaPos > 0) {
mantissa = mantissa + (mantissaPos * (c - '0'));
mantissaPos = Long.divideUnsigned(mantissaPos, 10);
}
++index;
hasOneDigit = true;
}
if (!hasOneDigit) {
throw new TNumberFormatException();
throw new NumberFormatException();
}
}
if (index < end) {
c = string.charAt(index);
if (c != 'e' && c != 'E') {
throw new TNumberFormatException();
throw new NumberFormatException();
}
++index;
boolean negativeExp = false;
if (index == end) {
throw new TNumberFormatException();
throw new NumberFormatException();
}
if (string.charAt(index) == '-') {
++index;
@ -175,39 +179,15 @@ public class TDouble extends TNumber implements TComparable<TDouble> {
++index;
}
if (!hasOneDigit) {
throw new TNumberFormatException();
throw new NumberFormatException();
}
if (negativeExp) {
numExp = -numExp;
}
exp += numExp;
}
if (exp > 308 || exp == 308 && mantissa > 17976931348623157L) {
return !negative ? POSITIVE_INFINITY : NEGATIVE_INFINITY;
}
if (negative) {
mantissa = -mantissa;
}
return mantissa * decimalExponent(exp);
}
public static double decimalExponent(int n) {
double d;
if (n < 0) {
d = 0.1;
n = -n;
} else {
d = 10;
}
double result = 1;
while (n != 0) {
if (n % 2 != 0) {
result *= d;
}
d *= d;
n /= 2;
}
return result;
return DoubleSynthesizer.synthesizeDouble(mantissa, exp, negative);
}
@Override
@ -255,12 +235,6 @@ public class TDouble extends TNumber implements TComparable<TDouble> {
@Unmanaged
public static native boolean isNaN(double v);
@JSBody(script = "return NaN;")
@Import(module = "teavm", name = "teavm_getNaN")
@NoSideEffects
@Unmanaged
private static native double getNaN();
@JSBody(params = "v", script = "return !isFinite(v);")
@Import(module = "teavm", name = "isinf")
@NoSideEffects

View File

@ -15,6 +15,7 @@
*/
package org.teavm.classlib.java.lang;
import org.teavm.classlib.impl.text.FloatSynthesizer;
import org.teavm.interop.Import;
import org.teavm.interop.NoSideEffects;
import org.teavm.interop.Unmanaged;
@ -23,7 +24,7 @@ import org.teavm.jso.JSBody;
public class TFloat extends TNumber implements TComparable<TFloat> {
public static final float POSITIVE_INFINITY = 1 / 0.0f;
public static final float NEGATIVE_INFINITY = -POSITIVE_INFINITY;
public static final float NaN = getNaN();
public static final float NaN = 0f / 0f;
public static final float MAX_VALUE = 0x1.fffffeP+127f;
public static final float MIN_VALUE = 0x1.0p-126f;
public static final float MIN_NORMAL = 0x0.000002P-126f;
@ -41,7 +42,7 @@ public class TFloat extends TNumber implements TComparable<TFloat> {
this((float) value);
}
public TFloat(TString value) throws TNumberFormatException {
public TFloat(String value) throws TNumberFormatException {
this(parseFloat(value));
}
@ -113,13 +114,7 @@ public class TFloat extends TNumber implements TComparable<TFloat> {
@Unmanaged
public static native boolean isFinite(float v);
@JSBody(script = "return NaN;")
@Import(module = "teavm", name = "teavm_getNaN")
@NoSideEffects
@Unmanaged
private static native float getNaN();
public static float parseFloat(TString string) throws TNumberFormatException {
public static float parseFloat(String string) throws TNumberFormatException {
// TODO: parse infinite and different radix
if (string.isEmpty()) {
@ -150,7 +145,8 @@ public class TFloat extends TNumber implements TComparable<TFloat> {
char c = string.charAt(index);
int mantissa = 0;
int exp = 0;
int exp = -1;
int mantissaPos = 100000000;
boolean hasOneDigit = false;
if (c != '.') {
@ -167,11 +163,11 @@ public class TFloat extends TNumber implements TComparable<TFloat> {
if (c < '0' || c > '9') {
break;
}
if (mantissa < (TInteger.MAX_VALUE / 10) - 9) {
mantissa = mantissa * 10 + (c - '0');
} else {
++exp;
if (mantissaPos > 0) {
mantissa = mantissa + (mantissaPos * (c - '0'));
mantissaPos = Integer.divideUnsigned(mantissaPos, 10);
}
++exp;
++index;
}
}
@ -183,9 +179,11 @@ public class TFloat extends TNumber implements TComparable<TFloat> {
if (c < '0' || c > '9') {
break;
}
if (mantissa < (TInteger.MAX_VALUE / 10) - 9) {
mantissa = mantissa * 10 + (c - '0');
--exp;
if (mantissa == 0 && c == '0') {
exp--;
} else if (mantissaPos > 0) {
mantissa = mantissa + (mantissaPos * (c - '0'));
mantissaPos = Integer.divideUnsigned(mantissaPos, 10);
}
++index;
hasOneDigit = true;
@ -229,35 +227,11 @@ public class TFloat extends TNumber implements TComparable<TFloat> {
}
exp += numExp;
}
if (exp > 38 || exp == 38 && mantissa > 34028234) {
return !negative ? POSITIVE_INFINITY : NEGATIVE_INFINITY;
}
if (negative) {
mantissa = -mantissa;
}
return mantissa * decimalExponent(exp);
return FloatSynthesizer.synthesizeFloat(mantissa, exp, negative);
}
private static float decimalExponent(int n) {
double d;
if (n < 0) {
d = 0.1;
n = -n;
} else {
d = 10;
}
double result = 1;
while (n != 0) {
if (n % 2 != 0) {
result *= d;
}
d *= d;
n /= 2;
}
return (float) result;
}
public static TFloat valueOf(TString s) throws TNumberFormatException {
public static TFloat valueOf(String s) throws TNumberFormatException {
return valueOf(parseFloat(s));
}

View File

@ -20,10 +20,10 @@ import java.math.BigInteger;
import java.text.DecimalFormatSymbols;
import java.util.Arrays;
import org.teavm.classlib.impl.text.DoubleAnalyzer;
import org.teavm.classlib.impl.text.DoubleSynthesizer;
import org.teavm.classlib.impl.text.FloatAnalyzer;
import org.teavm.classlib.impl.unicode.CLDRHelper;
import org.teavm.classlib.java.lang.TArithmeticException;
import org.teavm.classlib.java.lang.TDouble;
import org.teavm.classlib.java.util.TLocale;
public class TDecimalFormat extends TNumberFormat {
@ -494,15 +494,22 @@ public class TDecimalFormat extends TNumberFormat {
}
}
// Expose result
if (mantissa == 0 && !positive) {
return -0.0;
}
if (exponent == 0) {
return positive ? mantissa : -mantissa;
}
double result = TDouble.decimalExponent(exponent) * mantissa;
return positive ? result : -result;
exponent += 18;
if (mantissa != 0) {
while (mantissa / 1000000000000000000L == 0) {
mantissa *= 10;
exponent--;
}
}
return DoubleSynthesizer.synthesizeDouble(mantissa, exponent, !positive);
}
@Override

View File

@ -0,0 +1,53 @@
/*
* Copyright 2023 Alexey Andreev.
*
* 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 org.teavm.classlib.impl;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.teavm.classlib.java.lang.TDouble;
public class DoubleParseTest {
@Test
public void parse() {
assertEquals(1, TDouble.parseDouble("1"), 1E-12);
assertEquals(23, TDouble.parseDouble("23"), 1E-12);
assertEquals(23, TDouble.parseDouble("23.0"), 1E-12);
assertEquals(23, TDouble.parseDouble("23E0"), 1E-12);
assertEquals(23, TDouble.parseDouble("2.30000E1"), 1E-12);
assertEquals(23, TDouble.parseDouble("0.23E2"), 1E-12);
assertEquals(23, TDouble.parseDouble("0.000023E6"), 1E-12);
assertEquals(23, TDouble.parseDouble("00230000e-4"), 1E-12);
assertEquals(23, TDouble.parseDouble("2300000000000000000000e-20"), 1E-12);
assertEquals(23, TDouble.parseDouble("2300000000000000000000e-20"), 1E-12);
assertEquals(23, TDouble.parseDouble("23."), 1E-12);
assertEquals(0.1, TDouble.parseDouble("0.1"), 0.001);
assertEquals(0.1, TDouble.parseDouble(".1"), 0.001);
assertEquals(0.1, TDouble.parseDouble(" .1"), 0.001);
assertEquals(0.1, TDouble.parseDouble(".1 "), 0.001);
assertEquals(-23, TDouble.parseDouble("-23"), 1E-12);
assertEquals(0, TDouble.parseDouble("0.0"), 1E-12);
assertEquals(0, TDouble.parseDouble("0"), 1E-12);
assertEquals(0, TDouble.parseDouble("00"), 1E-12);
assertEquals(0, TDouble.parseDouble("0."), 1E-12);
assertEquals(0, TDouble.parseDouble(".0"), 1E-12);
assertEquals(0, TDouble.parseDouble("23E-8000"), 1E-12);
assertEquals(0, TDouble.parseDouble("00000"), 1E-12);
assertEquals(0, TDouble.parseDouble("00000.0000"), 1E-12);
assertEquals(9218868437227405312L, Double.doubleToLongBits(TDouble.parseDouble("7.17917199420887E309")));
assertEquals(3823970227817620L, Double.doubleToLongBits(TDouble.parseDouble("1.889292320284411E-308")));
}
}

View File

@ -0,0 +1,61 @@
/*
* Copyright 2023 Alexey Andreev.
*
* 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 org.teavm.classlib.impl;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.teavm.classlib.java.lang.TStringBuilder;
public class DoubleWriteTest {
@Test
public void lowerUpperEquidistant() {
var d = Double.longBitsToDouble(1551003667821398223L);
assertEquals("5.5497208273234845E-205", toString(d));
}
@Test
public void absLowNegativeExponent() {
var d = Double.longBitsToDouble(-4656062020850909140L);
assertEquals("-0.0022393517064785064", toString(d));
}
@Test
public void values() {
assertEquals("1.23456789E150", toString(1.23456789E150));
assertEquals("10.0", toString(10.0));
assertEquals("20.0", toString(20.0));
assertEquals("100.0", toString(100.0));
assertEquals("1000.0", toString(1000.0));
assertEquals("0.1", toString(0.1));
assertEquals("0.01", toString(0.01));
assertEquals("0.001", toString(0.001));
assertEquals("1.0E20", toString(1e20));
assertEquals("2.0E20", toString(2e20));
assertEquals("1.0E-12", toString(1e-12));
assertEquals("-1.23456789E150", toString(-1.23456789E150));
assertEquals("1.23456789E-150", toString(1.23456789E-150));
assertEquals("1.79769313486231E308", toString(1.79769313486231E308));
assertEquals("3.0E-308", toString(3E-308));
assertEquals("1200.0", toString(1200.0));
assertEquals("0.023", toString(0.023));
assertEquals("0.0", toString(0.0));
assertEquals("1.0", toString(1.0));
}
private String toString(double d) {
return new TStringBuilder().append(d).toString();
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright 2023 Alexey Andreev.
*
* 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 org.teavm.classlib.impl;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.teavm.classlib.java.lang.TFloat;
public class FloatParseTest {
@Test
public void parse() {
assertEquals(1, TFloat.parseFloat("1"), 1E-12f);
assertEquals(23, TFloat.parseFloat("23"), 1E-12F);
assertEquals(23, TFloat.parseFloat("23.0"), 1E-12F);
assertEquals(23, TFloat.parseFloat("23E0"), 1E-12F);
assertEquals(23, TFloat.parseFloat("2.30000E1"), 1E-12F);
assertEquals(23, TFloat.parseFloat("0.23E2"), 1E-12F);
assertEquals(23, TFloat.parseFloat("0.000023E6"), 1E-12F);
assertEquals(23, TFloat.parseFloat("00230000e-4"), 1E-12F);
assertEquals(23, TFloat.parseFloat("2300000000000000000000e-20"), 1E-12F);
assertEquals(23, TFloat.parseFloat("2300000000000000000000e-20"), 1E-12F);
assertEquals(23, TFloat.parseFloat("2300000000000000000000e-20"), 1E-12F);
assertEquals(23, TFloat.parseFloat("23."), 1E-12F);
assertEquals(0.1F, TFloat.parseFloat("0.1"), 0.001F);
assertEquals(0.1F, TFloat.parseFloat(".1"), 0.001F);
assertEquals(0.1F, TFloat.parseFloat(" .1"), 0.001F);
assertEquals(0.1F, TFloat.parseFloat(".1 "), 0.001F);
assertEquals(-23, TFloat.parseFloat("-23"), 1E-12F);
assertEquals(0, TFloat.parseFloat("0.0"), 1E-12F);
assertEquals(0, TFloat.parseFloat("0"), 1E-12F);
assertEquals(0, TFloat.parseFloat("00"), 1E-12F);
assertEquals(0, TFloat.parseFloat(".0"), 1E-12F);
assertEquals(0, TFloat.parseFloat("0."), 1E-12F);
assertEquals(0, TFloat.parseFloat("23E-8000"), 1E-12F);
assertEquals(0, TFloat.parseFloat("00000"), 1E-12F);
assertEquals(0, TFloat.parseFloat("00000.0000"), 1E-12F);
assertEquals(4499999285F, TFloat.parseFloat("4499999285"), 100F);
assertEquals(0.4499999285F, TFloat.parseFloat("0.4499999285"), 1E-9F);
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright 2023 Alexey Andreev.
*
* 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 org.teavm.classlib.impl;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.teavm.classlib.java.lang.TStringBuilder;
public class FloatWriteTest {
@Test
public void values() {
assertEquals("1.234E25", toString(1.234E25F));
assertEquals("9.8765E30", toString(9.8765E30F));
assertEquals("-1.234E25", toString(-1.234E25F));
assertEquals("-9.8765E30", toString(-9.8765E30F));
assertEquals("3.402823E38", toString(3.402823E38f));
assertEquals("9.8764E-30", toString(9.8764E-30F));
assertEquals("-1.234E-25", toString(-1.234E-25F));
assertEquals("-9.8764E-30", toString(-9.8764E-30F));
assertEquals("1.17549E-38", toString(1.17549E-38f));
assertEquals("1200.0", toString(1200f));
assertEquals("0.023", toString(0.023f));
assertEquals("0.0", toString(0f));
assertEquals("1.0", toString(1f));
}
private String toString(float f) {
return new TStringBuilder().append(f).toString();
}
}

View File

@ -151,10 +151,6 @@ static inline void* teavm_checkcast(void* obj, int32_t (*cls)(TeaVM_Class*)) {
extern double teavm_rand();
static inline float teavm_getNaN() {
return NAN;
}
extern void teavm_beforeInit();
extern void teavm_afterInitClasses();

View File

@ -15,10 +15,6 @@ static int32_t wasm_heap_size;
static int wasm_args;
static char** wasm_argv;
float teavm_teavm_getNaN() {
return NAN;
}
#define teavmMath_sin sin
#define teavmMath_cos cos
#define teavmMath_sqrt sqrt

View File

@ -68,6 +68,11 @@ gradle.allprojects {
tasks.withType<Javadoc>().configureEach {
options.encoding = "UTF-8"
}
tasks.withType<JavaExec>().configureEach {
if (name.endsWith("main()")) {
notCompatibleWithConfigurationCache("JavaExec created by IntelliJ")
}
}
}
gradle.afterProject {

View File

@ -18,6 +18,7 @@ package org.teavm.classlib.java.lang;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.Random;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.teavm.junit.TeaVMTestRunner;
@ -49,11 +50,28 @@ public class DoubleTest {
assertEquals(0, Double.parseDouble("23E-8000"), 1E-12);
assertEquals(0, Double.parseDouble("00000"), 1E-12);
assertEquals(0, Double.parseDouble("00000.0000"), 1E-12);
assertEquals("74.92507492507494", Double.toString(Double.parseDouble("74.92507492507494")));
assertEquals(4499999999999888888888888.0, Double.parseDouble("4499999999999888888888888"), 1E9);
assertEquals(0.4499999999999888888888888, Double.parseDouble("0.4499999999999888888888888"), 1E-15);
}
@Test
public void randomDoubles() {
var random = new Random();
for (var i = 0; i < 10000; ++i) {
var n = random.nextLong();
var d = Double.longBitsToDouble(n);
if (Double.isNaN(d) || Double.isInfinite(d)) {
continue;
}
var actual = Double.parseDouble(Double.toString(d));
if (n != Double.doubleToLongBits(actual)) {
System.out.println(d + ", " + n + ", " + Double.doubleToLongBits(actual));
}
}
}
@Test
public void parsedWithError() {
checkIllegalFormat("");

View File

@ -30,9 +30,11 @@ import org.junit.runner.RunWith;
import org.teavm.junit.TeaVMProperties;
import org.teavm.junit.TeaVMProperty;
import org.teavm.junit.TeaVMTestRunner;
import org.teavm.junit.WholeClassCompilation;
@RunWith(TeaVMTestRunner.class)
@TeaVMProperties(@TeaVMProperty(key = "java.util.Locale.available", value = "en, en_US, en_GB, ru, ru_RU"))
@WholeClassCompilation
public class DecimalFormatTest {
private static DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.ENGLISH);