]> granicus.if.org Git - icu/commitdiff
ICU-13839 Adding FormattedNumber API to PluralRules.
authorShane Carr <shane@unicode.org>
Fri, 11 Jan 2019 07:42:43 +0000 (23:42 -0800)
committerShane F. Carr <shane@unicode.org>
Mon, 28 Jan 2019 19:46:00 +0000 (11:46 -0800)
- Makes new dependency class for PluralRules+FormattedNumber.

18 files changed:
icu4c/source/i18n/Makefile.in
icu4c/source/i18n/i18n.vcxproj
icu4c/source/i18n/i18n.vcxproj.filters
icu4c/source/i18n/i18n_uwp.vcxproj
icu4c/source/i18n/number_capi.cpp
icu4c/source/i18n/number_fluent.cpp
icu4c/source/i18n/number_output.cpp [new file with mode: 0644]
icu4c/source/i18n/number_utypes.h
icu4c/source/i18n/plurrule.cpp
icu4c/source/i18n/unicode/plurrule.h
icu4c/source/i18n/unicode/upluralrules.h
icu4c/source/i18n/upluralrules.cpp
icu4c/source/test/cintltst/cpluralrulestest.c
icu4c/source/test/depstest/dependencies.txt
icu4c/source/test/intltest/plurults.cpp
icu4c/source/test/intltest/plurults.h
icu4j/main/classes/core/src/com/ibm/icu/text/PluralRules.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/PluralRulesTest.java

index 65705929b441155a3f3e29a96340cef414815af5..03617fe1b6411cf94bcf59439f5f3e81f53921e6 100644 (file)
@@ -101,7 +101,7 @@ uregion.o reldatefmt.o quantityformatter.o measunit.o \
 sharedbreakiterator.o scientificnumberformatter.o dayperiodrules.o nounit.o \
 number_affixutils.o number_compact.o number_decimalquantity.o \
 number_decimfmtprops.o number_fluent.o number_formatimpl.o number_grouping.o \
-number_integerwidth.o number_longnames.o number_modifiers.o number_notation.o \
+number_integerwidth.o number_longnames.o number_modifiers.o number_notation.o number_output.o \
 number_padding.o number_patternmodifier.o number_patternstring.o \
 number_rounding.o number_scientific.o number_stringbuilder.o number_utils.o number_asformat.o \
 number_mapper.o number_multiplier.o number_currencysymbols.o number_skeletons.o number_capi.o \
index 57cdb8da7bebe4fd11551699f39e79ee2971458f..4cccb5f3e6dbcb0a52ad81612feb5cd3ecc1eb30 100644 (file)
     <ClCompile Include="number_longnames.cpp" />
     <ClCompile Include="number_modifiers.cpp" />
     <ClCompile Include="number_notation.cpp" />
+    <ClCompile Include="number_output.cpp" />
     <ClCompile Include="number_padding.cpp" />
     <ClCompile Include="number_patternmodifier.cpp" />
     <ClCompile Include="number_patternstring.cpp" />
index dc90bfce5177bd776e020eb8bd5abc687054258f..ee6850555dcc8cf5fad9875fe507d4670817bfa7 100644 (file)
     <ClCompile Include="number_notation.cpp">
       <Filter>formatting</Filter>
     </ClCompile>
+    <ClCompile Include="number_output.cpp">
+      <Filter>formatting</Filter>
+    </ClCompile>
     <ClCompile Include="number_padding.cpp">
       <Filter>formatting</Filter>
     </ClCompile>
index 9db4848ed3346f09d63d6e48573be16a15aaba66..93ae76c8a34cd44f718554156cf3ba3d25895396 100644 (file)
     <ClCompile Include="number_longnames.cpp" />
     <ClCompile Include="number_modifiers.cpp" />
     <ClCompile Include="number_notation.cpp" />
+    <ClCompile Include="number_output.cpp" />
     <ClCompile Include="number_padding.cpp" />
     <ClCompile Include="number_patternmodifier.cpp" />
     <ClCompile Include="number_patternstring.cpp" />
