mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 08:14:09 -08:00
Fix issue in DecimalFormat rounding.
When value near 1 (0.999...) is rounded to 1, exponent should be increased. This fixes #557. Also reuse exponent/mantissa/sign analyzer written for StringBuilder.append(double)
This commit is contained in:
parent
e4c808c324
commit
0ade0313ce
|
@ -19,6 +19,8 @@ import java.math.BigDecimal;
|
|||
import java.math.BigInteger;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.util.Arrays;
|
||||
import org.teavm.classlib.impl.text.DoubleAnalyzer;
|
||||
import org.teavm.classlib.impl.text.FloatAnalyzer;
|
||||
import org.teavm.classlib.impl.unicode.CLDRHelper;
|
||||
import org.teavm.classlib.java.lang.TArithmeticException;
|
||||
import org.teavm.classlib.java.lang.TDouble;
|
||||
|
@ -31,9 +33,6 @@ public class TDecimalFormat extends TNumberFormat {
|
|||
1_0_0000_0000_0000_0000L, 1_00_0000_0000_0000_0000L };
|
||||
private static final int[] POW10_INT_ARRAY = { 1, 10, 100, 1000, 1_0000, 1_0_0000, 1_00_0000,
|
||||
1_000_0000, 1_0000_0000, 1_0_0000_0000 };
|
||||
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;
|
||||
private static final long MAX_LONG_DIV_10 = Long.MAX_VALUE / 10;
|
||||
TDecimalFormatSymbols symbols;
|
||||
FormatField[] positivePrefix = {};
|
||||
|
@ -550,28 +549,30 @@ public class TDecimalFormat extends TNumberFormat {
|
|||
fieldsToText(value > 0 ? positivePrefix : negativePrefix, buffer).append(symbols.getInfinity());
|
||||
appendSuffix(value > 0, buffer);
|
||||
} else {
|
||||
MantissaAndExponent me = getMantissaAndExponent(value);
|
||||
DoubleAnalyzer.Result analysisResult = Constants.doubleAnalysisResult;
|
||||
DoubleAnalyzer.analyze(value, analysisResult);
|
||||
if (exponentDigits > 0) {
|
||||
formatExponent(me.mantissa, me.exponent, buffer);
|
||||
formatExponent(analysisResult.mantissa, analysisResult.exponent, !analysisResult.sign, buffer);
|
||||
} else {
|
||||
formatRegular(me.mantissa, me.exponent, buffer);
|
||||
formatRegular(analysisResult.mantissa, analysisResult.exponent, !analysisResult.sign, buffer);
|
||||
}
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private void formatExponent(long value, StringBuffer buffer) {
|
||||
int exponent = fastLn10(Math.abs(value));
|
||||
formatExponent(value, exponent, buffer);
|
||||
long absValue = Math.abs(value);
|
||||
int exponent = fastLn10(absValue);
|
||||
formatExponent(absValue, exponent, value >= 0, buffer);
|
||||
}
|
||||
|
||||
private void formatRegular(long value, StringBuffer buffer) {
|
||||
int exponent = fastLn10(Math.abs(value));
|
||||
formatRegular(value, exponent, buffer);
|
||||
long absValue = Math.abs(value);
|
||||
int exponent = fastLn10(absValue);
|
||||
formatRegular(absValue, exponent, value >= 0, buffer);
|
||||
}
|
||||
|
||||
private void formatExponent(long mantissa, int exponent, StringBuffer buffer) {
|
||||
boolean positive = mantissa >= 0;
|
||||
private void formatExponent(long mantissa, int exponent, boolean sign, StringBuffer buffer) {
|
||||
int visibleExponent = fastLn10(mantissa);
|
||||
int mantissaLength = visibleExponent + 1;
|
||||
|
||||
|
@ -580,12 +581,11 @@ public class TDecimalFormat extends TNumberFormat {
|
|||
int tenMultiplier = POW10_INT_ARRAY[multiplierDigits];
|
||||
if (tenMultiplier == multiplier) {
|
||||
exponent += multiplierDigits;
|
||||
} else if (mantissa >= Long.MAX_VALUE / multiplier || mantissa <= Long.MIN_VALUE / multiplier) {
|
||||
} else if (mantissa >= Long.MAX_VALUE / multiplier) {
|
||||
formatExponent(new BigDecimal(BigInteger.valueOf(mantissa), visibleExponent - exponent), buffer);
|
||||
return;
|
||||
} else {
|
||||
mantissa *= multiplier;
|
||||
positive = mantissa >= 0;
|
||||
visibleExponent = fastLn10(mantissa);
|
||||
mantissaLength = visibleExponent + 1;
|
||||
}
|
||||
|
@ -605,16 +605,22 @@ public class TDecimalFormat extends TNumberFormat {
|
|||
if (significantSize < 0) {
|
||||
mantissa = 0;
|
||||
} else if (significantSize < mantissaLength) {
|
||||
mantissa = applyRounding(mantissa, mantissaLength, significantSize);
|
||||
mantissa = applyRounding(mantissa, mantissaLength, significantSize, sign);
|
||||
int newMantissaLength = fastLn10(mantissa) + 1;
|
||||
if (newMantissaLength > mantissaLength) {
|
||||
mantissaLength = newMantissaLength;
|
||||
++exponent;
|
||||
visibleExponent++;
|
||||
}
|
||||
}
|
||||
|
||||
// Append pattern prefix
|
||||
fieldsToText(positive ? positivePrefix : negativePrefix, buffer);
|
||||
fieldsToText(sign ? positivePrefix : negativePrefix, buffer);
|
||||
|
||||
int exponentPos = Math.max(visibleExponent, 0);
|
||||
for (int i = mantissaLength - 1; i >= exponentPos; --i) {
|
||||
long mantissaDigitMask = POW10_ARRAY[i];
|
||||
buffer.append(forDigit(Math.abs((int) (mantissa / mantissaDigitMask))));
|
||||
buffer.append(forDigit((int) (mantissa / mantissaDigitMask)));
|
||||
mantissa %= mantissaDigitMask;
|
||||
}
|
||||
for (int i = exponentPos - 1; i >= visibleExponent; --i) {
|
||||
|
@ -630,7 +636,7 @@ public class TDecimalFormat extends TNumberFormat {
|
|||
int count = 0;
|
||||
for (int i = visibleExponent - 1; i >= limit; --i) {
|
||||
long mantissaDigitMask = POW10_ARRAY[i];
|
||||
buffer.append(forDigit(Math.abs((int) (mantissa / mantissaDigitMask))));
|
||||
buffer.append(forDigit((int) (mantissa / mantissaDigitMask)));
|
||||
mantissa %= mantissaDigitMask;
|
||||
++count;
|
||||
if (mantissa == 0) {
|
||||
|
@ -655,11 +661,10 @@ public class TDecimalFormat extends TNumberFormat {
|
|||
}
|
||||
|
||||
// Add suffix
|
||||
appendSuffix(positive, buffer);
|
||||
appendSuffix(sign, buffer);
|
||||
}
|
||||
|
||||
private void formatRegular(long mantissa, int exponent, StringBuffer buffer) {
|
||||
boolean positive = mantissa >= 0;
|
||||
private void formatRegular(long mantissa, int exponent, boolean sign, StringBuffer buffer) {
|
||||
int mantissaLength = fastLn10(mantissa) + 1;
|
||||
++exponent;
|
||||
|
||||
|
@ -668,7 +673,7 @@ public class TDecimalFormat extends TNumberFormat {
|
|||
int tenMultiplier = POW10_INT_ARRAY[multiplierDigits];
|
||||
if (tenMultiplier == multiplier) {
|
||||
exponent += multiplierDigits;
|
||||
} else if (mantissa >= Long.MAX_VALUE / multiplier || mantissa <= Long.MIN_VALUE / multiplier) {
|
||||
} else if (mantissa >= Long.MAX_VALUE / multiplier) {
|
||||
formatRegular(new BigDecimal(BigInteger.valueOf(mantissa), mantissaLength - exponent), buffer);
|
||||
return;
|
||||
} else {
|
||||
|
@ -682,11 +687,16 @@ public class TDecimalFormat extends TNumberFormat {
|
|||
if (roundingPos < 0) {
|
||||
mantissa = 0;
|
||||
} else if (roundingPos < mantissaLength) {
|
||||
mantissa = applyRounding(mantissa, mantissaLength, roundingPos);
|
||||
mantissa = applyRounding(mantissa, mantissaLength, roundingPos, sign);
|
||||
int newMantissaLength = fastLn10(mantissa) + 1;
|
||||
if (newMantissaLength > mantissaLength) {
|
||||
mantissaLength = newMantissaLength;
|
||||
++exponent;
|
||||
}
|
||||
}
|
||||
|
||||
// Append pattern prefix
|
||||
fieldsToText(positive ? positivePrefix : negativePrefix, buffer);
|
||||
fieldsToText(sign ? positivePrefix : negativePrefix, buffer);
|
||||
|
||||
// Add insignificant integer zeros
|
||||
int intLength = Math.max(0, exponent);
|
||||
|
@ -765,7 +775,7 @@ public class TDecimalFormat extends TNumberFormat {
|
|||
}
|
||||
|
||||
// Add suffix
|
||||
appendSuffix(positive, buffer);
|
||||
appendSuffix(sign, buffer);
|
||||
}
|
||||
|
||||
private void formatExponent(BigDecimal value, StringBuffer buffer) {
|
||||
|
@ -962,24 +972,23 @@ public class TDecimalFormat extends TNumberFormat {
|
|||
appendSuffix(positive, buffer);
|
||||
}
|
||||
|
||||
private long applyRounding(long mantissa, int mantissaLength, int exponent) {
|
||||
private long applyRounding(long mantissa, int mantissaLength, int exponent, boolean sign) {
|
||||
long rounding = POW10_ARRAY[mantissaLength - exponent];
|
||||
long signedRounding = mantissa > 0 ? rounding : -rounding;
|
||||
switch (getRoundingMode()) {
|
||||
case CEILING:
|
||||
mantissa = (mantissa / rounding) * rounding;
|
||||
if (mantissa >= 0) {
|
||||
if (sign) {
|
||||
mantissa += rounding;
|
||||
}
|
||||
break;
|
||||
case FLOOR:
|
||||
mantissa = (mantissa / rounding) * rounding;
|
||||
if (mantissa <= 0) {
|
||||
mantissa -= rounding;
|
||||
if (!sign) {
|
||||
mantissa += rounding;
|
||||
}
|
||||
break;
|
||||
case UP:
|
||||
mantissa = (mantissa / rounding) * rounding + signedRounding;
|
||||
mantissa = (mantissa / rounding) * rounding + rounding;
|
||||
break;
|
||||
case DOWN:
|
||||
mantissa = (mantissa / rounding) * rounding;
|
||||
|
@ -990,27 +999,27 @@ public class TDecimalFormat extends TNumberFormat {
|
|||
}
|
||||
break;
|
||||
case HALF_DOWN:
|
||||
if (mantissa % rounding == signedRounding / 2) {
|
||||
if (mantissa % rounding == rounding / 2) {
|
||||
mantissa = (mantissa / rounding) * rounding;
|
||||
} else {
|
||||
mantissa = ((mantissa + signedRounding / 2) / rounding) * rounding;
|
||||
mantissa = ((mantissa + rounding / 2) / rounding) * rounding;
|
||||
}
|
||||
break;
|
||||
case HALF_UP:
|
||||
if (mantissa % rounding == signedRounding / 2) {
|
||||
mantissa = (mantissa / rounding) * rounding + signedRounding;
|
||||
if (mantissa % rounding == rounding / 2) {
|
||||
mantissa = (mantissa / rounding) * rounding + rounding;
|
||||
} else {
|
||||
mantissa = ((mantissa + signedRounding / 2) / rounding) * rounding;
|
||||
mantissa = ((mantissa + rounding / 2) / rounding) * rounding;
|
||||
}
|
||||
break;
|
||||
case HALF_EVEN: {
|
||||
if (mantissa % rounding == signedRounding / 2) {
|
||||
if (mantissa % rounding == rounding / 2) {
|
||||
mantissa = (mantissa / rounding) * rounding;
|
||||
if ((mantissa / rounding) % 2 != 0) {
|
||||
mantissa += signedRounding;
|
||||
mantissa += rounding;
|
||||
}
|
||||
} else {
|
||||
mantissa = ((mantissa + signedRounding / 2) / rounding) * rounding;
|
||||
mantissa = ((mantissa + rounding / 2) / rounding) * rounding;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -1078,8 +1087,10 @@ public class TDecimalFormat extends TNumberFormat {
|
|||
}
|
||||
|
||||
private int fastLn10(long value) {
|
||||
if (value == Long.MIN_VALUE) {
|
||||
return 18;
|
||||
}
|
||||
int result = 0;
|
||||
if (value >= 0) {
|
||||
if (value >= 1_0000_0000_0000_0000L) {
|
||||
result += 16;
|
||||
value /= 1_0000_0000_0000_0000L;
|
||||
|
@ -1100,28 +1111,6 @@ public class TDecimalFormat extends TNumberFormat {
|
|||
result += 1;
|
||||
value /= 10L;
|
||||
}
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -1158,47 +1147,6 @@ public class TDecimalFormat extends TNumberFormat {
|
|||
return value;
|
||||
}
|
||||
|
||||
private MantissaAndExponent getMantissaAndExponent(double value) {
|
||||
long mantissaPattern = POW10_ARRAY[17];
|
||||
int exp = 0;
|
||||
long mantissa = 0;
|
||||
boolean positive;
|
||||
if (value >= 0) {
|
||||
positive = true;
|
||||
} else {
|
||||
positive = false;
|
||||
value = -value;
|
||||
}
|
||||
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) * mantissaPattern) + 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 * mantissaPattern) / digit) + 0.5);
|
||||
}
|
||||
mantissa = ((mantissa + 500) / 1000) * 1000;
|
||||
return new MantissaAndExponent(positive ? mantissa : -mantissa, exp);
|
||||
}
|
||||
|
||||
private char forDigit(int n) {
|
||||
return (char) (symbols.getZeroDigit() + n);
|
||||
}
|
||||
|
@ -1318,4 +1266,9 @@ public class TDecimalFormat extends TNumberFormat {
|
|||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
static class Constants {
|
||||
static final DoubleAnalyzer.Result doubleAnalysisResult = new DoubleAnalyzer.Result();
|
||||
static final FloatAnalyzer.Result floatAnalysisResult = new FloatAnalyzer.Result();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -165,7 +165,7 @@ public class DecimalFormatTest {
|
|||
format = createFormat("00000000000000000000000000.0");
|
||||
assertEquals("00000000000000000000000023.0", format.format(23));
|
||||
assertEquals("00002300000000000000000000.0", format.format(23E20));
|
||||
assertEquals("23000000000000000000000000.0", format.format(23E24));
|
||||
assertEquals("24000000000000000000000000.0", format.format(24E24));
|
||||
|
||||
format = createFormat("0.00000000000000000000000000");
|
||||
assertEquals("23.00000000000000000000000000", format.format(23));
|
||||
|
@ -173,6 +173,9 @@ public class DecimalFormatTest {
|
|||
assertEquals("0.00230000000000000000000000", format.format(0.0023));
|
||||
assertEquals("0.00000000000000000000230000", format.format(23E-22));
|
||||
assertEquals("0.00000000000000000000000023", format.format(23E-26));
|
||||
|
||||
assertEquals("1", createFormat("#.##").format(0.9977993000000007d));
|
||||
assertEquals("1", createFormat("#.##").format(0.997799f));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -328,7 +331,7 @@ public class DecimalFormatTest {
|
|||
assertEquals("23.0", format.format(23));
|
||||
assertEquals("2,300.0", format.format(2300));
|
||||
assertEquals("2,300,000,000,000,000,000,000.0", format.format(23E20));
|
||||
assertEquals("23,000,000,000,000,000,000,000,000.0", format.format(23E24));
|
||||
assertEquals("24,000,000,000,000,000,000,000,000.0", format.format(24E24));
|
||||
|
||||
format = createFormat("000,000,000,000,000,000,000");
|
||||
assertEquals("000,000,000,000,000,000,023", format.format(23));
|
||||
|
@ -381,6 +384,9 @@ public class DecimalFormatTest {
|
|||
assertEquals("1.23E2", format.format(123));
|
||||
assertEquals("1.234E3", format.format(1234));
|
||||
assertEquals("1.234E4", format.format(12345));
|
||||
|
||||
//assertEquals("1.23E4", createFormat("#.##E0").format(12345));
|
||||
assertEquals("1E5", createFormat("#.##E0").format(99999));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
Loading…
Reference in New Issue
Block a user