diff --git a/icu4c/source/i18n/number_compact.cpp b/icu4c/source/i18n/number_compact.cpp index c19d495fd11..7cadba688a1 100644 --- a/icu4c/source/i18n/number_compact.cpp +++ b/icu4c/source/i18n/number_compact.cpp @@ -275,10 +275,10 @@ void CompactHandler::processQuantity(DecimalQuantity &quantity, MicroProps &micr int magnitude; if (quantity.isZero()) { magnitude = 0; - micros.rounding.apply(quantity, status); + micros.rounder.apply(quantity, status); } else { // TODO: Revisit chooseMultiplierAndApply - int multiplier = micros.rounding.chooseMultiplierAndApply(quantity, data, status); + int multiplier = micros.rounder.chooseMultiplierAndApply(quantity, data, status); magnitude = quantity.isZero() ? 0 : quantity.getMagnitude(); magnitude -= multiplier; } @@ -313,7 +313,7 @@ void CompactHandler::processQuantity(DecimalQuantity &quantity, MicroProps &micr } // We already performed rounding. Do not perform it again. - micros.rounding = Rounder::constructPassThrough(); + micros.rounder = RoundingImpl::passThrough(); } #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/number_decimalquantity.cpp b/icu4c/source/i18n/number_decimalquantity.cpp index 18f11c4a328..738b075a064 100644 --- a/icu4c/source/i18n/number_decimalquantity.cpp +++ b/icu4c/source/i18n/number_decimalquantity.cpp @@ -132,7 +132,7 @@ void DecimalQuantity::clear() { } void DecimalQuantity::setIntegerLength(int32_t minInt, int32_t maxInt) { - // Validation should happen outside of DecimalQuantity, e.g., in the Rounder class. + // Validation should happen outside of DecimalQuantity, e.g., in the Precision class. U_ASSERT(minInt >= 0); U_ASSERT(maxInt >= minInt); @@ -149,7 +149,7 @@ void DecimalQuantity::setIntegerLength(int32_t minInt, int32_t maxInt) { } void DecimalQuantity::setFractionLength(int32_t minFrac, int32_t maxFrac) { - // Validation should happen outside of DecimalQuantity, e.g., in the Rounder class. + // Validation should happen outside of DecimalQuantity, e.g., in the Precision class. U_ASSERT(minFrac >= 0); U_ASSERT(maxFrac >= minFrac); diff --git a/icu4c/source/i18n/number_fluent.cpp b/icu4c/source/i18n/number_fluent.cpp index 459cb04bb8d..ea4c10156a4 100644 --- a/icu4c/source/i18n/number_fluent.cpp +++ b/icu4c/source/i18n/number_fluent.cpp @@ -117,18 +117,32 @@ Derived NumberFormatterSettings<Derived>::adoptPerUnit(icu::MeasureUnit* perUnit } template<typename Derived> -Derived NumberFormatterSettings<Derived>::rounding(const Rounder& rounder) const& { +Derived NumberFormatterSettings<Derived>::precision(const Precision& precision) const& { Derived copy(*this); // NOTE: Slicing is OK. - copy.fMacros.rounder = rounder; + copy.fMacros.precision = precision; return copy; } template<typename Derived> -Derived NumberFormatterSettings<Derived>::rounding(const Rounder& rounder)&& { +Derived NumberFormatterSettings<Derived>::precision(const Precision& precision)&& { Derived move(std::move(*this)); // NOTE: Slicing is OK. - move.fMacros.rounder = rounder; + move.fMacros.precision = precision; + return move; +} + +template<typename Derived> +Derived NumberFormatterSettings<Derived>::roundingMode(UNumberFormatRoundingMode roundingMode) const& { + Derived copy(*this); + copy.fMacros.roundingMode = roundingMode; + return copy; +} + +template<typename Derived> +Derived NumberFormatterSettings<Derived>::roundingMode(UNumberFormatRoundingMode roundingMode)&& { + Derived move(std::move(*this)); + move.fMacros.roundingMode = roundingMode; return move; } diff --git a/icu4c/source/i18n/number_formatimpl.cpp b/icu4c/source/i18n/number_formatimpl.cpp index a6bda29f3e3..dc493e1dddb 100644 --- a/icu4c/source/i18n/number_formatimpl.cpp +++ b/icu4c/source/i18n/number_formatimpl.cpp @@ -246,16 +246,24 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe, } // Rounding strategy - if (!macros.rounder.isBogus()) { - fMicros.rounding = macros.rounder; + Precision precision; + if (!macros.precision.isBogus()) { + precision = macros.precision; } else if (macros.notation.fType == Notation::NTN_COMPACT) { - fMicros.rounding = Rounder::integer().withMinDigits(2); + precision = Precision::integer().withMinDigits(2); } else if (isCurrency) { - fMicros.rounding = Rounder::currency(UCURR_USAGE_STANDARD); + precision = Precision::currency(UCURR_USAGE_STANDARD); } else { - fMicros.rounding = Rounder::maxFraction(6); + precision = Precision::maxFraction(6); } - fMicros.rounding.setLocaleData(currency, status); + UNumberFormatRoundingMode roundingMode; + if (macros.roundingMode != kDefaultMode) { + roundingMode = macros.roundingMode; + } else { + // Temporary until ICU 64 + roundingMode = precision.fRoundingMode; + } + fMicros.rounder = {precision, roundingMode, currency, status}; // Grouping strategy if (!macros.grouper.isBogus()) { @@ -397,7 +405,7 @@ NumberFormatterImpl::resolvePluralRules(const PluralRules* rulesPtr, const Local int32_t NumberFormatterImpl::microsToString(const MicroProps& micros, DecimalQuantity& quantity, NumberStringBuilder& string, UErrorCode& status) { - micros.rounding.apply(quantity, status); + micros.rounder.apply(quantity, status); micros.integerWidth.apply(quantity, status); int32_t length = writeNumber(micros, quantity, string, status); // NOTE: When range formatting is added, these modifiers can bubble up. diff --git a/icu4c/source/i18n/number_longnames.cpp b/icu4c/source/i18n/number_longnames.cpp index e84766cbf29..cc0dd78d6d2 100644 --- a/icu4c/source/i18n/number_longnames.cpp +++ b/icu4c/source/i18n/number_longnames.cpp @@ -260,7 +260,7 @@ void LongNameHandler::processQuantity(DecimalQuantity &quantity, MicroProps &mic parent->processQuantity(quantity, micros, status); // TODO: Avoid the copy here? DecimalQuantity copy(quantity); - micros.rounding.apply(copy, status); + micros.rounder.apply(copy, status); micros.modOuter = &fModifiers[copy.getStandardPlural(rules)]; } diff --git a/icu4c/source/i18n/number_mapper.cpp b/icu4c/source/i18n/number_mapper.cpp index 51dd0a9c8c8..9df0e2d73ac 100644 --- a/icu4c/source/i18n/number_mapper.cpp +++ b/icu4c/source/i18n/number_mapper.cpp @@ -106,7 +106,7 @@ MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& propert // Resolve min/max frac for currencies, required for the validation logic and for when minFrac or // maxFrac was // set (but not both) on a currency instance. - // NOTE: Increments are handled in "Rounder.constructCurrency()". + // NOTE: Increments are handled in "Precision.constructCurrency()". if (useCurrency && (minFrac == -1 || maxFrac == -1)) { int32_t digits = ucurr_getDefaultFractionDigitsForUsage( currency.getISOCurrency(), currencyUsage, &status); @@ -135,24 +135,24 @@ MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& propert minInt = minInt <= 0 ? 1 : minInt > kMaxIntFracSig ? 1 : minInt; maxInt = maxInt < 0 ? -1 : maxInt < minInt ? minInt : maxInt > kMaxIntFracSig ? -1 : maxInt; } - Rounder rounding; + Precision precision; if (!properties.currencyUsage.isNull()) { - rounding = Rounder::constructCurrency(currencyUsage).withCurrency(currency); + precision = Precision::constructCurrency(currencyUsage).withCurrency(currency); } else if (roundingIncrement != 0.0) { - rounding = Rounder::constructIncrement(roundingIncrement, minFrac); + precision = Precision::constructIncrement(roundingIncrement, minFrac); } else if (explicitMinMaxSig) { minSig = minSig < 1 ? 1 : minSig > kMaxIntFracSig ? kMaxIntFracSig : minSig; maxSig = maxSig < 0 ? kMaxIntFracSig : maxSig < minSig ? minSig : maxSig > kMaxIntFracSig ? kMaxIntFracSig : maxSig; - rounding = Rounder::constructSignificant(minSig, maxSig); + precision = Precision::constructSignificant(minSig, maxSig); } else if (explicitMinMaxFrac) { - rounding = Rounder::constructFraction(minFrac, maxFrac); + precision = Precision::constructFraction(minFrac, maxFrac); } else if (useCurrency) { - rounding = Rounder::constructCurrency(currencyUsage); + precision = Precision::constructCurrency(currencyUsage); } - if (!rounding.isBogus()) { - rounding = rounding.withMode(roundingMode); - macros.rounder = rounding; + if (!precision.isBogus()) { + precision = precision.withMode(roundingMode); + macros.precision = precision; } /////////////////// @@ -221,7 +221,7 @@ MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& propert properties.exponentSignAlwaysShown ? UNUM_SIGN_ALWAYS : UNUM_SIGN_AUTO); // Scientific notation also involves overriding the rounding mode. // TODO: Overriding here is a bit of a hack. Should this logic go earlier? - if (macros.rounder.fType == Rounder::RounderType::RND_FRACTION) { + if (macros.precision.fType == Precision::PrecisionType::RND_FRACTION) { // For the purposes of rounding, get the original min/max int/frac, since the local // variables // have been manipulated for display purposes. @@ -230,13 +230,13 @@ MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& propert int maxFrac_ = properties.maximumFractionDigits; if (minInt_ == 0 && maxFrac_ == 0) { // Patterns like "#E0" and "##E0", which mean no rounding! - macros.rounder = Rounder::unlimited().withMode(roundingMode); + macros.precision = Precision::unlimited().withMode(roundingMode); } else if (minInt_ == 0 && minFrac_ == 0) { // Patterns like "#.##E0" (no zeros in the mantissa), which mean round to maxFrac+1 - macros.rounder = Rounder::constructSignificant(1, maxFrac_ + 1).withMode(roundingMode); + macros.precision = Precision::constructSignificant(1, maxFrac_ + 1).withMode(roundingMode); } else { // All other scientific patterns, which mean round to minInt+maxFrac - macros.rounder = Rounder::constructSignificant( + macros.precision = Precision::constructSignificant( minInt_ + minFrac_, minInt_ + maxFrac_).withMode(roundingMode); } } @@ -273,25 +273,25 @@ MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& propert exportedProperties->minimumIntegerDigits = minInt; exportedProperties->maximumIntegerDigits = maxInt == -1 ? INT32_MAX : maxInt; - Rounder rounding_; - if (rounding.fType == Rounder::RounderType::RND_CURRENCY) { - rounding_ = rounding.withCurrency(currency, status); + Precision rounding_; + if (precision.fType == Precision::PrecisionType::RND_CURRENCY) { + rounding_ = precision.withCurrency(currency, status); } else { - rounding_ = rounding; + rounding_ = precision; } int minFrac_ = minFrac; int maxFrac_ = maxFrac; int minSig_ = minSig; int maxSig_ = maxSig; double increment_ = 0.0; - if (rounding_.fType == Rounder::RounderType::RND_FRACTION) { + if (rounding_.fType == Precision::PrecisionType::RND_FRACTION) { minFrac_ = rounding_.fUnion.fracSig.fMinFrac; maxFrac_ = rounding_.fUnion.fracSig.fMaxFrac; - } else if (rounding_.fType == Rounder::RounderType::RND_INCREMENT) { + } else if (rounding_.fType == Precision::PrecisionType::RND_INCREMENT) { increment_ = rounding_.fUnion.increment.fIncrement; minFrac_ = rounding_.fUnion.increment.fMinFrac; maxFrac_ = rounding_.fUnion.increment.fMinFrac; - } else if (rounding_.fType == Rounder::RounderType::RND_SIGNIFICANT) { + } else if (rounding_.fType == Precision::PrecisionType::RND_SIGNIFICANT) { minSig_ = rounding_.fUnion.fracSig.fMinSig; maxSig_ = rounding_.fUnion.fracSig.fMaxSig; } diff --git a/icu4c/source/i18n/number_patternmodifier.cpp b/icu4c/source/i18n/number_patternmodifier.cpp index 45f790e571b..b45647d143d 100644 --- a/icu4c/source/i18n/number_patternmodifier.cpp +++ b/icu4c/source/i18n/number_patternmodifier.cpp @@ -165,7 +165,7 @@ void MutablePatternModifier::processQuantity(DecimalQuantity& fq, MicroProps& mi if (needsPlurals()) { // TODO: Fix this. Avoid the copy. DecimalQuantity copy(fq); - micros.rounding.apply(copy, status); + micros.rounder.apply(copy, status); nonConstThis->setNumberProperties(fq.signum(), copy.getStandardPlural(rules)); } else { nonConstThis->setNumberProperties(fq.signum(), StandardPlural::Form::COUNT); diff --git a/icu4c/source/i18n/number_rounding.cpp b/icu4c/source/i18n/number_rounding.cpp index 137848ec469..ae4b8849fbe 100644 --- a/icu4c/source/i18n/number_rounding.cpp +++ b/icu4c/source/i18n/number_rounding.cpp @@ -75,15 +75,15 @@ digits_t roundingutils::doubleFractionLength(double input) { } -Rounder Rounder::unlimited() { - return Rounder(RND_NONE, {}, kDefaultMode); +Precision Precision::unlimited() { + return Precision(RND_NONE, {}, kDefaultMode); } -FractionRounder Rounder::integer() { +FractionPrecision Precision::integer() { return constructFraction(0, 0); } -FractionRounder Rounder::fixedFraction(int32_t minMaxFractionPlaces) { +FractionPrecision Precision::fixedFraction(int32_t minMaxFractionPlaces) { if (minMaxFractionPlaces >= 0 && minMaxFractionPlaces <= kMaxIntFracSig) { return constructFraction(minMaxFractionPlaces, minMaxFractionPlaces); } else { @@ -91,7 +91,7 @@ FractionRounder Rounder::fixedFraction(int32_t minMaxFractionPlaces) { } } -FractionRounder Rounder::minFraction(int32_t minFractionPlaces) { +FractionPrecision Precision::minFraction(int32_t minFractionPlaces) { if (minFractionPlaces >= 0 && minFractionPlaces <= kMaxIntFracSig) { return constructFraction(minFractionPlaces, -1); } else { @@ -99,7 +99,7 @@ FractionRounder Rounder::minFraction(int32_t minFractionPlaces) { } } -FractionRounder Rounder::maxFraction(int32_t maxFractionPlaces) { +FractionPrecision Precision::maxFraction(int32_t maxFractionPlaces) { if (maxFractionPlaces >= 0 && maxFractionPlaces <= kMaxIntFracSig) { return constructFraction(0, maxFractionPlaces); } else { @@ -107,7 +107,7 @@ FractionRounder Rounder::maxFraction(int32_t maxFractionPlaces) { } } -FractionRounder Rounder::minMaxFraction(int32_t minFractionPlaces, int32_t maxFractionPlaces) { +FractionPrecision Precision::minMaxFraction(int32_t minFractionPlaces, int32_t maxFractionPlaces) { if (minFractionPlaces >= 0 && maxFractionPlaces <= kMaxIntFracSig && minFractionPlaces <= maxFractionPlaces) { return constructFraction(minFractionPlaces, maxFractionPlaces); @@ -116,7 +116,7 @@ FractionRounder Rounder::minMaxFraction(int32_t minFractionPlaces, int32_t maxFr } } -Rounder Rounder::fixedDigits(int32_t minMaxSignificantDigits) { +Precision Precision::fixedSignificantDigits(int32_t minMaxSignificantDigits) { if (minMaxSignificantDigits >= 1 && minMaxSignificantDigits <= kMaxIntFracSig) { return constructSignificant(minMaxSignificantDigits, minMaxSignificantDigits); } else { @@ -124,7 +124,7 @@ Rounder Rounder::fixedDigits(int32_t minMaxSignificantDigits) { } } -Rounder Rounder::minDigits(int32_t minSignificantDigits) { +Precision Precision::minSignificantDigits(int32_t minSignificantDigits) { if (minSignificantDigits >= 1 && minSignificantDigits <= kMaxIntFracSig) { return constructSignificant(minSignificantDigits, -1); } else { @@ -132,7 +132,7 @@ Rounder Rounder::minDigits(int32_t minSignificantDigits) { } } -Rounder Rounder::maxDigits(int32_t maxSignificantDigits) { +Precision Precision::maxSignificantDigits(int32_t maxSignificantDigits) { if (maxSignificantDigits >= 1 && maxSignificantDigits <= kMaxIntFracSig) { return constructSignificant(1, maxSignificantDigits); } else { @@ -140,7 +140,7 @@ Rounder Rounder::maxDigits(int32_t maxSignificantDigits) { } } -Rounder Rounder::minMaxDigits(int32_t minSignificantDigits, int32_t maxSignificantDigits) { +Precision Precision::minMaxSignificantDigits(int32_t minSignificantDigits, int32_t maxSignificantDigits) { if (minSignificantDigits >= 1 && maxSignificantDigits <= kMaxIntFracSig && minSignificantDigits <= maxSignificantDigits) { return constructSignificant(minSignificantDigits, maxSignificantDigits); @@ -149,7 +149,7 @@ Rounder Rounder::minMaxDigits(int32_t minSignificantDigits, int32_t maxSignifica } } -IncrementRounder Rounder::increment(double roundingIncrement) { +IncrementPrecision Precision::increment(double roundingIncrement) { if (roundingIncrement > 0.0) { return constructIncrement(roundingIncrement, 0); } else { @@ -157,16 +157,18 @@ IncrementRounder Rounder::increment(double roundingIncrement) { } } -CurrencyRounder Rounder::currency(UCurrencyUsage currencyUsage) { +CurrencyPrecision Precision::currency(UCurrencyUsage currencyUsage) { return constructCurrency(currencyUsage); } -Rounder Rounder::withMode(RoundingMode roundingMode) const { +Precision Precision::withMode(RoundingMode roundingMode) const { if (fType == RND_ERROR) { return *this; } // no-op in error state - return {fType, fUnion, roundingMode}; + Precision retval = *this; + retval.fRoundingMode = roundingMode; + return retval; } -Rounder FractionRounder::withMinDigits(int32_t minSignificantDigits) const { +Precision FractionPrecision::withMinDigits(int32_t minSignificantDigits) const { if (fType == RND_ERROR) { return *this; } // no-op in error state if (minSignificantDigits >= 1 && minSignificantDigits <= kMaxIntFracSig) { return constructFractionSignificant(*this, minSignificantDigits, -1); @@ -175,7 +177,7 @@ Rounder FractionRounder::withMinDigits(int32_t minSignificantDigits) const { } } -Rounder FractionRounder::withMaxDigits(int32_t maxSignificantDigits) const { +Precision FractionPrecision::withMaxDigits(int32_t maxSignificantDigits) const { if (fType == RND_ERROR) { return *this; } // no-op in error state if (maxSignificantDigits >= 1 && maxSignificantDigits <= kMaxIntFracSig) { return constructFractionSignificant(*this, -1, maxSignificantDigits); @@ -185,7 +187,7 @@ Rounder FractionRounder::withMaxDigits(int32_t maxSignificantDigits) const { } // Private method on base class -Rounder Rounder::withCurrency(const CurrencyUnit ¤cy, UErrorCode &status) const { +Precision Precision::withCurrency(const CurrencyUnit ¤cy, UErrorCode &status) const { if (fType == RND_ERROR) { return *this; } // no-op in error state U_ASSERT(fType == RND_CURRENCY); const char16_t *isoCode = currency.getISOCurrency(); @@ -199,17 +201,17 @@ Rounder Rounder::withCurrency(const CurrencyUnit ¤cy, UErrorCode &status) } } -// Public method on CurrencyRounder subclass -Rounder CurrencyRounder::withCurrency(const CurrencyUnit ¤cy) const { +// Public method on CurrencyPrecision subclass +Precision CurrencyPrecision::withCurrency(const CurrencyUnit ¤cy) const { UErrorCode localStatus = U_ZERO_ERROR; - Rounder result = Rounder::withCurrency(currency, localStatus); + Precision result = Precision::withCurrency(currency, localStatus); if (U_FAILURE(localStatus)) { return {localStatus}; } return result; } -Rounder IncrementRounder::withMinFraction(int32_t minFrac) const { +Precision IncrementPrecision::withMinFraction(int32_t minFrac) const { if (fType == RND_ERROR) { return *this; } // no-op in error state if (minFrac >= 0 && minFrac <= kMaxIntFracSig) { return constructIncrement(fUnion.increment.fIncrement, minFrac); @@ -218,70 +220,77 @@ Rounder IncrementRounder::withMinFraction(int32_t minFrac) const { } } -FractionRounder Rounder::constructFraction(int32_t minFrac, int32_t maxFrac) { +FractionPrecision Precision::constructFraction(int32_t minFrac, int32_t maxFrac) { FractionSignificantSettings settings; settings.fMinFrac = static_cast<digits_t>(minFrac); settings.fMaxFrac = static_cast<digits_t>(maxFrac); settings.fMinSig = -1; settings.fMaxSig = -1; - RounderUnion union_; + PrecisionUnion union_; union_.fracSig = settings; return {RND_FRACTION, union_, kDefaultMode}; } -Rounder Rounder::constructSignificant(int32_t minSig, int32_t maxSig) { +Precision Precision::constructSignificant(int32_t minSig, int32_t maxSig) { FractionSignificantSettings settings; settings.fMinFrac = -1; settings.fMaxFrac = -1; settings.fMinSig = static_cast<digits_t>(minSig); settings.fMaxSig = static_cast<digits_t>(maxSig); - RounderUnion union_; + PrecisionUnion union_; union_.fracSig = settings; return {RND_SIGNIFICANT, union_, kDefaultMode}; } -Rounder -Rounder::constructFractionSignificant(const FractionRounder &base, int32_t minSig, int32_t maxSig) { +Precision +Precision::constructFractionSignificant(const FractionPrecision &base, int32_t minSig, int32_t maxSig) { FractionSignificantSettings settings = base.fUnion.fracSig; settings.fMinSig = static_cast<digits_t>(minSig); settings.fMaxSig = static_cast<digits_t>(maxSig); - RounderUnion union_; + PrecisionUnion union_; union_.fracSig = settings; return {RND_FRACTION_SIGNIFICANT, union_, kDefaultMode}; } -IncrementRounder Rounder::constructIncrement(double increment, int32_t minFrac) { +IncrementPrecision Precision::constructIncrement(double increment, int32_t minFrac) { IncrementSettings settings; settings.fIncrement = increment; settings.fMinFrac = static_cast<digits_t>(minFrac); // One of the few pre-computed quantities: // Note: it is possible for minFrac to be more than maxFrac... (misleading) settings.fMaxFrac = roundingutils::doubleFractionLength(increment); - RounderUnion union_; + PrecisionUnion union_; union_.increment = settings; return {RND_INCREMENT, union_, kDefaultMode}; } -CurrencyRounder Rounder::constructCurrency(UCurrencyUsage usage) { - RounderUnion union_; +CurrencyPrecision Precision::constructCurrency(UCurrencyUsage usage) { + PrecisionUnion union_; union_.currencyUsage = usage; return {RND_CURRENCY, union_, kDefaultMode}; } -Rounder Rounder::constructPassThrough() { - RounderUnion union_; - union_.errorCode = U_ZERO_ERROR; // initialize the variable - return {RND_PASS_THROUGH, union_, kDefaultMode}; -} -void Rounder::setLocaleData(const CurrencyUnit ¤cy, UErrorCode &status) { - if (fType == RND_CURRENCY) { - *this = withCurrency(currency, status); +RoundingImpl::RoundingImpl(const Precision& precision, UNumberFormatRoundingMode roundingMode, + const CurrencyUnit& currency, UErrorCode& status) + : fPrecision(precision), fRoundingMode(roundingMode), fPassThrough(false) { + if (precision.fType == Precision::RND_CURRENCY) { + fPrecision = precision.withCurrency(currency, status); } } +RoundingImpl RoundingImpl::passThrough() { + RoundingImpl retval; + retval.fPassThrough = true; + return retval; +} + +bool RoundingImpl::isSignificantDigits() const { + return fPrecision.fType == Precision::RND_SIGNIFICANT; +} + int32_t -Rounder::chooseMultiplierAndApply(impl::DecimalQuantity &input, const impl::MultiplierProducer &producer, +RoundingImpl::chooseMultiplierAndApply(impl::DecimalQuantity &input, const impl::MultiplierProducer &producer, UErrorCode &status) { // Do not call this method with zero. U_ASSERT(!input.isZero()); @@ -319,49 +328,59 @@ Rounder::chooseMultiplierAndApply(impl::DecimalQuantity &input, const impl::Mult } /** This is the method that contains the actual rounding logic. */ -void Rounder::apply(impl::DecimalQuantity &value, UErrorCode& status) const { - switch (fType) { - case RND_BOGUS: - case RND_ERROR: +void RoundingImpl::apply(impl::DecimalQuantity &value, UErrorCode& status) const { + if (fPassThrough) { + return; + } + switch (fPrecision.fType) { + case Precision::RND_BOGUS: + case Precision::RND_ERROR: // Errors should be caught before the apply() method is called status = U_INTERNAL_PROGRAM_ERROR; break; - case RND_NONE: + case Precision::RND_NONE: value.roundToInfinity(); break; - case RND_FRACTION: + case Precision::RND_FRACTION: value.roundToMagnitude( - getRoundingMagnitudeFraction(fUnion.fracSig.fMaxFrac), fRoundingMode, status); - value.setFractionLength( - uprv_max(0, -getDisplayMagnitudeFraction(fUnion.fracSig.fMinFrac)), INT32_MAX); - break; - - case RND_SIGNIFICANT: - value.roundToMagnitude( - getRoundingMagnitudeSignificant(value, fUnion.fracSig.fMaxSig), + getRoundingMagnitudeFraction(fPrecision.fUnion.fracSig.fMaxFrac), fRoundingMode, status); value.setFractionLength( - uprv_max(0, -getDisplayMagnitudeSignificant(value, fUnion.fracSig.fMinSig)), + uprv_max(0, -getDisplayMagnitudeFraction(fPrecision.fUnion.fracSig.fMinFrac)), + INT32_MAX); + break; + + case Precision::RND_SIGNIFICANT: + value.roundToMagnitude( + getRoundingMagnitudeSignificant(value, fPrecision.fUnion.fracSig.fMaxSig), + fRoundingMode, + status); + value.setFractionLength( + uprv_max(0, -getDisplayMagnitudeSignificant(value, fPrecision.fUnion.fracSig.fMinSig)), INT32_MAX); // Make sure that digits are displayed on zero. - if (value.isZero() && fUnion.fracSig.fMinSig > 0) { + if (value.isZero() && fPrecision.fUnion.fracSig.fMinSig > 0) { value.setIntegerLength(1, INT32_MAX); } break; - case RND_FRACTION_SIGNIFICANT: { - int32_t displayMag = getDisplayMagnitudeFraction(fUnion.fracSig.fMinFrac); - int32_t roundingMag = getRoundingMagnitudeFraction(fUnion.fracSig.fMaxFrac); - if (fUnion.fracSig.fMinSig == -1) { + case Precision::RND_FRACTION_SIGNIFICANT: { + int32_t displayMag = getDisplayMagnitudeFraction(fPrecision.fUnion.fracSig.fMinFrac); + int32_t roundingMag = getRoundingMagnitudeFraction(fPrecision.fUnion.fracSig.fMaxFrac); + if (fPrecision.fUnion.fracSig.fMinSig == -1) { // Max Sig override - int32_t candidate = getRoundingMagnitudeSignificant(value, fUnion.fracSig.fMaxSig); + int32_t candidate = getRoundingMagnitudeSignificant( + value, + fPrecision.fUnion.fracSig.fMaxSig); roundingMag = uprv_max(roundingMag, candidate); } else { // Min Sig override - int32_t candidate = getDisplayMagnitudeSignificant(value, fUnion.fracSig.fMinSig); + int32_t candidate = getDisplayMagnitudeSignificant( + value, + fPrecision.fUnion.fracSig.fMinSig); roundingMag = uprv_min(roundingMag, candidate); } value.roundToMagnitude(roundingMag, fRoundingMode, status); @@ -369,30 +388,27 @@ void Rounder::apply(impl::DecimalQuantity &value, UErrorCode& status) const { break; } - case RND_INCREMENT: + case Precision::RND_INCREMENT: value.roundToIncrement( - fUnion.increment.fIncrement, - fRoundingMode, - fUnion.increment.fMaxFrac, - status); - value.setFractionLength(fUnion.increment.fMinFrac, INT32_MAX); + fPrecision.fUnion.increment.fIncrement, + fRoundingMode, + fPrecision.fUnion.increment.fMaxFrac, + status); + value.setFractionLength(fPrecision.fUnion.increment.fMinFrac, INT32_MAX); break; - case RND_CURRENCY: + case Precision::RND_CURRENCY: // Call .withCurrency() before .apply()! U_ASSERT(false); break; - - case RND_PASS_THROUGH: - break; } } -void Rounder::apply(impl::DecimalQuantity &value, int32_t minInt, UErrorCode /*status*/) { +void RoundingImpl::apply(impl::DecimalQuantity &value, int32_t minInt, UErrorCode /*status*/) { // This method is intended for the one specific purpose of helping print "00.000E0". - U_ASSERT(fType == RND_SIGNIFICANT); + U_ASSERT(isSignificantDigits()); U_ASSERT(value.isZero()); - value.setFractionLength(fUnion.fracSig.fMinSig - minInt, INT32_MAX); + value.setFractionLength(fPrecision.fUnion.fracSig.fMinSig - minInt, INT32_MAX); } #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/number_roundingutils.h b/icu4c/source/i18n/number_roundingutils.h index 0201623ac85..66d58bb775b 100644 --- a/icu4c/source/i18n/number_roundingutils.h +++ b/icu4c/source/i18n/number_roundingutils.h @@ -138,6 +138,55 @@ inline bool roundsAtMidpoint(int roundingMode) { digits_t doubleFractionLength(double input); } // namespace roundingutils + + +/** + * Encapsulates a Precision and a RoundingMode and performs rounding on a DecimalQuantity. + * + * This class does not exist in Java: instead, the base Precision class is used. + */ +class RoundingImpl { + public: + RoundingImpl() = default; // default constructor: leaves object in undefined state + + RoundingImpl(const Precision& precision, UNumberFormatRoundingMode roundingMode, + const CurrencyUnit& currency, UErrorCode& status); + + static RoundingImpl passThrough(); + + /** Required for ScientificFormatter */ + bool isSignificantDigits() const; + + /** + * Rounding endpoint used by Engineering and Compact notation. Chooses the most appropriate multiplier (magnitude + * adjustment), applies the adjustment, rounds, and returns the chosen multiplier. + * + * <p> + * In most cases, this is simple. However, when rounding the number causes it to cross a multiplier boundary, we + * need to re-do the rounding. For example, to display 999,999 in Engineering notation with 2 sigfigs, first you + * guess the multiplier to be -3. However, then you end up getting 1000E3, which is not the correct output. You then + * change your multiplier to be -6, and you get 1.0E6, which is correct. + * + * @param input The quantity to process. + * @param producer Function to call to return a multiplier based on a magnitude. + * @return The number of orders of magnitude the input was adjusted by this method. + */ + int32_t + chooseMultiplierAndApply(impl::DecimalQuantity &input, const impl::MultiplierProducer &producer, + UErrorCode &status); + + void apply(impl::DecimalQuantity &value, UErrorCode &status) const; + + /** Version of {@link #apply} that obeys minInt constraints. Used for scientific notation compatibility mode. */ + void apply(impl::DecimalQuantity &value, int32_t minInt, UErrorCode status); + + private: + Precision fPrecision; + UNumberFormatRoundingMode fRoundingMode; + bool fPassThrough; +}; + + } // namespace impl } // namespace number U_NAMESPACE_END diff --git a/icu4c/source/i18n/number_scientific.cpp b/icu4c/source/i18n/number_scientific.cpp index b5490536e72..72288f208a3 100644 --- a/icu4c/source/i18n/number_scientific.cpp +++ b/icu4c/source/i18n/number_scientific.cpp @@ -106,16 +106,16 @@ void ScientificHandler::processQuantity(DecimalQuantity &quantity, MicroProps &m // Treat zero as if it had magnitude 0 int32_t exponent; if (quantity.isZero()) { - if (fSettings.fRequireMinInt && micros.rounding.fType == Rounder::RND_SIGNIFICANT) { + if (fSettings.fRequireMinInt && micros.rounder.isSignificantDigits()) { // Show "00.000E0" on pattern "00.000E0" - micros.rounding.apply(quantity, fSettings.fEngineeringInterval, status); + micros.rounder.apply(quantity, fSettings.fEngineeringInterval, status); exponent = 0; } else { - micros.rounding.apply(quantity, status); + micros.rounder.apply(quantity, status); exponent = 0; } } else { - exponent = -micros.rounding.chooseMultiplierAndApply(quantity, *this, status); + exponent = -micros.rounder.chooseMultiplierAndApply(quantity, *this, status); } // Use MicroProps's helper ScientificModifier and save it as the modInner. @@ -124,7 +124,7 @@ void ScientificHandler::processQuantity(DecimalQuantity &quantity, MicroProps &m micros.modInner = &mod; // We already performed rounding. Do not perform it again. - micros.rounding = Rounder::constructPassThrough(); + micros.rounder = RoundingImpl::passThrough(); } int32_t ScientificHandler::getMultiplier(int32_t magnitude) const { diff --git a/icu4c/source/i18n/number_skeletons.cpp b/icu4c/source/i18n/number_skeletons.cpp index ec7041d3374..bae38ac445e 100644 --- a/icu4c/source/i18n/number_skeletons.cpp +++ b/icu4c/source/i18n/number_skeletons.cpp @@ -53,10 +53,18 @@ void U_CALLCONV initNumberSkeletons(UErrorCode& status) { b.add(u"base-unit", STEM_BASE_UNIT, status); b.add(u"percent", STEM_PERCENT, status); b.add(u"permille", STEM_PERMILLE, status); - b.add(u"round-integer", STEM_ROUND_INTEGER, status); - b.add(u"round-unlimited", STEM_ROUND_UNLIMITED, status); - b.add(u"round-currency-standard", STEM_ROUND_CURRENCY_STANDARD, status); - b.add(u"round-currency-cash", STEM_ROUND_CURRENCY_CASH, status); + b.add(u"precision-integer", STEM_PRECISION_INTEGER, status); + b.add(u"precision-unlimited", STEM_PRECISION_UNLIMITED, status); + b.add(u"precision-currency-standard", STEM_PRECISION_CURRENCY_STANDARD, status); + b.add(u"precision-currency-cash", STEM_PRECISION_CURRENCY_CASH, status); + b.add(u"rounding-mode-ceiling", STEM_ROUNDING_MODE_CEILING, status); + b.add(u"rounding-mode-floor", STEM_ROUNDING_MODE_FLOOR, status); + b.add(u"rounding-mode-down", STEM_ROUNDING_MODE_DOWN, status); + b.add(u"rounding-mode-up", STEM_ROUNDING_MODE_UP, status); + b.add(u"rounding-mode-half-even", STEM_ROUNDING_MODE_HALF_EVEN, status); + b.add(u"rounding-mode-half-down", STEM_ROUNDING_MODE_HALF_DOWN, status); + b.add(u"rounding-mode-half-up", STEM_ROUNDING_MODE_HALF_UP, status); + b.add(u"rounding-mode-unnecessary", STEM_ROUNDING_MODE_UNNECESSARY, status); b.add(u"group-off", STEM_GROUP_OFF, status); b.add(u"group-min2", STEM_GROUP_MIN2, status); b.add(u"group-auto", STEM_GROUP_AUTO, status); @@ -80,7 +88,7 @@ void U_CALLCONV initNumberSkeletons(UErrorCode& status) { if (U_FAILURE(status)) { return; } // Section 2: - b.add(u"round-increment", STEM_ROUND_INCREMENT, status); + b.add(u"precision-increment", STEM_PRECISION_INCREMENT, status); b.add(u"measure-unit", STEM_MEASURE_UNIT, status); b.add(u"per-measure-unit", STEM_PER_MEASURE_UNIT, status); b.add(u"currency", STEM_CURRENCY, status); @@ -134,16 +142,6 @@ inline void appendMultiple(UnicodeString& sb, UChar32 cp, int32_t count) { } -// NOTE: The order of these strings must be consistent with UNumberFormatRoundingMode -const char16_t* const kRoundingModeStrings[] = { - u"ceiling", u"floor", u"down", u"up", u"half-even", u"half-down", u"half-up", u"unnecessary"}; - -constexpr int32_t kRoundingModeCount = 8; -static_assert( - sizeof(kRoundingModeStrings) / sizeof(*kRoundingModeStrings) == kRoundingModeCount, - "kRoundingModeCount should be the number of rounding modes"); - - } // anonymous namespace @@ -182,19 +180,43 @@ MeasureUnit stem_to_object::unit(skeleton::StemEnum stem) { } } -Rounder stem_to_object::rounder(skeleton::StemEnum stem) { +Precision stem_to_object::precision(skeleton::StemEnum stem) { switch (stem) { - case STEM_ROUND_INTEGER: - return Rounder::integer(); - case STEM_ROUND_UNLIMITED: - return Rounder::unlimited(); - case STEM_ROUND_CURRENCY_STANDARD: - return Rounder::currency(UCURR_USAGE_STANDARD); - case STEM_ROUND_CURRENCY_CASH: - return Rounder::currency(UCURR_USAGE_CASH); + case STEM_PRECISION_INTEGER: + return Precision::integer(); + case STEM_PRECISION_UNLIMITED: + return Precision::unlimited(); + case STEM_PRECISION_CURRENCY_STANDARD: + return Precision::currency(UCURR_USAGE_STANDARD); + case STEM_PRECISION_CURRENCY_CASH: + return Precision::currency(UCURR_USAGE_CASH); default: U_ASSERT(false); - return Rounder::integer(); // return a value: silence compiler warning + return Precision::integer(); // return a value: silence compiler warning + } +} + +UNumberFormatRoundingMode stem_to_object::roundingMode(skeleton::StemEnum stem) { + switch (stem) { + case STEM_ROUNDING_MODE_CEILING: + return UNUM_ROUND_CEILING; + case STEM_ROUNDING_MODE_FLOOR: + return UNUM_ROUND_FLOOR; + case STEM_ROUNDING_MODE_DOWN: + return UNUM_ROUND_DOWN; + case STEM_ROUNDING_MODE_UP: + return UNUM_ROUND_UP; + case STEM_ROUNDING_MODE_HALF_EVEN: + return UNUM_ROUND_HALFEVEN; + case STEM_ROUNDING_MODE_HALF_DOWN: + return UNUM_ROUND_HALFDOWN; + case STEM_ROUNDING_MODE_HALF_UP: + return UNUM_ROUND_HALFUP; + case STEM_ROUNDING_MODE_UNNECESSARY: + return UNUM_ROUND_UNNECESSARY; + default: + U_ASSERT(false); + return UNUM_ROUND_UNNECESSARY; } } @@ -265,6 +287,37 @@ UNumberDecimalSeparatorDisplay stem_to_object::decimalSeparatorDisplay(skeleton: } +void enum_to_stem_string::roundingMode(UNumberFormatRoundingMode value, UnicodeString& sb) { + switch (value) { + case UNUM_ROUND_CEILING: + sb.append(u"rounding-mode-ceiling", -1); + break; + case UNUM_ROUND_FLOOR: + sb.append(u"rounding-mode-floor", -1); + break; + case UNUM_ROUND_DOWN: + sb.append(u"rounding-mode-down", -1); + break; + case UNUM_ROUND_UP: + sb.append(u"rounding-mode-up", -1); + break; + case UNUM_ROUND_HALFEVEN: + sb.append(u"rounding-mode-half-even", -1); + break; + case UNUM_ROUND_HALFDOWN: + sb.append(u"rounding-mode-half-down", -1); + break; + case UNUM_ROUND_HALFUP: + sb.append(u"rounding-mode-half-up", -1); + break; + case UNUM_ROUND_UNNECESSARY: + sb.append(u"rounding-mode-unnecessary", -1); + break; + default: + U_ASSERT(false); + } +} + void enum_to_stem_string::groupingStrategy(UGroupingStrategy value, UnicodeString& sb) { switch (value) { case UNUM_GROUPING_OFF: @@ -442,7 +495,7 @@ MacroProps skeleton::parseSkeleton(const UnicodeString& skeletonString, UErrorCo // Does the current stem require an option? if (isTokenSeparator && stem != STATE_NULL) { switch (stem) { - case STATE_INCREMENT_ROUNDER: + case STATE_INCREMENT_PRECISION: case STATE_MEASURE_UNIT: case STATE_PER_MEASURE_UNIT: case STATE_CURRENCY_UNIT: @@ -472,11 +525,11 @@ skeleton::parseStem(const StringSegment& segment, const UCharsTrie& stemTrie, Se // First check for "blueprint" stems, which start with a "signal char" switch (segment.charAt(0)) { case u'.': - CHECK_NULL(seen, rounder, status); + CHECK_NULL(seen, precision, status); blueprint_helpers::parseFractionStem(segment, macros, status); - return STATE_FRACTION_ROUNDER; + return STATE_FRACTION_PRECISION; case u'@': - CHECK_NULL(seen, rounder, status); + CHECK_NULL(seen, precision, status); blueprint_helpers::parseDigitsStem(segment, macros, status); return STATE_NULL; default: @@ -519,19 +572,31 @@ skeleton::parseStem(const StringSegment& segment, const UCharsTrie& stemTrie, Se macros.unit = stem_to_object::unit(stem); return STATE_NULL; - case STEM_ROUND_INTEGER: - case STEM_ROUND_UNLIMITED: - case STEM_ROUND_CURRENCY_STANDARD: - case STEM_ROUND_CURRENCY_CASH: - CHECK_NULL(seen, rounder, status); - macros.rounder = stem_to_object::rounder(stem); + case STEM_PRECISION_INTEGER: + case STEM_PRECISION_UNLIMITED: + case STEM_PRECISION_CURRENCY_STANDARD: + case STEM_PRECISION_CURRENCY_CASH: + CHECK_NULL(seen, precision, status); + macros.precision = stem_to_object::precision(stem); switch (stem) { - case STEM_ROUND_INTEGER: - return STATE_FRACTION_ROUNDER; // allows for "round-integer/@##" + case STEM_PRECISION_INTEGER: + return STATE_FRACTION_PRECISION; // allows for "precision-integer/@##" default: - return STATE_ROUNDER; // allows for rounding mode options + return STATE_NULL; } + case STEM_ROUNDING_MODE_CEILING: + case STEM_ROUNDING_MODE_FLOOR: + case STEM_ROUNDING_MODE_DOWN: + case STEM_ROUNDING_MODE_UP: + case STEM_ROUNDING_MODE_HALF_EVEN: + case STEM_ROUNDING_MODE_HALF_DOWN: + case STEM_ROUNDING_MODE_HALF_UP: + case STEM_ROUNDING_MODE_UNNECESSARY: + CHECK_NULL(seen, roundingMode, status); + macros.roundingMode = stem_to_object::roundingMode(stem); + return STATE_NULL; + case STEM_GROUP_OFF: case STEM_GROUP_MIN2: case STEM_GROUP_AUTO: @@ -574,9 +639,9 @@ skeleton::parseStem(const StringSegment& segment, const UCharsTrie& stemTrie, Se // Stems requiring an option: - case STEM_ROUND_INCREMENT: - CHECK_NULL(seen, rounder, status); - return STATE_INCREMENT_ROUNDER; + case STEM_PRECISION_INCREMENT: + CHECK_NULL(seen, precision, status); + return STATE_INCREMENT_PRECISION; case STEM_MEASURE_UNIT: CHECK_NULL(seen, unit, status); @@ -623,9 +688,9 @@ ParseState skeleton::parseOption(ParseState stem, const StringSegment& segment, case STATE_PER_MEASURE_UNIT: blueprint_helpers::parseMeasurePerUnitOption(segment, macros, status); return STATE_NULL; - case STATE_INCREMENT_ROUNDER: + case STATE_INCREMENT_PRECISION: blueprint_helpers::parseIncrementOption(segment, macros, status); - return STATE_ROUNDER; + return STATE_NULL; case STATE_INTEGER_WIDTH: blueprint_helpers::parseIntegerWidthOption(segment, macros, status); return STATE_NULL; @@ -657,21 +722,9 @@ ParseState skeleton::parseOption(ParseState stem, const StringSegment& segment, // Frac-sig option switch (stem) { - case STATE_FRACTION_ROUNDER: + case STATE_FRACTION_PRECISION: if (blueprint_helpers::parseFracSigOption(segment, macros, status)) { - return STATE_ROUNDER; - } - break; - default: - break; - } - - // Rounding mode option - switch (stem) { - case STATE_ROUNDER: - case STATE_FRACTION_ROUNDER: - if (blueprint_helpers::parseRoundingModeOption(segment, macros, status)) { - return STATE_ROUNDER; + return STATE_NULL; } break; default: @@ -698,7 +751,11 @@ void GeneratorHelpers::generateSkeleton(const MacroProps& macros, UnicodeString& sb.append(u' '); } if (U_FAILURE(status)) { return; } - if (GeneratorHelpers::rounding(macros, sb, status)) { + if (GeneratorHelpers::precision(macros, sb, status)) { + sb.append(u' '); + } + if (U_FAILURE(status)) { return; } + if (GeneratorHelpers::roundingMode(macros, sb, status)) { sb.append(u' '); } if (U_FAILURE(status)) { return; } @@ -927,16 +984,16 @@ void blueprint_helpers::parseFractionStem(const StringSegment& segment, MacroPro } // Use the public APIs to enforce bounds checking if (maxFrac == -1) { - macros.rounder = Rounder::minFraction(minFrac); + macros.precision = Precision::minFraction(minFrac); } else { - macros.rounder = Rounder::minMaxFraction(minFrac, maxFrac); + macros.precision = Precision::minMaxFraction(minFrac, maxFrac); } } void blueprint_helpers::generateFractionStem(int32_t minFrac, int32_t maxFrac, UnicodeString& sb, UErrorCode&) { if (minFrac == 0 && maxFrac == 0) { - sb.append(u"round-integer", -1); + sb.append(u"precision-integer", -1); return; } sb.append(u'.'); @@ -984,9 +1041,9 @@ blueprint_helpers::parseDigitsStem(const StringSegment& segment, MacroProps& mac } // Use the public APIs to enforce bounds checking if (maxSig == -1) { - macros.rounder = Rounder::minDigits(minSig); + macros.precision = Precision::minSignificantDigits(minSig); } else { - macros.rounder = Rounder::minMaxDigits(minSig, maxSig); + macros.precision = Precision::minMaxSignificantDigits(minSig, maxSig); } } @@ -1051,11 +1108,11 @@ bool blueprint_helpers::parseFracSigOption(const StringSegment& segment, MacroPr return false; } - auto& oldRounder = static_cast<const FractionRounder&>(macros.rounder); + auto& oldPrecision = static_cast<const FractionPrecision&>(macros.precision); if (maxSig == -1) { - macros.rounder = oldRounder.withMinDigits(minSig); + macros.precision = oldPrecision.withMinDigits(minSig); } else { - macros.rounder = oldRounder.withMaxDigits(maxSig); + macros.precision = oldPrecision.withMaxDigits(maxSig); } return true; } @@ -1083,10 +1140,10 @@ void blueprint_helpers::parseIncrementOption(const StringSegment& segment, Macro decimalOffset++; } if (decimalOffset == segment.length()) { - macros.rounder = Rounder::increment(increment); + macros.precision = Precision::increment(increment); } else { int32_t fractionLength = segment.length() - decimalOffset - 1; - macros.rounder = Rounder::increment(increment).withMinFraction(fractionLength); + macros.precision = Precision::increment(increment).withMinFraction(fractionLength); } } @@ -1104,21 +1161,6 @@ void blueprint_helpers::generateIncrementOption(double increment, int32_t traili } } -bool -blueprint_helpers::parseRoundingModeOption(const StringSegment& segment, MacroProps& macros, UErrorCode&) { - for (int rm = 0; rm < kRoundingModeCount; rm++) { - if (segment == UnicodeString(kRoundingModeStrings[rm], -1)) { - macros.rounder = macros.rounder.withMode(static_cast<RoundingMode>(rm)); - return true; - } - } - return false; -} - -void blueprint_helpers::generateRoundingModeOption(RoundingMode mode, UnicodeString& sb, UErrorCode&) { - sb.append(kRoundingModeStrings[mode], -1); -} - void blueprint_helpers::parseIntegerWidthOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status) { int32_t offset = 0; @@ -1307,17 +1349,17 @@ bool GeneratorHelpers::perUnit(const MacroProps& macros, UnicodeString& sb, UErr } } -bool GeneratorHelpers::rounding(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { - if (macros.rounder.fType == Rounder::RND_NONE) { - sb.append(u"round-unlimited", -1); - } else if (macros.rounder.fType == Rounder::RND_FRACTION) { - const Rounder::FractionSignificantSettings& impl = macros.rounder.fUnion.fracSig; +bool GeneratorHelpers::precision(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { + if (macros.precision.fType == Precision::RND_NONE) { + sb.append(u"precision-unlimited", -1); + } else if (macros.precision.fType == Precision::RND_FRACTION) { + const Precision::FractionSignificantSettings& impl = macros.precision.fUnion.fracSig; blueprint_helpers::generateFractionStem(impl.fMinFrac, impl.fMaxFrac, sb, status); - } else if (macros.rounder.fType == Rounder::RND_SIGNIFICANT) { - const Rounder::FractionSignificantSettings& impl = macros.rounder.fUnion.fracSig; + } else if (macros.precision.fType == Precision::RND_SIGNIFICANT) { + const Precision::FractionSignificantSettings& impl = macros.precision.fUnion.fracSig; blueprint_helpers::generateDigitsStem(impl.fMinSig, impl.fMaxSig, sb, status); - } else if (macros.rounder.fType == Rounder::RND_FRACTION_SIGNIFICANT) { - const Rounder::FractionSignificantSettings& impl = macros.rounder.fUnion.fracSig; + } else if (macros.precision.fType == Precision::RND_FRACTION_SIGNIFICANT) { + const Precision::FractionSignificantSettings& impl = macros.precision.fUnion.fracSig; blueprint_helpers::generateFractionStem(impl.fMinFrac, impl.fMaxFrac, sb, status); sb.append(u'/'); if (impl.fMinSig == -1) { @@ -1325,36 +1367,38 @@ bool GeneratorHelpers::rounding(const MacroProps& macros, UnicodeString& sb, UEr } else { blueprint_helpers::generateDigitsStem(impl.fMinSig, -1, sb, status); } - } else if (macros.rounder.fType == Rounder::RND_INCREMENT) { - const Rounder::IncrementSettings& impl = macros.rounder.fUnion.increment; - sb.append(u"round-increment/", -1); + } else if (macros.precision.fType == Precision::RND_INCREMENT) { + const Precision::IncrementSettings& impl = macros.precision.fUnion.increment; + sb.append(u"precision-increment/", -1); blueprint_helpers::generateIncrementOption( impl.fIncrement, impl.fMinFrac - impl.fMaxFrac, sb, status); - } else if (macros.rounder.fType == Rounder::RND_CURRENCY) { - UCurrencyUsage usage = macros.rounder.fUnion.currencyUsage; + } else if (macros.precision.fType == Precision::RND_CURRENCY) { + UCurrencyUsage usage = macros.precision.fUnion.currencyUsage; if (usage == UCURR_USAGE_STANDARD) { - sb.append(u"round-currency-standard", -1); + sb.append(u"precision-currency-standard", -1); } else { - sb.append(u"round-currency-cash", -1); + sb.append(u"precision-currency-cash", -1); } } else { // Bogus or Error return false; } - // Generate the options - if (macros.rounder.fRoundingMode != kDefaultMode) { - sb.append(u'/'); - blueprint_helpers::generateRoundingModeOption(macros.rounder.fRoundingMode, sb, status); - } - // NOTE: Always return true for rounding because the default value depends on other options. return true; } +bool GeneratorHelpers::roundingMode(const MacroProps& macros, UnicodeString& sb, UErrorCode&) { + if (macros.roundingMode == kDefaultMode) { + return false; // Default + } + enum_to_stem_string::roundingMode(macros.roundingMode, sb); + return true; +} + bool GeneratorHelpers::grouping(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { if (macros.grouper.isBogus()) { return false; // No value diff --git a/icu4c/source/i18n/number_skeletons.h b/icu4c/source/i18n/number_skeletons.h index b84f47cc327..3e4def26527 100644 --- a/icu4c/source/i18n/number_skeletons.h +++ b/icu4c/source/i18n/number_skeletons.h @@ -39,12 +39,11 @@ enum ParseState { // Section 1: We might accept an option, but it is not required: STATE_SCIENTIFIC, - STATE_ROUNDER, - STATE_FRACTION_ROUNDER, + STATE_FRACTION_PRECISION, // Section 2: An option is required: - STATE_INCREMENT_ROUNDER, + STATE_INCREMENT_PRECISION, STATE_MEASURE_UNIT, STATE_PER_MEASURE_UNIT, STATE_CURRENCY_UNIT, @@ -72,10 +71,18 @@ enum StemEnum { STEM_BASE_UNIT, STEM_PERCENT, STEM_PERMILLE, - STEM_ROUND_INTEGER, - STEM_ROUND_UNLIMITED, - STEM_ROUND_CURRENCY_STANDARD, - STEM_ROUND_CURRENCY_CASH, + STEM_PRECISION_INTEGER, + STEM_PRECISION_UNLIMITED, + STEM_PRECISION_CURRENCY_STANDARD, + STEM_PRECISION_CURRENCY_CASH, + STEM_ROUNDING_MODE_CEILING, + STEM_ROUNDING_MODE_FLOOR, + STEM_ROUNDING_MODE_DOWN, + STEM_ROUNDING_MODE_UP, + STEM_ROUNDING_MODE_HALF_EVEN, + STEM_ROUNDING_MODE_HALF_DOWN, + STEM_ROUNDING_MODE_HALF_UP, + STEM_ROUNDING_MODE_UNNECESSARY, STEM_GROUP_OFF, STEM_GROUP_MIN2, STEM_GROUP_AUTO, @@ -99,7 +106,7 @@ enum StemEnum { // Section 2: Stems that DO require an option: - STEM_ROUND_INCREMENT, + STEM_PRECISION_INCREMENT, STEM_MEASURE_UNIT, STEM_PER_MEASURE_UNIT, STEM_CURRENCY, @@ -163,7 +170,9 @@ Notation notation(skeleton::StemEnum stem); MeasureUnit unit(skeleton::StemEnum stem); -Rounder rounder(skeleton::StemEnum stem); +Precision precision(skeleton::StemEnum stem); + +UNumberFormatRoundingMode roundingMode(skeleton::StemEnum stem); UGroupingStrategy groupingStrategy(skeleton::StemEnum stem); @@ -181,6 +190,8 @@ UNumberDecimalSeparatorDisplay decimalSeparatorDisplay(skeleton::StemEnum stem); */ namespace enum_to_stem_string { +void roundingMode(UNumberFormatRoundingMode value, UnicodeString& sb); + void groupingStrategy(UGroupingStrategy value, UnicodeString& sb); void unitWidth(UNumberUnitWidth value, UnicodeString& sb); @@ -230,11 +241,6 @@ void parseIncrementOption(const StringSegment& segment, MacroProps& macros, UErr void generateIncrementOption(double increment, int32_t trailingZeros, UnicodeString& sb, UErrorCode& status); -/** @return Whether we successfully found and parsed a rounding mode. */ -bool parseRoundingModeOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status); - -void generateRoundingModeOption(RoundingMode mode, UnicodeString& sb, UErrorCode& status); - void parseIntegerWidthOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status); void generateIntegerWidthOption(int32_t minInt, int32_t maxInt, UnicodeString& sb, UErrorCode& status); @@ -273,7 +279,9 @@ class GeneratorHelpers { static bool perUnit(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); - static bool rounding(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + static bool precision(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + + static bool roundingMode(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); static bool grouping(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); @@ -299,7 +307,8 @@ struct SeenMacroProps { bool notation = false; bool unit = false; bool perUnit = false; - bool rounder = false; + bool precision = false; + bool roundingMode = false; bool grouper = false; bool padder = false; bool integerWidth = false; diff --git a/icu4c/source/i18n/number_utils.h b/icu4c/source/i18n/number_utils.h index 2fdbb68b40b..df6f0d3080f 100644 --- a/icu4c/source/i18n/number_utils.h +++ b/icu4c/source/i18n/number_utils.h @@ -14,6 +14,7 @@ #include "number_patternstring.h" #include "number_modifiers.h" #include "number_multiplier.h" +#include "number_roundingutils.h" #include "decNumber.h" #include "charstr.h" @@ -23,7 +24,7 @@ namespace impl { struct MicroProps : public MicroPropsGenerator { // NOTE: All of these fields are properly initialized in NumberFormatterImpl. - Rounder rounding; + RoundingImpl rounder; Grouper grouping; Padder padding; IntegerWidth integerWidth; diff --git a/icu4c/source/i18n/unicode/numberformatter.h b/icu4c/source/i18n/unicode/numberformatter.h index 5ec10182234..6d709c4bf76 100644 --- a/icu4c/source/i18n/unicode/numberformatter.h +++ b/icu4c/source/i18n/unicode/numberformatter.h @@ -33,11 +33,11 @@ * // Most basic usage: * NumberFormatter::withLocale(...).format(123).toString(); // 1,234 in en-US * - * // Custom notation, unit, and rounding strategy: + * // Custom notation, unit, and rounding precision: * NumberFormatter::with() * .notation(Notation::compactShort()) * .unit(CurrencyUnit("EUR", status)) - * .rounding(Rounder::maxDigits(2)) + * .precision(PrecisionPrecision:::maxDigits(2)) * .locale(...) * .format(1234) * .toString(); // €1.2K in en-US @@ -45,7 +45,7 @@ * // Create a formatter in a singleton for use later: * static const LocalizedNumberFormatter formatter = NumberFormatter::withLocale(...) * .unit(NoUnit::percent()) - * .rounding(Rounder::fixedFraction(3)); + * .precision(PrecisionPrecision:::fixedFraction(3)); * formatter.format(5.9831).toString(); // 5.983% in en-US * * // Create a "template" in a singleton but without setting a locale until the call site: @@ -65,7 +65,7 @@ * * <pre> * UnlocalizedNumberFormatter formatter = UnlocalizedNumberFormatter::with().notation(Notation::scientific()); - * formatter.rounding(Rounder.maxFraction(2)); // does nothing! + * formatter.precision(Precision.maxFraction(2)); // does nothing! * formatter.locale(Locale.getEnglish()).format(9.8765).toString(); // prints "9.8765E0", not "9.88E0" * </pre> * @@ -99,10 +99,10 @@ class LocalizedNumberFormatter; class FormattedNumber; class Notation; class ScientificNotation; -class Rounder; -class FractionRounder; -class CurrencyRounder; -class IncrementRounder; +class Precision; +class FractionPrecision; +class CurrencyPrecision; +class IncrementPrecision; class IntegerWidth; namespace impl { @@ -134,10 +134,8 @@ class NumberFormatterImpl; struct ParsedPatternInfo; class ScientificModifier; class MultiplierProducer; -class MutablePatternModifier; -class LongNameHandler; +class RoundingImpl; class ScientificHandler; -class CompactHandler; class Modifier; class NumberStringBuilder; class AffixPatternProvider; @@ -240,13 +238,13 @@ class U_I18N_API Notation : public UMemory { * </pre> * * <p> - * When compact notation is specified without an explicit rounding strategy, numbers are rounded off to the closest + * When compact notation is specified without an explicit rounding precision, numbers are rounded off to the closest * integer after scaling the number by the corresponding power of 10, but with a digit shown after the decimal - * separator if there is only one digit before the decimal separator. The default compact notation rounding strategy + * separator if there is only one digit before the decimal separator. The default compact notation rounding precision * is equivalent to: * * <pre> - * Rounder.integer().withMinDigits(2) + * Precision::integer().withMinDigits(2) * </pre> * * @return A CompactNotation for passing to the NumberFormatter notation() setter. @@ -411,17 +409,25 @@ class U_I18N_API ScientificNotation : public Notation { }; // Reserve extra names in case they are added as classes in the future: -typedef Rounder DigitRounder; +typedef Precision SignificantDigitsPrecision; + +// Typedefs for ICU 60/61 compatibility. +// These will be removed in ICU 64. +// See http://bugs.icu-project.org/trac/ticket/13746 +typedef Precision Rounder; +typedef FractionPrecision FractionRounder; +typedef IncrementPrecision IncrementRounder; +typedef CurrencyPrecision CurrencyRounder; /** - * A class that defines the rounding strategy to be used when formatting numbers in NumberFormatter. + * A class that defines the rounding precision to be used when formatting numbers in NumberFormatter. * * <p> - * To create a Rounder, use one of the factory methods. + * To create a Precision, use one of the factory methods. * * @draft ICU 60 */ -class U_I18N_API Rounder : public UMemory { +class U_I18N_API Precision : public UMemory { public: /** @@ -437,18 +443,18 @@ class U_I18N_API Rounder : public UMemory { * <p> * http://www.serpentine.com/blog/2011/06/29/here-be-dragons-advances-in-problems-you-didnt-even-know-you-had/ * - * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. + * @return A Precision for chaining or passing to the NumberFormatter precision() setter. * @draft ICU 60 */ - static Rounder unlimited(); + static Precision unlimited(); /** * Show numbers rounded if necessary to the nearest integer. * - * @return A FractionRounder for chaining or passing to the NumberFormatter rounding() setter. + * @return A FractionPrecision for chaining or passing to the NumberFormatter precision() setter. * @draft ICU 60 */ - static FractionRounder integer(); + static FractionPrecision integer(); /** * Show numbers rounded if necessary to a certain number of fraction places (numerals after the decimal separator). @@ -474,10 +480,10 @@ class U_I18N_API Rounder : public UMemory { * @param minMaxFractionPlaces * The minimum and maximum number of numerals to display after the decimal separator (rounding if too * long or padding with zeros if too short). - * @return A FractionRounder for chaining or passing to the NumberFormatter rounding() setter. + * @return A FractionPrecision for chaining or passing to the NumberFormatter precision() setter. * @draft ICU 60 */ - static FractionRounder fixedFraction(int32_t minMaxFractionPlaces); + static FractionPrecision fixedFraction(int32_t minMaxFractionPlaces); /** * Always show at least a certain number of fraction places after the decimal separator, padding with zeros if @@ -489,10 +495,10 @@ class U_I18N_API Rounder : public UMemory { * @param minFractionPlaces * The minimum number of numerals to display after the decimal separator (padding with zeros if * necessary). - * @return A FractionRounder for chaining or passing to the NumberFormatter rounding() setter. + * @return A FractionPrecision for chaining or passing to the NumberFormatter precision() setter. * @draft ICU 60 */ - static FractionRounder minFraction(int32_t minFractionPlaces); + static FractionPrecision minFraction(int32_t minFractionPlaces); /** * Show numbers rounded if necessary to a certain number of fraction places (numerals after the decimal separator). @@ -501,10 +507,10 @@ class U_I18N_API Rounder : public UMemory { * * @param maxFractionPlaces * The maximum number of numerals to display after the decimal mark (rounding if necessary). - * @return A FractionRounder for chaining or passing to the NumberFormatter rounding() setter. + * @return A FractionPrecision for chaining or passing to the NumberFormatter precision() setter. * @draft ICU 60 */ - static FractionRounder maxFraction(int32_t maxFractionPlaces); + static FractionPrecision maxFraction(int32_t maxFractionPlaces); /** * Show numbers rounded if necessary to a certain number of fraction places (numerals after the decimal separator); @@ -516,10 +522,10 @@ class U_I18N_API Rounder : public UMemory { * necessary). * @param maxFractionPlaces * The maximum number of numerals to display after the decimal separator (rounding if necessary). - * @return A FractionRounder for chaining or passing to the NumberFormatter rounding() setter. + * @return A FractionPrecision for chaining or passing to the NumberFormatter precision() setter. * @draft ICU 60 */ - static FractionRounder minMaxFraction(int32_t minFractionPlaces, int32_t maxFractionPlaces); + static FractionPrecision minMaxFraction(int32_t minFractionPlaces, int32_t maxFractionPlaces); /** * Show numbers rounded if necessary to a certain number of significant digits or significant figures. Additionally, @@ -531,10 +537,10 @@ class U_I18N_API Rounder : public UMemory { * @param minMaxSignificantDigits * The minimum and maximum number of significant digits to display (rounding if too long or padding with * zeros if too short). - * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. - * @draft ICU 60 + * @return A precision for chaining or passing to the NumberFormatter precision() setter. + * @draft ICU 62 */ - static DigitRounder fixedDigits(int32_t minMaxSignificantDigits); + static SignificantDigitsPrecision fixedSignificantDigits(int32_t minMaxSignificantDigits); /** * Always show at least a certain number of significant digits/figures, padding with zeros if necessary. Do not @@ -545,20 +551,20 @@ class U_I18N_API Rounder : public UMemory { * * @param minSignificantDigits * The minimum number of significant digits to display (padding with zeros if too short). - * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. - * @draft ICU 60 + * @return A precision for chaining or passing to the NumberFormatter precision() setter. + * @draft ICU 62 */ - static DigitRounder minDigits(int32_t minSignificantDigits); + static SignificantDigitsPrecision minSignificantDigits(int32_t minSignificantDigits); /** * Show numbers rounded if necessary to a certain number of significant digits/figures. * * @param maxSignificantDigits * The maximum number of significant digits to display (rounding if too long). - * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. - * @draft ICU 60 + * @return A precision for chaining or passing to the NumberFormatter precision() setter. + * @draft ICU 62 */ - static DigitRounder maxDigits(int32_t maxSignificantDigits); + static SignificantDigitsPrecision maxSignificantDigits(int32_t maxSignificantDigits); /** * Show numbers rounded if necessary to a certain number of significant digits/figures; in addition, always show at @@ -568,10 +574,34 @@ class U_I18N_API Rounder : public UMemory { * The minimum number of significant digits to display (padding with zeros if necessary). * @param maxSignificantDigits * The maximum number of significant digits to display (rounding if necessary). - * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. - * @draft ICU 60 + * @return A precision for chaining or passing to the NumberFormatter precision() setter. + * @draft ICU 62 */ - static DigitRounder minMaxDigits(int32_t minSignificantDigits, int32_t maxSignificantDigits); + static SignificantDigitsPrecision minMaxSignificantDigits(int32_t minSignificantDigits, + int32_t maxSignificantDigits); + + // Compatiblity methods that will be removed in ICU 64. + // See http://bugs.icu-project.org/trac/ticket/13746 + + /** @deprecated ICU 62 */ + static inline SignificantDigitsPrecision fixedDigits(int32_t a) { + return fixedSignificantDigits(a); + } + + /** @deprecated ICU 62 */ + static inline SignificantDigitsPrecision minDigits(int32_t a) { + return minSignificantDigits(a); + } + + /** @deprecated ICU 62 */ + static inline SignificantDigitsPrecision maxDigits(int32_t a) { + return maxSignificantDigits(a); + } + + /** @deprecated ICU 62 */ + static inline SignificantDigitsPrecision minMaxDigits(int32_t a, int32_t b) { + return minMaxSignificantDigits(a, b); + } /** * Show numbers rounded if necessary to the closest multiple of a certain rounding increment. For example, if the @@ -584,20 +614,20 @@ class U_I18N_API Rounder : public UMemory { * decimal separator (to display 1.2 as "1.00" and 1.3 as "1.50"), you can run: * * <pre> - * Rounder::increment(0.5).withMinFraction(2) + * Precision::increment(0.5).withMinFraction(2) * </pre> * * @param roundingIncrement * The increment to which to round numbers. - * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. + * @return A precision for chaining or passing to the NumberFormatter precision() setter. * @draft ICU 60 */ - static IncrementRounder increment(double roundingIncrement); + static IncrementPrecision increment(double roundingIncrement); /** * Show numbers rounded and padded according to the rules for the currency unit. The most common rounding settings - * for currencies include <code>Rounder.fixedFraction(2)</code>, <code>Rounder.integer()</code>, and - * <code>Rounder.increment(0.05)</code> for cash transactions ("nickel rounding"). + * for currencies include <code>Precision::fixedFraction(2)</code>, <code>Precision::integer()</code>, and + * <code>Precision::increment(0.05)</code> for cash transactions ("nickel rounding"). * * <p> * The exact rounding details will be resolved at runtime based on the currency unit specified in the @@ -607,10 +637,10 @@ class U_I18N_API Rounder : public UMemory { * @param currencyUsage * Either STANDARD (for digital transactions) or CASH (for transactions where the rounding increment may * be limited by the available denominations of cash or coins). - * @return A CurrencyRounder for chaining or passing to the NumberFormatter rounding() setter. + * @return A CurrencyPrecision for chaining or passing to the NumberFormatter precision() setter. * @draft ICU 60 */ - static CurrencyRounder currency(UCurrencyUsage currencyUsage); + static CurrencyPrecision currency(UCurrencyUsage currencyUsage); /** * Sets the rounding mode to use when picking the direction to round (up or down). Common values @@ -618,13 +648,15 @@ class U_I18N_API Rounder : public UMemory { * * @param roundingMode * The RoundingMode to use. - * @return A Rounder for passing to the NumberFormatter rounding() setter. - * @draft ICU 60 + * @return A Precision for passing to the NumberFormatter precision() setter. + * @deprecated ICU 62 Use the top-level roundingMode() setting instead. + * This method will be removed in ICU 64. + * See http://bugs.icu-project.org/trac/ticket/13746 */ - Rounder withMode(UNumberFormatRoundingMode roundingMode) const; + Precision withMode(UNumberFormatRoundingMode roundingMode) const; private: - enum RounderType { + enum PrecisionType { RND_BOGUS, RND_NONE, RND_FRACTION, @@ -632,11 +664,10 @@ class U_I18N_API Rounder : public UMemory { RND_FRACTION_SIGNIFICANT, RND_INCREMENT, RND_CURRENCY, - RND_PASS_THROUGH, RND_ERROR } fType; - union RounderUnion { + union PrecisionUnion { struct FractionSignificantSettings { // For RND_FRACTION, RND_SIGNIFICANT, and RND_FRACTION_SIGNIFICANT impl::digits_t fMinFrac; @@ -653,19 +684,21 @@ class U_I18N_API Rounder : public UMemory { UErrorCode errorCode; // For RND_ERROR } fUnion; - typedef RounderUnion::FractionSignificantSettings FractionSignificantSettings; - typedef RounderUnion::IncrementSettings IncrementSettings; + typedef PrecisionUnion::FractionSignificantSettings FractionSignificantSettings; + typedef PrecisionUnion::IncrementSettings IncrementSettings; + /** The Precision encapsulates the RoundingMode when used within the implementation. */ UNumberFormatRoundingMode fRoundingMode; - Rounder(const RounderType &type, const RounderUnion &union_, UNumberFormatRoundingMode roundingMode) + Precision(const PrecisionType& type, const PrecisionUnion& union_, + UNumberFormatRoundingMode roundingMode) : fType(type), fUnion(union_), fRoundingMode(roundingMode) {} - Rounder(UErrorCode errorCode) : fType(RND_ERROR) { + Precision(UErrorCode errorCode) : fType(RND_ERROR) { fUnion.errorCode = errorCode; } - Rounder() : fType(RND_BOGUS) {} + Precision() : fType(RND_BOGUS) {} bool isBogus() const { return fType == RND_BOGUS; @@ -679,47 +712,21 @@ class U_I18N_API Rounder : public UMemory { return FALSE; } - // On the parent type so that this method can be called internally on Rounder instances. - Rounder withCurrency(const CurrencyUnit ¤cy, UErrorCode &status) const; + // On the parent type so that this method can be called internally on Precision instances. + Precision withCurrency(const CurrencyUnit ¤cy, UErrorCode &status) const; - /** NON-CONST: mutates the current instance. */ - void setLocaleData(const CurrencyUnit ¤cy, UErrorCode &status); + static FractionPrecision constructFraction(int32_t minFrac, int32_t maxFrac); - void apply(impl::DecimalQuantity &value, UErrorCode &status) const; + static Precision constructSignificant(int32_t minSig, int32_t maxSig); - /** Version of {@link #apply} that obeys minInt constraints. Used for scientific notation compatibility mode. */ - void apply(impl::DecimalQuantity &value, int32_t minInt, UErrorCode status); + static Precision + constructFractionSignificant(const FractionPrecision &base, int32_t minSig, int32_t maxSig); - /** - * Rounding endpoint used by Engineering and Compact notation. Chooses the most appropriate multiplier (magnitude - * adjustment), applies the adjustment, rounds, and returns the chosen multiplier. - * - * <p> - * In most cases, this is simple. However, when rounding the number causes it to cross a multiplier boundary, we - * need to re-do the rounding. For example, to display 999,999 in Engineering notation with 2 sigfigs, first you - * guess the multiplier to be -3. However, then you end up getting 1000E3, which is not the correct output. You then - * change your multiplier to be -6, and you get 1.0E6, which is correct. - * - * @param input The quantity to process. - * @param producer Function to call to return a multiplier based on a magnitude. - * @return The number of orders of magnitude the input was adjusted by this method. - */ - int32_t - chooseMultiplierAndApply(impl::DecimalQuantity &input, const impl::MultiplierProducer &producer, - UErrorCode &status); + static IncrementPrecision constructIncrement(double increment, int32_t minFrac); - static FractionRounder constructFraction(int32_t minFrac, int32_t maxFrac); + static CurrencyPrecision constructCurrency(UCurrencyUsage usage); - static Rounder constructSignificant(int32_t minSig, int32_t maxSig); - - static Rounder - constructFractionSignificant(const FractionRounder &base, int32_t minSig, int32_t maxSig); - - static IncrementRounder constructIncrement(double increment, int32_t minFrac); - - static CurrencyRounder constructCurrency(UCurrencyUsage usage); - - static Rounder constructPassThrough(); + static Precision constructPassThrough(); // To allow MacroProps/MicroProps to initialize bogus instances: friend struct impl::MacroProps; @@ -731,31 +738,28 @@ class U_I18N_API Rounder : public UMemory { // To allow NumberPropertyMapper to create instances from DecimalFormatProperties: friend class impl::NumberPropertyMapper; - // To give access to apply() and chooseMultiplierAndApply(): - friend class impl::MutablePatternModifier; - friend class impl::LongNameHandler; - friend class impl::ScientificHandler; - friend class impl::CompactHandler; + // To allow access to the main implementation class: + friend class impl::RoundingImpl; // To allow child classes to call private methods: - friend class FractionRounder; - friend class CurrencyRounder; - friend class IncrementRounder; + friend class FractionPrecision; + friend class CurrencyPrecision; + friend class IncrementPrecision; // To allow access to the skeleton generation code: friend class impl::GeneratorHelpers; }; /** - * A class that defines a rounding strategy based on a number of fraction places and optionally significant digits to be + * A class that defines a rounding precision based on a number of fraction places and optionally significant digits to be * used when formatting numbers in NumberFormatter. * * <p> - * To create a FractionRounder, use one of the factory methods on Rounder. + * To create a FractionPrecision, use one of the factory methods on Precision. * * @draft ICU 60 */ -class U_I18N_API FractionRounder : public Rounder { +class U_I18N_API FractionPrecision : public Precision { public: /** * Ensure that no less than this number of significant digits are retained when rounding according to fraction @@ -770,10 +774,10 @@ class U_I18N_API FractionRounder : public Rounder { * * @param minSignificantDigits * The number of significant figures to guarantee. - * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. + * @return A precision for chaining or passing to the NumberFormatter precision() setter. * @draft ICU 60 */ - Rounder withMinDigits(int32_t minSignificantDigits) const; + Precision withMinDigits(int32_t minSignificantDigits) const; /** * Ensure that no more than this number of significant digits are retained when rounding according to fraction @@ -789,36 +793,36 @@ class U_I18N_API FractionRounder : public Rounder { * * @param maxSignificantDigits * Round the number to no more than this number of significant figures. - * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. + * @return A precision for chaining or passing to the NumberFormatter precision() setter. * @draft ICU 60 */ - Rounder withMaxDigits(int32_t maxSignificantDigits) const; + Precision withMaxDigits(int32_t maxSignificantDigits) const; private: // Inherit constructor - using Rounder::Rounder; + using Precision::Precision; // To allow parent class to call this class's constructor: - friend class Rounder; + friend class Precision; }; /** - * A class that defines a rounding strategy parameterized by a currency to be used when formatting numbers in + * A class that defines a rounding precision parameterized by a currency to be used when formatting numbers in * NumberFormatter. * * <p> - * To create a CurrencyRounder, use one of the factory methods on Rounder. + * To create a CurrencyPrecision, use one of the factory methods on Precision. * * @draft ICU 60 */ -class U_I18N_API CurrencyRounder : public Rounder { +class U_I18N_API CurrencyPrecision : public Precision { public: /** - * Associates a currency with this rounding strategy. + * Associates a currency with this rounding precision. * * <p> * <strong>Calling this method is <em>not required</em></strong>, because the currency specified in unit() - * is automatically applied to currency rounding strategies. However, + * is automatically applied to currency rounding precisions. However, * this method enables you to override that automatic association. * * <p> @@ -826,30 +830,30 @@ class U_I18N_API CurrencyRounder : public Rounder { * currency format. * * @param currency - * The currency to associate with this rounding strategy. - * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. + * The currency to associate with this rounding precision. + * @return A precision for chaining or passing to the NumberFormatter precision() setter. * @draft ICU 60 */ - Rounder withCurrency(const CurrencyUnit ¤cy) const; + Precision withCurrency(const CurrencyUnit ¤cy) const; private: // Inherit constructor - using Rounder::Rounder; + using Precision::Precision; // To allow parent class to call this class's constructor: - friend class Rounder; + friend class Precision; }; /** - * A class that defines a rounding strategy parameterized by a rounding increment to be used when formatting numbers in + * A class that defines a rounding precision parameterized by a rounding increment to be used when formatting numbers in * NumberFormatter. * * <p> - * To create an IncrementRounder, use one of the factory methods on Rounder. + * To create an IncrementPrecision, use one of the factory methods on Precision. * * @draft ICU 60 */ -class U_I18N_API IncrementRounder : public Rounder { +class U_I18N_API IncrementPrecision : public Precision { public: /** * Specifies the minimum number of fraction digits to render after the decimal separator, padding with zeros if @@ -863,17 +867,17 @@ class U_I18N_API IncrementRounder : public Rounder { * Note: In ICU4J, this functionality is accomplished via the scale of the BigDecimal rounding increment. * * @param minFrac The minimum number of digits after the decimal separator. - * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. + * @return A precision for chaining or passing to the NumberFormatter precision() setter. * @draft ICU 60 */ - Rounder withMinFraction(int32_t minFrac) const; + Precision withMinFraction(int32_t minFrac) const; private: // Inherit constructor - using Rounder::Rounder; + using Precision::Precision; // To allow parent class to call this class's constructor: - friend class Rounder; + friend class Precision; }; /** @@ -1350,7 +1354,10 @@ struct U_I18N_API MacroProps : public UMemory { MeasureUnit perUnit; // = NoUnit::base(); /** @internal */ - Rounder rounder; // = Rounder(); (bogus) + Precision precision; // = Precision(); (bogus) + + /** @internal */ + UNumberFormatRoundingMode roundingMode = UNUM_ROUND_HALFEVEN; /** @internal */ Grouper grouper; // = Grouper(); (bogus) @@ -1400,7 +1407,7 @@ struct U_I18N_API MacroProps : public UMemory { * @internal */ bool copyErrorTo(UErrorCode &status) const { - return notation.copyErrorTo(status) || rounder.copyErrorTo(status) || + return notation.copyErrorTo(status) || precision.copyErrorTo(status) || padder.copyErrorTo(status) || integerWidth.copyErrorTo(status) || symbols.copyErrorTo(status) || scale.copyErrorTo(status); } @@ -1426,7 +1433,7 @@ class U_I18N_API NumberFormatterSettings { * * <p> * All notation styles will be properly localized with locale data, and all notation styles are compatible with - * units, rounding strategies, and other number formatter settings. + * units, rounding precisions, and other number formatter settings. * * <p> * Pass this method the return value of a {@link Notation} factory method. For example: @@ -1466,7 +1473,7 @@ class U_I18N_API NumberFormatterSettings { * </ul> * * All units will be properly localized with locale data, and all units are compatible with notation styles, - * rounding strategies, and other number formatter settings. + * rounding precisions, and other number formatter settings. * * Pass this method any instance of {@link MeasureUnit}. For units of measure (which often involve the * factory methods that return a pointer): @@ -1602,7 +1609,7 @@ class U_I18N_API NumberFormatterSettings { Derived adoptPerUnit(icu::MeasureUnit *perUnit) &&; /** - * Specifies the rounding strategy to use when formatting numbers. + * Specifies the rounding precision to use when formatting numbers. * * <ul> * <li>Round to 3 decimal places: "3.142" @@ -1612,37 +1619,75 @@ class U_I18N_API NumberFormatterSettings { * </ul> * * <p> - * Pass this method the return value of one of the factory methods on {@link Rounder}. For example: + * Pass this method the return value of one of the factory methods on {@link Precision}. For example: * * <pre> - * NumberFormatter::with().rounding(Rounder::fixedFraction(2)) + * NumberFormatter::with().precision(Precision::fixedFraction(2)) * </pre> * * <p> * In most cases, the default rounding strategy is to round to 6 fraction places; i.e., - * <code>Rounder.maxFraction(6)</code>. The exceptions are if compact notation is being used, then the compact + * <code>Precision.maxFraction(6)</code>. The exceptions are if compact notation is being used, then the compact * notation rounding strategy is used (see {@link Notation#compactShort} for details), or if the unit is a currency, - * then standard currency rounding is used, which varies from currency to currency (see {@link Rounder#currency} for + * then standard currency rounding is used, which varies from currency to currency (see {@link Precision#currency} for * details). * - * @param rounder - * The rounding strategy to use. + * @param precision + * The rounding precision to use. * @return The fluent chain. - * @see Rounder + * @see Precision * @draft ICU 60 */ - Derived rounding(const Rounder &rounder) const &; + Derived precision(const Precision& precision) const &; /** - * Overload of rounding() for use on an rvalue reference. + * Overload of precision() for use on an rvalue reference. * - * @param rounder - * The rounding strategy to use. + * @param precision + * The rounding precision to use. * @return The fluent chain. - * @see #rounding + * @see #precision * @draft ICU 62 */ - Derived rounding(const Rounder& rounder) &&; + Derived precision(const Precision& precision) &&; + + // Compatibility method that will be removed in ICU 64. + // Use precision() instead. + // See http://bugs.icu-project.org/trac/ticket/13746 + /** @deprecated ICU 62 */ + Derived rounding(const Rounder& rounder) const & { + return precision(rounder); + } + + /** + * Specifies how to determine the direction to round a number when it has more digits than fit in the + * desired precision. When formatting 1.235: + * + * <ul> + * <li>Ceiling rounding mode with integer precision: "2" + * <li>Half-down rounding mode with 2 fixed fraction digits: "1.23" + * <li>Half-up rounding mode with 2 fixed fraction digits: "1.24" + * </ul> + * + * The default is HALF_EVEN. For more information on rounding mode, see the ICU userguide here: + * + * http://userguide.icu-project.org/formatparse/numbers/rounding-modes + * + * @param roundingMode The rounding mode to use. + * @return The fluent chain. + * @draft ICU 62 + */ + Derived roundingMode(UNumberFormatRoundingMode roundingMode) const &; + + /** + * Overload of roundingMode() for use on an rvalue reference. + * + * @param roundingMode The rounding mode to use. + * @return The fluent chain. + * @see #roundingMode + * @draft ICU 62 + */ + Derived roundingMode(UNumberFormatRoundingMode roundingMode) &&; /** * Specifies the grouping strategy to use when formatting numbers. diff --git a/icu4c/source/i18n/unicode/unumberformatter.h b/icu4c/source/i18n/unicode/unumberformatter.h index e73ab827bbc..2212f8481d0 100644 --- a/icu4c/source/i18n/unicode/unumberformatter.h +++ b/icu4c/source/i18n/unicode/unumberformatter.h @@ -30,7 +30,7 @@ * <pre> * // Setup: * UErrorCode ec = U_ZERO_ERROR; - * UNumberFormatter* uformatter = unumf_openFromSkeletonAndLocale(u"round-integer", -1, "en", &ec); + * UNumberFormatter* uformatter = unumf_openFromSkeletonAndLocale(u"precision-integer", -1, "en", &ec); * UFormattedNumber* uresult = unumf_openResult(&ec); * if (U_FAILURE(ec)) { return; } * @@ -423,7 +423,7 @@ typedef struct UFormattedNumber UFormattedNumber; * * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. * - * @param skeleton The skeleton string, like u"percent round-integer" + * @param skeleton The skeleton string, like u"percent precision-integer" * @param skeletonLen The number of UChars in the skeleton string, or -1 it it is NUL-terminated. * @param locale The NUL-terminated locale ID. * @param ec Set if an error occurs. diff --git a/icu4c/source/test/cintltst/unumberformattertst.c b/icu4c/source/test/cintltst/unumberformattertst.c index 0b35c463328..a8d2fc7f585 100644 --- a/icu4c/source/test/cintltst/unumberformattertst.c +++ b/icu4c/source/test/cintltst/unumberformattertst.c @@ -38,7 +38,7 @@ static void TestSkeletonFormatToString() { // setup: UNumberFormatter* f = unumf_openFromSkeletonAndLocale( - u"round-integer currency/USD sign-accounting", -1, "en", &ec); + u"precision-integer currency/USD sign-accounting", -1, "en", &ec); assertSuccess("Should create without error", &ec); UFormattedNumber* result = unumf_openResult(&ec); assertSuccess("Should create result without error", &ec); @@ -153,7 +153,7 @@ static void TestExampleCode() { // Setup: UErrorCode ec = U_ZERO_ERROR; - UNumberFormatter* uformatter = unumf_openFromSkeletonAndLocale(u"round-integer", -1, "en", &ec); + UNumberFormatter* uformatter = unumf_openFromSkeletonAndLocale(u"precision-integer", -1, "en", &ec); UFormattedNumber* uresult = unumf_openResult(&ec); assertSuccessCheck("There should not be a failure in the example code", &ec, TRUE); if (U_FAILURE(ec)) { return; } diff --git a/icu4c/source/test/intltest/numbertest_affixutils.cpp b/icu4c/source/test/intltest/numbertest_affixutils.cpp index 1815f8ed991..d72991a042f 100644 --- a/icu4c/source/test/intltest/numbertest_affixutils.cpp +++ b/icu4c/source/test/intltest/numbertest_affixutils.cpp @@ -18,7 +18,7 @@ class DefaultSymbolProvider : public SymbolProvider { public: DefaultSymbolProvider(UErrorCode &status) : fSymbols(Locale("ar_SA"), status) {} - virtual UnicodeString getSymbol(AffixPatternType type) const U_OVERRIDE { + UnicodeString getSymbol(AffixPatternType type) const U_OVERRIDE { switch (type) { case TYPE_MINUS_SIGN: return u"−"; diff --git a/icu4c/source/test/intltest/numbertest_api.cpp b/icu4c/source/test/intltest/numbertest_api.cpp index 4947af33a7f..95a5114ea31 100644 --- a/icu4c/source/test/intltest/numbertest_api.cpp +++ b/icu4c/source/test/intltest/numbertest_api.cpp @@ -533,7 +533,7 @@ void NumberFormatterApiTest::unitMeasure() { assertFormatSingle( u"MeasureUnit form without {0} in CLDR pattern and wide base form", u"measure-unit/temperature-kelvin .00000000000000000000 unit-width-full-name", - NumberFormatter::with().rounding(Rounder::fixedFraction(20)) + NumberFormatter::with().precision(Precision::fixedFraction(20)) .unit(KELVIN) .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME), Locale("es-MX"), @@ -792,8 +792,8 @@ void NumberFormatterApiTest::unitPercent() { void NumberFormatterApiTest::roundingFraction() { assertFormatDescending( u"Integer", - u"round-integer", - NumberFormatter::with().rounding(Rounder::integer()), + u"precision-integer", + NumberFormatter::with().precision(Precision::integer()), Locale::getEnglish(), u"87,650", u"8,765", @@ -808,7 +808,7 @@ void NumberFormatterApiTest::roundingFraction() { assertFormatDescending( u"Fixed Fraction", u".000", - NumberFormatter::with().rounding(Rounder::fixedFraction(3)), + NumberFormatter::with().precision(Precision::fixedFraction(3)), Locale::getEnglish(), u"87,650.000", u"8,765.000", @@ -823,7 +823,7 @@ void NumberFormatterApiTest::roundingFraction() { assertFormatDescending( u"Min Fraction", u".0+", - NumberFormatter::with().rounding(Rounder::minFraction(1)), + NumberFormatter::with().precision(Precision::minFraction(1)), Locale::getEnglish(), u"87,650.0", u"8,765.0", @@ -838,7 +838,7 @@ void NumberFormatterApiTest::roundingFraction() { assertFormatDescending( u"Max Fraction", u".#", - NumberFormatter::with().rounding(Rounder::maxFraction(1)), + NumberFormatter::with().precision(Precision::maxFraction(1)), Locale::getEnglish(), u"87,650", u"8,765", @@ -853,7 +853,7 @@ void NumberFormatterApiTest::roundingFraction() { assertFormatDescending( u"Min/Max Fraction", u".0##", - NumberFormatter::with().rounding(Rounder::minMaxFraction(1, 3)), + NumberFormatter::with().precision(Precision::minMaxFraction(1, 3)), Locale::getEnglish(), u"87,650.0", u"8,765.0", @@ -870,7 +870,7 @@ void NumberFormatterApiTest::roundingFigures() { assertFormatSingle( u"Fixed Significant", u"@@@", - NumberFormatter::with().rounding(Rounder::fixedDigits(3)), + NumberFormatter::with().precision(Precision::fixedSignificantDigits(3)), Locale::getEnglish(), -98, u"-98.0"); @@ -878,7 +878,7 @@ void NumberFormatterApiTest::roundingFigures() { assertFormatSingle( u"Fixed Significant Rounding", u"@@@", - NumberFormatter::with().rounding(Rounder::fixedDigits(3)), + NumberFormatter::with().precision(Precision::fixedSignificantDigits(3)), Locale::getEnglish(), -98.7654321, u"-98.8"); @@ -886,7 +886,7 @@ void NumberFormatterApiTest::roundingFigures() { assertFormatSingle( u"Fixed Significant Zero", u"@@@", - NumberFormatter::with().rounding(Rounder::fixedDigits(3)), + NumberFormatter::with().precision(Precision::fixedSignificantDigits(3)), Locale::getEnglish(), 0, u"0.00"); @@ -894,7 +894,7 @@ void NumberFormatterApiTest::roundingFigures() { assertFormatSingle( u"Min Significant", u"@@+", - NumberFormatter::with().rounding(Rounder::minDigits(2)), + NumberFormatter::with().precision(Precision::minSignificantDigits(2)), Locale::getEnglish(), -9, u"-9.0"); @@ -902,7 +902,7 @@ void NumberFormatterApiTest::roundingFigures() { assertFormatSingle( u"Max Significant", u"@###", - NumberFormatter::with().rounding(Rounder::maxDigits(4)), + NumberFormatter::with().precision(Precision::maxSignificantDigits(4)), Locale::getEnglish(), 98.7654321, u"98.77"); @@ -910,7 +910,7 @@ void NumberFormatterApiTest::roundingFigures() { assertFormatSingle( u"Min/Max Significant", u"@@@#", - NumberFormatter::with().rounding(Rounder::minMaxDigits(3, 4)), + NumberFormatter::with().precision(Precision::minMaxSignificantDigits(3, 4)), Locale::getEnglish(), 9.99999, u"10.0"); @@ -918,7 +918,7 @@ void NumberFormatterApiTest::roundingFigures() { assertFormatSingle( u"Fixed Significant on zero with lots of integer width", u"@ integer-width/+000", - NumberFormatter::with().rounding(Rounder::fixedDigits(1)) + NumberFormatter::with().precision(Precision::fixedSignificantDigits(1)) .integerWidth(IntegerWidth::zeroFillTo(3)), Locale::getEnglish(), 0, @@ -927,7 +927,7 @@ void NumberFormatterApiTest::roundingFigures() { assertFormatSingle( u"Fixed Significant on zero with zero integer width", u"@ integer-width/+", - NumberFormatter::with().rounding(Rounder::fixedDigits(1)) + NumberFormatter::with().precision(Precision::fixedSignificantDigits(1)) .integerWidth(IntegerWidth::zeroFillTo(0)), Locale::getEnglish(), 0, @@ -938,7 +938,7 @@ void NumberFormatterApiTest::roundingFractionFigures() { assertFormatDescending( u"Basic Significant", // for comparison u"@#", - NumberFormatter::with().rounding(Rounder::maxDigits(2)), + NumberFormatter::with().precision(Precision::maxSignificantDigits(2)), Locale::getEnglish(), u"88,000", u"8,800", @@ -953,7 +953,7 @@ void NumberFormatterApiTest::roundingFractionFigures() { assertFormatDescending( u"FracSig minMaxFrac minSig", u".0#/@@@+", - NumberFormatter::with().rounding(Rounder::minMaxFraction(1, 2).withMinDigits(3)), + NumberFormatter::with().precision(Precision::minMaxFraction(1, 2).withMinDigits(3)), Locale::getEnglish(), u"87,650.0", u"8,765.0", @@ -968,7 +968,7 @@ void NumberFormatterApiTest::roundingFractionFigures() { assertFormatDescending( u"FracSig minMaxFrac maxSig A", u".0##/@#", - NumberFormatter::with().rounding(Rounder::minMaxFraction(1, 3).withMaxDigits(2)), + NumberFormatter::with().precision(Precision::minMaxFraction(1, 3).withMaxDigits(2)), Locale::getEnglish(), u"88,000.0", // maxSig beats maxFrac u"8,800.0", // maxSig beats maxFrac @@ -983,7 +983,7 @@ void NumberFormatterApiTest::roundingFractionFigures() { assertFormatDescending( u"FracSig minMaxFrac maxSig B", u".00/@#", - NumberFormatter::with().rounding(Rounder::fixedFraction(2).withMaxDigits(2)), + NumberFormatter::with().precision(Precision::fixedFraction(2).withMaxDigits(2)), Locale::getEnglish(), u"88,000.00", // maxSig beats maxFrac u"8,800.00", // maxSig beats maxFrac @@ -998,7 +998,7 @@ void NumberFormatterApiTest::roundingFractionFigures() { assertFormatSingle( u"FracSig with trailing zeros A", u".00/@@@+", - NumberFormatter::with().rounding(Rounder::fixedFraction(2).withMinDigits(3)), + NumberFormatter::with().precision(Precision::fixedFraction(2).withMinDigits(3)), Locale::getEnglish(), 0.1, u"0.10"); @@ -1006,7 +1006,7 @@ void NumberFormatterApiTest::roundingFractionFigures() { assertFormatSingle( u"FracSig with trailing zeros B", u".00/@@@+", - NumberFormatter::with().rounding(Rounder::fixedFraction(2).withMinDigits(3)), + NumberFormatter::with().precision(Precision::fixedFraction(2).withMinDigits(3)), Locale::getEnglish(), 0.0999999, u"0.10"); @@ -1015,8 +1015,8 @@ void NumberFormatterApiTest::roundingFractionFigures() { void NumberFormatterApiTest::roundingOther() { assertFormatDescending( u"Rounding None", - u"round-unlimited", - NumberFormatter::with().rounding(Rounder::unlimited()), + u"precision-unlimited", + NumberFormatter::with().precision(Precision::unlimited()), Locale::getEnglish(), u"87,650", u"8,765", @@ -1030,8 +1030,8 @@ void NumberFormatterApiTest::roundingOther() { assertFormatDescending( u"Increment", - u"round-increment/0.5", - NumberFormatter::with().rounding(Rounder::increment(0.5).withMinFraction(1)), + u"precision-increment/0.5", + NumberFormatter::with().precision(Precision::increment(0.5).withMinFraction(1)), Locale::getEnglish(), u"87,650.0", u"8,765.0", @@ -1045,8 +1045,8 @@ void NumberFormatterApiTest::roundingOther() { assertFormatDescending( u"Increment with Min Fraction", - u"round-increment/0.50", - NumberFormatter::with().rounding(Rounder::increment(0.5).withMinFraction(2)), + u"precision-increment/0.50", + NumberFormatter::with().precision(Precision::increment(0.5).withMinFraction(2)), Locale::getEnglish(), u"87,650.00", u"8,765.00", @@ -1060,8 +1060,8 @@ void NumberFormatterApiTest::roundingOther() { assertFormatDescending( u"Currency Standard", - u"currency/CZK round-currency-standard", - NumberFormatter::with().rounding(Rounder::currency(UCurrencyUsage::UCURR_USAGE_STANDARD)) + u"currency/CZK precision-currency-standard", + NumberFormatter::with().precision(Precision::currency(UCurrencyUsage::UCURR_USAGE_STANDARD)) .unit(CZK), Locale::getEnglish(), u"CZK 87,650.00", @@ -1076,8 +1076,8 @@ void NumberFormatterApiTest::roundingOther() { assertFormatDescending( u"Currency Cash", - u"currency/CZK round-currency-cash", - NumberFormatter::with().rounding(Rounder::currency(UCurrencyUsage::UCURR_USAGE_CASH)) + u"currency/CZK precision-currency-cash", + NumberFormatter::with().precision(Precision::currency(UCurrencyUsage::UCURR_USAGE_CASH)) .unit(CZK), Locale::getEnglish(), u"CZK 87,650", @@ -1092,8 +1092,8 @@ void NumberFormatterApiTest::roundingOther() { assertFormatDescending( u"Currency Cash with Nickel Rounding", - u"currency/CAD round-currency-cash", - NumberFormatter::with().rounding(Rounder::currency(UCurrencyUsage::UCURR_USAGE_CASH)) + u"currency/CAD precision-currency-cash", + NumberFormatter::with().precision(Precision::currency(UCurrencyUsage::UCURR_USAGE_CASH)) .unit(CAD), Locale::getEnglish(), u"CA$87,650.00", @@ -1108,9 +1108,9 @@ void NumberFormatterApiTest::roundingOther() { assertFormatDescending( u"Currency not in top-level fluent chain", - u"round-integer", // calling .withCurrency() applies currency rounding rules immediately - NumberFormatter::with().rounding( - Rounder::currency(UCurrencyUsage::UCURR_USAGE_CASH).withCurrency(CZK)), + u"precision-integer", // calling .withCurrency() applies currency rounding rules immediately + NumberFormatter::with().precision( + Precision::currency(UCurrencyUsage::UCURR_USAGE_CASH).withCurrency(CZK)), Locale::getEnglish(), u"87,650", u"8,765", @@ -1125,8 +1125,8 @@ void NumberFormatterApiTest::roundingOther() { // NOTE: Other tests cover the behavior of the other rounding modes. assertFormatDescending( u"Rounding Mode CEILING", - u"round-integer/ceiling", - NumberFormatter::with().rounding(Rounder::integer().withMode(UNumberFormatRoundingMode::UNUM_ROUND_CEILING)), + u"precision-integer rounding-mode-ceiling", + NumberFormatter::with().precision(Precision::integer()).roundingMode(UNUM_ROUND_CEILING), Locale::getEnglish(), u"87,650", u"8,765", @@ -2063,7 +2063,7 @@ void NumberFormatterApiTest::formatTypes() { // The number needs to have exactly 40 digits, which is the size of the default buffer. // (issue discovered by the address sanitizer in C++) static const char* str = "0.009876543210987654321098765432109876543211"; - actual = formatter.rounding(Rounder::unlimited()).formatDecimal(str, status).toString(); + actual = formatter.precision(Precision::unlimited()).formatDecimal(str, status).toString(); assertEquals("Format decNumber to 40 digits", str, actual); } @@ -2156,8 +2156,8 @@ void NumberFormatterApiTest::fieldPosition() { } void NumberFormatterApiTest::errors() { - LocalizedNumberFormatter lnf = NumberFormatter::withLocale(Locale::getEnglish()).rounding( - Rounder::fixedFraction( + LocalizedNumberFormatter lnf = NumberFormatter::withLocale(Locale::getEnglish()).precision( + Precision::fixedFraction( -1)); // formatInt @@ -2278,16 +2278,16 @@ void NumberFormatterApiTest::validRanges() { } \ } - VALID_RANGE_ONEARG(rounding, Rounder::fixedFraction, 0); - VALID_RANGE_ONEARG(rounding, Rounder::minFraction, 0); - VALID_RANGE_ONEARG(rounding, Rounder::maxFraction, 0); - VALID_RANGE_TWOARGS(rounding, Rounder::minMaxFraction, 0); - VALID_RANGE_ONEARG(rounding, Rounder::fixedDigits, 1); - VALID_RANGE_ONEARG(rounding, Rounder::minDigits, 1); - VALID_RANGE_ONEARG(rounding, Rounder::maxDigits, 1); - VALID_RANGE_TWOARGS(rounding, Rounder::minMaxDigits, 1); - VALID_RANGE_ONEARG(rounding, Rounder::fixedFraction(1).withMinDigits, 1); - VALID_RANGE_ONEARG(rounding, Rounder::fixedFraction(1).withMaxDigits, 1); + VALID_RANGE_ONEARG(rounding, Precision::fixedFraction, 0); + VALID_RANGE_ONEARG(rounding, Precision::minFraction, 0); + VALID_RANGE_ONEARG(rounding, Precision::maxFraction, 0); + VALID_RANGE_TWOARGS(rounding, Precision::minMaxFraction, 0); + VALID_RANGE_ONEARG(rounding, Precision::fixedSignificantDigits, 1); + VALID_RANGE_ONEARG(rounding, Precision::minSignificantDigits, 1); + VALID_RANGE_ONEARG(rounding, Precision::maxSignificantDigits, 1); + VALID_RANGE_TWOARGS(rounding, Precision::minMaxSignificantDigits, 1); + VALID_RANGE_ONEARG(rounding, Precision::fixedFraction(1).withMinDigits, 1); + VALID_RANGE_ONEARG(rounding, Precision::fixedFraction(1).withMaxDigits, 1); VALID_RANGE_ONEARG(notation, Notation::scientific().withMinExponentDigits, 1); VALID_RANGE_ONEARG(integerWidth, IntegerWidth::zeroFillTo, 0); VALID_RANGE_ONEARG(integerWidth, IntegerWidth::zeroFillTo(0).truncateAt, -1); diff --git a/icu4c/source/test/intltest/numbertest_skeletons.cpp b/icu4c/source/test/intltest/numbertest_skeletons.cpp index f5ea2a9d076..fa71f34a669 100644 --- a/icu4c/source/test/intltest/numbertest_skeletons.cpp +++ b/icu4c/source/test/intltest/numbertest_skeletons.cpp @@ -34,8 +34,8 @@ void NumberSkeletonTest::validTokens() { // This tests only if the tokens are valid, not their behavior. // Most of these are from the design doc. static const char16_t* cases[] = { - u"round-integer", - u"round-unlimited", + u"precision-integer", + u"precision-unlimited", u"@@@##", u"@@+", u".000##", @@ -45,11 +45,11 @@ void NumberSkeletonTest::validTokens() { u".######", u".00/@@+", u".00/@##", - u"round-increment/3.14", - u"round-currency-standard", - u"round-integer/half-up", - u".00#/ceiling", - u".00/@@+/floor", + u"precision-increment/3.14", + u"precision-currency-standard", + u"precision-integer rounding-mode-half-up", + u".00# rounding-mode-ceiling", + u".00/@@+ rounding-mode-floor", u"scientific", u"scientific/+ee", u"scientific/sign-always", @@ -99,9 +99,9 @@ void NumberSkeletonTest::validTokens() { u"latin", u"numbering-system/arab", u"numbering-system/latn", - u"round-integer/@##", - u"round-integer/ceiling", - u"round-currency-cash/ceiling"}; + u"precision-integer/@##", + u"precision-integer rounding-mode-ceiling", + u"precision-currency-cash rounding-mode-ceiling"}; for (auto& cas : cases) { UnicodeString skeletonString(cas); @@ -127,12 +127,11 @@ void NumberSkeletonTest::invalidTokens() { u".00/@@#", u".00/@@#+", u".00/floor/@@+", // wrong order - u"round-increment/français", // non-invariant characters for C++ - u"round-currency-cash/XXX", + u"precision-increment/français", // non-invariant characters for C++ u"scientific/ee", - u"round-increment/xxx", - u"round-increment/NaN", - u"round-increment/0.1.2", + u"precision-increment/xxx", + u"precision-increment/NaN", + u"precision-increment/0.1.2", u"scale/xxx", u"scale/NaN", u"scale/0.1.2", @@ -164,20 +163,20 @@ void NumberSkeletonTest::unknownTokens() { void NumberSkeletonTest::unexpectedTokens() { static const char16_t* cases[] = { u"group-thousands/foo", - u"round-integer//ceiling group-off", - u"round-integer//ceiling group-off", - u"round-integer/ group-off", - u"round-integer// group-off"}; + u"precision-integer//@## group-off", + u"precision-integer//@## group-off", + u"precision-integer/ group-off", + u"precision-integer// group-off"}; expectedErrorSkeleton(cases, sizeof(cases) / sizeof(*cases)); } void NumberSkeletonTest::duplicateValues() { static const char16_t* cases[] = { - u"round-integer round-integer", - u"round-integer .00+", - u"round-integer round-unlimited", - u"round-integer @@@", + u"precision-integer precision-integer", + u"precision-integer .00+", + u"precision-integer precision-unlimited", + u"precision-integer @@@", u"scientific engineering", u"engineering compact-long", u"sign-auto sign-always"}; @@ -187,14 +186,14 @@ void NumberSkeletonTest::duplicateValues() { void NumberSkeletonTest::stemsRequiringOption() { static const char16_t* stems[] = { - u"round-increment", + u"precision-increment", u"measure-unit", u"per-unit", u"currency", u"integer-width", u"numbering-system", u"scale"}; - static const char16_t* suffixes[] = {u"", u"/ceiling", u" scientific", u"/ceiling scientific"}; + static const char16_t* suffixes[] = {u"", u"/@##", u" scientific", u"/@## scientific"}; for (auto& stem : stems) { for (auto& suffix : suffixes) { @@ -234,10 +233,10 @@ void NumberSkeletonTest::flexibleSeparators() { static struct TestCase { const char16_t* skeleton; const char16_t* expected; - } cases[] = {{u"round-integer group-off", u"5142"}, - {u"round-integer group-off", u"5142"}, - {u"round-integer/ceiling group-off", u"5143"}, - {u"round-integer/ceiling group-off", u"5143"}}; + } cases[] = {{u"precision-integer group-off", u"5142"}, + {u"precision-integer group-off", u"5142"}, + {u"precision-integer/@## group-off", u"5140"}, + {u"precision-integer/@## group-off", u"5140"}}; for (auto& cas : cases) { UnicodeString skeletonString(cas.skeleton); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java index 63a11f48932..bb86ba1ef5b 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java @@ -277,7 +277,7 @@ public class LongNameHandler implements MicroPropsGenerator { MicroProps micros = parent.processQuantity(quantity); // TODO: Avoid the copy here? DecimalQuantity copy = quantity.createCopy(); - micros.rounding.apply(copy); + micros.rounder.apply(copy); micros.modOuter = modifiers.get(copy.getStandardPlural(rules)); return micros; } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MacroProps.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MacroProps.java index 9cb89879e97..84f8fd86972 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MacroProps.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MacroProps.java @@ -2,14 +2,16 @@ // License & terms of use: http://www.unicode.org/copyright.html#License package com.ibm.icu.impl.number; +import java.math.RoundingMode; + import com.ibm.icu.impl.Utility; import com.ibm.icu.number.IntegerWidth; -import com.ibm.icu.number.Scale; import com.ibm.icu.number.Notation; import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay; import com.ibm.icu.number.NumberFormatter.SignDisplay; import com.ibm.icu.number.NumberFormatter.UnitWidth; -import com.ibm.icu.number.Rounder; +import com.ibm.icu.number.Precision; +import com.ibm.icu.number.Scale; import com.ibm.icu.text.PluralRules; import com.ibm.icu.util.MeasureUnit; import com.ibm.icu.util.ULocale; @@ -18,7 +20,8 @@ public class MacroProps implements Cloneable { public Notation notation; public MeasureUnit unit; public MeasureUnit perUnit; - public Rounder rounder; + public Precision precision; + public RoundingMode roundingMode; public Object grouping; public Padder padder; public IntegerWidth integerWidth; @@ -45,8 +48,10 @@ public class MacroProps implements Cloneable { unit = fallback.unit; if (perUnit == null) perUnit = fallback.perUnit; - if (rounder == null) - rounder = fallback.rounder; + if (precision == null) + precision = fallback.precision; + if (roundingMode == null) + roundingMode = fallback.roundingMode; if (grouping == null) grouping = fallback.grouping; if (padder == null) @@ -76,7 +81,8 @@ public class MacroProps implements Cloneable { return Utility.hash(notation, unit, perUnit, - rounder, + precision, + roundingMode, grouping, padder, integerWidth, @@ -102,7 +108,8 @@ public class MacroProps implements Cloneable { return Utility.equals(notation, other.notation) && Utility.equals(unit, other.unit) && Utility.equals(perUnit, other.perUnit) - && Utility.equals(rounder, other.rounder) + && Utility.equals(precision, other.precision) + && Utility.equals(roundingMode, other.roundingMode) && Utility.equals(grouping, other.grouping) && Utility.equals(padder, other.padder) && Utility.equals(integerWidth, other.integerWidth) diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MicroProps.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MicroProps.java index d5e3ba44f36..6aeda32775e 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MicroProps.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MicroProps.java @@ -5,7 +5,7 @@ package com.ibm.icu.impl.number; import com.ibm.icu.number.IntegerWidth; import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay; import com.ibm.icu.number.NumberFormatter.SignDisplay; -import com.ibm.icu.number.Rounder; +import com.ibm.icu.number.Precision; import com.ibm.icu.text.DecimalFormatSymbols; public class MicroProps implements Cloneable, MicroPropsGenerator { @@ -20,7 +20,7 @@ public class MicroProps implements Cloneable, MicroPropsGenerator { public Modifier modOuter; public Modifier modMiddle; public Modifier modInner; - public Rounder rounding; + public Precision rounder; public Grouper grouping; public boolean useCurrency; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MutablePatternModifier.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MutablePatternModifier.java index 801af947f0e..13d0ab2de10 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MutablePatternModifier.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MutablePatternModifier.java @@ -269,7 +269,7 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, MicroPr if (needsPlurals()) { // TODO: Fix this. Avoid the copy. DecimalQuantity copy = fq.createCopy(); - micros.rounding.apply(copy); + micros.rounder.apply(copy); setNumberProperties(fq.signum(), copy.getStandardPlural(rules)); } else { setNumberProperties(fq.signum(), null); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/CompactNotation.java b/icu4j/main/classes/core/src/com/ibm/icu/number/CompactNotation.java index 5219f0091dc..31d1a8404fc 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/CompactNotation.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/CompactNotation.java @@ -119,16 +119,16 @@ public class CompactNotation extends Notation { @Override public MicroProps processQuantity(DecimalQuantity quantity) { MicroProps micros = parent.processQuantity(quantity); - assert micros.rounding != null; + assert micros.rounder != null; // Treat zero as if it had magnitude 0 int magnitude; if (quantity.isZero()) { magnitude = 0; - micros.rounding.apply(quantity); + micros.rounder.apply(quantity); } else { // TODO: Revisit chooseMultiplierAndApply - int multiplier = micros.rounding.chooseMultiplierAndApply(quantity, data); + int multiplier = micros.rounder.chooseMultiplierAndApply(quantity, data); magnitude = quantity.isZero() ? 0 : quantity.getMagnitude(); magnitude -= multiplier; } @@ -152,7 +152,7 @@ public class CompactNotation extends Notation { } // We already performed rounding. Do not perform it again. - micros.rounding = Rounder.constructPassThrough(); + micros.rounder = Precision.constructPassThrough(); return micros; } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/CurrencyPrecision.java b/icu4j/main/classes/core/src/com/ibm/icu/number/CurrencyPrecision.java new file mode 100644 index 00000000000..09f3f5d1a45 --- /dev/null +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/CurrencyPrecision.java @@ -0,0 +1,49 @@ +// © 2017 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html#License +package com.ibm.icu.number; + +import com.ibm.icu.util.Currency; + +/** + * A class that defines a rounding strategy parameterized by a currency to be used when formatting + * numbers in NumberFormatter. + * + * <p> + * To create a CurrencyPrecision, use one of the factory methods on Precision. + * + * @draft ICU 60 + * @provisional This API might change or be removed in a future release. + * @see NumberFormatter + */ +public abstract class CurrencyPrecision extends Precision { + + /* package-private */ CurrencyPrecision() { + } + + /** + * Associates a currency with this rounding strategy. + * + * <p> + * <strong>Calling this method is <em>not required</em></strong>, because the currency specified in + * unit() or via a CurrencyAmount passed into format(Measure) is automatically applied to currency + * rounding strategies. However, this method enables you to override that automatic association. + * + * <p> + * This method also enables numbers to be formatted using currency rounding rules without explicitly + * using a currency format. + * + * @param currency + * The currency to associate with this rounding strategy. + * @return A Precision for chaining or passing to the NumberFormatter rounding() setter. + * @draft ICU 60 + * @provisional This API might change or be removed in a future release. + * @see NumberFormatter + */ + public Precision withCurrency(Currency currency) { + if (currency != null) { + return constructFromCurrency(this, currency); + } else { + throw new IllegalArgumentException("Currency must not be null"); + } + }; +} \ No newline at end of file diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/CurrencyRounder.java b/icu4j/main/classes/core/src/com/ibm/icu/number/CurrencyRounder.java index dd0d0f02ab3..44740512667 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/CurrencyRounder.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/CurrencyRounder.java @@ -2,48 +2,10 @@ // License & terms of use: http://www.unicode.org/copyright.html#License package com.ibm.icu.number; -import com.ibm.icu.util.Currency; - /** - * A class that defines a rounding strategy parameterized by a currency to be used when formatting - * numbers in NumberFormatter. - * - * <p> - * To create a CurrencyRounder, use one of the factory methods on Rounder. - * - * @draft ICU 60 - * @provisional This API might change or be removed in a future release. - * @see NumberFormatter + * @deprecated ICU 62 Use {@link CurrencyPrecision} instead. This class is for backwards compatibility + * and will be removed in ICU 64. See http://bugs.icu-project.org/trac/ticket/13746 */ -public abstract class CurrencyRounder extends Rounder { - - /* package-private */ CurrencyRounder() { - } - - /** - * Associates a currency with this rounding strategy. - * - * <p> - * <strong>Calling this method is <em>not required</em></strong>, because the currency specified in - * unit() or via a CurrencyAmount passed into format(Measure) is automatically applied to currency - * rounding strategies. However, this method enables you to override that automatic association. - * - * <p> - * This method also enables numbers to be formatted using currency rounding rules without explicitly - * using a currency format. - * - * @param currency - * The currency to associate with this rounding strategy. - * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. - * @draft ICU 60 - * @provisional This API might change or be removed in a future release. - * @see NumberFormatter - */ - public Rounder withCurrency(Currency currency) { - if (currency != null) { - return constructFromCurrency(this, currency); - } else { - throw new IllegalArgumentException("Currency must not be null"); - } - }; +@Deprecated +public abstract class CurrencyRounder extends CurrencyPrecision { } \ No newline at end of file diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/FractionPrecision.java b/icu4j/main/classes/core/src/com/ibm/icu/number/FractionPrecision.java new file mode 100644 index 00000000000..882a6c51550 --- /dev/null +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/FractionPrecision.java @@ -0,0 +1,80 @@ +// © 2017 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html#License +package com.ibm.icu.number; + +import com.ibm.icu.impl.number.RoundingUtils; + +/** + * A class that defines a rounding strategy based on a number of fraction places and optionally + * significant digits to be used when formatting numbers in NumberFormatter. + * + * <p> + * To create a FractionPrecision, use one of the factory methods on Precision. + * + * @draft ICU 60 + * @provisional This API might change or be removed in a future release. + * @see NumberFormatter + */ +public abstract class FractionPrecision extends Precision { + + /* package-private */ FractionPrecision() { + } + + /** + * Ensure that no less than this number of significant digits are retained when rounding according to + * fraction rules. + * + * <p> + * For example, with integer rounding, the number 3.141 becomes "3". However, with minimum figures + * set to 2, 3.141 becomes "3.1" instead. + * + * <p> + * This setting does not affect the number of trailing zeros. For example, 3.01 would print as "3", + * not "3.0". + * + * @param minSignificantDigits + * The number of significant figures to guarantee. + * @return A Precision for chaining or passing to the NumberFormatter rounding() setter. + * @draft ICU 60 + * @provisional This API might change or be removed in a future release. + * @see NumberFormatter + */ + public Precision withMinDigits(int minSignificantDigits) { + if (minSignificantDigits >= 1 && minSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) { + return constructFractionSignificant(this, minSignificantDigits, -1); + } else { + throw new IllegalArgumentException("Significant digits must be between 1 and " + + RoundingUtils.MAX_INT_FRAC_SIG + + " (inclusive)"); + } + } + + /** + * Ensure that no more than this number of significant digits are retained when rounding according to + * fraction rules. + * + * <p> + * For example, with integer rounding, the number 123.4 becomes "123". However, with maximum figures + * set to 2, 123.4 becomes "120" instead. + * + * <p> + * This setting does not affect the number of trailing zeros. For example, with fixed fraction of 2, + * 123.4 would become "120.00". + * + * @param maxSignificantDigits + * Round the number to no more than this number of significant figures. + * @return A Precision for chaining or passing to the NumberFormatter rounding() setter. + * @draft ICU 60 + * @provisional This API might change or be removed in a future release. + * @see NumberFormatter + */ + public Precision withMaxDigits(int maxSignificantDigits) { + if (maxSignificantDigits >= 1 && maxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) { + return constructFractionSignificant(this, -1, maxSignificantDigits); + } else { + throw new IllegalArgumentException("Significant digits must be between 1 and " + + RoundingUtils.MAX_INT_FRAC_SIG + + " (inclusive)"); + } + } +} \ No newline at end of file diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/FractionRounder.java b/icu4j/main/classes/core/src/com/ibm/icu/number/FractionRounder.java index 61ed736cb7e..56e91503070 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/FractionRounder.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/FractionRounder.java @@ -2,79 +2,10 @@ // License & terms of use: http://www.unicode.org/copyright.html#License package com.ibm.icu.number; -import com.ibm.icu.impl.number.RoundingUtils; - /** - * A class that defines a rounding strategy based on a number of fraction places and optionally - * significant digits to be used when formatting numbers in NumberFormatter. - * - * <p> - * To create a FractionRounder, use one of the factory methods on Rounder. - * - * @draft ICU 60 - * @provisional This API might change or be removed in a future release. - * @see NumberFormatter + * @deprecated ICU 62 Use {@link FractionPrecision} instead. This class is for backwards compatibility + * and will be removed in ICU 64. See http://bugs.icu-project.org/trac/ticket/13746 */ -public abstract class FractionRounder extends Rounder { - - /* package-private */ FractionRounder() { - } - - /** - * Ensure that no less than this number of significant digits are retained when rounding according to - * fraction rules. - * - * <p> - * For example, with integer rounding, the number 3.141 becomes "3". However, with minimum figures - * set to 2, 3.141 becomes "3.1" instead. - * - * <p> - * This setting does not affect the number of trailing zeros. For example, 3.01 would print as "3", - * not "3.0". - * - * @param minSignificantDigits - * The number of significant figures to guarantee. - * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. - * @draft ICU 60 - * @provisional This API might change or be removed in a future release. - * @see NumberFormatter - */ - public Rounder withMinDigits(int minSignificantDigits) { - if (minSignificantDigits >= 1 && minSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) { - return constructFractionSignificant(this, minSignificantDigits, -1); - } else { - throw new IllegalArgumentException("Significant digits must be between 1 and " - + RoundingUtils.MAX_INT_FRAC_SIG - + " (inclusive)"); - } - } - - /** - * Ensure that no more than this number of significant digits are retained when rounding according to - * fraction rules. - * - * <p> - * For example, with integer rounding, the number 123.4 becomes "123". However, with maximum figures - * set to 2, 123.4 becomes "120" instead. - * - * <p> - * This setting does not affect the number of trailing zeros. For example, with fixed fraction of 2, - * 123.4 would become "120.00". - * - * @param maxSignificantDigits - * Round the number to no more than this number of significant figures. - * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. - * @draft ICU 60 - * @provisional This API might change or be removed in a future release. - * @see NumberFormatter - */ - public Rounder withMaxDigits(int maxSignificantDigits) { - if (maxSignificantDigits >= 1 && maxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) { - return constructFractionSignificant(this, -1, maxSignificantDigits); - } else { - throw new IllegalArgumentException("Significant digits must be between 1 and " - + RoundingUtils.MAX_INT_FRAC_SIG - + " (inclusive)"); - } - } +@Deprecated +public abstract class FractionRounder extends FractionPrecision { } \ No newline at end of file diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatter.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatter.java index 881232918c0..b0ce5d12178 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatter.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatter.java @@ -20,7 +20,7 @@ import com.ibm.icu.util.ULocale; * NumberFormatter.with() * .notation(Notation.compactShort()) * .unit(Currency.getInstance("EUR")) - * .rounding(Rounder.maxDigits(2)) + * .precision(Precision.maxDigits(2)) * .locale(...) * .format(1234) * .toString(); // €1.2K in en-US @@ -28,7 +28,7 @@ import com.ibm.icu.util.ULocale; * // Create a formatter in a private static final field: * private static final LocalizedNumberFormatter formatter = NumberFormatter.withLocale(...) * .unit(NoUnit.PERCENT) - * .rounding(Rounder.fixedFraction(3)); + * .precision(Precision.fixedFraction(3)); * formatter.format(5.9831).toString(); // 5.983% in en-US * * // Create a "template" in a private static final field but without setting a locale until the call site: @@ -50,7 +50,7 @@ import com.ibm.icu.util.ULocale; * <pre> * UnlocalizedNumberFormatter formatter = UnlocalizedNumberFormatter.with() * .notation(Notation.scientific()); - * formatter.rounding(Rounder.maxFraction(2)); // does nothing! + * formatter.precision(Precision.maxFraction(2)); // does nothing! * formatter.locale(ULocale.ENGLISH).format(9.8765).toString(); // prints "9.8765E0", not "9.88E0" * </pre> * diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java index 8424e4a0eed..06dad539f33 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java @@ -220,16 +220,19 @@ class NumberFormatterImpl { } // Rounding strategy - if (macros.rounder != null) { - micros.rounding = macros.rounder; + if (macros.precision != null) { + micros.rounder = macros.precision; } else if (macros.notation instanceof CompactNotation) { - micros.rounding = Rounder.COMPACT_STRATEGY; + micros.rounder = Precision.COMPACT_STRATEGY; } else if (isCurrency) { - micros.rounding = Rounder.MONETARY_STANDARD; + micros.rounder = Precision.MONETARY_STANDARD; } else { - micros.rounding = Rounder.DEFAULT_MAX_FRAC_6; + micros.rounder = Precision.DEFAULT_MAX_FRAC_6; } - micros.rounding = micros.rounding.withLocaleData(currency); + if (macros.roundingMode != null) { + micros.rounder = micros.rounder.withMode(macros.roundingMode); + } + micros.rounder = micros.rounder.withLocaleData(currency); // Grouping strategy if (macros.grouping instanceof Grouper) { @@ -360,7 +363,7 @@ class NumberFormatterImpl { MicroProps micros, DecimalQuantity quantity, NumberStringBuilder string) { - micros.rounding.apply(quantity); + micros.rounder.apply(quantity); if (micros.integerWidth.maxInt == -1) { quantity.setIntegerLength(micros.integerWidth.minInt, Integer.MAX_VALUE); } else { diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java index 70cf69389c7..9633a44e730 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java @@ -2,6 +2,8 @@ // License & terms of use: http://www.unicode.org/copyright.html#License package com.ibm.icu.number; +import java.math.RoundingMode; + import com.ibm.icu.impl.number.MacroProps; import com.ibm.icu.impl.number.Padder; import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay; @@ -31,18 +33,19 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings< static final int KEY_LOCALE = 1; static final int KEY_NOTATION = 2; static final int KEY_UNIT = 3; - static final int KEY_ROUNDER = 4; - static final int KEY_GROUPING = 5; - static final int KEY_PADDER = 6; - static final int KEY_INTEGER = 7; - static final int KEY_SYMBOLS = 8; - static final int KEY_UNIT_WIDTH = 9; - static final int KEY_SIGN = 10; - static final int KEY_DECIMAL = 11; - static final int KEY_SCALE = 12; - static final int KEY_THRESHOLD = 13; - static final int KEY_PER_UNIT = 14; - static final int KEY_MAX = 15; + static final int KEY_PRECISION = 4; + static final int KEY_ROUNDING_MODE = 5; + static final int KEY_GROUPING = 6; + static final int KEY_PADDER = 7; + static final int KEY_INTEGER = 8; + static final int KEY_SYMBOLS = 9; + static final int KEY_UNIT_WIDTH = 10; + static final int KEY_SIGN = 11; + static final int KEY_DECIMAL = 12; + static final int KEY_SCALE = 13; + static final int KEY_THRESHOLD = 14; + static final int KEY_PER_UNIT = 15; + static final int KEY_MAX = 16; final NumberFormatterSettings<?> parent; final int key; @@ -175,7 +178,7 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings< } /** - * Specifies the rounding strategy to use when formatting numbers. + * Specifies the rounding precision to use when formatting numbers. * * <ul> * <li>Round to 3 decimal places: "3.142" @@ -185,28 +188,62 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings< * </ul> * * <p> - * Pass this method the return value of one of the factory methods on {@link Rounder}. For example: + * Pass this method the return value of one of the factory methods on {@link Precision}. For example: * * <pre> - * NumberFormatter.with().rounding(Rounder.fixedFraction(2)) + * NumberFormatter.with().precision(Precision.fixedFraction(2)) * </pre> * * <p> - * In most cases, the default rounding strategy is to round to 6 fraction places; i.e., - * <code>Rounder.maxFraction(6)</code>. The exceptions are if compact notation is being used, then - * the compact notation rounding strategy is used (see {@link Notation#compactShort} for details), or + * In most cases, the default rounding precision is to round to 6 fraction places; i.e., + * <code>Precision.maxFraction(6)</code>. The exceptions are if compact notation is being used, then + * the compact notation rounding precision is used (see {@link Notation#compactShort} for details), or * if the unit is a currency, then standard currency rounding is used, which varies from currency to - * currency (see {@link Rounder#currency} for details). + * currency (see {@link Precision#currency} for details). * - * @param rounder - * The rounding strategy to use. + * @param precision + * The rounding precision to use. * @return The fluent chain. - * @see Rounder + * @see Precision * @draft ICU 60 * @provisional This API might change or be removed in a future release. */ - public T rounding(Rounder rounder) { - return create(KEY_ROUNDER, rounder); + public T precision(Precision precision) { + return create(KEY_PRECISION, precision); + } + + /** + * @deprecated ICU 62 Use precision() instead. This method is for backwards compatibility and will be + * removed in ICU 64. See http://bugs.icu-project.org/trac/ticket/13746 + */ + @Deprecated + public T rounding(Precision rounder) { + return precision(rounder); + } + + /** + * Specifies how to determine the direction to round a number when it has more digits than fit in the + * desired precision. When formatting 1.235: + * + * <ul> + * <li>Ceiling rounding mode with integer precision: "2" + * <li>Half-down rounding mode with 2 fixed fraction digits: "1.23" + * <li>Half-up rounding mode with 2 fixed fraction digits: "1.24" + * </ul> + * + * The default is HALF_EVEN. For more information on rounding mode, see the ICU userguide here: + * + * http://userguide.icu-project.org/formatparse/numbers/rounding-modes + * + * @param roundingMode + * The rounding mode to use. + * @return The fluent chain. + * @see Precision + * @draft ICU 60 + * @provisional This API might change or be removed in a future release. + */ + public T roundingMode(RoundingMode roundingMode) { + return create(KEY_ROUNDING_MODE, roundingMode); } /** @@ -559,9 +596,14 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings< macros.unit = (MeasureUnit) current.value; } break; - case KEY_ROUNDER: - if (macros.rounder == null) { - macros.rounder = (Rounder) current.value; + case KEY_PRECISION: + if (macros.precision == null) { + macros.precision = (Precision) current.value; + } + break; + case KEY_ROUNDING_MODE: + if (macros.roundingMode == null) { + macros.roundingMode = (RoundingMode) current.value; } break; case KEY_GROUPING: diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberPropertyMapper.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberPropertyMapper.java index 99af4cd5224..aa0cca35379 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberPropertyMapper.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberPropertyMapper.java @@ -17,9 +17,9 @@ import com.ibm.icu.impl.number.PropertiesAffixPatternProvider; import com.ibm.icu.impl.number.RoundingUtils; import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay; import com.ibm.icu.number.NumberFormatter.SignDisplay; -import com.ibm.icu.number.Rounder.FractionRounderImpl; -import com.ibm.icu.number.Rounder.IncrementRounderImpl; -import com.ibm.icu.number.Rounder.SignificantRounderImpl; +import com.ibm.icu.number.Precision.FractionRounderImpl; +import com.ibm.icu.number.Precision.IncrementRounderImpl; +import com.ibm.icu.number.Precision.SignificantRounderImpl; import com.ibm.icu.text.CompactDecimalFormat.CompactStyle; import com.ibm.icu.text.DecimalFormatSymbols; import com.ibm.icu.util.Currency; @@ -170,11 +170,11 @@ final class NumberPropertyMapper { maxInt = maxInt < 0 ? -1 : maxInt < minInt ? minInt : maxInt > RoundingUtils.MAX_INT_FRAC_SIG ? -1 : maxInt; } - Rounder rounding = null; + Precision rounding = null; if (explicitCurrencyUsage) { - rounding = Rounder.constructCurrency(currencyUsage).withCurrency(currency); + rounding = Precision.constructCurrency(currencyUsage).withCurrency(currency); } else if (roundingIncrement != null) { - rounding = Rounder.constructIncrement(roundingIncrement); + rounding = Precision.constructIncrement(roundingIncrement); } else if (explicitMinMaxSig) { minSig = minSig < 1 ? 1 : minSig > RoundingUtils.MAX_INT_FRAC_SIG ? RoundingUtils.MAX_INT_FRAC_SIG : minSig; @@ -182,15 +182,15 @@ final class NumberPropertyMapper { : maxSig < minSig ? minSig : maxSig > RoundingUtils.MAX_INT_FRAC_SIG ? RoundingUtils.MAX_INT_FRAC_SIG : maxSig; - rounding = Rounder.constructSignificant(minSig, maxSig); + rounding = Precision.constructSignificant(minSig, maxSig); } else if (explicitMinMaxFrac) { - rounding = Rounder.constructFraction(minFrac, maxFrac); + rounding = Precision.constructFraction(minFrac, maxFrac); } else if (useCurrency) { - rounding = Rounder.constructCurrency(currencyUsage); + rounding = Precision.constructCurrency(currencyUsage); } if (rounding != null) { rounding = rounding.withMode(mathContext); - macros.rounder = rounding; + macros.precision = rounding; } /////////////////// @@ -256,7 +256,7 @@ final class NumberPropertyMapper { properties.getExponentSignAlwaysShown() ? SignDisplay.ALWAYS : SignDisplay.AUTO); // Scientific notation also involves overriding the rounding mode. // TODO: Overriding here is a bit of a hack. Should this logic go earlier? - if (macros.rounder instanceof FractionRounder) { + if (macros.precision instanceof FractionPrecision) { // For the purposes of rounding, get the original min/max int/frac, since the local // variables // have been manipulated for display purposes. @@ -265,13 +265,13 @@ final class NumberPropertyMapper { int maxFrac_ = properties.getMaximumFractionDigits(); if (minInt_ == 0 && maxFrac_ == 0) { // Patterns like "#E0" and "##E0", which mean no rounding! - macros.rounder = Rounder.constructInfinite().withMode(mathContext); + macros.precision = Precision.constructInfinite().withMode(mathContext); } else if (minInt_ == 0 && minFrac_ == 0) { // Patterns like "#.##E0" (no zeros in the mantissa), which mean round to maxFrac+1 - macros.rounder = Rounder.constructSignificant(1, maxFrac_ + 1).withMode(mathContext); + macros.precision = Precision.constructSignificant(1, maxFrac_ + 1).withMode(mathContext); } else { // All other scientific patterns, which mean round to minInt+maxFrac - macros.rounder = Rounder.constructSignificant(minInt_ + minFrac_, minInt_ + maxFrac_) + macros.precision = Precision.constructSignificant(minInt_ + minFrac_, minInt_ + maxFrac_) .withMode(mathContext); } } @@ -311,9 +311,9 @@ final class NumberPropertyMapper { exportedProperties.setMinimumIntegerDigits(minInt); exportedProperties.setMaximumIntegerDigits(maxInt == -1 ? Integer.MAX_VALUE : maxInt); - Rounder rounding_; - if (rounding instanceof CurrencyRounder) { - rounding_ = ((CurrencyRounder) rounding).withCurrency(currency); + Precision rounding_; + if (rounding instanceof CurrencyPrecision) { + rounding_ = ((CurrencyPrecision) rounding).withCurrency(currency); } else { rounding_ = rounding; } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java index 707ea0cedba..e93fd0db27e 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java @@ -47,11 +47,10 @@ class NumberSkeletonImpl { // Section 1: We might accept an option, but it is not required: STATE_SCIENTIFIC, - STATE_ROUNDER, - STATE_FRACTION_ROUNDER, + STATE_FRACTION_PRECISION, // Section 2: An option is required: - STATE_INCREMENT_ROUNDER, + STATE_INCREMENT_PRECISION, STATE_MEASURE_UNIT, STATE_PER_MEASURE_UNIT, STATE_CURRENCY_UNIT, @@ -77,10 +76,18 @@ class NumberSkeletonImpl { STEM_BASE_UNIT, STEM_PERCENT, STEM_PERMILLE, - STEM_ROUND_INTEGER, - STEM_ROUND_UNLIMITED, - STEM_ROUND_CURRENCY_STANDARD, - STEM_ROUND_CURRENCY_CASH, + STEM_PRECISION_INTEGER, + STEM_PRECISION_UNLIMITED, + STEM_PRECISION_CURRENCY_STANDARD, + STEM_PRECISION_CURRENCY_CASH, + STEM_ROUNDING_MODE_CEILING, + STEM_ROUNDING_MODE_FLOOR, + STEM_ROUNDING_MODE_DOWN, + STEM_ROUNDING_MODE_UP, + STEM_ROUNDING_MODE_HALF_EVEN, + STEM_ROUNDING_MODE_HALF_DOWN, + STEM_ROUNDING_MODE_HALF_UP, + STEM_ROUNDING_MODE_UNNECESSARY, STEM_GROUP_OFF, STEM_GROUP_MIN2, STEM_GROUP_AUTO, @@ -103,7 +110,7 @@ class NumberSkeletonImpl { STEM_DECIMAL_ALWAYS, // Section 2: Stems that DO require an option: - STEM_ROUND_INCREMENT, + STEM_PRECISION_INCREMENT, STEM_MEASURE_UNIT, STEM_PER_MEASURE_UNIT, STEM_CURRENCY, @@ -130,10 +137,18 @@ class NumberSkeletonImpl { b.add("base-unit", StemEnum.STEM_BASE_UNIT.ordinal()); b.add("percent", StemEnum.STEM_PERCENT.ordinal()); b.add("permille", StemEnum.STEM_PERMILLE.ordinal()); - b.add("round-integer", StemEnum.STEM_ROUND_INTEGER.ordinal()); - b.add("round-unlimited", StemEnum.STEM_ROUND_UNLIMITED.ordinal()); - b.add("round-currency-standard", StemEnum.STEM_ROUND_CURRENCY_STANDARD.ordinal()); - b.add("round-currency-cash", StemEnum.STEM_ROUND_CURRENCY_CASH.ordinal()); + b.add("precision-integer", StemEnum.STEM_PRECISION_INTEGER.ordinal()); + b.add("precision-unlimited", StemEnum.STEM_PRECISION_UNLIMITED.ordinal()); + b.add("precision-currency-standard", StemEnum.STEM_PRECISION_CURRENCY_STANDARD.ordinal()); + b.add("precision-currency-cash", StemEnum.STEM_PRECISION_CURRENCY_CASH.ordinal()); + b.add("rounding-mode-ceiling", StemEnum.STEM_ROUNDING_MODE_CEILING.ordinal()); + b.add("rounding-mode-floor", StemEnum.STEM_ROUNDING_MODE_FLOOR.ordinal()); + b.add("rounding-mode-down", StemEnum.STEM_ROUNDING_MODE_DOWN.ordinal()); + b.add("rounding-mode-up", StemEnum.STEM_ROUNDING_MODE_UP.ordinal()); + b.add("rounding-mode-half-even", StemEnum.STEM_ROUNDING_MODE_HALF_EVEN.ordinal()); + b.add("rounding-mode-half-down", StemEnum.STEM_ROUNDING_MODE_HALF_DOWN.ordinal()); + b.add("rounding-mode-half-up", StemEnum.STEM_ROUNDING_MODE_HALF_UP.ordinal()); + b.add("rounding-mode-unnecessary", StemEnum.STEM_ROUNDING_MODE_UNNECESSARY.ordinal()); b.add("group-off", StemEnum.STEM_GROUP_OFF.ordinal()); b.add("group-min2", StemEnum.STEM_GROUP_MIN2.ordinal()); b.add("group-auto", StemEnum.STEM_GROUP_AUTO.ordinal()); @@ -156,7 +171,7 @@ class NumberSkeletonImpl { b.add("decimal-always", StemEnum.STEM_DECIMAL_ALWAYS.ordinal()); // Section 2: - b.add("round-increment", StemEnum.STEM_ROUND_INCREMENT.ordinal()); + b.add("precision-increment", StemEnum.STEM_PRECISION_INCREMENT.ordinal()); b.add("measure-unit", StemEnum.STEM_MEASURE_UNIT.ordinal()); b.add("per-measure-unit", StemEnum.STEM_PER_MEASURE_UNIT.ordinal()); b.add("currency", StemEnum.STEM_CURRENCY.ordinal()); @@ -205,16 +220,39 @@ class NumberSkeletonImpl { } } - private static Rounder rounder(StemEnum stem) { + private static Precision precision(StemEnum stem) { switch (stem) { - case STEM_ROUND_INTEGER: - return Rounder.integer(); - case STEM_ROUND_UNLIMITED: - return Rounder.unlimited(); - case STEM_ROUND_CURRENCY_STANDARD: - return Rounder.currency(CurrencyUsage.STANDARD); - case STEM_ROUND_CURRENCY_CASH: - return Rounder.currency(CurrencyUsage.CASH); + case STEM_PRECISION_INTEGER: + return Precision.integer(); + case STEM_PRECISION_UNLIMITED: + return Precision.unlimited(); + case STEM_PRECISION_CURRENCY_STANDARD: + return Precision.currency(CurrencyUsage.STANDARD); + case STEM_PRECISION_CURRENCY_CASH: + return Precision.currency(CurrencyUsage.CASH); + default: + throw new AssertionError(); + } + } + + private static RoundingMode roundingMode(StemEnum stem) { + switch (stem) { + case STEM_ROUNDING_MODE_CEILING: + return RoundingMode.CEILING; + case STEM_ROUNDING_MODE_FLOOR: + return RoundingMode.FLOOR; + case STEM_ROUNDING_MODE_DOWN: + return RoundingMode.DOWN; + case STEM_ROUNDING_MODE_UP: + return RoundingMode.UP; + case STEM_ROUNDING_MODE_HALF_EVEN: + return RoundingMode.HALF_EVEN; + case STEM_ROUNDING_MODE_HALF_DOWN: + return RoundingMode.HALF_DOWN; + case STEM_ROUNDING_MODE_HALF_UP: + return RoundingMode.HALF_UP; + case STEM_ROUNDING_MODE_UNNECESSARY: + return RoundingMode.UNNECESSARY; default: throw new AssertionError(); } @@ -293,6 +331,37 @@ class NumberSkeletonImpl { */ static final class EnumToStemString { + private static void roundingMode(RoundingMode value, StringBuilder sb) { + switch (value) { + case CEILING: + sb.append("rounding-mode-ceiling"); + break; + case FLOOR: + sb.append("rounding-mode-floor"); + break; + case DOWN: + sb.append("rounding-mode-down"); + break; + case UP: + sb.append("rounding-mode-up"); + break; + case HALF_EVEN: + sb.append("rounding-mode-half-even"); + break; + case HALF_DOWN: + sb.append("rounding-mode-half-down"); + break; + case HALF_UP: + sb.append("rounding-mode-half-up"); + break; + case UNNECESSARY: + sb.append("rounding-mode-unnecessary"); + break; + default: + throw new AssertionError(); + } + } + private static void groupingStrategy(GroupingStrategy value, StringBuilder sb) { switch (value) { case OFF: @@ -379,17 +448,6 @@ class NumberSkeletonImpl { } } - /** Kebab case versions of the rounding mode enum. */ - static final String[] ROUNDING_MODE_STRINGS = { - "up", - "down", - "ceiling", - "floor", - "half-up", - "half-down", - "half-even", - "unnecessary" }; - ///// ENTRYPOINT FUNCTIONS ///// /** Cache for parsed skeleton strings. */ @@ -508,7 +566,7 @@ class NumberSkeletonImpl { // Does the current stem require an option? if (isTokenSeparator && stem != ParseState.STATE_NULL) { switch (stem) { - case STATE_INCREMENT_ROUNDER: + case STATE_INCREMENT_PRECISION: case STATE_MEASURE_UNIT: case STATE_PER_MEASURE_UNIT: case STATE_CURRENCY_UNIT: @@ -539,11 +597,11 @@ class NumberSkeletonImpl { // First check for "blueprint" stems, which start with a "signal char" switch (segment.charAt(0)) { case '.': - checkNull(macros.rounder, segment); + checkNull(macros.precision, segment); BlueprintHelpers.parseFractionStem(segment, macros); - return ParseState.STATE_FRACTION_ROUNDER; + return ParseState.STATE_FRACTION_PRECISION; case '@': - checkNull(macros.rounder, segment); + checkNull(macros.precision, segment); BlueprintHelpers.parseDigitsStem(segment, macros); return ParseState.STATE_NULL; } @@ -583,19 +641,31 @@ class NumberSkeletonImpl { macros.unit = StemToObject.unit(stem); return ParseState.STATE_NULL; - case STEM_ROUND_INTEGER: - case STEM_ROUND_UNLIMITED: - case STEM_ROUND_CURRENCY_STANDARD: - case STEM_ROUND_CURRENCY_CASH: - checkNull(macros.rounder, segment); - macros.rounder = StemToObject.rounder(stem); + case STEM_PRECISION_INTEGER: + case STEM_PRECISION_UNLIMITED: + case STEM_PRECISION_CURRENCY_STANDARD: + case STEM_PRECISION_CURRENCY_CASH: + checkNull(macros.precision, segment); + macros.precision = StemToObject.precision(stem); switch (stem) { - case STEM_ROUND_INTEGER: - return ParseState.STATE_FRACTION_ROUNDER; // allows for "round-integer/@##" + case STEM_PRECISION_INTEGER: + return ParseState.STATE_FRACTION_PRECISION; // allows for "precision-integer/@##" default: - return ParseState.STATE_ROUNDER; // allows for rounding mode options + return ParseState.STATE_NULL; } + case STEM_ROUNDING_MODE_CEILING: + case STEM_ROUNDING_MODE_FLOOR: + case STEM_ROUNDING_MODE_DOWN: + case STEM_ROUNDING_MODE_UP: + case STEM_ROUNDING_MODE_HALF_EVEN: + case STEM_ROUNDING_MODE_HALF_DOWN: + case STEM_ROUNDING_MODE_HALF_UP: + case STEM_ROUNDING_MODE_UNNECESSARY: + checkNull(macros.roundingMode, segment); + macros.roundingMode = StemToObject.roundingMode(stem); + return ParseState.STATE_NULL; + case STEM_GROUP_OFF: case STEM_GROUP_MIN2: case STEM_GROUP_AUTO: @@ -638,9 +708,9 @@ class NumberSkeletonImpl { // Stems requiring an option: - case STEM_ROUND_INCREMENT: - checkNull(macros.rounder, segment); - return ParseState.STATE_INCREMENT_ROUNDER; + case STEM_PRECISION_INCREMENT: + checkNull(macros.precision, segment); + return ParseState.STATE_INCREMENT_PRECISION; case STEM_MEASURE_UNIT: checkNull(macros.unit, segment); @@ -691,9 +761,9 @@ class NumberSkeletonImpl { case STATE_PER_MEASURE_UNIT: BlueprintHelpers.parseMeasurePerUnitOption(segment, macros); return ParseState.STATE_NULL; - case STATE_INCREMENT_ROUNDER: + case STATE_INCREMENT_PRECISION: BlueprintHelpers.parseIncrementOption(segment, macros); - return ParseState.STATE_ROUNDER; + return ParseState.STATE_NULL; case STATE_INTEGER_WIDTH: BlueprintHelpers.parseIntegerWidthOption(segment, macros); return ParseState.STATE_NULL; @@ -725,21 +795,9 @@ class NumberSkeletonImpl { // Frac-sig option switch (stem) { - case STATE_FRACTION_ROUNDER: + case STATE_FRACTION_PRECISION: if (BlueprintHelpers.parseFracSigOption(segment, macros)) { - return ParseState.STATE_ROUNDER; - } - break; - default: - break; - } - - // Rounding mode option - switch (stem) { - case STATE_ROUNDER: - case STATE_FRACTION_ROUNDER: - if (BlueprintHelpers.parseRoundingModeOption(segment, macros)) { - return ParseState.STATE_ROUNDER; + return ParseState.STATE_NULL; } break; default: @@ -767,7 +825,10 @@ class NumberSkeletonImpl { if (macros.perUnit != null && GeneratorHelpers.perUnit(macros, sb)) { sb.append(' '); } - if (macros.rounder != null && GeneratorHelpers.rounding(macros, sb)) { + if (macros.precision != null && GeneratorHelpers.precision(macros, sb)) { + sb.append(' '); + } + if (macros.roundingMode != null && GeneratorHelpers.roundingMode(macros, sb)) { sb.append(' '); } if (macros.grouping != null && GeneratorHelpers.grouping(macros, sb)) { @@ -951,15 +1012,15 @@ class NumberSkeletonImpl { } // Use the public APIs to enforce bounds checking if (maxFrac == -1) { - macros.rounder = Rounder.minFraction(minFrac); + macros.precision = Precision.minFraction(minFrac); } else { - macros.rounder = Rounder.minMaxFraction(minFrac, maxFrac); + macros.precision = Precision.minMaxFraction(minFrac, maxFrac); } } private static void generateFractionStem(int minFrac, int maxFrac, StringBuilder sb) { if (minFrac == 0 && maxFrac == 0) { - sb.append("round-integer"); + sb.append("precision-integer"); return; } sb.append('.'); @@ -1005,9 +1066,9 @@ class NumberSkeletonImpl { } // Use the public APIs to enforce bounds checking if (maxSig == -1) { - macros.rounder = Rounder.minDigits(minSig); + macros.precision = Precision.minSignificantDigits(minSig); } else { - macros.rounder = Rounder.minMaxDigits(minSig, maxSig); + macros.precision = Precision.minMaxSignificantDigits(minSig, maxSig); } } @@ -1066,11 +1127,11 @@ class NumberSkeletonImpl { throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", segment); } - FractionRounder oldRounder = (FractionRounder) macros.rounder; + FractionPrecision oldRounder = (FractionPrecision) macros.precision; if (maxSig == -1) { - macros.rounder = oldRounder.withMinDigits(minSig); + macros.precision = oldRounder.withMinDigits(minSig); } else { - macros.rounder = oldRounder.withMaxDigits(maxSig); + macros.precision = oldRounder.withMaxDigits(maxSig); } return true; } @@ -1084,28 +1145,13 @@ class NumberSkeletonImpl { } catch (NumberFormatException e) { throw new SkeletonSyntaxException("Invalid rounding increment", segment, e); } - macros.rounder = Rounder.increment(increment); + macros.precision = Precision.increment(increment); } private static void generateIncrementOption(BigDecimal increment, StringBuilder sb) { sb.append(increment.toPlainString()); } - /** @return Whether we successfully found and parsed a rounding mode. */ - private static boolean parseRoundingModeOption(StringSegment segment, MacroProps macros) { - for (int rm = 0; rm < ROUNDING_MODE_STRINGS.length; rm++) { - if (segment.equals(ROUNDING_MODE_STRINGS[rm])) { - macros.rounder = macros.rounder.withMode(RoundingMode.valueOf(rm)); - return true; - } - } - return false; - } - - private static void generateRoundingModeOption(RoundingMode mode, StringBuilder sb) { - sb.append(ROUNDING_MODE_STRINGS[mode.ordinal()]); - } - private static void parseIntegerWidthOption(StringSegment segment, MacroProps macros) { int offset = 0; int minInt = 0; @@ -1272,17 +1318,17 @@ class NumberSkeletonImpl { } } - private static boolean rounding(MacroProps macros, StringBuilder sb) { - if (macros.rounder instanceof Rounder.InfiniteRounderImpl) { - sb.append("round-unlimited"); - } else if (macros.rounder instanceof Rounder.FractionRounderImpl) { - Rounder.FractionRounderImpl impl = (Rounder.FractionRounderImpl) macros.rounder; + private static boolean precision(MacroProps macros, StringBuilder sb) { + if (macros.precision instanceof Precision.InfiniteRounderImpl) { + sb.append("precision-unlimited"); + } else if (macros.precision instanceof Precision.FractionRounderImpl) { + Precision.FractionRounderImpl impl = (Precision.FractionRounderImpl) macros.precision; BlueprintHelpers.generateFractionStem(impl.minFrac, impl.maxFrac, sb); - } else if (macros.rounder instanceof Rounder.SignificantRounderImpl) { - Rounder.SignificantRounderImpl impl = (Rounder.SignificantRounderImpl) macros.rounder; + } else if (macros.precision instanceof Precision.SignificantRounderImpl) { + Precision.SignificantRounderImpl impl = (Precision.SignificantRounderImpl) macros.precision; BlueprintHelpers.generateDigitsStem(impl.minSig, impl.maxSig, sb); - } else if (macros.rounder instanceof Rounder.FracSigRounderImpl) { - Rounder.FracSigRounderImpl impl = (Rounder.FracSigRounderImpl) macros.rounder; + } else if (macros.precision instanceof Precision.FracSigRounderImpl) { + Precision.FracSigRounderImpl impl = (Precision.FracSigRounderImpl) macros.precision; BlueprintHelpers.generateFractionStem(impl.minFrac, impl.maxFrac, sb); sb.append('/'); if (impl.minSig == -1) { @@ -1290,31 +1336,32 @@ class NumberSkeletonImpl { } else { BlueprintHelpers.generateDigitsStem(impl.minSig, -1, sb); } - } else if (macros.rounder instanceof Rounder.IncrementRounderImpl) { - Rounder.IncrementRounderImpl impl = (Rounder.IncrementRounderImpl) macros.rounder; - sb.append("round-increment/"); + } else if (macros.precision instanceof Precision.IncrementRounderImpl) { + Precision.IncrementRounderImpl impl = (Precision.IncrementRounderImpl) macros.precision; + sb.append("precision-increment/"); BlueprintHelpers.generateIncrementOption(impl.increment, sb); } else { - assert macros.rounder instanceof Rounder.CurrencyRounderImpl; - Rounder.CurrencyRounderImpl impl = (Rounder.CurrencyRounderImpl) macros.rounder; + assert macros.precision instanceof Precision.CurrencyRounderImpl; + Precision.CurrencyRounderImpl impl = (Precision.CurrencyRounderImpl) macros.precision; if (impl.usage == CurrencyUsage.STANDARD) { - sb.append("round-currency-standard"); + sb.append("precision-currency-standard"); } else { - sb.append("round-currency-cash"); + sb.append("precision-currency-cash"); } } - // Generate the options - if (macros.rounder.mathContext != RoundingUtils.DEFAULT_MATH_CONTEXT_UNLIMITED) { - sb.append('/'); - BlueprintHelpers.generateRoundingModeOption(macros.rounder.mathContext.getRoundingMode(), - sb); - } - // NOTE: Always return true for rounding because the default value depends on other options. return true; } + private static boolean roundingMode(MacroProps macros, StringBuilder sb) { + if (macros.roundingMode == RoundingUtils.DEFAULT_ROUNDING_MODE) { + return false; // Default value + } + EnumToStemString.roundingMode(macros.roundingMode, sb); + return true; + } + private static boolean grouping(MacroProps macros, StringBuilder sb) { if (macros.grouping instanceof GroupingStrategy) { if (macros.grouping == GroupingStrategy.AUTO) { diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/Precision.java b/icu4j/main/classes/core/src/com/ibm/icu/number/Precision.java new file mode 100644 index 00000000000..78a90e10a02 --- /dev/null +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/Precision.java @@ -0,0 +1,759 @@ +// © 2017 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html#License +package com.ibm.icu.number; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; + +import com.ibm.icu.impl.number.DecimalQuantity; +import com.ibm.icu.impl.number.MultiplierProducer; +import com.ibm.icu.impl.number.RoundingUtils; +import com.ibm.icu.util.Currency; +import com.ibm.icu.util.Currency.CurrencyUsage; + +/** + * A class that defines the rounding precision to be used when formatting numbers in NumberFormatter. + * + * <p> + * To create a Precision, use one of the factory methods. + * + * @draft ICU 62 + * @provisional This API might change or be removed in a future release. + * @see NumberFormatter + */ +public abstract class Precision implements Cloneable { + + /* package-private final */ MathContext mathContext; + + /* package-private */ Precision() { + mathContext = RoundingUtils.DEFAULT_MATH_CONTEXT_UNLIMITED; + } + + /** + * Show all available digits to full precision. + * + * <p> + * <strong>NOTE:</strong> When formatting a <em>double</em>, this method, along with + * {@link #minFraction} and {@link #minDigits}, will trigger complex algorithm similar to + * <em>Dragon4</em> to determine the low-order digits and the number of digits to display based on + * the value of the double. If the number of fraction places or significant digits can be bounded, + * consider using {@link #maxFraction} or {@link #maxDigits} instead to maximize performance. For + * more information, read the following blog post. + * + * <p> + * http://www.serpentine.com/blog/2011/06/29/here-be-dragons-advances-in-problems-you-didnt-even-know-you-had/ + * + * @return A Precision for chaining or passing to the NumberFormatter rounding() setter. + * @draft ICU 60 + * @provisional This API might change or be removed in a future release. + * @see NumberFormatter + */ + public static Precision unlimited() { + return constructInfinite(); + } + + /** + * Show numbers rounded if necessary to the nearest integer. + * + * @return A FractionPrecision for chaining or passing to the NumberFormatter rounding() setter. + * @draft ICU 60 + * @provisional This API might change or be removed in a future release. + * @see NumberFormatter + */ + public static FractionPrecision integer() { + return constructFraction(0, 0); + } + + /** + * Show numbers rounded if necessary to a certain number of fraction places (numerals after the + * decimal separator). Additionally, pad with zeros to ensure that this number of places are always + * shown. + * + * <p> + * Example output with minMaxFractionPlaces = 3: + * + * <p> + * 87,650.000<br> + * 8,765.000<br> + * 876.500<br> + * 87.650<br> + * 8.765<br> + * 0.876<br> + * 0.088<br> + * 0.009<br> + * 0.000 (zero) + * + * <p> + * This method is equivalent to {@link #minMaxFraction} with both arguments equal. + * + * @param minMaxFractionPlaces + * The minimum and maximum number of numerals to display after the decimal separator + * (rounding if too long or padding with zeros if too short). + * @return A FractionPrecision for chaining or passing to the NumberFormatter rounding() setter. + * @draft ICU 60 + * @provisional This API might change or be removed in a future release. + * @see NumberFormatter + */ + public static FractionPrecision fixedFraction(int minMaxFractionPlaces) { + if (minMaxFractionPlaces >= 0 && minMaxFractionPlaces <= RoundingUtils.MAX_INT_FRAC_SIG) { + return constructFraction(minMaxFractionPlaces, minMaxFractionPlaces); + } else { + throw new IllegalArgumentException("Fraction length must be between 0 and " + + RoundingUtils.MAX_INT_FRAC_SIG + + " (inclusive)"); + } + } + + /** + * Always show at least a certain number of fraction places after the decimal separator, padding with + * zeros if necessary. Do not perform rounding (display numbers to their full precision). + * + * <p> + * <strong>NOTE:</strong> If you are formatting <em>doubles</em>, see the performance note in + * {@link #unlimited}. + * + * @param minFractionPlaces + * The minimum number of numerals to display after the decimal separator (padding with + * zeros if necessary). + * @return A FractionPrecision for chaining or passing to the NumberFormatter rounding() setter. + * @draft ICU 60 + * @provisional This API might change or be removed in a future release. + * @see NumberFormatter + */ + public static FractionPrecision minFraction(int minFractionPlaces) { + if (minFractionPlaces >= 0 && minFractionPlaces <= RoundingUtils.MAX_INT_FRAC_SIG) { + return constructFraction(minFractionPlaces, -1); + } else { + throw new IllegalArgumentException("Fraction length must be between 0 and " + + RoundingUtils.MAX_INT_FRAC_SIG + + " (inclusive)"); + } + } + + /** + * Show numbers rounded if necessary to a certain number of fraction places (numerals after the + * decimal separator). Unlike the other fraction rounding strategies, this strategy does <em>not</em> + * pad zeros to the end of the number. + * + * @param maxFractionPlaces + * The maximum number of numerals to display after the decimal mark (rounding if + * necessary). + * @return A FractionPrecision for chaining or passing to the NumberFormatter rounding() setter. + * @draft ICU 60 + * @provisional This API might change or be removed in a future release. + * @see NumberFormatter + */ + public static FractionPrecision maxFraction(int maxFractionPlaces) { + if (maxFractionPlaces >= 0 && maxFractionPlaces <= RoundingUtils.MAX_INT_FRAC_SIG) { + return constructFraction(0, maxFractionPlaces); + } else { + throw new IllegalArgumentException("Fraction length must be between 0 and " + + RoundingUtils.MAX_INT_FRAC_SIG + + " (inclusive)"); + } + } + + /** + * Show numbers rounded if necessary to a certain number of fraction places (numerals after the + * decimal separator); in addition, always show at least a certain number of places after the decimal + * separator, padding with zeros if necessary. + * + * @param minFractionPlaces + * The minimum number of numerals to display after the decimal separator (padding with + * zeros if necessary). + * @param maxFractionPlaces + * The maximum number of numerals to display after the decimal separator (rounding if + * necessary). + * @return A FractionPrecision for chaining or passing to the NumberFormatter rounding() setter. + * @draft ICU 60 + * @provisional This API might change or be removed in a future release. + * @see NumberFormatter + */ + public static FractionPrecision minMaxFraction(int minFractionPlaces, int maxFractionPlaces) { + if (minFractionPlaces >= 0 + && maxFractionPlaces <= RoundingUtils.MAX_INT_FRAC_SIG + && minFractionPlaces <= maxFractionPlaces) { + return constructFraction(minFractionPlaces, maxFractionPlaces); + } else { + throw new IllegalArgumentException("Fraction length must be between 0 and " + + RoundingUtils.MAX_INT_FRAC_SIG + + " (inclusive)"); + } + } + + /** + * Show numbers rounded if necessary to a certain number of significant digits or significant + * figures. Additionally, pad with zeros to ensure that this number of significant digits/figures are + * always shown. + * + * <p> + * This method is equivalent to {@link #minMaxSignificantDigits} with both arguments equal. + * + * @param minMaxSignificantDigits + * The minimum and maximum number of significant digits to display (rounding if too long + * or padding with zeros if too short). + * @return A Precision for chaining or passing to the NumberFormatter rounding() setter. + * @draft ICU 62 + * @provisional This API might change or be removed in a future release. + * @see NumberFormatter + */ + public static Precision fixedSignificantDigits(int minMaxSignificantDigits) { + if (minMaxSignificantDigits >= 1 && minMaxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) { + return constructSignificant(minMaxSignificantDigits, minMaxSignificantDigits); + } else { + throw new IllegalArgumentException("Significant digits must be between 1 and " + + RoundingUtils.MAX_INT_FRAC_SIG + + " (inclusive)"); + } + } + + /** + * Always show at least a certain number of significant digits/figures, padding with zeros if + * necessary. Do not perform rounding (display numbers to their full precision). + * + * <p> + * <strong>NOTE:</strong> If you are formatting <em>doubles</em>, see the performance note in + * {@link #unlimited}. + * + * @param minSignificantDigits + * The minimum number of significant digits to display (padding with zeros if too short). + * @return A Precision for chaining or passing to the NumberFormatter rounding() setter. + * @draft ICU 62 + * @provisional This API might change or be removed in a future release. + * @see NumberFormatter + */ + public static Precision minSignificantDigits(int minSignificantDigits) { + if (minSignificantDigits >= 1 && minSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) { + return constructSignificant(minSignificantDigits, -1); + } else { + throw new IllegalArgumentException("Significant digits must be between 1 and " + + RoundingUtils.MAX_INT_FRAC_SIG + + " (inclusive)"); + } + } + + /** + * Show numbers rounded if necessary to a certain number of significant digits/figures. + * + * @param maxSignificantDigits + * The maximum number of significant digits to display (rounding if too long). + * @return A Precision for chaining or passing to the NumberFormatter rounding() setter. + * @draft ICU 62 + * @provisional This API might change or be removed in a future release. + * @see NumberFormatter + */ + public static Precision maxSignificantDigits(int maxSignificantDigits) { + if (maxSignificantDigits >= 1 && maxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) { + return constructSignificant(1, maxSignificantDigits); + } else { + throw new IllegalArgumentException("Significant digits must be between 1 and " + + RoundingUtils.MAX_INT_FRAC_SIG + + " (inclusive)"); + } + } + + /** + * Show numbers rounded if necessary to a certain number of significant digits/figures; in addition, + * always show at least a certain number of significant digits, padding with zeros if necessary. + * + * @param minSignificantDigits + * The minimum number of significant digits to display (padding with zeros if necessary). + * @param maxSignificantDigits + * The maximum number of significant digits to display (rounding if necessary). + * @return A Precision for chaining or passing to the NumberFormatter rounding() setter. + * @draft ICU 62 + * @provisional This API might change or be removed in a future release. + * @see NumberFormatter + */ + public static Precision minMaxSignificantDigits(int minSignificantDigits, int maxSignificantDigits) { + if (minSignificantDigits >= 1 + && maxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG + && minSignificantDigits <= maxSignificantDigits) { + return constructSignificant(minSignificantDigits, maxSignificantDigits); + } else { + throw new IllegalArgumentException("Significant digits must be between 1 and " + + RoundingUtils.MAX_INT_FRAC_SIG + + " (inclusive)"); + } + } + + /** + * @deprecated ICU 62 Use *SignificantDigits() instead. This method is for backwards compatibility + * and will be removed in ICU 64. See http://bugs.icu-project.org/trac/ticket/13746 + */ + @Deprecated + public static Precision fixedDigits(int a) { + return fixedSignificantDigits(a); + } + + /** + * @deprecated ICU 62 Use *SignificantDigits() instead. This method is for backwards compatibility + * and will be removed in ICU 64. See http://bugs.icu-project.org/trac/ticket/13746 + */ + @Deprecated + public static Precision minDigits(int a) { + return minSignificantDigits(a); + } + + /** + * @deprecated ICU 62 Use *SignificantDigits() instead. This method is for backwards compatibility + * and will be removed in ICU 64. See http://bugs.icu-project.org/trac/ticket/13746 + */ + @Deprecated + public static Precision maxDigits(int a) { + return maxSignificantDigits(a); + } + + /** + * @deprecated ICU 62 Use *SignificantDigits() instead. This method is for backwards compatibility + * and will be removed in ICU 64. See http://bugs.icu-project.org/trac/ticket/13746 + */ + @Deprecated + public static Precision minMaxDigits(int a, int b) { + return minMaxSignificantDigits(a, b); + } + + /** + * Show numbers rounded if necessary to the closest multiple of a certain rounding increment. For + * example, if the rounding increment is 0.5, then round 1.2 to 1 and round 1.3 to 1.5. + * + * <p> + * In order to ensure that numbers are padded to the appropriate number of fraction places, set the + * scale on the rounding increment BigDecimal. For example, to round to the nearest 0.5 and always + * display 2 numerals after the decimal separator (to display 1.2 as "1.00" and 1.3 as "1.50"), you + * can run: + * + * <pre> + * Precision.increment(new BigDecimal("0.50")) + * </pre> + * + * <p> + * For more information on the scale of Java BigDecimal, see {@link java.math.BigDecimal#scale()}. + * + * @param roundingIncrement + * The increment to which to round numbers. + * @return A Precision for chaining or passing to the NumberFormatter rounding() setter. + * @draft ICU 60 + * @provisional This API might change or be removed in a future release. + * @see NumberFormatter + */ + public static Precision increment(BigDecimal roundingIncrement) { + if (roundingIncrement != null && roundingIncrement.compareTo(BigDecimal.ZERO) > 0) { + return constructIncrement(roundingIncrement); + } else { + throw new IllegalArgumentException("Rounding increment must be positive and non-null"); + } + } + + /** + * Show numbers rounded and padded according to the rules for the currency unit. The most common + * rounding settings for currencies include <code>Precision.fixedFraction(2)</code>, + * <code>Precision.integer()</code>, and <code>Precision.increment(0.05)</code> for cash transactions + * ("nickel rounding"). + * + * <p> + * The exact rounding details will be resolved at runtime based on the currency unit specified in the + * NumberFormatter chain. To round according to the rules for one currency while displaying the + * symbol for another currency, the withCurrency() method can be called on the return value of this + * method. + * + * @param currencyUsage + * Either STANDARD (for digital transactions) or CASH (for transactions where the rounding + * increment may be limited by the available denominations of cash or coins). + * @return A CurrencyPrecision for chaining or passing to the NumberFormatter rounding() setter. + * @draft ICU 60 + * @provisional This API might change or be removed in a future release. + * @see NumberFormatter + */ + public static CurrencyPrecision currency(CurrencyUsage currencyUsage) { + if (currencyUsage != null) { + return constructCurrency(currencyUsage); + } else { + throw new IllegalArgumentException("CurrencyUsage must be non-null"); + } + } + + /** + * Sets the {@link java.math.RoundingMode} to use when picking the direction to round (up or down). + * Common values include HALF_EVEN, HALF_UP, and FLOOR. The default is HALF_EVEN. + * + * @param roundingMode + * The RoundingMode to use. + * @return A Precision for chaining. + * @deprecated ICU 62 Use the top-level rounding mode setting instead. This method is for backwards + * compatibility and will be removed in ICU 64. See + * http://bugs.icu-project.org/trac/ticket/13746 + * @see NumberFormatter + */ + @Deprecated + public Precision withMode(RoundingMode roundingMode) { + return withMode(RoundingUtils.mathContextUnlimited(roundingMode)); + } + + /** + * Sets a MathContext directly instead of RoundingMode. + * + * @internal + * @deprecated This API is ICU internal only. + */ + @Deprecated + public Precision withMode(MathContext mathContext) { + if (this.mathContext.equals(mathContext)) { + return this; + } + Precision other = (Precision) this.clone(); + other.mathContext = mathContext; + return other; + } + + /** + * @internal + * @deprecated This API is ICU internal only. + */ + @Deprecated + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + // Should not happen since parent is Object + throw new AssertionError(e); + } + } + + /** + * @internal + * @deprecated ICU 60 This API is ICU internal only. + */ + @Deprecated + public abstract void apply(DecimalQuantity value); + + ////////////////////////// + // PACKAGE-PRIVATE APIS // + ////////////////////////// + + static final InfiniteRounderImpl NONE = new InfiniteRounderImpl(); + + static final FractionRounderImpl FIXED_FRAC_0 = new FractionRounderImpl(0, 0); + static final FractionRounderImpl FIXED_FRAC_2 = new FractionRounderImpl(2, 2); + static final FractionRounderImpl DEFAULT_MAX_FRAC_6 = new FractionRounderImpl(0, 6); + + static final SignificantRounderImpl FIXED_SIG_2 = new SignificantRounderImpl(2, 2); + static final SignificantRounderImpl FIXED_SIG_3 = new SignificantRounderImpl(3, 3); + static final SignificantRounderImpl RANGE_SIG_2_3 = new SignificantRounderImpl(2, 3); + + static final FracSigRounderImpl COMPACT_STRATEGY = new FracSigRounderImpl(0, 0, 2, -1); + + static final IncrementRounderImpl NICKEL = new IncrementRounderImpl(BigDecimal.valueOf(0.05)); + + static final CurrencyRounderImpl MONETARY_STANDARD = new CurrencyRounderImpl(CurrencyUsage.STANDARD); + static final CurrencyRounderImpl MONETARY_CASH = new CurrencyRounderImpl(CurrencyUsage.CASH); + + static final PassThroughRounderImpl PASS_THROUGH = new PassThroughRounderImpl(); + + static Precision constructInfinite() { + return NONE; + } + + static FractionPrecision constructFraction(int minFrac, int maxFrac) { + if (minFrac == 0 && maxFrac == 0) { + return FIXED_FRAC_0; + } else if (minFrac == 2 && maxFrac == 2) { + return FIXED_FRAC_2; + } else if (minFrac == 0 && maxFrac == 6) { + return DEFAULT_MAX_FRAC_6; + } else { + return new FractionRounderImpl(minFrac, maxFrac); + } + } + + /** Assumes that minSig <= maxSig. */ + static Precision constructSignificant(int minSig, int maxSig) { + if (minSig == 2 && maxSig == 2) { + return FIXED_SIG_2; + } else if (minSig == 3 && maxSig == 3) { + return FIXED_SIG_3; + } else if (minSig == 2 && maxSig == 3) { + return RANGE_SIG_2_3; + } else { + return new SignificantRounderImpl(minSig, maxSig); + } + } + + static Precision constructFractionSignificant(FractionPrecision base_, int minSig, int maxSig) { + assert base_ instanceof FractionRounderImpl; + FractionRounderImpl base = (FractionRounderImpl) base_; + if (base.minFrac == 0 && base.maxFrac == 0 && minSig == 2 /* && maxSig == -1 */) { + return COMPACT_STRATEGY; + } else { + return new FracSigRounderImpl(base.minFrac, base.maxFrac, minSig, maxSig); + } + } + + static Precision constructIncrement(BigDecimal increment) { + // NOTE: .equals() is what we want, not .compareTo() + if (increment.equals(NICKEL.increment)) { + return NICKEL; + } else { + return new IncrementRounderImpl(increment); + } + } + + static CurrencyPrecision constructCurrency(CurrencyUsage usage) { + if (usage == CurrencyUsage.STANDARD) { + return MONETARY_STANDARD; + } else if (usage == CurrencyUsage.CASH) { + return MONETARY_CASH; + } else { + throw new AssertionError(); + } + } + + static Precision constructFromCurrency(CurrencyPrecision base_, Currency currency) { + assert base_ instanceof CurrencyRounderImpl; + CurrencyRounderImpl base = (CurrencyRounderImpl) base_; + double incrementDouble = currency.getRoundingIncrement(base.usage); + if (incrementDouble != 0.0) { + BigDecimal increment = BigDecimal.valueOf(incrementDouble); + return constructIncrement(increment); + } else { + int minMaxFrac = currency.getDefaultFractionDigits(base.usage); + return constructFraction(minMaxFrac, minMaxFrac); + } + } + + static Precision constructPassThrough() { + return PASS_THROUGH; + } + + /** + * Returns a valid working Rounder. If the Rounder is a CurrencyRounder, applies the given currency. + * Otherwise, simply passes through the argument. + * + * @param currency + * A currency object to use in case the input object needs it. + * @return A Rounder object ready for use. + */ + Precision withLocaleData(Currency currency) { + if (this instanceof CurrencyPrecision) { + return ((CurrencyPrecision) this).withCurrency(currency); + } else { + return this; + } + } + + /** + * Rounding endpoint used by Engineering and Compact notation. Chooses the most appropriate + * multiplier (magnitude adjustment), applies the adjustment, rounds, and returns the chosen + * multiplier. + * + * <p> + * In most cases, this is simple. However, when rounding the number causes it to cross a multiplier + * boundary, we need to re-do the rounding. For example, to display 999,999 in Engineering notation + * with 2 sigfigs, first you guess the multiplier to be -3. However, then you end up getting 1000E3, + * which is not the correct output. You then change your multiplier to be -6, and you get 1.0E6, + * which is correct. + * + * @param input + * The quantity to process. + * @param producer + * Function to call to return a multiplier based on a magnitude. + * @return The number of orders of magnitude the input was adjusted by this method. + */ + int chooseMultiplierAndApply(DecimalQuantity input, MultiplierProducer producer) { + // Do not call this method with zero. + assert !input.isZero(); + + // Perform the first attempt at rounding. + int magnitude = input.getMagnitude(); + int multiplier = producer.getMultiplier(magnitude); + input.adjustMagnitude(multiplier); + apply(input); + + // If the number rounded to zero, exit. + if (input.isZero()) { + return multiplier; + } + + // If the new magnitude after rounding is the same as it was before rounding, then we are done. + // This case applies to most numbers. + if (input.getMagnitude() == magnitude + multiplier) { + return multiplier; + } + + // If the above case DIDN'T apply, then we have a case like 99.9 -> 100 or 999.9 -> 1000: + // The number rounded up to the next magnitude. Check if the multiplier changes; if it doesn't, + // we do not need to make any more adjustments. + int _multiplier = producer.getMultiplier(magnitude + 1); + if (multiplier == _multiplier) { + return multiplier; + } + + // We have a case like 999.9 -> 1000, where the correct output is "1K", not "1000". + // Fix the magnitude and re-apply the rounding strategy. + input.adjustMagnitude(_multiplier - multiplier); + apply(input); + return _multiplier; + } + + /////////////// + // INTERNALS // + /////////////// + + static class InfiniteRounderImpl extends Precision { + + public InfiniteRounderImpl() { + } + + @Override + public void apply(DecimalQuantity value) { + value.roundToInfinity(); + value.setFractionLength(0, Integer.MAX_VALUE); + } + } + + static class FractionRounderImpl extends FractionPrecision { + final int minFrac; + final int maxFrac; + + public FractionRounderImpl(int minFrac, int maxFrac) { + this.minFrac = minFrac; + this.maxFrac = maxFrac; + } + + @Override + public void apply(DecimalQuantity value) { + value.roundToMagnitude(getRoundingMagnitudeFraction(maxFrac), mathContext); + value.setFractionLength(Math.max(0, -getDisplayMagnitudeFraction(minFrac)), + Integer.MAX_VALUE); + } + } + + static class SignificantRounderImpl extends Precision { + final int minSig; + final int maxSig; + + public SignificantRounderImpl(int minSig, int maxSig) { + this.minSig = minSig; + this.maxSig = maxSig; + } + + @Override + public void apply(DecimalQuantity value) { + value.roundToMagnitude(getRoundingMagnitudeSignificant(value, maxSig), mathContext); + value.setFractionLength(Math.max(0, -getDisplayMagnitudeSignificant(value, minSig)), + Integer.MAX_VALUE); + // Make sure that digits are displayed on zero. + if (value.isZero() && minSig > 0) { + value.setIntegerLength(1, Integer.MAX_VALUE); + } + } + + /** + * Version of {@link #apply} that obeys minInt constraints. Used for scientific notation + * compatibility mode. + */ + public void apply(DecimalQuantity quantity, int minInt) { + assert quantity.isZero(); + quantity.setFractionLength(minSig - minInt, Integer.MAX_VALUE); + } + } + + static class FracSigRounderImpl extends Precision { + final int minFrac; + final int maxFrac; + final int minSig; + final int maxSig; + + public FracSigRounderImpl(int minFrac, int maxFrac, int minSig, int maxSig) { + this.minFrac = minFrac; + this.maxFrac = maxFrac; + this.minSig = minSig; + this.maxSig = maxSig; + } + + @Override + public void apply(DecimalQuantity value) { + int displayMag = getDisplayMagnitudeFraction(minFrac); + int roundingMag = getRoundingMagnitudeFraction(maxFrac); + if (minSig == -1) { + // Max Sig override + int candidate = getRoundingMagnitudeSignificant(value, maxSig); + roundingMag = Math.max(roundingMag, candidate); + } else { + // Min Sig override + int candidate = getDisplayMagnitudeSignificant(value, minSig); + roundingMag = Math.min(roundingMag, candidate); + } + value.roundToMagnitude(roundingMag, mathContext); + value.setFractionLength(Math.max(0, -displayMag), Integer.MAX_VALUE); + } + } + + static class IncrementRounderImpl extends Precision { + final BigDecimal increment; + + public IncrementRounderImpl(BigDecimal increment) { + this.increment = increment; + } + + @Override + public void apply(DecimalQuantity value) { + value.roundToIncrement(increment, mathContext); + value.setFractionLength(increment.scale(), increment.scale()); + } + } + + static class CurrencyRounderImpl extends CurrencyPrecision { + final CurrencyUsage usage; + + public CurrencyRounderImpl(CurrencyUsage usage) { + this.usage = usage; + } + + @Override + public void apply(DecimalQuantity value) { + // Call .withCurrency() before .apply()! + throw new AssertionError(); + } + } + + static class PassThroughRounderImpl extends Precision { + + public PassThroughRounderImpl() { + } + + @Override + public void apply(DecimalQuantity value) { + // TODO: Assert that value has already been rounded + } + } + + private static int getRoundingMagnitudeFraction(int maxFrac) { + if (maxFrac == -1) { + return Integer.MIN_VALUE; + } + return -maxFrac; + } + + private static int getRoundingMagnitudeSignificant(DecimalQuantity value, int maxSig) { + if (maxSig == -1) { + return Integer.MIN_VALUE; + } + int magnitude = value.isZero() ? 0 : value.getMagnitude(); + return magnitude - maxSig + 1; + } + + private static int getDisplayMagnitudeFraction(int minFrac) { + if (minFrac == 0) { + return Integer.MAX_VALUE; + } + return -minFrac; + } + + private static int getDisplayMagnitudeSignificant(DecimalQuantity value, int minSig) { + int magnitude = value.isZero() ? 0 : value.getMagnitude(); + return magnitude - minSig + 1; + } +} diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/Rounder.java b/icu4j/main/classes/core/src/com/ibm/icu/number/Rounder.java index d38f99e926b..f9db169e364 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/Rounder.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/Rounder.java @@ -1,721 +1,11 @@ -// © 2017 and later: Unicode, Inc. and others. +// © 2018 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License package com.ibm.icu.number; -import java.math.BigDecimal; -import java.math.MathContext; -import java.math.RoundingMode; - -import com.ibm.icu.impl.number.DecimalQuantity; -import com.ibm.icu.impl.number.MultiplierProducer; -import com.ibm.icu.impl.number.RoundingUtils; -import com.ibm.icu.util.Currency; -import com.ibm.icu.util.Currency.CurrencyUsage; - /** - * A class that defines the rounding strategy to be used when formatting numbers in NumberFormatter. - * - * <p> - * To create a Rounder, use one of the factory methods. - * - * @draft ICU 60 - * @provisional This API might change or be removed in a future release. - * @see NumberFormatter + * @deprecated ICU 62 Use {@link Precision} instead. This class is for backwards compatibility and will + * be removed in ICU 64. See http://bugs.icu-project.org/trac/ticket/13746 */ -public abstract class Rounder implements Cloneable { - - /* package-private final */ MathContext mathContext; - - /* package-private */ Rounder() { - mathContext = RoundingUtils.DEFAULT_MATH_CONTEXT_UNLIMITED; - } - - /** - * Show all available digits to full precision. - * - * <p> - * <strong>NOTE:</strong> When formatting a <em>double</em>, this method, along with - * {@link #minFraction} and {@link #minDigits}, will trigger complex algorithm similar to - * <em>Dragon4</em> to determine the low-order digits and the number of digits to display based on - * the value of the double. If the number of fraction places or significant digits can be bounded, - * consider using {@link #maxFraction} or {@link #maxDigits} instead to maximize performance. For - * more information, read the following blog post. - * - * <p> - * http://www.serpentine.com/blog/2011/06/29/here-be-dragons-advances-in-problems-you-didnt-even-know-you-had/ - * - * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. - * @draft ICU 60 - * @provisional This API might change or be removed in a future release. - * @see NumberFormatter - */ - public static Rounder unlimited() { - return constructInfinite(); - } - - /** - * Show numbers rounded if necessary to the nearest integer. - * - * @return A FractionRounder for chaining or passing to the NumberFormatter rounding() setter. - * @draft ICU 60 - * @provisional This API might change or be removed in a future release. - * @see NumberFormatter - */ - public static FractionRounder integer() { - return constructFraction(0, 0); - } - - /** - * Show numbers rounded if necessary to a certain number of fraction places (numerals after the - * decimal separator). Additionally, pad with zeros to ensure that this number of places are always - * shown. - * - * <p> - * Example output with minMaxFractionPlaces = 3: - * - * <p> - * 87,650.000<br> - * 8,765.000<br> - * 876.500<br> - * 87.650<br> - * 8.765<br> - * 0.876<br> - * 0.088<br> - * 0.009<br> - * 0.000 (zero) - * - * <p> - * This method is equivalent to {@link #minMaxFraction} with both arguments equal. - * - * @param minMaxFractionPlaces - * The minimum and maximum number of numerals to display after the decimal separator - * (rounding if too long or padding with zeros if too short). - * @return A FractionRounder for chaining or passing to the NumberFormatter rounding() setter. - * @draft ICU 60 - * @provisional This API might change or be removed in a future release. - * @see NumberFormatter - */ - public static FractionRounder fixedFraction(int minMaxFractionPlaces) { - if (minMaxFractionPlaces >= 0 && minMaxFractionPlaces <= RoundingUtils.MAX_INT_FRAC_SIG) { - return constructFraction(minMaxFractionPlaces, minMaxFractionPlaces); - } else { - throw new IllegalArgumentException("Fraction length must be between 0 and " - + RoundingUtils.MAX_INT_FRAC_SIG - + " (inclusive)"); - } - } - - /** - * Always show at least a certain number of fraction places after the decimal separator, padding with - * zeros if necessary. Do not perform rounding (display numbers to their full precision). - * - * <p> - * <strong>NOTE:</strong> If you are formatting <em>doubles</em>, see the performance note in - * {@link #unlimited}. - * - * @param minFractionPlaces - * The minimum number of numerals to display after the decimal separator (padding with - * zeros if necessary). - * @return A FractionRounder for chaining or passing to the NumberFormatter rounding() setter. - * @draft ICU 60 - * @provisional This API might change or be removed in a future release. - * @see NumberFormatter - */ - public static FractionRounder minFraction(int minFractionPlaces) { - if (minFractionPlaces >= 0 && minFractionPlaces <= RoundingUtils.MAX_INT_FRAC_SIG) { - return constructFraction(minFractionPlaces, -1); - } else { - throw new IllegalArgumentException("Fraction length must be between 0 and " - + RoundingUtils.MAX_INT_FRAC_SIG - + " (inclusive)"); - } - } - - /** - * Show numbers rounded if necessary to a certain number of fraction places (numerals after the - * decimal separator). Unlike the other fraction rounding strategies, this strategy does <em>not</em> - * pad zeros to the end of the number. - * - * @param maxFractionPlaces - * The maximum number of numerals to display after the decimal mark (rounding if - * necessary). - * @return A FractionRounder for chaining or passing to the NumberFormatter rounding() setter. - * @draft ICU 60 - * @provisional This API might change or be removed in a future release. - * @see NumberFormatter - */ - public static FractionRounder maxFraction(int maxFractionPlaces) { - if (maxFractionPlaces >= 0 && maxFractionPlaces <= RoundingUtils.MAX_INT_FRAC_SIG) { - return constructFraction(0, maxFractionPlaces); - } else { - throw new IllegalArgumentException("Fraction length must be between 0 and " - + RoundingUtils.MAX_INT_FRAC_SIG - + " (inclusive)"); - } - } - - /** - * Show numbers rounded if necessary to a certain number of fraction places (numerals after the - * decimal separator); in addition, always show at least a certain number of places after the decimal - * separator, padding with zeros if necessary. - * - * @param minFractionPlaces - * The minimum number of numerals to display after the decimal separator (padding with - * zeros if necessary). - * @param maxFractionPlaces - * The maximum number of numerals to display after the decimal separator (rounding if - * necessary). - * @return A FractionRounder for chaining or passing to the NumberFormatter rounding() setter. - * @draft ICU 60 - * @provisional This API might change or be removed in a future release. - * @see NumberFormatter - */ - public static FractionRounder minMaxFraction(int minFractionPlaces, int maxFractionPlaces) { - if (minFractionPlaces >= 0 - && maxFractionPlaces <= RoundingUtils.MAX_INT_FRAC_SIG - && minFractionPlaces <= maxFractionPlaces) { - return constructFraction(minFractionPlaces, maxFractionPlaces); - } else { - throw new IllegalArgumentException("Fraction length must be between 0 and " - + RoundingUtils.MAX_INT_FRAC_SIG - + " (inclusive)"); - } - } - - /** - * Show numbers rounded if necessary to a certain number of significant digits or significant - * figures. Additionally, pad with zeros to ensure that this number of significant digits/figures are - * always shown. - * - * <p> - * This method is equivalent to {@link #minMaxDigits} with both arguments equal. - * - * @param minMaxSignificantDigits - * The minimum and maximum number of significant digits to display (rounding if too long - * or padding with zeros if too short). - * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. - * @draft ICU 60 - * @provisional This API might change or be removed in a future release. - * @see NumberFormatter - */ - public static Rounder fixedDigits(int minMaxSignificantDigits) { - if (minMaxSignificantDigits >= 1 && minMaxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) { - return constructSignificant(minMaxSignificantDigits, minMaxSignificantDigits); - } else { - throw new IllegalArgumentException("Significant digits must be between 1 and " - + RoundingUtils.MAX_INT_FRAC_SIG - + " (inclusive)"); - } - } - - /** - * Always show at least a certain number of significant digits/figures, padding with zeros if - * necessary. Do not perform rounding (display numbers to their full precision). - * - * <p> - * <strong>NOTE:</strong> If you are formatting <em>doubles</em>, see the performance note in - * {@link #unlimited}. - * - * @param minSignificantDigits - * The minimum number of significant digits to display (padding with zeros if too short). - * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. - * @draft ICU 60 - * @provisional This API might change or be removed in a future release. - * @see NumberFormatter - */ - public static Rounder minDigits(int minSignificantDigits) { - if (minSignificantDigits >= 1 && minSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) { - return constructSignificant(minSignificantDigits, -1); - } else { - throw new IllegalArgumentException("Significant digits must be between 1 and " - + RoundingUtils.MAX_INT_FRAC_SIG - + " (inclusive)"); - } - } - - /** - * Show numbers rounded if necessary to a certain number of significant digits/figures. - * - * @param maxSignificantDigits - * The maximum number of significant digits to display (rounding if too long). - * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. - * @draft ICU 60 - * @provisional This API might change or be removed in a future release. - * @see NumberFormatter - */ - public static Rounder maxDigits(int maxSignificantDigits) { - if (maxSignificantDigits >= 1 && maxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) { - return constructSignificant(1, maxSignificantDigits); - } else { - throw new IllegalArgumentException("Significant digits must be between 1 and " - + RoundingUtils.MAX_INT_FRAC_SIG - + " (inclusive)"); - } - } - - /** - * Show numbers rounded if necessary to a certain number of significant digits/figures; in addition, - * always show at least a certain number of significant digits, padding with zeros if necessary. - * - * @param minSignificantDigits - * The minimum number of significant digits to display (padding with zeros if necessary). - * @param maxSignificantDigits - * The maximum number of significant digits to display (rounding if necessary). - * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. - * @draft ICU 60 - * @provisional This API might change or be removed in a future release. - * @see NumberFormatter - */ - public static Rounder minMaxDigits(int minSignificantDigits, int maxSignificantDigits) { - if (minSignificantDigits >= 1 - && maxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG - && minSignificantDigits <= maxSignificantDigits) { - return constructSignificant(minSignificantDigits, maxSignificantDigits); - } else { - throw new IllegalArgumentException("Significant digits must be between 1 and " - + RoundingUtils.MAX_INT_FRAC_SIG - + " (inclusive)"); - } - } - - /** - * Show numbers rounded if necessary to the closest multiple of a certain rounding increment. For - * example, if the rounding increment is 0.5, then round 1.2 to 1 and round 1.3 to 1.5. - * - * <p> - * In order to ensure that numbers are padded to the appropriate number of fraction places, set the - * scale on the rounding increment BigDecimal. For example, to round to the nearest 0.5 and always - * display 2 numerals after the decimal separator (to display 1.2 as "1.00" and 1.3 as "1.50"), you - * can run: - * - * <pre> - * Rounder.increment(new BigDecimal("0.50")) - * </pre> - * - * <p> - * For more information on the scale of Java BigDecimal, see {@link java.math.BigDecimal#scale()}. - * - * @param roundingIncrement - * The increment to which to round numbers. - * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. - * @draft ICU 60 - * @provisional This API might change or be removed in a future release. - * @see NumberFormatter - */ - public static Rounder increment(BigDecimal roundingIncrement) { - if (roundingIncrement != null && roundingIncrement.compareTo(BigDecimal.ZERO) > 0) { - return constructIncrement(roundingIncrement); - } else { - throw new IllegalArgumentException("Rounding increment must be positive and non-null"); - } - } - - /** - * Show numbers rounded and padded according to the rules for the currency unit. The most common - * rounding settings for currencies include <code>Rounder.fixedFraction(2)</code>, - * <code>Rounder.integer()</code>, and <code>Rounder.increment(0.05)</code> for cash transactions - * ("nickel rounding"). - * - * <p> - * The exact rounding details will be resolved at runtime based on the currency unit specified in the - * NumberFormatter chain. To round according to the rules for one currency while displaying the - * symbol for another currency, the withCurrency() method can be called on the return value of this - * method. - * - * @param currencyUsage - * Either STANDARD (for digital transactions) or CASH (for transactions where the rounding - * increment may be limited by the available denominations of cash or coins). - * @return A CurrencyRounder for chaining or passing to the NumberFormatter rounding() setter. - * @draft ICU 60 - * @provisional This API might change or be removed in a future release. - * @see NumberFormatter - */ - public static CurrencyRounder currency(CurrencyUsage currencyUsage) { - if (currencyUsage != null) { - return constructCurrency(currencyUsage); - } else { - throw new IllegalArgumentException("CurrencyUsage must be non-null"); - } - } - - /** - * Sets the {@link java.math.RoundingMode} to use when picking the direction to round (up or down). - * Common values include HALF_EVEN, HALF_UP, and FLOOR. The default is HALF_EVEN. - * - * @param roundingMode - * The RoundingMode to use. - * @return A Rounder for chaining. - * @draft ICU 60 - * @provisional This API might change or be removed in a future release. - * @see NumberFormatter - */ - public Rounder withMode(RoundingMode roundingMode) { - return withMode(RoundingUtils.mathContextUnlimited(roundingMode)); - } - - /** - * Sets a MathContext directly instead of RoundingMode. - * - * @internal - * @deprecated This API is ICU internal only. - */ - @Deprecated - public Rounder withMode(MathContext mathContext) { - if (this.mathContext.equals(mathContext)) { - return this; - } - Rounder other = (Rounder) this.clone(); - other.mathContext = mathContext; - return other; - } - - /** - * @internal - * @deprecated This API is ICU internal only. - */ - @Deprecated - @Override - public Object clone() { - try { - return super.clone(); - } catch (CloneNotSupportedException e) { - // Should not happen since parent is Object - throw new AssertionError(e); - } - } - - /** - * @internal - * @deprecated ICU 60 This API is ICU internal only. - */ - @Deprecated - public abstract void apply(DecimalQuantity value); - - ////////////////////////// - // PACKAGE-PRIVATE APIS // - ////////////////////////// - - static final InfiniteRounderImpl NONE = new InfiniteRounderImpl(); - - static final FractionRounderImpl FIXED_FRAC_0 = new FractionRounderImpl(0, 0); - static final FractionRounderImpl FIXED_FRAC_2 = new FractionRounderImpl(2, 2); - static final FractionRounderImpl DEFAULT_MAX_FRAC_6 = new FractionRounderImpl(0, 6); - - static final SignificantRounderImpl FIXED_SIG_2 = new SignificantRounderImpl(2, 2); - static final SignificantRounderImpl FIXED_SIG_3 = new SignificantRounderImpl(3, 3); - static final SignificantRounderImpl RANGE_SIG_2_3 = new SignificantRounderImpl(2, 3); - - static final FracSigRounderImpl COMPACT_STRATEGY = new FracSigRounderImpl(0, 0, 2, -1); - - static final IncrementRounderImpl NICKEL = new IncrementRounderImpl(BigDecimal.valueOf(0.05)); - - static final CurrencyRounderImpl MONETARY_STANDARD = new CurrencyRounderImpl(CurrencyUsage.STANDARD); - static final CurrencyRounderImpl MONETARY_CASH = new CurrencyRounderImpl(CurrencyUsage.CASH); - - static final PassThroughRounderImpl PASS_THROUGH = new PassThroughRounderImpl(); - - static Rounder constructInfinite() { - return NONE; - } - - static FractionRounder constructFraction(int minFrac, int maxFrac) { - if (minFrac == 0 && maxFrac == 0) { - return FIXED_FRAC_0; - } else if (minFrac == 2 && maxFrac == 2) { - return FIXED_FRAC_2; - } else if (minFrac == 0 && maxFrac == 6) { - return DEFAULT_MAX_FRAC_6; - } else { - return new FractionRounderImpl(minFrac, maxFrac); - } - } - - /** Assumes that minSig <= maxSig. */ - static Rounder constructSignificant(int minSig, int maxSig) { - if (minSig == 2 && maxSig == 2) { - return FIXED_SIG_2; - } else if (minSig == 3 && maxSig == 3) { - return FIXED_SIG_3; - } else if (minSig == 2 && maxSig == 3) { - return RANGE_SIG_2_3; - } else { - return new SignificantRounderImpl(minSig, maxSig); - } - } - - static Rounder constructFractionSignificant(FractionRounder base_, int minSig, int maxSig) { - assert base_ instanceof FractionRounderImpl; - FractionRounderImpl base = (FractionRounderImpl) base_; - if (base.minFrac == 0 && base.maxFrac == 0 && minSig == 2 /* && maxSig == -1 */) { - return COMPACT_STRATEGY; - } else { - return new FracSigRounderImpl(base.minFrac, base.maxFrac, minSig, maxSig); - } - } - - static Rounder constructIncrement(BigDecimal increment) { - // NOTE: .equals() is what we want, not .compareTo() - if (increment.equals(NICKEL.increment)) { - return NICKEL; - } else { - return new IncrementRounderImpl(increment); - } - } - - static CurrencyRounder constructCurrency(CurrencyUsage usage) { - if (usage == CurrencyUsage.STANDARD) { - return MONETARY_STANDARD; - } else if (usage == CurrencyUsage.CASH) { - return MONETARY_CASH; - } else { - throw new AssertionError(); - } - } - - static Rounder constructFromCurrency(CurrencyRounder base_, Currency currency) { - assert base_ instanceof CurrencyRounderImpl; - CurrencyRounderImpl base = (CurrencyRounderImpl) base_; - double incrementDouble = currency.getRoundingIncrement(base.usage); - if (incrementDouble != 0.0) { - BigDecimal increment = BigDecimal.valueOf(incrementDouble); - return constructIncrement(increment); - } else { - int minMaxFrac = currency.getDefaultFractionDigits(base.usage); - return constructFraction(minMaxFrac, minMaxFrac); - } - } - - static Rounder constructPassThrough() { - return PASS_THROUGH; - } - - /** - * Returns a valid working Rounder. If the Rounder is a CurrencyRounder, applies the given currency. - * Otherwise, simply passes through the argument. - * - * @param currency - * A currency object to use in case the input object needs it. - * @return A Rounder object ready for use. - */ - Rounder withLocaleData(Currency currency) { - if (this instanceof CurrencyRounder) { - return ((CurrencyRounder) this).withCurrency(currency); - } else { - return this; - } - } - - /** - * Rounding endpoint used by Engineering and Compact notation. Chooses the most appropriate - * multiplier (magnitude adjustment), applies the adjustment, rounds, and returns the chosen - * multiplier. - * - * <p> - * In most cases, this is simple. However, when rounding the number causes it to cross a multiplier - * boundary, we need to re-do the rounding. For example, to display 999,999 in Engineering notation - * with 2 sigfigs, first you guess the multiplier to be -3. However, then you end up getting 1000E3, - * which is not the correct output. You then change your multiplier to be -6, and you get 1.0E6, - * which is correct. - * - * @param input - * The quantity to process. - * @param producer - * Function to call to return a multiplier based on a magnitude. - * @return The number of orders of magnitude the input was adjusted by this method. - */ - int chooseMultiplierAndApply(DecimalQuantity input, MultiplierProducer producer) { - // Do not call this method with zero. - assert !input.isZero(); - - // Perform the first attempt at rounding. - int magnitude = input.getMagnitude(); - int multiplier = producer.getMultiplier(magnitude); - input.adjustMagnitude(multiplier); - apply(input); - - // If the number rounded to zero, exit. - if (input.isZero()) { - return multiplier; - } - - // If the new magnitude after rounding is the same as it was before rounding, then we are done. - // This case applies to most numbers. - if (input.getMagnitude() == magnitude + multiplier) { - return multiplier; - } - - // If the above case DIDN'T apply, then we have a case like 99.9 -> 100 or 999.9 -> 1000: - // The number rounded up to the next magnitude. Check if the multiplier changes; if it doesn't, - // we do not need to make any more adjustments. - int _multiplier = producer.getMultiplier(magnitude + 1); - if (multiplier == _multiplier) { - return multiplier; - } - - // We have a case like 999.9 -> 1000, where the correct output is "1K", not "1000". - // Fix the magnitude and re-apply the rounding strategy. - input.adjustMagnitude(_multiplier - multiplier); - apply(input); - return _multiplier; - } - - /////////////// - // INTERNALS // - /////////////// - - static class InfiniteRounderImpl extends Rounder { - - public InfiniteRounderImpl() { - } - - @Override - public void apply(DecimalQuantity value) { - value.roundToInfinity(); - value.setFractionLength(0, Integer.MAX_VALUE); - } - } - - static class FractionRounderImpl extends FractionRounder { - final int minFrac; - final int maxFrac; - - public FractionRounderImpl(int minFrac, int maxFrac) { - this.minFrac = minFrac; - this.maxFrac = maxFrac; - } - - @Override - public void apply(DecimalQuantity value) { - value.roundToMagnitude(getRoundingMagnitudeFraction(maxFrac), mathContext); - value.setFractionLength(Math.max(0, -getDisplayMagnitudeFraction(minFrac)), - Integer.MAX_VALUE); - } - } - - static class SignificantRounderImpl extends Rounder { - final int minSig; - final int maxSig; - - public SignificantRounderImpl(int minSig, int maxSig) { - this.minSig = minSig; - this.maxSig = maxSig; - } - - @Override - public void apply(DecimalQuantity value) { - value.roundToMagnitude(getRoundingMagnitudeSignificant(value, maxSig), mathContext); - value.setFractionLength(Math.max(0, -getDisplayMagnitudeSignificant(value, minSig)), - Integer.MAX_VALUE); - // Make sure that digits are displayed on zero. - if (value.isZero() && minSig > 0) { - value.setIntegerLength(1, Integer.MAX_VALUE); - } - } - - /** - * Version of {@link #apply} that obeys minInt constraints. Used for scientific notation - * compatibility mode. - */ - public void apply(DecimalQuantity quantity, int minInt) { - assert quantity.isZero(); - quantity.setFractionLength(minSig - minInt, Integer.MAX_VALUE); - } - } - - static class FracSigRounderImpl extends Rounder { - final int minFrac; - final int maxFrac; - final int minSig; - final int maxSig; - - public FracSigRounderImpl(int minFrac, int maxFrac, int minSig, int maxSig) { - this.minFrac = minFrac; - this.maxFrac = maxFrac; - this.minSig = minSig; - this.maxSig = maxSig; - } - - @Override - public void apply(DecimalQuantity value) { - int displayMag = getDisplayMagnitudeFraction(minFrac); - int roundingMag = getRoundingMagnitudeFraction(maxFrac); - if (minSig == -1) { - // Max Sig override - int candidate = getRoundingMagnitudeSignificant(value, maxSig); - roundingMag = Math.max(roundingMag, candidate); - } else { - // Min Sig override - int candidate = getDisplayMagnitudeSignificant(value, minSig); - roundingMag = Math.min(roundingMag, candidate); - } - value.roundToMagnitude(roundingMag, mathContext); - value.setFractionLength(Math.max(0, -displayMag), Integer.MAX_VALUE); - } - } - - static class IncrementRounderImpl extends Rounder { - final BigDecimal increment; - - public IncrementRounderImpl(BigDecimal increment) { - this.increment = increment; - } - - @Override - public void apply(DecimalQuantity value) { - value.roundToIncrement(increment, mathContext); - value.setFractionLength(increment.scale(), increment.scale()); - } - } - - static class CurrencyRounderImpl extends CurrencyRounder { - final CurrencyUsage usage; - - public CurrencyRounderImpl(CurrencyUsage usage) { - this.usage = usage; - } - - @Override - public void apply(DecimalQuantity value) { - // Call .withCurrency() before .apply()! - throw new AssertionError(); - } - } - - static class PassThroughRounderImpl extends Rounder { - - public PassThroughRounderImpl() { - } - - @Override - public void apply(DecimalQuantity value) { - // TODO: Assert that value has already been rounded - } - } - - private static int getRoundingMagnitudeFraction(int maxFrac) { - if (maxFrac == -1) { - return Integer.MIN_VALUE; - } - return -maxFrac; - } - - private static int getRoundingMagnitudeSignificant(DecimalQuantity value, int maxSig) { - if (maxSig == -1) { - return Integer.MIN_VALUE; - } - int magnitude = value.isZero() ? 0 : value.getMagnitude(); - return magnitude - maxSig + 1; - } - - private static int getDisplayMagnitudeFraction(int minFrac) { - if (minFrac == 0) { - return Integer.MAX_VALUE; - } - return -minFrac; - } - - private static int getDisplayMagnitudeSignificant(DecimalQuantity value, int minSig) { - int magnitude = value.isZero() ? 0 : value.getMagnitude(); - return magnitude - minSig + 1; - } +@Deprecated +public abstract class Rounder extends Precision { } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/ScientificNotation.java b/icu4j/main/classes/core/src/com/ibm/icu/number/ScientificNotation.java index e121fb7db81..d3f62b963e3 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/ScientificNotation.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/ScientificNotation.java @@ -10,7 +10,7 @@ import com.ibm.icu.impl.number.MultiplierProducer; import com.ibm.icu.impl.number.NumberStringBuilder; import com.ibm.icu.impl.number.RoundingUtils; import com.ibm.icu.number.NumberFormatter.SignDisplay; -import com.ibm.icu.number.Rounder.SignificantRounderImpl; +import com.ibm.icu.number.Precision.SignificantRounderImpl; import com.ibm.icu.text.DecimalFormatSymbols; import com.ibm.icu.text.NumberFormat; @@ -159,22 +159,22 @@ public class ScientificNotation extends Notation implements Cloneable { @Override public MicroProps processQuantity(DecimalQuantity quantity) { MicroProps micros = parent.processQuantity(quantity); - assert micros.rounding != null; + assert micros.rounder != null; // Treat zero as if it had magnitude 0 int exponent; if (quantity.isZero()) { - if (notation.requireMinInt && micros.rounding instanceof SignificantRounderImpl) { + if (notation.requireMinInt && micros.rounder instanceof SignificantRounderImpl) { // Show "00.000E0" on pattern "00.000E0" - ((SignificantRounderImpl) micros.rounding).apply(quantity, + ((SignificantRounderImpl) micros.rounder).apply(quantity, notation.engineeringInterval); exponent = 0; } else { - micros.rounding.apply(quantity); + micros.rounder.apply(quantity); exponent = 0; } } else { - exponent = -micros.rounding.chooseMultiplierAndApply(quantity, this); + exponent = -micros.rounder.chooseMultiplierAndApply(quantity, this); } // Add the Modifier for the scientific format. @@ -191,7 +191,7 @@ public class ScientificNotation extends Notation implements Cloneable { } // We already performed rounding. Do not perform it again. - micros.rounding = Rounder.constructPassThrough(); + micros.rounder = Precision.constructPassThrough(); return micros; } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/MeasureFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/MeasureFormat.java index 0fe0220adc0..175d92e8d40 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/MeasureFormat.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/MeasureFormat.java @@ -40,7 +40,7 @@ import com.ibm.icu.number.FormattedNumber; import com.ibm.icu.number.LocalizedNumberFormatter; import com.ibm.icu.number.NumberFormatter; import com.ibm.icu.number.NumberFormatter.UnitWidth; -import com.ibm.icu.number.Rounder; +import com.ibm.icu.number.Precision; import com.ibm.icu.text.ListFormatter.FormattedListBuilder; import com.ibm.icu.util.Currency; import com.ibm.icu.util.ICUUncheckedIOException; @@ -738,7 +738,7 @@ public class MeasureFormat extends UFormat { } else { assert type == NUMBER_FORMATTER_INTEGER; formatter = getNumberFormatter().unit(unit).perUnit(perUnit).unitWidth(formatWidth.unitWidth) - .rounding(Rounder.integer().withMode(RoundingMode.DOWN)); + .rounding(Precision.integer().withMode(RoundingMode.DOWN)); } formatter3 = formatter2; formatter2 = formatter1; diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/ExhaustiveNumberTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/ExhaustiveNumberTest.java index b924b0525b3..bca8f848a6a 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/ExhaustiveNumberTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/ExhaustiveNumberTest.java @@ -15,7 +15,7 @@ import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD; import com.ibm.icu.impl.number.parse.UnicodeSetStaticCache.Key; import com.ibm.icu.lang.UCharacter; import com.ibm.icu.number.NumberFormatter; -import com.ibm.icu.number.Rounder; +import com.ibm.icu.number.Precision; import com.ibm.icu.text.DecimalFormatSymbols; import com.ibm.icu.text.UnicodeSet; import com.ibm.icu.util.ULocale; @@ -92,7 +92,7 @@ public class ExhaustiveNumberTest extends TestFmwk { BigDecimal ten10000 = BigDecimal.valueOf(10).pow(10000); BigDecimal longFraction = ten10000.subtract(BigDecimal.ONE).divide(ten10000); String expected = longFraction.toPlainString(); - String actual = NumberFormatter.withLocale(ULocale.ENGLISH).rounding(Rounder.unlimited()) + String actual = NumberFormatter.withLocale(ULocale.ENGLISH).rounding(Precision.unlimited()) .format(longFraction).toString(); assertEquals("All digits should be displayed", expected, actual); } diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java index 6a07b30d505..5b99460b8fe 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java @@ -30,7 +30,7 @@ import com.ibm.icu.impl.number.Padder.PadPosition; import com.ibm.icu.impl.number.PatternStringParser; import com.ibm.icu.number.CompactNotation; import com.ibm.icu.number.FormattedNumber; -import com.ibm.icu.number.FractionRounder; +import com.ibm.icu.number.FractionPrecision; import com.ibm.icu.number.IntegerWidth; import com.ibm.icu.number.LocalizedNumberFormatter; import com.ibm.icu.number.Notation; @@ -39,7 +39,7 @@ import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay; import com.ibm.icu.number.NumberFormatter.GroupingStrategy; import com.ibm.icu.number.NumberFormatter.SignDisplay; import com.ibm.icu.number.NumberFormatter.UnitWidth; -import com.ibm.icu.number.Rounder; +import com.ibm.icu.number.Precision; import com.ibm.icu.number.Scale; import com.ibm.icu.number.ScientificNotation; import com.ibm.icu.number.UnlocalizedNumberFormatter; @@ -510,7 +510,7 @@ public class NumberFormatterApiTest { "MeasureUnit form without {0} in CLDR pattern and wide base form", "measure-unit/temperature-kelvin .00000000000000000000 unit-width-full-name", NumberFormatter.with() - .rounding(Rounder.fixedFraction(20)) + .precision(Precision.fixedFraction(20)) .unit(MeasureUnit.KELVIN) .unitWidth(UnitWidth.FULL_NAME), ULocale.forLanguageTag("es-MX"), @@ -777,8 +777,8 @@ public class NumberFormatterApiTest { public void roundingFraction() { assertFormatDescending( "Integer", - "round-integer", - NumberFormatter.with().rounding(Rounder.integer()), + "precision-integer", + NumberFormatter.with().precision(Precision.integer()), ULocale.ENGLISH, "87,650", "8,765", @@ -793,7 +793,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Fixed Fraction", ".000", - NumberFormatter.with().rounding(Rounder.fixedFraction(3)), + NumberFormatter.with().precision(Precision.fixedFraction(3)), ULocale.ENGLISH, "87,650.000", "8,765.000", @@ -808,7 +808,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Min Fraction", ".0+", - NumberFormatter.with().rounding(Rounder.minFraction(1)), + NumberFormatter.with().precision(Precision.minFraction(1)), ULocale.ENGLISH, "87,650.0", "8,765.0", @@ -823,7 +823,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Max Fraction", ".#", - NumberFormatter.with().rounding(Rounder.maxFraction(1)), + NumberFormatter.with().precision(Precision.maxFraction(1)), ULocale.ENGLISH, "87,650", "8,765", @@ -838,7 +838,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Min/Max Fraction", ".0##", - NumberFormatter.with().rounding(Rounder.minMaxFraction(1, 3)), + NumberFormatter.with().precision(Precision.minMaxFraction(1, 3)), ULocale.ENGLISH, "87,650.0", "8,765.0", @@ -856,7 +856,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Fixed Significant", "@@@", - NumberFormatter.with().rounding(Rounder.fixedDigits(3)), + NumberFormatter.with().precision(Precision.fixedSignificantDigits(3)), ULocale.ENGLISH, -98, "-98.0"); @@ -864,7 +864,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Fixed Significant Rounding", "@@@", - NumberFormatter.with().rounding(Rounder.fixedDigits(3)), + NumberFormatter.with().precision(Precision.fixedSignificantDigits(3)), ULocale.ENGLISH, -98.7654321, "-98.8"); @@ -872,7 +872,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Fixed Significant Zero", "@@@", - NumberFormatter.with().rounding(Rounder.fixedDigits(3)), + NumberFormatter.with().precision(Precision.fixedSignificantDigits(3)), ULocale.ENGLISH, 0, "0.00"); @@ -880,7 +880,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Min Significant", "@@+", - NumberFormatter.with().rounding(Rounder.minDigits(2)), + NumberFormatter.with().precision(Precision.minSignificantDigits(2)), ULocale.ENGLISH, -9, "-9.0"); @@ -888,7 +888,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Max Significant", "@###", - NumberFormatter.with().rounding(Rounder.maxDigits(4)), + NumberFormatter.with().precision(Precision.maxSignificantDigits(4)), ULocale.ENGLISH, 98.7654321, "98.77"); @@ -896,7 +896,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Min/Max Significant", "@@@#", - NumberFormatter.with().rounding(Rounder.minMaxDigits(3, 4)), + NumberFormatter.with().precision(Precision.minMaxSignificantDigits(3, 4)), ULocale.ENGLISH, 9.99999, "10.0"); @@ -904,7 +904,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Fixed Significant on zero with zero integer width", "@ integer-width/+", - NumberFormatter.with().rounding(Rounder.fixedDigits(1)).integerWidth(IntegerWidth.zeroFillTo(0)), + NumberFormatter.with().precision(Precision.fixedSignificantDigits(1)).integerWidth(IntegerWidth.zeroFillTo(0)), ULocale.ENGLISH, 0, "0"); @@ -912,7 +912,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Fixed Significant on zero with lots of integer width", "@ integer-width/+000", - NumberFormatter.with().rounding(Rounder.fixedDigits(1)).integerWidth(IntegerWidth.zeroFillTo(3)), + NumberFormatter.with().precision(Precision.fixedSignificantDigits(1)).integerWidth(IntegerWidth.zeroFillTo(3)), ULocale.ENGLISH, 0, "000"); @@ -923,7 +923,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Basic Significant", // for comparison "@#", - NumberFormatter.with().rounding(Rounder.maxDigits(2)), + NumberFormatter.with().precision(Precision.maxSignificantDigits(2)), ULocale.ENGLISH, "88,000", "8,800", @@ -938,7 +938,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "FracSig minMaxFrac minSig", ".0#/@@@+", - NumberFormatter.with().rounding(Rounder.minMaxFraction(1, 2).withMinDigits(3)), + NumberFormatter.with().precision(Precision.minMaxFraction(1, 2).withMinDigits(3)), ULocale.ENGLISH, "87,650.0", "8,765.0", @@ -953,7 +953,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "FracSig minMaxFrac maxSig A", ".0##/@#", - NumberFormatter.with().rounding(Rounder.minMaxFraction(1, 3).withMaxDigits(2)), + NumberFormatter.with().precision(Precision.minMaxFraction(1, 3).withMaxDigits(2)), ULocale.ENGLISH, "88,000.0", // maxSig beats maxFrac "8,800.0", // maxSig beats maxFrac @@ -968,7 +968,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "FracSig minMaxFrac maxSig B", ".00/@#", - NumberFormatter.with().rounding(Rounder.fixedFraction(2).withMaxDigits(2)), + NumberFormatter.with().precision(Precision.fixedFraction(2).withMaxDigits(2)), ULocale.ENGLISH, "88,000.00", // maxSig beats maxFrac "8,800.00", // maxSig beats maxFrac @@ -983,7 +983,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "FracSig with trailing zeros A", ".00/@@@+", - NumberFormatter.with().rounding(Rounder.fixedFraction(2).withMinDigits(3)), + NumberFormatter.with().precision(Precision.fixedFraction(2).withMinDigits(3)), ULocale.ENGLISH, 0.1, "0.10"); @@ -991,7 +991,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "FracSig with trailing zeros B", ".00/@@@+", - NumberFormatter.with().rounding(Rounder.fixedFraction(2).withMinDigits(3)), + NumberFormatter.with().precision(Precision.fixedFraction(2).withMinDigits(3)), ULocale.ENGLISH, 0.0999999, "0.10"); @@ -1001,8 +1001,8 @@ public class NumberFormatterApiTest { public void roundingOther() { assertFormatDescending( "Rounding None", - "round-unlimited", - NumberFormatter.with().rounding(Rounder.unlimited()), + "precision-unlimited", + NumberFormatter.with().precision(Precision.unlimited()), ULocale.ENGLISH, "87,650", "8,765", @@ -1016,8 +1016,8 @@ public class NumberFormatterApiTest { assertFormatDescending( "Increment", - "round-increment/0.5", - NumberFormatter.with().rounding(Rounder.increment(BigDecimal.valueOf(0.5))), + "precision-increment/0.5", + NumberFormatter.with().precision(Precision.increment(BigDecimal.valueOf(0.5))), ULocale.ENGLISH, "87,650.0", "8,765.0", @@ -1031,8 +1031,8 @@ public class NumberFormatterApiTest { assertFormatDescending( "Increment with Min Fraction", - "round-increment/0.50", - NumberFormatter.with().rounding(Rounder.increment(new BigDecimal("0.50"))), + "precision-increment/0.50", + NumberFormatter.with().precision(Precision.increment(new BigDecimal("0.50"))), ULocale.ENGLISH, "87,650.00", "8,765.00", @@ -1046,8 +1046,8 @@ public class NumberFormatterApiTest { assertFormatDescending( "Currency Standard", - "currency/CZK round-currency-standard", - NumberFormatter.with().rounding(Rounder.currency(CurrencyUsage.STANDARD)).unit(CZK), + "currency/CZK precision-currency-standard", + NumberFormatter.with().precision(Precision.currency(CurrencyUsage.STANDARD)).unit(CZK), ULocale.ENGLISH, "CZK 87,650.00", "CZK 8,765.00", @@ -1061,8 +1061,8 @@ public class NumberFormatterApiTest { assertFormatDescending( "Currency Cash", - "currency/CZK round-currency-cash", - NumberFormatter.with().rounding(Rounder.currency(CurrencyUsage.CASH)).unit(CZK), + "currency/CZK precision-currency-cash", + NumberFormatter.with().precision(Precision.currency(CurrencyUsage.CASH)).unit(CZK), ULocale.ENGLISH, "CZK 87,650", "CZK 8,765", @@ -1076,8 +1076,8 @@ public class NumberFormatterApiTest { assertFormatDescending( "Currency Cash with Nickel Rounding", - "currency/CAD round-currency-cash", - NumberFormatter.with().rounding(Rounder.currency(CurrencyUsage.CASH)).unit(CAD), + "currency/CAD precision-currency-cash", + NumberFormatter.with().precision(Precision.currency(CurrencyUsage.CASH)).unit(CAD), ULocale.ENGLISH, "CA$87,650.00", "CA$8,765.00", @@ -1091,8 +1091,8 @@ public class NumberFormatterApiTest { assertFormatDescending( "Currency not in top-level fluent chain", - "round-integer", // calling .withCurrency() applies currency rounding rules immediately - NumberFormatter.with().rounding(Rounder.currency(CurrencyUsage.CASH).withCurrency(CZK)), + "precision-integer", // calling .withCurrency() applies currency rounding rules immediately + NumberFormatter.with().precision(Precision.currency(CurrencyUsage.CASH).withCurrency(CZK)), ULocale.ENGLISH, "87,650", "8,765", @@ -1107,8 +1107,8 @@ public class NumberFormatterApiTest { // NOTE: Other tests cover the behavior of the other rounding modes. assertFormatDescending( "Rounding Mode CEILING", - "round-integer/ceiling", - NumberFormatter.with().rounding(Rounder.integer().withMode(RoundingMode.CEILING)), + "precision-integer rounding-mode-ceiling", + NumberFormatter.with().precision(Precision.integer()).roundingMode(RoundingMode.CEILING), ULocale.ENGLISH, "87,650", "8,765", @@ -2040,7 +2040,7 @@ public class NumberFormatterApiTest { // The number needs to have exactly 40 digits, which is the size of the default buffer. // (issue discovered by the address sanitizer in C++) assertEquals("0.009876543210987654321098765432109876543211", - formatter.rounding(Rounder.unlimited()) + formatter.precision(Precision.unlimited()) .format(new BigDecimal("0.009876543210987654321098765432109876543211")) .toString()); } @@ -2124,8 +2124,8 @@ public class NumberFormatterApiTest { assertFormatSingle( "Plural 1", - "currency/USD round-integer unit-width-full-name", - NumberFormatter.with().unit(USD).unitWidth(UnitWidth.FULL_NAME).rounding(Rounder.fixedFraction(0)), + "currency/USD precision-integer unit-width-full-name", + NumberFormatter.with().unit(USD).unitWidth(UnitWidth.FULL_NAME).precision(Precision.fixedFraction(0)), ULocale.ENGLISH, 1, "1 US dollar"); @@ -2133,7 +2133,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Plural 1.00", "currency/USD .00 unit-width-full-name", - NumberFormatter.with().unit(USD).unitWidth(UnitWidth.FULL_NAME).rounding(Rounder.fixedFraction(2)), + NumberFormatter.with().unit(USD).unitWidth(UnitWidth.FULL_NAME).precision(Precision.fixedFraction(2)), ULocale.ENGLISH, 1, "1.00 US dollars"); @@ -2141,20 +2141,20 @@ public class NumberFormatterApiTest { @Test public void validRanges() throws NoSuchMethodException, IllegalAccessException { - Method[] methodsWithOneArgument = new Method[] { Rounder.class.getDeclaredMethod("fixedFraction", Integer.TYPE), - Rounder.class.getDeclaredMethod("minFraction", Integer.TYPE), - Rounder.class.getDeclaredMethod("maxFraction", Integer.TYPE), - Rounder.class.getDeclaredMethod("fixedDigits", Integer.TYPE), - Rounder.class.getDeclaredMethod("minDigits", Integer.TYPE), - Rounder.class.getDeclaredMethod("maxDigits", Integer.TYPE), - FractionRounder.class.getDeclaredMethod("withMinDigits", Integer.TYPE), - FractionRounder.class.getDeclaredMethod("withMaxDigits", Integer.TYPE), + Method[] methodsWithOneArgument = new Method[] { Precision.class.getDeclaredMethod("fixedFraction", Integer.TYPE), + Precision.class.getDeclaredMethod("minFraction", Integer.TYPE), + Precision.class.getDeclaredMethod("maxFraction", Integer.TYPE), + Precision.class.getDeclaredMethod("fixedDigits", Integer.TYPE), + Precision.class.getDeclaredMethod("minDigits", Integer.TYPE), + Precision.class.getDeclaredMethod("maxDigits", Integer.TYPE), + FractionPrecision.class.getDeclaredMethod("withMinDigits", Integer.TYPE), + FractionPrecision.class.getDeclaredMethod("withMaxDigits", Integer.TYPE), ScientificNotation.class.getDeclaredMethod("withMinExponentDigits", Integer.TYPE), IntegerWidth.class.getDeclaredMethod("zeroFillTo", Integer.TYPE), IntegerWidth.class.getDeclaredMethod("truncateAt", Integer.TYPE), }; Method[] methodsWithTwoArguments = new Method[] { - Rounder.class.getDeclaredMethod("minMaxFraction", Integer.TYPE, Integer.TYPE), - Rounder.class.getDeclaredMethod("minMaxDigits", Integer.TYPE, Integer.TYPE), }; + Precision.class.getDeclaredMethod("minMaxFraction", Integer.TYPE, Integer.TYPE), + Precision.class.getDeclaredMethod("minMaxDigits", Integer.TYPE, Integer.TYPE), }; final int EXPECTED_MAX_INT_FRAC_SIG = 999; final String expectedSubstring0 = "between 0 and 999 (inclusive)"; @@ -2182,8 +2182,8 @@ public class NumberFormatterApiTest { // Some of the methods require an object to be called upon. Map<String, Object> targets = new HashMap<String, Object>(); - targets.put("withMinDigits", Rounder.integer()); - targets.put("withMaxDigits", Rounder.integer()); + targets.put("withMinDigits", Precision.integer()); + targets.put("withMaxDigits", Precision.integer()); targets.put("withMinExponentDigits", Notation.scientific()); targets.put("truncateAt", IntegerWidth.zeroFillTo(0)); diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java index 6e854dfbf2c..335b1b23b33 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java @@ -11,7 +11,6 @@ import java.math.RoundingMode; import org.junit.Test; import com.ibm.icu.number.NumberFormatter; -import com.ibm.icu.number.Rounder; import com.ibm.icu.number.SkeletonSyntaxException; import com.ibm.icu.util.ULocale; @@ -26,8 +25,8 @@ public class NumberSkeletonTest { // This tests only if the tokens are valid, not their behavior. // Most of these are from the design doc. String[] cases = { - "round-integer", - "round-unlimited", + "precision-integer", + "precision-unlimited", "@@@##", "@@+", ".000##", @@ -37,11 +36,11 @@ public class NumberSkeletonTest { ".######", ".00/@@+", ".00/@##", - "round-increment/3.14", - "round-currency-standard", - "round-integer/half-up", - ".00#/ceiling", - ".00/@@+/floor", + "precision-increment/3.14", + "precision-currency-standard", + "precision-integer rounding-mode-half-up", + ".00# rounding-mode-ceiling", + ".00/@@+ rounding-mode-floor", "scientific", "scientific/+ee", "scientific/sign-always", @@ -91,9 +90,9 @@ public class NumberSkeletonTest { "latin", "numbering-system/arab", "numbering-system/latn", - "round-integer/@##", - "round-integer/ceiling", - "round-currency-cash/ceiling" }; + "precision-integer/@##", + "precision-integer rounding-mode-ceiling", + "precision-currency-cash rounding-mode-ceiling" }; for (String cas : cases) { try { @@ -121,12 +120,11 @@ public class NumberSkeletonTest { ".00/@@#", ".00/@@#+", ".00/floor/@@+", // wrong order - "round-increment/français", // non-invariant characters for C++ - "round-currency-cash/XXX", + "precision-increment/français", // non-invariant characters for C++ "scientific/ee", - "round-increment/xxx", - "round-increment/NaN", - "round-increment/0.1.2", + "precision-increment/xxx", + "precision-increment/NaN", + "precision-increment/0.1.2", "scale/xxx", "scale/NaN", "scale/0.1.2", @@ -174,10 +172,10 @@ public class NumberSkeletonTest { public void unexpectedTokens() { String[] cases = { "group-thousands/foo", - "round-integer//ceiling group-off", - "round-integer//ceiling group-off", - "round-integer/ group-off", - "round-integer// group-off" }; + "precision-integer//@## group-off", + "precision-integer//@## group-off", + "precision-integer/ group-off", + "precision-integer// group-off" }; for (String cas : cases) { try { @@ -192,10 +190,10 @@ public class NumberSkeletonTest { @Test public void duplicateValues() { String[] cases = { - "round-integer round-integer", - "round-integer .00+", - "round-integer round-unlimited", - "round-integer @@@", + "precision-integer precision-integer", + "precision-integer .00+", + "precision-integer precision-unlimited", + "precision-integer @@@", "scientific engineering", "engineering compact-long", "sign-auto sign-always" }; @@ -213,14 +211,14 @@ public class NumberSkeletonTest { @Test public void stemsRequiringOption() { String[] stems = { - "round-increment", + "precision-increment", "measure-unit", "per-unit", "currency", "integer-width", "numbering-system", "scale" }; - String[] suffixes = { "", "/ceiling", " scientific", "/ceiling scientific" }; + String[] suffixes = { "", "/@##", " scientific", "/@## scientific" }; for (String stem : stems) { for (String suffix : suffixes) { @@ -255,10 +253,10 @@ public class NumberSkeletonTest { @Test public void flexibleSeparators() { String[][] cases = { - { "round-integer group-off", "5142" }, - { "round-integer group-off", "5142" }, - { "round-integer/ceiling group-off", "5143" }, - { "round-integer/ceiling group-off", "5143" }, }; + { "precision-integer group-off", "5142" }, + { "precision-integer group-off", "5142" }, + { "precision-integer/@## group-off", "5140" }, + { "precision-integer/@## group-off", "5140" }, }; for (String[] cas : cases) { String skeleton = cas[0]; @@ -276,8 +274,7 @@ public class NumberSkeletonTest { // This rounding mode is not printed in the skeleton since it is the default continue; } - String skeleton = NumberFormatter.with().rounding(Rounder.integer().withMode(mode)) - .toSkeleton(); + String skeleton = NumberFormatter.with().roundingMode(mode).toSkeleton(); String modeString = mode.toString().toLowerCase().replace('_', '-'); assertEquals(mode.toString(), modeString, skeleton.substring(14)); }