diff --git a/icu4j/main/common_tests/src/test/java/com/ibm/icu/dev/test/format/NumberFormatTest.java b/icu4j/main/common_tests/src/test/java/com/ibm/icu/dev/test/format/NumberFormatTest.java index 406efb4935fc..b3ec7da0f72d 100644 --- a/icu4j/main/common_tests/src/test/java/com/ibm/icu/dev/test/format/NumberFormatTest.java +++ b/icu4j/main/common_tests/src/test/java/com/ibm/icu/dev/test/format/NumberFormatTest.java @@ -8677,4 +8677,42 @@ public void testStrictParse() throws java.text.ParseException { } } } + + /** + * Test that BigDecimal scale is correctly preserved when parsing numbers with trailing zeros. + * This is a regression test for ICU-22872. + */ + @Test + public void TestParseBigDecimalScale() throws ParseException { + DecimalFormat df = new DecimalFormat("0.00"); + df.setParseBigDecimal(true); + + // Test cases: input string, expected scale + Object[][] testCases = { + {"0.0", 1}, + {"1.0", 1}, + {"1.00", 2}, + {"1.10", 2}, + {"0.10", 2}, + {"123.450", 3}, + {"5.0000", 4}, + }; + + for (Object[] testCase : testCases) { + String input = (String) testCase[0]; + int expectedScale = (Integer) testCase[1]; + Number result = df.parse(input); + assertTrue("Result should be BigDecimal for input: " + input, result instanceof BigDecimal); + BigDecimal bd = (BigDecimal) result; + assertEquals("Scale mismatch for input: " + input, expectedScale, bd.scale()); + } + + // Verify values are correct, not just scales + assertEquals("Value check for 0.0", + new BigDecimal("0.0"), df.parse("0.0")); + assertEquals("Value check for 1.00", + new BigDecimal("1.00"), df.parse("1.00")); + assertEquals("Value check for 123.450", + new BigDecimal("123.450"), df.parse("123.450")); + } } diff --git a/icu4j/main/core/src/main/java/com/ibm/icu/impl/number/parse/DecimalMatcher.java b/icu4j/main/core/src/main/java/com/ibm/icu/impl/number/parse/DecimalMatcher.java index f3dd2a7a12aa..f4550a38f560 100644 --- a/icu4j/main/core/src/main/java/com/ibm/icu/impl/number/parse/DecimalMatcher.java +++ b/icu4j/main/core/src/main/java/com/ibm/icu/impl/number/parse/DecimalMatcher.java @@ -432,6 +432,7 @@ public boolean match(StringSegment segment, ParsedNumber result, int exponentSig // Set other information into the result and return. if (actualDecimalString != null) { result.flags |= ParsedNumber.FLAG_HAS_DECIMAL_SEPARATOR; + result.fractionDigitCount = digitsAfterDecimalPlace; } result.setCharsConsumed(segment); return segment.length() == 0 || maybeMore; diff --git a/icu4j/main/core/src/main/java/com/ibm/icu/impl/number/parse/ParsedNumber.java b/icu4j/main/core/src/main/java/com/ibm/icu/impl/number/parse/ParsedNumber.java index d05e51159d29..240ab5be5f84 100644 --- a/icu4j/main/core/src/main/java/com/ibm/icu/impl/number/parse/ParsedNumber.java +++ b/icu4j/main/core/src/main/java/com/ibm/icu/impl/number/parse/ParsedNumber.java @@ -36,6 +36,13 @@ public class ParsedNumber { /** The currency that got consumed. */ public String currencyCode; + /** + * The number of digits after the decimal separator, including trailing zeros. + * Used to preserve scale when parsing BigDecimals. + * A value of -1 means no decimal separator was seen. + */ + public int fractionDigitCount; + public static final int FLAG_NEGATIVE = 0x0001; public static final int FLAG_PERCENT = 0x0002; public static final int FLAG_PERMILLE = 0x0004; @@ -67,6 +74,7 @@ public void clear() { prefix = null; suffix = null; currencyCode = null; + fractionDigitCount = -1; } public void copyFrom(ParsedNumber other) { @@ -79,6 +87,7 @@ public void copyFrom(ParsedNumber other) { prefix = other.prefix; suffix = other.suffix; currencyCode = other.currencyCode; + fractionDigitCount = other.fractionDigitCount; } /** @@ -128,6 +137,7 @@ public Number getNumber() { public Number getNumber(int parseFlags) { boolean sawNaN = 0 != (flags & FLAG_NAN); boolean sawInfinity = 0 != (flags & FLAG_INFINITY); + boolean sawExponent = 0 != (flags & FLAG_HAS_EXPONENT); boolean forceBigDecimal = 0 != (parseFlags & ParsingUtils.PARSE_FLAG_FORCE_BIG_DECIMAL); boolean integerOnly = 0 != (parseFlags & ParsingUtils.PARSE_FLAG_INTEGER_ONLY); @@ -150,7 +160,13 @@ public Number getNumber(int parseFlags) { if (quantity.fitsInLong() && !forceBigDecimal) { return quantity.toLong(false); } else { - return quantity.toBigDecimal(); + java.math.BigDecimal result = quantity.toBigDecimal(); + // Preserve trailing zeros by adjusting scale based on fractionDigitCount + // Only do this when no exponent was seen (exponent changes the scale) + if (!sawExponent && fractionDigitCount >= 0 && result.scale() < fractionDigitCount) { + result = result.setScale(fractionDigitCount); + } + return result; } }