ICU-21270 Support exponent in FixedDecimal and samples in C++

This commit is contained in:
Elango Cheran 2020-09-22 18:16:21 -07:00 committed by Elango
parent c4fa504fec
commit 6700602974
5 changed files with 394 additions and 118 deletions

View 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) {

View file

@ -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;

View 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.

View 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.
// 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<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());
}
}
}
}
}
}
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<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)) {
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<PluralRules> 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<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);
}
}
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

View 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,