diff --git a/teavm-classlib/pom.xml b/teavm-classlib/pom.xml index 845a04d64..e242077f1 100644 --- a/teavm-classlib/pom.xml +++ b/teavm-classlib/pom.xml @@ -37,7 +37,7 @@ process-test-classes - true + false diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/DoubleNativeGenerator.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/DoubleNativeGenerator.java new file mode 100644 index 000000000..fdafa5fa2 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/DoubleNativeGenerator.java @@ -0,0 +1,35 @@ +package org.teavm.classlib.java.lang; + +import java.io.IOException; +import org.teavm.codegen.SourceWriter; +import org.teavm.javascript.ni.Generator; +import org.teavm.javascript.ni.GeneratorContext; +import org.teavm.model.MethodReference; + +/** + * + * @author Alexey Andreev + */ +public class DoubleNativeGenerator implements Generator { + @Override + public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException { + switch (methodRef.getName()) { + case "isNaN": + generateIsNaN(context, writer); + break; + case "isInfinite": + generateIsInfinite(context, writer); + break; + } + } + + private void generateIsNaN(GeneratorContext context, SourceWriter writer) throws IOException { + writer.append("return (isNaN(").append(context.getParameterName(1)).append(")").ws().append("?") + .ws().append("1").ws().append(":").ws().append("0").ws().append(");").softNewLine(); + } + + private void generateIsInfinite(GeneratorContext context, SourceWriter writer) throws IOException { + writer.append("return (isFinite(").append(context.getParameterName(1)).append(")").ws().append("?") + .ws().append("0").ws().append(":").ws().append("1").append(");").softNewLine(); + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TAbstractStringBuilder.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TAbstractStringBuilder.java index 161dd5b9b..59471d8c5 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TAbstractStringBuilder.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TAbstractStringBuilder.java @@ -12,13 +12,24 @@ import org.teavm.javascript.ni.Rename; */ class TAbstractStringBuilder extends TObject implements TSerializable, TCharSequence { private static final float[] powersOfTen = { 1E1f, 1E2f, 1E4f, 1E8f, 1E16f, 1E32f }; + private static final double[] doublePowersOfTen = { 1E1, 1E2, 1E4, 1E8, 1E16, 1E32, 1E64, 1E128, 1E256 }; private static final float[] negPowersOfTen = { 1E-1f, 1E-2f, 1E-4f, 1E-8f, 1E-16f, 1E-32f }; + private static final double[] negDoublePowersOfTen = { 1E-1, 1E-2, 1E-4, 1E-8, 1E-16, 1E-32, + 1E-64, 1E-128, 1E-256 }; private static final int[] intPowersOfTen = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; + private static final long[] longPowersOfTen = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, + 1000000000, 10000000000L, 100000000000L, 1000000000000L, 10000000000000L, 100000000000000L, + 1000000000000000L, 10000000000000000L, 100000000000000000L, 1000000000000000000L }; + private static final long[] longLogPowersOfTen = { 1, 10, 100, 10000, 100000000, 10000000000000000L, }; private static final int FLOAT_DECIMAL_PRECISION = 7; + private static final int DOUBLE_DECIMAL_PRECISION = 16; private static final float FLOAT_DECIMAL_FACTOR = 1E6f; + private static final double DOUBLE_DECIMAL_FACTOR = 1E15f; private static final int FLOAT_MAX_EXPONENT = 38; + private static final int DOUBLE_MAX_EXPONENT = 308; private static final int FLOAT_MAX_POS = 1000000; + private static final long DOUBLE_MAX_POS = 1000000000000000L; char[] buffer; int length; @@ -128,13 +139,13 @@ class TAbstractStringBuilder extends TObject implements TSerializable, TCharSequ buffer[length++] = '.'; buffer[length++] = '0'; return this; - } else if (Float.isNaN(value)) { + } else if (TFloat.isNaN(value)) { ensureCapacity(length + 3); buffer[length++] = 'N'; buffer[length++] = 'a'; buffer[length++] = 'N'; return this; - } else if (Float.isInfinite(value)) { + } else if (TFloat.isInfinite(value)) { if (value > 0) { ensureCapacity(8); } else { @@ -237,7 +248,6 @@ class TAbstractStringBuilder extends TObject implements TSerializable, TCharSequ intDigit = 0; } buffer[length++] = (char)('0' + intDigit); - value = (value - intDigit) * 10; if (--intPart == 0) { buffer[length++] = '.'; } @@ -259,6 +269,157 @@ class TAbstractStringBuilder extends TObject implements TSerializable, TCharSequ return this; } + protected TAbstractStringBuilder append(double value) { + if (value == 0) { + ensureCapacity(length + 3); + buffer[length++] = '0'; + buffer[length++] = '.'; + buffer[length++] = '0'; + return this; + } else if (value == -0) { + ensureCapacity(length + 4); + buffer[length++] = '-'; + buffer[length++] = '0'; + buffer[length++] = '.'; + buffer[length++] = '0'; + return this; + } else if (TDouble.isNaN(value)) { + ensureCapacity(length + 3); + buffer[length++] = 'N'; + buffer[length++] = 'a'; + buffer[length++] = 'N'; + return this; + } else if (TDouble.isInfinite(value)) { + if (value > 0) { + ensureCapacity(8); + } else { + ensureCapacity(9); + buffer[length++] = '-'; + } + buffer[length++] = 'I'; + buffer[length++] = 'n'; + buffer[length++] = 'f'; + buffer[length++] = 'i'; + buffer[length++] = 'n'; + buffer[length++] = 'i'; + buffer[length++] = 't'; + buffer[length++] = 'y'; + return this; + } + // Get absolute value + boolean negative = false; + int sz = 1; // Decimal point always included + if (value < 0) { + negative = true; + value = -value; + ++sz; // including '-' sign of mantissa + } + + // Split into decimal mantissa and decimal exponent + int exp = 0; + long mantissa = 0; + int intPart = 1; + int digits = 0; + if (value >= 1) { + int bit = 256; + exp = 0; + double digit = 1; + for (int i = doublePowersOfTen.length - 1; i >= 0; --i) { + if ((exp | bit) <= DOUBLE_MAX_EXPONENT && doublePowersOfTen[i] * digit < value) { + digit *= doublePowersOfTen[i]; + exp |= bit; + } + bit >>= 1; + } + mantissa = (long)((value / (digit / DOUBLE_DECIMAL_FACTOR)) + 0.5f); + } else { + ++sz; + int bit = 256; + exp = 0; + double digit = 1; + for (int i = negDoublePowersOfTen.length - 1; i >= 0; --i) { + if ((exp | bit) <= DOUBLE_MAX_EXPONENT && negDoublePowersOfTen[i] * digit * 10 > value) { + digit *= negPowersOfTen[i]; + exp |= bit; + } + bit >>= 1; + } + exp = -exp; + mantissa = (long)(((value * DOUBLE_MAX_POS) / digit) + 0.5f); + } + + // Remove trailing zeros + digits = DOUBLE_DECIMAL_PRECISION; + int zeros = trailingDecimalZeros(mantissa); + if (zeros > 0) { + digits -= zeros; + } + + // Handle special case of exponent close to 0 + if (exp < 7 && exp >= -3) { + if (exp >= 0) { + intPart = exp + 1; + digits = Math.max(digits, intPart + 1); + exp = 0; + } else if (exp < 0) { + mantissa /= longPowersOfTen[-exp]; + digits -= exp; + exp = 0; + } + } + sz += digits; + + // Extend buffer to store exponent + if (exp != 0) { + sz += 2; + if (exp < 10 || exp > 10) { + ++sz; + } + if (exp < 100 || exp > 100) { + ++sz; + } + } + + // Print mantissa + ensureCapacity(length + sz); + if (negative) { + buffer[length++] = '-'; + } + long pos = DOUBLE_MAX_POS; + for (int i = 0; i < digits; ++i) { + int intDigit; + if (pos > 0) { + intDigit = (int)(mantissa / pos); + mantissa %= pos; + } else { + intDigit = 0; + } + buffer[length++] = (char)('0' + intDigit); + if (--intPart == 0) { + buffer[length++] = '.'; + } + pos /= 10; + } + + // Print exponent + if (exp != 0) { + buffer[length++] = 'E'; + if (exp < 0) { + exp = -exp; + buffer[length++] = '-'; + } + if (exp > 100) { + buffer[length++] = (char)('0' + exp / 100); + exp %= 100; + } + if (exp > 10) { + buffer[length++] = (char)('0' + exp / 10); + } + buffer[length++] = (char)('0' + exp % 10); + } + return this; + } + private static int trailingDecimalZeros(int n) { if (n % 1000000000 == 0) { return 9; @@ -283,6 +444,20 @@ class TAbstractStringBuilder extends TObject implements TSerializable, TCharSequ return result; } + private static int trailingDecimalZeros(long n) { + long zeros = 1; + int result = 0; + int bit = 16; + for (int i = longLogPowersOfTen.length - 1; i >= 0; --i) { + if (n % zeros == 0) { + result |= bit; + zeros *= longLogPowersOfTen[i]; + } + bit >>>= 1; + } + return result; + } + protected TAbstractStringBuilder append(char c) { ensureCapacity(length + 1); buffer[length++] = c; diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TDouble.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TDouble.java new file mode 100644 index 000000000..78f4dc2d7 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TDouble.java @@ -0,0 +1,15 @@ +package org.teavm.classlib.java.lang; + +import org.teavm.javascript.ni.GeneratedBy; + +/** + * + * @author Alexey Andreev + */ +public class TDouble { + @GeneratedBy(DoubleNativeGenerator.class) + public static native boolean isNaN(double v); + + @GeneratedBy(DoubleNativeGenerator.class) + public static native boolean isInfinite(double v); +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TStringBuilder.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TStringBuilder.java index 1c06c7634..2b6b854a4 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TStringBuilder.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TStringBuilder.java @@ -29,6 +29,12 @@ public class TStringBuilder extends TAbstractStringBuilder { return this; } + @Override + protected TStringBuilder append(double value) { + super.append(value); + return this; + } + @Override public TStringBuilder append(char c) { super.append(c); diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/lang/StringBuilderTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/lang/StringBuilderTest.java index 11b541a47..a7cb5d611 100644 --- a/teavm-classlib/src/test/java/org/teavm/classlib/java/lang/StringBuilderTest.java +++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/lang/StringBuilderTest.java @@ -190,6 +190,13 @@ public class StringBuilderTest { assertEquals("-Infinity", sb.toString()); } + @Test + public void doubleAppended() { + StringBuilder sb = new StringBuilder(); + sb.append(1.2345678E150); + assertEquals("1.2345678E150", sb.toString()); + } + @Test public void appendsCodePoint() { StringBuilder sb = new StringBuilder();