classlib: implement float support for String.format (#873)

This commit is contained in:
J. Fronny 2023-12-07 19:16:38 +01:00 committed by GitHub
parent bb837bd020
commit 953c475b46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 139 additions and 8 deletions

View File

@ -245,7 +245,7 @@ public class TDecimalFormat extends TNumberFormat {
if (digit >= 0 && digit <= 9) { if (digit >= 0 && digit <= 9) {
if (!fractionalPart) { if (!fractionalPart) {
++intSize; ++intSize;
allowGroupSeparator = groupingSize > 1; allowGroupSeparator = isGroupingUsed() && groupingSize > 1;
} else { } else {
++fracSize; ++fracSize;
} }
@ -383,7 +383,7 @@ public class TDecimalFormat extends TNumberFormat {
if (digit >= 0 && digit <= 9) { if (digit >= 0 && digit <= 9) {
if (!fractionalPart) { if (!fractionalPart) {
++intSize; ++intSize;
allowGroupSeparator = groupingSize > 1; allowGroupSeparator = isGroupingUsed() && groupingSize > 1;
} else { } else {
++fracSize; ++fracSize;
} }
@ -710,7 +710,7 @@ public class TDecimalFormat extends TNumberFormat {
int digitPos = Math.max(intLength, getMinimumIntegerDigits()) - 1; int digitPos = Math.max(intLength, getMinimumIntegerDigits()) - 1;
for (int i = getMinimumIntegerDigits() - 1; i >= intLength; --i) { for (int i = getMinimumIntegerDigits() - 1; i >= intLength; --i) {
buffer.append('0'); buffer.append('0');
if (groupingSize > 0 && digitPos % groupingSize == 0 && digitPos > 0) { if (isGroupingUsed() && groupingSize > 0 && digitPos % groupingSize == 0 && digitPos > 0) {
buffer.append(symbols.getGroupingSeparator()); buffer.append(symbols.getGroupingSeparator());
} }
--digitPos; --digitPos;
@ -723,7 +723,7 @@ public class TDecimalFormat extends TNumberFormat {
long mantissaDigitMask = POW10_ARRAY[mantissaDigit--]; long mantissaDigitMask = POW10_ARRAY[mantissaDigit--];
buffer.append(forDigit(Math.abs((int) (mantissa / mantissaDigitMask)))); buffer.append(forDigit(Math.abs((int) (mantissa / mantissaDigitMask))));
mantissa %= mantissaDigitMask; mantissa %= mantissaDigitMask;
if (groupingSize > 0 && digitPos % groupingSize == 0 && digitPos > 0) { if (isGroupingUsed() && groupingSize > 0 && digitPos % groupingSize == 0 && digitPos > 0) {
buffer.append(symbols.getGroupingSeparator()); buffer.append(symbols.getGroupingSeparator());
} }
--digitPos; --digitPos;
@ -733,7 +733,7 @@ public class TDecimalFormat extends TNumberFormat {
intLength -= significantIntDigits; intLength -= significantIntDigits;
for (int i = 0; i < intLength; ++i) { for (int i = 0; i < intLength; ++i) {
buffer.append('0'); buffer.append('0');
if (groupingSize > 0 && digitPos % groupingSize == 0 && digitPos > 0) { if (isGroupingUsed() && groupingSize > 0 && digitPos % groupingSize == 0 && digitPos > 0) {
buffer.append(symbols.getGroupingSeparator()); buffer.append(symbols.getGroupingSeparator());
} }
--digitPos; --digitPos;
@ -901,7 +901,7 @@ public class TDecimalFormat extends TNumberFormat {
int digitPos = Math.max(intLength, getMinimumIntegerDigits()) - 1; int digitPos = Math.max(intLength, getMinimumIntegerDigits()) - 1;
for (int i = getMinimumIntegerDigits() - 1; i >= intLength; --i) { for (int i = getMinimumIntegerDigits() - 1; i >= intLength; --i) {
buffer.append('0'); buffer.append('0');
if (groupingSize > 0 && digitPos % groupingSize == 0 && digitPos > 0) { if (isGroupingUsed() && groupingSize > 0 && digitPos % groupingSize == 0 && digitPos > 0) {
buffer.append(symbols.getGroupingSeparator()); buffer.append(symbols.getGroupingSeparator());
} }
--digitPos; --digitPos;
@ -914,7 +914,7 @@ public class TDecimalFormat extends TNumberFormat {
BigInteger[] parts = mantissa.divideAndRemainder(mantissaDigitMask); BigInteger[] parts = mantissa.divideAndRemainder(mantissaDigitMask);
buffer.append(forDigit(Math.abs(parts[0].intValue()))); buffer.append(forDigit(Math.abs(parts[0].intValue())));
mantissa = parts[1]; mantissa = parts[1];
if (groupingSize > 0 && digitPos % groupingSize == 0 && digitPos > 0) { if (isGroupingUsed() && groupingSize > 0 && digitPos % groupingSize == 0 && digitPos > 0) {
buffer.append(symbols.getGroupingSeparator()); buffer.append(symbols.getGroupingSeparator());
} }
--digitPos; --digitPos;
@ -926,7 +926,7 @@ public class TDecimalFormat extends TNumberFormat {
intLength -= significantIntDigits; intLength -= significantIntDigits;
for (int i = 0; i < intLength; ++i) { for (int i = 0; i < intLength; ++i) {
buffer.append('0'); buffer.append('0');
if (groupingSize > 0 && digitPos % groupingSize == 0 && digitPos > 0) { if (isGroupingUsed() && groupingSize > 0 && digitPos % groupingSize == 0 && digitPos > 0) {
buffer.append(symbols.getGroupingSeparator()); buffer.append(symbols.getGroupingSeparator());
} }
--digitPos; --digitPos;

View File

@ -22,6 +22,7 @@ import java.io.OutputStream;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.io.PrintStream; import java.io.PrintStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols; import java.text.DecimalFormatSymbols;
import java.text.NumberFormat; import java.text.NumberFormat;
@ -31,6 +32,7 @@ import java.util.IllegalFormatConversionException;
import java.util.Locale; import java.util.Locale;
import java.util.UnknownFormatConversionException; import java.util.UnknownFormatConversionException;
import org.teavm.classlib.impl.IntegerUtil; import org.teavm.classlib.impl.IntegerUtil;
import org.teavm.classlib.java.text.TDecimalFormat;
public final class TFormatter implements Closeable, Flushable { public final class TFormatter implements Closeable, Flushable {
private Locale locale; private Locale locale;
@ -240,11 +242,98 @@ public final class TFormatter implements Closeable, Flushable {
formatRadixInt(specifier, 4, true); formatRadixInt(specifier, 4, true);
break; break;
case 'f':
formatFloat(specifier, false);
break;
default: default:
throw new UnknownFormatConversionException(String.valueOf(specifier)); throw new UnknownFormatConversionException(String.valueOf(specifier));
} }
} }
private void formatFloat(char specifier, boolean upperCase) throws IOException {
verifyFlags(specifier, MASK_FOR_INT_DECIMAL_FORMAT);
verifyFloatFlags();
if (precision == -1) {
precision = 6;
}
Object arg = args[argumentIndex];
boolean negative;
if (arg instanceof Double) {
negative = (Double) arg < 0;
} else if (arg instanceof Float) {
negative = (Float) arg < 0;
} else if (arg instanceof BigDecimal) {
negative = ((BigDecimal) arg).signum() < 0;
} else {
throw new IllegalFormatConversionException(specifier, arg == null ? null : arg.getClass());
}
TDecimalFormat format = new TDecimalFormat();
format.setDecimalFormatSymbols(new DecimalFormatSymbols(locale));
if (width != -1) {
int decimalSize = predictDecimalSize(negative, format);
format.setMaximumIntegerDigits(decimalSize);
if ((flags & TFormattableFlags.ZERO_PADDED) != 0) {
format.setMinimumIntegerDigits(decimalSize);
}
}
format.setMaximumFractionDigits(precision);
format.setMinimumFractionDigits(precision);
format.setGroupingUsed((flags & TFormattableFlags.GROUPING_SEPARATOR) != 0);
if ((flags & TFormattableFlags.PARENTHESIZED_NEGATIVE) != 0) {
format.setNegativePrefix("(");
format.setNegativeSuffix(")");
}
if ((flags & TFormattableFlags.SIGNED) != 0) {
format.setPositivePrefix("+"); // DecimalFormatSymbols has no plus sign
} else if ((flags & TFormattableFlags.LEADING_SPACE) != 0) {
format.setPositivePrefix(" ");
}
String str = format.format(arg);
precision = -1; // prevent formatGivenString from trimming
formatGivenString(upperCase, str);
}
private int predictDecimalSize(boolean negative, TDecimalFormat format) {
int decimalSize = width;
if (precision > 0) {
decimalSize -= precision + 1; // width also includes decimal places. Subtract them!
}
// signs take up space as well. Also subtract them!
if (negative) {
if ((flags & TFormattableFlags.PARENTHESIZED_NEGATIVE) != 0) {
decimalSize -= 2;
} else {
decimalSize--;
}
} else if ((flags & (TFormattableFlags.SIGNED | TFormattableFlags.LEADING_SPACE)) != 0) {
decimalSize--;
}
// the grouping separator also takes up space. You know the drill.
if ((flags & TFormattableFlags.GROUPING_SEPARATOR) != 0) {
decimalSize -= decimalSize / (format.getGroupingSize() + 1);
}
return decimalSize;
}
private void verifyFloatFlags() {
if ((flags & TFormattableFlags.SIGNED) != 0 && (flags & TFormattableFlags.LEADING_SPACE) != 0) {
throw new TIllegalFormatFlagsException("+ ");
}
if ((flags & TFormattableFlags.ZERO_PADDED) != 0 && (flags & TFormattableFlags.LEFT_JUSTIFY) != 0) {
throw new TIllegalFormatFlagsException("0-");
}
if ((flags & TFormattableFlags.LEFT_JUSTIFY) != 0 && width < 0) {
throw new TMissingFormatWidthException(format.substring(formatSpecifierStart, index));
}
}
private void formatBoolean(char specifier, boolean upperCase) throws IOException { private void formatBoolean(char specifier, boolean upperCase) throws IOException {
verifyFlagsForGeneralFormat(specifier); verifyFlagsForGeneralFormat(specifier);
Object arg = args[argumentIndex]; Object arg = args[argumentIndex];

View File

@ -479,6 +479,22 @@ public class DecimalFormatTest {
assertEquals("23.00 RUB", format.format(23)); assertEquals("23.00 RUB", format.format(23));
} }
@Test
public void formatsManual() {
DecimalFormat format = new DecimalFormat();
format.setDecimalFormatSymbols(new DecimalFormatSymbols(Locale.US));
format.setMaximumFractionDigits(6);
format.setMinimumFractionDigits(6);
format.setGroupingUsed(false);
assertEquals("1.200000", format.format((Object) 1.2));
assertEquals("12.200000", format.format((Object) 12.2));
format.setMaximumFractionDigits(0);
format.setMinimumFractionDigits(0);
format.setMaximumIntegerDigits(5);
format.setMinimumIntegerDigits(5);
assertEquals("00002", format.format((Object) 2.0));
}
private DecimalFormat createFormat(String format) { private DecimalFormat createFormat(String format) {
return new DecimalFormat(format, symbols); return new DecimalFormat(format, symbols);
} }

View File

@ -253,4 +253,30 @@ public class FormatterTest {
assertEquals(2, e.getPrecision()); assertEquals(2, e.getPrecision());
} }
} }
@Test
public void formatsDouble() {
assertEquals("1.200000", new Formatter(Locale.US).format("%f", 1.2).toString());
assertEquals("12.200000", new Formatter(Locale.US).format("%f", 12.2).toString());
assertEquals("00002", new Formatter(Locale.US).format("%05.0f", 2.3).toString());
assertEquals("-0023", new Formatter(Locale.US).format("%05.0f", -23f).toString());
assertEquals("1,234.600000", new Formatter(Locale.US).format("%0,9f", 1234.6).toString());
assertEquals("(1,234.6)", new Formatter(Locale.US).format("%0,(9.1f", -1234.6).toString());
assertEquals("1 12 123 1,234 12,345 123,456 1,234,567", new Formatter(Locale.US)
.format("%,.0f %,.0f %,.0f %,.0f %,.0f %,.0f %,.0f", 1f, 12f, 123f, 1234f, 12345f, 123456f, 1234567f)
.toString());
assertEquals(" -123.1:-234.2 ", new Formatter(Locale.US)
.format("%7.1f:%-7.1f", -123.1, -234.2).toString());
assertEquals("+123.1 +123.2 +0.3", new Formatter(Locale.US)
.format("%+.1f %+05.1f %+.1f", 123.1, 123.2, 0.3).toString());
assertEquals(": 123.0:-123.0:", new Formatter(Locale.US)
.format(":% .1f:% .1f:", 123f, -123d).toString());
assertEquals("12.050", new Formatter(Locale.US).format("%4.3f", 12.05).toString());
}
} }