From 6700602974c0a7ece4c2adced6629010dd1f7bc1 Mon Sep 17 00:00:00 2001 From: Elango Cheran Date: Tue, 22 Sep 2020 18:16:21 -0700 Subject: [PATCH] ICU-21270 Support exponent in FixedDecimal and samples in C++ --- icu4c/source/i18n/plurrule.cpp | 148 +++++++++-- icu4c/source/i18n/plurrule_impl.h | 16 ++ icu4c/source/i18n/unicode/plurrule.h | 27 ++ icu4c/source/test/intltest/plurults.cpp | 314 +++++++++++++++++------- icu4c/source/test/intltest/plurults.h | 7 + 5 files changed, 394 insertions(+), 118 deletions(-) diff --git a/icu4c/source/i18n/plurrule.cpp b/icu4c/source/i18n/plurrule.cpp index 7ecf1bc2b3a..e1e1667a6ea 100644 --- a/icu4c/source/i18n/plurrule.cpp +++ b/icu4c/source/i18n/plurrule.cpp @@ -378,9 +378,23 @@ static double scaleForInt(double d) { return scale; } +/** + * Helper method for the overrides of getSamples() for double and FixedDecimal + * return value types. Provide only one of an allocated array of doubles or + * FixedDecimals, and a nullptr for the other. + */ static int32_t -getSamplesFromString(const UnicodeString &samples, double *dest, - int32_t destCapacity, UErrorCode& status) { +getSamplesFromString(const UnicodeString &samples, double *destDbl, + FixedDecimal* destFd, int32_t destCapacity, + UErrorCode& status) { + + if ((destDbl == nullptr && destFd == nullptr) + || (destDbl != nullptr && destFd != nullptr)) { + status = U_INTERNAL_PROGRAM_ERROR; + return 0; + } + + bool isDouble = destDbl != nullptr; int32_t sampleCount = 0; int32_t sampleStartIdx = 0; int32_t sampleEndIdx = 0; @@ -398,9 +412,13 @@ getSamplesFromString(const UnicodeString &samples, double *dest, int32_t tildeIndex = sampleRange.indexOf(TILDE); if (tildeIndex < 0) { FixedDecimal fixed(sampleRange, status); - double sampleValue = fixed.source; - if (fixed.visibleDecimalDigitCount == 0 || sampleValue != floor(sampleValue)) { - dest[sampleCount++] = sampleValue; + if (isDouble) { + double sampleValue = fixed.source; + if (fixed.visibleDecimalDigitCount == 0 || sampleValue != floor(sampleValue)) { + destDbl[sampleCount++] = sampleValue; + } + } else { + destFd[sampleCount++] = fixed; } } else { @@ -427,14 +445,21 @@ getSamplesFromString(const UnicodeString &samples, double *dest, rangeLo *= scale; rangeHi *= scale; for (double n=rangeLo; n<=rangeHi; n+=1) { - // Hack Alert: don't return any decimal samples with integer values that - // originated from a format with trailing decimals. - // This API is returning doubles, which can't distinguish having displayed - // zeros to the right of the decimal. - // This results in test failures with values mapping back to a different keyword. double sampleValue = n/scale; - if (!(sampleValue == floor(sampleValue) && fixedLo.visibleDecimalDigitCount > 0)) { - dest[sampleCount++] = sampleValue; + if (isDouble) { + // Hack Alert: don't return any decimal samples with integer values that + // originated from a format with trailing decimals. + // This API is returning doubles, which can't distinguish having displayed + // zeros to the right of the decimal. + // This results in test failures with values mapping back to a different keyword. + if (!(sampleValue == floor(sampleValue) && fixedLo.visibleDecimalDigitCount > 0)) { + destDbl[sampleCount++] = sampleValue; + } + } else { + int32_t v = (int32_t) fixedLo.getPluralOperand(PluralOperand::PLURAL_OPERAND_V); + int32_t e = (int32_t) fixedLo.getPluralOperand(PluralOperand::PLURAL_OPERAND_E); + FixedDecimal newSample = FixedDecimal::createWithExponent(sampleValue, v, e); + destFd[sampleCount++] = newSample; } if (sampleCount >= destCapacity) { break; @@ -446,24 +471,52 @@ getSamplesFromString(const UnicodeString &samples, double *dest, return sampleCount; } - int32_t PluralRules::getSamples(const UnicodeString &keyword, double *dest, int32_t destCapacity, UErrorCode& status) { - if (destCapacity == 0 || U_FAILURE(status)) { + if (U_FAILURE(status)) { return 0; } if (U_FAILURE(mInternalStatus)) { status = mInternalStatus; return 0; } + if (dest != nullptr ? destCapacity < 0 : destCapacity != 0) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return 0; + } RuleChain *rc = rulesForKeyword(keyword); if (rc == nullptr) { return 0; } - int32_t numSamples = getSamplesFromString(rc->fIntegerSamples, dest, destCapacity, status); + int32_t numSamples = getSamplesFromString(rc->fIntegerSamples, dest, nullptr, destCapacity, status); if (numSamples == 0) { - numSamples = getSamplesFromString(rc->fDecimalSamples, dest, destCapacity, status); + numSamples = getSamplesFromString(rc->fDecimalSamples, dest, nullptr, destCapacity, status); + } + return numSamples; +} + +int32_t +PluralRules::getSamples(const UnicodeString &keyword, FixedDecimal *dest, + int32_t destCapacity, UErrorCode& status) { + if (U_FAILURE(status)) { + return 0; + } + if (U_FAILURE(mInternalStatus)) { + status = mInternalStatus; + return 0; + } + if (dest != nullptr ? destCapacity < 0 : destCapacity != 0) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return 0; + } + RuleChain *rc = rulesForKeyword(keyword); + 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); } return numSamples; } @@ -1548,8 +1601,8 @@ PluralOperand tokenTypeToPluralOperand(tokenType tt) { } } -FixedDecimal::FixedDecimal(double n, int32_t v, int64_t f) { - init(n, v, f); +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. // // long visiblePower = (int) Math.pow(10, v); @@ -1565,6 +1618,10 @@ FixedDecimal::FixedDecimal(double n, int32_t v, int64_t f) { // } } +FixedDecimal::FixedDecimal(double n, int32_t v, int64_t f) { + init(n, v, f); +} + FixedDecimal::FixedDecimal(double n, int32_t v) { // Ugly, but for samples we don't care. init(n, v, getFractionalDigits(n, v)); @@ -1584,20 +1641,36 @@ FixedDecimal::FixedDecimal() { FixedDecimal::FixedDecimal(const UnicodeString &num, UErrorCode &status) { CharString cs; - cs.appendInvariantChars(num, status); + int32_t parsedExponent = 0; + + int32_t exponentIdx = num.indexOf(u'e'); + if (exponentIdx < 0) { + exponentIdx = num.indexOf(u'E'); + } + if (exponentIdx >= 0) { + cs.appendInvariantChars(num.tempSubString(0, exponentIdx), status); + int32_t expSubstrStart = exponentIdx + 1; + parsedExponent = ICU_Utility::parseAsciiInteger(num, expSubstrStart); + } + else { + cs.appendInvariantChars(num, status); + } + DecimalQuantity dl; dl.setToDecNumber(cs.toStringPiece(), status); if (U_FAILURE(status)) { init(0, 0, 0); return; } + int32_t decimalPoint = num.indexOf(DOT); double n = dl.toDouble(); if (decimalPoint == -1) { - init(n, 0, 0); + init(n, 0, 0, parsedExponent); } else { - int32_t v = num.length() - decimalPoint - 1; - init(n, v, getFractionalDigits(n, v)); + int32_t fractionNumLength = exponentIdx < 0 ? num.length() : cs.length(); + int32_t v = fractionNumLength - decimalPoint - 1; + init(n, v, getFractionalDigits(n, v), parsedExponent); } } @@ -1608,6 +1681,7 @@ FixedDecimal::FixedDecimal(const FixedDecimal &other) { decimalDigits = other.decimalDigits; decimalDigitsWithoutTrailingZeros = other.decimalDigitsWithoutTrailingZeros; intValue = other.intValue; + exponent = other.exponent; _hasIntegerValue = other._hasIntegerValue; isNegative = other.isNegative; _isNaN = other._isNaN; @@ -1616,6 +1690,10 @@ FixedDecimal::FixedDecimal(const FixedDecimal &other) { FixedDecimal::~FixedDecimal() = default; +FixedDecimal FixedDecimal::createWithExponent(double n, int32_t v, int32_t e) { + return FixedDecimal(n, v, getFractionalDigits(n, v), e); +} + void FixedDecimal::init(double n) { int32_t numFractionDigits = decimals(n); @@ -1624,10 +1702,17 @@ void FixedDecimal::init(double n) { void FixedDecimal::init(double n, int32_t v, int64_t f) { + int32_t exponent = 0; + init(n, v, f, exponent); +} + + +void FixedDecimal::init(double n, int32_t v, int64_t f, int32_t e) { isNegative = n < 0.0; source = fabs(n); _isNaN = uprv_isNaN(source); _isInfinite = uprv_isInfinite(source); + exponent = e; if (_isNaN || _isInfinite) { v = 0; f = 0; @@ -1757,7 +1842,7 @@ double FixedDecimal::getPluralOperand(PluralOperand operand) const { case PLURAL_OPERAND_F: return static_cast(decimalDigits); case PLURAL_OPERAND_T: return static_cast(decimalDigitsWithoutTrailingZeros); case PLURAL_OPERAND_V: return visibleDecimalDigitCount; - case PLURAL_OPERAND_E: return 0; + case PLURAL_OPERAND_E: return exponent; default: UPRV_UNREACHABLE; // unexpected. } @@ -1783,6 +1868,23 @@ int32_t FixedDecimal::getVisibleFractionDigitCount() const { return visibleDecimalDigitCount; } +bool FixedDecimal::operator==(const FixedDecimal &other) const { + return source == other.source && visibleDecimalDigitCount == other.visibleDecimalDigitCount + && decimalDigits == other.decimalDigits && exponent == other.exponent; +} + +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 { + snprintf(pattern, sizeof(pattern), "%%.%dfe%%d", visibleDecimalDigitCount); + snprintf(buffer, sizeof(buffer), pattern, source, exponent); + } + return UnicodeString(buffer, -1, US_INV); +} PluralAvailableLocalesEnumeration::PluralAvailableLocalesEnumeration(UErrorCode &status) { diff --git a/icu4c/source/i18n/plurrule_impl.h b/icu4c/source/i18n/plurrule_impl.h index 6a85d55f91c..52af3a74131 100644 --- a/icu4c/source/i18n/plurrule_impl.h +++ b/icu4c/source/i18n/plurrule_impl.h @@ -30,6 +30,12 @@ #include "hash.h" #include "uassert.h" +/** + * A FixedDecimal version of UPLRULES_NO_UNIQUE_VALUE used in PluralRulesTest + * for parsing of samples. + */ +#define UPLRULES_NO_UNIQUE_VALUE_DECIMAL (FixedDecimal((double)-0.00123456777)) + class PluralRulesTest; U_NAMESPACE_BEGIN @@ -274,7 +280,9 @@ 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) */ + 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); explicit FixedDecimal(double n); @@ -283,6 +291,8 @@ class U_I18N_API FixedDecimal: public IFixedDecimal, public UObject { FixedDecimal(const UnicodeString &s, UErrorCode &ec); FixedDecimal(const FixedDecimal &other); + static FixedDecimal createWithExponent(double n, int32_t v, int32_t e); + double getPluralOperand(PluralOperand operand) const U_OVERRIDE; bool isNaN() const U_OVERRIDE; bool isInfinite() const U_OVERRIDE; @@ -292,6 +302,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); void init(double n, int32_t v, int64_t f); void init(double n); UBool quickInit(double n); // Try a fast-path only initialization, @@ -300,11 +311,16 @@ class U_I18N_API FixedDecimal: public IFixedDecimal, public UObject { static int64_t getFractionalDigits(double n, int32_t v); static int32_t decimals(double n); + bool operator==(const FixedDecimal &other) const; + + UnicodeString toString() const; + double source; int32_t visibleDecimalDigitCount; int64_t decimalDigits; int64_t decimalDigitsWithoutTrailingZeros; int64_t intValue; + int32_t exponent; UBool _hasIntegerValue; UBool isNegative; UBool _isNaN; diff --git a/icu4c/source/i18n/unicode/plurrule.h b/icu4c/source/i18n/unicode/plurrule.h index dd532232f96..7b30f69b077 100644 --- a/icu4c/source/i18n/unicode/plurrule.h +++ b/icu4c/source/i18n/unicode/plurrule.h @@ -46,6 +46,7 @@ U_NAMESPACE_BEGIN class Hashtable; class IFixedDecimal; +class FixedDecimal; class RuleChain; class PluralRuleParser; class PluralKeywordEnumeration; @@ -475,6 +476,32 @@ public: double *dest, int32_t destCapacity, UErrorCode& status); +#ifndef U_HIDE_INTERNAL_API + /** + * Internal-only function that returns FixedDecimals instead of doubles. + * + * Returns sample values for which select() would return the keyword. If + * the keyword is unknown, returns no values, but this is not an error. + * + * The number of returned values is typically small. + * + * @param keyword The keyword. + * @param dest Array into which to put the returned values. May + * be NULL if destCapacity is 0. + * @param destCapacity The capacity of the array, must be at least 0. + * @param status The error code. + * @return The count of values written. + * If more than destCapacity samples are available, then + * only destCapacity are written, and destCapacity is returned as the count, + * rather than setting a U_BUFFER_OVERFLOW_ERROR. + * (The actual number of keyword values could be unlimited.) + * @internal + */ + int32_t getSamples(const UnicodeString &keyword, + FixedDecimal *dest, int32_t destCapacity, + UErrorCode& status); +#endif /* U_HIDE_INTERNAL_API */ + /** * Returns true if the given keyword is defined in this * PluralRules object. diff --git a/icu4c/source/test/intltest/plurults.cpp b/icu4c/source/test/intltest/plurults.cpp index 2cde9b09a8f..c015197ba2b 100644 --- a/icu4c/source/test/intltest/plurults.cpp +++ b/icu4c/source/test/intltest/plurults.cpp @@ -26,6 +26,7 @@ #include "unicode/numberrangeformatter.h" #include "cmemory.h" +#include "cstr.h" #include "plurrule_impl.h" #include "plurults.h" #include "uhash.h" @@ -49,6 +50,8 @@ void PluralRulesTest::runIndexedTest( int32_t index, UBool exec, const char* &na TESTCASE_AUTO(testAPI); // TESTCASE_AUTO(testGetUniqueKeywordValue); TESTCASE_AUTO(testGetSamples); + TESTCASE_AUTO(testGetFixedDecimalSamples); + TESTCASE_AUTO(testSamplesWithExponent); TESTCASE_AUTO(testWithin); TESTCASE_AUTO(testGetAllKeywordValues); TESTCASE_AUTO(testCompactDecimalPluralKeyword); @@ -356,124 +359,245 @@ UBool testEquality(const PluralRules &test) { void PluralRulesTest::assertRuleValue(const UnicodeString& rule, double expected) { - assertRuleKeyValue("a:" + rule, "a", expected); + assertRuleKeyValue("a:" + rule, "a", expected); } void PluralRulesTest::assertRuleKeyValue(const UnicodeString& rule, const UnicodeString& key, double expected) { - UErrorCode status = U_ZERO_ERROR; - PluralRules *pr = PluralRules::createRules(rule, status); - double result = pr->getUniqueKeywordValue(key); - delete pr; - if (expected != result) { - errln("expected %g but got %g", expected, result); - } + UErrorCode status = U_ZERO_ERROR; + PluralRules *pr = PluralRules::createRules(rule, status); + double result = pr->getUniqueKeywordValue(key); + delete pr; + if (expected != result) { + errln("expected %g but got %g", expected, result); + } } // TODO: UniqueKeywordValue() is not currently supported. // If it never will be, this test code should be removed. void PluralRulesTest::testGetUniqueKeywordValue() { - assertRuleValue("n is 1", 1); - assertRuleValue("n in 2..2", 2); - assertRuleValue("n within 2..2", 2); - assertRuleValue("n in 3..4", UPLRULES_NO_UNIQUE_VALUE); - assertRuleValue("n within 3..4", UPLRULES_NO_UNIQUE_VALUE); - assertRuleValue("n is 2 or n is 2", 2); - assertRuleValue("n is 2 and n is 2", 2); - assertRuleValue("n is 2 or n is 3", UPLRULES_NO_UNIQUE_VALUE); - assertRuleValue("n is 2 and n is 3", UPLRULES_NO_UNIQUE_VALUE); - assertRuleValue("n is 2 or n in 2..3", UPLRULES_NO_UNIQUE_VALUE); - assertRuleValue("n is 2 and n in 2..3", 2); - assertRuleKeyValue("a: n is 1", "not_defined", UPLRULES_NO_UNIQUE_VALUE); // key not defined - assertRuleKeyValue("a: n is 1", "other", UPLRULES_NO_UNIQUE_VALUE); // key matches default rule + assertRuleValue("n is 1", 1); + assertRuleValue("n in 2..2", 2); + assertRuleValue("n within 2..2", 2); + assertRuleValue("n in 3..4", UPLRULES_NO_UNIQUE_VALUE); + assertRuleValue("n within 3..4", UPLRULES_NO_UNIQUE_VALUE); + assertRuleValue("n is 2 or n is 2", 2); + assertRuleValue("n is 2 and n is 2", 2); + assertRuleValue("n is 2 or n is 3", UPLRULES_NO_UNIQUE_VALUE); + assertRuleValue("n is 2 and n is 3", UPLRULES_NO_UNIQUE_VALUE); + assertRuleValue("n is 2 or n in 2..3", UPLRULES_NO_UNIQUE_VALUE); + assertRuleValue("n is 2 and n in 2..3", 2); + assertRuleKeyValue("a: n is 1", "not_defined", UPLRULES_NO_UNIQUE_VALUE); // key not defined + assertRuleKeyValue("a: n is 1", "other", UPLRULES_NO_UNIQUE_VALUE); // key matches default rule } void PluralRulesTest::testGetSamples() { - // TODO: fix samples, re-enable this test. + // TODO: fix samples, re-enable this test. - // no get functional equivalent API in ICU4C, so just - // test every locale... - UErrorCode status = U_ZERO_ERROR; - int32_t numLocales; - const Locale* locales = Locale::getAvailableLocales(numLocales); + // no get functional equivalent API in ICU4C, so just + // test every locale... + UErrorCode status = U_ZERO_ERROR; + int32_t numLocales; + const Locale* locales = Locale::getAvailableLocales(numLocales); - 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")) { - continue; - } - PluralRules *rules = PluralRules::forLocale(locales[i], status); - if (U_FAILURE(status)) { - break; - } - StringEnumeration *keywords = rules->getKeywords(status); - if (U_FAILURE(status)) { - delete rules; - break; - } - const UnicodeString* keyword; - while (NULL != (keyword = keywords->snext(status))) { - int32_t count = rules->getSamples(*keyword, values, UPRV_LENGTHOF(values), status); - if (U_FAILURE(status)) { - errln(UNICODE_STRING_SIMPLE("getSamples() failed for locale ") + - locales[i].getName() + - UNICODE_STRING_SIMPLE(", keyword ") + *keyword); - continue; - } - if (count == 0) { - // TODO: Lots of these. - // errln(UNICODE_STRING_SIMPLE("no samples for keyword ") + *keyword + UNICODE_STRING_SIMPLE(" in locale ") + locales[i].getName() ); - } - if (count > UPRV_LENGTHOF(values)) { - errln(UNICODE_STRING_SIMPLE("getSamples()=") + count + - UNICODE_STRING_SIMPLE(", too many values, for locale ") + - locales[i].getName() + - UNICODE_STRING_SIMPLE(", keyword ") + *keyword); - count = UPRV_LENGTHOF(values); - } - for (int32_t j = 0; j < count; ++j) { - if (values[j] == UPLRULES_NO_UNIQUE_VALUE) { - errln("got 'no unique value' among values"); - } else { - UnicodeString resultKeyword = rules->select(values[j]); - // if (strcmp(locales[i].getName(), "uk") == 0) { // Debug only. - // std::cout << " uk " << US(resultKeyword).cstr() << " " << values[j] << std::endl; - // } - if (*keyword != resultKeyword) { - errln("file %s, line %d, Locale %s, sample for keyword \"%s\": %g, select(%g) returns keyword \"%s\"", - __FILE__, __LINE__, locales[i].getName(), US(*keyword).cstr(), values[j], values[j], US(resultKeyword).cstr()); - } + 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")) { + continue; + } + LocalPointer rules(PluralRules::forLocale(locales[i], status)); + if (U_FAILURE(status)) { + break; + } + LocalPointer keywords(rules->getKeywords(status)); + if (U_FAILURE(status)) { + break; + } + const UnicodeString* keyword; + while (NULL != (keyword = keywords->snext(status))) { + int32_t count = rules->getSamples(*keyword, values, UPRV_LENGTHOF(values), status); + if (U_FAILURE(status)) { + errln(UnicodeString(u"getSamples() failed for locale ") + + locales[i].getName() + + UnicodeString(u", keyword ") + *keyword); + continue; + } + if (count == 0) { + // TODO: Lots of these. + // errln(UnicodeString(u"no samples for keyword ") + *keyword + UnicodeString(u" in locale ") + locales[i].getName() ); + } + if (count > UPRV_LENGTHOF(values)) { + errln(UnicodeString(u"getSamples()=") + count + + UnicodeString(u", too many values, for locale ") + + locales[i].getName() + + UnicodeString(u", keyword ") + *keyword); + count = UPRV_LENGTHOF(values); + } + for (int32_t j = 0; j < count; ++j) { + if (values[j] == UPLRULES_NO_UNIQUE_VALUE) { + errln("got 'no unique value' among values"); + } else { + UnicodeString resultKeyword = rules->select(values[j]); + // if (strcmp(locales[i].getName(), "uk") == 0) { // Debug only. + // std::cout << " uk " << US(resultKeyword).cstr() << " " << values[j] << std::endl; + // } + if (*keyword != resultKeyword) { + errln("file %s, line %d, Locale %s, sample for keyword \"%s\": %g, select(%g) returns keyword \"%s\"", + __FILE__, __LINE__, locales[i].getName(), US(*keyword).cstr(), values[j], values[j], US(resultKeyword).cstr()); + } + } + } } - } } - delete keywords; - delete rules; - } +} + +void PluralRulesTest::testGetFixedDecimalSamples() { + // TODO: fix samples, re-enable this test. + + // no get functional equivalent API in ICU4C, so just + // test every locale... + UErrorCode status = U_ZERO_ERROR; + int32_t numLocales; + const Locale* locales = Locale::getAvailableLocales(numLocales); + + 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")) { + continue; + } + LocalPointer rules(PluralRules::forLocale(locales[i], status)); + if (U_FAILURE(status)) { + break; + } + LocalPointer keywords(rules->getKeywords(status)); + if (U_FAILURE(status)) { + break; + } + const UnicodeString* keyword; + while (NULL != (keyword = keywords->snext(status))) { + int32_t count = rules->getSamples(*keyword, values, UPRV_LENGTHOF(values), status); + if (U_FAILURE(status)) { + errln(UnicodeString(u"getSamples() failed for locale ") + + locales[i].getName() + + UnicodeString(u", keyword ") + *keyword); + continue; + } + if (count == 0) { + // TODO: Lots of these. + // errln(UnicodeString(u"no samples for keyword ") + *keyword + UnicodeString(u" in locale ") + locales[i].getName() ); + } + if (count > UPRV_LENGTHOF(values)) { + errln(UnicodeString(u"getSamples()=") + count + + UnicodeString(u", too many values, for locale ") + + locales[i].getName() + + UnicodeString(u", keyword ") + *keyword); + count = UPRV_LENGTHOF(values); + } + for (int32_t j = 0; j < count; ++j) { + if (values[j] == UPLRULES_NO_UNIQUE_VALUE_DECIMAL) { + errln("got 'no unique value' among values"); + } else { + UnicodeString resultKeyword = rules->select(values[j]); + // if (strcmp(locales[i].getName(), "uk") == 0) { // Debug only. + // std::cout << " uk " << US(resultKeyword).cstr() << " " << values[j] << std::endl; + // } + if (*keyword != resultKeyword) { + errln("file %s, line %d, Locale %s, sample for keyword \"%s\": %s, select(%s) returns keyword \"%s\"", + __FILE__, __LINE__, locales[i].getName(), US(*keyword).cstr(), values[j].toString().getBuffer(), values[j].toString().getBuffer(), US(resultKeyword).cstr()); + } + } + } + } + } +} + +void PluralRulesTest::testSamplesWithExponent() { + // integer samples + UErrorCode status = U_ZERO_ERROR; + UnicodeString description( + u"one: i = 0,1 @integer 0, 1, 1e5 @decimal 0.0~1.5, 1.1e5; " + u"many: e = 0 and i != 0 and i % 1000000 = 0 and v = 0 or e != 0..5" + u" @integer 1000000, 2e6, 3e6, 4e6, 5e6, 6e6, 7e6, … @decimal 2.1e6, 3.1e6, 4.1e6, 5.1e6, 6.1e6, 7.1e6, …; " + u"other: @integer 2~17, 100, 1000, 10000, 100000, 2e5, 3e5, 4e5, 5e5, 6e5, 7e5, …" + 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, …" + ); + 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, 1e5", FixedDecimal(0)); + checkNewSamples(description, test, u"many", u"@integer 1000000, 2e6, 3e6, 4e6, 5e6, 6e6, 7e6, …", FixedDecimal(1000000)); + checkNewSamples(description, test, u"other", u"@integer 2~17, 100, 1000, 10000, 100000, 2e5, 3e5, 4e5, 5e5, 6e5, 7e5, …", FixedDecimal(2)); + + // decimal samples + status = U_ZERO_ERROR; + UnicodeString description2( + u"one: i = 0,1 @decimal 0.0~1.5, 1.1e5; " + u"many: e = 0 and i != 0 and i % 1000000 = 0 and v = 0 or e != 0..5" + u" @decimal 2.1e6, 3.1e6, 4.1e6, 5.1e6, 6.1e6, 7.1e6, …; " + u"other: @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, …" + ); + 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.1e5", FixedDecimal(0, 1)); + checkNewSamples(description2, test2, u"many", u"@decimal 2.1e6, 3.1e6, 4.1e6, 5.1e6, 6.1e6, 7.1e6, …", 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.1e5, 3.1e5, 4.1e5, 5.1e5, 6.1e5, 7.1e5, …", FixedDecimal(2.0, 1)); +} + +void PluralRulesTest::checkNewSamples( + UnicodeString description, + const LocalPointer &test, + UnicodeString keyword, + UnicodeString samplesString, + FixedDecimal firstInRange) { + + UErrorCode status = U_ZERO_ERROR; + FixedDecimal samples[1000]; + + test->getSamples(keyword, samples, UPRV_LENGTHOF(samples), status); + if (U_FAILURE(status)) { + errln("Couldn't retrieve plural samples, with error = %s", u_errorName(status)); + return; + } + FixedDecimal actualFirstSample = samples[0]; + + if (!(firstInRange == actualFirstSample)) { + CStr descCstr(description); + CStr samplesCstr(samplesString); + char errMsg[1000]; + snprintf(errMsg, sizeof(errMsg), "First parsed sample FixedDecimal not equal to expected for samples: %s in rule string: %s\n", descCstr(), samplesCstr()); + errln(errMsg); + } } void PluralRulesTest::testWithin() { - // goes to show you what lack of testing will do. - // of course, this has been broken for two years and no one has noticed... - UErrorCode status = U_ZERO_ERROR; - PluralRules *rules = PluralRules::createRules("a: n mod 10 in 5..8", status); - if (!rules) { - errln("couldn't instantiate rules"); - return; - } + // goes to show you what lack of testing will do. + // of course, this has been broken for two years and no one has noticed... + UErrorCode status = U_ZERO_ERROR; + PluralRules *rules = PluralRules::createRules("a: n mod 10 in 5..8", status); + if (!rules) { + errln("couldn't instantiate rules"); + return; + } - UnicodeString keyword = rules->select((int32_t)26); - if (keyword != "a") { - errln("expected 'a' for 26 but didn't get it."); - } + UnicodeString keyword = rules->select((int32_t)26); + if (keyword != "a") { + errln("expected 'a' for 26 but didn't get it."); + } - keyword = rules->select(26.5); - if (keyword != "other") { - errln("expected 'other' for 26.5 but didn't get it."); - } + keyword = rules->select(26.5); + if (keyword != "other") { + errln("expected 'other' for 26.5 but didn't get it."); + } - delete rules; + delete rules; } void diff --git a/icu4c/source/test/intltest/plurults.h b/icu4c/source/test/intltest/plurults.h index 2d7920da9e7..70823498ccd 100644 --- a/icu4c/source/test/intltest/plurults.h +++ b/icu4c/source/test/intltest/plurults.h @@ -30,6 +30,8 @@ private: void testAPI(); void testGetUniqueKeywordValue(); void testGetSamples(); + void testGetFixedDecimalSamples(); + void testSamplesWithExponent(); void testWithin(); void testGetAllKeywordValues(); void testCompactDecimalPluralKeyword(); @@ -45,6 +47,11 @@ private: void assertRuleValue(const UnicodeString& rule, double expected); void assertRuleKeyValue(const UnicodeString& rule, const UnicodeString& key, double expected); + void checkNewSamples(UnicodeString description, + const LocalPointer &test, + UnicodeString keyword, + UnicodeString samplesString, + FixedDecimal firstInRange); UnicodeString getPluralKeyword(const LocalPointer &rules, Locale locale, double number, const char16_t* skeleton); void checkSelect(const LocalPointer &rules, UErrorCode &status,