From 2bb146af47ef7a5341613bfb47d8806fa05c2d01 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Fri, 7 Dec 2018 00:21:18 +0300 Subject: [PATCH] Improve float to string conversion --- .../classlib/impl/text/DoubleAnalyzer.java | 3 + .../classlib/impl/text/FloatAnalyzer.java | 174 ++++++++++++++++++ .../java/lang/IntegerNativeGenerator.java | 43 +++++ .../java/lang/TAbstractStringBuilder.java | 62 ++----- .../teavm/classlib/java/lang/TInteger.java | 7 + .../java/org/teavm/backend/c/CTarget.java | 2 + .../backend/c/intrinsic/IntegerIntrinsic.java | 55 ++++++ .../rendering/StatementRenderer.java | 21 ++- .../org/teavm/backend/wasm/WasmTarget.java | 2 + .../wasm/intrinsics/IntegerIntrinsic.java | 56 ++++++ .../org/teavm/backend/javascript/runtime.js | 26 +++ .../classlib/java/lang/StringBuilderTest.java | 4 +- 12 files changed, 402 insertions(+), 53 deletions(-) create mode 100644 classlib/src/main/java/org/teavm/classlib/impl/text/FloatAnalyzer.java create mode 100644 classlib/src/main/java/org/teavm/classlib/java/lang/IntegerNativeGenerator.java create mode 100644 core/src/main/java/org/teavm/backend/c/intrinsic/IntegerIntrinsic.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/intrinsics/IntegerIntrinsic.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 fa3f51aff..ebeeeefb8 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 @@ -130,6 +130,9 @@ public final class DoubleAnalyzer { if (decMantissa >= 1000000000000000000L) { decExponent++; decMantissa /= 10; + } else if (decMantissa < 100000000000000000L) { + decExponent--; + decMantissa *= 10; } result.mantissa = decMantissa; diff --git a/classlib/src/main/java/org/teavm/classlib/impl/text/FloatAnalyzer.java b/classlib/src/main/java/org/teavm/classlib/impl/text/FloatAnalyzer.java new file mode 100644 index 000000000..faddd7ce2 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/impl/text/FloatAnalyzer.java @@ -0,0 +1,174 @@ +/* + * Copyright 2018 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.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]; + + 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; + int mantissa = bits & ((1 << 23) - 1); + int exponent = (bits >> 23) & ((1 << 8) - 1); + if (mantissa == 0 && exponent == 0) { + result.mantissa = 0; + result.exponent = 0; + return; + } + + int errorShift = 0; + if (exponent == 0) { + mantissa <<= 1; + while ((mantissa & (1L << 23)) == 0) { + mantissa <<= 1; + exponent--; + ++errorShift; + } + } else { + mantissa |= 1 << 23; + } + + int decExponent = Arrays.binarySearch(exp10Table, exponent); + if (decExponent < 0) { + decExponent = -decExponent - 2; + } + int binExponentCorrection = exponent - exp10Table[decExponent]; + int mantissaShift = 9 + binExponentCorrection; + + int decMantissa = (int) (((long) mantissa * mantissa10Table[decExponent]) >>> (32 - mantissaShift)); + if (decMantissa >= 1000000000) { + ++decExponent; + binExponentCorrection = exponent - exp10Table[decExponent]; + mantissaShift = 9 + binExponentCorrection; + decMantissa = (int) (((long) mantissa * mantissa10Table[decExponent]) >>> (32 - mantissaShift)); + } + + 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); + if (lowerPos > upperPos) { + decMantissa = (decMantissa / lowerPos) * lowerPos; + } else if (lowerPos < upperPos) { + decMantissa = (decMantissa / upperPos) * upperPos + upperPos; + } else { + decMantissa = ((decMantissa + upperPos / 2) / upperPos) * upperPos; + } + + if (decMantissa >= 1000000000) { + decExponent++; + decMantissa /= 10; + } else if (decMantissa < 100000000) { + decExponent--; + decMantissa *= 10; + } + + result.mantissa = decMantissa; + result.exponent = decExponent - MAX_ABS_DEC_EXP; + } + + private static int findLowerDistanceToZero(int mantissa, int error) { + int pos = 10; + while (pos <= error) { + 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) { + pos *= 10; + } + int mantissaRight = mantissa % pos; + if (pos - mantissaRight > error / 2) { + pos /= 10; + } + return pos; + } + + public static class Result { + public int mantissa; + public int exponent; + public boolean sign; + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/IntegerNativeGenerator.java b/classlib/src/main/java/org/teavm/classlib/java/lang/IntegerNativeGenerator.java new file mode 100644 index 000000000..97c050aaf --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/IntegerNativeGenerator.java @@ -0,0 +1,43 @@ +/* + * Copyright 2018 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.java.lang; + +import java.io.IOException; +import org.teavm.backend.javascript.spi.Injector; +import org.teavm.backend.javascript.spi.InjectorContext; +import org.teavm.model.MethodReference; + +public class IntegerNativeGenerator implements Injector { + @Override + public void generate(InjectorContext context, MethodReference methodRef) throws IOException { + switch (methodRef.getName()) { + case "divideUnsigned": + context.getWriter().append("$rt_udiv("); + context.writeExpr(context.getArgument(0)); + context.getWriter().append(",").ws(); + context.writeExpr(context.getArgument(1)); + context.getWriter().append(")"); + break; + case "remainderUnsigned": + context.getWriter().append("$rt_umod("); + context.writeExpr(context.getArgument(0)); + context.getWriter().append(",").ws(); + context.writeExpr(context.getArgument(1)); + context.getWriter().append(")"); + break; + } + } +} 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 a22294e82..2bb62b779 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 @@ -16,13 +16,12 @@ package org.teavm.classlib.java.lang; import org.teavm.classlib.impl.text.DoubleAnalyzer; +import org.teavm.classlib.impl.text.FloatAnalyzer; import org.teavm.classlib.java.io.TSerializable; import org.teavm.classlib.java.util.TArrays; class TAbstractStringBuilder extends TObject implements TSerializable, TCharSequence { static class Constants { - static float[] powersOfTen = { 1E1f, 1E2f, 1E4f, 1E8f, 1E16f, 1E32f }; - static float[] negPowersOfTen = { 1E-1f, 1E-2f, 1E-4f, 1E-8f, 1E-16f, 1E-32f }; 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, @@ -30,12 +29,8 @@ class TAbstractStringBuilder extends TObject implements TSerializable, TCharSequ 1000000000000000L, 10000000000000000L, 100000000000000000L, 1000000000000000000L }; static final long[] longLogPowersOfTen = { 1, 10, 100, 10000, 100000000, 10000000000000000L, }; - static final int FLOAT_DECIMAL_PRECISION = 7; - static final float FLOAT_DECIMAL_FACTOR = 1E6f; - static final int FLOAT_MAX_EXPONENT = 38; - static final int FLOAT_MAX_POS = 1000000; - static final DoubleAnalyzer.Result doubleAnalysisResult = new DoubleAnalyzer.Result(); + static final FloatAnalyzer.Result floatAnalysisResult = new FloatAnalyzer.Result(); } char[] buffer; @@ -225,54 +220,21 @@ class TAbstractStringBuilder extends TObject implements TSerializable, TCharSequ buffer[target++] = 'y'; return this; } - // Get absolute value - boolean negative = false; + + FloatAnalyzer.Result number = Constants.floatAnalysisResult; + FloatAnalyzer.analyze(value, number); + int mantissa = number.mantissa; + int exp = number.exponent; + boolean negative = number.sign; + int intPart = 1; int sz = 1; // Decimal point always included - if (value < 0) { + if (negative) { negative = true; - value = -value; ++sz; // including '-' sign of mantissa } - // Split into decimal mantissa and decimal exponent - int exp = 0; - int mantissa = 0; - int intPart = 1; - int digits = 0; - if (value >= 1) { - int bit = 32; - exp = 0; - float digit = 1; - for (int i = Constants.powersOfTen.length - 1; i >= 0; --i) { - if ((exp | bit) <= Constants.FLOAT_MAX_EXPONENT && Constants.powersOfTen[i] * digit <= value) { - digit *= Constants.powersOfTen[i]; - exp |= bit; - } - bit >>= 1; - } - mantissa = (int) ((value / (digit / Constants.FLOAT_DECIMAL_FACTOR)) + 0.5f); - } else { - int bit = 32; - exp = 0; - float digit = 1; - for (int i = Constants.negPowersOfTen.length - 1; i >= 0; --i) { - if ((exp | bit) <= 38 && Constants.negPowersOfTen[i] * digit * 10 > value) { - digit *= Constants.negPowersOfTen[i]; - exp |= bit; - } - bit >>= 1; - } - exp = -exp; - mantissa = (int) (((value * Constants.FLOAT_MAX_POS) / digit) + 0.5f); - - while (mantissa >= 10000000) { - mantissa /= 10; - exp--; - } - } - // Remove trailing zeros - digits = Constants.FLOAT_DECIMAL_PRECISION; + int digits = FloatAnalyzer.PRECISION; int zeros = trailingDecimalZeros(mantissa); if (zeros > 0) { digits -= zeros; @@ -312,7 +274,7 @@ class TAbstractStringBuilder extends TObject implements TSerializable, TCharSequ if (negative) { buffer[target++] = '-'; } - int pos = Constants.FLOAT_MAX_POS; + int pos = FloatAnalyzer.MAX_POS; for (int i = 0; i < digits; ++i) { int intDigit; if (pos > 0) { diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TInteger.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TInteger.java index aa4cc04ce..edc150018 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TInteger.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TInteger.java @@ -16,6 +16,7 @@ package org.teavm.classlib.java.lang; import static org.teavm.classlib.impl.IntegerUtil.toUnsignedLogRadixString; +import org.teavm.backend.javascript.spi.InjectedBy; public class TInteger extends TNumber implements TComparable { public static final int SIZE = 32; @@ -353,4 +354,10 @@ public class TInteger extends TNumber implements TComparable { public static int signum(int i) { return (i >> 31) | (-i >>> 31); } + + @InjectedBy(IntegerNativeGenerator.class) + public static native int divideUnsigned(int dividend, int divisor); + + @InjectedBy(IntegerNativeGenerator.class) + public static native int remainderUnsigned(int dividend, int divisor); } diff --git a/core/src/main/java/org/teavm/backend/c/CTarget.java b/core/src/main/java/org/teavm/backend/c/CTarget.java index 52d1c8e79..b3c9473b6 100644 --- a/core/src/main/java/org/teavm/backend/c/CTarget.java +++ b/core/src/main/java/org/teavm/backend/c/CTarget.java @@ -47,6 +47,7 @@ import org.teavm.backend.c.intrinsic.AllocatorIntrinsic; import org.teavm.backend.c.intrinsic.ExceptionHandlingIntrinsic; import org.teavm.backend.c.intrinsic.FunctionIntrinsic; import org.teavm.backend.c.intrinsic.GCIntrinsic; +import org.teavm.backend.c.intrinsic.IntegerIntrinsic; import org.teavm.backend.c.intrinsic.Intrinsic; import org.teavm.backend.c.intrinsic.IntrinsicFactory; import org.teavm.backend.c.intrinsic.LongIntrinsic; @@ -236,6 +237,7 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { intrinsics.add(new FunctionIntrinsic(characteristics, exportDependencyListener.getResolvedMethods())); intrinsics.add(new RuntimeClassIntrinsic()); intrinsics.add(new LongIntrinsic()); + intrinsics.add(new IntegerIntrinsic()); List generators = new ArrayList<>(); generators.add(new ArrayGenerator()); diff --git a/core/src/main/java/org/teavm/backend/c/intrinsic/IntegerIntrinsic.java b/core/src/main/java/org/teavm/backend/c/intrinsic/IntegerIntrinsic.java new file mode 100644 index 000000000..c48df6035 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/c/intrinsic/IntegerIntrinsic.java @@ -0,0 +1,55 @@ +/* + * Copyright 2018 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.backend.c.intrinsic; + +import org.teavm.ast.InvocationExpr; +import org.teavm.model.MethodReference; + +public class IntegerIntrinsic implements Intrinsic { + @Override + public boolean canHandle(MethodReference method) { + if (!method.getClassName().equals(Integer.class.getName())) { + return false; + } + switch (method.getName()) { + case "divideUnsigned": + case "remainderUnsigned": + return true; + default: + return false; + } + } + + @Override + public void apply(IntrinsicContext context, InvocationExpr invocation) { + switch (invocation.getMethod().getName()) { + case "divideUnsigned": + writeBinary(context, invocation, "/"); + break; + case "remainderUnsigned": + writeBinary(context, invocation, "%"); + break; + } + } + + private void writeBinary(IntrinsicContext context, InvocationExpr invocation, String operation) { + context.writer().print("((int32_t) ((uint32_t) "); + context.emit(invocation.getArguments().get(0)); + context.writer().print(" " + operation + " (uint32_t) "); + context.emit(invocation.getArguments().get(1)); + context.writer().print("))"); + } +} diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/StatementRenderer.java b/core/src/main/java/org/teavm/backend/javascript/rendering/StatementRenderer.java index cb57946f6..7dd517ed0 100644 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/StatementRenderer.java +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/StatementRenderer.java @@ -694,7 +694,12 @@ public class StatementRenderer implements ExprVisitor, StatementVisitor { visitBinary(expr, "-", expr.getType() == OperationType.INT); break; case MULTIPLY: - visitBinary(expr, "*", expr.getType() == OperationType.INT); + if (expr.getType() != OperationType.INT || isSmallInteger(expr.getFirstOperand()) + || isSmallInteger(expr.getSecondOperand())) { + visitBinary(expr, "*", expr.getType() == OperationType.INT); + } else { + visitBinaryFunction(expr, naming.getNameForFunction("$rt_imul")); + } break; case DIVIDE: visitBinary(expr, "/", expr.getType() == OperationType.INT); @@ -759,6 +764,20 @@ public class StatementRenderer implements ExprVisitor, StatementVisitor { } } + private static boolean isSmallInteger(Expr expr) { + if (!(expr instanceof ConstantExpr)) { + return false; + } + + Object constant = ((ConstantExpr) expr).getValue(); + if (!(constant instanceof Integer)) { + return false; + } + + int value = (Integer) constant; + return Math.abs(value) < (1 << 18); + } + @Override public void visit(UnaryExpr expr) { try { diff --git a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java index d4bc95174..15500d784 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java @@ -49,6 +49,7 @@ import org.teavm.backend.wasm.intrinsics.ExceptionHandlingIntrinsic; import org.teavm.backend.wasm.intrinsics.FloatIntrinsic; import org.teavm.backend.wasm.intrinsics.FunctionIntrinsic; import org.teavm.backend.wasm.intrinsics.GCIntrinsic; +import org.teavm.backend.wasm.intrinsics.IntegerIntrinsic; import org.teavm.backend.wasm.intrinsics.LongIntrinsic; import org.teavm.backend.wasm.intrinsics.MutatorIntrinsic; import org.teavm.backend.wasm.intrinsics.ObjectIntrinsic; @@ -345,6 +346,7 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { context.addIntrinsic(new FloatIntrinsic()); context.addIntrinsic(new DoubleIntrinsic()); context.addIntrinsic(new LongIntrinsic()); + context.addIntrinsic(new IntegerIntrinsic()); context.addIntrinsic(new ObjectIntrinsic()); context.addGenerator(new ArrayGenerator()); diff --git a/core/src/main/java/org/teavm/backend/wasm/intrinsics/IntegerIntrinsic.java b/core/src/main/java/org/teavm/backend/wasm/intrinsics/IntegerIntrinsic.java new file mode 100644 index 000000000..3e4a91c1e --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/intrinsics/IntegerIntrinsic.java @@ -0,0 +1,56 @@ +/* + * Copyright 2018 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.backend.wasm.intrinsics; + +import org.teavm.ast.InvocationExpr; +import org.teavm.backend.wasm.model.expression.WasmExpression; +import org.teavm.backend.wasm.model.expression.WasmIntBinary; +import org.teavm.backend.wasm.model.expression.WasmIntBinaryOperation; +import org.teavm.backend.wasm.model.expression.WasmIntType; +import org.teavm.model.MethodReference; + +public class IntegerIntrinsic implements WasmIntrinsic { + @Override + public boolean isApplicable(MethodReference methodReference) { + if (!methodReference.getClassName().equals(Integer.class.getName())) { + return false; + } + + switch (methodReference.getName()) { + case "divideUnsigned": + case "remainderUnsigned": + return true; + default: + return false; + } + } + + @Override + public WasmExpression apply(InvocationExpr invocation, WasmIntrinsicManager manager) { + switch (invocation.getMethod().getName()) { + case "divideUnsigned": + return new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.DIV_UNSIGNED, + manager.generate(invocation.getArguments().get(0)), + manager.generate(invocation.getArguments().get(1))); + case "remainderUnsigned": + return new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.REM_UNSIGNED, + manager.generate(invocation.getArguments().get(0)), + manager.generate(invocation.getArguments().get(1))); + default: + throw new AssertionError(); + } + } +} diff --git a/core/src/main/resources/org/teavm/backend/javascript/runtime.js b/core/src/main/resources/org/teavm/backend/javascript/runtime.js index 1579c5f23..3dae31179 100644 --- a/core/src/main/resources/org/teavm/backend/javascript/runtime.js +++ b/core/src/main/resources/org/teavm/backend/javascript/runtime.js @@ -623,3 +623,29 @@ function Long_toNumber(val) { } return 0x100000000 * hi + lo; } + +var $rt_imul = Math.imul || function(a, b) { + var ah = (a >>> 16) & 0xFFFF; + var al = a & 0xFFFF; + var bh = (b >>> 16) & 0xFFFF; + var bl = b & 0xFFFF; + return (al * bl + (((ah * bl + al * bh) << 16) >>> 0)) | 0; +}; +var $rt_udiv = function(a, b) { + if (a < 0) { + a += 0x100000000; + } + if (b < 0) { + b += 0x100000000; + } + return (a / b) | 0; +}; +var $rt_umod = function(a, b) { + if (a < 0) { + a += 0x100000000; + } + if (b < 0) { + b += 0x100000000; + } + return (a % b) | 0; +}; \ No newline at end of file diff --git a/tests/src/test/java/org/teavm/classlib/java/lang/StringBuilderTest.java b/tests/src/test/java/org/teavm/classlib/java/lang/StringBuilderTest.java index a15f155bb..dc95c9f9f 100644 --- a/tests/src/test/java/org/teavm/classlib/java/lang/StringBuilderTest.java +++ b/tests/src/test/java/org/teavm/classlib/java/lang/StringBuilderTest.java @@ -164,8 +164,8 @@ public class StringBuilderTest { @Test public void minFloatAppended() { StringBuilder sb = new StringBuilder(); - sb.append(1.175494E-38f); - assertEquals("1.175494E-38", sb.toString()); + sb.append(1.17549E-38f); + assertEquals("1.17549E-38", sb.toString()); } @Test