]> granicus.if.org Git - icu/commitdiff
ICU-21270 Support exponent in FixedDecimal and samples in C++
authorElango Cheran <elango@unicode.org>
Wed, 23 Sep 2020 01:16:21 +0000 (18:16 -0700)
committerElango <elango@google.com>
Fri, 2 Oct 2020 16:21:29 +0000 (09:21 -0700)
icu4c/source/i18n/plurrule.cpp
icu4c/source/i18n/plurrule_impl.h
icu4c/source/i18n/unicode/plurrule.h
icu4c/source/test/intltest/plurults.cpp
icu4c/source/test/intltest/plurults.h

index 7ecf1bc2b3a54948e22e2260b3e2881bbfe1e037..e1e1667a6ea9a249473901ffa822f621b69c1cb4 100644 (file)
@@ -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<double>(decimalDigits);
         case PLURAL_OPERAND_T: return static_cast<double>(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) {
index 6a85d55f91cee7d08c3aa1c5e314d3db32ecd124..52af3a741316dde0f7ad9558f56d8329bf51ece1 100644 (file)
 #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;
index dd532232f9678775af51a757c86feba3c354a296..7b30f69b0773d62b261835a9d523a4f3aca91583 100644 (file)
@@ -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
      * <code>PluralRules</code> object.
index 2cde9b09a8f10c650722416285059fb85ae9a027..c015197ba2bc58ced7f04391871d89d3c8d703ff 100644 (file)
@@ -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.
-
-  // 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;
+    // 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);
+
+    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<PluralRules> rules(PluralRules::forLocale(locales[i], status));
+        if (U_FAILURE(status)) {
+            break;
+        }
+        LocalPointer<StringEnumeration> 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());
+                    }
+                }
+            }
+        }
     }
-    PluralRules *rules = PluralRules::forLocale(locales[i], status);
+}
+
+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<PluralRules> rules(PluralRules::forLocale(locales[i], status));
+        if (U_FAILURE(status)) {
+            break;
+        }
+        LocalPointer<StringEnumeration> 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<PluralRules> test(PluralRules::createRules(description, status));
     if (U_FAILURE(status)) {
-      break;
+        errln("Couldn't create plural rules from a string using exponent notation, with error = %s", u_errorName(status));
+        return;
     }
-    StringEnumeration *keywords = rules->getKeywords(status);
+    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<PluralRules> test2(PluralRules::createRules(description2, status));
     if (U_FAILURE(status)) {
-      delete rules;
-      break;
+        errln("Couldn't create plural rules from a string using exponent notation, with error = %s", u_errorName(status));
+        return;
     }
-    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());
-          }
-        }
-      }
+    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<PluralRules> &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);
     }
-    delete keywords;
-    delete rules;
-  }
 }
 
 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
index 2d7920da9e7d7f173714c6dca3ba22817be47e9d..70823498ccd7cc5edc23103ff28ecc612e92dde2 100644 (file)
@@ -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<PluralRules> &test,
+                         UnicodeString keyword,
+                         UnicodeString samplesString,
+                         FixedDecimal firstInRange);
     UnicodeString getPluralKeyword(const LocalPointer<PluralRules> &rules,
                                    Locale locale, double number, const char16_t* skeleton);
     void checkSelect(const LocalPointer<PluralRules> &rules, UErrorCode &status,