From 0ffec9da6e0c45a18ff80405a8e6c857a2973b8b Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Wed, 3 Jun 2015 17:51:29 +0400 Subject: [PATCH] Add initial formatting via non-exponential pattern --- .../classlib/java/text/TDecimalFormat.java | 300 +++++++++++++++++- .../classlib/java/text/TNumberFormat.java | 18 +- .../classlib/java/text/DecimalFormatTest.java | 50 ++- 3 files changed, 339 insertions(+), 29 deletions(-) diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDecimalFormat.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDecimalFormat.java index 39ef1f90b..ff3000eab 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDecimalFormat.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDecimalFormat.java @@ -17,6 +17,8 @@ package org.teavm.classlib.java.text; import java.text.DecimalFormatSymbols; import org.teavm.classlib.impl.unicode.CLDRHelper; +import org.teavm.classlib.java.lang.TArithmeticException; +import org.teavm.classlib.java.lang.TString; import org.teavm.classlib.java.util.TLocale; /** @@ -24,6 +26,15 @@ import org.teavm.classlib.java.util.TLocale; * @author Alexey Andreev */ public class TDecimalFormat extends TNumberFormat { + private static final long MANTISSA_PATTERN = 100_0000_0000_0000_0000L; + private static final int MANTISSA_LENGTH = 18; + private static final long[] POW10_ARRAY = { 1, 10, 100, 1000, 1_0000, 1_0_0000, 1_00_0000, + 1_000_0000, 1_0000_0000, 1_0_0000_0000, 1_00_0000_0000L, 1_000_0000_0000L, 1_0000_0000_0000L, + 1_0_0000_0000_0000L, 1_00_0000_0000_0000L, 1_000_0000_0000_0000L, 1_0000_0000_0000_0000L, + 1_0_0000_0000_0000_0000L, 1_00_0000_0000_0000_0000L }; + private static final double[] POW10_FRAC_ARRAY = { 1E1, 1E2, 1E4, 1E8, 1E16, 1E32, 1E64, 1E128, 1E256 }; + private static final double[] POWM10_FRAC_ARRAY = { 1E-1, 1E-2, 1E-4, 1E-8, 1E-16, 1E-32, 1E-64, 1E-128, 1E-256 }; + private static final int DOUBLE_MAX_EXPONENT = 308; TDecimalFormatSymbols symbols; private String positivePrefix; private String negativePrefix; @@ -58,21 +69,6 @@ public class TDecimalFormat extends TNumberFormat { return (DecimalFormatSymbols)symbols.clone(); } - @Override - public StringBuffer format(long value, StringBuffer buffer, TFieldPosition field) { - return null; - } - - @Override - public Number parse(String string, TParsePosition position) { - return null; - } - - @Override - public StringBuffer format(double value, StringBuffer buffer, TFieldPosition field) { - return null; - } - public String getPositivePrefix() { return positivePrefix; } @@ -179,4 +175,278 @@ public class TDecimalFormat extends TNumberFormat { result = result * 31 + exponentDigits; return result; } + + @Override + public Number parse(String string, TParsePosition position) { + return null; + } + + @Override + public StringBuffer format(long value, StringBuffer buffer, TFieldPosition field) { + if (exponentDigits > 0) { + formatExponent(value, buffer); + } else { + formatRegular(value, buffer); + } + return buffer; + } + + @Override + public StringBuffer format(double value, StringBuffer buffer, TFieldPosition field) { + MantissaAndExponent me = getMantissaAndExponent(value); + if (exponentDigits > 0) { + formatExponent(me.mantissa, me.exponent, buffer); + } else { + formatRegular(me.mantissa, me.exponent, buffer); + } + return buffer; + } + + private void formatExponent(long value, StringBuffer buffer) { + int exponent = fastLn10(Math.abs(value)); + value = normalize(value); + formatExponent(value, exponent, buffer); + } + + private void formatRegular(long value, StringBuffer buffer) { + int exponent = fastLn10(Math.abs(value)); + value = normalize(value); + formatRegular(value, exponent, buffer); + } + + private void formatExponent(long mantissa, int exponent, StringBuffer buffer) { + } + + private void formatRegular(long mantissa, int exponent, StringBuffer buffer) { + // Make mantissa positive + boolean positive; + if (mantissa >= 0) { + positive = true; + } else { + positive = false; + mantissa = -mantissa; + } + + ++exponent; + + // Apply rounding if necessary + int roundingPos = exponent + getMinimumFractionDigits() - 1; + if (roundingPos < 0) { + mantissa = 0; + } else if (roundingPos < MANTISSA_LENGTH) { + mantissa = applyRounding(mantissa, roundingPos, positive); + } + + // Append pattern prefix + buffer.append(positive ? positivePrefix : negativePrefix); + + // Add insignificant integer zeroes + int digitPos = exponent; + int intLength = Math.max(0, exponent); + for (int i = getMinimumIntegerDigits() - 1; i >= intLength; --i) { + buffer.append('0'); + if (groupingSize > 0 && digitPos % groupingSize == 0 && digitPos > 0) { + buffer.append(symbols.getGroupingSeparator()); + } + --digitPos; + } + + // Add significant integer digits + int significantIntDigits = Math.min(MANTISSA_LENGTH, intLength); + int mantissaDigit = MANTISSA_LENGTH; + for (int i = 0; i < significantIntDigits; ++i) { + long mantissaDigitMask = POW10_ARRAY[mantissaDigit]; + buffer.append(Character.forDigit((int)(mantissa / mantissaDigitMask), 10)); + mantissa %= mantissaDigitMask; + --mantissaDigit; + if (groupingSize > 0 && digitPos % groupingSize == 0 && digitPos > 0) { + buffer.append(symbols.getGroupingSeparator()); + } + --digitPos; + } + + // Add significant integer zeroes + intLength -= significantIntDigits; + for (int i = 0; i < intLength; ++i) { + buffer.append('0'); + if (groupingSize > 0 && digitPos % groupingSize == 0 && digitPos > 0) { + buffer.append(symbols.getGroupingSeparator()); + } + --digitPos; + } + + if (digitPos == 0 && getMinimumFractionDigits() == 0) { + if (isDecimalSeparatorAlwaysShown()) { + buffer.append(symbols.getDecimalSeparator()); + } + } else { + buffer.append(symbols.getDecimalSeparator()); + + // Add significant fractional digits + int significantFracDigits = Math.min(getMinimumFractionDigits(), MANTISSA_LENGTH - significantIntDigits); + digitPos = 0; + for (int i = 0; i < significantFracDigits; ++i) { + if (groupingSize > 0 && digitPos % groupingSize == 0 && digitPos > 0) { + buffer.append(symbols.getGroupingSeparator()); + } + ++digitPos; + long mantissaDigitMask = POW10_ARRAY[mantissaDigit]; + buffer.append(Character.forDigit((int)(mantissa / mantissaDigitMask), 10)); + mantissa %= mantissaDigitMask; + mantissaDigit--; + } + + // Add insignificant integer zeroes + for (int i = significantFracDigits; i < getMinimumFractionDigits(); ++i) { + if (groupingSize > 0 && digitPos % groupingSize == 0 && digitPos > 0) { + buffer.append(symbols.getGroupingSeparator()); + } + ++digitPos; + buffer.append('0'); + } + } + + // Add suffix + if (positive) { + buffer.append(positiveSuffix != null ? positiveSuffix : ""); + } else { + buffer.append(negativeSuffix != null ? negativeSuffix : positiveSuffix != null ? positiveSuffix : ""); + } + } + + private long applyRounding(long mantissa, int exponent, boolean positive) { + long rounding = POW10_ARRAY[MANTISSA_LENGTH - exponent]; + switch (getRoundingMode()) { + case CEILING: + mantissa = (mantissa / rounding) * rounding; + if (positive) { + mantissa += rounding; + } + break; + case FLOOR: + mantissa = (mantissa / rounding) * rounding; + if (!positive) { + mantissa += rounding; + } + break; + case UP: + mantissa = (mantissa / rounding) * rounding + rounding; + break; + case DOWN: + mantissa = (mantissa / rounding) * rounding; + break; + case UNNECESSARY: + if (mantissa % rounding != 0) { + throw new TArithmeticException(TString.wrap("Can't avoid rounding")); + } + break; + case HALF_DOWN: + mantissa = ((mantissa + rounding / 2 - 1) / rounding) * rounding; + break; + case HALF_UP: + mantissa = ((mantissa + rounding / 2 + 1) / rounding) * rounding; + break; + case HALF_EVEN: { + int digit = (int)((mantissa / rounding) % 10); + int delta = digit % 2 == 0 ? -1 : 1; + mantissa = ((mantissa + rounding / 2 + delta) / rounding) * rounding; + break; + } + } + return mantissa; + } + + private int fastLn10(long value) { + int result = 0; + if (value >= 1_0000_0000_0000_0000L) { + result += 16; + value /= 1_0000_0000_0000_0000L; + } + if (value >= 1_0000_0000L) { + result += 8; + value /= 1_0000_0000L; + } + if (value >= 1_0000L) { + result += 4; + value /= 1_0000L; + } + if (value >= 100L) { + result += 2; + value /= 100L; + } + if (value >= 10L) { + result += 1; + value /= 10L; + } + return result; + } + + private long normalize(long value) { + if (value < MANTISSA_PATTERN / 1_000_0000_0000_0000L) { + value *= 1_0000_0000_0000_0000L; + } + if (value < MANTISSA_PATTERN / 1_000_0000L) { + value *= 1_0000_0000L; + } + if (value < MANTISSA_PATTERN / 1000L) { + value *= 1_0000L; + } + if (value < MANTISSA_PATTERN / 10L) { + value *= 100L; + } + if (value < MANTISSA_PATTERN / 1L) { + value *= 10L; + } + return value; + } + + private MantissaAndExponent getMantissaAndExponent(double value) { + int exp = 0; + long mantissa = 0; + boolean positive; + if (value >= 0) { + positive = true; + } else { + positive = false; + mantissa = -mantissa; + } + if (value >= 1) { + int bit = 256; + exp = 0; + double digit = 1; + for (int i = POW10_FRAC_ARRAY.length - 1; i >= 0; --i) { + if ((exp | bit) <= DOUBLE_MAX_EXPONENT && POW10_FRAC_ARRAY[i] * digit <= value) { + digit *= POW10_FRAC_ARRAY[i]; + exp |= bit; + } + bit >>= 1; + } + mantissa = (long)(((value / digit) * MANTISSA_PATTERN) + 0.5); + } else { + int bit = 256; + exp = 0; + double digit = 1; + for (int i = POWM10_FRAC_ARRAY.length - 1; i >= 0; --i) { + if ((exp | bit) <= DOUBLE_MAX_EXPONENT && POWM10_FRAC_ARRAY[i] * digit * 10 > value) { + digit *= POWM10_FRAC_ARRAY[i]; + exp |= bit; + } + bit >>= 1; + } + exp = -exp; + mantissa = (long)(((value * MANTISSA_PATTERN) / digit) + 0.5); + } + mantissa = ((mantissa + 500) / 1000) * 1000; + return new MantissaAndExponent(positive ? mantissa : -mantissa, exp); + } + + static class MantissaAndExponent { + long mantissa; + int exponent; + + public MantissaAndExponent(long mantissa, int exponent) { + this.mantissa = mantissa; + this.exponent = exponent; + } + } } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TNumberFormat.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TNumberFormat.java index 545185fe2..cd5680361 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TNumberFormat.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TNumberFormat.java @@ -16,6 +16,7 @@ package org.teavm.classlib.java.text; import org.teavm.classlib.impl.unicode.CLDRHelper; +import org.teavm.classlib.java.math.TRoundingMode; import org.teavm.classlib.java.util.TLocale; /** @@ -25,9 +26,10 @@ import org.teavm.classlib.java.util.TLocale; public abstract class TNumberFormat extends TFormat { public static final int INTEGER_FIELD = 0; public static final int FRACTION_FIELD = 1; - private boolean groupingUsed = true, parseIntegerOnly = false; + private boolean groupingUsed = true, parseIntegerOnly; private int maximumIntegerDigits = 40, minimumIntegerDigits = 1, maximumFractionDigits = 3, minimumFractionDigits = 0; + private TRoundingMode roundingMode = TRoundingMode.HALF_EVEN; public TNumberFormat() { } @@ -51,7 +53,8 @@ public abstract class TNumberFormat extends TFormat { && maximumFractionDigits == obj.maximumFractionDigits && maximumIntegerDigits == obj.maximumIntegerDigits && minimumFractionDigits == obj.minimumFractionDigits - && minimumIntegerDigits == obj.minimumIntegerDigits; + && minimumIntegerDigits == obj.minimumIntegerDigits + && roundingMode == obj.roundingMode; } public final String format(double value) { @@ -140,7 +143,8 @@ public abstract class TNumberFormat extends TFormat { public int hashCode() { return (groupingUsed ? 1231 : 1237) + (parseIntegerOnly ? 1231 : 1237) + maximumFractionDigits + maximumIntegerDigits - + minimumFractionDigits + minimumIntegerDigits; + + minimumFractionDigits + minimumIntegerDigits + + roundingMode.hashCode(); } public boolean isGroupingUsed() { @@ -211,6 +215,14 @@ public abstract class TNumberFormat extends TFormat { parseIntegerOnly = value; } + public TRoundingMode getRoundingMode() { + return roundingMode; + } + + public void setRoundingMode(TRoundingMode roundingMode) { + this.roundingMode = roundingMode; + } + public static class Field extends TFormat.Field { public static final Field SIGN = new Field("sign"); public static final Field INTEGER = new Field("integer"); diff --git a/teavm-tests/src/test/java/org/teavm/classlib/java/text/DecimalFormatTest.java b/teavm-tests/src/test/java/org/teavm/classlib/java/text/DecimalFormatTest.java index 6ec6ee754..682fb5b43 100644 --- a/teavm-tests/src/test/java/org/teavm/classlib/java/text/DecimalFormatTest.java +++ b/teavm-tests/src/test/java/org/teavm/classlib/java/text/DecimalFormatTest.java @@ -11,9 +11,11 @@ import org.junit.Test; * @author Alexey Andreev */ public class DecimalFormatTest { + private static DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.ENGLISH); + @Test public void parsesIntegerPattern() { - DecimalFormat format = new DecimalFormat("00"); + DecimalFormat format = createFormat("00"); assertEquals(2, format.getMinimumIntegerDigits()); assertFalse(format.isDecimalSeparatorAlwaysShown()); assertFalse(format.isGroupingUsed()); @@ -21,7 +23,7 @@ public class DecimalFormatTest { assertEquals(0, format.getMinimumFractionDigits()); assertEquals(0, format.getMaximumFractionDigits()); - format = new DecimalFormat("##"); + format = createFormat("##"); assertEquals(0, format.getMinimumIntegerDigits()); assertFalse(format.isDecimalSeparatorAlwaysShown()); assertFalse(format.isGroupingUsed()); @@ -29,7 +31,7 @@ public class DecimalFormatTest { assertEquals(0, format.getMinimumFractionDigits()); assertEquals(0, format.getMaximumFractionDigits()); - format = new DecimalFormat("#,##0"); + format = createFormat("#,##0"); assertEquals(1, format.getMinimumIntegerDigits()); assertFalse(format.isDecimalSeparatorAlwaysShown()); assertTrue(format.isGroupingUsed()); @@ -48,14 +50,14 @@ public class DecimalFormatTest { @Test public void parsesPrefixAndSuffixInPattern() { - DecimalFormat format = new DecimalFormat("(00)", new DecimalFormatSymbols(Locale.ENGLISH)); + DecimalFormat format = createFormat("(00)"); assertEquals(2, format.getMinimumIntegerDigits()); assertEquals("(", format.getPositivePrefix()); assertEquals(")", format.getPositiveSuffix()); assertEquals("-(", format.getNegativePrefix()); assertEquals(")", format.getNegativeSuffix()); - format = new DecimalFormat("+(00);-{#}", new DecimalFormatSymbols(Locale.ENGLISH)); + format = createFormat("+(00);-{#}"); assertEquals(2, format.getMinimumIntegerDigits()); assertEquals("+(", format.getPositivePrefix()); assertEquals(")", format.getPositiveSuffix()); @@ -64,7 +66,7 @@ public class DecimalFormatTest { @Test public void parsesFractionalPattern() { - DecimalFormat format = new DecimalFormat("#."); + DecimalFormat format = createFormat("#."); assertEquals(1, format.getMinimumIntegerDigits()); assertTrue(format.isDecimalSeparatorAlwaysShown()); assertFalse(format.isGroupingUsed()); @@ -72,28 +74,28 @@ public class DecimalFormatTest { assertEquals(0, format.getMinimumFractionDigits()); assertEquals(0, format.getMaximumFractionDigits()); - format = new DecimalFormat("#.00"); + format = createFormat("#.00"); assertEquals(0, format.getMinimumIntegerDigits()); assertFalse(format.isGroupingUsed()); assertEquals(0, format.getGroupingSize()); assertEquals(2, format.getMinimumFractionDigits()); assertEquals(2, format.getMaximumFractionDigits()); - format = new DecimalFormat("#.00##"); + format = createFormat("#.00##"); assertEquals(0, format.getMinimumIntegerDigits()); assertFalse(format.isGroupingUsed()); assertEquals(0, format.getGroupingSize()); assertEquals(2, format.getMinimumFractionDigits()); assertEquals(4, format.getMaximumFractionDigits()); - format = new DecimalFormat("#00.00##"); + format = createFormat("#00.00##"); assertEquals(2, format.getMinimumIntegerDigits()); assertFalse(format.isGroupingUsed()); assertEquals(0, format.getGroupingSize()); assertEquals(2, format.getMinimumFractionDigits()); assertEquals(4, format.getMaximumFractionDigits()); - format = new DecimalFormat("#,#00.00##"); + format = createFormat("#,#00.00##"); assertEquals(2, format.getMinimumIntegerDigits()); assertTrue(format.isGroupingUsed()); assertEquals(3, format.getGroupingSize()); @@ -103,10 +105,36 @@ public class DecimalFormatTest { @Test public void parsesExponentialPattern() { - DecimalFormat format = new DecimalFormat("##0E00"); + DecimalFormat format = createFormat("##0E00"); assertEquals(1, format.getMinimumIntegerDigits()); assertEquals(0, format.getGroupingSize()); assertEquals(0, format.getMinimumFractionDigits()); assertEquals(0, format.getMaximumFractionDigits()); } + + @Test + public void formatsNumber() { + DecimalFormat format = createFormat("0.0"); + assertEquals("23.0", format.format(23)); + assertEquals("23.2", format.format(23.2)); + assertEquals("23.2", format.format(23.23)); + assertEquals("23.3", format.format(23.27)); + assertEquals("0.0", format.format(0.0001)); + + format = createFormat("00000000000000000000000000.0"); + assertEquals("00000000000000000000000023.0", format.format(23)); + assertEquals("00002300000000000000000000.0", format.format(23E20)); + assertEquals("23000000000000000000000000.0", format.format(23E24)); + + format = createFormat("0.00000000000000000000000000"); + assertEquals("23.00000000000000000000000000", format.format(23)); + assertEquals("0.23000000000000000000000000", format.format(0.23)); + assertEquals("0.00000000000000000000230000", format.format(23E-22)); + assertEquals("0.00000000000000000000000023", format.format(23E-26)); + } + + + private DecimalFormat createFormat(String format) { + return new DecimalFormat(format, symbols); + } }