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
This commit is contained in:
Alexey Andreev 2023-12-07 19:14:55 +01:00
parent a9af6e4f33
commit bb837bd020
11 changed files with 48 additions and 29 deletions

View File

@ -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;

View File

@ -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();

View File

@ -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<TBigDecimal>, 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<TBigDecimal>, 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<TBigDecimal>, 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--;
}

View File

@ -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));
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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() {