8295372: CompactNumberFormat handling of number one with decimal part

Reviewed-by: joehw
This commit is contained in:
Naoto Sato 2022-10-19 16:24:15 +00:00
parent a5f6e31ccb
commit e238920bb6
2 changed files with 78 additions and 45 deletions

View file

@ -352,6 +352,11 @@ public final class CompactNumberFormat extends NumberFormat {
*/ */
private transient Map<String, String> rulesMap; private transient Map<String, String> rulesMap;
/**
* RegEx to parse number part of a compact number text
*/
private transient Pattern numberPattern;
/** /**
* Special pattern used for compact numbers * Special pattern used for compact numbers
*/ */
@ -588,17 +593,17 @@ public final class CompactNumberFormat extends NumberFormat {
int compactDataIndex = selectCompactPattern((long) roundedNumber); int compactDataIndex = selectCompactPattern((long) roundedNumber);
if (compactDataIndex != -1) { if (compactDataIndex != -1) {
long divisor = (Long) divisors.get(compactDataIndex); long divisor = (Long) divisors.get(compactDataIndex);
int iPart = getIntegerPart(number, divisor); double val = getNumberValue(number, divisor);
if (checkIncrement(iPart, compactDataIndex, divisor)) { if (checkIncrement(val, compactDataIndex, divisor)) {
divisor = (Long) divisors.get(++compactDataIndex); divisor = (Long) divisors.get(++compactDataIndex);
iPart = getIntegerPart(number, divisor); val = getNumberValue(number, divisor);
} }
String prefix = getAffix(false, true, isNegative, compactDataIndex, iPart); String prefix = getAffix(false, true, isNegative, compactDataIndex, val);
String suffix = getAffix(false, false, isNegative, compactDataIndex, iPart); String suffix = getAffix(false, false, isNegative, compactDataIndex, val);
if (!prefix.isEmpty() || !suffix.isEmpty()) { if (!prefix.isEmpty() || !suffix.isEmpty()) {
appendPrefix(result, prefix, delegate); appendPrefix(result, prefix, delegate);
if (!placeHolderPatterns.get(compactDataIndex).get(iPart).isEmpty()) { if (!placeHolderPatterns.get(compactDataIndex).get(val).isEmpty()) {
roundedNumber = roundedNumber / divisor; roundedNumber = roundedNumber / divisor;
decimalFormat.setDigitList(roundedNumber, isNegative, getMaximumFractionDigits()); decimalFormat.setDigitList(roundedNumber, isNegative, getMaximumFractionDigits());
decimalFormat.subformatNumber(result, delegate, isNegative, decimalFormat.subformatNumber(result, delegate, isNegative,
@ -661,16 +666,16 @@ public final class CompactNumberFormat extends NumberFormat {
int compactDataIndex = selectCompactPattern(number); int compactDataIndex = selectCompactPattern(number);
if (compactDataIndex != -1) { if (compactDataIndex != -1) {
long divisor = (Long) divisors.get(compactDataIndex); long divisor = (Long) divisors.get(compactDataIndex);
int iPart = getIntegerPart(number, divisor); double val = getNumberValue(number, divisor);
if (checkIncrement(iPart, compactDataIndex, divisor)) { if (checkIncrement(val, compactDataIndex, divisor)) {
divisor = (Long) divisors.get(++compactDataIndex); divisor = (Long) divisors.get(++compactDataIndex);
iPart = getIntegerPart(number, divisor); val = getNumberValue(number, divisor);
} }
String prefix = getAffix(false, true, isNegative, compactDataIndex, iPart); String prefix = getAffix(false, true, isNegative, compactDataIndex, val);
String suffix = getAffix(false, false, isNegative, compactDataIndex, iPart); String suffix = getAffix(false, false, isNegative, compactDataIndex, val);
if (!prefix.isEmpty() || !suffix.isEmpty()) { if (!prefix.isEmpty() || !suffix.isEmpty()) {
appendPrefix(result, prefix, delegate); appendPrefix(result, prefix, delegate);
if (!placeHolderPatterns.get(compactDataIndex).get(iPart).isEmpty()) { if (!placeHolderPatterns.get(compactDataIndex).get(val).isEmpty()) {
if ((number % divisor == 0)) { if ((number % divisor == 0)) {
number = number / divisor; number = number / divisor;
decimalFormat.setDigitList(number, isNegative, 0); decimalFormat.setDigitList(number, isNegative, 0);
@ -760,16 +765,16 @@ public final class CompactNumberFormat extends NumberFormat {
if (compactDataIndex != -1) { if (compactDataIndex != -1) {
Number divisor = divisors.get(compactDataIndex); Number divisor = divisors.get(compactDataIndex);
int iPart = getIntegerPart(number.doubleValue(), divisor.doubleValue()); double val = getNumberValue(number.doubleValue(), divisor.doubleValue());
if (checkIncrement(iPart, compactDataIndex, divisor.doubleValue())) { if (checkIncrement(val, compactDataIndex, divisor.doubleValue())) {
divisor = divisors.get(++compactDataIndex); divisor = divisors.get(++compactDataIndex);
iPart = getIntegerPart(number.doubleValue(), divisor.doubleValue()); val = getNumberValue(number.doubleValue(), divisor.doubleValue());
} }
String prefix = getAffix(false, true, isNegative, compactDataIndex, iPart); String prefix = getAffix(false, true, isNegative, compactDataIndex, val);
String suffix = getAffix(false, false, isNegative, compactDataIndex, iPart); String suffix = getAffix(false, false, isNegative, compactDataIndex, val);
if (!prefix.isEmpty() || !suffix.isEmpty()) { if (!prefix.isEmpty() || !suffix.isEmpty()) {
appendPrefix(result, prefix, delegate); appendPrefix(result, prefix, delegate);
if (!placeHolderPatterns.get(compactDataIndex).get(iPart).isEmpty()) { if (!placeHolderPatterns.get(compactDataIndex).get(val).isEmpty()) {
number = number.divide(new BigDecimal(divisor.toString()), getRoundingMode()); number = number.divide(new BigDecimal(divisor.toString()), getRoundingMode());
decimalFormat.setDigitList(number, isNegative, getMaximumFractionDigits()); decimalFormat.setDigitList(number, isNegative, getMaximumFractionDigits());
decimalFormat.subformatNumber(result, delegate, isNegative, decimalFormat.subformatNumber(result, delegate, isNegative,
@ -831,16 +836,16 @@ public final class CompactNumberFormat extends NumberFormat {
int compactDataIndex = selectCompactPattern(number); int compactDataIndex = selectCompactPattern(number);
if (compactDataIndex != -1) { if (compactDataIndex != -1) {
Number divisor = divisors.get(compactDataIndex); Number divisor = divisors.get(compactDataIndex);
int iPart = getIntegerPart(number.doubleValue(), divisor.doubleValue()); double val = getNumberValue(number.doubleValue(), divisor.doubleValue());
if (checkIncrement(iPart, compactDataIndex, divisor.doubleValue())) { if (checkIncrement(val, compactDataIndex, divisor.doubleValue())) {
divisor = divisors.get(++compactDataIndex); divisor = divisors.get(++compactDataIndex);
iPart = getIntegerPart(number.doubleValue(), divisor.doubleValue()); val = getNumberValue(number.doubleValue(), divisor.doubleValue());
} }
String prefix = getAffix(false, true, isNegative, compactDataIndex, iPart); String prefix = getAffix(false, true, isNegative, compactDataIndex, val);
String suffix = getAffix(false, false, isNegative, compactDataIndex, iPart); String suffix = getAffix(false, false, isNegative, compactDataIndex, val);
if (!prefix.isEmpty() || !suffix.isEmpty()) { if (!prefix.isEmpty() || !suffix.isEmpty()) {
appendPrefix(result, prefix, delegate); appendPrefix(result, prefix, delegate);
if (!placeHolderPatterns.get(compactDataIndex).get(iPart).isEmpty()) { if (!placeHolderPatterns.get(compactDataIndex).get(val).isEmpty()) {
if (number.mod(new BigInteger(divisor.toString())) if (number.mod(new BigInteger(divisor.toString()))
.compareTo(BigInteger.ZERO) == 0) { .compareTo(BigInteger.ZERO) == 0) {
number = number.divide(new BigInteger(divisor.toString())); number = number.divide(new BigInteger(divisor.toString()));
@ -879,12 +884,12 @@ public final class CompactNumberFormat extends NumberFormat {
* Obtain the designated affix from the appropriate list of affixes, * Obtain the designated affix from the appropriate list of affixes,
* based on the given arguments. * based on the given arguments.
*/ */
private String getAffix(boolean isExpanded, boolean isPrefix, boolean isNegative, int compactDataIndex, int iPart) { private String getAffix(boolean isExpanded, boolean isPrefix, boolean isNegative, int compactDataIndex, double val) {
return (isExpanded ? (isPrefix ? (isNegative ? negativePrefixes : positivePrefixes) : return (isExpanded ? (isPrefix ? (isNegative ? negativePrefixes : positivePrefixes) :
(isNegative ? negativeSuffixes : positiveSuffixes)) : (isNegative ? negativeSuffixes : positiveSuffixes)) :
(isPrefix ? (isNegative ? negativePrefixPatterns : positivePrefixPatterns) : (isPrefix ? (isNegative ? negativePrefixPatterns : positivePrefixPatterns) :
(isNegative ? negativeSuffixPatterns : positiveSuffixPatterns))) (isNegative ? negativeSuffixPatterns : positiveSuffixPatterns)))
.get(compactDataIndex).get(iPart); .get(compactDataIndex).get(val);
} }
/** /**
@ -1587,8 +1592,8 @@ public final class CompactNumberFormat extends NumberFormat {
// Prefix matching // Prefix matching
for (int compactIndex = 0; compactIndex < compactPatterns.length; compactIndex++) { for (int compactIndex = 0; compactIndex < compactPatterns.length; compactIndex++) {
String positivePrefix = getAffix(true, true, false, compactIndex, (int)num); String positivePrefix = getAffix(true, true, false, compactIndex, num);
String negativePrefix = getAffix(true, true, true, compactIndex, (int)num); String negativePrefix = getAffix(true, true, true, compactIndex, num);
// Do not break if a match occur; there is a possibility that the // Do not break if a match occur; there is a possibility that the
// subsequent affixes may match the longer subsequence in the given // subsequent affixes may match the longer subsequence in the given
@ -1736,7 +1741,6 @@ public final class CompactNumberFormat extends NumberFormat {
} }
} }
private static final Pattern DIGITS = Pattern.compile("\\p{Nd}+");
/** /**
* Parse the number part in the input text into a number * Parse the number part in the input text into a number
* *
@ -1745,15 +1749,18 @@ public final class CompactNumberFormat extends NumberFormat {
* @return the number * @return the number
*/ */
private double parseNumberPart(String text, int position) { private double parseNumberPart(String text, int position) {
if (numberPattern == null) {
numberPattern = Pattern.compile("[\\Q" + symbols.getDecimalSeparator() + "\\E\\p{Nd}]+");
}
if (text.startsWith(symbols.getInfinity(), position)) { if (text.startsWith(symbols.getInfinity(), position)) {
return Double.POSITIVE_INFINITY; return Double.POSITIVE_INFINITY;
} else if (!text.startsWith(symbols.getNaN(), position)) { } else if (!text.startsWith(symbols.getNaN(), position)) {
Matcher m = DIGITS.matcher(text); Matcher m = numberPattern.matcher(text);
if (m.find(position)) { if (m.find(position)) {
String digits = m.group(); String digits = m.group();
int cp = digits.codePointAt(0); if (Character.isDigit(digits.codePointAt(0))) {
if (Character.isDigit(cp)) {
return Double.parseDouble(digits.codePoints() return Double.parseDouble(digits.codePoints()
.filter(cp -> cp != symbols.getDecimalSeparator())
.map(Character::getNumericValue) .map(Character::getNumericValue)
.mapToObj(Integer::toString) .mapToObj(Integer::toString)
.collect(Collectors.joining())); .collect(Collectors.joining()));
@ -1909,10 +1916,10 @@ public final class CompactNumberFormat extends NumberFormat {
String matchedPosSuffix = ""; String matchedPosSuffix = "";
String matchedNegSuffix = ""; String matchedNegSuffix = "";
for (int compactIndex = 0; compactIndex < compactPatterns.length; compactIndex++) { for (int compactIndex = 0; compactIndex < compactPatterns.length; compactIndex++) {
String positivePrefix = getAffix(true, true, false, compactIndex, (int)num); String positivePrefix = getAffix(true, true, false, compactIndex, num);
String negativePrefix = getAffix(true, true, true, compactIndex, (int)num); String negativePrefix = getAffix(true, true, true, compactIndex, num);
String positiveSuffix = getAffix(true, false, false, compactIndex, (int)num); String positiveSuffix = getAffix(true, false, false, compactIndex, num);
String negativeSuffix = getAffix(true, false, true, compactIndex, (int)num); String negativeSuffix = getAffix(true, false, true, compactIndex, num);
// Do not break if a match occur; there is a possibility that the // Do not break if a match occur; there is a possibility that the
// subsequent affixes may match the longer subsequence in the given // subsequent affixes may match the longer subsequence in the given
@ -2407,19 +2414,20 @@ public final class CompactNumberFormat extends NumberFormat {
} }
} }
private int getIntegerPart(double number, double divisor) { private double getNumberValue(double number, double divisor) {
return BigDecimal.valueOf(number) var num = BigDecimal.valueOf(number)
.divide(BigDecimal.valueOf(divisor), roundingMode).intValue(); .divide(BigDecimal.valueOf(divisor), roundingMode);
return getMaximumFractionDigits() > 0 ? num.doubleValue() : num.intValue();
} }
// Checks whether the iPart is incremented by the BigDecimal division in // Checks whether the val is incremented by the BigDecimal division in
// getIntegerPart(), and affects the compact number index. // getNumberValue(), and affects the compact number index.
private boolean checkIncrement(int iPart, int index, double divisor) { private boolean checkIncrement(double val, int index, double divisor) {
if (index < compactPatterns.length - 1 && if (index < compactPatterns.length - 1 &&
!"".equals(compactPatterns[index])) { // ignore empty pattern !"".equals(compactPatterns[index])) { // ignore empty pattern
var nextDiv = divisors.get(index + 1).doubleValue(); var nextDiv = divisors.get(index + 1).doubleValue();
if (divisor != nextDiv) { if (divisor != nextDiv) {
return Math.log10(iPart) == Math.log10(nextDiv) - Math.log10(divisor); return Math.log10(val) == Math.log10(nextDiv) - Math.log10(divisor);
} }
} }
return false; return false;

View file

@ -22,7 +22,7 @@
*/ */
/* /*
* @test * @test
* @bug 8177552 8217721 8222756 * @bug 8177552 8217721 8222756 8295372
* @summary Checks the functioning of compact number format * @summary Checks the functioning of compact number format
* @modules jdk.localedata * @modules jdk.localedata
* @run testng/othervm TestCompactNumber * @run testng/othervm TestCompactNumber
@ -81,6 +81,21 @@ public class TestCompactNumber {
private static final NumberFormat FORMAT_SL_LONG = NumberFormat private static final NumberFormat FORMAT_SL_LONG = NumberFormat
.getCompactNumberInstance(Locale.of("sl"), NumberFormat.Style.LONG); .getCompactNumberInstance(Locale.of("sl"), NumberFormat.Style.LONG);
private static final NumberFormat FORMAT_ES_LONG_FD1 = NumberFormat
.getCompactNumberInstance(Locale.of("es"), NumberFormat.Style.LONG);
private static final NumberFormat FORMAT_DE_LONG_FD2 = NumberFormat
.getCompactNumberInstance(Locale.GERMAN, NumberFormat.Style.LONG);
private static final NumberFormat FORMAT_IT_LONG_FD3 = NumberFormat
.getCompactNumberInstance(Locale.ITALIAN, NumberFormat.Style.LONG);
private static final NumberFormat FORMAT_PT_LONG_FD4 = NumberFormat
.getCompactNumberInstance(Locale.of("pt"), NumberFormat.Style.LONG);
static {
FORMAT_ES_LONG_FD1.setMaximumFractionDigits(1);
FORMAT_DE_LONG_FD2.setMaximumFractionDigits(2);
FORMAT_IT_LONG_FD3.setMaximumFractionDigits(3);
FORMAT_PT_LONG_FD4.setMaximumFractionDigits(4);
}
@DataProvider(name = "format") @DataProvider(name = "format")
Object[][] compactFormatData() { Object[][] compactFormatData() {
return new Object[][]{ return new Object[][]{
@ -339,6 +354,11 @@ public class TestCompactNumber {
{FORMAT_SL_LONG, 2_000_000, "2 milijona"}, {FORMAT_SL_LONG, 2_000_000, "2 milijona"},
{FORMAT_SL_LONG, 3_000_000, "3 milijone"}, {FORMAT_SL_LONG, 3_000_000, "3 milijone"},
{FORMAT_SL_LONG, 5_000_000, "5 milijonov"}, {FORMAT_SL_LONG, 5_000_000, "5 milijonov"},
// Fractional plurals
{FORMAT_ES_LONG_FD1, 1_234_500, "1,2 millones"},
{FORMAT_DE_LONG_FD2, 1_234_500, "1,23 Millionen"},
{FORMAT_IT_LONG_FD3, 1_234_500, "1,234 milioni"},
{FORMAT_PT_LONG_FD4, 1_234_500, "1,2345 milh\u00f5es"},
}; };
} }
@ -441,13 +461,18 @@ public class TestCompactNumber {
{FORMAT_SL_LONG, "2 milijona", 2_000_000L, Long.class}, {FORMAT_SL_LONG, "2 milijona", 2_000_000L, Long.class},
{FORMAT_SL_LONG, "3 milijone", 3_000_000L, Long.class}, {FORMAT_SL_LONG, "3 milijone", 3_000_000L, Long.class},
{FORMAT_SL_LONG, "5 milijonov", 5_000_000L, Long.class}, {FORMAT_SL_LONG, "5 milijonov", 5_000_000L, Long.class},
// Fractional plurals
{FORMAT_ES_LONG_FD1, "1,2 millones", 1_200_000L, Long.class},
{FORMAT_DE_LONG_FD2, "1,23 Millionen", 1_230_000L, Long.class},
{FORMAT_IT_LONG_FD3, "1,234 milioni", 1_234_000L, Long.class},
{FORMAT_PT_LONG_FD4, "1,2345 milh\u00f5es", 1_234_500L, Long.class},
}; };
} }
@DataProvider(name = "exceptionParse") @DataProvider(name = "exceptionParse")
Object[][] exceptionParseData() { Object[][] exceptionParseData() {
return new Object[][]{ return new Object[][]{
// compact number instance, string to parse, null (no o/p; must throws exception) // compact number instance, string to parse, null (no o/p; must throw exception)
// no number // no number
{FORMAT_DZ_LONG, "\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55\u0FB2" {FORMAT_DZ_LONG, "\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55\u0FB2"
+ "\u0F42", null}, + "\u0F42", null},