diff --git a/icu4c/source/i18n/number_decimalquantity.cpp b/icu4c/source/i18n/number_decimalquantity.cpp index 72463576666..6f6ac9def65 100644 --- a/icu4c/source/i18n/number_decimalquantity.cpp +++ b/icu4c/source/i18n/number_decimalquantity.cpp @@ -265,6 +265,10 @@ bool DecimalQuantity::isNegative() const { return (flags & NEGATIVE_FLAG) != 0; } +int8_t DecimalQuantity::signum() const { + return isNegative() ? -1 : isZero() ? 0 : 1; +} + bool DecimalQuantity::isInfinite() const { return (flags & INFINITY_FLAG) != 0; } diff --git a/icu4c/source/i18n/number_decimalquantity.h b/icu4c/source/i18n/number_decimalquantity.h index ccb832623cb..3ff9fbeffef 100644 --- a/icu4c/source/i18n/number_decimalquantity.h +++ b/icu4c/source/i18n/number_decimalquantity.h @@ -115,6 +115,9 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { /** @return Whether the value represented by this {@link DecimalQuantity} is less than zero. */ bool isNegative() const; + /** @return -1 if the value is negative; 1 if positive; or 0 if zero. */ + int8_t signum() const; + /** @return Whether the value represented by this {@link DecimalQuantity} is infinite. */ bool isInfinite() const U_OVERRIDE; diff --git a/icu4c/source/i18n/number_formatimpl.cpp b/icu4c/source/i18n/number_formatimpl.cpp index fcabdcd83c9..e2fc4f20beb 100644 --- a/icu4c/source/i18n/number_formatimpl.cpp +++ b/icu4c/source/i18n/number_formatimpl.cpp @@ -161,8 +161,9 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps ¯os, bool safe, bool isPercent = isNoUnit && unitIsPercent(macros.unit); bool isPermille = isNoUnit && unitIsPermille(macros.unit); bool isCldrUnit = !isCurrency && !isNoUnit; - bool isAccounting = - macros.sign == UNUM_SIGN_ACCOUNTING || macros.sign == UNUM_SIGN_ACCOUNTING_ALWAYS; + bool isAccounting = macros.sign == UNUM_SIGN_ACCOUNTING + || macros.sign == UNUM_SIGN_ACCOUNTING_ALWAYS + || macros.sign == UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO; CurrencyUnit currency(kDefaultCurrency, status); if (isCurrency) { currency = CurrencyUnit(macros.unit, status); // Restore CurrencyUnit from MeasureUnit diff --git a/icu4c/source/i18n/number_modifiers.h b/icu4c/source/i18n/number_modifiers.h index 6a88828a44d..962d17b574a 100644 --- a/icu4c/source/i18n/number_modifiers.h +++ b/icu4c/source/i18n/number_modifiers.h @@ -216,31 +216,33 @@ class U_I18N_API ParameterizedModifier : public UMemory { } } - void adoptPositiveNegativeModifiers(const Modifier *positive, const Modifier *negative) { - mods[0] = positive; - mods[1] = negative; + void adoptPositiveNegativeModifiers( + const Modifier *positive, const Modifier *zero, const Modifier *negative) { + mods[2] = positive; + mods[1] = zero; + mods[0] = negative; } /** The modifier is ADOPTED. */ - void adoptSignPluralModifier(bool isNegative, StandardPlural::Form plural, const Modifier *mod) { - mods[getModIndex(isNegative, plural)] = mod; + void adoptSignPluralModifier(int8_t signum, StandardPlural::Form plural, const Modifier *mod) { + mods[getModIndex(signum, plural)] = mod; } /** Returns a reference to the modifier; no ownership change. */ - const Modifier *getModifier(bool isNegative) const { - return mods[isNegative ? 1 : 0]; + const Modifier *getModifier(int8_t signum) const { + return mods[signum + 1]; } /** Returns a reference to the modifier; no ownership change. */ - const Modifier *getModifier(bool isNegative, StandardPlural::Form plural) const { - return mods[getModIndex(isNegative, plural)]; + const Modifier *getModifier(int8_t signum, StandardPlural::Form plural) const { + return mods[getModIndex(signum, plural)]; } private: - const Modifier *mods[2 * StandardPlural::COUNT]; + const Modifier *mods[3 * StandardPlural::COUNT]; - inline static int32_t getModIndex(bool isNegative, StandardPlural::Form plural) { - return static_cast(plural) * 2 + (isNegative ? 1 : 0); + inline static int32_t getModIndex(int8_t signum, StandardPlural::Form plural) { + return static_cast(plural) * 3 + (signum + 1); } }; diff --git a/icu4c/source/i18n/number_patternmodifier.cpp b/icu4c/source/i18n/number_patternmodifier.cpp index 0599f92a4f3..0866285e459 100644 --- a/icu4c/source/i18n/number_patternmodifier.cpp +++ b/icu4c/source/i18n/number_patternmodifier.cpp @@ -38,8 +38,8 @@ MutablePatternModifier::setSymbols(const DecimalFormatSymbols *symbols, const Cu this->rules = rules; } -void MutablePatternModifier::setNumberProperties(bool isNegative, StandardPlural::Form plural) { - this->isNegative = isNegative; +void MutablePatternModifier::setNumberProperties(int8_t signum, StandardPlural::Form plural) { + this->signum = signum; this->plural = plural; } @@ -74,10 +74,12 @@ MutablePatternModifier::createImmutableAndChain(const MicroPropsGenerator *paren if (needsPlurals()) { // Slower path when we require the plural keyword. for (StandardPlural::Form plural : STANDARD_PLURAL_VALUES) { - setNumberProperties(false, plural); - pm->adoptSignPluralModifier(false, plural, createConstantModifier(status)); - setNumberProperties(true, plural); - pm->adoptSignPluralModifier(true, plural, createConstantModifier(status)); + setNumberProperties(1, plural); + pm->adoptSignPluralModifier(1, plural, createConstantModifier(status)); + setNumberProperties(0, plural); + pm->adoptSignPluralModifier(0, plural, createConstantModifier(status)); + setNumberProperties(-1, plural); + pm->adoptSignPluralModifier(-1, plural, createConstantModifier(status)); } if (U_FAILURE(status)) { delete pm; @@ -86,11 +88,13 @@ MutablePatternModifier::createImmutableAndChain(const MicroPropsGenerator *paren return new ImmutablePatternModifier(pm, rules, parent); // adopts pm } else { // Faster path when plural keyword is not needed. - setNumberProperties(false, StandardPlural::Form::COUNT); + setNumberProperties(1, StandardPlural::Form::COUNT); Modifier *positive = createConstantModifier(status); - setNumberProperties(true, StandardPlural::Form::COUNT); + setNumberProperties(0, StandardPlural::Form::COUNT); + Modifier *zero = createConstantModifier(status); + setNumberProperties(-1, StandardPlural::Form::COUNT); Modifier *negative = createConstantModifier(status); - pm->adoptPositiveNegativeModifiers(positive, negative); + pm->adoptPositiveNegativeModifiers(positive, zero, negative); if (U_FAILURE(status)) { delete pm; return nullptr; @@ -123,13 +127,13 @@ void ImmutablePatternModifier::processQuantity(DecimalQuantity &quantity, MicroP void ImmutablePatternModifier::applyToMicros(MicroProps µs, DecimalQuantity &quantity) const { if (rules == nullptr) { - micros.modMiddle = pm->getModifier(quantity.isNegative()); + micros.modMiddle = pm->getModifier(quantity.signum()); } else { // TODO: Fix this. Avoid the copy. DecimalQuantity copy(quantity); copy.roundToInfinity(); StandardPlural::Form plural = copy.getStandardPlural(rules); - micros.modMiddle = pm->getModifier(quantity.isNegative(), plural); + micros.modMiddle = pm->getModifier(quantity.signum(), plural); } } @@ -149,9 +153,9 @@ void MutablePatternModifier::processQuantity(DecimalQuantity &fq, MicroProps &mi // TODO: Fix this. Avoid the copy. DecimalQuantity copy(fq); micros.rounding.apply(copy, status); - nonConstThis->setNumberProperties(fq.isNegative(), copy.getStandardPlural(rules)); + nonConstThis->setNumberProperties(fq.signum(), copy.getStandardPlural(rules)); } else { - nonConstThis->setNumberProperties(fq.isNegative(), StandardPlural::Form::COUNT); + nonConstThis->setNumberProperties(fq.signum(), StandardPlural::Form::COUNT); } micros.modMiddle = this; } @@ -278,14 +282,17 @@ void MutablePatternModifier::enterCharSequenceMode(bool isPrefix) { inCharSequenceMode = true; // Should the output render '+' where '-' would normally appear in the pattern? - plusReplacesMinusSign = !isNegative && ( - signDisplay == UNUM_SIGN_ALWAYS || - signDisplay == UNUM_SIGN_ACCOUNTING_ALWAYS) && - patternInfo->positiveHasPlusSign() == false; + plusReplacesMinusSign = signum != -1 + && (signDisplay == UNUM_SIGN_ALWAYS + || signDisplay == UNUM_SIGN_ACCOUNTING_ALWAYS + || (signum == 1 + && (signDisplay == UNUM_SIGN_EXCEPT_ZERO + || signDisplay == UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO))) + && patternInfo->positiveHasPlusSign() == false; // Should we use the affix from the negative subpattern? (If not, we will use the positive subpattern.) bool useNegativeAffixPattern = patternInfo->hasNegativeSubpattern() && ( - isNegative || (patternInfo->negativeHasMinusSign() && plusReplacesMinusSign)); + signum == -1 || (patternInfo->negativeHasMinusSign() && plusReplacesMinusSign)); // Resolve the flags for the affix pattern. fFlags = 0; @@ -303,7 +310,7 @@ void MutablePatternModifier::enterCharSequenceMode(bool isPrefix) { // Should we prepend a sign to the pattern? if (!isPrefix || useNegativeAffixPattern) { prependSign = false; - } else if (isNegative) { + } else if (signum == -1) { prependSign = signDisplay != UNUM_SIGN_NEVER; } else { prependSign = plusReplacesMinusSign; diff --git a/icu4c/source/i18n/number_patternmodifier.h b/icu4c/source/i18n/number_patternmodifier.h index 3feaee04c8c..9c8b95f7764 100644 --- a/icu4c/source/i18n/number_patternmodifier.h +++ b/icu4c/source/i18n/number_patternmodifier.h @@ -125,13 +125,13 @@ class U_I18N_API MutablePatternModifier /** * Sets attributes of the current number being processed. * - * @param isNegative - * Whether the number is negative. + * @param signum + * -1 if negative; +1 if positive; or 0 if zero. * @param plural - * The plural form of the number, required only if the pattern contains the triple currency sign, "¤¤¤" - * (and as indicated by {@link #needsPlurals()}). + * The plural form of the number, required only if the pattern contains the triple + * currency sign, "¤¤¤" (and as indicated by {@link #needsPlurals()}). */ - void setNumberProperties(bool isNegative, StandardPlural::Form plural); + void setNumberProperties(int8_t signum, StandardPlural::Form plural); /** * Returns true if the pattern represented by this MurkyModifier requires a plural keyword in order to localize. @@ -211,7 +211,7 @@ class U_I18N_API MutablePatternModifier const PluralRules *rules; // Number details (initialized in setNumberProperties) - bool isNegative; + int8_t signum; StandardPlural::Form plural; // QuantityChain details (initialized in addToChain) diff --git a/icu4c/source/i18n/unicode/numberformatter.h b/icu4c/source/i18n/unicode/numberformatter.h index 2215d096d7f..397181536b9 100644 --- a/icu4c/source/i18n/unicode/numberformatter.h +++ b/icu4c/source/i18n/unicode/numberformatter.h @@ -190,21 +190,22 @@ typedef enum UNumberSignDisplay { * * @draft ICU 60 */ - UNUM_SIGN_AUTO, + UNUM_SIGN_AUTO, /** - * Show the minus sign on negative numbers and the plus sign on positive numbers. + * Show the minus sign on negative numbers and the plus sign on positive numbers, including zero. + * To hide the sign on zero, see {@link UNUM_SIGN_EXCEPT_ZERO}. * * @draft ICU 60 */ - UNUM_SIGN_ALWAYS, + UNUM_SIGN_ALWAYS, /** * Do not show the sign on positive or negative numbers. * * @draft ICU 60 */ - UNUM_SIGN_NEVER, + UNUM_SIGN_NEVER, /** * Use the locale-dependent accounting format on negative numbers, and do not show the sign on positive numbers. @@ -220,22 +221,41 @@ typedef enum UNumberSignDisplay { * * @draft ICU 60 */ - UNUM_SIGN_ACCOUNTING, + UNUM_SIGN_ACCOUNTING, /** - * Use the locale-dependent accounting format on negative numbers, and show the plus sign on positive numbers. - * For more information on the accounting format, see the ACCOUNTING sign display strategy. + * Use the locale-dependent accounting format on negative numbers, and show the plus sign on + * positive numbers, including zero. For more information on the accounting format, see the + * ACCOUNTING sign display strategy. To hide the sign on zero, see + * {@link UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO}. * * @draft ICU 60 */ - UNUM_SIGN_ACCOUNTING_ALWAYS, + UNUM_SIGN_ACCOUNTING_ALWAYS, + + /** + * Show the minus sign on negative numbers and the plus sign on positive numbers. Do not show a + * sign on zero. + * + * @draft ICU 61 + */ + UNUM_SIGN_EXCEPT_ZERO, + + /** + * Use the locale-dependent accounting format on negative numbers, and show the plus sign on + * positive numbers. Do not show a sign on zero. For more information on the accounting format, + * see the ACCOUNTING sign display strategy. + * + * @draft ICU 61 + */ + UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO, /** * One more than the highest UNumberSignDisplay value. * * @internal ICU 60: The numeric value may change over time; see ICU ticket #12420. */ - UNUM_SIGN_COUNT + UNUM_SIGN_COUNT } UNumberSignDisplay; /** diff --git a/icu4c/source/test/intltest/numbertest_api.cpp b/icu4c/source/test/intltest/numbertest_api.cpp index 4054e227271..1c1487372c3 100644 --- a/icu4c/source/test/intltest/numbertest_api.cpp +++ b/icu4c/source/test/intltest/numbertest_api.cpp @@ -1380,6 +1380,13 @@ void NumberFormatterApiTest::sign() { -444444, u"-444,444"); + assertFormatSingle( + u"Sign Auto Zero", + NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_AUTO), + Locale::getEnglish(), + 0, + u"0"); + assertFormatSingle( u"Sign Always Positive", NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ALWAYS), @@ -1394,6 +1401,13 @@ void NumberFormatterApiTest::sign() { -444444, u"-444,444"); + assertFormatSingle( + u"Sign Always Zero", + NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ALWAYS), + Locale::getEnglish(), + 0, + u"+0"); + assertFormatSingle( u"Sign Never Positive", NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_NEVER), @@ -1408,6 +1422,13 @@ void NumberFormatterApiTest::sign() { -444444, u"444,444"); + assertFormatSingle( + u"Sign Never Zero", + NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_NEVER), + Locale::getEnglish(), + 0, + u"0"); + assertFormatSingle( u"Sign Accounting Positive", NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING).unit(USD), @@ -1422,6 +1443,13 @@ void NumberFormatterApiTest::sign() { -444444, u"($444,444.00)"); + assertFormatSingle( + u"Sign Accounting Zero", + NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING).unit(USD), + Locale::getEnglish(), + 0, + u"$0.00"); + assertFormatSingle( u"Sign Accounting-Always Positive", NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_ALWAYS).unit(USD), @@ -1436,6 +1464,55 @@ void NumberFormatterApiTest::sign() { -444444, u"($444,444.00)"); + assertFormatSingle( + u"Sign Accounting-Always Zero", + NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_ALWAYS).unit(USD), + Locale::getEnglish(), + 0, + u"+$0.00"); + + assertFormatSingle( + u"Sign Except-Zero Positive", + NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_EXCEPT_ZERO), + Locale::getEnglish(), + 444444, + u"+444,444"); + + assertFormatSingle( + u"Sign Except-Zero Negative", + NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_EXCEPT_ZERO), + Locale::getEnglish(), + -444444, + u"-444,444"); + + assertFormatSingle( + u"Sign Except-Zero Zero", + NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_EXCEPT_ZERO), + Locale::getEnglish(), + 0, + u"0"); + + assertFormatSingle( + u"Sign Accounting-Except-Zero Positive", + NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO).unit(USD), + Locale::getEnglish(), + 444444, + u"+$444,444.00"); + + assertFormatSingle( + u"Sign Accounting-Except-Zero Negative", + NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO).unit(USD), + Locale::getEnglish(), + -444444, + u"($444,444.00)"); + + assertFormatSingle( + u"Sign Accounting-Except-Zero Zero", + NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO).unit(USD), + Locale::getEnglish(), + 0, + u"$0.00"); + assertFormatSingle( u"Sign Accounting Negative Hidden", NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING) diff --git a/icu4c/source/test/intltest/numbertest_patternmodifier.cpp b/icu4c/source/test/intltest/numbertest_patternmodifier.cpp index f30203ea80c..2d0d9d75cdf 100644 --- a/icu4c/source/test/intltest/numbertest_patternmodifier.cpp +++ b/icu4c/source/test/intltest/numbertest_patternmodifier.cpp @@ -31,13 +31,19 @@ void PatternModifierTest::testBasic() { assertSuccess("Spot 2", status); mod.setSymbols(&symbols, currency, UNUM_UNIT_WIDTH_SHORT, nullptr); - mod.setNumberProperties(false, StandardPlural::Form::COUNT); + mod.setNumberProperties(1, StandardPlural::Form::COUNT); assertEquals("Pattern a0b", u"a", getPrefix(mod, status)); assertEquals("Pattern a0b", u"b", getSuffix(mod, status)); mod.setPatternAttributes(UNUM_SIGN_ALWAYS, false); assertEquals("Pattern a0b", u"+a", getPrefix(mod, status)); assertEquals("Pattern a0b", u"b", getSuffix(mod, status)); - mod.setNumberProperties(true, StandardPlural::Form::COUNT); + mod.setNumberProperties(0, StandardPlural::Form::COUNT); + assertEquals("Pattern a0b", u"+a", getPrefix(mod, status)); + assertEquals("Pattern a0b", u"b", getSuffix(mod, status)); + mod.setPatternAttributes(UNUM_SIGN_EXCEPT_ZERO, false); + assertEquals("Pattern a0b", u"a", getPrefix(mod, status)); + assertEquals("Pattern a0b", u"b", getSuffix(mod, status)); + mod.setNumberProperties(-1, StandardPlural::Form::COUNT); assertEquals("Pattern a0b", u"-a", getPrefix(mod, status)); assertEquals("Pattern a0b", u"b", getSuffix(mod, status)); mod.setPatternAttributes(UNUM_SIGN_NEVER, false); @@ -50,20 +56,24 @@ void PatternModifierTest::testBasic() { assertSuccess("Spot 4", status); mod.setPatternInfo(&patternInfo2); mod.setPatternAttributes(UNUM_SIGN_AUTO, false); - mod.setNumberProperties(false, StandardPlural::Form::COUNT); + mod.setNumberProperties(1, StandardPlural::Form::COUNT); assertEquals("Pattern a0b;c-0d", u"a", getPrefix(mod, status)); assertEquals("Pattern a0b;c-0d", u"b", getSuffix(mod, status)); mod.setPatternAttributes(UNUM_SIGN_ALWAYS, false); assertEquals("Pattern a0b;c-0d", u"c+", getPrefix(mod, status)); assertEquals("Pattern a0b;c-0d", u"d", getSuffix(mod, status)); - mod.setNumberProperties(true, StandardPlural::Form::COUNT); + mod.setNumberProperties(0, StandardPlural::Form::COUNT); + assertEquals("Pattern a0b;c-0d", u"c+", getPrefix(mod, status)); + assertEquals("Pattern a0b;c-0d", u"d", getSuffix(mod, status)); + mod.setPatternAttributes(UNUM_SIGN_EXCEPT_ZERO, false); + assertEquals("Pattern a0b;c-0d", u"a", getPrefix(mod, status)); + assertEquals("Pattern a0b;c-0d", u"b", getSuffix(mod, status)); + mod.setNumberProperties(-1, StandardPlural::Form::COUNT); assertEquals("Pattern a0b;c-0d", u"c-", getPrefix(mod, status)); assertEquals("Pattern a0b;c-0d", u"d", getSuffix(mod, status)); mod.setPatternAttributes(UNUM_SIGN_NEVER, false); - assertEquals( - "Pattern a0b;c-0d", - u"c-", - getPrefix(mod, status)); // TODO: What should this behavior be? + // TODO: What should this behavior be? + assertEquals("Pattern a0b;c-0d", u"c-", getPrefix(mod, status)); assertEquals("Pattern a0b;c-0d", u"d", getSuffix(mod, status)); assertSuccess("Spot 5", status); } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity.java index 2f2fad57b72..c3e9734c7cb 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity.java @@ -113,6 +113,9 @@ public interface DecimalQuantity extends PluralRules.IFixedDecimal { /** @return Whether the value represented by this {@link DecimalQuantity} is less than zero. */ public boolean isNegative(); + /** @return -1 if the value is negative; 1 if positive; or 0 if zero. */ + public int signum(); + /** @return Whether the value represented by this {@link DecimalQuantity} is infinite. */ @Override public boolean isInfinite(); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java index 03bd98379f8..4be22a664a7 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java @@ -296,6 +296,11 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { return (flags & NEGATIVE_FLAG) != 0; } + @Override + public int signum() { + return isNegative() ? -1 : isZero() ? 0 : 1; + } + @Override public boolean isInfinite() { return (flags & INFINITY_FLAG) != 0; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MutablePatternModifier.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MutablePatternModifier.java index 3eb1a9b3b6e..09a80cc9c23 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MutablePatternModifier.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MutablePatternModifier.java @@ -48,7 +48,7 @@ public class MutablePatternModifier PluralRules rules; // Number details - boolean isNegative; + int signum; StandardPlural plural; // QuantityChain details @@ -121,15 +121,15 @@ public class MutablePatternModifier /** * Sets attributes of the current number being processed. * - * @param isNegative - * Whether the number is negative. + * @param signum + * -1 if negative; +1 if positive; or 0 if zero. * @param plural * The plural form of the number, required only if the pattern contains the triple * currency sign, "¤¤¤" (and as indicated by {@link #needsPlurals()}). */ - public void setNumberProperties(boolean isNegative, StandardPlural plural) { + public void setNumberProperties(int signum, StandardPlural plural) { assert (plural != null) == needsPlurals(); - this.isNegative = isNegative; + this.signum = signum; this.plural = plural; } @@ -172,20 +172,24 @@ public class MutablePatternModifier // Slower path when we require the plural keyword. ParameterizedModifier pm = new ParameterizedModifier(); for (StandardPlural plural : StandardPlural.VALUES) { - setNumberProperties(false, plural); - pm.setModifier(false, plural, createConstantModifier(a, b)); - setNumberProperties(true, plural); - pm.setModifier(true, plural, createConstantModifier(a, b)); + setNumberProperties(1, plural); + pm.setModifier(1, plural, createConstantModifier(a, b)); + setNumberProperties(0, plural); + pm.setModifier(0, plural, createConstantModifier(a, b)); + setNumberProperties(-1, plural); + pm.setModifier(-1, plural, createConstantModifier(a, b)); } pm.freeze(); return new ImmutablePatternModifier(pm, rules, parent); } else { // Faster path when plural keyword is not needed. - setNumberProperties(false, null); + setNumberProperties(1, null); Modifier positive = createConstantModifier(a, b); - setNumberProperties(true, null); + setNumberProperties(0, null); + Modifier zero = createConstantModifier(a, b); + setNumberProperties(-1, null); Modifier negative = createConstantModifier(a, b); - ParameterizedModifier pm = new ParameterizedModifier(positive, negative); + ParameterizedModifier pm = new ParameterizedModifier(positive, zero, negative); return new ImmutablePatternModifier(pm, null, parent); } } @@ -236,13 +240,13 @@ public class MutablePatternModifier public void applyToMicros(MicroProps micros, DecimalQuantity quantity) { if (rules == null) { - micros.modMiddle = pm.getModifier(quantity.isNegative()); + micros.modMiddle = pm.getModifier(quantity.signum()); } else { // TODO: Fix this. Avoid the copy. DecimalQuantity copy = quantity.createCopy(); copy.roundToInfinity(); StandardPlural plural = copy.getStandardPlural(rules); - micros.modMiddle = pm.getModifier(quantity.isNegative(), plural); + micros.modMiddle = pm.getModifier(quantity.signum(), plural); } } } @@ -260,9 +264,9 @@ public class MutablePatternModifier // TODO: Fix this. Avoid the copy. DecimalQuantity copy = fq.createCopy(); micros.rounding.apply(copy); - setNumberProperties(fq.isNegative(), copy.getStandardPlural(rules)); + setNumberProperties(fq.signum(), copy.getStandardPlural(rules)); } else { - setNumberProperties(fq.isNegative(), null); + setNumberProperties(fq.signum(), null); } micros.modMiddle = this; return micros; @@ -370,14 +374,18 @@ public class MutablePatternModifier inCharSequenceMode = true; // Should the output render '+' where '-' would normally appear in the pattern? - plusReplacesMinusSign = !isNegative - && (signDisplay == SignDisplay.ALWAYS || signDisplay == SignDisplay.ACCOUNTING_ALWAYS) + plusReplacesMinusSign = signum != -1 + && (signDisplay == SignDisplay.ALWAYS + || signDisplay == SignDisplay.ACCOUNTING_ALWAYS + || (signum == 1 + && (signDisplay == SignDisplay.EXCEPT_ZERO + || signDisplay == SignDisplay.ACCOUNTING_EXCEPT_ZERO))) && patternInfo.positiveHasPlusSign() == false; // Should we use the affix from the negative subpattern? (If not, we will use the positive // subpattern.) boolean useNegativeAffixPattern = patternInfo.hasNegativeSubpattern() - && (isNegative || (patternInfo.negativeHasMinusSign() && plusReplacesMinusSign)); + && (signum == -1 || (patternInfo.negativeHasMinusSign() && plusReplacesMinusSign)); // Resolve the flags for the affix pattern. flags = 0; @@ -395,7 +403,7 @@ public class MutablePatternModifier // Should we prepend a sign to the pattern? if (!isPrefix || useNegativeAffixPattern) { prependSign = false; - } else if (isNegative) { + } else if (signum == -1) { prependSign = signDisplay != SignDisplay.NEVER; } else { prependSign = plusReplacesMinusSign; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/ParameterizedModifier.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/ParameterizedModifier.java index 3ccf5080490..a553be4f30e 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/ParameterizedModifier.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/ParameterizedModifier.java @@ -10,6 +10,7 @@ import com.ibm.icu.impl.StandardPlural; */ public class ParameterizedModifier { private final Modifier positive; + private final Modifier zero; private final Modifier negative; final Modifier[] mods; boolean frozen; @@ -20,8 +21,9 @@ public class ParameterizedModifier { *

* If this constructor is used, a plural form CANNOT be passed to {@link #getModifier}. */ - public ParameterizedModifier(Modifier positive, Modifier negative) { + public ParameterizedModifier(Modifier positive, Modifier zero, Modifier negative) { this.positive = positive; + this.zero = zero; this.negative = negative; this.mods = null; this.frozen = true; @@ -36,33 +38,34 @@ public class ParameterizedModifier { */ public ParameterizedModifier() { this.positive = null; + this.zero = null; this.negative = null; - this.mods = new Modifier[2 * StandardPlural.COUNT]; + this.mods = new Modifier[3 * StandardPlural.COUNT]; this.frozen = false; } - public void setModifier(boolean isNegative, StandardPlural plural, Modifier mod) { + public void setModifier(int signum, StandardPlural plural, Modifier mod) { assert !frozen; - mods[getModIndex(isNegative, plural)] = mod; + mods[getModIndex(signum, plural)] = mod; } public void freeze() { frozen = true; } - public Modifier getModifier(boolean isNegative) { + public Modifier getModifier(int signum) { assert frozen; assert mods == null; - return isNegative ? negative : positive; + return signum == 0 ? zero : signum < 0 ? negative : positive; } - public Modifier getModifier(boolean isNegative, StandardPlural plural) { + public Modifier getModifier(int signum, StandardPlural plural) { assert frozen; assert positive == null; - return mods[getModIndex(isNegative, plural)]; + return mods[getModIndex(signum, plural)]; } - private static int getModIndex(boolean isNegative, StandardPlural plural) { - return plural.ordinal() * 2 + (isNegative ? 1 : 0); + private static int getModIndex(int signum, StandardPlural plural) { + return plural.ordinal() * 3 + (signum + 1); } } 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 5aece9b81b7..7a819b18aee 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 @@ -191,7 +191,8 @@ public final class NumberFormatter { AUTO, /** - * Show the minus sign on negative numbers and the plus sign on positive numbers. + * Show the minus sign on negative numbers and the plus sign on positive numbers, including zero. + * To hide the sign on zero, see {@link #EXCEPT_ZERO}. * * @draft ICU 60 * @provisional This API might change or be removed in a future release. @@ -229,14 +230,36 @@ public final class NumberFormatter { /** * Use the locale-dependent accounting format on negative numbers, and show the plus sign on - * positive numbers. For more information on the accounting format, see the ACCOUNTING sign - * display strategy. + * positive numbers, including zero. For more information on the accounting format, see the + * ACCOUNTING sign display strategy. To hide the sign on zero, see + * {@link #ACCOUNTING_EXCEPT_ZERO}. * * @draft ICU 60 * @provisional This API might change or be removed in a future release. * @see NumberFormatter */ ACCOUNTING_ALWAYS, + + /** + * Show the minus sign on negative numbers and the plus sign on positive numbers. Do not show a + * sign on zero. + * + * @draft ICU 61 + * @provisional This API might change or be removed in a future release. + * @see NumberFormatter + */ + EXCEPT_ZERO, + + /** + * Use the locale-dependent accounting format on negative numbers, and show the plus sign on + * positive numbers. Do not show a sign on zero. For more information on the accounting format, + * see the ACCOUNTING sign display strategy. + * + * @draft ICU 61 + * @provisional This API might change or be removed in a future release. + * @see NumberFormatter + */ + ACCOUNTING_EXCEPT_ZERO, } /** diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java index 0702398bfb2..d612ab27bf3 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java @@ -115,7 +115,8 @@ class NumberFormatterImpl { boolean isPermille = isNoUnit && unitIsPermille(macros.unit); boolean isCldrUnit = !isCurrency && !isNoUnit; boolean isAccounting = macros.sign == SignDisplay.ACCOUNTING - || macros.sign == SignDisplay.ACCOUNTING_ALWAYS; + || macros.sign == SignDisplay.ACCOUNTING_ALWAYS + || macros.sign == SignDisplay.ACCOUNTING_EXCEPT_ZERO; Currency currency = isCurrency ? (Currency) macros.unit : DEFAULT_CURRENCY; UnitWidth unitWidth = UnitWidth.SHORT; if (macros.unitWidth != null) { diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_SimpleStorage.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_SimpleStorage.java index c4db104e5f6..3383fad20b2 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_SimpleStorage.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_SimpleStorage.java @@ -509,6 +509,11 @@ public class DecimalQuantity_SimpleStorage implements DecimalQuantity { return (flags & NEGATIVE_FLAG) != 0; } + @Override + public int signum() { + return isNegative() ? -1 : isZero() ? 0 : 1; + } + private void setNegative(boolean isNegative) { flags = (flags & (~NEGATIVE_FLAG)) | (isNegative ? NEGATIVE_FLAG : 0); } diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/MutablePatternModifierTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/MutablePatternModifierTest.java index 9622e8dd1aa..7069073c14b 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/MutablePatternModifierTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/MutablePatternModifierTest.java @@ -32,13 +32,19 @@ public class MutablePatternModifierTest { UnitWidth.SHORT, null); - mod.setNumberProperties(false, null); + mod.setNumberProperties(1, null); assertEquals("a", getPrefix(mod)); assertEquals("b", getSuffix(mod)); mod.setPatternAttributes(SignDisplay.ALWAYS, false); assertEquals("+a", getPrefix(mod)); assertEquals("b", getSuffix(mod)); - mod.setNumberProperties(true, null); + mod.setNumberProperties(0, null); + assertEquals("+a", getPrefix(mod)); + assertEquals("b", getSuffix(mod)); + mod.setPatternAttributes(SignDisplay.EXCEPT_ZERO, false); + assertEquals("a", getPrefix(mod)); + assertEquals("b", getSuffix(mod)); + mod.setNumberProperties(-1, null); assertEquals("-a", getPrefix(mod)); assertEquals("b", getSuffix(mod)); mod.setPatternAttributes(SignDisplay.NEVER, false); @@ -47,13 +53,19 @@ public class MutablePatternModifierTest { mod.setPatternInfo(PatternStringParser.parseToPatternInfo("a0b;c-0d")); mod.setPatternAttributes(SignDisplay.AUTO, false); - mod.setNumberProperties(false, null); + mod.setNumberProperties(1, null); assertEquals("a", getPrefix(mod)); assertEquals("b", getSuffix(mod)); mod.setPatternAttributes(SignDisplay.ALWAYS, false); assertEquals("c+", getPrefix(mod)); assertEquals("d", getSuffix(mod)); - mod.setNumberProperties(true, null); + mod.setNumberProperties(0, null); + assertEquals("c+", getPrefix(mod)); + assertEquals("d", getSuffix(mod)); + mod.setPatternAttributes(SignDisplay.EXCEPT_ZERO, false); + assertEquals("a", getPrefix(mod)); + assertEquals("b", getSuffix(mod)); + mod.setNumberProperties(-1, null); assertEquals("c-", getPrefix(mod)); assertEquals("d", getSuffix(mod)); mod.setPatternAttributes(SignDisplay.NEVER, false); 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 0e6aaa12860..e117875264c 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 @@ -1477,6 +1477,14 @@ public class NumberFormatterApiTest { -444444, "-444,444"); + assertFormatSingle( + "Sign Auto Zero", + "", + NumberFormatter.with().sign(SignDisplay.AUTO), + ULocale.ENGLISH, + 0, + "0"); + assertFormatSingle( "Sign Always Positive", "sign=ALWAYS", @@ -1493,6 +1501,14 @@ public class NumberFormatterApiTest { -444444, "-444,444"); + assertFormatSingle( + "Sign Always Zero", + "", + NumberFormatter.with().sign(SignDisplay.ALWAYS), + ULocale.ENGLISH, + 0, + "+0"); + assertFormatSingle( "Sign Never Positive", "sign=NEVER", @@ -1509,6 +1525,14 @@ public class NumberFormatterApiTest { -444444, "444,444"); + assertFormatSingle( + "Sign Never Zero", + "", + NumberFormatter.with().sign(SignDisplay.NEVER), + ULocale.ENGLISH, + 0, + "0"); + assertFormatSingle( "Sign Accounting Positive", "$USD sign=ACCOUNTING", @@ -1525,6 +1549,14 @@ public class NumberFormatterApiTest { -444444, "($444,444.00)"); + assertFormatSingle( + "Sign Accounting Zero", + "", + NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD), + ULocale.ENGLISH, + 0, + "$0.00"); + assertFormatSingle( "Sign Accounting-Always Positive", "$USD sign=ACCOUNTING_ALWAYS", @@ -1541,6 +1573,62 @@ public class NumberFormatterApiTest { -444444, "($444,444.00)"); + assertFormatSingle( + "Sign Accounting-Always Zero", + "", + NumberFormatter.with().sign(SignDisplay.ACCOUNTING_ALWAYS).unit(USD), + ULocale.ENGLISH, + 0, + "+$0.00"); + + assertFormatSingle( + "Sign Except-Zero Positive", + "", + NumberFormatter.with().sign(SignDisplay.EXCEPT_ZERO), + ULocale.ENGLISH, + 444444, + "+444,444"); + + assertFormatSingle( + "Sign Always Negative", + "", + NumberFormatter.with().sign(SignDisplay.EXCEPT_ZERO), + ULocale.ENGLISH, + -444444, + "-444,444"); + + assertFormatSingle( + "Sign Except-Zero Zero", + "", + NumberFormatter.with().sign(SignDisplay.EXCEPT_ZERO), + ULocale.ENGLISH, + 0, + "0"); + + assertFormatSingle( + "Sign Accounting-Except-Zero Positive", + "$USD sign=ACCOUNTING_ALWAYS", + NumberFormatter.with().sign(SignDisplay.ACCOUNTING_EXCEPT_ZERO).unit(USD), + ULocale.ENGLISH, + 444444, + "+$444,444.00"); + + assertFormatSingle( + "Sign Accounting-Except-Zero Negative", + "$USD sign=ACCOUNTING_ALWAYS", + NumberFormatter.with().sign(SignDisplay.ACCOUNTING_EXCEPT_ZERO).unit(USD), + ULocale.ENGLISH, + -444444, + "($444,444.00)"); + + assertFormatSingle( + "Sign Accounting-Except-Zero Zero", + "", + NumberFormatter.with().sign(SignDisplay.ACCOUNTING_EXCEPT_ZERO).unit(USD), + ULocale.ENGLISH, + 0, + "$0.00"); + assertFormatSingle( "Sign Accounting Negative Hidden", "$USD unit-width=HIDDEN sign=ACCOUNTING",