PluralRules*
PluralRules::clone() const { // Since clone doesn't have a 'status' parameter, the best we can do is return nullptr if // the newly created object was not fully constructed properly (an error occurred).
UErrorCode localStatus = U_ZERO_ERROR; return clone(localStatus);
}
PluralRules&
PluralRules::operator=(const PluralRules& other) { if (this != &other) { delete mRules;
mRules = nullptr; delete mStandardPluralRanges;
mStandardPluralRanges = nullptr;
mInternalStatus = other.mInternalStatus; if (U_FAILURE(mInternalStatus)) { // bail out early if the object we were copying from was already 'invalid'. return *this;
} if (other.mRules != nullptr) {
mRules = new RuleChain(*other.mRules); if (mRules == nullptr) {
mInternalStatus = U_MEMORY_ALLOCATION_ERROR;
} elseif (U_FAILURE(mRules->fInternalStatus)) { // If the RuleChain wasn't fully copied, then set our status to failure as well.
mInternalStatus = mRules->fInternalStatus;
}
} if (other.mStandardPluralRanges != nullptr) {
mStandardPluralRanges = other.mStandardPluralRanges->copy(mInternalStatus)
.toPointer(mInternalStatus)
.orphan();
}
} return *this;
}
PluralRules* U_EXPORT2
PluralRules::internalForLocale(const Locale& locale, UPluralType type, UErrorCode& status) { if (U_FAILURE(status)) { return nullptr;
} if (type >= UPLURAL_TYPE_COUNT) {
status = U_ILLEGAL_ARGUMENT_ERROR; return nullptr;
}
LocalPointer<PluralRules> newObj(new PluralRules(status), status); if (U_FAILURE(status)) { return nullptr;
}
UnicodeString locRule = newObj->getRuleFromResource(locale, type, status); // TODO: which other errors, if any, should be returned? if (locRule.length() == 0) { // If an out-of-memory error occurred, then stop and report the failure. if (status == U_MEMORY_ALLOCATION_ERROR) { return nullptr;
} // Locales with no specific rules (all numbers have the "other" category // will return a U_MISSING_RESOURCE_ERROR at this point. This is not // an error.
locRule = UnicodeString(PLURAL_DEFAULT_RULE);
status = U_ZERO_ERROR;
}
PluralRuleParser parser;
parser.parse(locRule, newObj.getAlias(), status); // TODO: should rule parse errors be returned, or // should we silently use default rules? // Original impl used default rules. // Ask the question to ICU Core.
/** * Helper method for the overrides of getSamples() for double and DecimalQuantity * return value types. Provide only one of an allocated array of double or * DecimalQuantity, and a nullptr for the other.
*/ static int32_t
getSamplesFromString(const UnicodeString &samples, double *destDbl,
DecimalQuantity* destDq, int32_t destCapacity,
UErrorCode& status) {
DecimalQuantity dq(rangeLo); double dblValue = dq.toDouble(); double end = rangeHi.toDouble();
while (dblValue <= end) { 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 (!(dblValue == floor(dblValue) && dq.fractionCount() > 0)) {
destDbl[sampleCount++] = dblValue;
}
} else {
destDq[sampleCount++] = dq;
} if (sampleCount >= destCapacity) { break;
}
// Increment dq for next iteration
// Because DecNum and DecimalQuantity do not support // add operations, we need to convert to/from double, // despite precision lossiness for decimal fractions like 0.1.
dblValue += incrementVal;
DecNum newDqDecNum;
newDqDecNum.setTo(dblValue, status);
DecimalQuantity newDq;
newDq.setToDecNum(newDqDecNum, status);
newDq.setMinFraction(-lowerDispMag);
newDq.roundToMagnitude(lowerDispMag, RoundingMode::UNUM_ROUND_HALFEVEN, status);
newDq.adjustMagnitude(-exponent);
newDq.adjustExponent(exponent);
dblValue = newDq.toDouble();
dq = newDq;
}
}
sampleStartIdx = sampleEndIdx + 1;
} return sampleCount;
}
if ( this == &other ) { returntrue;
}
LocalPointer<StringEnumeration> myKeywordList(getKeywords(status));
LocalPointer<StringEnumeration> otherKeywordList(other.getKeywords(status)); if (U_FAILURE(status)) { returnfalse;
}
if (myKeywordList->count(status)!=otherKeywordList->count(status)) { returnfalse;
}
myKeywordList->reset(status); while ((ptrKeyword=myKeywordList->snext(status))!=nullptr) { if (!other.isKeyword(*ptrKeyword)) { returnfalse;
}
}
otherKeywordList->reset(status); while ((ptrKeyword=otherKeywordList->snext(status))!=nullptr) { if (!this->isKeyword(*ptrKeyword)) { returnfalse;
}
} if (U_FAILURE(status)) { returnfalse;
}
returntrue;
}
void
PluralRuleParser::parse(const UnicodeString& ruleData, PluralRules *prules, UErrorCode &status)
{ if (U_FAILURE(status)) { return;
}
U_ASSERT(ruleIndex == 0); // Parsers are good for a single use only!
ruleSrc = &ruleData;
while (ruleIndex< ruleSrc->length()) {
getNextToken(status); if (U_FAILURE(status)) { return;
}
checkSyntax(status); if (U_FAILURE(status)) { return;
} switch (type) { case tAnd:
U_ASSERT(curAndConstraint != nullptr);
curAndConstraint = curAndConstraint->add(status); break; case tOr:
{
U_ASSERT(currentChain != nullptr);
OrConstraint *orNode=currentChain->ruleHeader; while (orNode->next != nullptr) {
orNode = orNode->next;
}
orNode->next= new OrConstraint(); if (orNode->next == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR; break;
}
orNode=orNode->next;
orNode->next=nullptr;
curAndConstraint = orNode->add(status);
} break; case tIs:
U_ASSERT(curAndConstraint != nullptr);
U_ASSERT(curAndConstraint->value == -1);
U_ASSERT(curAndConstraint->rangeList == nullptr); break; case tNot:
U_ASSERT(curAndConstraint != nullptr);
curAndConstraint->negated=true; break;
case tNotEqual:
curAndConstraint->negated=true;
U_FALLTHROUGH; case tIn: case tWithin: case tEqual:
{
U_ASSERT(curAndConstraint != nullptr); if (curAndConstraint->rangeList != nullptr) { // Already get a '='.
status = U_UNEXPECTED_TOKEN; break;
}
LocalPointer<UVector32> newRangeList(new UVector32(status), status); if (U_FAILURE(status)) { break;
}
curAndConstraint->rangeList = newRangeList.orphan();
curAndConstraint->rangeList->addElement(-1, status); // range Low
curAndConstraint->rangeList->addElement(-1, status); // range Hi
rangeLowIdx = 0;
rangeHiIdx = 1;
curAndConstraint->value=PLURAL_RANGE_HIGH;
curAndConstraint->integerOnly = (type != tWithin);
} break; case tNumber:
U_ASSERT(curAndConstraint != nullptr); if ( (curAndConstraint->op==AndConstraint::MOD)&&
(curAndConstraint->opNum == -1 ) ) {
int32_t num = getNumberValue(token); if (num == -1) {
status = U_UNEXPECTED_TOKEN; break;
}
curAndConstraint->opNum=num;
} else { if (curAndConstraint->rangeList == nullptr) { // this is for an 'is' rule
int32_t num = getNumberValue(token); if (num == -1) {
status = U_UNEXPECTED_TOKEN; break;
}
curAndConstraint->value = num;
} else { // this is for an 'in' or 'within' rule if (curAndConstraint->rangeList->elementAti(rangeLowIdx) == -1) {
int32_t num = getNumberValue(token); if (num == -1) {
status = U_UNEXPECTED_TOKEN; break;
}
curAndConstraint->rangeList->setElementAt(num, rangeLowIdx);
curAndConstraint->rangeList->setElementAt(num, rangeHiIdx);
} else {
int32_t num = getNumberValue(token); if (num == -1) {
status = U_UNEXPECTED_TOKEN; break;
}
curAndConstraint->rangeList->setElementAt(num, rangeHiIdx); if (curAndConstraint->rangeList->elementAti(rangeLowIdx) >
curAndConstraint->rangeList->elementAti(rangeHiIdx)) { // Range Lower bound > Range Upper bound. // U_UNEXPECTED_TOKEN seems a little funny, but it is consistently // used for all plural rule parse errors.
status = U_UNEXPECTED_TOKEN; break;
}
}
}
} break; case tComma: // TODO: rule syntax checking is inadequate, can happen with badly formed rules. // Catch cases like "n mod 10, is 1" here instead. if (curAndConstraint == nullptr || curAndConstraint->rangeList == nullptr) {
status = U_UNEXPECTED_TOKEN; break;
}
U_ASSERT(curAndConstraint->rangeList->size() >= 2);
rangeLowIdx = curAndConstraint->rangeList->size();
curAndConstraint->rangeList->addElement(-1, status); // range Low
rangeHiIdx = curAndConstraint->rangeList->size();
curAndConstraint->rangeList->addElement(-1, status); // range Hi break; case tMod:
U_ASSERT(curAndConstraint != nullptr);
curAndConstraint->op=AndConstraint::MOD; break; case tVariableN: case tVariableI: case tVariableF: case tVariableT: case tVariableE: case tVariableC: case tVariableV:
U_ASSERT(curAndConstraint != nullptr);
curAndConstraint->digitsType = type; break; case tKeyword:
{
RuleChain *newChain = new RuleChain; if (newChain == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR; break;
}
newChain->fKeyword = token; if (prules->mRules == nullptr) {
prules->mRules = newChain;
} else { // The new rule chain goes at the end of the linked list of rule chains, // unless there is an "other" keyword & chain. "other" must remain last.
RuleChain *insertAfter = prules->mRules; while (insertAfter->fNext!=nullptr &&
insertAfter->fNext->fKeyword.compare(PLURAL_KEYWORD_OTHER, 5) != 0 ){
insertAfter=insertAfter->fNext;
}
newChain->fNext = insertAfter->fNext;
insertAfter->fNext = newChain;
}
OrConstraint *orNode = new OrConstraint(); if (orNode == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR; break;
}
newChain->ruleHeader = orNode;
curAndConstraint = orNode->add(status);
currentChain = newChain;
} break;
case tInteger: for (;;) {
getNextToken(status); if (U_FAILURE(status) || type == tSemiColon || type == tEOF || type == tAt) { break;
} if (type == tEllipsis) {
currentChain->fIntegerSamplesUnbounded = true; continue;
}
currentChain->fIntegerSamples.append(token);
} break;
case tDecimal: for (;;) {
getNextToken(status); if (U_FAILURE(status) || type == tSemiColon || type == tEOF || type == tAt) { break;
} if (type == tEllipsis) {
currentChain->fDecimalSamplesUnbounded = true; continue;
}
currentChain->fDecimalSamples.append(token);
} break;
UBool
AndConstraint::isFulfilled(const IFixedDecimal &number) {
UBool result = true; if (digitsType == none) { // An empty AndConstraint, created by a rule with a keyword but no following expression. returntrue;
}
PluralOperand operand = tokenTypeToPluralOperand(digitsType); double n = number.getPluralOperand(operand); // pulls n | i | v | f value for the number. // Will always be positive. // May be non-integer (n option only) do { if (integerOnly && n != uprv_floor(n)) {
result = false; break;
}
if (op == MOD) {
n = fmod(n, opNum);
} if (rangeList == nullptr) {
result = value == -1 || // empty rule
n == value; // 'is' rule break;
}
result = false; // 'in' or 'within' rule for (int32_t r=0; r<rangeList->size(); r+=2) { if (rangeList->elementAti(r) <= n && n <= rangeList->elementAti(r+1)) {
result = true; break;
}
}
} while (false);
if (negated) {
result = !result;
} return result;
}
AndConstraint*
AndConstraint::add(UErrorCode& status) { if (U_FAILURE(fInternalStatus)) {
status = fInternalStatus; return nullptr;
}
this->next = new AndConstraint(); if (this->next == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
} return this->next;
}
OrConstraint::OrConstraint(const OrConstraint& other) {
this->fInternalStatus = other.fInternalStatus; if (U_FAILURE(fInternalStatus)) { return; // stop early if the object we are copying from is invalid.
} if ( other.childNode != nullptr ) {
this->childNode = new AndConstraint(*(other.childNode)); if (this->childNode == nullptr) {
fInternalStatus = U_MEMORY_ALLOCATION_ERROR; return;
}
} if (other.next != nullptr ) {
this->next = new OrConstraint(*(other.next)); if (this->next == nullptr) {
fInternalStatus = U_MEMORY_ALLOCATION_ERROR; return;
} if (U_FAILURE(this->next->fInternalStatus)) {
this->fInternalStatus = this->next->fInternalStatus;
}
}
}
while (orRule!=nullptr && !result) {
result=true;
AndConstraint* andRule = orRule->childNode; while (andRule!=nullptr && result) {
result = andRule->isFulfilled(number);
andRule=andRule->next;
}
orRule = orRule->next;
}
return result;
}
RuleChain::RuleChain(const RuleChain& other) :
fKeyword(other.fKeyword), fDecimalSamples(other.fDecimalSamples),
fIntegerSamples(other.fIntegerSamples), fDecimalSamplesUnbounded(other.fDecimalSamplesUnbounded),
fIntegerSamplesUnbounded(other.fIntegerSamplesUnbounded), fInternalStatus(other.fInternalStatus) { if (U_FAILURE(this->fInternalStatus)) { return; // stop early if the object we are copying from is invalid.
} if (other.ruleHeader != nullptr) {
this->ruleHeader = new OrConstraint(*(other.ruleHeader)); if (this->ruleHeader == nullptr) {
this->fInternalStatus = U_MEMORY_ALLOCATION_ERROR;
} elseif (U_FAILURE(this->ruleHeader->fInternalStatus)) { // If the OrConstraint wasn't fully copied, then set our status to failure as well.
this->fInternalStatus = this->ruleHeader->fInternalStatus; return; // exit early.
}
} if (other.fNext != nullptr ) {
this->fNext = new RuleChain(*other.fNext); if (this->fNext == nullptr) {
this->fInternalStatus = U_MEMORY_ALLOCATION_ERROR;
} elseif (U_FAILURE(this->fNext->fInternalStatus)) { // If the RuleChain wasn't fully copied, then set our status to failure as well.
this->fInternalStatus = this->fNext->fInternalStatus;
}
}
}
if ( ruleHeader != nullptr ) {
result += fKeyword;
result += COLON;
result += SPACE;
OrConstraint* orRule=ruleHeader; while ( orRule != nullptr ) {
AndConstraint* andRule=orRule->childNode; while ( andRule != nullptr ) { if ((andRule->op==AndConstraint::NONE) && (andRule->rangeList==nullptr) && (andRule->value == -1)) { // Empty Rules.
} elseif ( (andRule->op==AndConstraint::NONE) && (andRule->rangeList==nullptr) ) {
result += tokenString(andRule->digitsType);
result += UNICODE_STRING_SIMPLE(" is "); if (andRule->negated) {
result += UNICODE_STRING_SIMPLE("not ");
}
uprv_itou(digitString,16, andRule->value,10,0);
result += UnicodeString(digitString);
} else {
result += tokenString(andRule->digitsType);
result += SPACE; if (andRule->op==AndConstraint::MOD) {
result += UNICODE_STRING_SIMPLE("mod ");
uprv_itou(digitString,16, andRule->opNum,10,0);
result += UnicodeString(digitString);
} if (andRule->rangeList==nullptr) { if (andRule->negated) {
result += UNICODE_STRING_SIMPLE(" is not ");
uprv_itou(digitString,16, andRule->value,10,0);
result += UnicodeString(digitString);
} else {
result += UNICODE_STRING_SIMPLE(" is ");
uprv_itou(digitString,16, andRule->value,10,0);
result += UnicodeString(digitString);
}
} else { if (andRule->negated) { if ( andRule->integerOnly ) {
result += UNICODE_STRING_SIMPLE(" not in ");
} else {
result += UNICODE_STRING_SIMPLE(" not within ");
}
} else { if ( andRule->integerOnly ) {
result += UNICODE_STRING_SIMPLE(" in ");
} else {
result += UNICODE_STRING_SIMPLE(" within ");
}
} for (int32_t r=0; r<andRule->rangeList->size(); r+=2) {
int32_t rangeLo = andRule->rangeList->elementAti(r);
int32_t rangeHi = andRule->rangeList->elementAti(r+1);
uprv_itou(digitString,16, rangeLo, 10, 0);
result += UnicodeString(digitString);
result += UNICODE_STRING_SIMPLE("..");
uprv_itou(digitString,16, rangeHi, 10,0);
result += UnicodeString(digitString); if (r+2 < andRule->rangeList->size()) {
result += UNICODE_STRING_SIMPLE(", ");
}
}
}
} if ( (andRule=andRule->next) != nullptr) {
result += UNICODE_STRING_SIMPLE(" and ");
}
} if ( (orRule = orRule->next) != nullptr ) {
result += UNICODE_STRING_SIMPLE(" or ");
}
}
} if ( fNext != nullptr ) {
result += UNICODE_STRING_SIMPLE("; ");
fNext->dumpRules(result);
}
}
void
PluralRuleParser::checkSyntax(UErrorCode &status)
{ if (U_FAILURE(status)) { return;
} if (!(prevType==none || prevType==tSemiColon)) {
type = getKeyType(token, type); // Switch token type from tKeyword if we scanned a reserved word, // and we are not at the start of a rule, where a // keyword is expected.
}
switch(prevType) { case none: case tSemiColon: if (type!=tKeyword && type != tEOF) {
status = U_UNEXPECTED_TOKEN;
} break; case tVariableN: case tVariableI: case tVariableF: case tVariableT: case tVariableE: case tVariableC: case tVariableV: if (type != tIs && type != tMod && type != tIn &&
type != tNot && type != tWithin && type != tEqual && type != tNotEqual) {
status = U_UNEXPECTED_TOKEN;
} break; case tKeyword: if (type != tColon) {
status = U_UNEXPECTED_TOKEN;
} break; case tColon: if (!(type == tVariableN ||
type == tVariableI ||
type == tVariableF ||
type == tVariableT ||
type == tVariableE ||
type == tVariableC ||
type == tVariableV ||
type == tAt)) {
status = U_UNEXPECTED_TOKEN;
} break; case tIs: if ( type != tNumber && type != tNot) {
status = U_UNEXPECTED_TOKEN;
} break; case tNot: if (type != tNumber && type != tIn && type != tWithin) {
status = U_UNEXPECTED_TOKEN;
} break; case tMod: case tDot2: case tIn: case tWithin: case tEqual: case tNotEqual: if (type != tNumber) {
status = U_UNEXPECTED_TOKEN;
} break; case tAnd: case tOr: if ( type != tVariableN &&
type != tVariableI &&
type != tVariableF &&
type != tVariableT &&
type != tVariableE &&
type != tVariableC &&
type != tVariableV) {
status = U_UNEXPECTED_TOKEN;
} break; case tComma: if (type != tNumber) {
status = U_UNEXPECTED_TOKEN;
} break; case tNumber: if (type != tDot2 && type != tSemiColon && type != tIs && type != tNot &&
type != tIn && type != tEqual && type != tNotEqual && type != tWithin &&
type != tAnd && type != tOr && type != tComma && type != tAt &&
type != tEOF)
{
status = U_UNEXPECTED_TOKEN;
} // TODO: a comma following a number that is not part of a range will be allowed. // It's not the only case of this sort of thing. Parser needs a re-write. break; case tAt: if (type != tDecimal && type != tInteger) {
status = U_UNEXPECTED_TOKEN;
} break; default:
status = U_UNEXPECTED_TOKEN; break;
}
}
/* * Scan the next token from the input rules. * rules and returned token type are in the parser state variables.
*/ void
PluralRuleParser::getNextToken(UErrorCode &status)
{ if (U_FAILURE(status)) { return;
}
char16_t ch; while (ruleIndex < ruleSrc->length()) {
ch = ruleSrc->charAt(ruleIndex);
type = charType(ch); if (type != tSpace) { break;
}
++(ruleIndex);
} if (ruleIndex >= ruleSrc->length()) {
type = tEOF; return;
}
int32_t curIndex= ruleIndex;
switch (type) { case tColon: case tSemiColon: case tComma: case tEllipsis: case tTilde: // scanned '~' case tAt: // scanned '@' case tEqual: // scanned '=' case tMod: // scanned '%' // Single character tokens.
++curIndex; break;
case tNotEqual: // scanned '!' if (ruleSrc->charAt(curIndex+1) == EQUALS) {
curIndex += 2;
} else {
type = none;
curIndex += 1;
} break;
case tKeyword: while (type == tKeyword && ++curIndex < ruleSrc->length()) {
ch = ruleSrc->charAt(curIndex);
type = charType(ch);
}
type = tKeyword; break;
case tNumber: while (type == tNumber && ++curIndex < ruleSrc->length()) {
ch = ruleSrc->charAt(curIndex);
type = charType(ch);
}
type = tNumber; break;
case tDot: // We could be looking at either ".." in a range, or "..." at the end of a sample. if (curIndex+1 >= ruleSrc->length() || ruleSrc->charAt(curIndex+1) != DOT) {
++curIndex; break; // Single dot
} if (curIndex+2 >= ruleSrc->length() || ruleSrc->charAt(curIndex+2) != DOT) {
curIndex += 2;
type = tDot2; break; // double dot
}
type = tEllipsis;
curIndex += 3; break; // triple dot
default:
status = U_UNEXPECTED_TOKEN;
++curIndex; break;
}
// Fast path only exact initialization. Return true if successful. // Note: Do not multiply by 10 each time through loop, rounding cruft can build // up that makes the check for an integer result fail. // A single multiply of the original number works more reliably. static int32_t p10[] = {1, 10, 100, 1000, 10000};
UBool FixedDecimal::quickInit(double n) {
UBool success = false;
n = fabs(n);
int32_t numFractionDigits; for (numFractionDigits = 0; numFractionDigits <= 3; numFractionDigits++) { double scaledN = n * p10[numFractionDigits]; if (scaledN == floor(scaledN)) {
success = true; break;
}
} if (success) {
init(n, numFractionDigits, getFractionalDigits(n, numFractionDigits));
} return success;
}
int32_t FixedDecimal::decimals(double n) { // Count the number of decimal digits in the fraction part of the number, excluding trailing zeros. // fastpath the common cases, integers or fractions with 3 or fewer digits
n = fabs(n); for (int ndigits=0; ndigits<=3; ndigits++) { double scaledN = n * p10[ndigits]; if (scaledN == floor(scaledN)) { return ndigits;
}
}
// Slow path, convert with snprintf, parse converted output. char buf[30] = {0};
snprintf(buf, sizeof(buf), "%1.15e", n); // formatted number looks like this: 1.234567890123457e-01 int exponent = atoi(buf+18); int numFractionDigits = 15; for (int i=16; ; --i) { if (buf[i] != '0') { break;
}
--numFractionDigits;
}
numFractionDigits -= exponent; // Fraction part of fixed point representation. return numFractionDigits;
}
// Get the fraction digits of a double, represented as an integer. // v is the number of visible fraction digits in the displayed form of the number. // Example: n = 1001.234, v = 6, result = 234000 // TODO: need to think through how this is used in the plural rule context. // This function can easily encounter integer overflow, // and can easily return noise digits when the precision of a double is exceeded.
int64_t FixedDecimal::getFractionalDigits(double n, int32_t v) { if (v == 0 || n == floor(n) || uprv_isNaN(n) || uprv_isPositiveInfinity(n)) { return 0;
}
n = fabs(n); double fract = n - floor(n); switch (v) { case 1: returnstatic_cast<int64_t>(fract * 10.0 + 0.5); case 2: returnstatic_cast<int64_t>(fract * 100.0 + 0.5); case 3: returnstatic_cast<int64_t>(fract * 1000.0 + 0.5); default: double scaled = floor(fract * pow(10.0, static_cast<double>(v)) + 0.5); if (scaled >= static_cast<double>(U_INT64_MAX)) { // Note: a double cannot accurately represent U_INT64_MAX. Casting it to double // will round up to the next representable value, which is U_INT64_MAX + 1. return U_INT64_MAX;
} else { returnstatic_cast<int64_t>(scaled);
}
}
}
void FixedDecimal::adjustForMinFractionDigits(int32_t minFractionDigits) {
int32_t numTrailingFractionZeros = minFractionDigits - visibleDecimalDigitCount; if (numTrailingFractionZeros > 0) { for (int32_t i=0; i<numTrailingFractionZeros; i++) { // Do not let the decimalDigits value overflow if there are many trailing zeros. // Limit the value to 18 digits, the most that a 64 bit int can fully represent. if (decimalDigits >= 100000000000000000LL) { break;
}
decimalDigits *= 10;
}
visibleDecimalDigitCount += numTrailingFractionZeros;
}
}
double FixedDecimal::getPluralOperand(PluralOperand operand) const { switch(operand) { case PLURAL_OPERAND_N: return (exponent == 0 ? source : source * pow(10.0, exponent)); case PLURAL_OPERAND_I: returnstatic_cast<double>(longValue()); case PLURAL_OPERAND_F: returnstatic_cast<double>(decimalDigits); case PLURAL_OPERAND_T: returnstatic_cast<double>(decimalDigitsWithoutTrailingZeros); case PLURAL_OPERAND_V: return visibleDecimalDigitCount; case PLURAL_OPERAND_E: return exponent; case PLURAL_OPERAND_C: return exponent; default:
UPRV_UNREACHABLE_EXIT; // unexpected.
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.