ICU-21484 Add SignDisplay NEGATIVE
diff --git a/docs/userguide/format_parse/numbers/skeletons.md b/docs/userguide/format_parse/numbers/skeletons.md index 6e4986f..227ea30 100644 --- a/docs/userguide/format_parse/numbers/skeletons.md +++ b/docs/userguide/format_parse/numbers/skeletons.md
@@ -349,6 +349,8 @@ - `sign-accounting-always` or `()!` (concise) - `sign-except-zero` or `+?` (concise) - `sign-accounting-except-zero` or `()?` (concise) +- `sign-negative` or `+-` (concise) +- `sign-accounting-negative` or `()-` (concise) For more details, see [`UNumberSignDisplay`](https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/unumberformatter_8h.html).
diff --git a/icu4c/source/i18n/number_formatimpl.cpp b/icu4c/source/i18n/number_formatimpl.cpp index eb904dc..b2325aa 100644 --- a/icu4c/source/i18n/number_formatimpl.cpp +++ b/icu4c/source/i18n/number_formatimpl.cpp
@@ -139,8 +139,10 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe, bool isPermille = utils::unitIsPermille(macros.unit); bool isCompactNotation = macros.notation.fType == Notation::NTN_COMPACT; bool isAccounting = - macros.sign == UNUM_SIGN_ACCOUNTING || macros.sign == UNUM_SIGN_ACCOUNTING_ALWAYS || - macros.sign == UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO; + macros.sign == UNUM_SIGN_ACCOUNTING || + macros.sign == UNUM_SIGN_ACCOUNTING_ALWAYS || + macros.sign == UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO || + macros.sign == UNUM_SIGN_ACCOUNTING_NEGATIVE; CurrencyUnit currency(u"", status); if (isCurrency) { currency = CurrencyUnit(macros.unit, status); // Restore CurrencyUnit from MeasureUnit
diff --git a/icu4c/source/i18n/number_patternstring.cpp b/icu4c/source/i18n/number_patternstring.cpp index 9d845056..ac9e8b7 100644 --- a/icu4c/source/i18n/number_patternstring.cpp +++ b/icu4c/source/i18n/number_patternstring.cpp
@@ -1106,6 +1106,20 @@ PatternSignType PatternStringUtils::resolveSignDisplay(UNumberSignDisplay signDi } break; + case UNUM_SIGN_NEGATIVE: + case UNUM_SIGN_ACCOUNTING_NEGATIVE: + switch (signum) { + case SIGNUM_NEG: + return PATTERN_SIGN_TYPE_NEG; + case SIGNUM_NEG_ZERO: + case SIGNUM_POS_ZERO: + case SIGNUM_POS: + return PATTERN_SIGN_TYPE_POS; + default: + break; + } + break; + case UNUM_SIGN_NEVER: return PATTERN_SIGN_TYPE_POS;
diff --git a/icu4c/source/i18n/number_skeletons.cpp b/icu4c/source/i18n/number_skeletons.cpp index 53ebf80..5aae555 100644 --- a/icu4c/source/i18n/number_skeletons.cpp +++ b/icu4c/source/i18n/number_skeletons.cpp
@@ -91,6 +91,8 @@ void U_CALLCONV initNumberSkeletons(UErrorCode& status) { b.add(u"sign-accounting-always", STEM_SIGN_ACCOUNTING_ALWAYS, status); b.add(u"sign-except-zero", STEM_SIGN_EXCEPT_ZERO, status); b.add(u"sign-accounting-except-zero", STEM_SIGN_ACCOUNTING_EXCEPT_ZERO, status); + b.add(u"sign-negative", STEM_SIGN_NEGATIVE, status); + b.add(u"sign-accounting-negative", STEM_SIGN_ACCOUNTING_NEGATIVE, status); b.add(u"decimal-auto", STEM_DECIMAL_AUTO, status); b.add(u"decimal-always", STEM_DECIMAL_ALWAYS, status); if (U_FAILURE(status)) { return; } @@ -121,6 +123,8 @@ void U_CALLCONV initNumberSkeletons(UErrorCode& status) { b.add(u"()!", STEM_SIGN_ACCOUNTING_ALWAYS, status); b.add(u"+?", STEM_SIGN_EXCEPT_ZERO, status); b.add(u"()?", STEM_SIGN_ACCOUNTING_EXCEPT_ZERO, status); + b.add(u"+-", STEM_SIGN_NEGATIVE, status); + b.add(u"()-", STEM_SIGN_ACCOUNTING_NEGATIVE, status); if (U_FAILURE(status)) { return; } // Build the CharsTrie @@ -278,6 +282,10 @@ UNumberSignDisplay stem_to_object::signDisplay(skeleton::StemEnum stem) { return UNUM_SIGN_EXCEPT_ZERO; case STEM_SIGN_ACCOUNTING_EXCEPT_ZERO: return UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO; + case STEM_SIGN_NEGATIVE: + return UNUM_SIGN_NEGATIVE; + case STEM_SIGN_ACCOUNTING_NEGATIVE: + return UNUM_SIGN_ACCOUNTING_NEGATIVE; default: return UNUM_SIGN_COUNT; // for objects, throw; for enums, return COUNT } @@ -399,6 +407,12 @@ void enum_to_stem_string::signDisplay(UNumberSignDisplay value, UnicodeString& s case UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO: sb.append(u"sign-accounting-except-zero", -1); break; + case UNUM_SIGN_NEGATIVE: + sb.append(u"sign-negative", -1); + break; + case UNUM_SIGN_ACCOUNTING_NEGATIVE: + sb.append(u"sign-accounting-negative", -1); + break; default: UPRV_UNREACHABLE; } @@ -697,6 +711,8 @@ skeleton::parseStem(const StringSegment& segment, const UCharsTrie& stemTrie, Se case STEM_SIGN_ACCOUNTING_ALWAYS: case STEM_SIGN_EXCEPT_ZERO: case STEM_SIGN_ACCOUNTING_EXCEPT_ZERO: + case STEM_SIGN_NEGATIVE: + case STEM_SIGN_ACCOUNTING_NEGATIVE: CHECK_NULL(seen, sign, status); macros.sign = stem_to_object::signDisplay(stem); return STATE_NULL; @@ -1205,6 +1221,7 @@ void blueprint_helpers::parseScientificStem(const StringSegment& segment, MacroP } else if (segment.charAt(offset) == u'?') { signDisplay = UNUM_SIGN_EXCEPT_ZERO; } else { + // NOTE: Other sign displays are not included because they aren't useful in this context goto fail; } offset++;
diff --git a/icu4c/source/i18n/number_skeletons.h b/icu4c/source/i18n/number_skeletons.h index 201267e..5c1055b 100644 --- a/icu4c/source/i18n/number_skeletons.h +++ b/icu4c/source/i18n/number_skeletons.h
@@ -108,6 +108,8 @@ enum StemEnum { STEM_SIGN_ACCOUNTING_ALWAYS, STEM_SIGN_EXCEPT_ZERO, STEM_SIGN_ACCOUNTING_EXCEPT_ZERO, + STEM_SIGN_NEGATIVE, + STEM_SIGN_ACCOUNTING_NEGATIVE, STEM_DECIMAL_AUTO, STEM_DECIMAL_ALWAYS,
diff --git a/icu4c/source/i18n/unicode/unumberformatter.h b/icu4c/source/i18n/unicode/unumberformatter.h index 754987a..bd1164d 100644 --- a/icu4c/source/i18n/unicode/unumberformatter.h +++ b/icu4c/source/i18n/unicode/unumberformatter.h
@@ -314,9 +314,12 @@ typedef enum UNumberSignDisplay { * Show the minus sign on negative numbers, and do not show the sign on positive numbers. This is the default * behavior. * + * If using this option, a sign will be displayed on negative zero, including negative numbers + * that round to zero. To hide the sign on negative zero, use the NEGATIVE option. + * * @stable ICU 60 */ - UNUM_SIGN_AUTO, + UNUM_SIGN_AUTO, /** * Show the minus sign on negative numbers and the plus sign on positive numbers, including zero. @@ -324,14 +327,14 @@ typedef enum UNumberSignDisplay { * * @stable ICU 60 */ - UNUM_SIGN_ALWAYS, + UNUM_SIGN_ALWAYS, /** * Do not show the sign on positive or negative numbers. * * @stable 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. @@ -347,7 +350,7 @@ typedef enum UNumberSignDisplay { * * @stable ICU 60 */ - UNUM_SIGN_ACCOUNTING, + UNUM_SIGN_ACCOUNTING, /** * Use the locale-dependent accounting format on negative numbers, and show the plus sign on @@ -357,7 +360,7 @@ typedef enum UNumberSignDisplay { * * @stable 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 @@ -365,7 +368,7 @@ typedef enum UNumberSignDisplay { * * @stable ICU 61 */ - UNUM_SIGN_EXCEPT_ZERO, + UNUM_SIGN_EXCEPT_ZERO, /** * Use the locale-dependent accounting format on negative numbers, and show the plus sign on @@ -374,14 +377,30 @@ typedef enum UNumberSignDisplay { * * @stable ICU 61 */ - UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO, + UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO, + +#ifndef U_HIDE_DRAFT_API + /** + * Same as AUTO, but do not show the sign on negative zero. + * + * @draft ICU 69 + */ + UNUM_SIGN_NEGATIVE, + + /** + * Same as ACCOUNTING, but do not show the sign on negative zero. + * + * @draft ICU 69 + */ + UNUM_SIGN_ACCOUNTING_NEGATIVE, +#endif // U_HIDE_DRAFT_API /** * 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 = 9, } UNumberSignDisplay; /**
diff --git a/icu4c/source/test/intltest/numbertest_api.cpp b/icu4c/source/test/intltest/numbertest_api.cpp index 04c0754..31b4357 100644 --- a/icu4c/source/test/intltest/numbertest_api.cpp +++ b/icu4c/source/test/intltest/numbertest_api.cpp
@@ -3783,6 +3783,60 @@ void NumberFormatterApiTest::sign() { u"$0.00"); assertFormatSingle( + u"Sign Negative Positive", + u"sign-negative", + u"+-", + NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_NEGATIVE), + Locale::getEnglish(), + 444444, + u"444,444"); + + assertFormatSingle( + u"Sign Negative Negative", + u"sign-negative", + u"+-", + NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_NEGATIVE), + Locale::getEnglish(), + -444444, + u"-444,444"); + + assertFormatSingle( + u"Sign Negative Negative Zero", + u"sign-negative", + u"+-", + NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_NEGATIVE), + Locale::getEnglish(), + -0.0000001, + u"0"); + + assertFormatSingle( + u"Sign Accounting-Negative Positive", + u"currency/USD sign-accounting-negative", + u"currency/USD ()-", + NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_NEGATIVE).unit(USD), + Locale::getEnglish(), + 444444, + u"$444,444.00"); + + assertFormatSingle( + u"Sign Accounting-Negative Negative", + u"currency/USD sign-accounting-negative", + u"currency/USD ()-", + NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_NEGATIVE).unit(USD), + Locale::getEnglish(), + -444444, + "($444,444.00)"); + + assertFormatSingle( + u"Sign Accounting-Negative Negative Zero", + u"currency/USD sign-accounting-negative", + u"currency/USD ()-", + NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_NEGATIVE).unit(USD), + Locale::getEnglish(), + -0.0000001, + u"$0.00"); + + assertFormatSingle( u"Sign Accounting Negative Hidden", u"currency/USD unit-width-hidden sign-accounting", u"currency/USD unit-width-hidden ()", @@ -3871,6 +3925,12 @@ void NumberFormatterApiTest::signNearZero() { { UNUM_SIGN_EXCEPT_ZERO, -0.1, u"0" }, // interesting case { UNUM_SIGN_EXCEPT_ZERO, -0.9, u"-1" }, { UNUM_SIGN_EXCEPT_ZERO, -1.1, u"-1" }, + { UNUM_SIGN_NEGATIVE, 1.1, u"1" }, + { UNUM_SIGN_NEGATIVE, 0.9, u"1" }, + { UNUM_SIGN_NEGATIVE, 0.1, u"0" }, + { UNUM_SIGN_NEGATIVE, -0.1, u"0" }, // interesting case + { UNUM_SIGN_NEGATIVE, -0.9, u"-1" }, + { UNUM_SIGN_NEGATIVE, -1.1, u"-1" }, }; for (auto& cas : cases) { auto sign = cas.sign;
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/PatternStringUtils.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/PatternStringUtils.java index 7bf65ae..d1001f6 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/PatternStringUtils.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/PatternStringUtils.java
@@ -529,6 +529,18 @@ public static PatternSignType resolveSignDisplay(SignDisplay signDisplay, Signum } break; + case NEGATIVE: + case ACCOUNTING_NEGATIVE: + switch (signum) { + case NEG: + return PatternSignType.NEG; + case NEG_ZERO: + case POS_ZERO: + case POS: + return PatternSignType.POS; + } + break; + case NEVER: return PatternSignType.POS;
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 5791e47..d347af8 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
@@ -302,6 +302,9 @@ public static enum SignDisplay { * Show the minus sign on negative numbers, and do not show the sign on positive numbers. This is * the default behavior. * + * If using this option, a sign will be displayed on negative zero, including negative numbers + * that round to zero. To hide the sign on negative zero, use the NEGATIVE option. + * * @stable ICU 60 * @see NumberFormatter */ @@ -371,6 +374,22 @@ public static enum SignDisplay { * @see NumberFormatter */ ACCOUNTING_EXCEPT_ZERO, + + /** + * Same as AUTO, but do not show the sign on negative zero. + * + * @draft ICU 69 + * @provisional This API might change or be removed in a future release. + */ + NEGATIVE, + + /** + * Same as ACCOUNTING, but do not show the sign on negative zero. + * + * @draft ICU 69 + * @provisional This API might change or be removed in a future release. + */ + ACCOUNTING_NEGATIVE, } /**
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 458001b..e7fa6ca 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
@@ -193,7 +193,8 @@ private static MicroPropsGenerator macrosToMicroGenerator(MacroProps macros, Mic boolean isCompactNotation = (macros.notation instanceof CompactNotation); boolean isAccounting = macros.sign == SignDisplay.ACCOUNTING || macros.sign == SignDisplay.ACCOUNTING_ALWAYS - || macros.sign == SignDisplay.ACCOUNTING_EXCEPT_ZERO; + || macros.sign == SignDisplay.ACCOUNTING_EXCEPT_ZERO + || macros.sign == SignDisplay.ACCOUNTING_NEGATIVE; Currency currency = isCurrency ? (Currency) macros.unit : DEFAULT_CURRENCY; UnitWidth unitWidth = UnitWidth.SHORT; if (macros.unitWidth != null) {
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 25411aa..246abea 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
@@ -113,6 +113,8 @@ static enum StemEnum { STEM_SIGN_ACCOUNTING_ALWAYS, STEM_SIGN_EXCEPT_ZERO, STEM_SIGN_ACCOUNTING_EXCEPT_ZERO, + STEM_SIGN_NEGATIVE, + STEM_SIGN_ACCOUNTING_NEGATIVE, STEM_DECIMAL_AUTO, STEM_DECIMAL_ALWAYS, @@ -189,6 +191,8 @@ static String buildStemTrie() { b.add("sign-accounting-always", StemEnum.STEM_SIGN_ACCOUNTING_ALWAYS.ordinal()); b.add("sign-except-zero", StemEnum.STEM_SIGN_EXCEPT_ZERO.ordinal()); b.add("sign-accounting-except-zero", StemEnum.STEM_SIGN_ACCOUNTING_EXCEPT_ZERO.ordinal()); + b.add("sign-negative", StemEnum.STEM_SIGN_NEGATIVE.ordinal()); + b.add("sign-accounting-negative", StemEnum.STEM_SIGN_ACCOUNTING_NEGATIVE.ordinal()); b.add("decimal-auto", StemEnum.STEM_DECIMAL_AUTO.ordinal()); b.add("decimal-always", StemEnum.STEM_DECIMAL_ALWAYS.ordinal()); @@ -217,6 +221,8 @@ static String buildStemTrie() { b.add("()!", StemEnum.STEM_SIGN_ACCOUNTING_ALWAYS.ordinal()); b.add("+?", StemEnum.STEM_SIGN_EXCEPT_ZERO.ordinal()); b.add("()?", StemEnum.STEM_SIGN_ACCOUNTING_EXCEPT_ZERO.ordinal()); + b.add("+-", StemEnum.STEM_SIGN_NEGATIVE.ordinal()); + b.add("()-", StemEnum.STEM_SIGN_ACCOUNTING_NEGATIVE.ordinal()); // Build the CharsTrie // TODO: Use SLOW or FAST here? @@ -351,6 +357,10 @@ private static SignDisplay signDisplay(StemEnum stem) { return SignDisplay.EXCEPT_ZERO; case STEM_SIGN_ACCOUNTING_EXCEPT_ZERO: return SignDisplay.ACCOUNTING_EXCEPT_ZERO; + case STEM_SIGN_NEGATIVE: + return SignDisplay.NEGATIVE; + case STEM_SIGN_ACCOUNTING_NEGATIVE: + return SignDisplay.ACCOUNTING_NEGATIVE; default: return null; // for objects, throw; for enums, return null } @@ -478,6 +488,12 @@ private static void signDisplay(SignDisplay value, StringBuilder sb) { case ACCOUNTING_EXCEPT_ZERO: sb.append("sign-accounting-except-zero"); break; + case NEGATIVE: + sb.append("sign-negative"); + break; + case ACCOUNTING_NEGATIVE: + sb.append("sign-accounting-negative"); + break; default: throw new AssertionError(); } @@ -764,6 +780,8 @@ private static ParseState parseStem(StringSegment segment, CharsTrie stemTrie, M case STEM_SIGN_ACCOUNTING_ALWAYS: case STEM_SIGN_EXCEPT_ZERO: case STEM_SIGN_ACCOUNTING_EXCEPT_ZERO: + case STEM_SIGN_NEGATIVE: + case STEM_SIGN_ACCOUNTING_NEGATIVE: checkNull(macros.sign, segment); macros.sign = StemToObject.signDisplay(stem); return ParseState.STATE_NULL; @@ -1219,6 +1237,7 @@ private static void parseScientificStem(StringSegment segment, MacroProps macros } else if (segment.charAt(offset) == '?') { signDisplay = SignDisplay.EXCEPT_ZERO; } else { + // NOTE: Other sign displays are not included because they aren't useful in this context break block; } offset++;
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 519958e..a7c6890 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
@@ -3574,6 +3574,60 @@ public void sign() { "$0.00"); assertFormatSingle( + "Sign Negative Positive", + "sign-negative", + "+-", + NumberFormatter.with().sign(SignDisplay.NEGATIVE), + ULocale.ENGLISH, + 444444, + "444,444"); + + assertFormatSingle( + "Sign Negative Negative", + "sign-negative", + "+-", + NumberFormatter.with().sign(SignDisplay.NEGATIVE), + ULocale.ENGLISH, + -444444, + "-444,444"); + + assertFormatSingle( + "Sign Negative Negative Zero", + "sign-negative", + "+-", + NumberFormatter.with().sign(SignDisplay.NEGATIVE), + ULocale.ENGLISH, + -0.0000001, + "0"); + + assertFormatSingle( + "Sign Accounting-Negative Positive", + "currency/USD sign-accounting-negative", + "currency/USD ()-", + NumberFormatter.with().sign(SignDisplay.ACCOUNTING_NEGATIVE).unit(USD), + ULocale.ENGLISH, + 444444, + "$444,444.00"); + + assertFormatSingle( + "Sign Accounting-Negative Negative", + "currency/USD sign-accounting-negative", + "currency/USD ()-", + NumberFormatter.with().sign(SignDisplay.ACCOUNTING_NEGATIVE).unit(USD), + ULocale.ENGLISH, + -444444, + "($444,444.00)"); + + assertFormatSingle( + "Sign Accounting-Negative Negative Zero", + "currency/USD sign-accounting-negative", + "currency/USD ()-", + NumberFormatter.with().sign(SignDisplay.ACCOUNTING_NEGATIVE).unit(USD), + ULocale.ENGLISH, + -0.0000001, + "$0.00"); + + assertFormatSingle( "Sign Accounting Negative Hidden", "currency/USD unit-width-hidden sign-accounting", "currency/USD unit-width-hidden ()", @@ -3643,6 +3697,12 @@ public void signNearZero() { { SignDisplay.EXCEPT_ZERO, -0.1, "0" }, // interesting case { SignDisplay.EXCEPT_ZERO, -0.9, "-1" }, { SignDisplay.EXCEPT_ZERO, -1.1, "-1" }, + { SignDisplay.NEGATIVE, 1.1, "1" }, + { SignDisplay.NEGATIVE, 0.9, "1" }, + { SignDisplay.NEGATIVE, 0.1, "0" }, + { SignDisplay.NEGATIVE, -0.1, "0" }, // interesting case + { SignDisplay.NEGATIVE, -0.9, "-1" }, + { SignDisplay.NEGATIVE, -1.1, "-1" }, }; for (Object[] cas : cases) { SignDisplay sign = (SignDisplay) cas[0];