diff --git a/src/java.base/share/classes/java/text/DecimalFormat.java b/src/java.base/share/classes/java/text/DecimalFormat.java
index c803a97ad86..7d8297f42bc 100644
--- a/src/java.base/share/classes/java/text/DecimalFormat.java
+++ b/src/java.base/share/classes/java/text/DecimalFormat.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -250,7 +250,7 @@ import sun.util.locale.provider.ResourceBundleBasedAdapter;
*
* | {@code ;}
* | {@link DecimalFormatSymbols#getPatternSeparator()}
@@ -2059,8 +2059,7 @@ public class DecimalFormat extends NumberFormat {
// Output grouping separator if necessary. Don't output a
// grouping separator if i==0 though; that's at the end of
// the integer part.
- if (isGroupingUsed() && i>0 && (groupingSize != 0) &&
- (i % groupingSize == 0)) {
+ if (isGroupingEnabled() && i > 0 && i % groupingSize == 0) {
int gStart = result.length();
result.append(grouping);
delegate.formatted(Field.GROUPING_SEPARATOR,
@@ -2242,8 +2241,14 @@ public class DecimalFormat extends NumberFormat {
public Number parse(String text, ParsePosition pos) {
// special case NaN
if (text.regionMatches(pos.index, symbols.getNaN(), 0, symbols.getNaN().length())) {
- pos.index = pos.index + symbols.getNaN().length();
- return Double.valueOf(Double.NaN);
+ var nanEnd = pos.index + symbols.getNaN().length();
+ // When strict, parsing NaN must be exact
+ if (parseStrict && nanEnd != text.length()) {
+ pos.errorIndex = nanEnd;
+ return null;
+ }
+ pos.index = nanEnd;
+ return Double.NaN;
}
boolean[] status = new boolean[STATUS_LENGTH];
@@ -2568,7 +2573,8 @@ public class DecimalFormat extends NumberFormat {
}
// Enforce the grouping size on the first group
- if (parseStrict && isGroupingUsed() && position == startPos + groupingSize
+ if (parseStrict && isGroupingEnabled()
+ && position == startPos + groupingSize
&& prevSeparatorIndex == -groupingSize && !sawDecimal
&& digit >= 0 && digit <= 9) {
return new NumericPosition(position, intIndex);
@@ -2612,7 +2618,8 @@ public class DecimalFormat extends NumberFormat {
return new NumericPosition(-1, intIndex);
}
// Check grouping size on decimal separator
- if (parseStrict && isGroupingViolation(position, prevSeparatorIndex)) {
+ if (parseStrict && isGroupingEnabled()
+ && isGroupingViolation(position, prevSeparatorIndex)) {
return new NumericPosition(
groupingViolationIndex(position, prevSeparatorIndex), intIndex);
}
@@ -2624,7 +2631,8 @@ public class DecimalFormat extends NumberFormat {
intIndex = position;
digits.decimalAt = digitCount; // Not digits.count!
sawDecimal = true;
- } else if (!isExponent && ch == grouping && isGroupingUsed()) {
+ } else if (!isExponent && ch == grouping &&
+ (parseStrict ? isGroupingEnabled() : isGroupingUsed())) {
if (parseStrict) {
// text should not start with grouping when strict
if (position == startPos) {
@@ -2679,10 +2687,11 @@ public class DecimalFormat extends NumberFormat {
// (When strict), within the loop we enforce grouping when encountering
// decimal/grouping symbols. Once outside loop, we need to check
// the final grouping, ex: "1,234". Only check the final grouping
- // if we have not seen a decimal separator, to prevent a non needed check,
- // for ex: "1,234.", "1,234.12"
+ // if we have not seen a decimal separator, to prevent a grouping check in the
+ // fraction portion for ex: "1,234.", "1,234.12"
if (parseStrict) {
- if (!sawDecimal && isGroupingViolation(position, prevSeparatorIndex)) {
+ if (!sawDecimal && isGroupingEnabled()
+ && isGroupingViolation(position, prevSeparatorIndex)) {
// -1, since position is incremented by one too many when loop is finished
// "1,234%" and "1,234" both end with pos = 5, since '%' breaks
// the loop before incrementing position. In both cases, check
@@ -2743,12 +2752,23 @@ public class DecimalFormat extends NumberFormat {
return decimalAt;
}
+ /*
+ * DecimalFormat defines both setGroupingUsed(boolean) and setGroupingSize(int).
+ * These operate independently, and setting a grouping size of 0 does not mean that
+ * isGroupingUsed() returns false. As a result, to effectively check whether grouping is used
+ * for strict parsing, both values need to be verified. Lenient parsing, which preserves the
+ * legacy parsing behavior, does not require this exhaustive check because grouping size positioning
+ * is not checked.
+ */
+ private boolean isGroupingEnabled() {
+ return isGroupingUsed() && groupingSize > 0;
+ }
+
// Checks to make sure grouping size is not violated. Used when strict.
private boolean isGroupingViolation(int pos, int prevGroupingPos) {
assert parseStrict : "Grouping violations should only occur when strict";
- return isGroupingUsed() && // Only violates if using grouping
- // Checks if a previous grouping symbol was seen.
- prevGroupingPos != -groupingSize &&
+ // Checks if a previous grouping symbol was seen.
+ return prevGroupingPos != -groupingSize &&
// The check itself, - 1 to account for grouping/decimal symbol
pos - 1 != prevGroupingPos + groupingSize;
}
@@ -3586,7 +3606,7 @@ public class DecimalFormat extends NumberFormat {
int digitCount = useExponentialNotation ? getMaximumIntegerDigits() :
Math.max(groupingSize, getMinimumIntegerDigits()) + 1;
for (int i = digitCount; i > 0; --i) {
- if (i != digitCount && isGroupingUsed() && groupingSize != 0 &&
+ if (i != digitCount && isGroupingEnabled() &&
i % groupingSize == 0) {
result.append(groupingSymbol);
}
diff --git a/test/jdk/java/text/Format/NumberFormat/LenientParseTest.java b/test/jdk/java/text/Format/NumberFormat/LenientParseTest.java
index c85fe0f6cbb..69fb2a15221 100644
--- a/test/jdk/java/text/Format/NumberFormat/LenientParseTest.java
+++ b/test/jdk/java/text/Format/NumberFormat/LenientParseTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -23,7 +23,7 @@
/*
* @test
- * @bug 8327640 8331485 8333456 8335668
+ * @bug 8327640 8331485 8333456 8335668 8388507
* @summary Test suite for NumberFormat parsing when lenient.
* @run junit/othervm -Duser.language=en -Duser.country=US LenientParseTest
* @run junit/othervm -Duser.language=ja -Duser.country=JP LenientParseTest
@@ -162,6 +162,12 @@ public class LenientParseTest {
assertEquals(1.23E45, successParse(fmt, "1.23E45FOO3222", 7));
}
+ @Test // Non-localized, only run once
+ @EnabledIfSystemProperty(named = "user.language", matches = "en")
+ public void nanNumberFormatTest() {
+ assertEquals(Double.NaN, successParse(new DecimalFormat(), "NaNFoo", 3));
+ }
+
// ---- CurrencyFormat tests ----
// All input Strings should pass and return expected value.
@ParameterizedTest
diff --git a/test/jdk/java/text/Format/NumberFormat/StrictParseTest.java b/test/jdk/java/text/Format/NumberFormat/StrictParseTest.java
index 3e90ccb39ce..1b69c53407b 100644
--- a/test/jdk/java/text/Format/NumberFormat/StrictParseTest.java
+++ b/test/jdk/java/text/Format/NumberFormat/StrictParseTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -23,7 +23,7 @@
/*
* @test
- * @bug 8327640 8331485 8333755 8335668
+ * @bug 8327640 8331485 8333755 8335668 8388507
* @summary Test suite for NumberFormat parsing with strict leniency
* @run junit/othervm -Duser.language=en -Duser.country=US StrictParseTest
* @run junit/othervm -Duser.language=ja -Duser.country=JP StrictParseTest
@@ -114,8 +114,20 @@ public class StrictParseTest {
successParse(nonLocalizedDFmt, "a12345,67890b");
successParse(nonLocalizedDFmt, "a1234,67890b");
failParse(nonLocalizedDFmt, "a123456,7890b", 6);
- }
+ // Special case: NaN
+ failParse(nonLocalizedDFmt, "NaNFoo", 3);
+ successParse(nonLocalizedDFmt, "NaN");
+ // Grouping size == 0 cases
+ var fmt = new DecimalFormat();
+ fmt.setStrict(true);
+ fmt.setGroupingSize(0);
+ fmt.setGroupingUsed(true);
+ var pp = new ParsePosition(0);
+ assertNull(fmt.parse("555,000.0", pp));
+ assertEquals(3, pp.getErrorIndex());
+ assertDoesNotThrow(() -> fmt.parse("555.0"));
+ }
// 8333755: Check that parsing with integer only against a suffix value works
@Test // Non-localized, run once
|