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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
} 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 @@
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 @@
* 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 @@
*
* @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 @@
*
* @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 @@
*
* @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 @@
*
* @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 @@
*
* @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 @@
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 @@
{ 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 @@
}
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 @@
* 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 @@
* @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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
} 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 @@
"$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 @@
{ 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];