ICU-13677 Changing NumberFormatter rounding setting to precision and roundingMode.

X-SVN-Rev: 41330
This commit is contained in:
Shane Carr 2018-05-05 06:32:29 +00:00
parent a4b1517e11
commit e27cf9ce39
40 changed files with 1937 additions and 1585 deletions

View file

@ -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 */

View file

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

View file

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

View file

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

View file

@ -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)];
}

View file

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

View file

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

View file

@ -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 &currency, UErrorCode &status) const {
Precision Precision::withCurrency(const CurrencyUnit &currency, 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 &currency, UErrorCode &status)
}
}
// Public method on CurrencyRounder subclass
Rounder CurrencyRounder::withCurrency(const CurrencyUnit &currency) const {
// Public method on CurrencyPrecision subclass
Precision CurrencyPrecision::withCurrency(const CurrencyUnit &currency) 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 &currency, 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 */

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 &currency, UErrorCode &status) const;
// On the parent type so that this method can be called internally on Precision instances.
Precision withCurrency(const CurrencyUnit &currency, UErrorCode &status) const;
/** NON-CONST: mutates the current instance. */
void setLocaleData(const CurrencyUnit &currency, 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 &currency) const;
Precision withCurrency(const CurrencyUnit &currency) 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.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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");
}
};
}

View file

@ -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 {
}

View file

@ -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)");
}
}
}

View file

@ -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 {
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 {
}

View file

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

View file

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

View file

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

View file

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

View file

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