mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 16:14:10 -08:00
Add initial formatting via non-exponential pattern
This commit is contained in:
parent
abd9c10e2b
commit
0ffec9da6e
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user