number_rounding.cpp (20281B)
1 // © 2017 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html 3 4 #include "unicode/utypes.h" 5 6 #if !UCONFIG_NO_FORMATTING 7 8 #include "charstr.h" 9 #include "uassert.h" 10 #include "unicode/numberformatter.h" 11 #include "number_types.h" 12 #include "number_decimalquantity.h" 13 #ifdef JS_HAS_INTL_API 14 #include "double-conversion/double-conversion.h" 15 #else 16 #include "double-conversion.h" 17 #endif 18 #include "number_roundingutils.h" 19 #include "number_skeletons.h" 20 #include "number_decnum.h" 21 #include "putilimp.h" 22 #include "string_segment.h" 23 24 using namespace icu; 25 using namespace icu::number; 26 using namespace icu::number::impl; 27 28 29 using double_conversion::DoubleToStringConverter; 30 using icu::StringSegment; 31 32 void number::impl::parseIncrementOption(const StringSegment &segment, 33 Precision &outPrecision, 34 UErrorCode &status) { 35 // Need to do char <-> char16_t conversion... 36 U_ASSERT(U_SUCCESS(status)); 37 CharString buffer; 38 SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status); 39 40 // Utilize DecimalQuantity/decNumber to parse this for us. 41 DecimalQuantity dq; 42 UErrorCode localStatus = U_ZERO_ERROR; 43 dq.setToDecNumber({buffer.data(), buffer.length()}, localStatus); 44 if (U_FAILURE(localStatus) || dq.isNaN() || dq.isInfinite()) { 45 // throw new SkeletonSyntaxException("Invalid rounding increment", segment, e); 46 status = U_NUMBER_SKELETON_SYNTAX_ERROR; 47 return; 48 } 49 // Now we break apart the number into a mantissa and exponent (magnitude). 50 int32_t magnitude = dq.adjustToZeroScale(); 51 // setToDecNumber drops trailing zeros, so we search for the '.' manually. 52 for (int32_t i=0; i<buffer.length(); i++) { 53 if (buffer[i] == '.') { 54 int32_t newMagnitude = i - buffer.length() + 1; 55 dq.adjustMagnitude(magnitude - newMagnitude); 56 magnitude = newMagnitude; 57 break; 58 } 59 } 60 outPrecision = Precision::incrementExact(dq.toLong(), magnitude); 61 } 62 63 namespace { 64 65 int32_t getRoundingMagnitudeFraction(int maxFrac) { 66 if (maxFrac == -1) { 67 return INT32_MIN; 68 } 69 return -maxFrac; 70 } 71 72 int32_t getRoundingMagnitudeSignificant(const DecimalQuantity &value, int maxSig) { 73 if (maxSig == -1) { 74 return INT32_MIN; 75 } 76 int magnitude = value.isZeroish() ? 0 : value.getMagnitude(); 77 return magnitude - maxSig + 1; 78 } 79 80 int32_t getDisplayMagnitudeFraction(int minFrac) { 81 if (minFrac == 0) { 82 return INT32_MAX; 83 } 84 return -minFrac; 85 } 86 87 int32_t getDisplayMagnitudeSignificant(const DecimalQuantity &value, int minSig) { 88 int magnitude = value.isZeroish() ? 0 : value.getMagnitude(); 89 return magnitude - minSig + 1; 90 } 91 92 } 93 94 95 MultiplierProducer::~MultiplierProducer() = default; 96 97 98 Precision Precision::unlimited() { 99 return Precision(RND_NONE, {}); 100 } 101 102 FractionPrecision Precision::integer() { 103 return constructFraction(0, 0); 104 } 105 106 FractionPrecision Precision::fixedFraction(int32_t minMaxFractionPlaces) { 107 if (minMaxFractionPlaces >= 0 && minMaxFractionPlaces <= kMaxIntFracSig) { 108 return constructFraction(minMaxFractionPlaces, minMaxFractionPlaces); 109 } else { 110 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR}; 111 } 112 } 113 114 FractionPrecision Precision::minFraction(int32_t minFractionPlaces) { 115 if (minFractionPlaces >= 0 && minFractionPlaces <= kMaxIntFracSig) { 116 return constructFraction(minFractionPlaces, -1); 117 } else { 118 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR}; 119 } 120 } 121 122 FractionPrecision Precision::maxFraction(int32_t maxFractionPlaces) { 123 if (maxFractionPlaces >= 0 && maxFractionPlaces <= kMaxIntFracSig) { 124 return constructFraction(0, maxFractionPlaces); 125 } else { 126 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR}; 127 } 128 } 129 130 FractionPrecision Precision::minMaxFraction(int32_t minFractionPlaces, int32_t maxFractionPlaces) { 131 if (minFractionPlaces >= 0 && maxFractionPlaces <= kMaxIntFracSig && 132 minFractionPlaces <= maxFractionPlaces) { 133 return constructFraction(minFractionPlaces, maxFractionPlaces); 134 } else { 135 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR}; 136 } 137 } 138 139 Precision Precision::fixedSignificantDigits(int32_t minMaxSignificantDigits) { 140 if (minMaxSignificantDigits >= 1 && minMaxSignificantDigits <= kMaxIntFracSig) { 141 return constructSignificant(minMaxSignificantDigits, minMaxSignificantDigits); 142 } else { 143 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR}; 144 } 145 } 146 147 Precision Precision::minSignificantDigits(int32_t minSignificantDigits) { 148 if (minSignificantDigits >= 1 && minSignificantDigits <= kMaxIntFracSig) { 149 return constructSignificant(minSignificantDigits, -1); 150 } else { 151 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR}; 152 } 153 } 154 155 Precision Precision::maxSignificantDigits(int32_t maxSignificantDigits) { 156 if (maxSignificantDigits >= 1 && maxSignificantDigits <= kMaxIntFracSig) { 157 return constructSignificant(1, maxSignificantDigits); 158 } else { 159 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR}; 160 } 161 } 162 163 Precision Precision::minMaxSignificantDigits(int32_t minSignificantDigits, int32_t maxSignificantDigits) { 164 if (minSignificantDigits >= 1 && maxSignificantDigits <= kMaxIntFracSig && 165 minSignificantDigits <= maxSignificantDigits) { 166 return constructSignificant(minSignificantDigits, maxSignificantDigits); 167 } else { 168 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR}; 169 } 170 } 171 172 Precision Precision::trailingZeroDisplay(UNumberTrailingZeroDisplay trailingZeroDisplay) const { 173 Precision result(*this); // copy constructor 174 result.fTrailingZeroDisplay = trailingZeroDisplay; 175 return result; 176 } 177 178 IncrementPrecision Precision::increment(double roundingIncrement) { 179 if (roundingIncrement > 0.0) { 180 DecimalQuantity dq; 181 dq.setToDouble(roundingIncrement); 182 dq.roundToInfinity(); 183 int32_t magnitude = dq.adjustToZeroScale(); 184 return constructIncrement(dq.toLong(), magnitude); 185 } else { 186 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR}; 187 } 188 } 189 190 IncrementPrecision Precision::incrementExact(uint64_t mantissa, int16_t magnitude) { 191 if (mantissa > 0.0) { 192 return constructIncrement(mantissa, magnitude); 193 } else { 194 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR}; 195 } 196 } 197 198 CurrencyPrecision Precision::currency(UCurrencyUsage currencyUsage) { 199 return constructCurrency(currencyUsage); 200 } 201 202 Precision FractionPrecision::withSignificantDigits( 203 int32_t minSignificantDigits, 204 int32_t maxSignificantDigits, 205 UNumberRoundingPriority priority) const { 206 if (fType == RND_ERROR) { return *this; } // no-op in error state 207 if (minSignificantDigits >= 1 && 208 maxSignificantDigits >= minSignificantDigits && 209 maxSignificantDigits <= kMaxIntFracSig) { 210 return constructFractionSignificant( 211 *this, 212 minSignificantDigits, 213 maxSignificantDigits, 214 priority, 215 false); 216 } else { 217 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR}; 218 } 219 } 220 221 Precision FractionPrecision::withMinDigits(int32_t minSignificantDigits) const { 222 if (fType == RND_ERROR) { return *this; } // no-op in error state 223 if (minSignificantDigits >= 1 && minSignificantDigits <= kMaxIntFracSig) { 224 return constructFractionSignificant( 225 *this, 226 1, 227 minSignificantDigits, 228 UNUM_ROUNDING_PRIORITY_RELAXED, 229 true); 230 } else { 231 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR}; 232 } 233 } 234 235 Precision FractionPrecision::withMaxDigits(int32_t maxSignificantDigits) const { 236 if (fType == RND_ERROR) { return *this; } // no-op in error state 237 if (maxSignificantDigits >= 1 && maxSignificantDigits <= kMaxIntFracSig) { 238 return constructFractionSignificant(*this, 239 1, 240 maxSignificantDigits, 241 UNUM_ROUNDING_PRIORITY_STRICT, 242 true); 243 } else { 244 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR}; 245 } 246 } 247 248 // Private method on base class 249 Precision Precision::withCurrency(const CurrencyUnit ¤cy, UErrorCode &status) const { 250 if (fType == RND_ERROR) { return *this; } // no-op in error state 251 U_ASSERT(fType == RND_CURRENCY); 252 const char16_t *isoCode = currency.getISOCurrency(); 253 double increment = ucurr_getRoundingIncrementForUsage(isoCode, fUnion.currencyUsage, &status); 254 int32_t minMaxFrac = ucurr_getDefaultFractionDigitsForUsage( 255 isoCode, fUnion.currencyUsage, &status); 256 Precision retval = (increment != 0.0) 257 ? Precision::increment(increment) 258 : static_cast<Precision>(Precision::fixedFraction(minMaxFrac)); 259 retval.fTrailingZeroDisplay = fTrailingZeroDisplay; 260 return retval; 261 } 262 263 // Public method on CurrencyPrecision subclass 264 Precision CurrencyPrecision::withCurrency(const CurrencyUnit ¤cy) const { 265 UErrorCode localStatus = U_ZERO_ERROR; 266 Precision result = Precision::withCurrency(currency, localStatus); 267 if (U_FAILURE(localStatus)) { 268 return {localStatus}; 269 } 270 return result; 271 } 272 273 Precision IncrementPrecision::withMinFraction(int32_t minFrac) const { 274 if (fType == RND_ERROR) { return *this; } // no-op in error state 275 if (minFrac >= 0 && minFrac <= kMaxIntFracSig) { 276 IncrementPrecision copy = *this; 277 copy.fUnion.increment.fMinFrac = minFrac; 278 return copy; 279 } else { 280 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR}; 281 } 282 } 283 284 FractionPrecision Precision::constructFraction(int32_t minFrac, int32_t maxFrac) { 285 FractionSignificantSettings settings{}; 286 settings.fMinFrac = static_cast<digits_t>(minFrac); 287 settings.fMaxFrac = static_cast<digits_t>(maxFrac); 288 settings.fMinSig = -1; 289 settings.fMaxSig = -1; 290 PrecisionUnion union_{}; 291 union_.fracSig = settings; 292 return {RND_FRACTION, union_}; 293 } 294 295 Precision Precision::constructSignificant(int32_t minSig, int32_t maxSig) { 296 FractionSignificantSettings settings{}; 297 settings.fMinFrac = -1; 298 settings.fMaxFrac = -1; 299 settings.fMinSig = static_cast<digits_t>(minSig); 300 settings.fMaxSig = static_cast<digits_t>(maxSig); 301 PrecisionUnion union_{}; 302 union_.fracSig = settings; 303 return {RND_SIGNIFICANT, union_}; 304 } 305 306 Precision 307 Precision::constructFractionSignificant( 308 const FractionPrecision &base, 309 int32_t minSig, 310 int32_t maxSig, 311 UNumberRoundingPriority priority, 312 bool retain) { 313 FractionSignificantSettings settings = base.fUnion.fracSig; 314 settings.fMinSig = static_cast<digits_t>(minSig); 315 settings.fMaxSig = static_cast<digits_t>(maxSig); 316 settings.fPriority = priority; 317 settings.fRetain = retain; 318 PrecisionUnion union_{}; 319 union_.fracSig = settings; 320 return {RND_FRACTION_SIGNIFICANT, union_}; 321 } 322 323 IncrementPrecision Precision::constructIncrement(uint64_t increment, digits_t magnitude) { 324 IncrementSettings settings{}; 325 // Note: For number formatting, fIncrement is used for RND_INCREMENT but not 326 // RND_INCREMENT_ONE or RND_INCREMENT_FIVE. However, fIncrement is used in all 327 // three when constructing a skeleton. 328 settings.fIncrement = increment; 329 settings.fIncrementMagnitude = magnitude; 330 settings.fMinFrac = magnitude > 0 ? 0 : -magnitude; 331 PrecisionUnion union_{}; 332 union_.increment = settings; 333 if (increment == 1) { 334 // NOTE: In C++, we must return the correct value type with the correct union. 335 // It would be invalid to return a RND_FRACTION here because the methods on the 336 // IncrementPrecision type assume that the union is backed by increment data. 337 return {RND_INCREMENT_ONE, union_}; 338 } else if (increment == 5) { 339 return {RND_INCREMENT_FIVE, union_}; 340 } else { 341 return {RND_INCREMENT, union_}; 342 } 343 } 344 345 CurrencyPrecision Precision::constructCurrency(UCurrencyUsage usage) { 346 PrecisionUnion union_{}; 347 union_.currencyUsage = usage; 348 return {RND_CURRENCY, union_}; 349 } 350 351 352 RoundingImpl::RoundingImpl(const Precision& precision, UNumberFormatRoundingMode roundingMode, 353 const CurrencyUnit& currency, UErrorCode& status) 354 : fPrecision(precision), fRoundingMode(roundingMode), fPassThrough(false) { 355 if (precision.fType == Precision::RND_CURRENCY) { 356 fPrecision = precision.withCurrency(currency, status); 357 } 358 } 359 360 RoundingImpl RoundingImpl::passThrough() { 361 return {}; 362 } 363 364 bool RoundingImpl::isSignificantDigits() const { 365 return fPrecision.fType == Precision::RND_SIGNIFICANT; 366 } 367 368 int32_t 369 RoundingImpl::chooseMultiplierAndApply(impl::DecimalQuantity &input, const impl::MultiplierProducer &producer, 370 UErrorCode &status) { 371 // Do not call this method with zero, NaN, or infinity. 372 U_ASSERT(!input.isZeroish()); 373 374 // Perform the first attempt at rounding. 375 int magnitude = input.getMagnitude(); 376 int multiplier = producer.getMultiplier(magnitude); 377 input.adjustMagnitude(multiplier); 378 apply(input, status); 379 380 // If the number rounded to zero, exit. 381 if (input.isZeroish() || U_FAILURE(status)) { 382 return multiplier; 383 } 384 385 // If the new magnitude after rounding is the same as it was before rounding, then we are done. 386 // This case applies to most numbers. 387 if (input.getMagnitude() == magnitude + multiplier) { 388 return multiplier; 389 } 390 391 // If the above case DIDN'T apply, then we have a case like 99.9 -> 100 or 999.9 -> 1000: 392 // The number rounded up to the next magnitude. Check if the multiplier changes; if it doesn't, 393 // we do not need to make any more adjustments. 394 int _multiplier = producer.getMultiplier(magnitude + 1); 395 if (multiplier == _multiplier) { 396 return multiplier; 397 } 398 399 // We have a case like 999.9 -> 1000, where the correct output is "1K", not "1000". 400 // Fix the magnitude and re-apply the rounding strategy. 401 input.adjustMagnitude(_multiplier - multiplier); 402 apply(input, status); 403 return _multiplier; 404 } 405 406 /** This is the method that contains the actual rounding logic. */ 407 void RoundingImpl::apply(impl::DecimalQuantity &value, UErrorCode& status) const { 408 if (U_FAILURE(status)) { 409 return; 410 } 411 if (fPassThrough) { 412 return; 413 } 414 int32_t resolvedMinFraction = 0; 415 switch (fPrecision.fType) { 416 case Precision::RND_BOGUS: 417 case Precision::RND_ERROR: 418 // Errors should be caught before the apply() method is called 419 status = U_INTERNAL_PROGRAM_ERROR; 420 break; 421 422 case Precision::RND_NONE: 423 value.roundToInfinity(); 424 break; 425 426 case Precision::RND_FRACTION: 427 value.roundToMagnitude( 428 getRoundingMagnitudeFraction(fPrecision.fUnion.fracSig.fMaxFrac), 429 fRoundingMode, 430 status); 431 resolvedMinFraction = 432 uprv_max(0, -getDisplayMagnitudeFraction(fPrecision.fUnion.fracSig.fMinFrac)); 433 break; 434 435 case Precision::RND_SIGNIFICANT: 436 value.roundToMagnitude( 437 getRoundingMagnitudeSignificant(value, fPrecision.fUnion.fracSig.fMaxSig), 438 fRoundingMode, 439 status); 440 resolvedMinFraction = 441 uprv_max(0, -getDisplayMagnitudeSignificant(value, fPrecision.fUnion.fracSig.fMinSig)); 442 // Make sure that digits are displayed on zero. 443 if (value.isZeroish() && fPrecision.fUnion.fracSig.fMinSig > 0) { 444 value.increaseMinIntegerTo(1); 445 } 446 break; 447 448 case Precision::RND_FRACTION_SIGNIFICANT: { 449 // From ECMA-402: 450 /* 451 Let sResult be ToRawPrecision(...). 452 Let fResult be ToRawFixed(...). 453 If intlObj.[[RoundingType]] is morePrecision, then 454 If sResult.[[RoundingMagnitude]] ≤ fResult.[[RoundingMagnitude]], then 455 Let result be sResult. 456 Else, 457 Let result be fResult. 458 Else, 459 Assert: intlObj.[[RoundingType]] is lessPrecision. 460 If sResult.[[RoundingMagnitude]] ≤ fResult.[[RoundingMagnitude]], then 461 Let result be fResult. 462 Else, 463 Let result be sResult. 464 */ 465 466 int32_t roundingMag1 = getRoundingMagnitudeFraction(fPrecision.fUnion.fracSig.fMaxFrac); 467 int32_t roundingMag2 = getRoundingMagnitudeSignificant(value, fPrecision.fUnion.fracSig.fMaxSig); 468 int32_t roundingMag; 469 if (fPrecision.fUnion.fracSig.fPriority == UNUM_ROUNDING_PRIORITY_RELAXED) { 470 roundingMag = uprv_min(roundingMag1, roundingMag2); 471 } else { 472 roundingMag = uprv_max(roundingMag1, roundingMag2); 473 } 474 if (!value.isZeroish()) { 475 int32_t upperMag = value.getMagnitude(); 476 value.roundToMagnitude(roundingMag, fRoundingMode, status); 477 if (!value.isZeroish() && value.getMagnitude() != upperMag && roundingMag1 == roundingMag2) { 478 // roundingMag2 needs to be the magnitude after rounding 479 roundingMag2 += 1; 480 } 481 } 482 483 int32_t displayMag1 = getDisplayMagnitudeFraction(fPrecision.fUnion.fracSig.fMinFrac); 484 int32_t displayMag2 = getDisplayMagnitudeSignificant(value, fPrecision.fUnion.fracSig.fMinSig); 485 int32_t displayMag; 486 if (fPrecision.fUnion.fracSig.fRetain) { 487 // withMinDigits + withMaxDigits 488 displayMag = uprv_min(displayMag1, displayMag2); 489 } else if (fPrecision.fUnion.fracSig.fPriority == UNUM_ROUNDING_PRIORITY_RELAXED) { 490 if (roundingMag2 <= roundingMag1) { 491 displayMag = displayMag2; 492 } else { 493 displayMag = displayMag1; 494 } 495 } else { 496 U_ASSERT(fPrecision.fUnion.fracSig.fPriority == UNUM_ROUNDING_PRIORITY_STRICT); 497 if (roundingMag2 <= roundingMag1) { 498 displayMag = displayMag1; 499 } else { 500 displayMag = displayMag2; 501 } 502 } 503 resolvedMinFraction = uprv_max(0, -displayMag); 504 505 break; 506 } 507 508 case Precision::RND_INCREMENT: 509 value.roundToIncrement( 510 fPrecision.fUnion.increment.fIncrement, 511 fPrecision.fUnion.increment.fIncrementMagnitude, 512 fRoundingMode, 513 status); 514 resolvedMinFraction = fPrecision.fUnion.increment.fMinFrac; 515 break; 516 517 case Precision::RND_INCREMENT_ONE: 518 value.roundToMagnitude( 519 fPrecision.fUnion.increment.fIncrementMagnitude, 520 fRoundingMode, 521 status); 522 resolvedMinFraction = fPrecision.fUnion.increment.fMinFrac; 523 break; 524 525 case Precision::RND_INCREMENT_FIVE: 526 value.roundToNickel( 527 fPrecision.fUnion.increment.fIncrementMagnitude, 528 fRoundingMode, 529 status); 530 resolvedMinFraction = fPrecision.fUnion.increment.fMinFrac; 531 break; 532 533 case Precision::RND_CURRENCY: 534 // Call .withCurrency() before .apply()! 535 UPRV_UNREACHABLE_EXIT; 536 537 default: 538 UPRV_UNREACHABLE_EXIT; 539 } 540 541 if (fPrecision.fTrailingZeroDisplay == UNUM_TRAILING_ZERO_AUTO || 542 // PLURAL_OPERAND_T returns fraction digits as an integer 543 value.getPluralOperand(PLURAL_OPERAND_T) != 0) { 544 value.setMinFraction(resolvedMinFraction); 545 } 546 } 547 548 void RoundingImpl::apply(impl::DecimalQuantity &value, int32_t minInt, UErrorCode /*status*/) { 549 // This method is intended for the one specific purpose of helping print "00.000E0". 550 // Question: Is it useful to look at trailingZeroDisplay here? 551 U_ASSERT(isSignificantDigits()); 552 U_ASSERT(value.isZeroish()); 553 value.setMinFraction(fPrecision.fUnion.fracSig.fMinSig - minInt); 554 } 555 556 #endif /* #if !UCONFIG_NO_FORMATTING */