ICU-20886 Implement trailingZeroDisplay

See #1583
This commit is contained in:
Shane F. Carr 2021-03-01 07:42:10 +00:00
parent 6bfa5c02ed
commit b79c299f90
14 changed files with 318 additions and 28 deletions

View file

@ -257,6 +257,16 @@ digits. Then it contains either a `*`, for unlimited maximum significant
digits, or zero or more `#` symbols, which implies the minimum significant
digits when added to the `@` symbols.
#### Trailing Zero Display
***Starting with ICU 69***, a new option called `trailingZeroDisplay` was added.
To enable this in an ICU number skeleton, append `/w` to any precision token:
| Skeleton | Explanation | Equivalent C++ Code |
|---|---|---|
| `.00/w` | Exactly 2 fraction digits, but hide <br/> them if they are all 0 | `Precision::fixedFraction(2)` <br/> `.trailingZeroDisplay(` <br/> `UNUM_TRAILING_ZERO_HIDE_IF_WHOLE)` |
| `precision-curren` <br/> `cy-standard/w` | Currency rounding, but hide <br/> fraction digits if they are all 0 | `Precision::currency(UCURR_USAGE_STANDARD)` <br/> `.trailingZeroDisplay(` <br/> `UNUM_TRAILING_ZERO_HIDE_IF_WHOLE)` |
#### Wildcard Character
***Prior to ICU 67***, the symbol `+` was used for unlimited precision, instead

View file

@ -193,6 +193,12 @@ Precision Precision::minMaxSignificantDigits(int32_t minSignificantDigits, int32
}
}
Precision Precision::trailingZeroDisplay(UNumberTrailingZeroDisplay trailingZeroDisplay) const {
Precision result(*this); // copy constructor
result.fTrailingZeroDisplay = trailingZeroDisplay;
return result;
}
IncrementPrecision Precision::increment(double roundingIncrement) {
if (roundingIncrement > 0.0) {
return constructIncrement(roundingIncrement, 0);
@ -256,11 +262,11 @@ Precision Precision::withCurrency(const CurrencyUnit &currency, UErrorCode &stat
double increment = ucurr_getRoundingIncrementForUsage(isoCode, fUnion.currencyUsage, &status);
int32_t minMaxFrac = ucurr_getDefaultFractionDigitsForUsage(
isoCode, fUnion.currencyUsage, &status);
if (increment != 0.0) {
return constructIncrement(increment, minMaxFrac);
} else {
return constructFraction(minMaxFrac, minMaxFrac);
}
Precision retval = (increment != 0.0)
? static_cast<Precision>(constructIncrement(increment, minMaxFrac))
: static_cast<Precision>(constructFraction(minMaxFrac, minMaxFrac));
retval.fTrailingZeroDisplay = fTrailingZeroDisplay;
return retval;
}
// Public method on CurrencyPrecision subclass
@ -413,6 +419,7 @@ void RoundingImpl::apply(impl::DecimalQuantity &value, UErrorCode& status) const
if (fPassThrough) {
return;
}
int32_t resolvedMinFraction = 0;
switch (fPrecision.fType) {
case Precision::RND_BOGUS:
case Precision::RND_ERROR:
@ -429,8 +436,8 @@ void RoundingImpl::apply(impl::DecimalQuantity &value, UErrorCode& status) const
getRoundingMagnitudeFraction(fPrecision.fUnion.fracSig.fMaxFrac),
fRoundingMode,
status);
value.setMinFraction(
uprv_max(0, -getDisplayMagnitudeFraction(fPrecision.fUnion.fracSig.fMinFrac)));
resolvedMinFraction =
uprv_max(0, -getDisplayMagnitudeFraction(fPrecision.fUnion.fracSig.fMinFrac));
break;
case Precision::RND_SIGNIFICANT:
@ -438,8 +445,8 @@ void RoundingImpl::apply(impl::DecimalQuantity &value, UErrorCode& status) const
getRoundingMagnitudeSignificant(value, fPrecision.fUnion.fracSig.fMaxSig),
fRoundingMode,
status);
value.setMinFraction(
uprv_max(0, -getDisplayMagnitudeSignificant(value, fPrecision.fUnion.fracSig.fMinSig)));
resolvedMinFraction =
uprv_max(0, -getDisplayMagnitudeSignificant(value, fPrecision.fUnion.fracSig.fMinSig));
// Make sure that digits are displayed on zero.
if (value.isZeroish() && fPrecision.fUnion.fracSig.fMinSig > 0) {
value.setMinInteger(1);
@ -460,7 +467,7 @@ void RoundingImpl::apply(impl::DecimalQuantity &value, UErrorCode& status) const
int32_t displayMag1 = getDisplayMagnitudeFraction(fPrecision.fUnion.fracSig.fMinFrac);
int32_t displayMag2 = getDisplayMagnitudeSignificant(value, fPrecision.fUnion.fracSig.fMinSig);
int32_t displayMag = uprv_min(displayMag1, displayMag2);
value.setMinFraction(uprv_max(0, -displayMag));
resolvedMinFraction = uprv_max(0, -displayMag);
break;
}
@ -470,7 +477,7 @@ void RoundingImpl::apply(impl::DecimalQuantity &value, UErrorCode& status) const
fPrecision.fUnion.increment.fIncrement,
fRoundingMode,
status);
value.setMinFraction(fPrecision.fUnion.increment.fMinFrac);
resolvedMinFraction = fPrecision.fUnion.increment.fMinFrac;
break;
case Precision::RND_INCREMENT_ONE:
@ -478,7 +485,7 @@ void RoundingImpl::apply(impl::DecimalQuantity &value, UErrorCode& status) const
-fPrecision.fUnion.increment.fMaxFrac,
fRoundingMode,
status);
value.setMinFraction(fPrecision.fUnion.increment.fMinFrac);
resolvedMinFraction = fPrecision.fUnion.increment.fMinFrac;
break;
case Precision::RND_INCREMENT_FIVE:
@ -486,7 +493,7 @@ void RoundingImpl::apply(impl::DecimalQuantity &value, UErrorCode& status) const
-fPrecision.fUnion.increment.fMaxFrac,
fRoundingMode,
status);
value.setMinFraction(fPrecision.fUnion.increment.fMinFrac);
resolvedMinFraction = fPrecision.fUnion.increment.fMinFrac;
break;
case Precision::RND_CURRENCY:
@ -496,10 +503,17 @@ void RoundingImpl::apply(impl::DecimalQuantity &value, UErrorCode& status) const
default:
UPRV_UNREACHABLE;
}
if (fPrecision.fTrailingZeroDisplay == UNUM_TRAILING_ZERO_AUTO ||
// PLURAL_OPERAND_T returns fraction digits as an integer
value.getPluralOperand(PLURAL_OPERAND_T) != 0) {
value.setMinFraction(resolvedMinFraction);
}
}
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".
// Question: Is it useful to look at trailingZeroDisplay here?
U_ASSERT(isSignificantDigits());
U_ASSERT(value.isZeroish());
value.setMinFraction(fPrecision.fUnion.fracSig.fMinSig - minInt);

