mirror of
https://github.com/unicode-org/icu.git
synced 2025-04-10 07:39:16 +00:00
parent
6bfa5c02ed
commit
b79c299f90
14 changed files with 318 additions and 28 deletions
|
@ -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
|
||||
|
|
|
@ -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 ¤cy, 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);
|
||||
|
|
|
@ -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 ¯os,
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Add table
Reference in a new issue