ICU-20886 Implement trailingZeroDisplay
See #1583
diff --git a/docs/userguide/format_parse/numbers/skeletons.md b/docs/userguide/format_parse/numbers/skeletons.md
index fd5e2e9..fa30ba1 100644
--- a/docs/userguide/format_parse/numbers/skeletons.md
+++ b/docs/userguide/format_parse/numbers/skeletons.md
@@ -257,6 +257,16 @@
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
diff --git a/icu4c/source/i18n/number_rounding.cpp b/icu4c/source/i18n/number_rounding.cpp
index 0932d2f..40392ee 100644
--- a/icu4c/source/i18n/number_rounding.cpp
+++ b/icu4c/source/i18n/number_rounding.cpp
@@ -193,6 +193,12 @@
}
}
+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 @@
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 @@
if (fPassThrough) {
return;
}
+ int32_t resolvedMinFraction = 0;
switch (fPrecision.fType) {
case Precision::RND_BOGUS:
case Precision::RND_ERROR:
@@ -429,8 +436,8 @@
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 @@
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 @@
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 @@
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 @@
-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 @@
-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 @@
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);
diff --git a/icu4c/source/i18n/number_skeletons.cpp b/icu4c/source/i18n/number_skeletons.cpp
index 0110e95e..97d7430 100644
--- a/icu4c/source/i18n/number_skeletons.cpp
+++ b/icu4c/source/i18n/number_skeletons.cpp
@@ -616,7 +616,7 @@
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 @@
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 @@
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 @@
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 @@
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 ¯os,
UErrorCode &status) {
number::impl::parseIncrementOption(segment, macros.precision, status);
@@ -1617,6 +1641,10 @@
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;
}
diff --git a/icu4c/source/i18n/number_skeletons.h b/icu4c/source/i18n/number_skeletons.h
index cb3cdf3..af63650 100644
--- a/icu4c/source/i18n/number_skeletons.h
+++ b/icu4c/source/i18n/number_skeletons.h
@@ -42,6 +42,7 @@
STATE_SCIENTIFIC,
STATE_FRACTION_PRECISION,
+ STATE_PRECISION,
// Section 2: An option is required:
@@ -278,6 +279,9 @@
/** @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
diff --git a/icu4c/source/i18n/unicode/numberformatter.h b/icu4c/source/i18n/unicode/numberformatter.h
index 10e4830..8d867c0 100644
--- a/icu4c/source/i18n/unicode/numberformatter.h
+++ b/icu4c/source/i18n/unicode/numberformatter.h
@@ -659,6 +659,17 @@
*/
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 @@
UErrorCode errorCode; // For RND_ERROR
} fUnion;
+ UNumberTrailingZeroDisplay fTrailingZeroDisplay = UNUM_TRAILING_ZERO_AUTO;
+
typedef PrecisionUnion::FractionSignificantSettings FractionSignificantSettings;
typedef PrecisionUnion::IncrementSettings IncrementSettings;
diff --git a/icu4c/source/i18n/unicode/unumberformatter.h b/icu4c/source/i18n/unicode/unumberformatter.h
index 1430f65..f92b780 100644
--- a/icu4c/source/i18n/unicode/unumberformatter.h
+++ b/icu4c/source/i18n/unicode/unumberformatter.h
@@ -494,6 +494,32 @@
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.
diff --git a/icu4c/source/test/intltest/numbertest_api.cpp b/icu4c/source/test/intltest/numbertest_api.cpp
index 20d0374..40124c2 100644
--- a/icu4c/source/test/intltest/numbertest_api.cpp
+++ b/icu4c/source/test/intltest/numbertest_api.cpp
@@ -2545,6 +2545,26 @@
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 @@
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() {
@@ -2888,6 +2918,25 @@
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",
u"currency/CAD precision-currency-cash",
diff --git a/icu4c/source/test/intltest/numbertest_skeletons.cpp b/icu4c/source/test/intltest/numbertest_skeletons.cpp
index 67c755d..7be57f0 100644
--- a/icu4c/source/test/intltest/numbertest_skeletons.cpp
+++ b/icu4c/source/test/intltest/numbertest_skeletons.cpp
@@ -46,22 +46,28 @@
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::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::unexpectedTokens() {
static const char16_t* cases[] = {
+ u".00/w/w",
u"group-thousands/foo",
u"precision-integer//@## group-off",
u"precision-integer//@## group-off",
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/CurrencyPrecision.java b/icu4j/main/classes/core/src/com/ibm/icu/number/CurrencyPrecision.java
index cdd3d99..6bb587a 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/number/CurrencyPrecision.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/number/CurrencyPrecision.java
@@ -40,7 +40,9 @@
*/
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");
}
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatter.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatter.java
index 091bccb..99e9ae3 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatter.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatter.java
@@ -481,6 +481,33 @@
}
/**
+ * 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
* unsafe code path.
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java
index 3da00bf..f36a98a 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java
@@ -16,6 +16,7 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
}
+ 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;
}
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/Precision.java b/icu4j/main/classes/core/src/com/ibm/icu/number/Precision.java
index 2d0a28a..31aded5 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/number/Precision.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/number/Precision.java
@@ -10,6 +10,8 @@
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 @@
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 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 @@
@Override
public void apply(DecimalQuantity value) {
value.roundToInfinity();
- value.setMinFraction(0);
+ setResolvedMinFraction(value, 0);
}
@Override
@@ -634,7 +643,7 @@
@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 @@
@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 void apply(DecimalQuantity quantity, int minInt) {
assert quantity.isZeroish();
- quantity.setMinFraction(minSig - minInt);
+ setResolvedMinFraction(quantity, minSig - minInt);
}
@Override
@@ -711,7 +720,7 @@
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 @@
@Override
public void apply(DecimalQuantity value) {
value.roundToIncrement(increment, mathContext);
- value.setMinFraction(increment.scale());
+ setResolvedMinFraction(value, increment.scale());
}
@Override
@@ -764,7 +773,7 @@
@Override
public void apply(DecimalQuantity value) {
value.roundToMagnitude(-maxFrac, mathContext);
- value.setMinFraction(minFrac);
+ setResolvedMinFraction(value, minFrac);
}
@Override
@@ -791,7 +800,7 @@
@Override
public void apply(DecimalQuantity value) {
value.roundToNickel(-maxFrac, mathContext);
- value.setMinFraction(minFrac);
+ setResolvedMinFraction(value, minFrac);
}
@Override
@@ -845,7 +854,17 @@
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;
}
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java
index 2eb887c..cd56e27 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java
@@ -39,6 +39,7 @@
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 @@
"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 @@
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
@@ -2866,6 +2897,25 @@
"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",
"currency/CAD precision-currency-cash",
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java
index 7f9645f..e563a9f 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java
@@ -30,22 +30,28 @@
"@@@##",
"@@*",
"@@+",
+ "@@+/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 void invalidTokens() {
String[] cases = {
".00x",
+ ".00i",
+ ".00/x",
+ ".00/ww",
".00##0",
".##*",
".00##*",
@@ -225,6 +234,7 @@
@Test
public void unexpectedTokens() {
String[] cases = {
+ ".00/w/w",
"group-thousands/foo",
"precision-integer//@## group-off",
"precision-integer//@## group-off",