diff --git a/icu4c/source/i18n/plurrule.cpp b/icu4c/source/i18n/plurrule.cpp index ad950e1176e..d1918c46981 100644 --- a/icu4c/source/i18n/plurrule.cpp +++ b/icu4c/source/i18n/plurrule.cpp @@ -1881,8 +1881,8 @@ void FixedDecimal::adjustForMinFractionDigits(int32_t minFractionDigits) { double FixedDecimal::getPluralOperand(PluralOperand operand) const { switch(operand) { - case PLURAL_OPERAND_N: return source; - case PLURAL_OPERAND_I: return static_cast(intValue); + case PLURAL_OPERAND_N: return (exponent == 0 ? source : source * pow(10, exponent)); + case PLURAL_OPERAND_I: return (double) longValue(); case PLURAL_OPERAND_F: return static_cast(decimalDigits); case PLURAL_OPERAND_T: return static_cast(decimalDigitsWithoutTrailingZeros); case PLURAL_OPERAND_V: return visibleDecimalDigitCount; @@ -1931,6 +1931,18 @@ UnicodeString FixedDecimal::toString() const { return UnicodeString(buffer, -1, US_INV); } +double FixedDecimal::doubleValue() const { + return (isNegative ? -source : source) * pow(10, exponent); +} + +int64_t FixedDecimal::longValue() const { + if (exponent == 0) { + return intValue; + } else { + return (long) (pow(10, exponent) * intValue); + } +} + PluralAvailableLocalesEnumeration::PluralAvailableLocalesEnumeration(UErrorCode &status) { fOpenStatus = status; diff --git a/icu4c/source/i18n/plurrule_impl.h b/icu4c/source/i18n/plurrule_impl.h index 1a30096f2f4..7274da58f06 100644 --- a/icu4c/source/i18n/plurrule_impl.h +++ b/icu4c/source/i18n/plurrule_impl.h @@ -329,6 +329,9 @@ class U_I18N_API FixedDecimal: public IFixedDecimal, public UObject { UnicodeString toString() const; + double doubleValue() const; + int64_t longValue() const; + double source; int32_t visibleDecimalDigitCount; int64_t decimalDigits; diff --git a/icu4c/source/test/intltest/plurults.cpp b/icu4c/source/test/intltest/plurults.cpp index df37bd20b70..9131d370317 100644 --- a/icu4c/source/test/intltest/plurults.cpp +++ b/icu4c/source/test/intltest/plurults.cpp @@ -57,6 +57,8 @@ void PluralRulesTest::runIndexedTest( int32_t index, UBool exec, const char* &na TESTCASE_AUTO(testGetAllKeywordValues); TESTCASE_AUTO(testScientificPluralKeyword); TESTCASE_AUTO(testCompactDecimalPluralKeyword); + TESTCASE_AUTO(testDoubleValue); + TESTCASE_AUTO(testLongValue); TESTCASE_AUTO(testOrdinal); TESTCASE_AUTO(testSelect); TESTCASE_AUTO(testSelectRange); @@ -898,6 +900,94 @@ PluralRulesTest::testCompactDecimalPluralKeyword() { } } +void +PluralRulesTest::testDoubleValue() { + IcuTestErrorCode errorCode(*this, "testDoubleValue"); + + struct IntTestCase { + const int64_t inputNum; + const double expVal; + } intCases[] = { + {-101, -101.0}, + {-100, -100.0}, + {-1, -1.0}, + {0, 0.0}, + {1, 1.0}, + {100, 100.0} + }; + for (const auto& cas : intCases) { + const int64_t inputNum = cas.inputNum; + const double expVal = cas.expVal; + + FixedDecimal fd(inputNum); + UnicodeString message(u"FixedDecimal::doubleValue() for" + Int64ToUnicodeString(inputNum)); + assertEquals(message, expVal, fd.doubleValue()); + } + + struct DoubleTestCase { + const double inputNum; + const double expVal; + } dblCases[] = { + {-0.0, -0.0}, + {0.1, 0.1}, + {1.999, 1.999}, + {2.0, 2.0}, + {100.001, 100.001} + }; + for (const auto & cas : dblCases) { + const double inputNum = cas.inputNum; + const double expVal = cas.expVal; + + FixedDecimal fd(inputNum); + UnicodeString message(u"FixedDecimal::doubleValue() for" + DoubleToUnicodeString(inputNum)); + assertEquals(message, expVal, fd.doubleValue()); + } +} + +void +PluralRulesTest::testLongValue() { + IcuTestErrorCode errorCode(*this, "testLongValue"); + + struct IntTestCase { + const int64_t inputNum; + const int64_t expVal; + } intCases[] = { + {-101, 101}, + {-100, 100}, + {-1, 1}, + {0, 0}, + {1, 1}, + {100, 100} + }; + for (const auto& cas : intCases) { + const int64_t inputNum = cas.inputNum; + const int64_t expVal = cas.expVal; + + FixedDecimal fd(inputNum); + UnicodeString message(u"FixedDecimal::longValue() for" + Int64ToUnicodeString(inputNum)); + assertEquals(message, expVal, fd.longValue()); + } + + struct DoubleTestCase { + const double inputNum; + const int64_t expVal; + } dblCases[] = { + {-0.0, 0}, + {0.1, 0}, + {1.999, 1}, + {2.0, 2}, + {100.001, 100} + }; + for (const auto & cas : dblCases) { + const double inputNum = cas.inputNum; + const int64_t expVal = cas.expVal; + + FixedDecimal fd(inputNum); + UnicodeString message(u"FixedDecimal::longValue() for" + DoubleToUnicodeString(inputNum)); + assertEquals(message, expVal, fd.longValue()); + } +} + UnicodeString PluralRulesTest::getPluralKeyword(const LocalPointer &rules, Locale locale, double number, const char16_t* skeleton) { IcuTestErrorCode errorCode(*this, "getPluralKeyword"); UnlocalizedNumberFormatter ulnf = NumberFormatter::forSkeleton(skeleton, errorCode); diff --git a/icu4c/source/test/intltest/plurults.h b/icu4c/source/test/intltest/plurults.h index 6a92225aec2..76ecb6bf191 100644 --- a/icu4c/source/test/intltest/plurults.h +++ b/icu4c/source/test/intltest/plurults.h @@ -36,6 +36,8 @@ private: void testWithin(); void testGetAllKeywordValues(); void testCompactDecimalPluralKeyword(); + void testDoubleValue(); + void testLongValue(); void testScientificPluralKeyword(); void testOrdinal(); void testSelect(); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/PluralRules.java b/icu4j/main/classes/core/src/com/ibm/icu/text/PluralRules.java index ba715bd225e..c9a2936c908 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/PluralRules.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/PluralRules.java @@ -688,9 +688,7 @@ public class PluralRules implements Serializable { source = isNegative ? -n : n; visibleDecimalDigitCount = v; decimalDigits = f; - integerValue = n > MAX - ? MAX - : (long)n; + integerValue = n > MAX ? MAX : (long) source; int initExpVal = e; if (initExpVal == 0) { initExpVal = c; @@ -922,15 +920,15 @@ public class PluralRules implements Serializable { @Deprecated public double getPluralOperand(Operand operand) { switch(operand) { - case n: return source; - case i: return integerValue; + case n: return (exponent == 0 ? source : source * Math.pow(10, exponent)); + case i: return intValue(); case f: return decimalDigits; case t: return decimalDigitsWithoutTrailingZeros; case v: return visibleDecimalDigitCount; case w: return visibleDecimalDigitCountWithoutTrailingZeros; case e: return exponent; case c: return exponent; - default: return source; + default: return doubleValue(); } } @@ -1078,7 +1076,7 @@ public class PluralRules implements Serializable { @Deprecated public long getShiftedValue() { if (exponent != 0 && visibleDecimalDigitCount == 0 && decimalDigits == 0) { - // Need to taxe exponent into account if we have it + // Need to take exponent into account if we have it return (long)(source * Math.pow(10, exponent)); } return integerValue * baseFactor + decimalDigits; diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/PluralRulesTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/PluralRulesTest.java index d4cb9bbfc02..6aae8c27ed2 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/PluralRulesTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/PluralRulesTest.java @@ -465,21 +465,56 @@ public class PluralRulesTest extends TestFmwk { } public void checkValue(String title1, PluralRules rules, String expected, String value) { - double number = Double.parseDouble(value); - int decimalPos = value.indexOf('.') + 1; - int countVisibleFractionDigits; - int fractionaldigits; - if (decimalPos == 0) { - countVisibleFractionDigits = fractionaldigits = 0; - } else { - countVisibleFractionDigits = value.length() - decimalPos; - fractionaldigits = Integer.parseInt(value.substring(decimalPos)); - } - String result = rules.select(number, countVisibleFractionDigits, fractionaldigits); + FixedDecimal fdNum = new FixedDecimal(value); + + String result = rules.select(fdNum); ULocale locale = null; assertEquals(getAssertMessage(title1, locale, rules, expected) + "; value: " + value, expected, result); } + /** + * Check the testing helper method checkValue(), which parses a plural + * rule's sample string as a {@link FormattedNumber} in order to call + * {@code PluralRules.select(FormattedNumber)}, which in turn can support + * the exponent in plural sample numbers like 1e6 and 2.8c3. + */ + @Test + public void testCheckValue() { + String ruleString = + "many: e = 0 and i != 0 and i % 1000000 = 0 and v = 0 or e != 0..5" + + " @integer 1000000, 1e6, 2e6, 3e6, 4e6, 5e6, 6e6, …" + + " @decimal 1.0000001e6, 1.1e6, 2.0000001e6, 2.1e6, 3.0000001e6, 3.1e6, …; " + + "one: i = 1 and v = 0" + + " @integer 1; " + + "other: " + + " @integer 0, 2~16, 100, 1000, 10000, 100000, 1e3, 2e3, 3e3, 4e3, 5e3, 6e3, …" + + " @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, 1.0001e3, 1.1e3, 2.0001e3, 2.1e3, 3.0001e3, 3.1e3, …"; + PluralRules rules = PluralRules.createRules(ruleString); + + Object[][] casesData = { + // expected category, value string + {"many", "1000000"}, + {"many", "1e6"}, + {"many", "1.1e6"}, + {"one", "1"}, + {"other", "0"}, + {"other", "1e5"}, + {"other", "100000"}, + {"other", "0.0"}, + {"other", "100000.0"}, + {"other", "1000000.0"} + }; + + for (Object[] caseDatum : casesData) { + String expCategory = (String) caseDatum[0]; + String inputValueStr = (String) caseDatum[1]; + + String msg = "checkValue(" + inputValueStr + ")"; + + checkValue(msg, rules, expCategory, inputValueStr); + } + } + private static String[][] equalityTestData = { // once we add fractions, we had to retract the "test all possibilities" for equality, // so we only have a limited set of equality tests now. @@ -1110,6 +1145,78 @@ public class PluralRulesTest extends TestFmwk { return pluralKeyword; } + @Test + public void testDoubleValue() { + Object[][] intCasesData = { + // source number, expected double value + {-101, -101.0}, + {-100, -100.0}, + {-1, -1.0}, + {0, 0.0}, + {1, 1.0}, + {100, 100.0} + }; + + for (Object[] caseDatum : intCasesData) { + double inputNum = (int) caseDatum[0]; + double expVal = (double) caseDatum[1]; + FixedDecimal fd = new FixedDecimal(inputNum); + assertEquals("FixedDecimal.doubleValue() for " + inputNum, expVal, fd.doubleValue()); + } + + Object[][] doubleCasesData = { + // source number, expected double value + {-0.0, -0.0}, + {0.1, 0.1}, + {1.999, 1.999}, + {2.0, 2.0}, + {100.001, 100.001} + }; + + for (Object[] caseDatum : doubleCasesData) { + double inputNum = (double) caseDatum[0]; + double expVal = (double) caseDatum[1]; + FixedDecimal fd = new FixedDecimal(inputNum); + assertEquals("FixedDecimal.doubleValue() for " + inputNum, expVal, fd.doubleValue()); + } + } + + @Test + public void testLongValue() { + Object[][] intCasesData = { + // source number, expected double value + {-101, 101}, + {-100, 100}, + {-1, 1}, + {0, 0}, + {1, 1}, + {100, 100} + }; + + for (Object[] caseDatum : intCasesData) { + long inputNum = (int) caseDatum[0]; + long expVal = (int) caseDatum[1]; + FixedDecimal fd = new FixedDecimal(inputNum); + assertEquals("FixedDecimal.longValue() for " + inputNum, expVal, fd.longValue()); + } + + Object[][] doubleCasesData = { + // source number, expected double value + {-0.0, 0}, + {0.1, 0}, + {1.999, 1}, + {2.0, 2}, + {100.001, 100} + }; + + for (Object[] caseDatum : doubleCasesData) { + double inputNum = (double) caseDatum[0]; + long expVal = (int) caseDatum[1]; + FixedDecimal fd = new FixedDecimal(inputNum); + assertEquals("FixedDecimal.longValue() for " + inputNum, expVal, fd.longValue()); + } + } + enum StandardPluralCategories { zero, one, two, few, many, other; /**