From 469456604aa6aa00476f4c570748d708b3319aee Mon Sep 17 00:00:00 2001 From: "Shane F. Carr" Date: Tue, 21 Sep 2021 20:09:45 +0000 Subject: [PATCH] ICU-21675 Allow NaN and Infinity to appear in DecNum strings See #1871 --- icu4c/source/i18n/number_decimalquantity.cpp | 6 +++++- icu4c/source/i18n/number_decnum.h | 7 +++++++ icu4c/source/i18n/number_rounding.cpp | 7 +++++-- icu4c/source/i18n/number_skeletons.cpp | 2 +- icu4c/source/i18n/number_utils.cpp | 18 ++++++++++++------ .../source/test/intltest/numbertest_range.cpp | 4 ++++ .../test/intltest/numbertest_skeletons.cpp | 2 ++ .../dev/test/number/NumberSkeletonTest.java | 2 ++ 8 files changed, 38 insertions(+), 10 deletions(-) diff --git a/icu4c/source/i18n/number_decimalquantity.cpp b/icu4c/source/i18n/number_decimalquantity.cpp index 308c0e30e81..6a2847b1c18 100644 --- a/icu4c/source/i18n/number_decimalquantity.cpp +++ b/icu4c/source/i18n/number_decimalquantity.cpp @@ -539,7 +539,11 @@ void DecimalQuantity::_setToDecNum(const DecNum& decnum, UErrorCode& status) { if (decnum.isNegative()) { flags |= NEGATIVE_FLAG; } - if (!decnum.isZero()) { + if (decnum.isNaN()) { + flags |= NAN_FLAG; + } else if (decnum.isInfinity()) { + flags |= INFINITY_FLAG; + } else if (!decnum.isZero()) { readDecNumberToBcd(decnum); compact(); } diff --git a/icu4c/source/i18n/number_decnum.h b/icu4c/source/i18n/number_decnum.h index 3bb8d107807..94a0b31bcb5 100644 --- a/icu4c/source/i18n/number_decnum.h +++ b/icu4c/source/i18n/number_decnum.h @@ -56,6 +56,13 @@ class U_I18N_API DecNum : public UMemory { bool isZero() const; + /** Is infinity or NaN */ + bool isSpecial() const; + + bool isInfinity() const; + + bool isNaN() const; + void toString(ByteSink& output, UErrorCode& status) const; inline CharString toCharString(UErrorCode& status) const { diff --git a/icu4c/source/i18n/number_rounding.cpp b/icu4c/source/i18n/number_rounding.cpp index 7059a2ecb94..877df63c8f6 100644 --- a/icu4c/source/i18n/number_rounding.cpp +++ b/icu4c/source/i18n/number_rounding.cpp @@ -13,6 +13,7 @@ #include "double-conversion.h" #include "number_roundingutils.h" #include "number_skeletons.h" +#include "number_decnum.h" #include "putilimp.h" #include "string_segment.h" @@ -35,8 +36,10 @@ void number::impl::parseIncrementOption(const StringSegment &segment, // Utilize DecimalQuantity/decNumber to parse this for us. DecimalQuantity dq; UErrorCode localStatus = U_ZERO_ERROR; - dq.setToDecNumber({buffer.data(), buffer.length()}, localStatus); - if (U_FAILURE(localStatus)) { + DecNum decnum; + decnum.setTo({buffer.data(), buffer.length()}, localStatus); + dq.setToDecNum(decnum, localStatus); + if (U_FAILURE(localStatus) || decnum.isSpecial()) { // throw new SkeletonSyntaxException("Invalid rounding increment", segment, e); status = U_NUMBER_SKELETON_SYNTAX_ERROR; return; diff --git a/icu4c/source/i18n/number_skeletons.cpp b/icu4c/source/i18n/number_skeletons.cpp index 9a2993fda9f..ba9eee6b5d0 100644 --- a/icu4c/source/i18n/number_skeletons.cpp +++ b/icu4c/source/i18n/number_skeletons.cpp @@ -1493,7 +1493,7 @@ void blueprint_helpers::parseScaleOption(const StringSegment& segment, MacroProp LocalPointer decnum(new DecNum(), status); if (U_FAILURE(status)) { return; } decnum->setTo({buffer.data(), buffer.length()}, status); - if (U_FAILURE(status)) { + if (U_FAILURE(status) || decnum->isSpecial()) { // This is a skeleton syntax error; don't let the low-level decnum error bubble up status = U_NUMBER_SKELETON_SYNTAX_ERROR; return; diff --git a/icu4c/source/i18n/number_utils.cpp b/icu4c/source/i18n/number_utils.cpp index b93d3d0ea4b..ad705321407 100644 --- a/icu4c/source/i18n/number_utils.cpp +++ b/icu4c/source/i18n/number_utils.cpp @@ -180,12 +180,6 @@ void DecNum::_setTo(const char* str, int32_t maxDigits, UErrorCode& status) { status = U_UNSUPPORTED_ERROR; return; } - - // For consistency with Java BigDecimal, no support for DecNum that is NaN or Infinity! - if (decNumberIsSpecial(fData.getAlias())) { - status = U_UNSUPPORTED_ERROR; - return; - } } void @@ -252,6 +246,18 @@ bool DecNum::isZero() const { return decNumberIsZero(fData.getAlias()); } +bool DecNum::isSpecial() const { + return decNumberIsSpecial(fData.getAlias()); +} + +bool DecNum::isInfinity() const { + return decNumberIsInfinite(fData.getAlias()); +} + +bool DecNum::isNaN() const { + return decNumberIsNaN(fData.getAlias()); +} + void DecNum::toString(ByteSink& output, UErrorCode& status) const { if (U_FAILURE(status)) { return; diff --git a/icu4c/source/test/intltest/numbertest_range.cpp b/icu4c/source/test/intltest/numbertest_range.cpp index a6bf8314143..2f59f43ab6c 100644 --- a/icu4c/source/test/intltest/numbertest_range.cpp +++ b/icu4c/source/test/intltest/numbertest_range.cpp @@ -677,6 +677,8 @@ void NumberRangeFormatterTest::testNaNInfinity() { auto result4 = lnf.formatFormattableRange(uprv_getNaN(), 0, status); auto result5 = lnf.formatFormattableRange(0, uprv_getNaN(), status); auto result6 = lnf.formatFormattableRange(uprv_getNaN(), uprv_getNaN(), status); + auto result7 = lnf.formatFormattableRange({"1000", status}, {"Infinity", status}, status); + auto result8 = lnf.formatFormattableRange({"-Infinity", status}, {"NaN", status}, status); assertEquals("0 - inf", u"-∞ – 0", result1.toTempString(status)); assertEquals("-inf - 0", u"0–∞", result2.toTempString(status)); @@ -684,6 +686,8 @@ void NumberRangeFormatterTest::testNaNInfinity() { assertEquals("NaN - 0", u"NaN–0", result4.toTempString(status)); assertEquals("0 - NaN", u"0–NaN", result5.toTempString(status)); assertEquals("NaN - NaN", u"~NaN", result6.toTempString(status)); + assertEquals("1000 - inf", u"1,000–∞", result7.toTempString(status)); + assertEquals("-inf - NaN", u"-∞ – NaN", result8.toTempString(status)); } void NumberRangeFormatterTest::testPlurals() { diff --git a/icu4c/source/test/intltest/numbertest_skeletons.cpp b/icu4c/source/test/intltest/numbertest_skeletons.cpp index 7be57f044ad..f09fb60c8c6 100644 --- a/icu4c/source/test/intltest/numbertest_skeletons.cpp +++ b/icu4c/source/test/intltest/numbertest_skeletons.cpp @@ -184,9 +184,11 @@ void NumberSkeletonTest::invalidTokens() { u"scientific/ee", u"precision-increment/xxx", u"precision-increment/NaN", + u"precision-increment/Infinity", u"precision-increment/0.1.2", u"scale/xxx", u"scale/NaN", + u"scale/Infinity", u"scale/0.1.2", u"scale/français", // non-invariant characters for C++ u"currency/dummy", diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java index e563a9fbfb5..6b99583a1c8 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java @@ -167,9 +167,11 @@ public class NumberSkeletonTest { "scientific/ee", "precision-increment/xxx", "precision-increment/NaN", + "precision-increment/Infinity", "precision-increment/0.1.2", "scale/xxx", "scale/NaN", + "scale/Infinity", "scale/0.1.2", "scale/français", // non-invariant characters for C++ "currency/dummy",