From e2f21c7bdbb9cb4669b3fd9a0c2265d0841477ef Mon Sep 17 00:00:00 2001 From: Elango Cheran Date: Wed, 3 Mar 2021 23:33:03 -0800 Subject: [PATCH] ICU-21519 Add PluralOperand 'c' as alias to 'e', parse FixedDecimal strings --- icu4c/source/i18n/number_decimalquantity.cpp | 3 + icu4c/source/i18n/plurrule.cpp | 49 +++++- icu4c/source/i18n/plurrule_impl.h | 19 ++- icu4c/source/test/intltest/numbertest.h | 2 +- .../intltest/numbertest_decimalquantity.cpp | 159 +++++++++-------- icu4c/source/test/intltest/plurults.cpp | 110 +++++++++++- icu4c/source/test/intltest/plurults.h | 2 + .../number/DecimalQuantity_AbstractBCD.java | 3 + .../src/com/ibm/icu/text/PluralRules.java | 49 +++++- .../icu/dev/test/format/PluralRulesTest.java | 86 +++++++++- .../dev/test/number/DecimalQuantityTest.java | 161 ++++++++++-------- 11 files changed, 482 insertions(+), 161 deletions(-) diff --git a/icu4c/source/i18n/number_decimalquantity.cpp b/icu4c/source/i18n/number_decimalquantity.cpp index 74547c1842d1..77209558ee02 100644 --- a/icu4c/source/i18n/number_decimalquantity.cpp +++ b/icu4c/source/i18n/number_decimalquantity.cpp @@ -273,6 +273,9 @@ double DecimalQuantity::getPluralOperand(PluralOperand operand) const { return fractionCountWithoutTrailingZeros(); case PLURAL_OPERAND_E: return static_cast(getExponent()); + case PLURAL_OPERAND_C: + // Plural operand `c` is currently an alias for `e`. + return static_cast(getExponent()); default: return std::abs(toDouble()); } diff --git a/icu4c/source/i18n/plurrule.cpp b/icu4c/source/i18n/plurrule.cpp index e1e1667a6ea9..bfcb6fb28dee 100644 --- a/icu4c/source/i18n/plurrule.cpp +++ b/icu4c/source/i18n/plurrule.cpp @@ -60,6 +60,7 @@ static const UChar PK_VAR_I[]={LOW_I,0}; static const UChar PK_VAR_F[]={LOW_F,0}; static const UChar PK_VAR_T[]={LOW_T,0}; static const UChar PK_VAR_E[]={LOW_E,0}; +static const UChar PK_VAR_C[]={LOW_C,0}; static const UChar PK_VAR_V[]={LOW_V,0}; static const UChar PK_WITHIN[]={LOW_W,LOW_I,LOW_T,LOW_H,LOW_I,LOW_N,0}; static const UChar PK_DECIMAL[]={LOW_D,LOW_E,LOW_C,LOW_I,LOW_M,LOW_A,LOW_L,0}; @@ -421,7 +422,6 @@ getSamplesFromString(const UnicodeString &samples, double *destDbl, destFd[sampleCount++] = fixed; } } else { - FixedDecimal fixedLo(sampleRange.tempSubStringBetween(0, tildeIndex), status); FixedDecimal fixedHi(sampleRange.tempSubStringBetween(tildeIndex+1), status); double rangeLo = fixedLo.source; @@ -514,6 +514,7 @@ PluralRules::getSamples(const UnicodeString &keyword, FixedDecimal *dest, if (rc == nullptr) { return 0; } + int32_t numSamples = getSamplesFromString(rc->fIntegerSamples, nullptr, dest, destCapacity, status); if (numSamples == 0) { numSamples = getSamplesFromString(rc->fDecimalSamples, nullptr, dest, destCapacity, status); @@ -706,6 +707,7 @@ PluralRuleParser::parse(const UnicodeString& ruleData, PluralRules *prules, UErr case tVariableF: case tVariableT: case tVariableE: + case tVariableC: case tVariableV: U_ASSERT(curAndConstraint != nullptr); curAndConstraint->digitsType = type; @@ -1092,6 +1094,8 @@ static UnicodeString tokenString(tokenType tok) { s.append(LOW_T); break; case tVariableE: s.append(LOW_E); break; + case tVariableC: + s.append(LOW_C); break; default: s.append(TILDE); } @@ -1269,6 +1273,7 @@ PluralRuleParser::checkSyntax(UErrorCode &status) case tVariableF: case tVariableT: case tVariableE: + case tVariableC: case tVariableV: if (type != tIs && type != tMod && type != tIn && type != tNot && type != tWithin && type != tEqual && type != tNotEqual) { @@ -1286,6 +1291,7 @@ PluralRuleParser::checkSyntax(UErrorCode &status) type == tVariableF || type == tVariableT || type == tVariableE || + type == tVariableC || type == tVariableV || type == tAt)) { status = U_UNEXPECTED_TOKEN; @@ -1318,6 +1324,7 @@ PluralRuleParser::checkSyntax(UErrorCode &status) type != tVariableF && type != tVariableT && type != tVariableE && + type != tVariableC && type != tVariableV) { status = U_UNEXPECTED_TOKEN; } @@ -1497,6 +1504,8 @@ PluralRuleParser::getKeyType(const UnicodeString &token, tokenType keyType) keyType = tVariableT; } else if (0 == token.compare(PK_VAR_E, 1)) { keyType = tVariableE; + } else if (0 == token.compare(PK_VAR_C, 1)) { + keyType = tVariableC; } else if (0 == token.compare(PK_VAR_V, 1)) { keyType = tVariableV; } else if (0 == token.compare(PK_IS, 2)) { @@ -1596,11 +1605,17 @@ PluralOperand tokenTypeToPluralOperand(tokenType tt) { return PLURAL_OPERAND_T; case tVariableE: return PLURAL_OPERAND_E; + case tVariableC: + return PLURAL_OPERAND_E; default: UPRV_UNREACHABLE; // unexpected. } } +FixedDecimal::FixedDecimal(double n, int32_t v, int64_t f, int32_t e, int32_t c) { + init(n, v, f, e, c); +} + FixedDecimal::FixedDecimal(double n, int32_t v, int64_t f, int32_t e) { init(n, v, f, e); // check values. TODO make into unit test. @@ -1642,16 +1657,30 @@ FixedDecimal::FixedDecimal() { FixedDecimal::FixedDecimal(const UnicodeString &num, UErrorCode &status) { CharString cs; int32_t parsedExponent = 0; + int32_t parsedCompactExponent = 0; int32_t exponentIdx = num.indexOf(u'e'); if (exponentIdx < 0) { exponentIdx = num.indexOf(u'E'); } + int32_t compactExponentIdx = num.indexOf(u'c'); + if (compactExponentIdx < 0) { + compactExponentIdx = num.indexOf(u'C'); + } + if (exponentIdx >= 0) { cs.appendInvariantChars(num.tempSubString(0, exponentIdx), status); int32_t expSubstrStart = exponentIdx + 1; parsedExponent = ICU_Utility::parseAsciiInteger(num, expSubstrStart); } + else if (compactExponentIdx >= 0) { + cs.appendInvariantChars(num.tempSubString(0, compactExponentIdx), status); + int32_t expSubstrStart = compactExponentIdx + 1; + parsedCompactExponent = ICU_Utility::parseAsciiInteger(num, expSubstrStart); + + parsedExponent = parsedCompactExponent; + exponentIdx = compactExponentIdx; + } else { cs.appendInvariantChars(num, status); } @@ -1706,13 +1735,20 @@ void FixedDecimal::init(double n, int32_t v, int64_t f) { init(n, v, f, exponent); } - void FixedDecimal::init(double n, int32_t v, int64_t f, int32_t e) { + // Currently, `c` is an alias for `e` + init(n, v, f, e, e); +} + +void FixedDecimal::init(double n, int32_t v, int64_t f, int32_t e, int32_t c) { isNegative = n < 0.0; source = fabs(n); _isNaN = uprv_isNaN(source); _isInfinite = uprv_isInfinite(source); exponent = e; + if (exponent == 0) { + exponent = c; + } if (_isNaN || _isInfinite) { v = 0; f = 0; @@ -1843,6 +1879,7 @@ double FixedDecimal::getPluralOperand(PluralOperand operand) const { case PLURAL_OPERAND_T: return static_cast(decimalDigitsWithoutTrailingZeros); case PLURAL_OPERAND_V: return visibleDecimalDigitCount; case PLURAL_OPERAND_E: return exponent; + case PLURAL_OPERAND_C: return exponent; default: UPRV_UNREACHABLE; // unexpected. } @@ -1876,12 +1913,12 @@ bool FixedDecimal::operator==(const FixedDecimal &other) const { UnicodeString FixedDecimal::toString() const { char pattern[15]; char buffer[20]; - if (exponent == 0) { - snprintf(pattern, sizeof(pattern), "%%.%df", visibleDecimalDigitCount); - snprintf(buffer, sizeof(buffer), pattern, source); - } else { + if (exponent != 0) { snprintf(pattern, sizeof(pattern), "%%.%dfe%%d", visibleDecimalDigitCount); snprintf(buffer, sizeof(buffer), pattern, source, exponent); + } else { + snprintf(pattern, sizeof(pattern), "%%.%df", visibleDecimalDigitCount); + snprintf(buffer, sizeof(buffer), pattern, source); } return UnicodeString(buffer, -1, US_INV); } diff --git a/icu4c/source/i18n/plurrule_impl.h b/icu4c/source/i18n/plurrule_impl.h index 52af3a741316..ccc481370c3c 100644 --- a/icu4c/source/i18n/plurrule_impl.h +++ b/icu4c/source/i18n/plurrule_impl.h @@ -145,6 +145,7 @@ enum tokenType { tVariableV, tVariableT, tVariableE, + tVariableC, tDecimal, tInteger, tEOF @@ -222,11 +223,20 @@ enum PluralOperand { PLURAL_OPERAND_W, /** - * Suppressed exponent for compact notation (exponent needed in - * scientific notation with compact notation to approximate i). + * Suppressed exponent for scientific notation (exponent needed in + * scientific notation to approximate i). */ PLURAL_OPERAND_E, + /** + * This operand is currently treated as an alias for `PLURAL_OPERAND_E`. + * In the future, it will represent: + * + * Suppressed exponent for compact notation (exponent needed in + * compact notation to approximate i). + */ + PLURAL_OPERAND_C, + /** * THIS OPERAND IS DEPRECATED AND HAS BEEN REMOVED FROM THE SPEC. * @@ -280,8 +290,10 @@ class U_I18N_API FixedDecimal: public IFixedDecimal, public UObject { * @param n the number, e.g. 12.345 * @param v The number of visible fraction digits, e.g. 3 * @param f The fraction digits, e.g. 345 - * @param e The exponent, e.g. 7 in 1.2e7 (for compact/scientific) + * @param e The exponent, e.g. 7 in 1.2e7, for scientific notation + * @param c Currently: an alias for param `e`. */ + FixedDecimal(double n, int32_t v, int64_t f, int32_t e, int32_t c); FixedDecimal(double n, int32_t v, int64_t f, int32_t e); FixedDecimal(double n, int32_t v, int64_t f); FixedDecimal(double n, int32_t); @@ -302,6 +314,7 @@ class U_I18N_API FixedDecimal: public IFixedDecimal, public UObject { int32_t getVisibleFractionDigitCount() const; + void init(double n, int32_t v, int64_t f, int32_t e, int32_t c); void init(double n, int32_t v, int64_t f, int32_t e); void init(double n, int32_t v, int64_t f); void init(double n); diff --git a/icu4c/source/test/intltest/numbertest.h b/icu4c/source/test/intltest/numbertest.h index 3285ff7369c5..39e888582f75 100644 --- a/icu4c/source/test/intltest/numbertest.h +++ b/icu4c/source/test/intltest/numbertest.h @@ -201,7 +201,7 @@ class DecimalQuantityTest : public IntlTest { void testToDouble(); void testMaxDigits(); void testNickelRounding(); - void testCompactDecimalSuppressedExponent(); + void testScientificAndCompactSuppressedExponent(); void testSuppressedExponentUnchangedByInitialScaling(); void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0); diff --git a/icu4c/source/test/intltest/numbertest_decimalquantity.cpp b/icu4c/source/test/intltest/numbertest_decimalquantity.cpp index 74b97a9ef422..610df9658c83 100644 --- a/icu4c/source/test/intltest/numbertest_decimalquantity.cpp +++ b/icu4c/source/test/intltest/numbertest_decimalquantity.cpp @@ -30,7 +30,7 @@ void DecimalQuantityTest::runIndexedTest(int32_t index, UBool exec, const char * TESTCASE_AUTO(testToDouble); TESTCASE_AUTO(testMaxDigits); TESTCASE_AUTO(testNickelRounding); - TESTCASE_AUTO(testCompactDecimalSuppressedExponent); + TESTCASE_AUTO(testScientificAndCompactSuppressedExponent); TESTCASE_AUTO(testSuppressedExponentUnchangedByInitialScaling); TESTCASE_AUTO_END; } @@ -478,8 +478,8 @@ void DecimalQuantityTest::testNickelRounding() { status.expectErrorAndReset(U_FORMAT_INEXACT_ERROR); } -void DecimalQuantityTest::testCompactDecimalSuppressedExponent() { - IcuTestErrorCode status(*this, "testCompactDecimalSuppressedExponent"); +void DecimalQuantityTest::testScientificAndCompactSuppressedExponent() { + IcuTestErrorCode status(*this, "testScientificAndCompactSuppressedExponent"); Locale ulocale("fr-FR"); struct TestCase { @@ -489,53 +489,56 @@ void DecimalQuantityTest::testCompactDecimalSuppressedExponent() { int64_t expectedLong; double expectedDouble; const char16_t* expectedPlainString; - int32_t expectedSuppressedExponent; + int32_t expectedSuppressedScientificExponent; + int32_t expectedSuppressedCompactExponent; } cases[] = { - // unlocalized formatter skeleton, input, string output, long output, double output, BigDecimal output, plain string, suppressed exponent - {u"", 123456789, u"123 456 789", 123456789L, 123456789.0, u"123456789", 0}, - {u"compact-long", 123456789, u"123 millions", 123000000L, 123000000.0, u"123000000", 6}, - {u"compact-short", 123456789, u"123 M", 123000000L, 123000000.0, u"123000000", 6}, - {u"scientific", 123456789, u"1,234568E8", 123456800L, 123456800.0, u"123456800", 8}, - - {u"", 1234567, u"1 234 567", 1234567L, 1234567.0, u"1234567", 0}, - {u"compact-long", 1234567, u"1,2 million", 1200000L, 1200000.0, u"1200000", 6}, - {u"compact-short", 1234567, u"1,2 M", 1200000L, 1200000.0, u"1200000", 6}, - {u"scientific", 1234567, u"1,234567E6", 1234567L, 1234567.0, u"1234567", 6}, - - {u"", 123456, u"123 456", 123456L, 123456.0, u"123456", 0}, - {u"compact-long", 123456, u"123 mille", 123000L, 123000.0, u"123000", 3}, - {u"compact-short", 123456, u"123 k", 123000L, 123000.0, u"123000", 3}, - {u"scientific", 123456, u"1,23456E5", 123456L, 123456.0, u"123456", 5}, - - {u"", 123, u"123", 123L, 123.0, u"123", 0}, - {u"compact-long", 123, u"123", 123L, 123.0, u"123", 0}, - {u"compact-short", 123, u"123", 123L, 123.0, u"123", 0}, - {u"scientific", 123, u"1,23E2", 123L, 123.0, u"123", 2}, - - {u"", 1.2, u"1,2", 1L, 1.2, u"1.2", 0}, - {u"compact-long", 1.2, u"1,2", 1L, 1.2, u"1.2", 0}, - {u"compact-short", 1.2, u"1,2", 1L, 1.2, u"1.2", 0}, - {u"scientific", 1.2, u"1,2E0", 1L, 1.2, u"1.2", 0}, - - {u"", 0.12, u"0,12", 0L, 0.12, u"0.12", 0}, - {u"compact-long", 0.12, u"0,12", 0L, 0.12, u"0.12", 0}, - {u"compact-short", 0.12, u"0,12", 0L, 0.12, u"0.12", 0}, - {u"scientific", 0.12, u"1,2E-1", 0L, 0.12, u"0.12", -1}, - - {u"", 0.012, u"0,012", 0L, 0.012, u"0.012", 0}, - {u"compact-long", 0.012, u"0,012", 0L, 0.012, u"0.012", 0}, - {u"compact-short", 0.012, u"0,012", 0L, 0.012, u"0.012", 0}, - {u"scientific", 0.012, u"1,2E-2", 0L, 0.012, u"0.012", -2}, - - {u"", 999.9, u"999,9", 999L, 999.9, u"999.9", 0}, - {u"compact-long", 999.9, u"1 millier", 1000L, 1000.0, u"1000", 3}, - {u"compact-short", 999.9, u"1 k", 1000L, 1000.0, u"1000", 3}, - {u"scientific", 999.9, u"9,999E2", 999L, 999.9, u"999.9", 2}, - - {u"", 1000.0, u"1 000", 1000L, 1000.0, u"1000", 0}, - {u"compact-long", 1000.0, u"1 millier", 1000L, 1000.0, u"1000", 3}, - {u"compact-short", 1000.0, u"1 k", 1000L, 1000.0, u"1000", 3}, - {u"scientific", 1000.0, u"1E3", 1000L, 1000.0, u"1000", 3}, + // unlocalized formatter skeleton, input, string output, long output, + // double output, BigDecimal output, plain string, + // suppressed scientific exponent, suppressed compact exponent + {u"", 123456789, u"123 456 789", 123456789L, 123456789.0, u"123456789", 0, 0}, + {u"compact-long", 123456789, u"123 millions", 123000000L, 123000000.0, u"123000000", 6, 6}, + {u"compact-short", 123456789, u"123 M", 123000000L, 123000000.0, u"123000000", 6, 6}, + {u"scientific", 123456789, u"1,234568E8", 123456800L, 123456800.0, u"123456800", 8, 8}, + + {u"", 1234567, u"1 234 567", 1234567L, 1234567.0, u"1234567", 0, 0}, + {u"compact-long", 1234567, u"1,2 million", 1200000L, 1200000.0, u"1200000", 6, 6}, + {u"compact-short", 1234567, u"1,2 M", 1200000L, 1200000.0, u"1200000", 6, 6}, + {u"scientific", 1234567, u"1,234567E6", 1234567L, 1234567.0, u"1234567", 6, 6}, + + {u"", 123456, u"123 456", 123456L, 123456.0, u"123456", 0, 0}, + {u"compact-long", 123456, u"123 mille", 123000L, 123000.0, u"123000", 3, 3}, + {u"compact-short", 123456, u"123 k", 123000L, 123000.0, u"123000", 3, 3}, + {u"scientific", 123456, u"1,23456E5", 123456L, 123456.0, u"123456", 5, 5}, + + {u"", 123, u"123", 123L, 123.0, u"123", 0, 0}, + {u"compact-long", 123, u"123", 123L, 123.0, u"123", 0, 0}, + {u"compact-short", 123, u"123", 123L, 123.0, u"123", 0, 0}, + {u"scientific", 123, u"1,23E2", 123L, 123.0, u"123", 2, 2}, + + {u"", 1.2, u"1,2", 1L, 1.2, u"1.2", 0, 0}, + {u"compact-long", 1.2, u"1,2", 1L, 1.2, u"1.2", 0, 0}, + {u"compact-short", 1.2, u"1,2", 1L, 1.2, u"1.2", 0, 0}, + {u"scientific", 1.2, u"1,2E0", 1L, 1.2, u"1.2", 0, 0}, + + {u"", 0.12, u"0,12", 0L, 0.12, u"0.12", 0, 0}, + {u"compact-long", 0.12, u"0,12", 0L, 0.12, u"0.12", 0, 0}, + {u"compact-short", 0.12, u"0,12", 0L, 0.12, u"0.12", 0, 0}, + {u"scientific", 0.12, u"1,2E-1", 0L, 0.12, u"0.12", -1, -1}, + + {u"", 0.012, u"0,012", 0L, 0.012, u"0.012", 0, 0}, + {u"compact-long", 0.012, u"0,012", 0L, 0.012, u"0.012", 0, 0}, + {u"compact-short", 0.012, u"0,012", 0L, 0.012, u"0.012", 0, 0}, + {u"scientific", 0.012, u"1,2E-2", 0L, 0.012, u"0.012", -2, -2}, + + {u"", 999.9, u"999,9", 999L, 999.9, u"999.9", 0, 0}, + {u"compact-long", 999.9, u"1 millier", 1000L, 1000.0, u"1000", 3, 3}, + {u"compact-short", 999.9, u"1 k", 1000L, 1000.0, u"1000", 3, 3}, + {u"scientific", 999.9, u"9,999E2", 999L, 999.9, u"999.9", 2, 2}, + + {u"", 1000.0, u"1 000", 1000L, 1000.0, u"1000", 0, 0}, + {u"compact-long", 1000.0, u"1 millier", 1000L, 1000.0, u"1000", 3, 3}, + {u"compact-short", 1000.0, u"1 k", 1000L, 1000.0, u"1000", 3, 3}, + {u"scientific", 1000.0, u"1E3", 1000L, 1000.0, u"1000", 3, 3}, }; for (const auto& cas : cases) { // test the helper methods used to compute plural operand values @@ -550,18 +553,19 @@ void DecimalQuantityTest::testCompactDecimalSuppressedExponent() { int64_t actualLong = dq.toLong(); double actualDouble = dq.toDouble(); UnicodeString actualPlainString = dq.toPlainString(); - int32_t actualSuppressedExponent = dq.getExponent(); + int32_t actualSuppressedScientificExponent = dq.getExponent(); + int32_t actualSuppressedCompactExponent = dq.getExponent(); assertEquals( u"formatted number " + cas.skeleton + u" toString: " + cas.input, cas.expectedString, actualString); assertEquals( - u"compact decimal " + cas.skeleton + u" toLong: " + cas.input, + u"formatted number " + cas.skeleton + u" toLong: " + cas.input, cas.expectedLong, actualLong); assertDoubleEquals( - u"compact decimal " + cas.skeleton + u" toDouble: " + cas.input, + u"formatted number " + cas.skeleton + u" toDouble: " + cas.input, cas.expectedDouble, actualDouble); assertEquals( @@ -569,36 +573,46 @@ void DecimalQuantityTest::testCompactDecimalSuppressedExponent() { cas.expectedPlainString, actualPlainString); assertEquals( - u"compact decimal " + cas.skeleton + u" suppressed exponent: " + cas.input, - cas.expectedSuppressedExponent, - actualSuppressedExponent); + u"formatted number " + cas.skeleton + u" suppressed scientific exponent: " + cas.input, + cas.expectedSuppressedScientificExponent, + actualSuppressedScientificExponent); + assertEquals( + u"formatted number " + cas.skeleton + u" suppressed compact exponent: " + cas.input, + cas.expectedSuppressedCompactExponent, + actualSuppressedCompactExponent); // test the actual computed values of the plural operands double expectedNOperand = cas.expectedDouble; double expectedIOperand = cas.expectedLong; - double expectedEOperand = cas.expectedSuppressedExponent; + double expectedEOperand = cas.expectedSuppressedScientificExponent; + double expectedCOperand = cas.expectedSuppressedCompactExponent; double actualNOperand = dq.getPluralOperand(PLURAL_OPERAND_N); double actualIOperand = dq.getPluralOperand(PLURAL_OPERAND_I); double actualEOperand = dq.getPluralOperand(PLURAL_OPERAND_E); + double actualCOperand = dq.getPluralOperand(PLURAL_OPERAND_C); assertDoubleEquals( - u"compact decimal " + cas.skeleton + u" n operand: " + cas.input, + u"formatted number " + cas.skeleton + u" n operand: " + cas.input, expectedNOperand, actualNOperand); assertDoubleEquals( - u"compact decimal " + cas.skeleton + u" i operand: " + cas.input, + u"formatted number " + cas.skeleton + u" i operand: " + cas.input, expectedIOperand, actualIOperand); assertDoubleEquals( - u"compact decimal " + cas.skeleton + " e operand: " + cas.input, + u"formatted number " + cas.skeleton + " e operand: " + cas.input, expectedEOperand, actualEOperand); + assertDoubleEquals( + u"formatted number " + cas.skeleton + " c operand: " + cas.input, + expectedCOperand, + actualCOperand); } } void DecimalQuantityTest::testSuppressedExponentUnchangedByInitialScaling() { - IcuTestErrorCode status(*this, "testCompactDecimalSuppressedExponent"); + IcuTestErrorCode status(*this, "testSuppressedExponentUnchangedByInitialScaling"); Locale ulocale("fr-FR"); LocalizedNumberFormatter withLocale = NumberFormatter::withLocale(ulocale); LocalizedNumberFormatter compactLong = @@ -612,20 +626,22 @@ void DecimalQuantityTest::testSuppressedExponentUnchangedByInitialScaling() { double expectedNOperand; double expectedIOperand; double expectedEOperand; + double expectedCOperand; } cases[] = { // input, compact long string output, - // compact n operand, compact i operand, compact e operand - {123456789, "123 millions", 123000000.0, 123000000.0, 6.0}, - {1234567, "1,2 million", 1200000.0, 1200000.0, 6.0}, - {123456, "123 mille", 123000.0, 123000.0, 3.0}, - {123, "123", 123.0, 123.0, 0.0}, + // compact n operand, compact i operand, compact e operand, + // compact c operand + {123456789, "123 millions", 123000000.0, 123000000.0, 6.0, 6.0}, + {1234567, "1,2 million", 1200000.0, 1200000.0, 6.0, 6.0}, + {123456, "123 mille", 123000.0, 123000.0, 3.0, 3.0}, + {123, "123", 123.0, 123.0, 0.0, 0.0}, }; for (const auto& cas : cases) { FormattedNumber fnCompactScaled = compactScaled.formatInt(cas.input, status); DecimalQuantity dqCompactScaled; fnCompactScaled.getDecimalQuantity(dqCompactScaled, status); - double compactScaledEOperand = dqCompactScaled.getPluralOperand(PLURAL_OPERAND_E); + double compactScaledCOperand = dqCompactScaled.getPluralOperand(PLURAL_OPERAND_C); FormattedNumber fnCompact = compactLong.formatInt(cas.input, status); DecimalQuantity dqCompact; @@ -634,6 +650,7 @@ void DecimalQuantityTest::testSuppressedExponentUnchangedByInitialScaling() { double compactNOperand = dqCompact.getPluralOperand(PLURAL_OPERAND_N); double compactIOperand = dqCompact.getPluralOperand(PLURAL_OPERAND_I); double compactEOperand = dqCompact.getPluralOperand(PLURAL_OPERAND_E); + double compactCOperand = dqCompact.getPluralOperand(PLURAL_OPERAND_C); assertEquals( u"formatted number " + Int64ToUnicodeString(cas.input) + " compactLong toString: ", cas.expectedString, @@ -650,14 +667,18 @@ void DecimalQuantityTest::testSuppressedExponentUnchangedByInitialScaling() { u"compact decimal " + DoubleToUnicodeString(cas.input) + ", e operand vs. expected", cas.expectedEOperand, compactEOperand); + assertDoubleEquals( + u"compact decimal " + DoubleToUnicodeString(cas.input) + ", c operand vs. expected", + cas.expectedCOperand, + compactCOperand); // By scaling by 10^3 in a locale that has words / compact notation // based on powers of 10^3, we guarantee that the suppressed // exponent will differ by 3. assertDoubleEquals( - u"decimal " + DoubleToUnicodeString(cas.input) + ", e operand for compact vs. compact scaled", - compactEOperand + 3, - compactScaledEOperand); + u"decimal " + DoubleToUnicodeString(cas.input) + ", c operand for compact vs. compact scaled", + compactCOperand + 3, + compactScaledCOperand); } } diff --git a/icu4c/source/test/intltest/plurults.cpp b/icu4c/source/test/intltest/plurults.cpp index 9610fb6eeb77..407e484c163a 100644 --- a/icu4c/source/test/intltest/plurults.cpp +++ b/icu4c/source/test/intltest/plurults.cpp @@ -52,8 +52,10 @@ void PluralRulesTest::runIndexedTest( int32_t index, UBool exec, const char* &na TESTCASE_AUTO(testGetSamples); TESTCASE_AUTO(testGetFixedDecimalSamples); TESTCASE_AUTO(testSamplesWithExponent); + TESTCASE_AUTO(testSamplesWithCompactNotation); TESTCASE_AUTO(testWithin); TESTCASE_AUTO(testGetAllKeywordValues); + TESTCASE_AUTO(testScientificPluralKeyword); TESTCASE_AUTO(testCompactDecimalPluralKeyword); TESTCASE_AUTO(testOrdinal); TESTCASE_AUTO(testSelect); @@ -404,7 +406,7 @@ void PluralRulesTest::testGetSamples() { double values[1000]; for (int32_t i = 0; U_SUCCESS(status) && i < numLocales; ++i) { if (uprv_strcmp(locales[i].getLanguage(), "fr") == 0 && - logKnownIssue("21299", "PluralRules::getSamples cannot distinguish 1e5 from 100000")) { + logKnownIssue("21322", "PluralRules::getSamples cannot distinguish 1e5 from 100000")) { continue; } LocalPointer rules(PluralRules::forLocale(locales[i], status)); @@ -465,7 +467,7 @@ void PluralRulesTest::testGetFixedDecimalSamples() { FixedDecimal values[1000]; for (int32_t i = 0; U_SUCCESS(status) && i < numLocales; ++i) { if (uprv_strcmp(locales[i].getLanguage(), "fr") == 0 && - logKnownIssue("21299", "PluralRules::getSamples cannot distinguish 1e5 from 100000")) { + logKnownIssue("21322", "PluralRules::getSamples cannot distinguish 1e5 from 100000")) { continue; } LocalPointer rules(PluralRules::forLocale(locales[i], status)); @@ -551,6 +553,44 @@ void PluralRulesTest::testSamplesWithExponent() { checkNewSamples(description2, test2, u"other", u"@decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, 2.1e5, 3.1e5, 4.1e5, 5.1e5, 6.1e5, 7.1e5, …", FixedDecimal(2.0, 1)); } + +void PluralRulesTest::testSamplesWithCompactNotation() { + // integer samples + UErrorCode status = U_ZERO_ERROR; + UnicodeString description( + u"one: i = 0,1 @integer 0, 1, 1c5 @decimal 0.0~1.5, 1.1c5; " + u"many: c = 0 and i != 0 and i % 1000000 = 0 and v = 0 or c != 0..5" + u" @integer 1000000, 2c6, 3c6, 4c6, 5c6, 6c6, 7c6, … @decimal 2.1c6, 3.1c6, 4.1c6, 5.1c6, 6.1c6, 7.1c6, …; " + u"other: @integer 2~17, 100, 1000, 10000, 100000, 2c5, 3c5, 4c5, 5c5, 6c5, 7c5, …" + u" @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, 2.1c5, 3.1c5, 4.1c5, 5.1c5, 6.1c5, 7.1c5, …" + ); + LocalPointer test(PluralRules::createRules(description, status)); + if (U_FAILURE(status)) { + errln("Couldn't create plural rules from a string using exponent notation, with error = %s", u_errorName(status)); + return; + } + checkNewSamples(description, test, u"one", u"@integer 0, 1, 1c5", FixedDecimal(0)); + checkNewSamples(description, test, u"many", u"@integer 1000000, 2c6, 3c6, 4c6, 5c6, 6c6, 7c6, …", FixedDecimal(1000000)); + checkNewSamples(description, test, u"other", u"@integer 2~17, 100, 1000, 10000, 100000, 2c5, 3c5, 4c5, 5c5, 6c5, 7c5, …", FixedDecimal(2)); + + // decimal samples + status = U_ZERO_ERROR; + UnicodeString description2( + u"one: i = 0,1 @decimal 0.0~1.5, 1.1c5; " + u"many: c = 0 and i != 0 and i % 1000000 = 0 and v = 0 or c != 0..5" + u" @decimal 2.1c6, 3.1c6, 4.1c6, 5.1c6, 6.1c6, 7.1c6, …; " + u"other: @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, 2.1c5, 3.1c5, 4.1c5, 5.1c5, 6.1c5, 7.1c5, …" + ); + LocalPointer test2(PluralRules::createRules(description2, status)); + if (U_FAILURE(status)) { + errln("Couldn't create plural rules from a string using exponent notation, with error = %s", u_errorName(status)); + return; + } + checkNewSamples(description2, test2, u"one", u"@decimal 0.0~1.5, 1.1c5", FixedDecimal(0, 1)); + checkNewSamples(description2, test2, u"many", u"@decimal 2.1c6, 3.1c6, 4.1c6, 5.1c6, 6.1c6, 7.1c6, …", FixedDecimal::createWithExponent(2.1, 1, 6)); + checkNewSamples(description2, test2, u"other", u"@decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, 2.1c5, 3.1c5, 4.1c5, 5.1c5, 6.1c5, 7.1c5, …", FixedDecimal(2.0, 1)); +} + void PluralRulesTest::checkNewSamples( UnicodeString description, const LocalPointer &test, @@ -727,13 +767,77 @@ PluralRulesTest::testGetAllKeywordValues() { } } +// For the time being, the compact notation exponent operand `c` is an alias +// for the scientific exponent operand `e` and compact notation. +void +PluralRulesTest::testScientificPluralKeyword() { + IcuTestErrorCode errorCode(*this, "testScientificPluralKeyword"); + + LocalPointer rules(PluralRules::createRules( + u"one: i = 0,1 @integer 0, 1 @decimal 0.0~1.5; " + u"many: e = 0 and i % 1000000 = 0 and v = 0 or e != 0 .. 5; " + u"other: @integer 2~17, 100, 1000, 10000, 100000, 1000000, " + u" @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …", errorCode)); + + if (U_FAILURE(errorCode)) { + errln("Couldn't instantiate plurals rules from string, with error = %s", u_errorName(errorCode)); + return; + } + + const char* localeName = "fr-FR"; + Locale locale = Locale::createFromName(localeName); + + struct TestCase { + const char16_t* skeleton; + const int input; + const char16_t* expectedFormattedOutput; + const char16_t* expectedPluralRuleKeyword; + } cases[] = { + // unlocalized formatter skeleton, input, string output, plural rule keyword + {u"", 0, u"0", u"one"}, + {u"scientific", 0, u"0", u"one"}, + + {u"", 1, u"1", u"one"}, + {u"scientific", 1, u"1", u"one"}, + + {u"", 2, u"2", u"other"}, + {u"scientific", 2, u"2", u"other"}, + + {u"", 1000000, u"1 000 000", u"many"}, + {u"scientific", 1000000, u"1 million", u"many"}, + + {u"", 1000001, u"1 000 001", u"other"}, + {u"scientific", 1000001, u"1 million", u"many"}, + + {u"", 120000, u"1 200 000", u"other"}, + {u"scientific", 1200000, u"1,2 millions", u"many"}, + + {u"", 1200001, u"1 200 001", u"other"}, + {u"scientific", 1200001, u"1,2 millions", u"many"}, + + {u"", 2000000, u"2 000 000", u"many"}, + {u"scientific", 2000000, u"2 millions", u"many"}, + }; + for (const auto& cas : cases) { + const char16_t* skeleton = cas.skeleton; + const int input = cas.input; + const char16_t* expectedPluralRuleKeyword = cas.expectedPluralRuleKeyword; + + UnicodeString actualPluralRuleKeyword = + getPluralKeyword(rules, locale, input, skeleton); + + UnicodeString message(UnicodeString(localeName) + u" " + DoubleToUnicodeString(input)); + assertEquals(message, expectedPluralRuleKeyword, actualPluralRuleKeyword); + } +} + void PluralRulesTest::testCompactDecimalPluralKeyword() { IcuTestErrorCode errorCode(*this, "testCompactDecimalPluralKeyword"); LocalPointer rules(PluralRules::createRules( u"one: i = 0,1 @integer 0, 1 @decimal 0.0~1.5; " - u"many: e = 0 and i % 1000000 = 0 and v = 0 or e != 0 .. 5; " + u"many: c = 0 and i % 1000000 = 0 and v = 0 or c != 0 .. 5; " u"other: @integer 2~17, 100, 1000, 10000, 100000, 1000000, " u" @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …", errorCode)); diff --git a/icu4c/source/test/intltest/plurults.h b/icu4c/source/test/intltest/plurults.h index 8e220dc3661c..927a24d94a42 100644 --- a/icu4c/source/test/intltest/plurults.h +++ b/icu4c/source/test/intltest/plurults.h @@ -32,9 +32,11 @@ class PluralRulesTest : public IntlTest { void testGetSamples(); void testGetFixedDecimalSamples(); void testSamplesWithExponent(); + void testSamplesWithCompactNotation(); void testWithin(); void testGetAllKeywordValues(); void testCompactDecimalPluralKeyword(); + void testScientificPluralKeyword(); void testOrdinal(); void testSelect(); void testSelectRange(); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java index bce34004783c..2d98cc2e098f 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java @@ -265,6 +265,9 @@ public double getPluralOperand(Operand operand) { return fractionCountWithoutTrailingZeros(); case e: return getExponent(); + case c: + // Plural operand `c` is currently an alias for `e`. + return getExponent(); default: return Math.abs(toDouble()); } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/PluralRules.java b/icu4j/main/classes/core/src/com/ibm/icu/text/PluralRules.java index b302b4e6fe5b..870b9476b8f9 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/PluralRules.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/PluralRules.java @@ -481,8 +481,8 @@ public static enum Operand { w, /** - * Suppressed exponent for compact notation (exponent needed in - * scientific notation with compact notation to approximate i). + * Suppressed exponent for scientific notation (exponent needed in + * scientific notation to approximate i). * * @internal * @deprecated This API is ICU internal only. @@ -490,6 +490,19 @@ public static enum Operand { @Deprecated e, + /** + * This operand is currently treated as an alias for `PLURAL_OPERAND_E`. + * In the future, it will represent: + * + * Suppressed exponent for compact notation (exponent needed in + * compact notation to approximate i). + * + * @internal + * @deprecated This API is ICU internal only. + */ + @Deprecated + c, + /** * THIS OPERAND IS DEPRECATED AND HAS BEEN REMOVED FROM THE SPEC. * @@ -657,10 +670,11 @@ public int getBaseFactor() { * @param v number of digits to the right of the decimal place. e.g 1.00 = 2 25. = 0 * @param f Corresponds to f in the plural rules grammar. * The digits to the right of the decimal place as an integer. e.g 1.10 = 10 - * @param e Suppressed exponent for scientific and compact notation + * @param e Suppressed exponent for scientific notation + * @param c Currently: an alias for param `e` */ @Deprecated - public FixedDecimal(double n, int v, long f, int e) { + public FixedDecimal(double n, int v, long f, int e, int c) { isNegative = n < 0; source = isNegative ? -n : n; visibleDecimalDigitCount = v; @@ -668,7 +682,11 @@ public FixedDecimal(double n, int v, long f, int e) { integerValue = n > MAX ? MAX : (long)n; - exponent = e; + int initExpVal = e; + if (initExpVal == 0) { + initExpVal = c; + } + exponent = initExpVal; hasIntegerValue = source == integerValue; // check values. TODO make into unit test. // @@ -699,6 +717,15 @@ public FixedDecimal(double n, int v, long f, int e) { baseFactor = (int) Math.pow(10, v); } + /** + * @internal CLDR + * @deprecated This API is ICU internal only. + */ + @Deprecated + public FixedDecimal(double n, int v, long f, int e) { + this(n, v, f, e, e); + } + /** * @internal CLDR * @deprecated This API is ICU internal only. @@ -848,8 +875,11 @@ public FixedDecimal (String n) { */ @Deprecated private static FixedDecimal parseDecimalSampleRangeNumString(String num) { - if (num.contains("e")) { + if (num.contains("e") || num.contains("c")) { int ePos = num.lastIndexOf('e'); + if (ePos < 0) { + ePos = num.lastIndexOf('c'); + } int expNumPos = ePos + 1; String exponentStr = num.substring(expNumPos); int exponent = Integer.parseInt(exponentStr); @@ -890,6 +920,7 @@ public double getPluralOperand(Operand operand) { case v: return visibleDecimalDigitCount; case w: return visibleDecimalDigitCountWithoutTrailingZeros; case e: return exponent; + case c: return exponent; default: return source; } } @@ -970,10 +1001,10 @@ public int hashCode() { @Override public String toString() { String baseString = String.format(Locale.ROOT, "%." + visibleDecimalDigitCount + "f", source); - if (exponent == 0) { - return baseString; - } else { + if (exponent != 0) { return baseString + "e" + exponent; + } else { + return baseString; } } diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/PluralRulesTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/PluralRulesTest.java index bca6fc99e321..b08f45282dfc 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/PluralRulesTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/PluralRulesTest.java @@ -219,6 +219,37 @@ public void testSamplesWithExponent() { new FixedDecimal(2.0, 1)); } + /** + * This test is for the support of X.YcZ compactnotation of numbers in + * the plural sample string. + */ + @Test + public void testSamplesWithCompactNotation() { + String description = "one: i = 0,1 @integer 0, 1, 1c5 @decimal 0.0~1.5, 1.1c5; " + + "many: c = 0 and i != 0 and i % 1000000 = 0 and v = 0 or c != 0..5" + + " @integer 1000000, 2c6, 3c6, 4c6, 5c6, 6c6, 7c6, … @decimal 2.1c6, 3.1c6, 4.1c6, 5.1c6, 6.1c6, 7.1c6, …; " + + "other: @integer 2~17, 100, 1000, 10000, 100000, 2c5, 3c5, 4c5, 5c5, 6c5, 7c5, …" + + " @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, 2.1c5, 3.1c5, 4.1c5, 5.1c5, 6.1c5, 7.1c5, …" + ; + // Creating the PluralRules object means being able to parse numbers + // like 1c5 and 1.1c5. + // Note: Since `c` is currently an alias to `e`, the toString() of + // FixedDecimal will return "1e5" even when input is "1c5". + PluralRules test = PluralRules.createRules(description); + checkNewSamples(description, test, "one", PluralRules.SampleType.INTEGER, "@integer 0, 1, 1e5", true, + new FixedDecimal(0)); + checkNewSamples(description, test, "one", PluralRules.SampleType.DECIMAL, "@decimal 0.0~1.5, 1.1e5", true, + new FixedDecimal(0, 1)); + checkNewSamples(description, test, "many", PluralRules.SampleType.INTEGER, "@integer 1000000, 2e6, 3e6, 4e6, 5e6, 6e6, 7e6, …", false, + new FixedDecimal(1000000)); + checkNewSamples(description, test, "many", PluralRules.SampleType.DECIMAL, "@decimal 2.1e6, 3.1e6, 4.1e6, 5.1e6, 6.1e6, 7.1e6, …", false, + FixedDecimal.createWithExponent(2.1, 1, 6)); + checkNewSamples(description, test, "other", PluralRules.SampleType.INTEGER, "@integer 2~17, 100, 1000, 10000, 100000, 2e5, 3e5, 4e5, 5e5, 6e5, 7e5, …", false, + new FixedDecimal(2)); + checkNewSamples(description, test, "other", PluralRules.SampleType.DECIMAL, "@decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, 2.1e5, 3.1e5, 4.1e5, 5.1e5, 6.1e5, 7.1e5, …", false, + new FixedDecimal(2.0, 1)); + } + public void checkOldSamples(String description, PluralRules rules, String keyword, SampleType sampleType, Double... expected) { Collection oldSamples = rules.getSamples(keyword, sampleType); @@ -713,7 +744,7 @@ public void TestGetSamples() { } for (ULocale locale : uniqueRuleSet) { if (locale.getLanguage().equals("fr") && - logKnownIssue("21299", "PluralRules::getSamples cannot distinguish 1e5 from 100000")) { + logKnownIssue("21322", "PluralRules::getSamples cannot distinguish 1e5 from 100000")) { continue; } PluralRules rules = factory.forLocale(locale); @@ -968,12 +999,61 @@ public void TestKeywords() { } } + // For the time being, the compact notation exponent operand `c` is an alias + // for the scientific exponent operand `e` and compact notation. + @Test + public void testScientificPluralKeyword() { + PluralRules rules = PluralRules.createRules("one: i = 0,1 @integer 0, 1 @decimal 0.0~1.5; many: e = 0 and i % 1000000 = 0 and v = 0 or " + + "e != 0 .. 5; other: @integer 2~17, 100, 1000, 10000, 100000, 1000000, @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"); + ULocale locale = new ULocale("fr-FR"); + Object[][] casesData = { + // unlocalized formatter skeleton, input, string output, plural rule keyword + {"", 0, "0", "one"}, + {"scientific", 0, "0", "one"}, + + {"", 1, "1", "one"}, + {"scientific", 1, "1", "one"}, + + {"", 2, "2", "other"}, + {"scientific", 2, "2", "other"}, + + {"", 1000000, "1 000 000", "many"}, + {"scientific", 1000000, "1 million", "many"}, + + {"", 1000001, "1 000 001", "other"}, + {"scientific", 1000001, "1 million", "many"}, + + {"", 120000, "1 200 000", "other"}, + {"scientific", 1200000, "1,2 millions", "many"}, + + {"", 1200001, "1 200 001", "other"}, + {"scientific", 1200001, "1,2 millions", "many"}, + + {"", 2000000, "2 000 000", "many"}, + {"scientific", 2000000, "2 millions", "many"}, + }; + + for (Object[] caseDatum : casesData) { + String skeleton = (String) caseDatum[0]; + int input = (int) caseDatum[1]; + // String expectedString = (String) caseDatum[2]; + String expectPluralRuleKeyword = (String) caseDatum[3]; + + String actualPluralRuleKeyword = + getPluralKeyword(rules, locale, input, skeleton); + + assertEquals( + String.format("PluralRules select %s: %d", skeleton, input), + expectPluralRuleKeyword, + actualPluralRuleKeyword); + } + } @Test public void testCompactDecimalPluralKeyword() { - PluralRules rules = PluralRules.createRules("one: i = 0,1 @integer 0, 1 @decimal 0.0~1.5; many: e = 0 and i % 1000000 = 0 and v = 0 or " + - "e != 0 .. 5; other: @integer 2~17, 100, 1000, 10000, 100000, 1000000, @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"); + PluralRules rules = PluralRules.createRules("one: i = 0,1 @integer 0, 1 @decimal 0.0~1.5; many: c = 0 and i % 1000000 = 0 and v = 0 or " + + "c != 0 .. 5; other: @integer 2~17, 100, 1000, 10000, 100000, 1000000, @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"); ULocale locale = new ULocale("fr-FR"); Object[][] casesData = { diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java index 9131382ffe4e..ec2d59821976 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java @@ -609,55 +609,57 @@ public void testNickelRounding() { } @Test - public void testCompactDecimalSuppressedExponent() { + public void testScientificAndCompactSuppressedExponent() { ULocale locale = new ULocale("fr-FR"); Object[][] casesData = { - // unlocalized formatter skeleton, input, string output, long output, double output, BigDecimal output, plain string, suppressed exponent - {"", 123456789, "123 456 789", 123456789L, 123456789.0, new BigDecimal("123456789"), "123456789", 0}, - {"compact-long", 123456789, "123 millions", 123000000L, 123000000.0, new BigDecimal("123000000"), "123000000", 6}, - {"compact-short", 123456789, "123 M", 123000000L, 123000000.0, new BigDecimal("123000000"), "123000000", 6}, - {"scientific", 123456789, "1,234568E8", 123456800L, 123456800.0, new BigDecimal("123456800"), "123456800", 8}, - - {"", 1234567, "1 234 567", 1234567L, 1234567.0, new BigDecimal("1234567"), "1234567", 0}, - {"compact-long", 1234567, "1,2 million", 1200000L, 1200000.0, new BigDecimal("1200000"), "1200000", 6}, - {"compact-short", 1234567, "1,2 M", 1200000L, 1200000.0, new BigDecimal("1200000"), "1200000", 6}, - {"scientific", 1234567, "1,234567E6", 1234567L, 1234567.0, new BigDecimal("1234567"), "1234567", 6}, - - {"", 123456, "123 456", 123456L, 123456.0, new BigDecimal("123456"), "123456", 0}, - {"compact-long", 123456, "123 mille", 123000L, 123000.0, new BigDecimal("123000"), "123000", 3}, - {"compact-short", 123456, "123 k", 123000L, 123000.0, new BigDecimal("123000"), "123000", 3}, - {"scientific", 123456, "1,23456E5", 123456L, 123456.0, new BigDecimal("123456"), "123456", 5}, - - {"", 123, "123", 123L, 123.0, new BigDecimal("123"), "123", 0}, - {"compact-long", 123, "123", 123L, 123.0, new BigDecimal("123"), "123", 0}, - {"compact-short", 123, "123", 123L, 123.0, new BigDecimal("123"), "123", 0}, - {"scientific", 123, "1,23E2", 123L, 123.0, new BigDecimal("123"), "123", 2}, - - {"", 1.2, "1,2", 1L, 1.2, new BigDecimal("1.2"), "1.2", 0}, - {"compact-long", 1.2, "1,2", 1L, 1.2, new BigDecimal("1.2"), "1.2", 0}, - {"compact-short", 1.2, "1,2", 1L, 1.2, new BigDecimal("1.2"), "1.2", 0}, - {"scientific", 1.2, "1,2E0", 1L, 1.2, new BigDecimal("1.2"), "1.2", 0}, - - {"", 0.12, "0,12", 0L, 0.12, new BigDecimal("0.12"), "0.12", 0}, - {"compact-long", 0.12, "0,12", 0L, 0.12, new BigDecimal("0.12"), "0.12", 0}, - {"compact-short", 0.12, "0,12", 0L, 0.12, new BigDecimal("0.12"), "0.12", 0}, - {"scientific", 0.12, "1,2E-1", 0L, 0.12, new BigDecimal("0.12"), "0.12", -1}, - - {"", 0.012, "0,012", 0L, 0.012, new BigDecimal("0.012"), "0.012", 0}, - {"compact-long", 0.012, "0,012", 0L, 0.012, new BigDecimal("0.012"), "0.012", 0}, - {"compact-short", 0.012, "0,012", 0L, 0.012, new BigDecimal("0.012"), "0.012", 0}, - {"scientific", 0.012, "1,2E-2", 0L, 0.012, new BigDecimal("0.012"), "0.012", -2}, - - {"", 999.9, "999,9", 999L, 999.9, new BigDecimal("999.9"), "999.9", 0}, - {"compact-long", 999.9, "1 millier", 1000L, 1000.0, new BigDecimal("1000"), "1000", 3}, - {"compact-short", 999.9, "1 k", 1000L, 1000.0, new BigDecimal("1000"), "1000", 3}, - {"scientific", 999.9, "9,999E2", 999L, 999.9, new BigDecimal("999.9"), "999.9", 2}, - - {"", 1000.0, "1 000", 1000L, 1000.0, new BigDecimal("1000"), "1000", 0}, - {"compact-long", 1000.0, "1 millier", 1000L, 1000.0, new BigDecimal("1000"), "1000", 3}, - {"compact-short", 1000.0, "1 k", 1000L, 1000.0, new BigDecimal("1000"), "1000", 3}, - {"scientific", 1000.0, "1E3", 1000L, 1000.0, new BigDecimal("1000"), "1000", 3}, + // unlocalized formatter skeleton, input, string output, long output, + // double output, BigDecimal output, plain string, + // suppressed scientific exponent, suppressed compact exponent + {"", 123456789, "123 456 789", 123456789L, 123456789.0, new BigDecimal("123456789"), "123456789", 0, 0}, + {"compact-long", 123456789, "123 millions", 123000000L, 123000000.0, new BigDecimal("123000000"), "123000000", 6, 6}, + {"compact-short", 123456789, "123 M", 123000000L, 123000000.0, new BigDecimal("123000000"), "123000000", 6, 6}, + {"scientific", 123456789, "1,234568E8", 123456800L, 123456800.0, new BigDecimal("123456800"), "123456800", 8, 8}, + + {"", 1234567, "1 234 567", 1234567L, 1234567.0, new BigDecimal("1234567"), "1234567", 0, 0}, + {"compact-long", 1234567, "1,2 million", 1200000L, 1200000.0, new BigDecimal("1200000"), "1200000", 6, 6}, + {"compact-short", 1234567, "1,2 M", 1200000L, 1200000.0, new BigDecimal("1200000"), "1200000", 6, 6}, + {"scientific", 1234567, "1,234567E6", 1234567L, 1234567.0, new BigDecimal("1234567"), "1234567", 6, 6}, + + {"", 123456, "123 456", 123456L, 123456.0, new BigDecimal("123456"), "123456", 0, 0}, + {"compact-long", 123456, "123 mille", 123000L, 123000.0, new BigDecimal("123000"), "123000", 3, 3}, + {"compact-short", 123456, "123 k", 123000L, 123000.0, new BigDecimal("123000"), "123000", 3, 3}, + {"scientific", 123456, "1,23456E5", 123456L, 123456.0, new BigDecimal("123456"), "123456", 5, 5}, + + {"", 123, "123", 123L, 123.0, new BigDecimal("123"), "123", 0, 0}, + {"compact-long", 123, "123", 123L, 123.0, new BigDecimal("123"), "123", 0, 0}, + {"compact-short", 123, "123", 123L, 123.0, new BigDecimal("123"), "123", 0, 0}, + {"scientific", 123, "1,23E2", 123L, 123.0, new BigDecimal("123"), "123", 2, 2}, + + {"", 1.2, "1,2", 1L, 1.2, new BigDecimal("1.2"), "1.2", 0, 0}, + {"compact-long", 1.2, "1,2", 1L, 1.2, new BigDecimal("1.2"), "1.2", 0, 0}, + {"compact-short", 1.2, "1,2", 1L, 1.2, new BigDecimal("1.2"), "1.2", 0, 0}, + {"scientific", 1.2, "1,2E0", 1L, 1.2, new BigDecimal("1.2"), "1.2", 0, 0}, + + {"", 0.12, "0,12", 0L, 0.12, new BigDecimal("0.12"), "0.12", 0, 0}, + {"compact-long", 0.12, "0,12", 0L, 0.12, new BigDecimal("0.12"), "0.12", 0, 0}, + {"compact-short", 0.12, "0,12", 0L, 0.12, new BigDecimal("0.12"), "0.12", 0, 0}, + {"scientific", 0.12, "1,2E-1", 0L, 0.12, new BigDecimal("0.12"), "0.12", -1, -1}, + + {"", 0.012, "0,012", 0L, 0.012, new BigDecimal("0.012"), "0.012", 0, 0}, + {"compact-long", 0.012, "0,012", 0L, 0.012, new BigDecimal("0.012"), "0.012", 0, 0}, + {"compact-short", 0.012, "0,012", 0L, 0.012, new BigDecimal("0.012"), "0.012", 0, 0}, + {"scientific", 0.012, "1,2E-2", 0L, 0.012, new BigDecimal("0.012"), "0.012", -2, -2}, + + {"", 999.9, "999,9", 999L, 999.9, new BigDecimal("999.9"), "999.9", 0, 0}, + {"compact-long", 999.9, "1 millier", 1000L, 1000.0, new BigDecimal("1000"), "1000", 3, 3}, + {"compact-short", 999.9, "1 k", 1000L, 1000.0, new BigDecimal("1000"), "1000", 3, 3}, + {"scientific", 999.9, "9,999E2", 999L, 999.9, new BigDecimal("999.9"), "999.9", 2, 2}, + + {"", 1000.0, "1 000", 1000L, 1000.0, new BigDecimal("1000"), "1000", 0, 0}, + {"compact-long", 1000.0, "1 millier", 1000L, 1000.0, new BigDecimal("1000"), "1000", 3, 3}, + {"compact-short", 1000.0, "1 k", 1000L, 1000.0, new BigDecimal("1000"), "1000", 3, 3}, + {"scientific", 1000.0, "1E3", 1000L, 1000.0, new BigDecimal("1000"), "1000", 3, 3}, }; for (Object[] caseDatum : casesData) { @@ -673,7 +675,8 @@ public void testCompactDecimalSuppressedExponent() { double expectedDouble = (double) caseDatum[4]; BigDecimal expectedBigDecimal = (BigDecimal) caseDatum[5]; String expectedPlainString = (String) caseDatum[6]; - int expectedSuppressedExponent = (int) caseDatum[7]; + int expectedSuppressedScientificExponent = (int) caseDatum[7]; + int expectedSuppressedCompactExponent = (int) caseDatum[8]; FormattedNumber fn = formatter.format(input); DecimalQuantity_DualStorageBCD dq = (DecimalQuantity_DualStorageBCD) @@ -683,22 +686,23 @@ public void testCompactDecimalSuppressedExponent() { double actualDouble = dq.toDouble(); BigDecimal actualBigDecimal = dq.toBigDecimal(); String actualPlainString = dq.toPlainString(); - int actualSuppressedExponent = dq.getExponent(); + int actualSuppressedScientificExponent = dq.getExponent(); + int actualSuppressedCompactExponent = dq.getExponent(); assertEquals( String.format("formatted number %s toString: %f", skeleton, input), expectedString, actualString); assertEquals( - String.format("compact decimal %s toLong: %f", skeleton, input), + String.format("formatted number %s toLong: %f", skeleton, input), expectedLong, actualLong); assertDoubleEquals( - String.format("compact decimal %s toDouble: %f", skeleton, input), + String.format("formatted number %s toDouble: %f", skeleton, input), expectedDouble, actualDouble); assertBigDecimalEquals( - String.format("compact decimal %s toBigDecimal: %f", skeleton, input), + String.format("formatted number %s toBigDecimal: %f", skeleton, input), expectedBigDecimal, actualBigDecimal); assertEquals( @@ -706,35 +710,45 @@ public void testCompactDecimalSuppressedExponent() { expectedPlainString, actualPlainString); assertEquals( - String.format("compact decimal %s suppressed exponent: %f", skeleton, input), - expectedSuppressedExponent, - actualSuppressedExponent); + String.format("formatted number %s suppressed scientific exponent: %f", skeleton, input), + expectedSuppressedScientificExponent, + actualSuppressedScientificExponent); + assertEquals( + String.format("formatted number %s suppressed compact exponent: %f", skeleton, input), + expectedSuppressedCompactExponent, + actualSuppressedCompactExponent); // test the actual computed values of the plural operands double expectedNOperand = expectedDouble; double expectedIOperand = expectedLong; - double expectedEOperand = expectedSuppressedExponent; + double expectedEOperand = expectedSuppressedScientificExponent; + double expectedCOperand = expectedSuppressedCompactExponent; double actualNOperand = dq.getPluralOperand(Operand.n); double actualIOperand = dq.getPluralOperand(Operand.i); double actualEOperand = dq.getPluralOperand(Operand.e); + double actualCOperand = dq.getPluralOperand(Operand.c); assertEquals( String.format("formatted number %s toString: %s", skeleton, input), expectedString, actualString); assertDoubleEquals( - String.format("compact decimal %s n operand: %f", skeleton, input), + String.format("formatted number %s n operand: %f", skeleton, input), expectedNOperand, actualNOperand); assertDoubleEquals( - String.format("compact decimal %s i operand: %f", skeleton, input), + String.format("formatted number %s i operand: %f", skeleton, input), expectedIOperand, actualIOperand); assertDoubleEquals( - String.format("compact decimal %s e operand: %f", skeleton, input), + String.format("formatted number %s e operand: %f", skeleton, input), expectedEOperand, actualEOperand); + assertDoubleEquals( + String.format("formatted number %s c operand: %f", skeleton, input), + expectedCOperand, + actualCOperand); } } @@ -760,6 +774,7 @@ public void testCompactNotationFractionPluralOperands() { double expectedVOperand = 2; double expectedWOperand = 1; double expectedEOperand = 3; + double expectedCOperand = 3; String expectedString = "1,23450 millier"; double actualNOperand = dq.getPluralOperand(Operand.n); double actualIOperand = dq.getPluralOperand(Operand.i); @@ -768,6 +783,7 @@ public void testCompactNotationFractionPluralOperands() { double actualVOperand = dq.getPluralOperand(Operand.v); double actualWOperand = dq.getPluralOperand(Operand.w); double actualEOperand = dq.getPluralOperand(Operand.e); + double actualCOperand = dq.getPluralOperand(Operand.c); String actualString = fn.toString(); assertDoubleEquals( @@ -798,6 +814,10 @@ public void testCompactNotationFractionPluralOperands() { String.format("compact decimal fraction e operand: %f", inputVal), expectedEOperand, actualEOperand); + assertDoubleEquals( + String.format("compact decimal fraction c operand: %f", inputVal), + expectedCOperand, + actualCOperand); assertEquals( String.format("compact decimal fraction toString: %f", inputVal), expectedString, @@ -815,11 +835,12 @@ public void testSuppressedExponentUnchangedByInitialScaling() { Object[][] casesData = { // input, compact long string output, - // compact n operand, compact i operand, compact e operand - {123456789, "123 millions", 123000000.0, 123000000.0, 6.0}, - {1234567, "1,2 million", 1200000.0, 1200000.0, 6.0}, - {123456, "123 mille", 123000.0, 123000.0, 3.0}, - {123, "123", 123.0, 123.0, 0.0}, + // compact n operand, compact i operand, compact e operand, + // compact c operand + {123456789, "123 millions", 123000000.0, 123000000.0, 6.0, 6.0}, + {1234567, "1,2 million", 1200000.0, 1200000.0, 6.0, 6.0}, + {123456, "123 mille", 123000.0, 123000.0, 3.0, 3.0}, + {123, "123", 123.0, 123.0, 0.0, 0.0}, }; for (Object[] caseDatum : casesData) { @@ -828,11 +849,12 @@ public void testSuppressedExponentUnchangedByInitialScaling() { double expectedNOperand = (double) caseDatum[2]; double expectedIOperand = (double) caseDatum[3]; double expectedEOperand = (double) caseDatum[4]; + double expectedCOperand = (double) caseDatum[5]; FormattedNumber fnCompactScaled = compactScaled.format(input); DecimalQuantity_DualStorageBCD dqCompactScaled = (DecimalQuantity_DualStorageBCD) fnCompactScaled.getFixedDecimal(); - double compactScaledEOperand = dqCompactScaled.getPluralOperand(Operand.e); + double compactScaledCOperand = dqCompactScaled.getPluralOperand(Operand.c); FormattedNumber fnCompact = compactLong.format(input); DecimalQuantity_DualStorageBCD dqCompact = @@ -841,6 +863,7 @@ public void testSuppressedExponentUnchangedByInitialScaling() { double compactNOperand = dqCompact.getPluralOperand(Operand.n); double compactIOperand = dqCompact.getPluralOperand(Operand.i); double compactEOperand = dqCompact.getPluralOperand(Operand.e); + double compactCOperand = dqCompact.getPluralOperand(Operand.c); assertEquals( String.format("formatted number compactLong toString: %s", input), expectedString, @@ -857,14 +880,18 @@ public void testSuppressedExponentUnchangedByInitialScaling() { String.format("compact decimal %d, e operand vs. expected", input), expectedEOperand, compactEOperand); + assertDoubleEquals( + String.format("compact decimal %d, c operand vs. expected", input), + expectedCOperand, + compactCOperand); // By scaling by 10^3 in a locale that has words / compact notation // based on powers of 10^3, we guarantee that the suppressed // exponent will differ by 3. assertDoubleEquals( - String.format("decimal %d, e operand for compact vs. compact scaled", input), - compactEOperand + 3, - compactScaledEOperand); + String.format("decimal %d, c operand for compact vs. compact scaled", input), + compactCOperand + 3, + compactScaledCOperand); } }