mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 23:04:50 +02:00
8295372: CompactNumberFormat handling of number one with decimal part
Reviewed-by: joehw
This commit is contained in:
parent
a5f6e31ccb
commit
e238920bb6
2 changed files with 78 additions and 45 deletions
|
@ -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;
|
||||||
|
|
|
@ -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},
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue