From bb837bd020a8a6f46739369b34b9587d24a57a3c Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Thu, 7 Dec 2023 19:14:55 +0100 Subject: [PATCH] classlib: replace usage of Math.log10 with custom implementation of int log10 algorithm This fixes BigDecimal support on WASI, which does not support log10 at the moment --- .../classlib/impl/text/DoubleAnalyzer.java | 11 +++++++ .../java/lang/TAbstractStringBuilder.java | 5 --- .../teavm/classlib/java/math/TBigDecimal.java | 7 ++-- .../impl/text/DoubleAnalyzerTest.java | 33 +++++++++++++++++++ .../java/math/BigDecimalArithmeticTest.java | 3 -- .../java/math/BigDecimalCompareTest.java | 3 -- .../java/math/BigDecimalConstructorsTest.java | 3 -- .../math/BigDecimalScaleOperationsTest.java | 3 -- .../java/math/BigIntegerToStringTest.java | 3 -- .../classlib/java/text/DecimalFormatTest.java | 3 -- .../classlib/java/text/NumberFormatTest.java | 3 -- 11 files changed, 48 insertions(+), 29 deletions(-) create mode 100644 classlib/src/test/java/org/teavm/classlib/impl/text/DoubleAnalyzerTest.java diff --git a/classlib/src/main/java/org/teavm/classlib/impl/text/DoubleAnalyzer.java b/classlib/src/main/java/org/teavm/classlib/impl/text/DoubleAnalyzer.java index 3e52e0c71..90b25b9ed 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/text/DoubleAnalyzer.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/text/DoubleAnalyzer.java @@ -22,10 +22,21 @@ public final class DoubleAnalyzer { public static final int DECIMAL_PRECISION = 18; public static final long DOUBLE_MAX_POS = 100000000000000000L; private static final long MAX_MANTISSA = Long.divideUnsigned(-1, 10); + private static final Result resultForLog10 = new Result(); private DoubleAnalyzer() { } + public static int fastIntLog10(double d) { + var result = resultForLog10; + analyze(d, result); + var exponent = result.exponent; + if (exponent < 0 && result.mantissa > 100000000000000000L) { + ++exponent; + } + return exponent; + } + public static void analyze(double d, Result result) { long bits = Double.doubleToLongBits(d); result.sign = (bits & (1L << 63)) != 0; diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TAbstractStringBuilder.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TAbstractStringBuilder.java index b2bda3d00..c4e8ac1eb 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TAbstractStringBuilder.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TAbstractStringBuilder.java @@ -23,11 +23,6 @@ import org.teavm.classlib.java.util.TArrays; class TAbstractStringBuilder implements TSerializable, TCharSequence { static class Constants { - static int[] intPowersOfTen = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, - 1000000000 }; - static long[] longPowersOfTen = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, - 1000000000, 10000000000L, 100000000000L, 1000000000000L, 10000000000000L, 100000000000000L, - 1000000000000000L, 10000000000000000L, 100000000000000000L, 1000000000000000000L }; static final long[] longLogPowersOfTen = { 1, 10, 100, 10000, 100000000, 10000000000000000L, }; static final DoubleAnalyzer.Result doubleAnalysisResult = new DoubleAnalyzer.Result(); diff --git a/classlib/src/main/java/org/teavm/classlib/java/math/TBigDecimal.java b/classlib/src/main/java/org/teavm/classlib/java/math/TBigDecimal.java index 88eb7788f..72b7a11f3 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/math/TBigDecimal.java +++ b/classlib/src/main/java/org/teavm/classlib/java/math/TBigDecimal.java @@ -16,6 +16,7 @@ package org.teavm.classlib.java.math; import java.io.Serializable; +import org.teavm.classlib.impl.text.DoubleAnalyzer; /** * This class represents immutable arbitrary precision decimal numbers. Each @@ -1691,7 +1692,7 @@ public class TBigDecimal extends Number implements Comparable, Seri // The ANSI standard X3.274-1996 algorithm int m = Math.abs(n); int mcPrecision = mc.getPrecision(); - int elength = (int) Math.log10(m) + 1; // decimal digits in 'n' + int elength = (int) DoubleAnalyzer.fastIntLog10(m) + 1; // decimal digits in 'n' int oneBitMask; // mask of bits TBigDecimal accum; // the single accumulator TMathContext newPrecision = mc; // MathContext by default @@ -1850,7 +1851,7 @@ public class TBigDecimal extends Number implements Comparable, Seri } else if (bitLength >= 1) { doubleUnsc = smallValue; } - decimalDigits += Math.log10(Math.abs(doubleUnsc)); + decimalDigits += DoubleAnalyzer.fastIntLog10(Math.abs(doubleUnsc)); } else { // (bitLength >= 1024) /* To calculate the precision for large numbers @@ -2816,7 +2817,7 @@ public class TBigDecimal extends Number implements Comparable, Seri Long.signum(fraction) * (5 + compRem), mc.getRoundingMode()); // If after to add the increment the precision changed, we normalize the size - if (Math.log10(Math.abs(integer)) >= mc.getPrecision()) { + if (DoubleAnalyzer.fastIntLog10(Math.abs(integer)) >= mc.getPrecision()) { integer /= 10; newScale--; } diff --git a/classlib/src/test/java/org/teavm/classlib/impl/text/DoubleAnalyzerTest.java b/classlib/src/test/java/org/teavm/classlib/impl/text/DoubleAnalyzerTest.java new file mode 100644 index 000000000..1ba44302c --- /dev/null +++ b/classlib/src/test/java/org/teavm/classlib/impl/text/DoubleAnalyzerTest.java @@ -0,0 +1,33 @@ +/* + * 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 static org.junit.Assert.assertEquals; +import org.junit.Test; + +public class DoubleAnalyzerTest { + @Test + public void decimalExponent() { + var numbers = new double[] { 1e-200, 1e-5, 1e-4, 1e-3, 1e-2, 1e-1, 1, 1e1, 1e2, 1e3, 1e4, 1e5, 1e200 }; + var result = new DoubleAnalyzer.Result(); + for (var number : numbers) { + var e = number / 100; + assertEquals((int) Math.log10(number), DoubleAnalyzer.fastIntLog10(number)); + assertEquals((int) Math.log10(number + e), DoubleAnalyzer.fastIntLog10(number + e)); + assertEquals((int) Math.log10(number - e), DoubleAnalyzer.fastIntLog10(number - e)); + } + } +} diff --git a/tests/src/test/java/org/teavm/classlib/java/math/BigDecimalArithmeticTest.java b/tests/src/test/java/org/teavm/classlib/java/math/BigDecimalArithmeticTest.java index e45eb492d..d585ef3be 100644 --- a/tests/src/test/java/org/teavm/classlib/java/math/BigDecimalArithmeticTest.java +++ b/tests/src/test/java/org/teavm/classlib/java/math/BigDecimalArithmeticTest.java @@ -41,12 +41,9 @@ import java.math.MathContext; import java.math.RoundingMode; import org.junit.Test; import org.junit.runner.RunWith; -import org.teavm.junit.SkipPlatform; import org.teavm.junit.TeaVMTestRunner; -import org.teavm.junit.TestPlatform; @RunWith(TeaVMTestRunner.class) -@SkipPlatform(TestPlatform.WASI) public class BigDecimalArithmeticTest { /** * Add two numbers of equal positive scales diff --git a/tests/src/test/java/org/teavm/classlib/java/math/BigDecimalCompareTest.java b/tests/src/test/java/org/teavm/classlib/java/math/BigDecimalCompareTest.java index 0420ae1ef..cc385c90b 100644 --- a/tests/src/test/java/org/teavm/classlib/java/math/BigDecimalCompareTest.java +++ b/tests/src/test/java/org/teavm/classlib/java/math/BigDecimalCompareTest.java @@ -46,12 +46,9 @@ import java.math.MathContext; import java.math.RoundingMode; import org.junit.Test; import org.junit.runner.RunWith; -import org.teavm.junit.SkipPlatform; import org.teavm.junit.TeaVMTestRunner; -import org.teavm.junit.TestPlatform; @RunWith(TeaVMTestRunner.class) -@SkipPlatform(TestPlatform.WASI) public class BigDecimalCompareTest { /** * Abs() of a negative BigDecimal diff --git a/tests/src/test/java/org/teavm/classlib/java/math/BigDecimalConstructorsTest.java b/tests/src/test/java/org/teavm/classlib/java/math/BigDecimalConstructorsTest.java index 3f9c75bda..33195b950 100644 --- a/tests/src/test/java/org/teavm/classlib/java/math/BigDecimalConstructorsTest.java +++ b/tests/src/test/java/org/teavm/classlib/java/math/BigDecimalConstructorsTest.java @@ -44,12 +44,9 @@ import java.math.MathContext; import java.math.RoundingMode; import org.junit.Test; import org.junit.runner.RunWith; -import org.teavm.junit.SkipPlatform; import org.teavm.junit.TeaVMTestRunner; -import org.teavm.junit.TestPlatform; @RunWith(TeaVMTestRunner.class) -@SkipPlatform(TestPlatform.WASI) public class BigDecimalConstructorsTest { /** * check ONE diff --git a/tests/src/test/java/org/teavm/classlib/java/math/BigDecimalScaleOperationsTest.java b/tests/src/test/java/org/teavm/classlib/java/math/BigDecimalScaleOperationsTest.java index 6ea05c0a1..6af1b1685 100644 --- a/tests/src/test/java/org/teavm/classlib/java/math/BigDecimalScaleOperationsTest.java +++ b/tests/src/test/java/org/teavm/classlib/java/math/BigDecimalScaleOperationsTest.java @@ -44,12 +44,9 @@ import java.math.BigInteger; import java.math.RoundingMode; import org.junit.Test; import org.junit.runner.RunWith; -import org.teavm.junit.SkipPlatform; import org.teavm.junit.TeaVMTestRunner; -import org.teavm.junit.TestPlatform; @RunWith(TeaVMTestRunner.class) -@SkipPlatform(TestPlatform.WASI) public class BigDecimalScaleOperationsTest { /** * Check the default scale diff --git a/tests/src/test/java/org/teavm/classlib/java/math/BigIntegerToStringTest.java b/tests/src/test/java/org/teavm/classlib/java/math/BigIntegerToStringTest.java index d81b55cf7..4107bfcda 100644 --- a/tests/src/test/java/org/teavm/classlib/java/math/BigIntegerToStringTest.java +++ b/tests/src/test/java/org/teavm/classlib/java/math/BigIntegerToStringTest.java @@ -40,12 +40,9 @@ import static org.junit.Assert.assertTrue; import java.math.BigInteger; import org.junit.Test; import org.junit.runner.RunWith; -import org.teavm.junit.SkipPlatform; import org.teavm.junit.TeaVMTestRunner; -import org.teavm.junit.TestPlatform; @RunWith(TeaVMTestRunner.class) -@SkipPlatform(TestPlatform.WASI) public class BigIntegerToStringTest { /** * If 36 < radix < 2 it should be set to 10 diff --git a/tests/src/test/java/org/teavm/classlib/java/text/DecimalFormatTest.java b/tests/src/test/java/org/teavm/classlib/java/text/DecimalFormatTest.java index c07dd3106..6a59645de 100644 --- a/tests/src/test/java/org/teavm/classlib/java/text/DecimalFormatTest.java +++ b/tests/src/test/java/org/teavm/classlib/java/text/DecimalFormatTest.java @@ -27,15 +27,12 @@ import java.util.Currency; import java.util.Locale; import org.junit.Test; import org.junit.runner.RunWith; -import org.teavm.junit.SkipPlatform; import org.teavm.junit.TeaVMProperties; import org.teavm.junit.TeaVMProperty; import org.teavm.junit.TeaVMTestRunner; -import org.teavm.junit.TestPlatform; @RunWith(TeaVMTestRunner.class) @TeaVMProperties(@TeaVMProperty(key = "java.util.Locale.available", value = "en, en_US, en_GB, ru, ru_RU")) -@SkipPlatform(TestPlatform.WASI) public class DecimalFormatTest { private static DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.ENGLISH); diff --git a/tests/src/test/java/org/teavm/classlib/java/text/NumberFormatTest.java b/tests/src/test/java/org/teavm/classlib/java/text/NumberFormatTest.java index b3e35c761..f65de08fa 100644 --- a/tests/src/test/java/org/teavm/classlib/java/text/NumberFormatTest.java +++ b/tests/src/test/java/org/teavm/classlib/java/text/NumberFormatTest.java @@ -23,16 +23,13 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.teavm.junit.EachTestCompiledSeparately; import org.teavm.junit.SkipJVM; -import org.teavm.junit.SkipPlatform; import org.teavm.junit.TeaVMProperties; import org.teavm.junit.TeaVMProperty; import org.teavm.junit.TeaVMTestRunner; -import org.teavm.junit.TestPlatform; @RunWith(TeaVMTestRunner.class) @TeaVMProperties(@TeaVMProperty(key = "java.util.Locale.available", value = "en, en_US, en_GB, ru, ru_RU")) @EachTestCompiledSeparately -@SkipPlatform(TestPlatform.WASI) public class NumberFormatTest { @Test public void formatsNumber() {