View file

@ -616,7 +616,7 @@ skeleton::parseStem(const StringSegment& segment, const UCharsTrie& stemTrie, Se
case u'@':
CHECK_NULL(seen, precision, status);
blueprint_helpers::parseDigitsStem(segment, macros, status);
return STATE_NULL;
return STATE_PRECISION;
case u'E':
CHECK_NULL(seen, notation, status);
blueprint_helpers::parseScientificStem(segment, macros, status);
@ -682,7 +682,7 @@ skeleton::parseStem(const StringSegment& segment, const UCharsTrie& stemTrie, Se
case STEM_PRECISION_INTEGER:
return STATE_FRACTION_PRECISION; // allows for "precision-integer/@##"
default:
return STATE_NULL;
return STATE_PRECISION;
}
case STEM_ROUNDING_MODE_CEILING:
@ -813,7 +813,7 @@ ParseState skeleton::parseOption(ParseState stem, const StringSegment& segment,
return STATE_NULL;
case STATE_INCREMENT_PRECISION:
blueprint_helpers::parseIncrementOption(segment, macros, status);
return STATE_NULL;
return STATE_PRECISION;
case STATE_INTEGER_WIDTH:
blueprint_helpers::parseIntegerWidthOption(segment, macros, status);
return STATE_NULL;
@ -853,6 +853,22 @@ ParseState skeleton::parseOption(ParseState stem, const StringSegment& segment,
switch (stem) {
case STATE_FRACTION_PRECISION:
if (blueprint_helpers::parseFracSigOption(segment, macros, status)) {
return STATE_PRECISION;
}
if (U_FAILURE(status)) {
return {};
}
// If the fracSig option was not found, try normal precision options.
stem = STATE_PRECISION;
break;
default:
break;
}
// Trailing zeros option
switch (stem) {
case STATE_PRECISION:
if (blueprint_helpers::parseTrailingZeroOption(segment, macros, status)) {
return STATE_NULL;
}
if (U_FAILURE(status)) {
@ -1362,6 +1378,14 @@ bool blueprint_helpers::parseFracSigOption(const StringSegment& segment, MacroPr
return true;
}
bool blueprint_helpers::parseTrailingZeroOption(const StringSegment& segment, MacroProps& macros, UErrorCode&) {
if (segment == u"w") {
macros.precision = macros.precision.trailingZeroDisplay(UNUM_TRAILING_ZERO_HIDE_IF_WHOLE);
return true;
}
return false;
}
void blueprint_helpers::parseIncrementOption(const StringSegment &segment, MacroProps &macros,
UErrorCode &status) {
number::impl::parseIncrementOption(segment, macros.precision, status);
@ -1617,6 +1641,10 @@ bool GeneratorHelpers::precision(const MacroProps& macros, UnicodeString& sb, UE
return false;
}
if (macros.precision.fTrailingZeroDisplay == UNUM_TRAILING_ZERO_HIDE_IF_WHOLE) {
sb.append(u"/w", -1);
}
// NOTE: Always return true for rounding because the default value depends on other options.
return true;
}

View file

@ -42,6 +42,7 @@ enum ParseState {
STATE_SCIENTIFIC,
STATE_FRACTION_PRECISION,
STATE_PRECISION,
// Section 2: An option is required:
@ -278,6 +279,9 @@ void parseIntegerStem(const StringSegment& segment, MacroProps& macros, UErrorCo
/** @return Whether we successfully found and parsed a frac-sig option. */
bool parseFracSigOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status);
/** @return Whether we successfully found and parsed a trailing zero option. */
bool parseTrailingZeroOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status);
void parseIncrementOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status);
void

View file

@ -659,6 +659,17 @@ class U_I18N_API Precision : public UMemory {
*/
static CurrencyPrecision currency(UCurrencyUsage currencyUsage);
#ifndef U_HIDE_DRAFT_API
/**
* Configure how trailing zeros are displayed on numbers. For example, to hide trailing zeros
* when the number is an integer, use UNUM_TRAILING_ZERO_HIDE_IF_WHOLE.
*
* @param trailingZeroDisplay Option to configure the display of trailing zeros.
* @draft ICU 69
*/
Precision trailingZeroDisplay(UNumberTrailingZeroDisplay trailingZeroDisplay) const;
#endif // U_HIDE_DRAFT_API
private:
enum PrecisionType {
RND_BOGUS,
@ -711,6 +722,8 @@ class U_I18N_API Precision : public UMemory {
UErrorCode errorCode; // For RND_ERROR
} fUnion;
UNumberTrailingZeroDisplay fTrailingZeroDisplay = UNUM_TRAILING_ZERO_AUTO;
typedef PrecisionUnion::FractionSignificantSettings FractionSignificantSettings;
typedef PrecisionUnion::IncrementSettings IncrementSettings;

View file

@ -494,6 +494,32 @@ typedef enum UNumberDecimalSeparatorDisplay {
UNUM_DECIMAL_SEPARATOR_COUNT
} UNumberDecimalSeparatorDisplay;
#ifndef U_FORCE_HIDE_DRAFT_API
/**
* An enum declaring how to render trailing zeros.
*
* - UNUM_TRAILING_ZERO_AUTO: 0.90, 1.00, 1.10
* - UNUM_TRAILING_ZERO_HIDE_IF_WHOLE: 0.90, 1, 1.10
*
* @draft ICU 69
*/
typedef enum UNumberTrailingZeroDisplay {
/**
* Display trailing zeros according to the settings for minimum fraction and significant digits.
*
* @draft ICU 69
*/
UNUM_TRAILING_ZERO_AUTO,
/**
* Same as AUTO, but hide trailing zeros after the decimal separator if they are all zero.
*
* @draft ICU 69
*/
UNUM_TRAILING_ZERO_HIDE_IF_WHOLE,
} UNumberTrailingZeroDisplay;
#endif // U_FORCE_HIDE_DRAFT_API
struct UNumberFormatter;
/**
* C-compatible version of icu::number::LocalizedNumberFormatter.

View file

@ -2545,6 +2545,26 @@ void NumberFormatterApiTest::roundingFraction() {
u"0.088",
u"0.009",
u"0.0");
assertFormatSingle(
u"Hide If Whole A",
u".00/w",
u".00/w",
NumberFormatter::with().precision(Precision::fixedFraction(2)
.trailingZeroDisplay(UNUM_TRAILING_ZERO_HIDE_IF_WHOLE)),
Locale::getEnglish(),
1.2,
"1.20");
assertFormatSingle(
u"Hide If Whole B",
u".00/w",
u".00/w",
NumberFormatter::with().precision(Precision::fixedFraction(2)
.trailingZeroDisplay(UNUM_TRAILING_ZERO_HIDE_IF_WHOLE)),
Locale::getEnglish(),
1,
"1");
}
void NumberFormatterApiTest::roundingFigures() {
@ -2770,6 +2790,16 @@ void NumberFormatterApiTest::roundingFractionFigures() {
Locale::getEnglish(),
9.99,
u"10.0");
assertFormatSingle(
u"FracSig with Trailing Zero Display",
u".00/@@@*/w",
u".00/@@@+/w",
NumberFormatter::with().precision(Precision::fixedFraction(2).withMinDigits(3)
.trailingZeroDisplay(UNUM_TRAILING_ZERO_HIDE_IF_WHOLE)),
Locale::getEnglish(),
1,
u"1");
}
void NumberFormatterApiTest::roundingOther() {
@ -2887,6 +2917,25 @@ void NumberFormatterApiTest::roundingOther() {
u"CZK 0",
u"CZK 0");
assertFormatDescending(
u"Currency Standard with Trailing Zero Display",
u"currency/CZK precision-currency-standard/w",
u"currency/CZK precision-currency-standard/w",
NumberFormatter::with().precision(
Precision::currency(UCurrencyUsage::UCURR_USAGE_STANDARD)
.trailingZeroDisplay(UNUM_TRAILING_ZERO_HIDE_IF_WHOLE))
.unit(CZK),
Locale::getEnglish(),
u"CZK 87,650",
u"CZK 8,765",
u"CZK 876.50",
u"CZK 87.65",
u"CZK 8.76",
u"CZK 0.88",
u"CZK 0.09",
u"CZK 0.01",
u"CZK 0");
assertFormatDescending(
u"Currency Cash with Nickel Rounding",
u"currency/CAD precision-currency-cash",

View file

@ -46,22 +46,28 @@ void NumberSkeletonTest::validTokens() {
u"@@@##",
u"@@*",
u"@@+",
u"@@+/w",
u".000##",
u".00*",
u".00+",
u".",
u"./w",
u".*",
u".+",
u".+/w",
u".######",
u".00/@@*",
u".00/@@+",
u".00/@##",
u".00/@##/w",
u".00/@",
u".00/@r",
u".00/@@s",
u".00/@@#r",
u"precision-increment/3.14",
u"precision-increment/3.14/w",
u"precision-currency-standard",
u"precision-currency-standard/w",
u"precision-integer rounding-mode-half-up",
u".00# rounding-mode-ceiling",
u".00/@@* rounding-mode-floor",
@ -152,6 +158,9 @@ void NumberSkeletonTest::validTokens() {
void NumberSkeletonTest::invalidTokens() {
static const char16_t* cases[] = {
u".00x",
u".00i",
u".00/x",
u".00/ww",
u".00##0",
u".##*",
u".00##*",
@ -226,6 +235,7 @@ void NumberSkeletonTest::unknownTokens() {
void NumberSkeletonTest::unexpectedTokens() {
static const char16_t* cases[] = {
u".00/w/w",
u"group-thousands/foo",
u"precision-integer//@## group-off",
u"precision-integer//@## group-off",

View file

@ -40,7 +40,9 @@ public abstract class CurrencyPrecision extends Precision {
*/
public Precision withCurrency(Currency currency) {
if (currency != null) {
return constructFromCurrency(this, currency);
Precision retval = constructFromCurrency(this, currency);
retval.trailingZeroDisplay = trailingZeroDisplay;
return retval;
} else {
throw new IllegalArgumentException("Currency must not be null");
}

View file

@ -480,6 +480,33 @@ public final class NumberFormatter {
ALWAYS,
}
/**
* An enum declaring how to render trailing zeros.
*
* <ul>
* <li>AUTO: 0.90, 1.00, 1.10
* <li>HIDE_IF_WHOLE: 0.90, 1, 1.10
* </ul>
*
* @draft ICU 69
* @provisional This API might change or be removed in a future release.
*/
public static enum TrailingZeroDisplay {
/**
* Display trailing zeros according to the settings for minimum fraction and significant digits.
*
* @draft ICU 69
*/
AUTO,
/**
* Same as AUTO, but hide trailing zeros after the decimal separator if they are all zero.
*
* @draft ICU 69
*/
HIDE_IF_WHOLE,
}
/**
* Use a default threshold of 3. This means that the third time .format() is called, the data
* structures get built using the "safe" code path. The first two calls to .format() will trigger the

View file

@ -16,6 +16,7 @@ import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay;
import com.ibm.icu.number.NumberFormatter.GroupingStrategy;
import com.ibm.icu.number.NumberFormatter.RoundingPriority;
import com.ibm.icu.number.NumberFormatter.SignDisplay;
import com.ibm.icu.number.NumberFormatter.TrailingZeroDisplay;
import com.ibm.icu.number.NumberFormatter.UnitWidth;
import com.ibm.icu.text.DecimalFormatSymbols;
import com.ibm.icu.text.NumberingSystem;
@ -51,6 +52,7 @@ class NumberSkeletonImpl {
// Section 1: We might accept an option, but it is not required:
STATE_SCIENTIFIC,
STATE_FRACTION_PRECISION,
STATE_PRECISION,
// Section 2: An option is required:
STATE_INCREMENT_PRECISION,
@ -671,7 +673,7 @@ class NumberSkeletonImpl {
case '@':
checkNull(macros.precision, segment);
BlueprintHelpers.parseDigitsStem(segment, macros);
return ParseState.STATE_NULL;
return ParseState.STATE_PRECISION;
case 'E':
checkNull(macros.notation, segment);
BlueprintHelpers.parseScientificStem(segment, macros);
@ -734,7 +736,7 @@ class NumberSkeletonImpl {
case STEM_PRECISION_INTEGER:
return ParseState.STATE_FRACTION_PRECISION; // allows for "precision-integer/@##"
default:
return ParseState.STATE_NULL;
return ParseState.STATE_PRECISION;
}
case STEM_ROUNDING_MODE_CEILING:
@ -871,7 +873,7 @@ class NumberSkeletonImpl {
return ParseState.STATE_NULL;
case STATE_INCREMENT_PRECISION:
BlueprintHelpers.parseIncrementOption(segment, macros);
return ParseState.STATE_NULL;
return ParseState.STATE_PRECISION;
case STATE_INTEGER_WIDTH:
BlueprintHelpers.parseIntegerWidthOption(segment, macros);
return ParseState.STATE_NULL;
@ -905,6 +907,19 @@ class NumberSkeletonImpl {
switch (stem) {
case STATE_FRACTION_PRECISION:
if (BlueprintHelpers.parseFracSigOption(segment, macros)) {
return ParseState.STATE_PRECISION;
}
// If the fracSig option was not found, try normal precision options.
stem = ParseState.STATE_PRECISION;
break;
default:
break;
}
// Trailing zeros option
switch (stem) {
case STATE_PRECISION:
if (BlueprintHelpers.parseTrailingZeroOption(segment, macros)) {
return ParseState.STATE_NULL;
}
break;
@ -1351,6 +1366,15 @@ class NumberSkeletonImpl {
return true;
}
/** @return Whether we successfully found and parsed a trailing zero option. */
private static boolean parseTrailingZeroOption(StringSegment segment, MacroProps macros) {
if (segment.equals("w")) {
macros.precision = macros.precision.trailingZeroDisplay(TrailingZeroDisplay.HIDE_IF_WHOLE);
return true;
}
return false;
}
private static void parseIncrementOption(StringSegment segment, MacroProps macros) {
// Call segment.subSequence() because segment.toString() doesn't create a clean string.
String str = segment.subSequence(0, segment.length()).toString();
@ -1566,6 +1590,10 @@ class NumberSkeletonImpl {
}
}
if (macros.precision.trailingZeroDisplay == TrailingZeroDisplay.HIDE_IF_WHOLE) {
sb.append("/w");
}
// NOTE: Always return true for rounding because the default value depends on other options.
return true;
}

View file

@ -10,6 +10,8 @@ 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.number.NumberFormatter.RoundingPriority;
import com.ibm.icu.number.NumberFormatter.TrailingZeroDisplay;
import com.ibm.icu.text.PluralRules.Operand;
import com.ibm.icu.util.Currency;
import com.ibm.icu.util.Currency.CurrencyUsage;
@ -25,6 +27,7 @@ import com.ibm.icu.util.Currency.CurrencyUsage;
public abstract class Precision {
/* package-private final */ MathContext mathContext;
/* package-private final */ TrailingZeroDisplay trailingZeroDisplay;
/* package-private */ Precision() {
mathContext = RoundingUtils.DEFAULT_MATH_CONTEXT_UNLIMITED;
@ -336,6 +339,12 @@ public abstract class Precision {
}
}
public Precision trailingZeroDisplay(TrailingZeroDisplay trailingZeroDisplay) {
Precision result = this.createCopy();
result.trailingZeroDisplay = trailingZeroDisplay;
return result;
}
/**
* Sets a MathContext to use on this Precision.
*
@ -611,7 +620,7 @@ public abstract class Precision {
@Override
public void apply(DecimalQuantity value) {
value.roundToInfinity();
value.setMinFraction(0);
setResolvedMinFraction(value, 0);
}
@Override
@ -634,7 +643,7 @@ public abstract class Precision {
@Override
public void apply(DecimalQuantity value) {
value.roundToMagnitude(getRoundingMagnitudeFraction(maxFrac), mathContext);
value.setMinFraction(Math.max(0, -getDisplayMagnitudeFraction(minFrac)));
setResolvedMinFraction(value, Math.max(0, -getDisplayMagnitudeFraction(minFrac)));
}
@Override
@ -657,7 +666,7 @@ public abstract class Precision {
@Override
public void apply(DecimalQuantity value) {
value.roundToMagnitude(getRoundingMagnitudeSignificant(value, maxSig), mathContext);
value.setMinFraction(Math.max(0, -getDisplayMagnitudeSignificant(value, minSig)));
setResolvedMinFraction(value, Math.max(0, -getDisplayMagnitudeSignificant(value, minSig)));
// Make sure that digits are displayed on zero.
if (value.isZeroish() && minSig > 0) {
value.setMinInteger(1);
@ -670,7 +679,7 @@ public abstract class Precision {
*/
public void apply(DecimalQuantity quantity, int minInt) {
assert quantity.isZeroish();
quantity.setMinFraction(minSig - minInt);
setResolvedMinFraction(quantity, minSig - minInt);
}
@Override
@ -711,7 +720,7 @@ public abstract class Precision {
int displayMag1 = getDisplayMagnitudeFraction(minFrac);
int displayMag2 = getDisplayMagnitudeSignificant(value, minSig);
int displayMag = Math.min(displayMag1, displayMag2);
value.setMinFraction(Math.max(0, -displayMag));
setResolvedMinFraction(value, Math.max(0, -displayMag));
}
@Override
@ -735,7 +744,7 @@ public abstract class Precision {
@Override
public void apply(DecimalQuantity value) {
value.roundToIncrement(increment, mathContext);
value.setMinFraction(increment.scale());
setResolvedMinFraction(value, increment.scale());
}
@Override
@ -764,7 +773,7 @@ public abstract class Precision {
@Override
public void apply(DecimalQuantity value) {
value.roundToMagnitude(-maxFrac, mathContext);
value.setMinFraction(minFrac);
setResolvedMinFraction(value, minFrac);
}
@Override
@ -791,7 +800,7 @@ public abstract class Precision {
@Override
public void apply(DecimalQuantity value) {
value.roundToNickel(-maxFrac, mathContext);
value.setMinFraction(minFrac);
setResolvedMinFraction(value, minFrac);
}
@Override
@ -845,7 +854,17 @@ public abstract class Precision {
return -minFrac;
}
void setResolvedMinFraction(DecimalQuantity value, int resolvedMinFraction) {
if (trailingZeroDisplay == null ||
trailingZeroDisplay == TrailingZeroDisplay.AUTO ||
// PLURAL_OPERAND_T returns fraction digits as an integer
value.getPluralOperand(Operand.t) != 0) {
value.setMinFraction(resolvedMinFraction);
}
}
private static int getDisplayMagnitudeSignificant(DecimalQuantity value, int minSig) {
// Question: Is it useful to look at trailingZeroDisplay here?
int magnitude = value.isZeroish() ? 0 : value.getMagnitude();
return magnitude - minSig + 1;
}

View file

@ -39,6 +39,7 @@ import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay;
import com.ibm.icu.number.NumberFormatter.GroupingStrategy;
import com.ibm.icu.number.NumberFormatter.RoundingPriority;
import com.ibm.icu.number.NumberFormatter.SignDisplay;
import com.ibm.icu.number.NumberFormatter.TrailingZeroDisplay;
import com.ibm.icu.number.NumberFormatter.UnitWidth;
import com.ibm.icu.number.Precision;
import com.ibm.icu.number.Scale;
@ -2508,6 +2509,26 @@ public class NumberFormatterApiTest extends TestFmwk {
"0.088",
"0.009",
"0.0");
assertFormatSingle(
"Hide If Whole A",
".00/w",
".00/w",
NumberFormatter.with().precision(Precision.fixedFraction(2)
.trailingZeroDisplay(TrailingZeroDisplay.HIDE_IF_WHOLE)),
ULocale.ENGLISH,
1.2,
"1.20");
assertFormatSingle(
"Hide If Whole B",
".00/w",
".00/w",
NumberFormatter.with().precision(Precision.fixedFraction(2)
.trailingZeroDisplay(TrailingZeroDisplay.HIDE_IF_WHOLE)),
ULocale.ENGLISH,
1,
"1");
}
@Test
@ -2749,6 +2770,16 @@ public class NumberFormatterApiTest extends TestFmwk {
ULocale.ENGLISH,
9.99,
"10.0");
assertFormatSingle(
"FracSig with Trailing Zero Display",
".00/@@@*/w",
".00/@@@+/w",
NumberFormatter.with().precision(Precision.fixedFraction(2).withMinDigits(3)
.trailingZeroDisplay(TrailingZeroDisplay.HIDE_IF_WHOLE)),
ULocale.ENGLISH,
1,
"1");
}
@Test
@ -2865,6 +2896,25 @@ public class NumberFormatterApiTest extends TestFmwk {
"CZK 0",
"CZK 0");
assertFormatDescending(
"Currency Standard with Trailing Zero Display",
"currency/CZK precision-currency-standard/w",
"currency/CZK precision-currency-standard/w",
NumberFormatter.with().precision(
Precision.currency(CurrencyUsage.STANDARD)
.trailingZeroDisplay(TrailingZeroDisplay.HIDE_IF_WHOLE))
.unit(CZK),
ULocale.ENGLISH,
"CZK 87,650",
"CZK 8,765",
"CZK 876.50",
"CZK 87.65",
"CZK 8.76",
"CZK 0.88",
"CZK 0.09",
"CZK 0.01",
"CZK 0");
assertFormatDescending(
"Currency Cash with Nickel Rounding",
"currency/CAD precision-currency-cash",

View file

@ -30,22 +30,28 @@ public class NumberSkeletonTest {
"@@@##",
"@@*",
"@@+",
"@@+/w",
".000##",
".00*",
".00+",
".",
"./w",
".*",
".+",
".+/w",
".######",
".00/@@*",
".00/@@+",
".00/@##",
".00/@##/w",
".00/@",
".00/@r",
".00/@@s",
".00/@@#r",
"precision-increment/3.14",
"precision-increment/3.14/w",
"precision-currency-standard",
"precision-currency-standard/w",
"precision-integer rounding-mode-half-up",
".00# rounding-mode-ceiling",
".00/@@* rounding-mode-floor",
@ -135,6 +141,9 @@ public class NumberSkeletonTest {
public void invalidTokens() {
String[] cases = {
".00x",
".00i",
".00/x",
".00/ww",
".00##0",
".##*",
".00##*",
@ -225,6 +234,7 @@ public class NumberSkeletonTest {
@Test
public void unexpectedTokens() {
String[] cases = {
".00/w/w",
"group-thousands/foo",
"precision-integer//@## group-off",
"precision-integer//@## group-off",