index 5e196a262fbd2bb478d5af7a2b3b4dacc800767a..c39c69f4cea3267683490b473dae70b5ad367e29 100644 (file)
@@ -61,6 +61,16 @@ UFormattedNumberImpl::~UFormattedNumberImpl() {
 U_NAMESPACE_END
 
 
+const DecimalQuantity* icu::number::impl::validateUFormattedNumberToDecimalQuantity(
+        const UFormattedNumber* uresult, UErrorCode& status) {
+    auto* result = UFormattedNumberApiHelper::validate(uresult, status);
+    if (U_FAILURE(status)) {
+        return nullptr;
+    }
+    return &result->fData.quantity;
+}
+
+
 
 U_CAPI UNumberFormatter* U_EXPORT2
 unumf_openForSkeletonAndLocale(const UChar* skeleton, int32_t skeletonLen, const char* locale,
index 5bc1f982fa6877ee5715c225056b905dff1356ad..84e7d05ac594251c31b17abe811287e177cb14bf 100644 (file)
@@ -770,111 +770,4 @@ Format* LocalizedNumberFormatter::toFormat(UErrorCode& status) const {
 }
 
 
-FormattedNumber::FormattedNumber(FormattedNumber&& src) U_NOEXCEPT
-        : fResults(src.fResults), fErrorCode(src.fErrorCode) {
-    // Disown src.fResults to prevent double-deletion
-    src.fResults = nullptr;
-    src.fErrorCode = U_INVALID_STATE_ERROR;
-}
-
-FormattedNumber& FormattedNumber::operator=(FormattedNumber&& src) U_NOEXCEPT {
-    delete fResults;
-    fResults = src.fResults;
-    fErrorCode = src.fErrorCode;
-    // Disown src.fResults to prevent double-deletion
-    src.fResults = nullptr;
-    src.fErrorCode = U_INVALID_STATE_ERROR;
-    return *this;
-}
-
-UnicodeString FormattedNumber::toString(UErrorCode& status) const {
-    if (U_FAILURE(status)) {
-        return ICU_Utility::makeBogusString();
-    }
-    if (fResults == nullptr) {
-        status = fErrorCode;
-        return ICU_Utility::makeBogusString();
-    }
-    return fResults->string.toUnicodeString();
-}
-
-UnicodeString FormattedNumber::toTempString(UErrorCode& status) const {
-    if (U_FAILURE(status)) {
-        return ICU_Utility::makeBogusString();
-    }
-    if (fResults == nullptr) {
-        status = fErrorCode;
-        return ICU_Utility::makeBogusString();
-    }
-    return fResults->string.toTempUnicodeString();
-}
-
-Appendable& FormattedNumber::appendTo(Appendable& appendable, UErrorCode& status) const {
-    if (U_FAILURE(status)) {
-        return appendable;
-    }
-    if (fResults == nullptr) {
-        status = fErrorCode;
-        return appendable;
-    }
-    appendable.appendString(fResults->string.chars(), fResults->string.length());
-    return appendable;
-}
-
-UBool FormattedNumber::nextPosition(ConstrainedFieldPosition& cfpos, UErrorCode& status) const {
-    if (U_FAILURE(status)) {
-        return FALSE;
-    }
-    if (fResults == nullptr) {
-        status = fErrorCode;
-        return FALSE;
-    }
-    // NOTE: MSVC sometimes complains when implicitly converting between bool and UBool
-    return fResults->string.nextPosition(cfpos, status) ? TRUE : FALSE;
-}
-
-UBool FormattedNumber::nextFieldPosition(FieldPosition& fieldPosition, UErrorCode& status) const {
-    if (U_FAILURE(status)) {
-        return FALSE;
-    }
-    if (fResults == nullptr) {
-        status = fErrorCode;
-        return FALSE;
-    }
-    // NOTE: MSVC sometimes complains when implicitly converting between bool and UBool
-    return fResults->string.nextFieldPosition(fieldPosition, status) ? TRUE : FALSE;
-}
-
-void FormattedNumber::getAllFieldPositions(FieldPositionIterator& iterator, UErrorCode& status) const {
-    FieldPositionIteratorHandler fpih(&iterator, status);
-    getAllFieldPositionsImpl(fpih, status);
-}
-
-void FormattedNumber::getAllFieldPositionsImpl(FieldPositionIteratorHandler& fpih,
-                                               UErrorCode& status) const {
-    if (U_FAILURE(status)) {
-        return;
-    }
-    if (fResults == nullptr) {
-        status = fErrorCode;
-        return;
-    }
-    fResults->string.getAllFieldPositions(fpih, status);
-}
-
-void FormattedNumber::getDecimalQuantity(DecimalQuantity& output, UErrorCode& status) const {
-    if (U_FAILURE(status)) {
-        return;
-    }
-    if (fResults == nullptr) {
-        status = fErrorCode;
-        return;
-    }
-    output = fResults->quantity;
-}
-
-FormattedNumber::~FormattedNumber() {
-    delete fResults;
-}
-
 #endif /* #if !UCONFIG_NO_FORMATTING */
diff --git a/icu4c/source/i18n/number_output.cpp b/icu4c/source/i18n/number_output.cpp
new file mode 100644 (file)
index 0000000..e39f575
--- /dev/null
@@ -0,0 +1,128 @@
+// © 2019 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+
+#include "unicode/utypes.h"
+
+#if !UCONFIG_NO_FORMATTING
+
+#include "unicode/numberformatter.h"
+#include "number_utypes.h"
+#include "util.h"
+#include "number_decimalquantity.h"
+
+U_NAMESPACE_BEGIN
+namespace number {
+
+
+FormattedNumber::FormattedNumber(FormattedNumber&& src) U_NOEXCEPT
+        : fResults(src.fResults), fErrorCode(src.fErrorCode) {
+    // Disown src.fResults to prevent double-deletion
+    src.fResults = nullptr;
+    src.fErrorCode = U_INVALID_STATE_ERROR;
+}
+
+FormattedNumber& FormattedNumber::operator=(FormattedNumber&& src) U_NOEXCEPT {
+    delete fResults;
+    fResults = src.fResults;
+    fErrorCode = src.fErrorCode;
+    // Disown src.fResults to prevent double-deletion
+    src.fResults = nullptr;
+    src.fErrorCode = U_INVALID_STATE_ERROR;
+    return *this;
+}
+
+UnicodeString FormattedNumber::toString(UErrorCode& status) const {
+    if (U_FAILURE(status)) {
+        return ICU_Utility::makeBogusString();
+    }
+    if (fResults == nullptr) {
+        status = fErrorCode;
+        return ICU_Utility::makeBogusString();
+    }
+    return fResults->string.toUnicodeString();
+}
+
+UnicodeString FormattedNumber::toTempString(UErrorCode& status) const {
+    if (U_FAILURE(status)) {
+        return ICU_Utility::makeBogusString();
+    }
+    if (fResults == nullptr) {
+        status = fErrorCode;
+        return ICU_Utility::makeBogusString();
+    }
+    return fResults->string.toTempUnicodeString();
+}
+
+Appendable& FormattedNumber::appendTo(Appendable& appendable, UErrorCode& status) const {
+    if (U_FAILURE(status)) {
+        return appendable;
+    }
+    if (fResults == nullptr) {
+        status = fErrorCode;
+        return appendable;
+    }
+    appendable.appendString(fResults->string.chars(), fResults->string.length());
+    return appendable;
+}
+
+UBool FormattedNumber::nextPosition(ConstrainedFieldPosition& cfpos, UErrorCode& status) const {
+    if (U_FAILURE(status)) {
+        return FALSE;
+    }
+    if (fResults == nullptr) {
+        status = fErrorCode;
+        return FALSE;
+    }
+    // NOTE: MSVC sometimes complains when implicitly converting between bool and UBool
+    return fResults->string.nextPosition(cfpos, status) ? TRUE : FALSE;
+}
+
+UBool FormattedNumber::nextFieldPosition(FieldPosition& fieldPosition, UErrorCode& status) const {
+    if (U_FAILURE(status)) {
+        return FALSE;
+    }
+    if (fResults == nullptr) {
+        status = fErrorCode;
+        return FALSE;
+    }
+    // NOTE: MSVC sometimes complains when implicitly converting between bool and UBool
+    return fResults->string.nextFieldPosition(fieldPosition, status) ? TRUE : FALSE;
+}
+
+void FormattedNumber::getAllFieldPositions(FieldPositionIterator& iterator, UErrorCode& status) const {
+    FieldPositionIteratorHandler fpih(&iterator, status);
+    getAllFieldPositionsImpl(fpih, status);
+}
+
+void FormattedNumber::getAllFieldPositionsImpl(FieldPositionIteratorHandler& fpih,
+                                               UErrorCode& status) const {
+    if (U_FAILURE(status)) {
+        return;
+    }
+    if (fResults == nullptr) {
+        status = fErrorCode;
+        return;
+    }
+    fResults->string.getAllFieldPositions(fpih, status);
+}
+
+void FormattedNumber::getDecimalQuantity(impl::DecimalQuantity& output, UErrorCode& status) const {
+    if (U_FAILURE(status)) {
+        return;
+    }
+    if (fResults == nullptr) {
+        status = fErrorCode;
+        return;
+    }
+    output = fResults->quantity;
+}
+
+FormattedNumber::~FormattedNumber() {
+    delete fResults;
+}
+
+
+} // namespace number
+U_NAMESPACE_END
+
+#endif /* #if !UCONFIG_NO_FORMATTING */
index 18917dea514dcdd905acd95816dc276b372e5a52..56e0fa8ebaa08afcc7270c856f8be76a0c730c30 100644 (file)
@@ -27,6 +27,11 @@ struct UFormattedValueImpl : public UMemory, public UFormattedValueApiHelper {
 };
 
 
+/** Helper function used in upluralrules.cpp */
+const DecimalQuantity* validateUFormattedNumberToDecimalQuantity(
+    const UFormattedNumber* uresult, UErrorCode& status);
+
+
 /**
  * Struct for data used by FormattedNumber.
  *
index 90876781d54ff312cfc875ebbb35e34c9d38356b..e82c02cb592c417dd9bb931cc521a67f2394a60d 100644 (file)
@@ -35,6 +35,7 @@
 #include "sharedpluralrules.h"
 #include "unifiedcache.h"
 #include "number_decimalquantity.h"
+#include "util.h"
 
 #if !UCONFIG_NO_FORMATTING
 
@@ -264,6 +265,16 @@ PluralRules::select(double number) const {
     return select(FixedDecimal(number));
 }
 
+UnicodeString
+PluralRules::select(const number::FormattedNumber& number, UErrorCode& status) const {
+    DecimalQuantity dq;
+    number.getDecimalQuantity(dq, status);
+    if (U_FAILURE(status)) {
+        return ICU_Utility::makeBogusString();
+    }
+    return select(dq);
+}
+
 UnicodeString
 PluralRules::select(const IFixedDecimal &number) const {
     if (mRules == nullptr) {
index daeed52bee632e3118cad04315b5f6d543df6850..55167136a089ce0f5a8f956995ec02d262ca6839 100644 (file)
@@ -50,6 +50,10 @@ class PluralKeywordEnumeration;
 class AndConstraint;
 class SharedPluralRules;
 
+namespace number {
+class FormattedNumber;
+}
+
 /**
  * Defines rules for mapping non-negative numeric values onto a small set of
  * keywords. Rules are constructed from a text description, consisting
@@ -323,9 +327,9 @@ public:
 #endif  /* U_HIDE_INTERNAL_API */
 
     /**
-     * Given a number, returns the keyword of the first rule that applies to
-     * the number.  This function can be used with isKeyword* functions to
-     * determine the keyword for default plural rules.
+     * Given an integer, returns the keyword of the first rule
+     * that applies to  the number.  This function can be used with
+     * isKeyword* functions to determine the keyword for default plural rules.
      *
      * @param number  The number for which the rule has to be determined.
      * @return        The keyword of the selected rule.
@@ -334,9 +338,9 @@ public:
     UnicodeString select(int32_t number) const;
 
     /**
-     * Given a number, returns the keyword of the first rule that applies to
-     * the number.  This function can be used with isKeyword* functions to
-     * determine the keyword for default plural rules.
+     * Given a floating-point number, returns the keyword of the first rule
+     * that applies to  the number.  This function can be used with
+     * isKeyword* functions to determine the keyword for default plural rules.
      *
      * @param number  The number for which the rule has to be determined.
      * @return        The keyword of the selected rule.
@@ -344,6 +348,23 @@ public:
      */
     UnicodeString select(double number) const;
 
+    /**
+     * Given a formatted number, returns the keyword of the first rule
+     * that applies to  the number.  This function can be used with
+     * isKeyword* functions to determine the keyword for default plural rules.
+     *
+     * A FormattedNumber allows you to specify an exponent or trailing zeros,
+     * which can affect the plural category. To get a FormattedNumber, see
+     * NumberFormatter.
+     *
+     * @param number  The number for which the rule has to be determined.
+     * @param status  Set if an error occurs while selecting plural keyword.
+     *                This could happen if the FormattedNumber is invalid.
+     * @return        The keyword of the selected rule.
+     * @draft ICU 64
+     */
+    UnicodeString select(const number::FormattedNumber& number, UErrorCode& status) const;
+
 #ifndef U_HIDE_INTERNAL_API
     /**
       * @internal
index 690846bc89cd013a49ba9eeb3146066e734ebd41..7c51e47aeaea1fc5d08d406f1d72769dfd8e9233 100644 (file)
@@ -20,6 +20,9 @@
 #include "unicode/unum.h"
 #endif  /* U_HIDE_INTERNAL_API */
 
+// Forward-declaration
+struct UFormattedNumber;
+
 /**
  * \file
  * \brief C API: Plural rules, select plural keywords for numeric values.
@@ -132,14 +135,15 @@ U_NAMESPACE_END
 
 
 /**
- * Given a number, returns the keyword of the first rule that
+ * Given a floating-point number, returns the keyword of the first rule that
  * applies to the number, according to the supplied UPluralRules object.
  * @param uplrules The UPluralRules object specifying the rules.
  * @param number The number for which the rule has to be determined.
- * @param keyword The keyword of the rule that applies to number.
- * @param capacity The capacity of keyword.
+ * @param keyword An output buffer to write the keyword of the rule that
+ *         applies to number.
+ * @param capacity The capacity of the keyword buffer.
  * @param status A pointer to a UErrorCode to receive any errors.
- * @return The length of keyword.
+ * @return The length of the keyword.
  * @stable ICU 4.8
  */
 U_CAPI int32_t U_EXPORT2
@@ -148,6 +152,29 @@ uplrules_select(const UPluralRules *uplrules,
                UChar *keyword, int32_t capacity,
                UErrorCode *status);
 
+/**
+ * Given a formatted number, returns the keyword of the first rule
+ * that applies to the number, according to the supplied UPluralRules object.
+ *
+ * A UFormattedNumber allows you to specify an exponent or trailing zeros,
+ * which can affect the plural category. To get a UFormattedNumber, see
+ * {@link UNumberFormatter}.
+ *
+ * @param uplrules The UPluralRules object specifying the rules.
+ * @param number The formatted number for which the rule has to be determined.
+ * @param keyword The destination buffer for the keyword of the rule that
+ *         applies to number.
+ * @param capacity The capacity of the keyword buffer.
+ * @param status A pointer to a UErrorCode to receive any errors.
+ * @return The length of the keyword.
+ * @draft ICU 64
+ */
+U_CAPI int32_t U_EXPORT2
+uplrules_selectFormatted(const UPluralRules *uplrules,
+               const struct UFormattedNumber* number,
+               UChar *keyword, int32_t capacity,
+               UErrorCode *status);
+
 #ifndef U_HIDE_INTERNAL_API
 /**
  * Given a number, returns the keyword of the first rule that applies to the
@@ -160,7 +187,8 @@ uplrules_select(const UPluralRules *uplrules,
  * @param fmt The UNumberFormat specifying how the number will be formatted
  *        (this can affect the plural form, e.g. "1 dollar" vs "1.0 dollars").
  *        If this is NULL, the function behaves like uplrules_select.
- * @param keyword The keyword of the rule that applies to number.
+ * @param keyword An output buffer to write the keyword of the rule that
+ *         applies to number.
  * @param capacity The capacity of the keyword buffer.
  * @param status A pointer to a UErrorCode to receive any errors.
  * @return The length of keyword.
index bba6dfe3101ec3e09aec8036a4baee66db06cbdd..5119257fd804f7d9aa92755ad05a0faa3b9a5d01 100644 (file)
@@ -17,7 +17,9 @@
 #include "unicode/unistr.h"
 #include "unicode/unum.h"
 #include "unicode/numfmt.h"
+#include "unicode/unumberformatter.h"
 #include "number_decimalquantity.h"
+#include "number_utypes.h"
 
 U_NAMESPACE_USE
 
@@ -91,6 +93,28 @@ uplrules_select(const UPluralRules *uplrules,
     return result.extract(keyword, capacity, *status);
 }
 
+U_CAPI int32_t U_EXPORT2
+uplrules_selectFormatted(const UPluralRules *uplrules,
+                const UFormattedNumber* number,
+                UChar *keyword, int32_t capacity,
+                UErrorCode *status)
+{
+    if (U_FAILURE(*status)) {
+        return 0;
+    }
+    if (keyword == NULL ? capacity != 0 : capacity < 0) {
+        *status = U_ILLEGAL_ARGUMENT_ERROR;
+        return 0;
+    }
+    const number::impl::DecimalQuantity* dq =
+        number::impl::validateUFormattedNumberToDecimalQuantity(number, *status);
+    if (U_FAILURE(*status)) {
+        return 0;
+    }
+    UnicodeString result = ((PluralRules*)uplrules)->select(*dq);
+    return result.extract(keyword, capacity, *status);
+}
+
 U_CAPI int32_t U_EXPORT2
 uplrules_selectWithFormat(const UPluralRules *uplrules,
                           double number,
index 24b65433f69a7cb9251304d476a1c3dbd9ed70d2..8db9d1fb889704998e961dcde76c212293dc60b2 100644 (file)
@@ -13,6 +13,7 @@
 #include "unicode/upluralrules.h"
 #include "unicode/ustring.h"
 #include "unicode/uenum.h"
+#include "unicode/unumberformatter.h"
 #include "cintltst.h"
 #include "cmemory.h"
 #include "cstring.h"
@@ -20,6 +21,7 @@
 static void TestPluralRules(void);
 static void TestOrdinalRules(void);
 static void TestGetKeywords(void);
+static void TestFormatted(void);
 
 void addPluralRulesTest(TestNode** root);
 
@@ -30,6 +32,7 @@ void addPluralRulesTest(TestNode** root)
     TESTCASE(TestPluralRules);
     TESTCASE(TestOrdinalRules);
     TESTCASE(TestGetKeywords);
+    TESTCASE(TestFormatted);
 }
 
 typedef struct {
@@ -252,4 +255,44 @@ static void TestGetKeywords() {
     }
 }
 
+static void TestFormatted() {
+    UErrorCode ec = U_ZERO_ERROR;
+    UNumberFormatter* unumf = NULL;
+    UFormattedNumber* uresult = NULL;
+    UPluralRules* uplrules = NULL;
+
+    uplrules = uplrules_open("hr", &ec);
+    if (!assertSuccess("open plural rules", &ec)) {
+        goto cleanup;
+    }
+
+    unumf = unumf_openForSkeletonAndLocale(u".00", -1, "hr", &ec);
+    if (!assertSuccess("open unumf", &ec)) {
+        goto cleanup;
+    }
+
+    uresult = unumf_openResult(&ec);
+    if (!assertSuccess("open result", &ec)) {
+        goto cleanup;
+    }
+
+    unumf_formatDouble(unumf, 100.2, uresult, &ec);
+    if (!assertSuccess("format", &ec)) {
+        goto cleanup;
+    }
+
+    UChar buffer[40];
+    uplrules_selectFormatted(uplrules, uresult, buffer, 40, &ec);
+    if (!assertSuccess("select", &ec)) {
+        goto cleanup;
+    }
+
+    assertUEquals("0.20 is plural category 'other' in hr", u"other", buffer);
+
+cleanup:
+    uplrules_close(uplrules);
+    unumf_close(unumf);
+    unumf_closeResult(uresult);
+}
+
 #endif /* #if !UCONFIG_NO_FORMATTING */
index a54f024f1ff2d9a080d49b4699bba1a15eae5521..423469f7f26c563749a41c05bb84b3f63946c079 100644 (file)
@@ -840,7 +840,7 @@ library: i18n
     dayperiodrules
     listformatter
     formatting formattable_cnv regex regex_cnv translit
-    double_conversion number_representation numberformatter numberparser
+    double_conversion number_representation number_output numberformatter numberparser
     universal_time_scale
     uclean_i18n
 
@@ -937,6 +937,16 @@ group: number_representation
     ucase uniset_core
     formatted_value
 
+group: number_output
+    # PluralRules and FormattedNumber
+    number_output.o
+    standardplural.o plurrule.o
+  deps
+    # FormattedNumber internals:
+    number_representation format
+    # PluralRules internals:
+    unifiedcache
+
 group: numberformatter
     # ICU 60+ NumberFormatter API
     number_affixutils.o number_asformat.o
@@ -950,11 +960,9 @@ group: numberformatter
     number_scientific.o number_skeletons.o
     currpinf.o dcfmtsym.o numsys.o
     numrange_fluent.o numrange_impl.o
-    # pluralrules
-    standardplural.o plurrule.o
   deps
-    decnumber double_conversion formattable format units
-    number_representation
+    decnumber double_conversion formattable units
+    number_representation number_output
     uclean_i18n common
 
 group: numberparser
index 80ce09a7498ca09f56a4272cc43da0a2631a2295..0d59a5a6703a9a813eed70acbb8c42b94b4fd0d3 100644 (file)
@@ -6,7 +6,7 @@
 * others. All Rights Reserved.
 ********************************************************************************
 
-* File PLURRULTS.cpp
+* File PLURULTS.cpp
 *
 ********************************************************************************
 */
@@ -22,6 +22,7 @@
 #include "unicode/localpointer.h"
 #include "unicode/plurrule.h"
 #include "unicode/stringpiece.h"
+#include "unicode/numberformatter.h"
 
 #include "cmemory.h"
 #include "plurrule_impl.h"
@@ -53,6 +54,7 @@ void PluralRulesTest::runIndexedTest( int32_t index, UBool exec, const char* &na
     TESTCASE_AUTO(testAvailbleLocales);
     TESTCASE_AUTO(testParseErrors);
     TESTCASE_AUTO(testFixedDecimal);
+    TESTCASE_AUTO(testSelectTrailingZeros);
     TESTCASE_AUTO_END;
 }
 
@@ -1004,5 +1006,36 @@ void PluralRulesTest::testFixedDecimal() {
 }
 
 
+void PluralRulesTest::testSelectTrailingZeros() {
+    IcuTestErrorCode status(*this, "testSelectTrailingZeros");
+    number::UnlocalizedNumberFormatter unf = number::NumberFormatter::with()
+            .precision(number::Precision::fixedFraction(2));
+    struct TestCase {
+        const char* localeName;
+        const char16_t* expectedDoubleKeyword;
+        const char16_t* expectedFormattedKeyword;
+        double number;
+    } cases[] = {
+        {"bs",  u"few",   u"other", 5.2},  // 5.2 => two, but 5.20 => other
+        {"si",  u"one",   u"one",   0.0},
+        {"si",  u"one",   u"one",   1.0},
+        {"si",  u"one",   u"other", 0.1},  // 0.1 => one, but 0.10 => other
+        {"si",  u"one",   u"one",   0.01}, // 0.01 => one
+        {"hsb", u"few",   u"few",   1.03}, // (f % 100 == 3) => few
+        {"hsb", u"few",   u"other", 1.3},  // 1.3 => few, but 1.30 => other
+    };
+    for (const auto& cas : cases) {
+        UnicodeString message(UnicodeString(cas.localeName) + u" " + DoubleToUnicodeString(cas.number));
+        status.setScope(message);
+        Locale locale(cas.localeName);
+        LocalPointer<PluralRules> rules(PluralRules::forLocale(locale, status));
+        assertEquals(message, cas.expectedDoubleKeyword, rules->select(cas.number));
+        number::FormattedNumber fn = unf.locale(locale).formatDouble(cas.number, status);
+        assertEquals(message, cas.expectedFormattedKeyword, rules->select(fn, status));
+        status.errIfFailureAndReset();
+    }
+}
+
+
 
 #endif /* #if !UCONFIG_NO_FORMATTING */
index 2189593aad10cf35f9994f50f189f3d86308b59e..e4e2497ec69706a4888fe79f8bea0d55c45a570f 100644 (file)
@@ -37,6 +37,7 @@ private:
     void testAvailbleLocales();
     void testParseErrors();
     void testFixedDecimal();
+    void testSelectTrailingZeros();
 
     void assertRuleValue(const UnicodeString& rule, double expected);
     void assertRuleKeyValue(const UnicodeString& rule, const UnicodeString& key,
index 2a6ab6f9f45a395a4e87496c500d834d25c64cd6..708edee7c29ed4909f199c40bad72e6b3fee40f4 100644 (file)
@@ -29,6 +29,8 @@ import java.util.TreeSet;
 import java.util.regex.Pattern;
 
 import com.ibm.icu.impl.PluralRulesLoader;
+import com.ibm.icu.number.FormattedNumber;
+import com.ibm.icu.number.NumberFormatter;
 import com.ibm.icu.util.Output;
 import com.ibm.icu.util.ULocale;
 
@@ -2026,9 +2028,10 @@ public class PluralRules implements Serializable {
     public int hashCode() {
         return rules.hashCode();
     }
+
     /**
-     * Given a number, returns the keyword of the first rule that applies to
-     * the number.
+     * Given a floating-point number, returns the keyword of the first rule
+     * that applies to the number.
      *
      * @param number The number for which the rule has to be determined.
      * @return The keyword of the selected rule.
@@ -2038,6 +2041,23 @@ public class PluralRules implements Serializable {
         return rules.select(new FixedDecimal(number));
     }
 
+    /**
+     * Given a formatted number, returns the keyword of the first rule that
+     * applies to the number.
+     *
+     * A FormattedNumber allows you to specify an exponent or trailing zeros,
+     * which can affect the plural category. To get a FormattedNumber, see
+     * {@link NumberFormatter}.
+     *
+     * @param number The number for which the rule has to be determined.
+     * @return The keyword of the selected rule.
+     * @draft ICU 64
+     * @provisional This API might change or be removed in a future release.
+     */
+    public String select(FormattedNumber number) {
+        return rules.select(number.getFixedDecimal());
+    }
+
     /**
      * Given a number, returns the keyword of the first rule that applies to
      * the number.
index 64d913825bad3eed4de52da565db1669d1785177..c23b8a3cd3545586132966026690b3e43abd6d6b 100644 (file)
@@ -41,6 +41,10 @@ import com.ibm.icu.dev.test.serializable.SerializableTestUtility;
 import com.ibm.icu.dev.util.CollectionUtilities;
 import com.ibm.icu.impl.Relation;
 import com.ibm.icu.impl.Utility;
+import com.ibm.icu.number.FormattedNumber;
+import com.ibm.icu.number.NumberFormatter;
+import com.ibm.icu.number.Precision;
+import com.ibm.icu.number.UnlocalizedNumberFormatter;
 import com.ibm.icu.text.NumberFormat;
 import com.ibm.icu.text.PluralRules;
 import com.ibm.icu.text.PluralRules.FixedDecimal;
@@ -323,8 +327,8 @@ public class PluralRulesTest extends TestFmwk {
     public void testUniqueRules() {
         main: for (ULocale locale : factory.getAvailableULocales()) {
             PluralRules rules = factory.forLocale(locale);
-            Map<String, PluralRules> keywordToRule = new HashMap<String, PluralRules>();
-            Collection<FixedDecimalSamples> samples = new LinkedHashSet<FixedDecimalSamples>();
+            Map<String, PluralRules> keywordToRule = new HashMap<>();
+            Collection<FixedDecimalSamples> samples = new LinkedHashSet<>();
 
             for (String keyword : rules.getKeywords()) {
                 for (SampleType sampleType : SampleType.values()) {
@@ -467,21 +471,59 @@ public class PluralRulesTest extends TestFmwk {
 
     @Test
     public void testBuiltInRules() {
-        // spot check
-        PluralRules rules = factory.forLocale(ULocale.US);
-        assertEquals("us 0", PluralRules.KEYWORD_OTHER, rules.select(0));
-        assertEquals("us 1", PluralRules.KEYWORD_ONE, rules.select(1));
-        assertEquals("us 2", PluralRules.KEYWORD_OTHER, rules.select(2));
-
-        rules = factory.forLocale(ULocale.JAPAN);
-        assertEquals("ja 0", PluralRules.KEYWORD_OTHER, rules.select(0));
-        assertEquals("ja 1", PluralRules.KEYWORD_OTHER, rules.select(1));
-        assertEquals("ja 2", PluralRules.KEYWORD_OTHER, rules.select(2));
-
-        rules = factory.forLocale(ULocale.createCanonical("ru"));
-        assertEquals("ru 0", PluralRules.KEYWORD_MANY, rules.select(0));
-        assertEquals("ru 1", PluralRules.KEYWORD_ONE, rules.select(1));
-        assertEquals("ru 2", PluralRules.KEYWORD_FEW, rules.select(2));
+        Object[][] cases = {
+                {"en-US", PluralRules.KEYWORD_OTHER, 0},
+                {"en-US", PluralRules.KEYWORD_ONE, 1},
+                {"en-US", PluralRules.KEYWORD_OTHER, 2},
+                {"ja-JP", PluralRules.KEYWORD_OTHER, 0},
+                {"ja-JP", PluralRules.KEYWORD_OTHER, 1},
+                {"ja-JP", PluralRules.KEYWORD_OTHER, 2},
+                {"ru", PluralRules.KEYWORD_MANY, 0},
+                {"ru", PluralRules.KEYWORD_ONE, 1},
+                {"ru", PluralRules.KEYWORD_FEW, 2}
+        };
+        for (Object[] cas : cases) {
+            ULocale locale = new ULocale((String) cas[0]);
+            PluralRules rules = factory.forLocale(locale);
+            String expectedKeyword = (String) cas[1];
+            double number = (Integer) cas[2];
+            String message = locale + " " + number;
+            // Check both as double and as FormattedNumber.
+            assertEquals(message, expectedKeyword, rules.select(number));
+            FormattedNumber fn = NumberFormatter.withLocale(locale).format(number);
+            assertEquals(message, expectedKeyword, rules.select(fn));
+        }
+    }
+
+    @Test
+    public void testSelectTrailingZeros() {
+        UnlocalizedNumberFormatter unf = NumberFormatter.with()
+                .precision(Precision.fixedFraction(2));
+        Object[][] cases = {
+                // 1) locale
+                // 2) double expected keyword
+                // 3) formatted number expected keyword (2 fraction digits)
+                // 4) input number
+                {"bs",  PluralRules.KEYWORD_FEW,   PluralRules.KEYWORD_OTHER, 5.2},  // 5.2 => two, but 5.20 => other
+                {"si",  PluralRules.KEYWORD_ONE,   PluralRules.KEYWORD_ONE,   0.0},
+                {"si",  PluralRules.KEYWORD_ONE,   PluralRules.KEYWORD_ONE,   1.0},
+                {"si",  PluralRules.KEYWORD_ONE,   PluralRules.KEYWORD_OTHER, 0.1},  // 0.1 => one, but 0.10 => other
+                {"si",  PluralRules.KEYWORD_ONE,   PluralRules.KEYWORD_ONE,   0.01}, // 0.01 => one
+                {"hsb", PluralRules.KEYWORD_FEW,   PluralRules.KEYWORD_FEW,   1.03}, // (f % 100 == 3) => few
+                {"hsb", PluralRules.KEYWORD_FEW,   PluralRules.KEYWORD_OTHER, 1.3},  // 1.3 => few, but 1.30 => other
+        };
+        for (Object[] cas : cases) {
+            ULocale locale = new ULocale((String) cas[0]);
+            PluralRules rules = factory.forLocale(locale);
+            String expectedDoubleKeyword = (String) cas[1];
+            String expectedFormattedKeyword = (String) cas[2];
+            double number = (Double) cas[3];
+            String message = locale + " " + number;
+            // Check both as double and as FormattedNumber.
+            assertEquals(message, expectedDoubleKeyword, rules.select(number));
+            FormattedNumber fn = unf.locale(locale).format(number);
+            assertEquals(message, expectedFormattedKeyword, rules.select(fn));
+        }
     }
 
     @Test
@@ -605,7 +647,7 @@ public class PluralRulesTest extends TestFmwk {
      */
     @Test
     public void TestGetSamples() {
-        Set<ULocale> uniqueRuleSet = new HashSet<ULocale>();
+        Set<ULocale> uniqueRuleSet = new HashSet<>();
         for (ULocale locale : factory.getAvailableULocales()) {
             uniqueRuleSet.add(PluralRules.getFunctionalEquivalent(locale, null));
         }
@@ -719,7 +761,7 @@ public class PluralRulesTest extends TestFmwk {
                 } else if ("null".equals(valueList)) {
                     values = null;
                 } else {
-                    values = new TreeSet<Double>();
+                    values = new TreeSet<>();
                     for (String value : valueList.split(",")) {
                         values.add(Double.parseDouble(value));
                     }
@@ -825,9 +867,9 @@ public class PluralRulesTest extends TestFmwk {
                                                                                                    // suppressed in
                                                                                                    // INTEGER but not
                                                                                                    // DECIMAL
-                }, { { "en", new HashSet<Double>(Arrays.asList(1.0d)) }, // check that 1 is suppressed
+                }, { { "en", new HashSet<>(Arrays.asList(1.0d)) }, // check that 1 is suppressed
                         { "one", KeywordStatus.SUPPRESSED, null }, { "other", KeywordStatus.UNBOUNDED, null } }, };
-        Output<Double> uniqueValue = new Output<Double>();
+        Output<Double> uniqueValue = new Output<>();
         for (Object[][] test : tests) {
             ULocale locale = new ULocale((String) test[0][0]);
             // NumberType numberType = (NumberType) test[1];
@@ -938,7 +980,7 @@ public class PluralRulesTest extends TestFmwk {
     };
 
     private void generateLOCALE_SNAPSHOT() {
-        Comparator c = new CollectionUtilities.CollectionComparator<Comparable>();
+        Comparator c = new CollectionUtilities.CollectionComparator<>();
         Relation<Set<StandardPluralCategories>, PluralRules> setsToRules = Relation.of(
                 new TreeMap<Set<StandardPluralCategories>, Set<PluralRules>>(c), TreeSet.class, PLURAL_RULE_COMPARATOR);
         Relation<PluralRules, ULocale> data = Relation.of(
@@ -1054,7 +1096,7 @@ public class PluralRulesTest extends TestFmwk {
 
     @Test
     public void TestSerialization() {
-        Output<Integer> size = new Output<Integer>();
+        Output<Integer> size = new Output<>();
         int max = 0;
         for (ULocale locale : PluralRules.getAvailableULocales()) {
             PluralRules item = PluralRules.forLocale(locale);