From 237acf183a1c5842103487b1e787e5a0f21a8a9e Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Tue, 6 Feb 2018 07:52:58 +0000 Subject: [PATCH 001/129] ICU-13574 Porting the parsing utility classes StringSegment and UnicodeSetStaticCache to C++. X-SVN-Rev: 40841 --- icu4c/source/i18n/Makefile.in | 3 +- icu4c/source/i18n/numparse_stringsegment.cpp | 79 +++++++++++ icu4c/source/i18n/numparse_stringsegment.h | 79 +++++++++++ icu4c/source/i18n/numparse_types.h | 22 ++++ icu4c/source/i18n/numparse_unisets.cpp | 124 ++++++++++++++++++ icu4c/source/i18n/numparse_unisets.h | 72 ++++++++++ icu4c/source/i18n/ucln_in.h | 1 + icu4c/source/test/intltest/Makefile.in | 2 +- icu4c/source/test/intltest/numbertest.h | 30 +++++ .../intltest/numbertest_stringsegment.cpp | 94 +++++++++++++ .../test/intltest/numbertest_unisets.cpp | 99 ++++++++++++++ 11 files changed, 603 insertions(+), 2 deletions(-) create mode 100644 icu4c/source/i18n/numparse_stringsegment.cpp create mode 100644 icu4c/source/i18n/numparse_stringsegment.h create mode 100644 icu4c/source/i18n/numparse_types.h create mode 100644 icu4c/source/i18n/numparse_unisets.cpp create mode 100644 icu4c/source/i18n/numparse_unisets.h create mode 100644 icu4c/source/test/intltest/numbertest_stringsegment.cpp create mode 100644 icu4c/source/test/intltest/numbertest_unisets.cpp diff --git a/icu4c/source/i18n/Makefile.in b/icu4c/source/i18n/Makefile.in index dda6050af5..2b9cca7055 100644 --- a/icu4c/source/i18n/Makefile.in +++ b/icu4c/source/i18n/Makefile.in @@ -107,7 +107,8 @@ number_affixutils.o number_compact.o number_decimalquantity.o \ number_decimfmtprops.o number_fluent.o number_formatimpl.o number_grouping.o \ number_integerwidth.o number_longnames.o number_modifiers.o number_notation.o \ number_padding.o number_patternmodifier.o number_patternstring.o \ -number_rounding.o number_scientific.o number_stringbuilder.o +number_rounding.o number_scientific.o number_stringbuilder.o \ +numparse_stringsegment.o numparse_unisets.o ## Header files to install diff --git a/icu4c/source/i18n/numparse_stringsegment.cpp b/icu4c/source/i18n/numparse_stringsegment.cpp new file mode 100644 index 0000000000..ecabab5faa --- /dev/null +++ b/icu4c/source/i18n/numparse_stringsegment.cpp @@ -0,0 +1,79 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT + +#include "numparse_types.h" +#include "numparse_stringsegment.h" +#include "putilimp.h" +#include "unicode/utf16.h" + +using namespace icu; +using namespace icu::numparse; +using namespace icu::numparse::impl; + + +StringSegment::StringSegment(const UnicodeString &str) : fStr(str), fStart(0), fEnd(str.length()) {} + +int32_t StringSegment::getOffset() const { + return fStart; +} + +void StringSegment::setOffset(int32_t start) { + fStart = start; +} + +void StringSegment::adjustOffset(int32_t delta) { + fStart += delta; +} + +void StringSegment::setLength(int32_t length) { + fEnd = fStart + length; +} + +void StringSegment::resetLength() { + fEnd = fStr.length(); +} + +int32_t StringSegment::length() const { + return fEnd - fStart; +} + +char16_t StringSegment::charAt(int32_t index) const { + return fStr.charAt(index + fStart); +} + +UChar32 StringSegment::codePointAt(int32_t index) const { + return fStr.char32At(index + fStart); +} + +UnicodeString StringSegment::toUnicodeString() const { + return UnicodeString(fStr, fStart, fEnd - fStart); +} + +UChar32 StringSegment::getCodePoint() const { + char16_t lead = fStr.charAt(fStart); + if (U16_IS_LEAD(lead) && fStart + 1 < fEnd) { + return fStr.char32At(fStart); + } else if (U16_IS_SURROGATE(lead)) { + return -1; + } else { + return lead; + } +} + +int32_t StringSegment::getCommonPrefixLength(const UnicodeString &other) { + int32_t offset = 0; + for (; offset < uprv_min(length(), other.length());) { + if (charAt(offset) != other.charAt(offset)) { + break; + } + offset++; + } + return offset; +} + + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/numparse_stringsegment.h b/icu4c/source/i18n/numparse_stringsegment.h new file mode 100644 index 0000000000..30f11af7a1 --- /dev/null +++ b/icu4c/source/i18n/numparse_stringsegment.h @@ -0,0 +1,79 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#ifndef __NUMPARSE_STRINGSEGMENT_H__ +#define __NUMPARSE_STRINGSEGMENT_H__ + +#include "numparse_types.h" +#include "number_types.h" +#include "unicode/unistr.h" + +U_NAMESPACE_BEGIN +namespace numparse { +namespace impl { + +/** + * A mutable class allowing for a String with a variable offset and length. The charAt, length, and + * subSequence methods all operate relative to the fixed offset into the String. + * + * @author sffc + */ +class StringSegment : public UMemory, public ::icu::number::impl::CharSequence { + public: + explicit StringSegment(const UnicodeString &str); + + int32_t getOffset() const; + + void setOffset(int32_t start); + + /** + * Equivalent to setOffset(getOffset()+delta). + * + *

+ * This method is usually called by a Matcher to register that a char was consumed. If the char is + * strong (it usually is, except for things like whitespace), follow this with a call to + * {@link ParsedNumber#setCharsConsumed}. For more information on strong chars, see that method. + */ + void adjustOffset(int32_t delta); + + void setLength(int32_t length); + + void resetLength(); + + int32_t length() const override; + + char16_t charAt(int32_t index) const override; + + UChar32 codePointAt(int32_t index) const override; + + UnicodeString toUnicodeString() const override; + + /** + * Returns the first code point in the string segment, or -1 if the string starts with an invalid + * code point. + */ + UChar32 getCodePoint() const; + + /** + * Returns the length of the prefix shared by this StringSegment and the given CharSequence. For + * example, if this string segment is "aab", and the char sequence is "aac", this method returns 2, + * since the first 2 characters are the same. + */ + int32_t getCommonPrefixLength(const UnicodeString &other); + + private: + const UnicodeString fStr; + int32_t fStart; + int32_t fEnd; +}; + + +} // namespace impl +} // namespace numparse +U_NAMESPACE_END + +#endif //__NUMPARSE_STRINGSEGMENT_H__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/numparse_types.h b/icu4c/source/i18n/numparse_types.h new file mode 100644 index 0000000000..b607f36cc9 --- /dev/null +++ b/icu4c/source/i18n/numparse_types.h @@ -0,0 +1,22 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#ifndef __NUMPARSE_TYPES_H__ +#define __NUMPARSE_TYPES_H__ + +#include "unicode/uobject.h" + +U_NAMESPACE_BEGIN +namespace numparse { +namespace impl { + + +} // namespace impl +} // namespace numparse +U_NAMESPACE_END + +#endif //__NUMPARSE_TYPES_H__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/numparse_unisets.cpp b/icu4c/source/i18n/numparse_unisets.cpp new file mode 100644 index 0000000000..8477870e29 --- /dev/null +++ b/icu4c/source/i18n/numparse_unisets.cpp @@ -0,0 +1,124 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT + +#include "numparse_unisets.h" +#include "numparse_types.h" +#include "umutex.h" +#include "ucln_in.h" +#include "unicode/uniset.h" + +using namespace icu; +using namespace icu::numparse; +using namespace icu::numparse::impl; +using namespace icu::numparse::impl::unisets; + + +namespace { + +UnicodeSet* gUnicodeSets[COUNT] = {}; + +UnicodeSet* computeUnion(Key k1, Key k2) { + UnicodeSet* result = new UnicodeSet(); + if (result == nullptr) { + return nullptr; + } + result->addAll(*gUnicodeSets[k1]); + result->addAll(*gUnicodeSets[k2]); + result->freeze(); + return result; +} + +UnicodeSet* computeUnion(Key k1, Key k2, Key k3) { + UnicodeSet* result = new UnicodeSet(); + if (result == nullptr) { + return nullptr; + } + result->addAll(*gUnicodeSets[k1]); + result->addAll(*gUnicodeSets[k2]); + result->addAll(*gUnicodeSets[k3]); + result->freeze(); + return result; +} + +icu::UInitOnce gNumberParseUniSetsInitOnce = U_INITONCE_INITIALIZER; + +UBool U_CALLCONV cleanupNumberParseUnitSets() { + for (int32_t i = 0; i < COUNT; i++) { + delete gUnicodeSets[i]; + gUnicodeSets[i] = nullptr; + } + return TRUE; +} + +void U_CALLCONV initNumberParseUniSets(UErrorCode &status) { + ucln_i18n_registerCleanup(UCLN_I18N_NUMPARSE_UNISETS, cleanupNumberParseUnitSets); +#define NEW_UNISET(pattern, status) new UnicodeSet(UnicodeString(pattern), status) + + // BiDi characters are skipped over and ignored at any point in the string, even in strict mode. + gUnicodeSets[BIDI] = NEW_UNISET(u"[[\\u200E\\u200F\\u061C]]", status); + + // This set was decided after discussion with icu-design@. See ticket #13309. + // Zs+TAB is "horizontal whitespace" according to UTS #18 (blank property). + gUnicodeSets[WHITESPACE] = NEW_UNISET(u"[[:Zs:][\\u0009]]", status); + + gUnicodeSets[DEFAULT_IGNORABLES] = computeUnion(BIDI, WHITESPACE); + gUnicodeSets[STRICT_IGNORABLES] = gUnicodeSets[BIDI]; + + // TODO: Re-generate these sets from the UCD. They probably haven't been updated in a while. + gUnicodeSets[COMMA] = NEW_UNISET(u"[,،٫、︐︑﹐﹑,、]", status); + gUnicodeSets[STRICT_COMMA] = NEW_UNISET(u"[,٫︐﹐,]", status); + gUnicodeSets[PERIOD] = NEW_UNISET(u"[.․。︒﹒.。]", status); + gUnicodeSets[STRICT_PERIOD] = NEW_UNISET(u"[.․﹒.。]", status); + gUnicodeSets[OTHER_GROUPING_SEPARATORS] = NEW_UNISET( + u"['٬‘’'\\u0020\\u00A0\\u2000-\\u200A\\u202F\\u205F\\u3000]", status); + gUnicodeSets[ALL_SEPARATORS] = computeUnion(COMMA, PERIOD, OTHER_GROUPING_SEPARATORS); + gUnicodeSets[STRICT_ALL_SEPARATORS] = computeUnion( + STRICT_COMMA, STRICT_PERIOD, OTHER_GROUPING_SEPARATORS); + + gUnicodeSets[MINUS_SIGN] = NEW_UNISET(u"[-⁻₋−➖﹣-]", status); + gUnicodeSets[PLUS_SIGN] = NEW_UNISET(u"[+⁺₊➕﬩﹢+]", status); + + gUnicodeSets[PERCENT_SIGN] = NEW_UNISET(u"[%٪]", status); + gUnicodeSets[PERMILLE_SIGN] = NEW_UNISET(u"[‰؉]", status); + gUnicodeSets[INFINITY] = NEW_UNISET(u"[∞]", status); + + gUnicodeSets[DIGITS] = NEW_UNISET(u"[:digit:]", status); + gUnicodeSets[NAN_LEAD] = NEW_UNISET( + u"[NnТтmeՈոс¤НнчTtsҳ\u975e\u1002\u0e9a\u10d0\u0f68\u0644\u0646]", status); + gUnicodeSets[SCIENTIFIC_LEAD] = NEW_UNISET(u"[Ee×·е\u0627]", status); + gUnicodeSets[CWCF] = NEW_UNISET(u"[:CWCF:]", status); + + gUnicodeSets[DIGITS_OR_ALL_SEPARATORS] = computeUnion(DIGITS, ALL_SEPARATORS); + gUnicodeSets[DIGITS_OR_STRICT_ALL_SEPARATORS] = computeUnion(DIGITS, STRICT_ALL_SEPARATORS); + + for (int32_t i = 0; i < COUNT; i++) { + gUnicodeSets[i]->freeze(); + } +} + +} + +const UnicodeSet* unisets::get(Key key) { + UErrorCode localStatus = U_ZERO_ERROR; + umtx_initOnce(gNumberParseUniSetsInitOnce, &initNumberParseUniSets, localStatus); + if (U_FAILURE(localStatus)) { + // TODO: This returns non-null in Java, and callers assume that. + return nullptr; + } + return gUnicodeSets[key]; +} + +Key unisets::chooseFrom(UnicodeString str, Key key1) { + return get(key1)->contains(str) ? key1 : COUNT; +} + +Key unisets::chooseFrom(UnicodeString str, Key key1, Key key2) { + return get(key1)->contains(str) ? key1 : chooseFrom(str, key2); +} + + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/numparse_unisets.h b/icu4c/source/i18n/numparse_unisets.h new file mode 100644 index 0000000000..1d923613e9 --- /dev/null +++ b/icu4c/source/i18n/numparse_unisets.h @@ -0,0 +1,72 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#ifndef __NUMPARSE_UNISETS_H__ +#define __NUMPARSE_UNISETS_H__ + +#include "numparse_types.h" +#include "unicode/uniset.h" + +U_NAMESPACE_BEGIN namespace numparse { +namespace impl { +namespace unisets { + +enum Key { + // Ignorables + BIDI, + WHITESPACE, + DEFAULT_IGNORABLES, + STRICT_IGNORABLES, + + // Separators + // Notes: + // - COMMA is a superset of STRICT_COMMA + // - PERIOD is a superset of SCRICT_PERIOD + // - ALL_SEPARATORS is the union of COMMA, PERIOD, and OTHER_GROUPING_SEPARATORS + // - STRICT_ALL_SEPARATORS is the union of STRICT_COMMA, STRICT_PERIOD, and OTHER_GRP_SEPARATORS + COMMA, + PERIOD, + STRICT_COMMA, + STRICT_PERIOD, + OTHER_GROUPING_SEPARATORS, + ALL_SEPARATORS, + STRICT_ALL_SEPARATORS, + + // Symbols + // TODO: NaN? + MINUS_SIGN, + PLUS_SIGN, + PERCENT_SIGN, + PERMILLE_SIGN, + INFINITY, + + // Other + DIGITS, + NAN_LEAD, + SCIENTIFIC_LEAD, + CWCF, + + // Combined Separators with Digits (for lead code points) + DIGITS_OR_ALL_SEPARATORS, + DIGITS_OR_STRICT_ALL_SEPARATORS, + + // The number of elements in the enum. Also used to indicate null. + COUNT +}; + +const UnicodeSet* get(Key key); + +Key chooseFrom(UnicodeString str, Key key1); + +Key chooseFrom(UnicodeString str, Key key1, Key key2); + +} // namespace unisets +} // namespace impl +} // namespace numparse +U_NAMESPACE_END + +#endif //__NUMPARSE_UNISETS_H__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/ucln_in.h b/icu4c/source/i18n/ucln_in.h index 40a5c36d87..d9e8741e7f 100644 --- a/icu4c/source/i18n/ucln_in.h +++ b/icu4c/source/i18n/ucln_in.h @@ -26,6 +26,7 @@ as the functions are suppose to be called. It's usually best to have child dependencies called first. */ typedef enum ECleanupI18NType { UCLN_I18N_START = -1, + UCLN_I18N_NUMPARSE_UNISETS, UCLN_I18N_CURRENCY_SPACING, UCLN_I18N_SPOOF, UCLN_I18N_SPOOFDATA, diff --git a/icu4c/source/test/intltest/Makefile.in b/icu4c/source/test/intltest/Makefile.in index d41ef25f52..ed1aa256b1 100644 --- a/icu4c/source/test/intltest/Makefile.in +++ b/icu4c/source/test/intltest/Makefile.in @@ -64,7 +64,7 @@ scientificnumberformattertest.o datadrivennumberformattestsuite.o \ numberformattesttuple.o numberformat2test.o pluralmaptest.o \ numbertest_affixutils.o numbertest_api.o numbertest_decimalquantity.o \ numbertest_modifiers.o numbertest_patternmodifier.o numbertest_patternstring.o \ -numbertest_stringbuilder.o +numbertest_stringbuilder.o numbertest_stringsegment.o numbertest_unisets.o DEPS = $(OBJECTS:.o=.d) diff --git a/icu4c/source/test/intltest/numbertest.h b/icu4c/source/test/intltest/numbertest.h index 9d4ffb7cef..60743ed5a1 100644 --- a/icu4c/source/test/intltest/numbertest.h +++ b/icu4c/source/test/intltest/numbertest.h @@ -9,9 +9,13 @@ #include "number_stringbuilder.h" #include "intltest.h" #include "number_affixutils.h" +#include "numparse_stringsegment.h" +#include "unicode/locid.h" using namespace icu::number; using namespace icu::number::impl; +using namespace icu::numparse; +using namespace icu::numparse::impl; //////////////////////////////////////////////////////////////////////////////////////// // INSTRUCTIONS: // @@ -178,6 +182,30 @@ class NumberStringBuilderTest : public IntlTest { void assertEqualsImpl(const UnicodeString &a, const NumberStringBuilder &b); }; +class StringSegmentTest : public IntlTest { + public: + void testOffset(); + void testLength(); + void testCharAt(); + void testGetCodePoint(); + void testCommonPrefixLength(); + + void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0); +}; + +class UniSetsTest : public IntlTest { + public: + void testSetCoverage(); + + void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0); + + private: + void assertInSet(const UnicodeString& localeName, const UnicodeString &setName, + const UnicodeSet& set, const UnicodeString& str); + void assertInSet(const UnicodeString& localeName, const UnicodeString &setName, + const UnicodeSet& set, UChar32 cp); +}; + // NOTE: This macro is identical to the one in itformat.cpp #define TESTCLASS(id, TestClass) \ @@ -206,6 +234,8 @@ class NumberTest : public IntlTest { TESTCLASS(4, PatternModifierTest); TESTCLASS(5, PatternStringTest); TESTCLASS(6, NumberStringBuilderTest); + TESTCLASS(7, StringSegmentTest); + TESTCLASS(8, UniSetsTest); default: name = ""; break; // needed to end loop } } diff --git a/icu4c/source/test/intltest/numbertest_stringsegment.cpp b/icu4c/source/test/intltest/numbertest_stringsegment.cpp new file mode 100644 index 0000000000..519642e49a --- /dev/null +++ b/icu4c/source/test/intltest/numbertest_stringsegment.cpp @@ -0,0 +1,94 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT + +#include "numbertest.h" +#include "numparse_stringsegment.h" + +static const char16_t* SAMPLE_STRING = u"📻 radio 📻"; + +void StringSegmentTest::runIndexedTest(int32_t index, UBool exec, const char*&name, char*) { + if (exec) { + logln("TestSuite StringSegmentTest: "); + } + TESTCASE_AUTO_BEGIN; + TESTCASE_AUTO(testOffset); + TESTCASE_AUTO(testLength); + TESTCASE_AUTO(testCharAt); + TESTCASE_AUTO(testGetCodePoint); + TESTCASE_AUTO(testCommonPrefixLength); + TESTCASE_AUTO_END; +} + +void StringSegmentTest::testOffset() { + StringSegment segment(SAMPLE_STRING); + assertEquals("Initial Offset", 0, segment.getOffset()); + segment.adjustOffset(3); + assertEquals("Adjust A", 3, segment.getOffset()); + segment.adjustOffset(2); + assertEquals("Adjust B", 5, segment.getOffset()); + segment.setOffset(4); + assertEquals("Set Offset", 4, segment.getOffset()); +} + +void StringSegmentTest::testLength() { + StringSegment segment(SAMPLE_STRING); + assertEquals("Initial length", 11, segment.length()); + segment.adjustOffset(3); + assertEquals("Adjust", 8, segment.length()); + segment.setLength(4); + assertEquals("Set Length", 4, segment.length()); + segment.setOffset(5); + assertEquals("After adjust offset", 2, segment.length()); + segment.resetLength(); + assertEquals("After reset length", 6, segment.length()); +} + +void StringSegmentTest::testCharAt() { + StringSegment segment(SAMPLE_STRING); + assertEquals("Initial", SAMPLE_STRING, segment.toUnicodeString()); + segment.adjustOffset(3); + assertEquals("After adjust-offset", UnicodeString(u"radio 📻"), segment.toUnicodeString()); + segment.setLength(5); + assertEquals("After adjust-length", UnicodeString(u"radio"), segment.toUnicodeString()); +} + +void StringSegmentTest::testGetCodePoint() { + StringSegment segment(SAMPLE_STRING); + assertEquals("Double-width code point", 0x1F4FB, segment.getCodePoint()); + segment.setLength(1); + assertEquals("Inalid A", -1, segment.getCodePoint()); + segment.resetLength(); + segment.adjustOffset(1); + assertEquals("Invalid B", -1, segment.getCodePoint()); + segment.adjustOffset(1); + assertEquals("Valid again", 0x20, segment.getCodePoint()); +} + +void StringSegmentTest::testCommonPrefixLength() { + StringSegment segment(SAMPLE_STRING); + assertEquals("", 11, segment.getCommonPrefixLength(SAMPLE_STRING)); + assertEquals("", 4, segment.getCommonPrefixLength(u"📻 r")); + assertEquals("", 3, segment.getCommonPrefixLength(u"📻 x")); + assertEquals("", 0, segment.getCommonPrefixLength(u"x")); + assertEquals("", 0, segment.getCommonPrefixLength(u"")); + segment.adjustOffset(3); + assertEquals("", 0, segment.getCommonPrefixLength(u"RADiO")); + assertEquals("", 5, segment.getCommonPrefixLength(u"radio")); + assertEquals("", 2, segment.getCommonPrefixLength(u"rafio")); + assertEquals("", 0, segment.getCommonPrefixLength(u"fadio")); + assertEquals("", 0, segment.getCommonPrefixLength(u"")); + segment.setLength(3); + assertEquals("", 3, segment.getCommonPrefixLength(u"radio")); + assertEquals("", 2, segment.getCommonPrefixLength(u"rafio")); + assertEquals("", 0, segment.getCommonPrefixLength(u"fadio")); + assertEquals("", 0, segment.getCommonPrefixLength(u"")); + segment.resetLength(); + segment.setOffset(11); // end of string + assertEquals("", 0, segment.getCommonPrefixLength(u"foo")); +} + +#endif diff --git a/icu4c/source/test/intltest/numbertest_unisets.cpp b/icu4c/source/test/intltest/numbertest_unisets.cpp new file mode 100644 index 0000000000..a41f3f6efb --- /dev/null +++ b/icu4c/source/test/intltest/numbertest_unisets.cpp @@ -0,0 +1,99 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT + +#include "numbertest.h" +#include "numparse_unisets.h" +#include "unicode/dcfmtsym.h" + +#include +#include + +using icu::numparse::impl::unisets::get; + +void UniSetsTest::runIndexedTest(int32_t index, UBool exec, const char*&name, char*) { + if (exec) { + logln("TestSuite UniSetsTest: "); + } + TESTCASE_AUTO_BEGIN; + TESTCASE_AUTO(testSetCoverage); + TESTCASE_AUTO_END; +} + +void UniSetsTest::testSetCoverage() { + UErrorCode status = U_ZERO_ERROR; + + // Lenient comma/period should be supersets of strict comma/period; + // it also makes the coverage logic cheaper. + assertTrue( + "COMMA should be superset of STRICT_COMMA", + get(unisets::COMMA)->containsAll(*get(unisets::STRICT_COMMA))); + assertTrue( + "PERIOD should be superset of STRICT_PERIOD", + get(unisets::PERIOD)->containsAll(*get(unisets::STRICT_PERIOD))); + + UnicodeSet decimals; + decimals.addAll(*get(unisets::STRICT_COMMA)); + decimals.addAll(*get(unisets::STRICT_PERIOD)); + decimals.freeze(); + UnicodeSet grouping; + grouping.addAll(decimals); + grouping.addAll(*get(unisets::OTHER_GROUPING_SEPARATORS)); + decimals.freeze(); + + const UnicodeSet &plusSign = *get(unisets::PLUS_SIGN); + const UnicodeSet &minusSign = *get(unisets::MINUS_SIGN); + const UnicodeSet &percent = *get(unisets::PERCENT_SIGN); + const UnicodeSet &permille = *get(unisets::PERMILLE_SIGN); + const UnicodeSet &infinity = *get(unisets::INFINITY); + const UnicodeSet &nanLead = *get(unisets::NAN_LEAD); + const UnicodeSet &scientificLead = *get(unisets::SCIENTIFIC_LEAD); + + int32_t localeCount; + const Locale* allAvailableLocales = Locale::getAvailableLocales(localeCount); + for (int32_t i = 0; i < localeCount; i++) { + Locale locale = allAvailableLocales[i]; + DecimalFormatSymbols dfs(locale, status); + UnicodeString localeName; + locale.getDisplayName(localeName); + assertSuccess(UnicodeString("Making DFS for ") + localeName, status); + +#define ASSERT_IN_SET(name, foo) assertInSet(localeName, UnicodeString("" #name ""), name, foo) + ASSERT_IN_SET(decimals, dfs.getConstSymbol(DecimalFormatSymbols::kDecimalSeparatorSymbol)); + ASSERT_IN_SET(grouping, dfs.getConstSymbol(DecimalFormatSymbols::kGroupingSeparatorSymbol)); + ASSERT_IN_SET(plusSign, dfs.getConstSymbol(DecimalFormatSymbols::kPlusSignSymbol)); + ASSERT_IN_SET(minusSign, dfs.getConstSymbol(DecimalFormatSymbols::kMinusSignSymbol)); + ASSERT_IN_SET(percent, dfs.getConstSymbol(DecimalFormatSymbols::kPercentSymbol)); + ASSERT_IN_SET(permille, dfs.getConstSymbol(DecimalFormatSymbols::kPerMillSymbol)); + ASSERT_IN_SET(infinity, dfs.getConstSymbol(DecimalFormatSymbols::kInfinitySymbol)); + ASSERT_IN_SET(nanLead, dfs.getConstSymbol(DecimalFormatSymbols::kNaNSymbol).char32At(0)); + ASSERT_IN_SET(nanLead, + u_foldCase(dfs.getConstSymbol(DecimalFormatSymbols::kNaNSymbol).char32At(0), 0)); + ASSERT_IN_SET(scientificLead, + u_foldCase(dfs.getConstSymbol(DecimalFormatSymbols::kExponentialSymbol).char32At(0), 0)); + } +} + +void UniSetsTest::assertInSet(const UnicodeString &localeName, const UnicodeString &setName, + const UnicodeSet &set, const UnicodeString &str) { + if (str.countChar32(0, str.length()) != 1) { + // Ignore locale strings with more than one code point (usually a bidi mark) + return; + } + assertInSet(localeName, setName, set, str.char32At(0)); +} + +void UniSetsTest::assertInSet(const UnicodeString &localeName, const UnicodeString &setName, + const UnicodeSet &set, UChar32 cp) { + // If this test case fails, add the specified code point to the corresponding set in + // UnicodeSetStaticCache.java and numparse_unisets.cpp + assertTrue( + localeName + UnicodeString(u" ") + UnicodeString(cp) + UnicodeString(u" is missing in ") + + setName, set.contains(cp)); +} + + +#endif From 48a633f41f36a42f9331fdeb6813bd6181d07313 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Tue, 6 Feb 2018 09:43:37 +0000 Subject: [PATCH 002/129] ICU-13574 Defining more fundamental parsing types. X-SVN-Rev: 40843 --- icu4c/source/i18n/Makefile.in | 2 +- icu4c/source/i18n/number_decimalquantity.cpp | 30 +++ icu4c/source/i18n/number_decimalquantity.h | 13 +- icu4c/source/i18n/numparse_parsednumber.cpp | 75 ++++++ icu4c/source/i18n/numparse_stringsegment.h | 55 ----- icu4c/source/i18n/numparse_types.h | 216 +++++++++++++++++- icu4c/source/i18n/plurrule.cpp | 2 + icu4c/source/i18n/plurrule_impl.h | 124 +++++----- .../icu/impl/number/parse/AffixMatcher.java | 2 +- .../impl/number/parse/NumberParseMatcher.java | 2 + .../impl/number/parse/NumberParserImpl.java | 16 +- .../icu/impl/number/parse/ParsedNumber.java | 6 +- .../number/parse/UnicodeSetStaticCache.java | 5 - .../icu/dev/test/number/NumberParserTest.java | 17 +- .../number/UnicodeSetStaticCacheTest.java | 2 +- 15 files changed, 422 insertions(+), 145 deletions(-) create mode 100644 icu4c/source/i18n/numparse_parsednumber.cpp diff --git a/icu4c/source/i18n/Makefile.in b/icu4c/source/i18n/Makefile.in index 2b9cca7055..a5752781f2 100644 --- a/icu4c/source/i18n/Makefile.in +++ b/icu4c/source/i18n/Makefile.in @@ -108,7 +108,7 @@ number_decimfmtprops.o number_fluent.o number_formatimpl.o number_grouping.o \ number_integerwidth.o number_longnames.o number_modifiers.o number_notation.o \ number_padding.o number_patternmodifier.o number_patternstring.o \ number_rounding.o number_scientific.o number_stringbuilder.o \ -numparse_stringsegment.o numparse_unisets.o +numparse_stringsegment.o numparse_unisets.o numparse_parsednumber.o ## Header files to install diff --git a/icu4c/source/i18n/number_decimalquantity.cpp b/icu4c/source/i18n/number_decimalquantity.cpp index 6f6ac9def6..3342771b98 100644 --- a/icu4c/source/i18n/number_decimalquantity.cpp +++ b/icu4c/source/i18n/number_decimalquantity.cpp @@ -103,6 +103,7 @@ DecimalQuantity &DecimalQuantity::operator=(const DecimalQuantity &other) { return *this; } copyBcdFrom(other); + bogus = other.bogus; lOptPos = other.lOptPos; lReqPos = other.lReqPos; rReqPos = other.rReqPos; @@ -466,6 +467,35 @@ int64_t DecimalQuantity::toFractionLong(bool includeTrailingZeros) const { return result; } +bool DecimalQuantity::fitsInLong() const { + if (isZero()) { + return true; + } + if (scale < 0) { + return false; + } + int magnitude = getMagnitude(); + if (magnitude < 18) { + return true; + } + if (magnitude > 18) { + return false; + } + // Hard case: the magnitude is 10^18. + // The largest int64 is: 9,223,372,036,854,775,807 + for (int p = 0; p < precision; p++) { + int8_t digit = getDigit(18 - p); + static int8_t INT64_BCD[] = { 9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 7 }; + if (digit < INT64_BCD[p]) { + return true; + } else if (digit > INT64_BCD[p]) { + return false; + } + } + // Exactly equal to max long. + return true; +} + double DecimalQuantity::toDouble() const { if (isApproximate) { return toDoubleFromOriginal(); diff --git a/icu4c/source/i18n/number_decimalquantity.h b/icu4c/source/i18n/number_decimalquantity.h index 3ff9fbeffe..aea66fdb7c 100644 --- a/icu4c/source/i18n/number_decimalquantity.h +++ b/icu4c/source/i18n/number_decimalquantity.h @@ -35,7 +35,7 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { DecimalQuantity(); - ~DecimalQuantity(); + ~DecimalQuantity() override; /** * Sets this instance to be equal to another instance. @@ -128,6 +128,12 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { int64_t toFractionLong(bool includeTrailingZeros) const; + /** + * Returns whether or not a Long can fully represent the value stored in this DecimalQuantity. + * Assumes that the DecimalQuantity is positive. + */ + bool fitsInLong() const; + /** @return The value contained in this {@link DecimalQuantity} approximated as a double. */ double toDouble() const; @@ -235,6 +241,11 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { /** Visible for testing */ inline bool isExplicitExactDouble() { return explicitExactDouble; }; + /** + * Bogus flag for when a DecimalQuantity is stored on the stack. + */ + bool bogus = false; + private: /** * The power of ten corresponding to the least significant digit in the BCD. For example, if this diff --git a/icu4c/source/i18n/numparse_parsednumber.cpp b/icu4c/source/i18n/numparse_parsednumber.cpp new file mode 100644 index 0000000000..9db933502a --- /dev/null +++ b/icu4c/source/i18n/numparse_parsednumber.cpp @@ -0,0 +1,75 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT + +#include "numparse_types.h" +#include + +using namespace icu; +using namespace icu::numparse; +using namespace icu::numparse::impl; + + +ParsedNumber::ParsedNumber() { + clear(); +} + +void ParsedNumber::clear() { + quantity.bogus = true; + charEnd = 0; + flags = 0; + prefix.setToBogus(); + suffix.setToBogus(); + currencyCode.setToBogus(); +} + +void ParsedNumber::setCharsConsumed(const StringSegment& segment) { + charEnd = segment.getOffset(); +} + +bool ParsedNumber::success() const { + return charEnd > 0 && 0 == (flags & FLAG_FAIL); +} + +bool ParsedNumber::seenNumber() const { + return !quantity.bogus || 0 != (flags & FLAG_NAN) || 0 != (flags & FLAG_INFINITY); +} + +double ParsedNumber::getDouble() const { + bool sawNegative = 0 != (flags & FLAG_NEGATIVE); + bool sawNaN = 0 != (flags & FLAG_NAN); + bool sawInfinity = 0 != (flags & FLAG_INFINITY); + + // Check for NaN, infinity, and -0.0 + if (sawNaN) { + return NAN; + } + if (sawInfinity) { + if (sawNegative) { + return -INFINITY; + } else { + return INFINITY; + } + } + if (quantity.isZero() && sawNegative) { + return -0.0; + } + + if (quantity.fitsInLong()) { + long l = quantity.toLong(); + if (0 != (flags & FLAG_NEGATIVE)) { + l *= -1; + } + return l; + } + + // TODO: MIN_LONG + return quantity.toDouble(); +} + + + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/numparse_stringsegment.h b/icu4c/source/i18n/numparse_stringsegment.h index 30f11af7a1..d2e6154cd3 100644 --- a/icu4c/source/i18n/numparse_stringsegment.h +++ b/icu4c/source/i18n/numparse_stringsegment.h @@ -15,61 +15,6 @@ U_NAMESPACE_BEGIN namespace numparse { namespace impl { -/** - * A mutable class allowing for a String with a variable offset and length. The charAt, length, and - * subSequence methods all operate relative to the fixed offset into the String. - * - * @author sffc - */ -class StringSegment : public UMemory, public ::icu::number::impl::CharSequence { - public: - explicit StringSegment(const UnicodeString &str); - - int32_t getOffset() const; - - void setOffset(int32_t start); - - /** - * Equivalent to setOffset(getOffset()+delta). - * - *

- * This method is usually called by a Matcher to register that a char was consumed. If the char is - * strong (it usually is, except for things like whitespace), follow this with a call to - * {@link ParsedNumber#setCharsConsumed}. For more information on strong chars, see that method. - */ - void adjustOffset(int32_t delta); - - void setLength(int32_t length); - - void resetLength(); - - int32_t length() const override; - - char16_t charAt(int32_t index) const override; - - UChar32 codePointAt(int32_t index) const override; - - UnicodeString toUnicodeString() const override; - - /** - * Returns the first code point in the string segment, or -1 if the string starts with an invalid - * code point. - */ - UChar32 getCodePoint() const; - - /** - * Returns the length of the prefix shared by this StringSegment and the given CharSequence. For - * example, if this string segment is "aab", and the char sequence is "aac", this method returns 2, - * since the first 2 characters are the same. - */ - int32_t getCommonPrefixLength(const UnicodeString &other); - - private: - const UnicodeString fStr; - int32_t fStart; - int32_t fEnd; -}; - } // namespace impl } // namespace numparse diff --git a/icu4c/source/i18n/numparse_types.h b/icu4c/source/i18n/numparse_types.h index b607f36cc9..92957204ba 100644 --- a/icu4c/source/i18n/numparse_types.h +++ b/icu4c/source/i18n/numparse_types.h @@ -8,11 +8,223 @@ #define __NUMPARSE_TYPES_H__ #include "unicode/uobject.h" +#include "number_decimalquantity.h" -U_NAMESPACE_BEGIN -namespace numparse { +U_NAMESPACE_BEGIN namespace numparse { namespace impl { +// Forward-declarations +class StringSegment; +class ParsedNumber; + + +/** + * Struct-like class to hold the results of a parsing routine. + * + * @author sffc + */ +class ParsedNumber { + public: + enum ParsedNumberFlags { + FLAG_NEGATIVE = 0x0001, + FLAG_PERCENT = 0x0002, + FLAG_PERMILLE = 0x0004, + FLAG_HAS_EXPONENT = 0x0008, + FLAG_HAS_DEFAULT_CURRENCY = 0x0010, + FLAG_HAS_DECIMAL_SEPARATOR = 0x0020, + FLAG_NAN = 0x0040, + FLAG_INFINITY = 0x0080, + FLAG_FAIL = 0x0100, + }; + + /** + * The numerical value that was parsed. + */ + ::icu::number::impl::DecimalQuantity quantity; + + /** + * The index of the last char consumed during parsing. If parsing started at index 0, this is equal + * to the number of chars consumed. This is NOT necessarily the same as the StringSegment offset; + * "weak" chars, like whitespace, change the offset, but the charsConsumed is not touched until a + * "strong" char is encountered. + */ + int32_t charEnd; + + /** + * Boolean flags (see constants below). + */ + int32_t flags; + + /** + * The pattern string corresponding to the prefix that got consumed. + */ + UnicodeString prefix; + + /** + * The pattern string corresponding to the suffix that got consumed. + */ + UnicodeString suffix; + + /** + * The currency that got consumed. + */ + UnicodeString currencyCode; + + ParsedNumber(); + + ParsedNumber(const ParsedNumber& other) = default; + + ParsedNumber& operator=(const ParsedNumber& other) = default; + + void clear(); + + /** + * Call this method to register that a "strong" char was consumed. This should be done after calling + * {@link StringSegment#setOffset} or {@link StringSegment#adjustOffset} except when the char is + * "weak", like whitespace. + * + *

+ * What is a strong versus weak char? The behavior of number parsing is to "stop" + * after reading the number, even if there is other content following the number. For example, after + * parsing the string "123 " (123 followed by a space), the cursor should be set to 3, not 4, even + * though there are matchers that accept whitespace. In this example, the digits are strong, whereas + * the whitespace is weak. Grouping separators are weak, whereas decimal separators are strong. Most + * other chars are strong. + * + * @param segment + * The current StringSegment, usually immediately following a call to setOffset. + */ + void setCharsConsumed(const StringSegment& segment); + + /** + * Returns whether this the parse was successful. To be successful, at least one char must have been + * consumed, and the failure flag must not be set. + */ + bool success() const; + + bool seenNumber() const; + + double getDouble() const; +}; + + +/** + * A mutable class allowing for a String with a variable offset and length. The charAt, length, and + * subSequence methods all operate relative to the fixed offset into the String. + * + * @author sffc + */ +class StringSegment : public UMemory, public ::icu::number::impl::CharSequence { + public: + explicit StringSegment(const UnicodeString& str); + + int32_t getOffset() const; + + void setOffset(int32_t start); + + /** + * Equivalent to setOffset(getOffset()+delta). + * + *

+ * This method is usually called by a Matcher to register that a char was consumed. If the char is + * strong (it usually is, except for things like whitespace), follow this with a call to + * {@link ParsedNumber#setCharsConsumed}. For more information on strong chars, see that method. + */ + void adjustOffset(int32_t delta); + + void setLength(int32_t length); + + void resetLength(); + + int32_t length() const override; + + char16_t charAt(int32_t index) const override; + + UChar32 codePointAt(int32_t index) const override; + + UnicodeString toUnicodeString() const override; + + /** + * Returns the first code point in the string segment, or -1 if the string starts with an invalid + * code point. + */ + UChar32 getCodePoint() const; + + /** + * Returns the length of the prefix shared by this StringSegment and the given CharSequence. For + * example, if this string segment is "aab", and the char sequence is "aac", this method returns 2, + * since the first 2 characters are the same. + */ + int32_t getCommonPrefixLength(const UnicodeString& other); + + private: + const UnicodeString fStr; + int32_t fStart; + int32_t fEnd; +}; + + +/** + * The core interface implemented by all matchers used for number parsing. + * + * Given a string, there should NOT be more than one way to consume the string with the same matcher + * applied multiple times. If there is, the non-greedy parsing algorithm will be unhappy and may enter an + * exponential-time loop. For example, consider the "A Matcher" that accepts "any number of As". Given + * the string "AAAA", there are 2^N = 8 ways to apply the A Matcher to this string: you could have the A + * Matcher apply 4 times to each character; you could have it apply just once to all the characters; you + * could have it apply to the first 2 characters and the second 2 characters; and so on. A better version + * of the "A Matcher" would be for it to accept exactly one A, and allow the algorithm to run it + * repeatedly to consume a string of multiple As. The A Matcher can implement the Flexible interface + * below to signal that it can be applied multiple times in a row. + * + * @author sffc + */ +class NumberParseMatcher { + public: + virtual ~NumberParseMatcher() = default; + + /** + * Matchers can override this method to return true to indicate that they are optional and can be run + * repeatedly. Used by SeriesMatcher, primarily in the context of IgnorablesMatcher. + */ + virtual bool isFlexible() const { + return false; + } + + /** + * Runs this matcher starting at the beginning of the given StringSegment. If this matcher finds + * something interesting in the StringSegment, it should update the offset of the StringSegment + * corresponding to how many chars were matched. + * + * @param segment + * The StringSegment to match against. Matches always start at the beginning of the + * segment. The segment is guaranteed to contain at least one char. + * @param result + * The data structure to store results if the match succeeds. + * @return Whether this matcher thinks there may be more interesting chars beyond the end of the + * string segment. + */ + virtual bool match(StringSegment& segment, ParsedNumber& result) const = 0; + + /** + * Should return a set representing all possible chars (UTF-16 code units) that could be the first + * char that this matcher can consume. This method is only called during construction phase, and its + * return value is used to skip this matcher unless a segment begins with a char in this set. To make + * this matcher always run, return {@link UnicodeSet#ALL_CODE_POINTS}. + */ + virtual UnicodeSet getLeadCodePoints() const = 0; + + /** + * Method called at the end of a parse, after all matchers have failed to consume any more chars. + * Allows a matcher to make final modifications to the result given the knowledge that no more + * matches are possible. + * + * @param result + * The data structure to store results. + */ + virtual void postProcess(ParsedNumber& result) const = 0; +}; + } // namespace impl } // namespace numparse diff --git a/icu4c/source/i18n/plurrule.cpp b/icu4c/source/i18n/plurrule.cpp index dcf28b2bc1..14b5fe6d9d 100644 --- a/icu4c/source/i18n/plurrule.cpp +++ b/icu4c/source/i18n/plurrule.cpp @@ -42,6 +42,8 @@ U_NAMESPACE_BEGIN +using namespace icu::pluralimpl; + static const UChar PLURAL_KEYWORD_OTHER[]={LOW_O,LOW_T,LOW_H,LOW_E,LOW_R,0}; static const UChar PLURAL_DEFAULT_RULE[]={LOW_O,LOW_T,LOW_H,LOW_E,LOW_R,COLON,SPACE,LOW_N,0}; static const UChar PK_IN[]={LOW_I,LOW_N,0}; diff --git a/icu4c/source/i18n/plurrule_impl.h b/icu4c/source/i18n/plurrule_impl.h index b93fc501ba..152c33e862 100644 --- a/icu4c/source/i18n/plurrule_impl.h +++ b/icu4c/source/i18n/plurrule_impl.h @@ -40,67 +40,71 @@ class DigitInterval; class PluralRules; class VisibleDigits; -static const UChar DOT = ((UChar)0x002E); -static const UChar SINGLE_QUOTE = ((UChar)0x0027); -static const UChar SLASH = ((UChar)0x002F); -static const UChar BACKSLASH = ((UChar)0x005C); -static const UChar SPACE = ((UChar)0x0020); -static const UChar EXCLAMATION = ((UChar)0x0021); -static const UChar QUOTATION_MARK = ((UChar)0x0022); -static const UChar NUMBER_SIGN = ((UChar)0x0023); -static const UChar PERCENT_SIGN = ((UChar)0x0025); -static const UChar ASTERISK = ((UChar)0x002A); -static const UChar COMMA = ((UChar)0x002C); -static const UChar HYPHEN = ((UChar)0x002D); -static const UChar U_ZERO = ((UChar)0x0030); -static const UChar U_ONE = ((UChar)0x0031); -static const UChar U_TWO = ((UChar)0x0032); -static const UChar U_THREE = ((UChar)0x0033); -static const UChar U_FOUR = ((UChar)0x0034); -static const UChar U_FIVE = ((UChar)0x0035); -static const UChar U_SIX = ((UChar)0x0036); -static const UChar U_SEVEN = ((UChar)0x0037); -static const UChar U_EIGHT = ((UChar)0x0038); -static const UChar U_NINE = ((UChar)0x0039); -static const UChar COLON = ((UChar)0x003A); -static const UChar SEMI_COLON = ((UChar)0x003B); -static const UChar EQUALS = ((UChar)0x003D); -static const UChar AT = ((UChar)0x0040); -static const UChar CAP_A = ((UChar)0x0041); -static const UChar CAP_B = ((UChar)0x0042); -static const UChar CAP_R = ((UChar)0x0052); -static const UChar CAP_Z = ((UChar)0x005A); -static const UChar LOWLINE = ((UChar)0x005F); -static const UChar LEFTBRACE = ((UChar)0x007B); -static const UChar RIGHTBRACE = ((UChar)0x007D); -static const UChar TILDE = ((UChar)0x007E); -static const UChar ELLIPSIS = ((UChar)0x2026); +namespace pluralimpl { -static const UChar LOW_A = ((UChar)0x0061); -static const UChar LOW_B = ((UChar)0x0062); -static const UChar LOW_C = ((UChar)0x0063); -static const UChar LOW_D = ((UChar)0x0064); -static const UChar LOW_E = ((UChar)0x0065); -static const UChar LOW_F = ((UChar)0x0066); -static const UChar LOW_G = ((UChar)0x0067); -static const UChar LOW_H = ((UChar)0x0068); -static const UChar LOW_I = ((UChar)0x0069); -static const UChar LOW_J = ((UChar)0x006a); -static const UChar LOW_K = ((UChar)0x006B); -static const UChar LOW_L = ((UChar)0x006C); -static const UChar LOW_M = ((UChar)0x006D); -static const UChar LOW_N = ((UChar)0x006E); -static const UChar LOW_O = ((UChar)0x006F); -static const UChar LOW_P = ((UChar)0x0070); -static const UChar LOW_Q = ((UChar)0x0071); -static const UChar LOW_R = ((UChar)0x0072); -static const UChar LOW_S = ((UChar)0x0073); -static const UChar LOW_T = ((UChar)0x0074); -static const UChar LOW_U = ((UChar)0x0075); -static const UChar LOW_V = ((UChar)0x0076); -static const UChar LOW_W = ((UChar)0x0077); -static const UChar LOW_Y = ((UChar)0x0079); -static const UChar LOW_Z = ((UChar)0x007A); +static const UChar DOT = ((UChar) 0x002E); +static const UChar SINGLE_QUOTE = ((UChar) 0x0027); +static const UChar SLASH = ((UChar) 0x002F); +static const UChar BACKSLASH = ((UChar) 0x005C); +static const UChar SPACE = ((UChar) 0x0020); +static const UChar EXCLAMATION = ((UChar) 0x0021); +static const UChar QUOTATION_MARK = ((UChar) 0x0022); +static const UChar NUMBER_SIGN = ((UChar) 0x0023); +static const UChar PERCENT_SIGN = ((UChar) 0x0025); +static const UChar ASTERISK = ((UChar) 0x002A); +static const UChar COMMA = ((UChar) 0x002C); +static const UChar HYPHEN = ((UChar) 0x002D); +static const UChar U_ZERO = ((UChar) 0x0030); +static const UChar U_ONE = ((UChar) 0x0031); +static const UChar U_TWO = ((UChar) 0x0032); +static const UChar U_THREE = ((UChar) 0x0033); +static const UChar U_FOUR = ((UChar) 0x0034); +static const UChar U_FIVE = ((UChar) 0x0035); +static const UChar U_SIX = ((UChar) 0x0036); +static const UChar U_SEVEN = ((UChar) 0x0037); +static const UChar U_EIGHT = ((UChar) 0x0038); +static const UChar U_NINE = ((UChar) 0x0039); +static const UChar COLON = ((UChar) 0x003A); +static const UChar SEMI_COLON = ((UChar) 0x003B); +static const UChar EQUALS = ((UChar) 0x003D); +static const UChar AT = ((UChar) 0x0040); +static const UChar CAP_A = ((UChar) 0x0041); +static const UChar CAP_B = ((UChar) 0x0042); +static const UChar CAP_R = ((UChar) 0x0052); +static const UChar CAP_Z = ((UChar) 0x005A); +static const UChar LOWLINE = ((UChar) 0x005F); +static const UChar LEFTBRACE = ((UChar) 0x007B); +static const UChar RIGHTBRACE = ((UChar) 0x007D); +static const UChar TILDE = ((UChar) 0x007E); +static const UChar ELLIPSIS = ((UChar) 0x2026); + +static const UChar LOW_A = ((UChar) 0x0061); +static const UChar LOW_B = ((UChar) 0x0062); +static const UChar LOW_C = ((UChar) 0x0063); +static const UChar LOW_D = ((UChar) 0x0064); +static const UChar LOW_E = ((UChar) 0x0065); +static const UChar LOW_F = ((UChar) 0x0066); +static const UChar LOW_G = ((UChar) 0x0067); +static const UChar LOW_H = ((UChar) 0x0068); +static const UChar LOW_I = ((UChar) 0x0069); +static const UChar LOW_J = ((UChar) 0x006a); +static const UChar LOW_K = ((UChar) 0x006B); +static const UChar LOW_L = ((UChar) 0x006C); +static const UChar LOW_M = ((UChar) 0x006D); +static const UChar LOW_N = ((UChar) 0x006E); +static const UChar LOW_O = ((UChar) 0x006F); +static const UChar LOW_P = ((UChar) 0x0070); +static const UChar LOW_Q = ((UChar) 0x0071); +static const UChar LOW_R = ((UChar) 0x0072); +static const UChar LOW_S = ((UChar) 0x0073); +static const UChar LOW_T = ((UChar) 0x0074); +static const UChar LOW_U = ((UChar) 0x0075); +static const UChar LOW_V = ((UChar) 0x0076); +static const UChar LOW_W = ((UChar) 0x0077); +static const UChar LOW_Y = ((UChar) 0x0079); +static const UChar LOW_Z = ((UChar) 0x007A); + +}; static const int32_t PLURAL_RANGE_HIGH = 0x7fffffff; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixMatcher.java index 58bf69ac7d..5104e29b9d 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixMatcher.java @@ -75,7 +75,7 @@ public class AffixMatcher implements NumberParseMatcher { return true; } - public static void newGenerate( + public static void createMatchers( AffixPatternProvider patternInfo, NumberParserImpl output, MatcherFactory factory, diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParseMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParseMatcher.java index 1d576cee2c..cd7b04ade6 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParseMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParseMatcher.java @@ -5,6 +5,8 @@ package com.ibm.icu.impl.number.parse; import com.ibm.icu.text.UnicodeSet; /** + * The core interface implemented by all matchers used for number parsing. + * * Given a string, there should NOT be more than one way to consume the string with the same matcher * applied multiple times. If there is, the non-greedy parsing algorithm will be unhappy and may enter an * exponential-time loop. For example, consider the "A Matcher" that accepts "any number of As". Given diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java index ff59ca052c..55a046a43a 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java @@ -66,18 +66,10 @@ public class NumberParserImpl { STRICT, } - @Deprecated - public static NumberParserImpl createParserFromPattern( + public static NumberParserImpl createSimpleParser( ULocale locale, String pattern, - boolean strictGrouping) { - // Temporary frontend for testing. - - int parseFlags = ParsingUtils.PARSE_FLAG_IGNORE_CASE - | ParsingUtils.PARSE_FLAG_INCLUDE_UNPAIRED_AFFIXES; - if (strictGrouping) { - parseFlags |= ParsingUtils.PARSE_FLAG_STRICT_GROUPING_SIZE; - } + int parseFlags) { NumberParserImpl parser = new NumberParserImpl(parseFlags, true); DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale); @@ -91,7 +83,7 @@ public class NumberParserImpl { factory.parseFlags = parseFlags; ParsedPatternInfo patternInfo = PatternStringParser.parseToPatternInfo(pattern); - AffixMatcher.newGenerate(patternInfo, parser, factory, ignorables, parseFlags); + AffixMatcher.createMatchers(patternInfo, parser, factory, ignorables, parseFlags); Grouper grouper = Grouper.forStrategy(GroupingStrategy.AUTO).withLocaleData(locale, patternInfo); @@ -209,7 +201,7 @@ public class NumberParserImpl { ////////////////////// // Set up a pattern modifier with mostly defaults to generate AffixMatchers. - AffixMatcher.newGenerate(patternInfo, parser, factory, ignorables, parseFlags); + AffixMatcher.createMatchers(patternInfo, parser, factory, ignorables, parseFlags); //////////////////////// /// CURRENCY MATCHER /// diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java index 27ce15df1f..2bd45cc08b 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java @@ -8,11 +8,15 @@ import java.util.Comparator; import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD; /** - * @author sffc + * Struct-like class to hold the results of a parsing routine. * + * @author sffc */ public class ParsedNumber { + /** + * The numerical value that was parsed. + */ public DecimalQuantity_DualStorageBCD quantity; /** diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/UnicodeSetStaticCache.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/UnicodeSetStaticCache.java index bf0593e123..3839301cea 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/UnicodeSetStaticCache.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/UnicodeSetStaticCache.java @@ -72,10 +72,6 @@ public class UnicodeSetStaticCache { return get(key1).contains(str) ? key1 : chooseFrom(str, key2); } - public static Key chooseFrom(String str, Key key1, Key key2, Key key3) { - return get(key1).contains(str) ? key1 : chooseFrom(str, key2, key3); - } - private static UnicodeSet computeUnion(Key k1, Key k2) { return new UnicodeSet().addAll(get(k1)).addAll(get(k2)).freeze(); } @@ -110,7 +106,6 @@ public class UnicodeSetStaticCache { unicodeSets.put(Key.MINUS_SIGN, new UnicodeSet("[-⁻₋−➖﹣-]").freeze()); unicodeSets.put(Key.PLUS_SIGN, new UnicodeSet("[+⁺₊➕﬩﹢+]").freeze()); - // TODO: Fill in the next three sets. unicodeSets.put(Key.PERCENT_SIGN, new UnicodeSet("[%٪]").freeze()); unicodeSets.put(Key.PERMILLE_SIGN, new UnicodeSet("[‰؉]").freeze()); unicodeSets.put(Key.INFINITY, new UnicodeSet("[∞]").freeze()); diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java index 4e69a76258..5bb123968b 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java @@ -13,6 +13,7 @@ import com.ibm.icu.impl.number.parse.IgnorablesMatcher; import com.ibm.icu.impl.number.parse.MinusSignMatcher; import com.ibm.icu.impl.number.parse.NumberParserImpl; import com.ibm.icu.impl.number.parse.ParsedNumber; +import com.ibm.icu.impl.number.parse.ParsingUtils; import com.ibm.icu.impl.number.parse.PercentMatcher; import com.ibm.icu.impl.number.parse.PlusSignMatcher; import com.ibm.icu.impl.number.parse.SeriesMatcher; @@ -58,8 +59,8 @@ public class NumberParserTest { { 3, "-𝟱𝟭𝟰𝟮𝟯-", "0", 11, -51423. }, { 3, "a51423US dollars", "a0¤¤¤", 16, 51423. }, { 3, "a 51423 US dollars", "a0¤¤¤", 18, 51423. }, - { 3, "514.23 USD", "0", 10, 514.23 }, - { 3, "514.23 GBP", "0", 10, 514.23 }, + { 3, "514.23 USD", "¤0", 10, 514.23 }, + { 3, "514.23 GBP", "¤0", 10, 514.23 }, { 3, "a 𝟱𝟭𝟰𝟮𝟯 b", "a0b", 14, 51423. }, { 3, "-a 𝟱𝟭𝟰𝟮𝟯 b", "a0b", 15, -51423. }, { 3, "a -𝟱𝟭𝟰𝟮𝟯 b", "a0b", 15, -51423. }, @@ -79,7 +80,7 @@ public class NumberParserTest { { 3, "𝟱.𝟭𝟰𝟮E𝟯", "0", 12, 5142. }, { 3, "𝟱.𝟭𝟰𝟮E-𝟯", "0", 13, 0.005142 }, { 3, "𝟱.𝟭𝟰𝟮e-𝟯", "0", 13, 0.005142 }, - { 7, "5,142.50 Canadian dollars", "#,##,##0", 25, 5142.5 }, + { 7, "5,142.50 Canadian dollars", "#,##,##0 ¤¤¤", 25, 5142.5 }, { 3, "a$ b5", "a ¤ b0", 5, 5.0 }, { 3, "📺1.23", "📺0;📻0", 6, 1.23 }, { 3, "📻1.23", "📺0;📻0", 6, -1.23 }, @@ -87,6 +88,8 @@ public class NumberParserTest { { 3, " 0", "a0", 31, 0.0 }, // should not hang { 3, "0", "0", 1, 0.0 } }; + int parseFlags = ParsingUtils.PARSE_FLAG_IGNORE_CASE + | ParsingUtils.PARSE_FLAG_INCLUDE_UNPAIRED_AFFIXES; for (Object[] cas : cases) { int flags = (Integer) cas[0]; String input = (String) cas[1]; @@ -94,7 +97,7 @@ public class NumberParserTest { int expectedCharsConsumed = (Integer) cas[3]; double resultDouble = (Double) cas[4]; NumberParserImpl parser = NumberParserImpl - .createParserFromPattern(ULocale.ENGLISH, pattern, false); + .createSimpleParser(ULocale.ENGLISH, pattern, parseFlags); String message = "Input <" + input + "> Parser " + parser; if (0 != (flags & 0x01)) { @@ -127,7 +130,9 @@ public class NumberParserTest { if (0 != (flags & 0x04)) { // Test with strict separators - parser = NumberParserImpl.createParserFromPattern(ULocale.ENGLISH, pattern, true); + parser = NumberParserImpl.createSimpleParser(ULocale.ENGLISH, + pattern, + parseFlags | ParsingUtils.PARSE_FLAG_STRICT_GROUPING_SIZE); ParsedNumber resultObject = new ParsedNumber(); parser.parse(input, true, resultObject); assertNotNull("Strict Parse failed: " + message, resultObject.quantity); @@ -146,7 +151,7 @@ public class NumberParserTest { public void testLocaleFi() { // This case is interesting because locale fi has NaN starting with 'e', the same as scientific NumberParserImpl parser = NumberParserImpl - .createParserFromPattern(new ULocale("fi"), "0", false); + .createSimpleParser(new ULocale("fi"), "0", ParsingUtils.PARSE_FLAG_IGNORE_CASE); ParsedNumber resultObject = new ParsedNumber(); parser.parse("epäluku", false, resultObject); diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/UnicodeSetStaticCacheTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/UnicodeSetStaticCacheTest.java index 97283a1400..7aec4f77f1 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/UnicodeSetStaticCacheTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/UnicodeSetStaticCacheTest.java @@ -75,7 +75,7 @@ public class UnicodeSetStaticCacheTest { static void assertInSet(ULocale locale, UnicodeSet set, int cp) { // If this test case fails, add the specified code point to the corresponding set in - // UnicodeSetStaticCache.java + // UnicodeSetStaticCache.java and numparse_unisets.cpp assertTrue( locale + " U+" From 2ee42b9288521ade065c26370f7b1eec2bd2e2a8 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Thu, 8 Feb 2018 08:49:50 +0000 Subject: [PATCH 003/129] ICU-13574 Checkpoint commit. Basic NumberParseMatcher implementations in DecimalMatcher and SymbolMatcher. Cleanup in ICU4J. X-SVN-Rev: 40869 --- icu4c/source/i18n/Makefile.in | 3 +- icu4c/source/i18n/number_decimalquantity.cpp | 1 + icu4c/source/i18n/number_formatimpl.cpp | 110 +++--- icu4c/source/i18n/number_grouping.cpp | 8 + icu4c/source/i18n/number_scientific.cpp | 3 +- icu4c/source/i18n/number_utils.h | 31 +- icu4c/source/i18n/numparse_decimal.cpp | 313 ++++++++++++++++++ icu4c/source/i18n/numparse_decimal.h | 69 ++++ icu4c/source/i18n/numparse_impl.cpp | 113 +++++++ icu4c/source/i18n/numparse_impl.h | 56 ++++ icu4c/source/i18n/numparse_symbols.cpp | 95 ++++++ icu4c/source/i18n/numparse_symbols.h | 60 ++++ icu4c/source/i18n/numparse_types.h | 61 +++- icu4c/source/i18n/numparse_utils.h | 38 +++ icu4c/source/i18n/unicode/numberformatter.h | 22 +- icu4c/source/test/intltest/Makefile.in | 3 +- icu4c/source/test/intltest/numbertest.h | 11 + .../source/test/intltest/numbertest_parse.cpp | 144 ++++++++ .../intltest/numbertest_stringbuilder.cpp | 2 +- .../test/intltest/numbertest_unisets.cpp | 3 - .../icu/impl/number/parse/DecimalMatcher.java | 33 +- .../impl/number/parse/NumberParserImpl.java | 27 +- .../icu/impl/number/parse/ParsingUtils.java | 3 +- .../impl/number/parse/ScientificMatcher.java | 12 +- .../ibm/icu/text/DecimalFormatSymbols.java | 6 +- .../format/IntlTestDecimalFormatSymbols.java | 3 + .../icu/dev/test/number/NumberParserTest.java | 33 +- 27 files changed, 1112 insertions(+), 151 deletions(-) create mode 100644 icu4c/source/i18n/numparse_decimal.cpp create mode 100644 icu4c/source/i18n/numparse_decimal.h create mode 100644 icu4c/source/i18n/numparse_impl.cpp create mode 100644 icu4c/source/i18n/numparse_impl.h create mode 100644 icu4c/source/i18n/numparse_symbols.cpp create mode 100644 icu4c/source/i18n/numparse_symbols.h create mode 100644 icu4c/source/i18n/numparse_utils.h create mode 100644 icu4c/source/test/intltest/numbertest_parse.cpp diff --git a/icu4c/source/i18n/Makefile.in b/icu4c/source/i18n/Makefile.in index a5752781f2..94dac4e235 100644 --- a/icu4c/source/i18n/Makefile.in +++ b/icu4c/source/i18n/Makefile.in @@ -108,7 +108,8 @@ number_decimfmtprops.o number_fluent.o number_formatimpl.o number_grouping.o \ number_integerwidth.o number_longnames.o number_modifiers.o number_notation.o \ number_padding.o number_patternmodifier.o number_patternstring.o \ number_rounding.o number_scientific.o number_stringbuilder.o \ -numparse_stringsegment.o numparse_unisets.o numparse_parsednumber.o +numparse_stringsegment.o numparse_unisets.o numparse_parsednumber.o \ +numparse_impl.o numparse_symbols.o numparse_decimal.o ## Header files to install diff --git a/icu4c/source/i18n/number_decimalquantity.cpp b/icu4c/source/i18n/number_decimalquantity.cpp index 3342771b98..31150dfa12 100644 --- a/icu4c/source/i18n/number_decimalquantity.cpp +++ b/icu4c/source/i18n/number_decimalquantity.cpp @@ -190,6 +190,7 @@ int32_t DecimalQuantity::getMagnitude() const { void DecimalQuantity::adjustMagnitude(int32_t delta) { if (precision != 0) { + // TODO: How to handle overflow cases? scale += delta; origDelta += delta; } diff --git a/icu4c/source/i18n/number_formatimpl.cpp b/icu4c/source/i18n/number_formatimpl.cpp index bc96cb15da..795c3d1348 100644 --- a/icu4c/source/i18n/number_formatimpl.cpp +++ b/icu4c/source/i18n/number_formatimpl.cpp @@ -38,9 +38,9 @@ enum CldrPatternStyle { // TODO: Consider scientific format. }; -const char16_t * -doGetPattern(UResourceBundle *res, const char *nsName, const char *patternKey, UErrorCode &publicStatus, - UErrorCode &localStatus) { +const char16_t* +doGetPattern(UResourceBundle* res, const char* nsName, const char* patternKey, UErrorCode& publicStatus, + UErrorCode& localStatus) { // Construct the path into the resource bundle CharString key; key.append("NumberElements/", publicStatus); @@ -53,9 +53,9 @@ doGetPattern(UResourceBundle *res, const char *nsName, const char *patternKey, U return ures_getStringByKeyWithFallback(res, key.data(), nullptr, &localStatus); } -const char16_t *getPatternForStyle(const Locale &locale, const char *nsName, CldrPatternStyle style, - UErrorCode &status) { - const char *patternKey; +const char16_t* getPatternForStyle(const Locale& locale, const char* nsName, CldrPatternStyle style, + UErrorCode& status) { + const char* patternKey; switch (style) { case CLDR_PATTERN_STYLE_DECIMAL: patternKey = "decimalFormat"; @@ -76,7 +76,7 @@ const char16_t *getPatternForStyle(const Locale &locale, const char *nsName, Cld // Attempt to get the pattern with the native numbering system. UErrorCode localStatus = U_ZERO_ERROR; - const char16_t *pattern; + const char16_t* pattern; pattern = doGetPattern(res.getAlias(), nsName, patternKey, status, localStatus); if (U_FAILURE(status)) { return u""; } @@ -96,18 +96,21 @@ struct CurrencyFormatInfoResult { const char16_t* decimalSeparator; const char16_t* groupingSeparator; }; -CurrencyFormatInfoResult getCurrencyFormatInfo(const Locale& locale, const char* isoCode, UErrorCode& status) { + +CurrencyFormatInfoResult +getCurrencyFormatInfo(const Locale& locale, const char* isoCode, UErrorCode& status) { // TODO: Load this data in a centralized location like ICU4J? // TODO: Parts of this same data are loaded in dcfmtsym.cpp; should clean up. - CurrencyFormatInfoResult result = { false, nullptr, nullptr, nullptr }; - if (U_FAILURE(status)) return result; + CurrencyFormatInfoResult result = {false, nullptr, nullptr, nullptr}; + if (U_FAILURE(status)) { return result; } CharString key; key.append("Currencies/", status); key.append(isoCode, status); UErrorCode localStatus = status; LocalUResourceBundlePointer bundle(ures_open(U_ICUDATA_CURR, locale.getName(), &localStatus)); ures_getByKeyWithFallback(bundle.getAlias(), key.data(), bundle.getAlias(), &localStatus); - if (U_SUCCESS(localStatus) && ures_getSize(bundle.getAlias())>2) { // the length is 3 if more data is present + if (U_SUCCESS(localStatus) && + ures_getSize(bundle.getAlias()) > 2) { // the length is 3 if more data is present ures_getByIndex(bundle.getAlias(), 2, bundle.getAlias(), &localStatus); int32_t dummy; result.exists = true; @@ -121,30 +124,30 @@ CurrencyFormatInfoResult getCurrencyFormatInfo(const Locale& locale, const char* return result; } -inline bool unitIsCurrency(const MeasureUnit &unit) { +inline bool unitIsCurrency(const MeasureUnit& unit) { return uprv_strcmp("currency", unit.getType()) == 0; } -inline bool unitIsNoUnit(const MeasureUnit &unit) { +inline bool unitIsNoUnit(const MeasureUnit& unit) { return uprv_strcmp("none", unit.getType()) == 0; } -inline bool unitIsPercent(const MeasureUnit &unit) { +inline bool unitIsPercent(const MeasureUnit& unit) { return uprv_strcmp("percent", unit.getSubtype()) == 0; } -inline bool unitIsPermille(const MeasureUnit &unit) { +inline bool unitIsPermille(const MeasureUnit& unit) { return uprv_strcmp("permille", unit.getSubtype()) == 0; } } // namespace -NumberFormatterImpl *NumberFormatterImpl::fromMacros(const MacroProps ¯os, UErrorCode &status) { +NumberFormatterImpl* NumberFormatterImpl::fromMacros(const MacroProps& macros, UErrorCode& status) { return new NumberFormatterImpl(macros, true, status); } -void NumberFormatterImpl::applyStatic(const MacroProps ¯os, DecimalQuantity &inValue, - NumberStringBuilder &outString, UErrorCode &status) { +void NumberFormatterImpl::applyStatic(const MacroProps& macros, DecimalQuantity& inValue, + NumberStringBuilder& outString, UErrorCode& status) { NumberFormatterImpl impl(macros, false, status); impl.applyUnsafe(inValue, outString, status); } @@ -154,8 +157,8 @@ void NumberFormatterImpl::applyStatic(const MacroProps ¯os, DecimalQuantity // The "unsafe" method simply re-uses fMicros, eliminating the extra copy operation. // See MicroProps::processQuantity() for details. -void NumberFormatterImpl::apply(DecimalQuantity &inValue, NumberStringBuilder &outString, - UErrorCode &status) const { +void NumberFormatterImpl::apply(DecimalQuantity& inValue, NumberStringBuilder& outString, + UErrorCode& status) const { if (U_FAILURE(status)) { return; } MicroProps micros; fMicroPropsGenerator->processQuantity(inValue, micros, status); @@ -163,23 +166,23 @@ void NumberFormatterImpl::apply(DecimalQuantity &inValue, NumberStringBuilder &o microsToString(micros, inValue, outString, status); } -void NumberFormatterImpl::applyUnsafe(DecimalQuantity &inValue, NumberStringBuilder &outString, - UErrorCode &status) { +void NumberFormatterImpl::applyUnsafe(DecimalQuantity& inValue, NumberStringBuilder& outString, + UErrorCode& status) { if (U_FAILURE(status)) { return; } fMicroPropsGenerator->processQuantity(inValue, fMicros, status); if (U_FAILURE(status)) { return; } microsToString(fMicros, inValue, outString, status); } -NumberFormatterImpl::NumberFormatterImpl(const MacroProps ¯os, bool safe, UErrorCode &status) { +NumberFormatterImpl::NumberFormatterImpl(const MacroProps& macros, bool safe, UErrorCode& status) { fMicroPropsGenerator = macrosToMicroGenerator(macros, safe, status); } ////////// -const MicroPropsGenerator * -NumberFormatterImpl::macrosToMicroGenerator(const MacroProps ¯os, bool safe, UErrorCode &status) { - const MicroPropsGenerator *chain = &fMicros; +const MicroPropsGenerator* +NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe, UErrorCode& status) { + const MicroPropsGenerator* chain = &fMicros; // Check that macros is error-free before continuing. if (macros.copyErrorTo(status)) { @@ -194,9 +197,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 - || macros.sign == UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO; + 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 @@ -208,7 +211,7 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps ¯os, bool safe, // Select the numbering system. LocalPointer nsLocal; - const NumberingSystem *ns; + const NumberingSystem* ns; if (macros.symbols.isNumberingSystem()) { ns = macros.symbols.getNumberingSystem(); } else { @@ -217,7 +220,7 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps ¯os, bool safe, // Give ownership to the function scope. nsLocal.adoptInstead(ns); } - const char *nsName = U_SUCCESS(status) ? ns->getName() : "latn"; + const char* nsName = U_SUCCESS(status) ? ns->getName() : "latn"; // Resolve the symbols. Do this here because currency may need to customize them. if (macros.symbols.isDecimalFormatSymbols()) { @@ -232,7 +235,8 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps ¯os, bool safe, // If we are formatting currency, check for a currency-specific pattern. const char16_t* pattern = nullptr; if (isCurrency) { - CurrencyFormatInfoResult info = getCurrencyFormatInfo(macros.locale, currency.getSubtype(), status); + CurrencyFormatInfoResult info = getCurrencyFormatInfo( + macros.locale, currency.getSubtype(), status); if (info.exists) { pattern = info.pattern; // It's clunky to clone an object here, but this code is not frequently executed. @@ -240,13 +244,13 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps ¯os, bool safe, fMicros.symbols = symbols; fSymbols.adoptInstead(symbols); symbols->setSymbol( - DecimalFormatSymbols::ENumberFormatSymbol::kMonetarySeparatorSymbol, - UnicodeString(info.decimalSeparator), - FALSE); + DecimalFormatSymbols::ENumberFormatSymbol::kMonetarySeparatorSymbol, + UnicodeString(info.decimalSeparator), + FALSE); symbols->setSymbol( - DecimalFormatSymbols::ENumberFormatSymbol::kMonetaryGroupingSeparatorSymbol, - UnicodeString(info.groupingSeparator), - FALSE); + DecimalFormatSymbols::ENumberFormatSymbol::kMonetaryGroupingSeparatorSymbol, + UnicodeString(info.groupingSeparator), + FALSE); } } if (pattern == nullptr) { @@ -407,9 +411,9 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps ¯os, bool safe, return chain; } -const PluralRules * -NumberFormatterImpl::resolvePluralRules(const PluralRules *rulesPtr, const Locale &locale, - UErrorCode &status) { +const PluralRules* +NumberFormatterImpl::resolvePluralRules(const PluralRules* rulesPtr, const Locale& locale, + UErrorCode& status) { if (rulesPtr != nullptr) { return rulesPtr; } @@ -420,8 +424,8 @@ NumberFormatterImpl::resolvePluralRules(const PluralRules *rulesPtr, const Local return fRules.getAlias(); } -int32_t NumberFormatterImpl::microsToString(const MicroProps µs, DecimalQuantity &quantity, - NumberStringBuilder &string, UErrorCode &status) { +int32_t NumberFormatterImpl::microsToString(const MicroProps& micros, DecimalQuantity& quantity, + NumberStringBuilder& string, UErrorCode& status) { micros.rounding.apply(quantity, status); micros.integerWidth.apply(quantity, status); int32_t length = writeNumber(micros, quantity, string, status); @@ -439,8 +443,8 @@ int32_t NumberFormatterImpl::microsToString(const MicroProps µs, DecimalQua return length; } -int32_t NumberFormatterImpl::writeNumber(const MicroProps µs, DecimalQuantity &quantity, - NumberStringBuilder &string, UErrorCode &status) { +int32_t NumberFormatterImpl::writeNumber(const MicroProps& micros, DecimalQuantity& quantity, + NumberStringBuilder& string, UErrorCode& status) { int32_t length = 0; if (quantity.isInfinite()) { length += string.insert( @@ -480,8 +484,8 @@ int32_t NumberFormatterImpl::writeNumber(const MicroProps µs, DecimalQuanti return length; } -int32_t NumberFormatterImpl::writeIntegerDigits(const MicroProps µs, DecimalQuantity &quantity, - NumberStringBuilder &string, UErrorCode &status) { +int32_t NumberFormatterImpl::writeIntegerDigits(const MicroProps& micros, DecimalQuantity& quantity, + NumberStringBuilder& string, UErrorCode& status) { int length = 0; int integerCount = quantity.getUpperDisplayMagnitude() + 1; for (int i = 0; i < integerCount; i++) { @@ -499,21 +503,21 @@ int32_t NumberFormatterImpl::writeIntegerDigits(const MicroProps µs, Decima // Get and append the next digit value int8_t nextDigit = quantity.getDigit(i); - length += string.insert( - 0, getDigitFromSymbols(nextDigit, *micros.symbols), UNUM_INTEGER_FIELD, status); + length += insertDigitFromSymbols( + string, 0, nextDigit, *micros.symbols, UNUM_INTEGER_FIELD, status); } return length; } -int32_t NumberFormatterImpl::writeFractionDigits(const MicroProps µs, DecimalQuantity &quantity, - NumberStringBuilder &string, UErrorCode &status) { +int32_t NumberFormatterImpl::writeFractionDigits(const MicroProps& micros, DecimalQuantity& quantity, + NumberStringBuilder& string, UErrorCode& status) { int length = 0; int fractionCount = -quantity.getLowerDisplayMagnitude(); for (int i = 0; i < fractionCount; i++) { // Get and append the next digit value int8_t nextDigit = quantity.getDigit(-i - 1); - length += string.append( - getDigitFromSymbols(nextDigit, *micros.symbols), UNUM_FRACTION_FIELD, status); + length += insertDigitFromSymbols( + string, string.length(), nextDigit, *micros.symbols, UNUM_INTEGER_FIELD, status); } return length; } diff --git a/icu4c/source/i18n/number_grouping.cpp b/icu4c/source/i18n/number_grouping.cpp index 67fd4c9431..03daffa629 100644 --- a/icu4c/source/i18n/number_grouping.cpp +++ b/icu4c/source/i18n/number_grouping.cpp @@ -86,4 +86,12 @@ bool Grouper::groupAtPosition(int32_t position, const impl::DecimalQuantity &val && value.getUpperDisplayMagnitude() - fGrouping1 + 1 >= fMinGrouping; } +int16_t Grouper::getPrimary() const { + return fGrouping1; +} + +int16_t Grouper::getSecondary() const { + return fGrouping2; +} + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/number_scientific.cpp b/icu4c/source/i18n/number_scientific.cpp index 0557adb63e..548ce625ad 100644 --- a/icu4c/source/i18n/number_scientific.cpp +++ b/icu4c/source/i18n/number_scientific.cpp @@ -64,8 +64,7 @@ int32_t ScientificModifier::apply(NumberStringBuilder &output, int32_t /*leftInd int32_t disp = std::abs(fExponent); for (int j = 0; j < fHandler->fSettings.fMinExponentDigits || disp > 0; j++, disp /= 10) { auto d = static_cast(disp % 10); - const UnicodeString &digitString = getDigitFromSymbols(d, *fHandler->fSymbols); - i += output.insert(i - j, digitString, UNUM_EXPONENT_FIELD, status); + i += insertDigitFromSymbols(output, i - j, d, *fHandler->fSymbols, UNUM_EXPONENT_FIELD, status); } return i - rightIndex; } diff --git a/icu4c/source/i18n/number_utils.h b/icu4c/source/i18n/number_utils.h index 3a408d6007..a889c69eb7 100644 --- a/icu4c/source/i18n/number_utils.h +++ b/icu4c/source/i18n/number_utils.h @@ -19,7 +19,7 @@ namespace impl { class UnicodeStringCharSequence : public CharSequence { public: - explicit UnicodeStringCharSequence(const UnicodeString &other) { + explicit UnicodeStringCharSequence(const UnicodeString& other) { fStr = other; } @@ -62,10 +62,10 @@ struct MicroProps : public MicroPropsGenerator { bool useCurrency; // Note: This struct has no direct ownership of the following pointers. - const DecimalFormatSymbols *symbols; - const Modifier *modOuter; - const Modifier *modMiddle; - const Modifier *modInner; + const DecimalFormatSymbols* symbols; + const Modifier* modOuter; + const Modifier* modMiddle; + const Modifier* modInner; // The following "helper" fields may optionally be used during the MicroPropsGenerator. // They live here to retain memory. @@ -78,12 +78,12 @@ struct MicroProps : public MicroPropsGenerator { MicroProps() = default; - MicroProps(const MicroProps &other) = default; + MicroProps(const MicroProps& other) = default; - MicroProps &operator=(const MicroProps &other) = default; + MicroProps& operator=(const MicroProps& other) = default; - void processQuantity(DecimalQuantity &, MicroProps µs, UErrorCode &status) const U_OVERRIDE { - (void)status; + void processQuantity(DecimalQuantity&, MicroProps& micros, UErrorCode& status) const U_OVERRIDE { + (void) status; if (this == µs) { // Unsafe path: no need to perform a copy. U_ASSERT(!exhausted); @@ -111,14 +111,13 @@ struct NumberFormatterResults : public UMemory { NumberStringBuilder string; }; -inline const UnicodeString getDigitFromSymbols(int8_t digit, const DecimalFormatSymbols &symbols) { - // TODO: Implement DecimalFormatSymbols.getCodePointZero()? - if (digit == 0) { - return symbols.getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kZeroDigitSymbol); - } else { - return symbols.getSymbol(static_cast( - DecimalFormatSymbols::ENumberFormatSymbol::kOneDigitSymbol + digit - 1)); +inline int32_t insertDigitFromSymbols(NumberStringBuilder& output, int32_t index, int8_t digit, + const DecimalFormatSymbols& symbols, Field field, + UErrorCode& status) { + if (symbols.getCodePointZero() != -1) { + return output.insertCodePoint(index, symbols.getCodePointZero() + digit, field, status); } + return output.insert(index, symbols.getConstDigitSymbol(digit), field, status); } } // namespace impl diff --git a/icu4c/source/i18n/numparse_decimal.cpp b/icu4c/source/i18n/numparse_decimal.cpp new file mode 100644 index 0000000000..bfc9c4f8a7 --- /dev/null +++ b/icu4c/source/i18n/numparse_decimal.cpp @@ -0,0 +1,313 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT + +#include "numparse_types.h" +#include "numparse_decimal.h" +#include "numparse_unisets.h" +#include "numparse_utils.h" +#include "unicode/uchar.h" + +using namespace icu; +using namespace icu::numparse; +using namespace icu::numparse::impl; + + +DecimalMatcher::DecimalMatcher(const DecimalFormatSymbols& symbols, const Grouper& grouper, + parse_flags_t parseFlags) { + if (0 != (parseFlags & PARSE_FLAG_MONETARY_SEPARATORS)) { + groupingSeparator = symbols.getConstSymbol(DecimalFormatSymbols::kMonetaryGroupingSeparatorSymbol); + decimalSeparator = symbols.getConstSymbol(DecimalFormatSymbols::kMonetarySeparatorSymbol); + } else { + groupingSeparator = symbols.getConstSymbol(DecimalFormatSymbols::kGroupingSeparatorSymbol); + decimalSeparator = symbols.getConstSymbol(DecimalFormatSymbols::kDecimalSeparatorSymbol); + } + bool strictSeparators = 0 != (parseFlags & PARSE_FLAG_STRICT_SEPARATORS); + unisets::Key groupingKey = strictSeparators ? unisets::STRICT_ALL_SEPARATORS + : unisets::ALL_SEPARATORS; + + // Attempt to find separators in the static cache + + groupingUniSet = unisets::get(groupingKey); + unisets::Key decimalKey = unisets::chooseFrom( + decimalSeparator, + strictSeparators ? unisets::STRICT_COMMA : unisets::COMMA, + strictSeparators ? unisets::STRICT_PERIOD : unisets::PERIOD); + if (decimalKey != unisets::COUNT) { + decimalUniSet = unisets::get(decimalKey); + } else { + auto* set = new UnicodeSet(); + set->add(decimalSeparator.char32At(0)); + set->freeze(); + decimalUniSet = set; + fLocalDecimalUniSet.adoptInstead(set); + } + + if (groupingKey != unisets::COUNT && decimalKey != unisets::COUNT) { + // Everything is available in the static cache + separatorSet = groupingUniSet; + leadSet = unisets::get( + strictSeparators ? unisets::DIGITS_OR_ALL_SEPARATORS + : unisets::DIGITS_OR_STRICT_ALL_SEPARATORS); + } else { + auto* set = new UnicodeSet(); + set->addAll(*groupingUniSet); + set->addAll(*decimalUniSet); + set->freeze(); + separatorSet = set; + fLocalSeparatorSet.adoptInstead(set); + leadSet = nullptr; + } + + int cpZero = symbols.getCodePointZero(); + if (cpZero == -1 || !u_isdigit(cpZero) || u_digit(cpZero, 10) != 0) { + // Uncommon case: okay to allocate. + auto digitStrings = new UnicodeString[10]; + fLocalDigitStrings.adoptInstead(digitStrings); + for (int32_t i = 0; i <= 9; i++) { + digitStrings[i] = symbols.getConstDigitSymbol(i); + } + } + + requireGroupingMatch = 0 != (parseFlags & PARSE_FLAG_STRICT_GROUPING_SIZE); + groupingDisabled = 0 != (parseFlags & PARSE_FLAG_GROUPING_DISABLED); + fractionGroupingDisabled = 0 != ( + parseFlags & PARSE_FLAG_FRACTION_GROUPING_DISABLED); + integerOnly = 0 != (parseFlags & PARSE_FLAG_INTEGER_ONLY); + grouping1 = grouper.getPrimary(); + grouping2 = grouper.getSecondary(); +} + +bool DecimalMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const { + return match(segment, result, 0, status); +} + +bool DecimalMatcher::match(StringSegment& segment, ParsedNumber& result, int8_t exponentSign, + UErrorCode&) const { + if (result.seenNumber() && exponentSign == 0) { + // A number has already been consumed. + return false; + } else if (exponentSign != 0) { + // scientific notation always comes after the number + U_ASSERT(!result.quantity.bogus); + } + + ParsedNumber backupResult(result); + + // strict parsing + bool strictFail = false; // did we exit with a strict parse failure? + UnicodeString actualGroupingString = groupingSeparator; + UnicodeString actualDecimalString = decimalSeparator; + int32_t groupedDigitCount = 0; // tracking count of digits delimited by grouping separator + int32_t backupOffset = -1; // used for preserving the last confirmed position + bool afterFirstGrouping = false; + bool seenGrouping = false; + bool seenDecimal = false; + int32_t digitsAfterDecimal = 0; + int32_t initialOffset = segment.getOffset(); + int32_t exponent = 0; + bool hasPartialPrefix = false; + while (segment.length() > 0) { + hasPartialPrefix = false; + + // Attempt to match a digit. + int8_t digit = -1; + + // Try by code point digit value. + int cp = segment.getCodePoint(); + if (u_isdigit(cp)) { + segment.adjustOffset(U16_LENGTH(cp)); + digit = static_cast(u_digit(cp, 10)); + } + + // Try by digit string. + if (digit == -1 && !fLocalDigitStrings.isNull()) { + for (int i = 0; i < 10; i++) { + const UnicodeString& str = fLocalDigitStrings[i]; + int overlap = segment.getCommonPrefixLength(str); + if (overlap == str.length()) { + segment.adjustOffset(overlap); + digit = static_cast(i); + break; + } else if (overlap == segment.length()) { + hasPartialPrefix = true; + } + } + } + + if (digit >= 0) { + // Digit was found. + // Check for grouping size violation + if (backupOffset != -1) { + if (requireGroupingMatch) { + // comma followed by digit, so group before comma is a secondary + // group. If there was a group separator before that, the group + // must == the secondary group length, else it can be <= the the + // secondary group length. + if ((afterFirstGrouping && groupedDigitCount != grouping2) || + (!afterFirstGrouping && groupedDigitCount > grouping2)) { + strictFail = true; + break; + } + } + afterFirstGrouping = true; + backupOffset = -1; + groupedDigitCount = 0; + } + + // Save the digit in the DecimalQuantity or scientific adjustment. + if (exponentSign != 0) { + int nextExponent = digit + exponent * 10; + if (nextExponent < exponent) { + // Overflow + exponent = INT32_MAX; + } else { + exponent = nextExponent; + } + } else { + if (result.quantity.bogus) { + result.quantity.bogus = false; + } + result.quantity.appendDigit(digit, 0, true); + } + result.setCharsConsumed(segment); + groupedDigitCount++; + if (seenDecimal) { + digitsAfterDecimal++; + } + continue; + } + + // Attempt to match a literal grouping or decimal separator + int32_t decimalOverlap = segment.getCommonPrefixLength(actualDecimalString); + bool decimalStringMatch = decimalOverlap == actualDecimalString.length(); + int32_t groupingOverlap = segment.getCommonPrefixLength(actualGroupingString); + bool groupingStringMatch = groupingOverlap == actualGroupingString.length(); + + hasPartialPrefix = (decimalOverlap == segment.length()) || (groupingOverlap == segment.length()); + + if (!seenDecimal && !groupingStringMatch && + (decimalStringMatch || (!seenDecimal && decimalUniSet->contains(cp)))) { + // matched a decimal separator + if (requireGroupingMatch) { + if (backupOffset != -1 || (seenGrouping && groupedDigitCount != grouping1)) { + strictFail = true; + break; + } + } + + // If we're only parsing integers, then don't parse this one. + if (integerOnly) { + break; + } + + seenDecimal = true; + if (!decimalStringMatch) { + actualDecimalString = UnicodeString(cp); + } + segment.adjustOffset(actualDecimalString.length()); + result.setCharsConsumed(segment); + result.flags |= FLAG_HAS_DECIMAL_SEPARATOR; + continue; + } + + if (!groupingDisabled && !decimalStringMatch && + (groupingStringMatch || (!seenGrouping && groupingUniSet->contains(cp)))) { + // matched a grouping separator + if (requireGroupingMatch) { + if (groupedDigitCount == 0) { + // leading group + strictFail = true; + break; + } else if (backupOffset != -1) { + // two group separators in a row + break; + } + } + + if (fractionGroupingDisabled && seenDecimal) { + // Stop parsing here. + break; + } + + seenGrouping = true; + if (!groupingStringMatch) { + actualGroupingString = UnicodeString(cp); + } + backupOffset = segment.getOffset(); + segment.adjustOffset(actualGroupingString.length()); + // Note: do NOT set charsConsumed + continue; + } + + // Not a digit and not a separator + break; + } + + // Check the final grouping for validity + if (requireGroupingMatch && !seenDecimal && seenGrouping && afterFirstGrouping && + groupedDigitCount != grouping1) { + strictFail = true; + } + + if (requireGroupingMatch && strictFail) { + result = backupResult; + segment.setOffset(initialOffset); + } + + if (result.quantity.bogus && segment.getOffset() != initialOffset) { + // Strings that start with a separator but have no digits. + // We don't need a backup of ParsedNumber because no changes could have been made to it. + segment.setOffset(initialOffset); + hasPartialPrefix = true; + } + + if (!result.quantity.bogus) { + // The final separator was a decimal separator. + result.quantity.adjustMagnitude(-digitsAfterDecimal); + } + + if (exponentSign != 0 && segment.getOffset() != initialOffset) { + U_ASSERT(!result.quantity.bogus); + bool overflow = (exponent == INT32_MAX); + if (!overflow) { + result.quantity.adjustMagnitude(exponentSign * exponent); + } + if (overflow) { + if (exponentSign == -1) { + // Set to zero + result.quantity.clear(); + } else { + // Set to infinity + result.quantity.bogus = true; + result.flags |= FLAG_INFINITY; + } + } + } + + return segment.length() == 0 || hasPartialPrefix; +} + +const UnicodeSet* DecimalMatcher::getLeadCodePoints() const { + if (fLocalDigitStrings.isNull() && leadSet != nullptr) { + return new UnicodeSet(*leadSet); + } + + auto* leadCodePoints = new UnicodeSet(); + // Assumption: the sets are all single code points. + leadCodePoints->addAll(*unisets::get(unisets::DIGITS)); + leadCodePoints->addAll(*separatorSet); + if (!fLocalDigitStrings.isNull()) { + for (int i = 0; i < 10; i++) { + utils::putLeadCodePoint(fLocalDigitStrings[i], leadCodePoints); + } + } + leadCodePoints->freeze(); + return leadCodePoints; +} + + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/numparse_decimal.h b/icu4c/source/i18n/numparse_decimal.h new file mode 100644 index 0000000000..b7bda16f58 --- /dev/null +++ b/icu4c/source/i18n/numparse_decimal.h @@ -0,0 +1,69 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#ifndef __NUMPARSE_DECIMAL_H__ +#define __NUMPARSE_DECIMAL_H__ + +#include "unicode/uniset.h" +#include "numparse_types.h" + +U_NAMESPACE_BEGIN namespace numparse { +namespace impl { + +using ::icu::number::impl::Grouper; + +class DecimalMatcher : public NumberParseMatcher, public UMemory { + public: + DecimalMatcher(const DecimalFormatSymbols& symbols, const Grouper& grouper, + parse_flags_t parseFlags); + + bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; + + bool + match(StringSegment& segment, ParsedNumber& result, int8_t exponentSign, UErrorCode& status) const; + + const UnicodeSet* getLeadCodePoints() const override; + + private: + /** If true, only accept strings whose grouping sizes match the locale */ + bool requireGroupingMatch; + + /** If true, do not accept grouping separators at all */ + bool groupingDisabled; + + /** If true, do not accept fraction grouping separators */ + bool fractionGroupingDisabled; + + /** If true, do not accept numbers in the fraction */ + bool integerOnly; + + int16_t grouping1; + int16_t grouping2; + + UnicodeString groupingSeparator; + UnicodeString decimalSeparator; + + // Assumption: these sets all consist of single code points. If this assumption needs to be broken, + // fix getLeadCodePoints() as well as matching logic. Be careful of the performance impact. + const UnicodeSet* groupingUniSet; + const UnicodeSet* decimalUniSet; + const UnicodeSet* separatorSet; + const UnicodeSet* leadSet; + + // Make this class the owner of a few objects that could be allocated. + // The first two LocalPointers are used for assigning ownership only. + LocalPointer fLocalDecimalUniSet; + LocalPointer fLocalSeparatorSet; + LocalArray fLocalDigitStrings; +}; + + +} // namespace impl +} // namespace numparse +U_NAMESPACE_END + +#endif //__NUMPARSE_DECIMAL_H__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/numparse_impl.cpp b/icu4c/source/i18n/numparse_impl.cpp new file mode 100644 index 0000000000..d93c0173f4 --- /dev/null +++ b/icu4c/source/i18n/numparse_impl.cpp @@ -0,0 +1,113 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT + +#include "number_types.h" +#include "number_patternstring.h" +#include "numparse_types.h" +#include "numparse_impl.h" +#include "numparse_symbols.h" +#include "numparse_decimal.h" +#include "unicode/numberformatter.h" + +using namespace icu; +using namespace icu::number; +using namespace icu::number::impl; +using namespace icu::numparse; +using namespace icu::numparse::impl; + + +NumberParserImpl* +NumberParserImpl::createSimpleParser(const Locale& locale, const UnicodeString& patternString, + parse_flags_t parseFlags, UErrorCode& status) { + + auto* parser = new NumberParserImpl(parseFlags, true); + DecimalFormatSymbols symbols(locale, status); + +// IgnorablesMatcher* ignorables = IgnorablesMatcher.getDefault(); +// +// MatcherFactory factory = new MatcherFactory(); +// factory.currency = Currency.getInstance("USD"); +// factory.symbols = symbols; +// factory.ignorables = ignorables; +// factory.locale = locale; +// factory.parseFlags = parseFlags; + + ParsedPatternInfo patternInfo; + PatternParser::parseToPatternInfo(patternString, patternInfo, status); +// AffixMatcher.createMatchers(patternInfo, parser, factory, ignorables, parseFlags); + + Grouper grouper = Grouper::forStrategy(UNUM_GROUPING_AUTO); + grouper.setLocaleData(patternInfo, locale); + +// parser.addMatcher({ignorables, false}); + parser->addAndAdoptMatcher(new DecimalMatcher(symbols, grouper, parseFlags)); + parser->addAndAdoptMatcher(new MinusSignMatcher(symbols, false)); +// parser.addMatcher(NanMatcher.getInstance(symbols, parseFlags)); +// parser.addMatcher(ScientificMatcher.getInstance(symbols, grouper, parseFlags)); +// parser.addMatcher(CurrencyTrieMatcher.getInstance(locale)); +// parser.addMatcher(new RequireNumberMatcher()); + + parser->freeze(); + return parser; +} + +NumberParserImpl::NumberParserImpl(parse_flags_t parseFlags, bool computeLeads) + : fParseFlags(parseFlags), fComputeLeads(computeLeads) { +} + +NumberParserImpl::~NumberParserImpl() { + for (int32_t i = 0; i < fNumMatchers; i++) { + delete (fMatchers[i]); + if (fComputeLeads) { + delete (fLeads[i]); + } + } + fNumMatchers = 0; +} + +void NumberParserImpl::addAndAdoptMatcher(const NumberParseMatcher* matcher) { + if (fNumMatchers + 1 > fMatchers.getCapacity()) { + fMatchers.resize(fNumMatchers * 2, fNumMatchers); + if (fComputeLeads) { + // The two arrays should grow in tandem: + U_ASSERT(fNumMatchers >= fLeads.getCapacity()); + fLeads.resize(fNumMatchers * 2, fNumMatchers); + } + } + + fMatchers[fNumMatchers] = matcher; + + if (fComputeLeads) { + fLeads[fNumMatchers] = matcher->getLeadCodePoints(); + } + + fNumMatchers++; +} + +void NumberParserImpl::freeze() { + fFrozen = true; +} + +//void +//NumberParserImpl::parse(const UnicodeString& input, int32_t start, bool greedy, ParsedNumber& result, +// UErrorCode& status) const { +// U_ASSERT(frozen); +// // TODO: Check start >= 0 and start < input.length() +// StringSegment segment(utils::maybeFold(input, parseFlags)); +// segment.adjustOffset(start); +// if (greedy) { +// parseGreedyRecursive(segment, result); +// } else { +// parseLongestRecursive(segment, result); +// } +// for (NumberParseMatcher matcher : matchers) { +// matcher.postProcess(result); +// } +//} + + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/numparse_impl.h b/icu4c/source/i18n/numparse_impl.h new file mode 100644 index 0000000000..2ded607d82 --- /dev/null +++ b/icu4c/source/i18n/numparse_impl.h @@ -0,0 +1,56 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#ifndef __NUMPARSE_IMPL_H__ +#define __NUMPARSE_IMPL_H__ + +#include "numparse_types.h" +#include "unicode/uniset.h" + +U_NAMESPACE_BEGIN namespace numparse { +namespace impl { + +class NumberParserImpl { + public: + static NumberParserImpl* createSimpleParser(const Locale& locale, const UnicodeString& patternString, + parse_flags_t parseFlags, UErrorCode& status); + + void addAndAdoptMatcher(const NumberParseMatcher* matcher); + + void freeze(); + + void parse(const UnicodeString& input, bool greedy, ParsedNumber& result, UErrorCode& status) const; + + void parse(const UnicodeString& input, int32_t start, bool greedy, ParsedNumber& result, + UErrorCode& status) const; + + UnicodeString toString() const; + + private: + parse_flags_t fParseFlags; + int32_t fNumMatchers = 0; + // NOTE: The stack capacity for fMatchers and fLeads should be the same + MaybeStackArray fMatchers; + MaybeStackArray fLeads; + bool fComputeLeads; + bool fFrozen = false; + + NumberParserImpl(parse_flags_t parseFlags, bool computeLeads); + + ~NumberParserImpl(); + + void parseGreedyRecursive(StringSegment& segment, ParsedNumber& result) const; + + void parseLongestRecursive(StringSegment& segment, ParsedNumber& result) const; +}; + + +} // namespace impl +} // namespace numparse +U_NAMESPACE_END + +#endif //__NUMPARSE_IMPL_H__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/numparse_symbols.cpp b/icu4c/source/i18n/numparse_symbols.cpp new file mode 100644 index 0000000000..8d1631256c --- /dev/null +++ b/icu4c/source/i18n/numparse_symbols.cpp @@ -0,0 +1,95 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT + +#include "numparse_types.h" +#include "numparse_symbols.h" +#include "numparse_utils.h" + +using namespace icu; +using namespace icu::numparse; +using namespace icu::numparse::impl; + + +SymbolMatcher::SymbolMatcher(const UnicodeString& symbolString, unisets::Key key) { + fUniSet = unisets::get(key); + fOwnsUniSet = false; + if (fUniSet->contains(symbolString)) { + fString.setToBogus(); + } else { + fString = symbolString; + } +} + +SymbolMatcher::~SymbolMatcher() { + if (fOwnsUniSet) { + delete fUniSet; + fUniSet = nullptr; + } +} + +const UnicodeSet* SymbolMatcher::getSet() { + return fUniSet; +} + +bool SymbolMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCode&) const { + // Smoke test first; this matcher might be disabled. + if (isDisabled(result)) { + return false; + } + + // Test the string first in order to consume trailing chars greedily. + int overlap = 0; + if (!fString.isEmpty()) { + overlap = segment.getCommonPrefixLength(fString); + if (overlap == fString.length()) { + segment.adjustOffset(fString.length()); + accept(segment, result); + return false; + } + } + + int cp = segment.getCodePoint(); + if (cp != -1 && fUniSet->contains(cp)) { + segment.adjustOffset(U16_LENGTH(cp)); + accept(segment, result); + return false; + } + + return overlap == segment.length(); +} + +const UnicodeSet* SymbolMatcher::getLeadCodePoints() const { + if (fString.isEmpty()) { + // Assumption: for sets from UnicodeSetStaticCache, uniSet == leadCodePoints. + return new UnicodeSet(*fUniSet); + } + + UnicodeSet* leadCodePoints = new UnicodeSet(); + utils::putLeadCodePoints(fUniSet, leadCodePoints); + utils::putLeadCodePoint(fString, leadCodePoints); + leadCodePoints->freeze(); + return leadCodePoints; +} + + +MinusSignMatcher::MinusSignMatcher(const DecimalFormatSymbols& dfs, bool allowTrailing) : SymbolMatcher( + dfs.getConstSymbol(DecimalFormatSymbols::kMinusSignSymbol), + unisets::MINUS_SIGN), fAllowTrailing(allowTrailing) { +} + +bool MinusSignMatcher::isDisabled(const ParsedNumber& result) const { + return 0 != (result.flags & FLAG_NEGATIVE) || + (fAllowTrailing ? false : result.seenNumber()); +} + +void MinusSignMatcher::accept(StringSegment& segment, ParsedNumber& result) const { + result.flags |= FLAG_NEGATIVE; + result.setCharsConsumed(segment); +} + + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/numparse_symbols.h b/icu4c/source/i18n/numparse_symbols.h new file mode 100644 index 0000000000..d730ef5753 --- /dev/null +++ b/icu4c/source/i18n/numparse_symbols.h @@ -0,0 +1,60 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#ifndef __NUMPARSE_SYMBOLS_H__ +#define __NUMPARSE_SYMBOLS_H__ + +#include "numparse_types.h" +#include "unicode/uniset.h" +#include "numparse_unisets.h" + +U_NAMESPACE_BEGIN namespace numparse { +namespace impl { + + +class SymbolMatcher : public NumberParseMatcher, public UMemory { + public: + ~SymbolMatcher() override; + + const UnicodeSet* getSet(); + + bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; + + const UnicodeSet* getLeadCodePoints() const override; + + virtual bool isDisabled(const ParsedNumber& result) const = 0; + + virtual void accept(StringSegment& segment, ParsedNumber& result) const = 0; + + protected: + UnicodeString fString; + const UnicodeSet* fUniSet; + bool fOwnsUniSet; + + SymbolMatcher(const UnicodeString& symbolString, unisets::Key key); +}; + + +class MinusSignMatcher : public SymbolMatcher { + public: + MinusSignMatcher(const DecimalFormatSymbols& dfs, bool allowTrailing); + + protected: + bool isDisabled(const ParsedNumber& result) const override; + + void accept(StringSegment& segment, ParsedNumber& result) const override; + + private: + bool fAllowTrailing; +}; + + +} // namespace impl +} // namespace numparse +U_NAMESPACE_END + +#endif //__NUMPARSE_SYMBOLS_H__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/numparse_types.h b/icu4c/source/i18n/numparse_types.h index 92957204ba..fe8a565247 100644 --- a/icu4c/source/i18n/numparse_types.h +++ b/icu4c/source/i18n/numparse_types.h @@ -17,6 +17,42 @@ namespace impl { class StringSegment; class ParsedNumber; +typedef int32_t result_flags_t; +typedef int32_t parse_flags_t; + +/** Flags for the type result_flags_t */ +enum ResultFlags { + FLAG_NEGATIVE = 0x0001, + FLAG_PERCENT = 0x0002, + FLAG_PERMILLE = 0x0004, + FLAG_HAS_EXPONENT = 0x0008, + FLAG_HAS_DEFAULT_CURRENCY = 0x0010, + FLAG_HAS_DECIMAL_SEPARATOR = 0x0020, + FLAG_NAN = 0x0040, + FLAG_INFINITY = 0x0080, + FLAG_FAIL = 0x0100, +}; + +/** Flags for the type parse_flags_t */ +enum ParseFlags { + PARSE_FLAG_IGNORE_CASE = 0x0001, + PARSE_FLAG_MONETARY_SEPARATORS = 0x0002, + PARSE_FLAG_STRICT_SEPARATORS = 0x0004, + PARSE_FLAG_STRICT_GROUPING_SIZE = 0x0008, + PARSE_FLAG_INTEGER_ONLY = 0x0010, + PARSE_FLAG_GROUPING_DISABLED = 0x0020, + PARSE_FLAG_FRACTION_GROUPING_DISABLED = 0x0040, + PARSE_FLAG_INCLUDE_UNPAIRED_AFFIXES = 0x0080, + PARSE_FLAG_USE_FULL_AFFIXES = 0x0100, + PARSE_FLAG_EXACT_AFFIX = 0x0200, + PARSE_FLAG_PLUS_SIGN_ALLOWED = 0x0400, +}; + +//template +//struct MaybeNeedsAdoption { +// T* ptr; +// bool needsAdoption; +//}; /** * Struct-like class to hold the results of a parsing routine. @@ -25,17 +61,6 @@ class ParsedNumber; */ class ParsedNumber { public: - enum ParsedNumberFlags { - FLAG_NEGATIVE = 0x0001, - FLAG_PERCENT = 0x0002, - FLAG_PERMILLE = 0x0004, - FLAG_HAS_EXPONENT = 0x0008, - FLAG_HAS_DEFAULT_CURRENCY = 0x0010, - FLAG_HAS_DECIMAL_SEPARATOR = 0x0020, - FLAG_NAN = 0x0040, - FLAG_INFINITY = 0x0080, - FLAG_FAIL = 0x0100, - }; /** * The numerical value that was parsed. @@ -51,9 +76,9 @@ class ParsedNumber { int32_t charEnd; /** - * Boolean flags (see constants below). + * Boolean flags (see constants above). */ - int32_t flags; + result_flags_t flags; /** * The pattern string corresponding to the prefix that got consumed. @@ -204,15 +229,17 @@ class NumberParseMatcher { * @return Whether this matcher thinks there may be more interesting chars beyond the end of the * string segment. */ - virtual bool match(StringSegment& segment, ParsedNumber& result) const = 0; + virtual bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const = 0; /** * Should return a set representing all possible chars (UTF-16 code units) that could be the first * char that this matcher can consume. This method is only called during construction phase, and its * return value is used to skip this matcher unless a segment begins with a char in this set. To make * this matcher always run, return {@link UnicodeSet#ALL_CODE_POINTS}. + * + * The returned UnicodeSet needs adoption! */ - virtual UnicodeSet getLeadCodePoints() const = 0; + virtual const UnicodeSet* getLeadCodePoints() const = 0; /** * Method called at the end of a parse, after all matchers have failed to consume any more chars. @@ -222,7 +249,9 @@ class NumberParseMatcher { * @param result * The data structure to store results. */ - virtual void postProcess(ParsedNumber& result) const = 0; + virtual void postProcess(ParsedNumber&) const { + // Default implementation: no-op + }; }; diff --git a/icu4c/source/i18n/numparse_utils.h b/icu4c/source/i18n/numparse_utils.h new file mode 100644 index 0000000000..a25f9ef9df --- /dev/null +++ b/icu4c/source/i18n/numparse_utils.h @@ -0,0 +1,38 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#ifndef __NUMPARSE_UTILS_H__ +#define __NUMPARSE_UTILS_H__ + +#include "numparse_types.h" +#include "unicode/uniset.h" + +U_NAMESPACE_BEGIN namespace numparse { +namespace impl { +namespace utils { + + +inline static void putLeadCodePoints(const UnicodeSet* input, UnicodeSet* output) { + for (int32_t i = 0; i < input->getRangeCount(); i++) { + output->add(input->getRangeStart(i), input->getRangeEnd(i)); + } + // TODO: ANDY: How to iterate over the strings in ICU4C UnicodeSet? +} + +inline static void putLeadCodePoint(const UnicodeString& input, UnicodeSet* output) { + if (!input.isEmpty()) { + output->add(input.char32At(0)); + } +} + + +} // namespace utils +} // namespace impl +} // namespace numparse +U_NAMESPACE_END + +#endif //__NUMPARSE_UTILS_H__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/unicode/numberformatter.h b/icu4c/source/i18n/unicode/numberformatter.h index be4593309e..a3d80d2870 100644 --- a/icu4c/source/i18n/unicode/numberformatter.h +++ b/icu4c/source/i18n/unicode/numberformatter.h @@ -378,7 +378,18 @@ typedef enum UNumberDecimalSeparatorDisplay { UNUM_DECIMAL_SEPARATOR_COUNT } UNumberDecimalMarkDisplay; -U_NAMESPACE_BEGIN namespace number { // icu::number +U_NAMESPACE_BEGIN + +namespace numparse { +namespace impl { + +// Forward declarations: +class NumberParserImpl; + +} +} + +namespace number { // icu::number // Forward declarations: class UnlocalizedNumberFormatter; @@ -1311,6 +1322,12 @@ class U_I18N_API Grouper : public UMemory { Grouper(int16_t grouping1, int16_t grouping2, int16_t minGrouping) : fGrouping1(grouping1), fGrouping2(grouping2), fMinGrouping(minGrouping) {} + /** @internal */ + int16_t getPrimary() const; + + /** @internal */ + int16_t getSecondary() const; + private: /** * The grouping sizes, with the following special values: @@ -1349,6 +1366,9 @@ class U_I18N_API Grouper : public UMemory { // To allow NumberFormatterImpl to access isBogus() and perform other operations: friend class NumberFormatterImpl; + + // To allow NumberParserImpl to perform setLocaleData(): + friend class ::icu::numparse::impl::NumberParserImpl; }; /** @internal */ diff --git a/icu4c/source/test/intltest/Makefile.in b/icu4c/source/test/intltest/Makefile.in index ed1aa256b1..c67f0006ca 100644 --- a/icu4c/source/test/intltest/Makefile.in +++ b/icu4c/source/test/intltest/Makefile.in @@ -64,7 +64,8 @@ scientificnumberformattertest.o datadrivennumberformattestsuite.o \ numberformattesttuple.o numberformat2test.o pluralmaptest.o \ numbertest_affixutils.o numbertest_api.o numbertest_decimalquantity.o \ numbertest_modifiers.o numbertest_patternmodifier.o numbertest_patternstring.o \ -numbertest_stringbuilder.o numbertest_stringsegment.o numbertest_unisets.o +numbertest_stringbuilder.o numbertest_stringsegment.o numbertest_unisets.o \ +numbertest_parse.o DEPS = $(OBJECTS:.o=.d) diff --git a/icu4c/source/test/intltest/numbertest.h b/icu4c/source/test/intltest/numbertest.h index 272f87c882..5da55bbe9c 100644 --- a/icu4c/source/test/intltest/numbertest.h +++ b/icu4c/source/test/intltest/numbertest.h @@ -207,6 +207,16 @@ class UniSetsTest : public IntlTest { const UnicodeSet& set, UChar32 cp); }; +class NumberParserTest : public IntlTest { + public: + void testBasic(); + void testLocaleFi(); + void testSeriesMatcher(); + void testGroupingDisabled(); + + void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0); +}; + // NOTE: This macro is identical to the one in itformat.cpp #define TESTCLASS(id, TestClass) \ @@ -237,6 +247,7 @@ class NumberTest : public IntlTest { TESTCLASS(6, NumberStringBuilderTest); TESTCLASS(7, StringSegmentTest); TESTCLASS(8, UniSetsTest); + TESTCLASS(9, NumberParserTest); default: name = ""; break; // needed to end loop } } diff --git a/icu4c/source/test/intltest/numbertest_parse.cpp b/icu4c/source/test/intltest/numbertest_parse.cpp new file mode 100644 index 0000000000..b0d2fe8cf1 --- /dev/null +++ b/icu4c/source/test/intltest/numbertest_parse.cpp @@ -0,0 +1,144 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT + +#include "numbertest.h" +#include "numparse_impl.h" +#include "numparse_unisets.h" +#include "unicode/dcfmtsym.h" +#include "unicode/testlog.h" + +#include + +using icu::numparse::impl::unisets::get; + +void NumberParserTest::runIndexedTest(int32_t index, UBool exec, const char*& name, char*) { + if (exec) { + logln("TestSuite NumberParserTest: "); + } + TESTCASE_AUTO_BEGIN; + TESTCASE_AUTO(testBasic); + TESTCASE_AUTO_END; +} + +void NumberParserTest::testBasic() { + IcuTestErrorCode status(*this, "testBasic"); + + static const struct TestCase { + int32_t flags; + const char16_t* inputString; + const char16_t* patternString; + int32_t expectedCharsConsumed; + double expectedResultDouble; + } cases[] = {{3, u"51423", u"0", 5, 51423.}, + {3, u"51423x", u"0", 5, 51423.}, + {3, u" 51423", u"0", 6, 51423.}, + {3, u"51423 ", u"0", 5, 51423.}, + {3, u"𝟱𝟭𝟰𝟮𝟯", u"0", 10, 51423.}, + {3, u"𝟱𝟭𝟰𝟮𝟯x", u"0", 10, 51423.}, + {3, u" 𝟱𝟭𝟰𝟮𝟯", u"0", 11, 51423.}, + {3, u"𝟱𝟭𝟰𝟮𝟯 ", u"0", 10, 51423.}, + {7, u"𝟱𝟭,𝟰𝟮𝟯", u"#,##,##0", 11, 51423.}, + {7, u"𝟳,𝟴𝟵,𝟱𝟭,𝟰𝟮𝟯", u"#,##,##0", 19, 78951423.}, + {7, u"𝟳𝟴,𝟵𝟱𝟭.𝟰𝟮𝟯", u"#,##,##0", 18, 78951.423}, + {7, u"𝟳𝟴,𝟬𝟬𝟬", u"#,##,##0", 11, 78000.}, + {7, u"𝟳𝟴,𝟬𝟬𝟬.𝟬𝟬𝟬", u"#,##,##0", 18, 78000.}, + {7, u"𝟳𝟴,𝟬𝟬𝟬.𝟬𝟮𝟯", u"#,##,##0", 18, 78000.023}, + {7, u"𝟳𝟴.𝟬𝟬𝟬.𝟬𝟮𝟯", u"#,##,##0", 11, 78.}, + {3, u"-𝟱𝟭𝟰𝟮𝟯", u"0", 11, -51423.}, + {3, u"-𝟱𝟭𝟰𝟮𝟯-", u"0", 11, -51423.}, + {3, u"a51423US dollars", u"a0¤¤¤", 16, 51423.}, + {3, u"a 51423 US dollars", u"a0¤¤¤", 18, 51423.}, + {3, u"514.23 USD", u"¤0", 10, 514.23}, + {3, u"514.23 GBP", u"¤0", 10, 514.23}, + {3, u"a 𝟱𝟭𝟰𝟮𝟯 b", u"a0b", 14, 51423.}, + {3, u"-a 𝟱𝟭𝟰𝟮𝟯 b", u"a0b", 15, -51423.}, + {3, u"a -𝟱𝟭𝟰𝟮𝟯 b", u"a0b", 15, -51423.}, + {3, u"𝟱𝟭𝟰𝟮𝟯", u"[0];(0)", 10, 51423.}, + {3, u"[𝟱𝟭𝟰𝟮𝟯", u"[0];(0)", 11, 51423.}, + {3, u"𝟱𝟭𝟰𝟮𝟯]", u"[0];(0)", 11, 51423.}, + {3, u"[𝟱𝟭𝟰𝟮𝟯]", u"[0];(0)", 12, 51423.}, + {3, u"(𝟱𝟭𝟰𝟮𝟯", u"[0];(0)", 11, -51423.}, + {3, u"𝟱𝟭𝟰𝟮𝟯)", u"[0];(0)", 11, -51423.}, + {3, u"(𝟱𝟭𝟰𝟮𝟯)", u"[0];(0)", 12, -51423.}, + {3, u"𝟱𝟭𝟰𝟮𝟯", u"{0};{0}", 10, 51423.}, + {3, u"{𝟱𝟭𝟰𝟮𝟯", u"{0};{0}", 11, 51423.}, + {3, u"𝟱𝟭𝟰𝟮𝟯}", u"{0};{0}", 11, 51423.}, + {3, u"{𝟱𝟭𝟰𝟮𝟯}", u"{0};{0}", 12, 51423.}, + {1, u"a40b", u"a0'0b'", 3, 40.}, // greedy code path thinks "40" is the number + {2, u"a40b", u"a0'0b'", 4, 4.}, // slow code path finds the suffix "0b" + {3, u"𝟱.𝟭𝟰𝟮E𝟯", u"0", 12, 5142.}, + {3, u"𝟱.𝟭𝟰𝟮E-𝟯", u"0", 13, 0.005142}, + {3, u"𝟱.𝟭𝟰𝟮e-𝟯", u"0", 13, 0.005142}, + {7, u"5,142.50 Canadian dollars", u"#,##,##0 ¤¤¤", 25, 5142.5}, + {3, u"a$ b5", u"a ¤ b0", 5, 5.0}, + {3, u"📺1.23", u"📺0;📻0", 6, 1.23}, + {3, u"📻1.23", u"📺0;📻0", 6, -1.23}, + {3, u".00", u"0", 3, 0.0}, + {3, u" 0", u"a0", 31, 0.0}, // should not hang + {3, u"NaN", u"0", 3, NAN}, + {3, u"NaN E5", u"0", 3, NAN}, + {3, u"0", u"0", 1, 0.0}}; + + parse_flags_t parseFlags = PARSE_FLAG_IGNORE_CASE | PARSE_FLAG_INCLUDE_UNPAIRED_AFFIXES; + for (auto cas : cases) { + UnicodeString inputString(cas.inputString); + UnicodeString patternString(cas.patternString); + const NumberParserImpl* parser = NumberParserImpl::createSimpleParser( + Locale("en"), patternString, parseFlags, status); + UnicodeString message = + UnicodeString("Input <") + inputString + UnicodeString("> Parser ") + parser->toString(); + + if (0 != (cas.flags & 0x01)) { + // Test greedy code path + ParsedNumber resultObject; + parser->parse(inputString, true, resultObject, status); + assertTrue("Greedy Parse failed: " + message, resultObject.success()); + assertEquals( + "Greedy Parse failed: " + message, cas.expectedCharsConsumed, resultObject.charEnd); + assertEquals( + "Greedy Parse failed: " + message, + cas.expectedResultDouble, + resultObject.getDouble()); + } + + if (0 != (cas.flags & 0x02)) { + // Test slow code path + ParsedNumber resultObject; + parser->parse(inputString, false, resultObject, status); + assertTrue("Non-Greedy Parse failed: " + message, resultObject.success()); + assertEquals( + "Non-Greedy Parse failed: " + message, + cas.expectedCharsConsumed, + resultObject.charEnd); + assertEquals( + "Non-Greedy Parse failed: " + message, + cas.expectedResultDouble, + resultObject.getDouble()); + } + + if (0 != (cas.flags & 0x04)) { + // Test with strict separators + parser = NumberParserImpl::createSimpleParser( + Locale("en"), + patternString, + parseFlags | PARSE_FLAG_STRICT_GROUPING_SIZE, + status); + ParsedNumber resultObject; + parser->parse(inputString, true, resultObject, status); + assertTrue("Strict Parse failed: " + message, resultObject.success()); + assertEquals( + "Strict Parse failed: " + message, cas.expectedCharsConsumed, resultObject.charEnd); + assertEquals( + "Strict Parse failed: " + message, + cas.expectedResultDouble, + resultObject.getDouble()); + } + } +} + + +#endif diff --git a/icu4c/source/test/intltest/numbertest_stringbuilder.cpp b/icu4c/source/test/intltest/numbertest_stringbuilder.cpp index 76d27e1b12..cdc3836173 100644 --- a/icu4c/source/test/intltest/numbertest_stringbuilder.cpp +++ b/icu4c/source/test/intltest/numbertest_stringbuilder.cpp @@ -77,7 +77,7 @@ void NumberStringBuilderTest::testInsertAppendUnicodeString() { } void NumberStringBuilderTest::testSplice() { - const struct TestCase { + static const struct TestCase { const char16_t* input; const int32_t startThis; const int32_t endThis; diff --git a/icu4c/source/test/intltest/numbertest_unisets.cpp b/icu4c/source/test/intltest/numbertest_unisets.cpp index a41f3f6efb..ed7fb08d83 100644 --- a/icu4c/source/test/intltest/numbertest_unisets.cpp +++ b/icu4c/source/test/intltest/numbertest_unisets.cpp @@ -9,9 +9,6 @@ #include "numparse_unisets.h" #include "unicode/dcfmtsym.h" -#include -#include - using icu::numparse::impl::unisets::get; void UniSetsTest::runIndexedTest(int32_t index, UBool exec, const char*&name, char*) { diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/DecimalMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/DecimalMatcher.java index b965263af1..8ba8eb79e5 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/DecimalMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/DecimalMatcher.java @@ -27,9 +27,6 @@ public class DecimalMatcher implements NumberParseMatcher { /** If true, do not accept numbers in the fraction */ private final boolean integerOnly; - /** If true, save the result as an exponent instead of a quantity in the ParsedNumber */ - private final boolean isScientific; - private final int grouping1; private final int grouping2; @@ -97,20 +94,28 @@ public class DecimalMatcher implements NumberParseMatcher { fractionGroupingDisabled = 0 != (parseFlags & ParsingUtils.PARSE_FLAG_FRACTION_GROUPING_DISABLED); integerOnly = 0 != (parseFlags & ParsingUtils.PARSE_FLAG_INTEGER_ONLY); - isScientific = 0 != (parseFlags & ParsingUtils.PARSE_FLAG_DECIMAL_SCIENTIFIC); grouping1 = grouper.getPrimary(); grouping2 = grouper.getSecondary(); } @Override public boolean match(StringSegment segment, ParsedNumber result) { - return match(segment, result, false); + return match(segment, result, 0); } - public boolean match(StringSegment segment, ParsedNumber result, boolean negativeExponent) { - if (result.seenNumber() && !isScientific) { + /** + * @param exponentSign + * -1 means a negative exponent; +1 means a positive exponent; 0 means NO exponent. If -1 + * or +1, the number will be saved by scaling the pre-existing DecimalQuantity in the + * ParsedNumber. If 0, a new DecimalQuantity will be created to store the number. + */ + public boolean match(StringSegment segment, ParsedNumber result, int exponentSign) { + if (result.seenNumber() && exponentSign == 0) { // A number has already been consumed. return false; + } else if (exponentSign != 0) { + // scientific notation always comes after the number + assert result.quantity != null; } ParsedNumber backupResult = null; @@ -181,7 +186,7 @@ public class DecimalMatcher implements NumberParseMatcher { } // Save the digit in the DecimalQuantity or scientific adjustment. - if (isScientific) { + if (exponentSign != 0) { int nextExponent = digit + exponent * 10; if (nextExponent < exponent) { // Overflow @@ -272,11 +277,6 @@ public class DecimalMatcher implements NumberParseMatcher { break; } - // if (backupOffset != -1) { - // segment.setOffset(backupOffset); - // hasPartialPrefix = true; - // } - // Check the final grouping for validity if (requireGroupingMatch && !seenDecimal @@ -303,18 +303,17 @@ public class DecimalMatcher implements NumberParseMatcher { result.quantity.adjustMagnitude(-digitsAfterDecimal); } - if (isScientific && segment.getOffset() != initialOffset) { - assert result.quantity != null; // scientific notation always comes after the number + if (exponentSign != 0 && segment.getOffset() != initialOffset) { boolean overflow = (exponent == Integer.MAX_VALUE); if (!overflow) { try { - result.quantity.adjustMagnitude(negativeExponent ? -exponent : exponent); + result.quantity.adjustMagnitude(exponentSign * exponent); } catch (ArithmeticException e) { overflow = true; } } if (overflow) { - if (negativeExponent) { + if (exponentSign == -1) { // Set to zero result.quantity.clear(); } else { diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java index 55a046a43a..f34effcf67 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java @@ -266,28 +266,27 @@ public class NumberParserImpl { private final int parseFlags; private final List matchers; - private final List leadCodePointses; + private final List leads; private Comparator comparator; private boolean frozen; /** * Creates a new, empty parser. * - * @param ignoreCase - * If true, perform case-folding. This parameter needs to go into the constructor because - * its value is used during the construction of the matcher chain. - * @param optimize + * @param parseFlags + * Settings for constructing the parser. + * @param computeLeads * If true, compute "lead chars" UnicodeSets for the matchers. This reduces parsing * runtime but increases construction runtime. If the parser is going to be used only once * or twice, set this to false; if it is going to be used hundreds of times, set it to * true. */ - public NumberParserImpl(int parseFlags, boolean optimize) { + public NumberParserImpl(int parseFlags, boolean computeLeads) { matchers = new ArrayList(); - if (optimize) { - leadCodePointses = new ArrayList(); + if (computeLeads) { + leads = new ArrayList(); } else { - leadCodePointses = null; + leads = null; } comparator = ParsedNumber.COMPARATOR; // default value this.parseFlags = parseFlags; @@ -297,21 +296,21 @@ public class NumberParserImpl { public void addMatcher(NumberParseMatcher matcher) { assert !frozen; this.matchers.add(matcher); - if (leadCodePointses != null) { + if (leads != null) { UnicodeSet leadCodePoints = matcher.getLeadCodePoints(); assert leadCodePoints.isFrozen(); - this.leadCodePointses.add(leadCodePoints); + this.leads.add(leadCodePoints); } } public void addMatchers(Collection matchers) { assert !frozen; this.matchers.addAll(matchers); - if (leadCodePointses != null) { + if (leads != null) { for (NumberParseMatcher matcher : matchers) { UnicodeSet leadCodePoints = matcher.getLeadCodePoints(); assert leadCodePoints.isFrozen(); - this.leadCodePointses.add(leadCodePoints); + this.leads.add(leadCodePoints); } } } @@ -366,7 +365,7 @@ public class NumberParserImpl { int initialOffset = segment.getOffset(); int leadCp = segment.getCodePoint(); for (int i = 0; i < matchers.size(); i++) { - if (leadCodePointses != null && !leadCodePointses.get(i).contains(leadCp)) { + if (leads != null && !leads.get(i).contains(leadCp)) { continue; } NumberParseMatcher matcher = matchers.get(i); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsingUtils.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsingUtils.java index c4a0005c0e..c4d11b8b4e 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsingUtils.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsingUtils.java @@ -17,12 +17,11 @@ public class ParsingUtils { public static final int PARSE_FLAG_STRICT_GROUPING_SIZE = 0x0008; public static final int PARSE_FLAG_INTEGER_ONLY = 0x0010; public static final int PARSE_FLAG_GROUPING_DISABLED = 0x0020; - public static final int PARSE_FLAG_DECIMAL_SCIENTIFIC = 0x0040; + public static final int PARSE_FLAG_FRACTION_GROUPING_DISABLED = 0x0040; public static final int PARSE_FLAG_INCLUDE_UNPAIRED_AFFIXES = 0x0080; public static final int PARSE_FLAG_USE_FULL_AFFIXES = 0x0100; public static final int PARSE_FLAG_EXACT_AFFIX = 0x0200; public static final int PARSE_FLAG_PLUS_SIGN_ALLOWED = 0x0400; - public static final int PARSE_FLAG_FRACTION_GROUPING_DISABLED = 0x0800; public static void putLeadCodePoints(UnicodeSet input, UnicodeSet output) { for (EntryRange range : input.ranges()) { diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ScientificMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ScientificMatcher.java index a6c053af7e..1f51ae3e9c 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ScientificMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ScientificMatcher.java @@ -27,13 +27,15 @@ public class ScientificMatcher implements NumberParseMatcher { exponentSeparatorString = ParsingUtils.maybeFold(symbols.getExponentSeparator(), parseFlags); exponentMatcher = DecimalMatcher.getInstance(symbols, grouper, - ParsingUtils.PARSE_FLAG_DECIMAL_SCIENTIFIC | ParsingUtils.PARSE_FLAG_INTEGER_ONLY); + ParsingUtils.PARSE_FLAG_INTEGER_ONLY); } @Override public boolean match(StringSegment segment, ParsedNumber result) { // Only accept scientific notation after the mantissa. - if (!result.seenNumber()) { + // Most places use result.hasNumber(), but we need a stronger condition here (i.e., exponent is + // not well-defined after NaN or infinity). + if (result.quantity == null) { return false; } @@ -54,16 +56,16 @@ public class ScientificMatcher implements NumberParseMatcher { } // Allow a sign, and then try to match digits. - boolean minusSign = false; + int exponentSign = 1; if (UnicodeSetStaticCache.get(UnicodeSetStaticCache.Key.MINUS_SIGN).contains(leadCp)) { - minusSign = true; + exponentSign = -1; segment.adjustOffset(Character.charCount(leadCp)); } else if (UnicodeSetStaticCache.get(UnicodeSetStaticCache.Key.PLUS_SIGN).contains(leadCp)) { segment.adjustOffset(Character.charCount(leadCp)); } int digitsOffset = segment.getOffset(); - boolean digitsReturnValue = exponentMatcher.match(segment, result, minusSign); + boolean digitsReturnValue = exponentMatcher.match(segment, result, exponentSign); if (segment.getOffset() != digitsOffset) { // At least one exponent digit was matched. result.flags |= ParsedNumber.FLAG_HAS_EXPONENT; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormatSymbols.java b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormatSymbols.java index bed3481e95..07757d0b68 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormatSymbols.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormatSymbols.java @@ -1654,13 +1654,13 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { * represents a sequence of ten code points in order. * *

If the value stored here is positive, it means that the code point stored in this value - * corresponds to the digitStrings array, and zeroCodePoint can be used instead of the + * corresponds to the digitStrings array, and codePointZero can be used instead of the * digitStrings array for the purposes of efficient formatting; if -1, then digitStrings does * *not* contain a sequence of code points, and it must be used directly. * - *

It is assumed that zeroCodePoint always shadows the value in digitStrings. zeroCodePoint + *

It is assumed that codePointZero always shadows the value in digitStrings. codePointZero * should never be set directly; rather, it should be updated only when digitStrings mutates. - * That is, the flow of information is digitStrings -> zeroCodePoint, not the other way. + * That is, the flow of information is digitStrings -> codePointZero, not the other way. */ private transient int codePointZero; diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/IntlTestDecimalFormatSymbols.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/IntlTestDecimalFormatSymbols.java index 724247a585..0d6f7d0b1f 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/IntlTestDecimalFormatSymbols.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/IntlTestDecimalFormatSymbols.java @@ -296,6 +296,9 @@ public class IntlTestDecimalFormatSymbols extends TestFmwk final String[] differentDigitStrings = {"0", "b", "3", "d", "5", "ff", "7", "h", "9", "j"}; DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.ENGLISH); + if (defZero != symbols.getCodePointZero()) { + errln("ERROR: Code point zero initialize to ASCII 0"); + } symbols.setDigitStrings(osmanyaDigitStrings); if (!Arrays.equals(symbols.getDigitStrings(), osmanyaDigitStrings)) { diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java index 5bb123968b..41cc2d7935 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java @@ -3,7 +3,6 @@ package com.ibm.icu.dev.test.number; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import org.junit.Test; @@ -86,30 +85,32 @@ public class NumberParserTest { { 3, "📻1.23", "📺0;📻0", 6, -1.23 }, { 3, ".00", "0", 3, 0.0 }, { 3, " 0", "a0", 31, 0.0 }, // should not hang + { 3, "NaN", "0", 3, Double.NaN }, + { 3, "NaN E5", "0", 3, Double.NaN }, { 3, "0", "0", 1, 0.0 } }; int parseFlags = ParsingUtils.PARSE_FLAG_IGNORE_CASE | ParsingUtils.PARSE_FLAG_INCLUDE_UNPAIRED_AFFIXES; for (Object[] cas : cases) { int flags = (Integer) cas[0]; - String input = (String) cas[1]; - String pattern = (String) cas[2]; + String inputString = (String) cas[1]; + String patternString = (String) cas[2]; int expectedCharsConsumed = (Integer) cas[3]; - double resultDouble = (Double) cas[4]; + double expectedResultDouble = (Double) cas[4]; NumberParserImpl parser = NumberParserImpl - .createSimpleParser(ULocale.ENGLISH, pattern, parseFlags); - String message = "Input <" + input + "> Parser " + parser; + .createSimpleParser(ULocale.ENGLISH, patternString, parseFlags); + String message = "Input <" + inputString + "> Parser " + parser; if (0 != (flags & 0x01)) { // Test greedy code path ParsedNumber resultObject = new ParsedNumber(); - parser.parse(input, true, resultObject); - assertNotNull("Greedy Parse failed: " + message, resultObject.quantity); + parser.parse(inputString, true, resultObject); + assertTrue("Greedy Parse failed: " + message, resultObject.success()); assertEquals("Greedy Parse failed: " + message, expectedCharsConsumed, resultObject.charEnd); assertEquals("Greedy Parse failed: " + message, - resultDouble, + expectedResultDouble, resultObject.getNumber().doubleValue(), 0.0); } @@ -117,13 +118,13 @@ public class NumberParserTest { if (0 != (flags & 0x02)) { // Test slow code path ParsedNumber resultObject = new ParsedNumber(); - parser.parse(input, false, resultObject); - assertNotNull("Non-Greedy Parse failed: " + message, resultObject.quantity); + parser.parse(inputString, false, resultObject); + assertTrue("Non-Greedy Parse failed: " + message, resultObject.success()); assertEquals("Non-Greedy Parse failed: " + message, expectedCharsConsumed, resultObject.charEnd); assertEquals("Non-Greedy Parse failed: " + message, - resultDouble, + expectedResultDouble, resultObject.getNumber().doubleValue(), 0.0); } @@ -131,16 +132,16 @@ public class NumberParserTest { if (0 != (flags & 0x04)) { // Test with strict separators parser = NumberParserImpl.createSimpleParser(ULocale.ENGLISH, - pattern, + patternString, parseFlags | ParsingUtils.PARSE_FLAG_STRICT_GROUPING_SIZE); ParsedNumber resultObject = new ParsedNumber(); - parser.parse(input, true, resultObject); - assertNotNull("Strict Parse failed: " + message, resultObject.quantity); + parser.parse(inputString, true, resultObject); + assertTrue("Strict Parse failed: " + message, resultObject.success()); assertEquals("Strict Parse failed: " + message, expectedCharsConsumed, resultObject.charEnd); assertEquals("Strict Parse failed: " + message, - resultDouble, + expectedResultDouble, resultObject.getNumber().doubleValue(), 0.0); } From 31a4dfe3e443431361c6c896f0b4afc1ce084a24 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Thu, 8 Feb 2018 08:56:01 +0000 Subject: [PATCH 004/129] ICU-13574 Fixing compile errors introduced by last merge from trunk. X-SVN-Rev: 40871 --- .../impl/number/parse/NumberParserImpl.java | 33 ++----------------- .../icu/dev/test/number/NumberParserTest.java | 7 ++-- 2 files changed, 6 insertions(+), 34 deletions(-) diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java index ea3a853fc4..6fd6050442 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java @@ -31,30 +31,6 @@ import com.ibm.icu.util.ULocale; */ public class NumberParserImpl { - @Deprecated - public static NumberParserImpl removeMeWhenMerged(ULocale locale, String pattern, int parseFlags) { - NumberParserImpl parser = new NumberParserImpl(parseFlags); - DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale); - IgnorablesMatcher ignorables = IgnorablesMatcher.DEFAULT; - - MatcherFactory factory = new MatcherFactory(); - factory.currency = Currency.getInstance("USD"); - factory.symbols = symbols; - factory.ignorables = ignorables; - factory.locale = locale; - - ParsedPatternInfo patternInfo = PatternStringParser.parseToPatternInfo(pattern); - AffixMatcher.newGenerate(patternInfo, parser, factory, ignorables, parseFlags); - - Grouper grouper = Grouper.forStrategy(GroupingStrategy.AUTO).withLocaleData(locale, patternInfo); - parser.addMatcher(DecimalMatcher.getInstance(symbols, grouper, parseFlags)); - parser.addMatcher(CurrencyTrieMatcher.getInstance(locale)); - parser.addMatcher(NanMatcher.getInstance(symbols, parseFlags)); - - parser.freeze(); - return parser; - } - // TODO: Find a better place for this enum. /** Controls the set of rules for parsing a string. */ public static enum ParseMode { @@ -300,16 +276,11 @@ public class NumberParserImpl { * * @param parseFlags * Settings for constructing the parser. - * @param computeLeads - * If true, compute "lead chars" UnicodeSets for the matchers. This reduces parsing - * runtime but increases construction runtime. If the parser is going to be used only once - * or twice, set this to false; if it is going to be used hundreds of times, set it to - * true. */ public NumberParserImpl(int parseFlags) { matchers = new ArrayList(); if (0 != (parseFlags & ParsingUtils.PARSE_FLAG_OPTIMIZE)) { - leadCodePointses = new ArrayList(); + leads = new ArrayList(); } else { leads = null; } @@ -344,7 +315,7 @@ public class NumberParserImpl { leadCodePoints = leadCodePoints.cloneAsThawed().closeOver(UnicodeSet.ADD_CASE_MAPPINGS) .freeze(); } - this.leadCodePointses.add(leadCodePoints); + this.leads.add(leadCodePoints); } public void setComparator(Comparator comparator) { diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java index 726b4313b0..8c57dd6e1d 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java @@ -238,15 +238,16 @@ public class NumberParserTest { int expectedCaseSensitiveChars = (Integer) cas[2]; int expectedCaseFoldingChars = (Integer) cas[3]; - NumberParserImpl caseSensitiveParser = NumberParserImpl - .removeMeWhenMerged(ULocale.ENGLISH, patternString, ParsingUtils.PARSE_FLAG_OPTIMIZE); + NumberParserImpl caseSensitiveParser = NumberParserImpl.createSimpleParser(ULocale.ENGLISH, + patternString, + ParsingUtils.PARSE_FLAG_OPTIMIZE); ParsedNumber result = new ParsedNumber(); caseSensitiveParser.parse(inputString, true, result); assertEquals("Case-Sensitive: " + inputString + " on " + patternString, expectedCaseSensitiveChars, result.charEnd); - NumberParserImpl caseFoldingParser = NumberParserImpl.removeMeWhenMerged(ULocale.ENGLISH, + NumberParserImpl caseFoldingParser = NumberParserImpl.createSimpleParser(ULocale.ENGLISH, patternString, ParsingUtils.PARSE_FLAG_IGNORE_CASE | ParsingUtils.PARSE_FLAG_OPTIMIZE); result = new ParsedNumber(); From 8393405113305c28ed1c85f22f0fc256498a65cd Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Thu, 8 Feb 2018 09:59:35 +0000 Subject: [PATCH 005/129] ICU-13574 Basic parsing tests are passing on the pieces of code written so far, DecimalMatcher and MinusSignMatcher. X-SVN-Rev: 40872 --- icu4c/source/i18n/numparse_impl.cpp | 133 +++++++++++++++--- icu4c/source/i18n/numparse_impl.h | 4 +- icu4c/source/i18n/numparse_parsednumber.cpp | 5 + icu4c/source/i18n/numparse_stringsegment.cpp | 50 ++++++- icu4c/source/i18n/numparse_types.h | 40 +++++- .../source/test/intltest/numbertest_parse.cpp | 70 +++++---- .../intltest/numbertest_stringsegment.cpp | 10 +- .../impl/number/parse/NumberParserImpl.java | 13 +- .../icu/impl/number/parse/ParsedNumber.java | 4 + .../icu/impl/number/parse/StringSegment.java | 4 + 10 files changed, 260 insertions(+), 73 deletions(-) diff --git a/icu4c/source/i18n/numparse_impl.cpp b/icu4c/source/i18n/numparse_impl.cpp index d93c0173f4..4348d86c6d 100644 --- a/icu4c/source/i18n/numparse_impl.cpp +++ b/icu4c/source/i18n/numparse_impl.cpp @@ -13,6 +13,8 @@ #include "numparse_decimal.h" #include "unicode/numberformatter.h" +#include + using namespace icu; using namespace icu::number; using namespace icu::number::impl; @@ -92,22 +94,121 @@ void NumberParserImpl::freeze() { fFrozen = true; } -//void -//NumberParserImpl::parse(const UnicodeString& input, int32_t start, bool greedy, ParsedNumber& result, -// UErrorCode& status) const { -// U_ASSERT(frozen); -// // TODO: Check start >= 0 and start < input.length() -// StringSegment segment(utils::maybeFold(input, parseFlags)); -// segment.adjustOffset(start); -// if (greedy) { -// parseGreedyRecursive(segment, result); -// } else { -// parseLongestRecursive(segment, result); -// } -// for (NumberParseMatcher matcher : matchers) { -// matcher.postProcess(result); -// } -//} +void NumberParserImpl::parse(const UnicodeString& input, bool greedy, ParsedNumber& result, + UErrorCode& status) const { + return parse(input, 0, greedy, result, status); +} + +void +NumberParserImpl::parse(const UnicodeString& input, int32_t start, bool greedy, ParsedNumber& result, + UErrorCode& status) const { + U_ASSERT(fFrozen); + // TODO: Check start >= 0 and start < input.length() + StringSegment segment(input, fParseFlags); + segment.adjustOffset(start); + if (greedy) { + parseGreedyRecursive(segment, result, status); + } else { + parseLongestRecursive(segment, result, status); + } + for (int32_t i = 0; i < fNumMatchers; i++) { + fMatchers[i]->postProcess(result); + } +} + +void NumberParserImpl::parseGreedyRecursive(StringSegment& segment, ParsedNumber& result, + UErrorCode& status) const { + // Base Case + if (segment.length() == 0) { + return; + } + + int initialOffset = segment.getOffset(); + int leadCp = segment.getCodePoint(); + for (int32_t i = 0; i < fNumMatchers; i++) { + if (fComputeLeads && !fLeads[i]->contains(leadCp)) { + continue; + } + const NumberParseMatcher* matcher = fMatchers[i]; + matcher->match(segment, result, status); + if (U_FAILURE(status)) { + return; + } + if (segment.getOffset() != initialOffset) { + // In a greedy parse, recurse on only the first match. + parseGreedyRecursive(segment, result, status); + // The following line resets the offset so that the StringSegment says the same across + // the function + // call boundary. Since we recurse only once, this line is not strictly necessary. + segment.setOffset(initialOffset); + return; + } + } + + // NOTE: If we get here, the greedy parse completed without consuming the entire string. +} + +void NumberParserImpl::parseLongestRecursive(StringSegment& segment, ParsedNumber& result, + UErrorCode& status) const { + // Base Case + if (segment.length() == 0) { + return; + } + + // TODO: Give a nice way for the matcher to reset the ParsedNumber? + ParsedNumber initial(result); + ParsedNumber candidate; + + int initialOffset = segment.getOffset(); + for (int32_t i = 0; i < fNumMatchers; i++) { + // TODO: Check leadChars here? + const NumberParseMatcher* matcher = fMatchers[i]; + + // In a non-greedy parse, we attempt all possible matches and pick the best. + for (int32_t charsToConsume = 0; charsToConsume < segment.length();) { + charsToConsume += U16_LENGTH(segment.codePointAt(charsToConsume)); + + // Run the matcher on a segment of the current length. + candidate = initial; + segment.setLength(charsToConsume); + bool maybeMore = matcher->match(segment, candidate, status); + segment.resetLength(); + if (U_FAILURE(status)) { + return; + } + + // If the entire segment was consumed, recurse. + if (segment.getOffset() - initialOffset == charsToConsume) { + parseLongestRecursive(segment, candidate, status); + if (U_FAILURE(status)) { + return; + } + if (candidate.isBetterThan(result)) { + result = candidate; + } + } + + // Since the segment can be re-used, reset the offset. + // This does not have an effect if the matcher did not consume any chars. + segment.setOffset(initialOffset); + + // Unless the matcher wants to see the next char, continue to the next matcher. + if (!maybeMore) { + break; + } + } + } +} + +UnicodeString NumberParserImpl::toString() const { + UnicodeString result(u"", -1); + return result; +} #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/numparse_impl.h b/icu4c/source/i18n/numparse_impl.h index 2ded607d82..adb9294689 100644 --- a/icu4c/source/i18n/numparse_impl.h +++ b/icu4c/source/i18n/numparse_impl.h @@ -42,9 +42,9 @@ class NumberParserImpl { ~NumberParserImpl(); - void parseGreedyRecursive(StringSegment& segment, ParsedNumber& result) const; + void parseGreedyRecursive(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const; - void parseLongestRecursive(StringSegment& segment, ParsedNumber& result) const; + void parseLongestRecursive(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const; }; diff --git a/icu4c/source/i18n/numparse_parsednumber.cpp b/icu4c/source/i18n/numparse_parsednumber.cpp index 9db933502a..203383692f 100644 --- a/icu4c/source/i18n/numparse_parsednumber.cpp +++ b/icu4c/source/i18n/numparse_parsednumber.cpp @@ -70,6 +70,11 @@ double ParsedNumber::getDouble() const { return quantity.toDouble(); } +bool ParsedNumber::isBetterThan(const ParsedNumber& other) { + // Favor results with strictly more characters consumed. + return charEnd > other.charEnd; +} + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/numparse_stringsegment.cpp b/icu4c/source/i18n/numparse_stringsegment.cpp index ecabab5faa..3683890090 100644 --- a/icu4c/source/i18n/numparse_stringsegment.cpp +++ b/icu4c/source/i18n/numparse_stringsegment.cpp @@ -9,13 +9,16 @@ #include "numparse_stringsegment.h" #include "putilimp.h" #include "unicode/utf16.h" +#include "unicode/uniset.h" using namespace icu; using namespace icu::numparse; using namespace icu::numparse::impl; -StringSegment::StringSegment(const UnicodeString &str) : fStr(str), fStart(0), fEnd(str.length()) {} +StringSegment::StringSegment(const UnicodeString& str, parse_flags_t parseFlags) + : fStr(str), fStart(0), fEnd(str.length()), + fFoldCase(0 != (parseFlags & PARSE_FLAG_IGNORE_CASE)) {} int32_t StringSegment::getOffset() const { return fStart; @@ -29,6 +32,10 @@ void StringSegment::adjustOffset(int32_t delta) { fStart += delta; } +void StringSegment::adjustOffsetByCodePoint() { + fStart += U16_LENGTH(getCodePoint()); +} + void StringSegment::setLength(int32_t length) { fEnd = fStart + length; } @@ -64,10 +71,35 @@ UChar32 StringSegment::getCodePoint() const { } } -int32_t StringSegment::getCommonPrefixLength(const UnicodeString &other) { +bool StringSegment::matches(UChar32 otherCp) const { + return codePointsEqual(getCodePoint(), otherCp, fFoldCase); +} + +bool StringSegment::matches(const UnicodeSet& uniset) const { + // TODO: Move UnicodeSet case-folding logic here. + // TODO: Handle string matches here instead of separately. + UChar32 cp = getCodePoint(); + if (cp == -1) { + return false; + } + return uniset.contains(cp); +} + +int32_t StringSegment::getCommonPrefixLength(const UnicodeString& other) { + return getPrefixLengthInternal(other, fFoldCase); +} + +int32_t StringSegment::getCaseSensitivePrefixLength(const UnicodeString& other) { + return getPrefixLengthInternal(other, false); +} + +int32_t StringSegment::getPrefixLengthInternal(const UnicodeString& other, bool foldCase) { int32_t offset = 0; for (; offset < uprv_min(length(), other.length());) { - if (charAt(offset) != other.charAt(offset)) { + // TODO: case-fold code points, not chars + char16_t c1 = charAt(offset); + char16_t c2 = other.charAt(offset); + if (!codePointsEqual(c1, c2, foldCase)) { break; } offset++; @@ -75,5 +107,17 @@ int32_t StringSegment::getCommonPrefixLength(const UnicodeString &other) { return offset; } +bool StringSegment::codePointsEqual(UChar32 cp1, UChar32 cp2, bool foldCase) { + if (cp1 == cp2) { + return true; + } + if (!foldCase) { + return false; + } + cp1 = u_foldCase(cp1, TRUE); + cp2 = u_foldCase(cp2, TRUE); + return cp1 == cp2; +} + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/numparse_types.h b/icu4c/source/i18n/numparse_types.h index fe8a565247..5280c41fec 100644 --- a/icu4c/source/i18n/numparse_types.h +++ b/icu4c/source/i18n/numparse_types.h @@ -130,6 +130,8 @@ class ParsedNumber { bool seenNumber() const; double getDouble() const; + + bool isBetterThan(const ParsedNumber& other); }; @@ -141,7 +143,7 @@ class ParsedNumber { */ class StringSegment : public UMemory, public ::icu::number::impl::CharSequence { public: - explicit StringSegment(const UnicodeString& str); + explicit StringSegment(const UnicodeString& str, parse_flags_t parseFlags); int32_t getOffset() const; @@ -157,6 +159,11 @@ class StringSegment : public UMemory, public ::icu::number::impl::CharSequence { */ void adjustOffset(int32_t delta); + /** + * Adjusts the offset by the width of the current code point, either 1 or 2 chars. + */ + void adjustOffsetByCodePoint(); + void setLength(int32_t length); void resetLength(); @@ -172,20 +179,51 @@ class StringSegment : public UMemory, public ::icu::number::impl::CharSequence { /** * Returns the first code point in the string segment, or -1 if the string starts with an invalid * code point. + * + *

+ * Important: Most of the time, you should use {@link #matches}, which handles case + * folding logic, instead of this method. */ UChar32 getCodePoint() const; + /** + * Returns true if the first code point of this StringSegment equals the given code point. + * + *

+ * This method will perform case folding if case folding is enabled for the parser. + */ + bool matches(UChar32 otherCp) const; + + /** + * Returns true if the first code point of this StringSegment is in the given UnicodeSet. + */ + bool matches(const UnicodeSet& uniset) const; + /** * Returns the length of the prefix shared by this StringSegment and the given CharSequence. For * example, if this string segment is "aab", and the char sequence is "aac", this method returns 2, * since the first 2 characters are the same. + * + *

+ * This method will perform case folding if case folding is enabled for the parser. */ int32_t getCommonPrefixLength(const UnicodeString& other); + /** + * Like {@link #getCommonPrefixLength}, but never performs case folding, even if case folding is + * enabled for the parser. + */ + int32_t getCaseSensitivePrefixLength(const UnicodeString& other); + private: const UnicodeString fStr; int32_t fStart; int32_t fEnd; + bool fFoldCase; + + int32_t getPrefixLengthInternal(const UnicodeString& other, bool foldCase); + + static bool codePointsEqual(UChar32 cp1, UChar32 cp2, bool foldCase); }; diff --git a/icu4c/source/test/intltest/numbertest_parse.cpp b/icu4c/source/test/intltest/numbertest_parse.cpp index b0d2fe8cf1..c594a493ad 100644 --- a/icu4c/source/test/intltest/numbertest_parse.cpp +++ b/icu4c/source/test/intltest/numbertest_parse.cpp @@ -50,38 +50,39 @@ void NumberParserTest::testBasic() { {7, u"𝟳𝟴.𝟬𝟬𝟬.𝟬𝟮𝟯", u"#,##,##0", 11, 78.}, {3, u"-𝟱𝟭𝟰𝟮𝟯", u"0", 11, -51423.}, {3, u"-𝟱𝟭𝟰𝟮𝟯-", u"0", 11, -51423.}, - {3, u"a51423US dollars", u"a0¤¤¤", 16, 51423.}, - {3, u"a 51423 US dollars", u"a0¤¤¤", 18, 51423.}, - {3, u"514.23 USD", u"¤0", 10, 514.23}, - {3, u"514.23 GBP", u"¤0", 10, 514.23}, - {3, u"a 𝟱𝟭𝟰𝟮𝟯 b", u"a0b", 14, 51423.}, - {3, u"-a 𝟱𝟭𝟰𝟮𝟯 b", u"a0b", 15, -51423.}, - {3, u"a -𝟱𝟭𝟰𝟮𝟯 b", u"a0b", 15, -51423.}, - {3, u"𝟱𝟭𝟰𝟮𝟯", u"[0];(0)", 10, 51423.}, - {3, u"[𝟱𝟭𝟰𝟮𝟯", u"[0];(0)", 11, 51423.}, - {3, u"𝟱𝟭𝟰𝟮𝟯]", u"[0];(0)", 11, 51423.}, - {3, u"[𝟱𝟭𝟰𝟮𝟯]", u"[0];(0)", 12, 51423.}, - {3, u"(𝟱𝟭𝟰𝟮𝟯", u"[0];(0)", 11, -51423.}, - {3, u"𝟱𝟭𝟰𝟮𝟯)", u"[0];(0)", 11, -51423.}, - {3, u"(𝟱𝟭𝟰𝟮𝟯)", u"[0];(0)", 12, -51423.}, - {3, u"𝟱𝟭𝟰𝟮𝟯", u"{0};{0}", 10, 51423.}, - {3, u"{𝟱𝟭𝟰𝟮𝟯", u"{0};{0}", 11, 51423.}, - {3, u"𝟱𝟭𝟰𝟮𝟯}", u"{0};{0}", 11, 51423.}, - {3, u"{𝟱𝟭𝟰𝟮𝟯}", u"{0};{0}", 12, 51423.}, - {1, u"a40b", u"a0'0b'", 3, 40.}, // greedy code path thinks "40" is the number - {2, u"a40b", u"a0'0b'", 4, 4.}, // slow code path finds the suffix "0b" - {3, u"𝟱.𝟭𝟰𝟮E𝟯", u"0", 12, 5142.}, - {3, u"𝟱.𝟭𝟰𝟮E-𝟯", u"0", 13, 0.005142}, - {3, u"𝟱.𝟭𝟰𝟮e-𝟯", u"0", 13, 0.005142}, - {7, u"5,142.50 Canadian dollars", u"#,##,##0 ¤¤¤", 25, 5142.5}, - {3, u"a$ b5", u"a ¤ b0", 5, 5.0}, - {3, u"📺1.23", u"📺0;📻0", 6, 1.23}, - {3, u"📻1.23", u"📺0;📻0", 6, -1.23}, - {3, u".00", u"0", 3, 0.0}, - {3, u" 0", u"a0", 31, 0.0}, // should not hang - {3, u"NaN", u"0", 3, NAN}, - {3, u"NaN E5", u"0", 3, NAN}, - {3, u"0", u"0", 1, 0.0}}; +// {3, u"a51423US dollars", u"a0¤¤¤", 16, 51423.}, +// {3, u"a 51423 US dollars", u"a0¤¤¤", 18, 51423.}, +// {3, u"514.23 USD", u"¤0", 10, 514.23}, +// {3, u"514.23 GBP", u"¤0", 10, 514.23}, +// {3, u"a 𝟱𝟭𝟰𝟮𝟯 b", u"a0b", 14, 51423.}, +// {3, u"-a 𝟱𝟭𝟰𝟮𝟯 b", u"a0b", 15, -51423.}, +// {3, u"a -𝟱𝟭𝟰𝟮𝟯 b", u"a0b", 15, -51423.}, +// {3, u"𝟱𝟭𝟰𝟮𝟯", u"[0];(0)", 10, 51423.}, +// {3, u"[𝟱𝟭𝟰𝟮𝟯", u"[0];(0)", 11, 51423.}, +// {3, u"𝟱𝟭𝟰𝟮𝟯]", u"[0];(0)", 11, 51423.}, +// {3, u"[𝟱𝟭𝟰𝟮𝟯]", u"[0];(0)", 12, 51423.}, +// {3, u"(𝟱𝟭𝟰𝟮𝟯", u"[0];(0)", 11, -51423.}, +// {3, u"𝟱𝟭𝟰𝟮𝟯)", u"[0];(0)", 11, -51423.}, +// {3, u"(𝟱𝟭𝟰𝟮𝟯)", u"[0];(0)", 12, -51423.}, +// {3, u"𝟱𝟭𝟰𝟮𝟯", u"{0};{0}", 10, 51423.}, +// {3, u"{𝟱𝟭𝟰𝟮𝟯", u"{0};{0}", 11, 51423.}, +// {3, u"𝟱𝟭𝟰𝟮𝟯}", u"{0};{0}", 11, 51423.}, +// {3, u"{𝟱𝟭𝟰𝟮𝟯}", u"{0};{0}", 12, 51423.}, +// {1, u"a40b", u"a0'0b'", 3, 40.}, // greedy code path thinks "40" is the number +// {2, u"a40b", u"a0'0b'", 4, 4.}, // slow code path finds the suffix "0b" +// {3, u"𝟱.𝟭𝟰𝟮E𝟯", u"0", 12, 5142.}, +// {3, u"𝟱.𝟭𝟰𝟮E-𝟯", u"0", 13, 0.005142}, +// {3, u"𝟱.𝟭𝟰𝟮e-𝟯", u"0", 13, 0.005142}, +// {7, u"5,142.50 Canadian dollars", u"#,##,##0 ¤¤¤", 25, 5142.5}, +// {3, u"a$ b5", u"a ¤ b0", 5, 5.0}, +// {3, u"📺1.23", u"📺0;📻0", 6, 1.23}, +// {3, u"📻1.23", u"📺0;📻0", 6, -1.23}, +// {3, u".00", u"0", 3, 0.0}, +// {3, u" 0", u"a0", 31, 0.0}, // should not hang +// {3, u"NaN", u"0", 3, NAN}, +// {3, u"NaN E5", u"0", 3, NAN}, +// {3, u"0", u"0", 1, 0.0} + }; parse_flags_t parseFlags = PARSE_FLAG_IGNORE_CASE | PARSE_FLAG_INCLUDE_UNPAIRED_AFFIXES; for (auto cas : cases) { @@ -123,10 +124,7 @@ void NumberParserTest::testBasic() { if (0 != (cas.flags & 0x04)) { // Test with strict separators parser = NumberParserImpl::createSimpleParser( - Locale("en"), - patternString, - parseFlags | PARSE_FLAG_STRICT_GROUPING_SIZE, - status); + Locale("en"), patternString, parseFlags | PARSE_FLAG_STRICT_GROUPING_SIZE, status); ParsedNumber resultObject; parser->parse(inputString, true, resultObject, status); assertTrue("Strict Parse failed: " + message, resultObject.success()); diff --git a/icu4c/source/test/intltest/numbertest_stringsegment.cpp b/icu4c/source/test/intltest/numbertest_stringsegment.cpp index 519642e49a..665bc7c52b 100644 --- a/icu4c/source/test/intltest/numbertest_stringsegment.cpp +++ b/icu4c/source/test/intltest/numbertest_stringsegment.cpp @@ -24,7 +24,7 @@ void StringSegmentTest::runIndexedTest(int32_t index, UBool exec, const char*&na } void StringSegmentTest::testOffset() { - StringSegment segment(SAMPLE_STRING); + StringSegment segment(SAMPLE_STRING, 0); assertEquals("Initial Offset", 0, segment.getOffset()); segment.adjustOffset(3); assertEquals("Adjust A", 3, segment.getOffset()); @@ -35,7 +35,7 @@ void StringSegmentTest::testOffset() { } void StringSegmentTest::testLength() { - StringSegment segment(SAMPLE_STRING); + StringSegment segment(SAMPLE_STRING, 0); assertEquals("Initial length", 11, segment.length()); segment.adjustOffset(3); assertEquals("Adjust", 8, segment.length()); @@ -48,7 +48,7 @@ void StringSegmentTest::testLength() { } void StringSegmentTest::testCharAt() { - StringSegment segment(SAMPLE_STRING); + StringSegment segment(SAMPLE_STRING, 0); assertEquals("Initial", SAMPLE_STRING, segment.toUnicodeString()); segment.adjustOffset(3); assertEquals("After adjust-offset", UnicodeString(u"radio 📻"), segment.toUnicodeString()); @@ -57,7 +57,7 @@ void StringSegmentTest::testCharAt() { } void StringSegmentTest::testGetCodePoint() { - StringSegment segment(SAMPLE_STRING); + StringSegment segment(SAMPLE_STRING, 0); assertEquals("Double-width code point", 0x1F4FB, segment.getCodePoint()); segment.setLength(1); assertEquals("Inalid A", -1, segment.getCodePoint()); @@ -69,7 +69,7 @@ void StringSegmentTest::testGetCodePoint() { } void StringSegmentTest::testCommonPrefixLength() { - StringSegment segment(SAMPLE_STRING); + StringSegment segment(SAMPLE_STRING, 0); assertEquals("", 11, segment.getCommonPrefixLength(SAMPLE_STRING)); assertEquals("", 4, segment.getCommonPrefixLength(u"📻 r")); assertEquals("", 3, segment.getCommonPrefixLength(u"📻 x")); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java index 6fd6050442..4f9d6c0f32 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java @@ -5,7 +5,6 @@ package com.ibm.icu.impl.number.parse; import java.text.ParsePosition; import java.util.ArrayList; import java.util.Collection; -import java.util.Comparator; import java.util.List; import com.ibm.icu.impl.number.AffixPatternProvider; @@ -268,7 +267,6 @@ public class NumberParserImpl { private final int parseFlags; private final List matchers; private final List leads; - private Comparator comparator; private boolean frozen; /** @@ -284,7 +282,6 @@ public class NumberParserImpl { } else { leads = null; } - comparator = ParsedNumber.COMPARATOR; // default value this.parseFlags = parseFlags; frozen = false; } @@ -318,11 +315,6 @@ public class NumberParserImpl { this.leads.add(leadCodePoints); } - public void setComparator(Comparator comparator) { - assert !frozen; - this.comparator = comparator; - } - public void freeze() { frozen = true; } @@ -400,11 +392,12 @@ public class NumberParserImpl { int initialOffset = segment.getOffset(); for (int i = 0; i < matchers.size(); i++) { + // TODO: Check leadChars here? NumberParseMatcher matcher = matchers.get(i); // In a non-greedy parse, we attempt all possible matches and pick the best. for (int charsToConsume = 0; charsToConsume < segment.length();) { - charsToConsume += Character.charCount(Character.codePointAt(segment, charsToConsume)); + charsToConsume += Character.charCount(segment.codePointAt(charsToConsume)); // Run the matcher on a segment of the current length. candidate.copyFrom(initial); @@ -415,7 +408,7 @@ public class NumberParserImpl { // If the entire segment was consumed, recurse. if (segment.getOffset() - initialOffset == charsToConsume) { parseLongestRecursive(segment, candidate); - if (comparator.compare(candidate, result) > 0) { + if (candidate.isBetterThan(result)) { result.copyFrom(candidate); } } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java index 2bd45cc08b..d1b6751834 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java @@ -166,4 +166,8 @@ public class ParsedNumber { return d; } + + boolean isBetterThan(ParsedNumber other) { + return COMPARATOR.compare(this, other) > 0; + } } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/StringSegment.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/StringSegment.java index bc0cab0c5d..39416fd753 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/StringSegment.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/StringSegment.java @@ -74,6 +74,10 @@ public class StringSegment implements CharSequence { return str.charAt(index + start); } + public int codePointAt(int index) { + return str.codePointAt(index + start); + } + @Override public CharSequence subSequence(int start, int end) { throw new AssertionError(); // Never used From 12764fa0828f819c5e9c33dc1fe1f1cdd1e80aee Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Fri, 9 Feb 2018 02:35:02 +0000 Subject: [PATCH 006/129] ICU-13574 Adding more matchers derived from SymbolMatcher. X-SVN-Rev: 40876 --- icu4c/source/i18n/numparse_impl.cpp | 11 +- icu4c/source/i18n/numparse_symbols.cpp | 115 ++++++++++++++++-- icu4c/source/i18n/numparse_symbols.h | 71 ++++++++++- icu4c/source/i18n/numparse_unisets.cpp | 2 + icu4c/source/i18n/numparse_unisets.h | 2 + icu4c/source/test/intltest/intltest.cpp | 4 +- .../source/test/intltest/numbertest_parse.cpp | 26 ++-- .../impl/number/parse/NumberParserImpl.java | 3 + .../icu/dev/test/number/NumberParserTest.java | 17 ++- 9 files changed, 218 insertions(+), 33 deletions(-) diff --git a/icu4c/source/i18n/numparse_impl.cpp b/icu4c/source/i18n/numparse_impl.cpp index 4348d86c6d..99d19df455 100644 --- a/icu4c/source/i18n/numparse_impl.cpp +++ b/icu4c/source/i18n/numparse_impl.cpp @@ -29,8 +29,8 @@ NumberParserImpl::createSimpleParser(const Locale& locale, const UnicodeString& auto* parser = new NumberParserImpl(parseFlags, true); DecimalFormatSymbols symbols(locale, status); -// IgnorablesMatcher* ignorables = IgnorablesMatcher.getDefault(); -// + IgnorablesMatcher* ignorables = new IgnorablesMatcher(unisets::DEFAULT_IGNORABLES); + // MatcherFactory factory = new MatcherFactory(); // factory.currency = Currency.getInstance("USD"); // factory.symbols = symbols; @@ -45,10 +45,13 @@ NumberParserImpl::createSimpleParser(const Locale& locale, const UnicodeString& Grouper grouper = Grouper::forStrategy(UNUM_GROUPING_AUTO); grouper.setLocaleData(patternInfo, locale); -// parser.addMatcher({ignorables, false}); + parser->addAndAdoptMatcher(ignorables); parser->addAndAdoptMatcher(new DecimalMatcher(symbols, grouper, parseFlags)); parser->addAndAdoptMatcher(new MinusSignMatcher(symbols, false)); -// parser.addMatcher(NanMatcher.getInstance(symbols, parseFlags)); + parser->addAndAdoptMatcher(new PlusSignMatcher(symbols, false)); + parser->addAndAdoptMatcher(new PercentMatcher(symbols)); + parser->addAndAdoptMatcher(new PermilleMatcher(symbols)); + parser->addAndAdoptMatcher(new NanMatcher(symbols)); // parser.addMatcher(ScientificMatcher.getInstance(symbols, grouper, parseFlags)); // parser.addMatcher(CurrencyTrieMatcher.getInstance(locale)); // parser.addMatcher(new RequireNumberMatcher()); diff --git a/icu4c/source/i18n/numparse_symbols.cpp b/icu4c/source/i18n/numparse_symbols.cpp index 8d1631256c..5fabd2fb17 100644 --- a/icu4c/source/i18n/numparse_symbols.cpp +++ b/icu4c/source/i18n/numparse_symbols.cpp @@ -16,7 +16,6 @@ using namespace icu::numparse::impl; SymbolMatcher::SymbolMatcher(const UnicodeString& symbolString, unisets::Key key) { fUniSet = unisets::get(key); - fOwnsUniSet = false; if (fUniSet->contains(symbolString)) { fString.setToBogus(); } else { @@ -24,13 +23,6 @@ SymbolMatcher::SymbolMatcher(const UnicodeString& symbolString, unisets::Key key } } -SymbolMatcher::~SymbolMatcher() { - if (fOwnsUniSet) { - delete fUniSet; - fUniSet = nullptr; - } -} - const UnicodeSet* SymbolMatcher::getSet() { return fUniSet; } @@ -76,14 +68,30 @@ const UnicodeSet* SymbolMatcher::getLeadCodePoints() const { } -MinusSignMatcher::MinusSignMatcher(const DecimalFormatSymbols& dfs, bool allowTrailing) : SymbolMatcher( - dfs.getConstSymbol(DecimalFormatSymbols::kMinusSignSymbol), - unisets::MINUS_SIGN), fAllowTrailing(allowTrailing) { +IgnorablesMatcher::IgnorablesMatcher(unisets::Key key) + : SymbolMatcher({}, key) { +} + +bool IgnorablesMatcher::isFlexible() const { + return true; +} + +bool IgnorablesMatcher::isDisabled(const ParsedNumber&) const { + return false; +} + +void IgnorablesMatcher::accept(StringSegment&, ParsedNumber&) const { + // No-op +} + + +MinusSignMatcher::MinusSignMatcher(const DecimalFormatSymbols& dfs, bool allowTrailing) + : SymbolMatcher(dfs.getConstSymbol(DecimalFormatSymbols::kMinusSignSymbol), unisets::MINUS_SIGN), + fAllowTrailing(allowTrailing) { } bool MinusSignMatcher::isDisabled(const ParsedNumber& result) const { - return 0 != (result.flags & FLAG_NEGATIVE) || - (fAllowTrailing ? false : result.seenNumber()); + return 0 != (result.flags & FLAG_NEGATIVE) || (fAllowTrailing ? false : result.seenNumber()); } void MinusSignMatcher::accept(StringSegment& segment, ParsedNumber& result) const { @@ -92,4 +100,85 @@ void MinusSignMatcher::accept(StringSegment& segment, ParsedNumber& result) cons } +NanMatcher::NanMatcher(const DecimalFormatSymbols& dfs) + : SymbolMatcher(dfs.getConstSymbol(DecimalFormatSymbols::kNaNSymbol), unisets::EMPTY) { +} + +const UnicodeSet* NanMatcher::getLeadCodePoints() const { + // Overriding this here to allow use of statically allocated sets + int leadCp = fString.char32At(0); + const UnicodeSet* s = unisets::get(unisets::NAN_LEAD); + if (s->contains(leadCp)) { + return new UnicodeSet(*s); + } else { + return SymbolMatcher::getLeadCodePoints(); + } +} + +bool NanMatcher::isDisabled(const ParsedNumber& result) const { + return result.seenNumber(); +} + +void NanMatcher::accept(StringSegment& segment, ParsedNumber& result) const { + result.flags |= FLAG_NAN; + result.setCharsConsumed(segment); +} + + +PercentMatcher::PercentMatcher(const DecimalFormatSymbols& dfs) + : SymbolMatcher(dfs.getConstSymbol(DecimalFormatSymbols::kPercentSymbol), unisets::PERCENT_SIGN) { +} + +void PercentMatcher::postProcess(ParsedNumber& result) const { + SymbolMatcher::postProcess(result); + if (0 != (result.flags & FLAG_PERCENT) && !result.quantity.bogus) { + result.quantity.adjustMagnitude(-2); + } +} + +bool PercentMatcher::isDisabled(const ParsedNumber& result) const { + return 0 != (result.flags & FLAG_PERCENT); +} + +void PercentMatcher::accept(StringSegment& segment, ParsedNumber& result) const { + result.flags |= FLAG_PERCENT; + result.setCharsConsumed(segment); +} + + +PermilleMatcher::PermilleMatcher(const DecimalFormatSymbols& dfs) + : SymbolMatcher(dfs.getConstSymbol(DecimalFormatSymbols::kPerMillSymbol), unisets::PERMILLE_SIGN) { +} + +void PermilleMatcher::postProcess(ParsedNumber& result) const { + SymbolMatcher::postProcess(result); + if (0 != (result.flags & FLAG_PERMILLE) && !result.quantity.bogus) { + result.quantity.adjustMagnitude(-3); + } +} + +bool PermilleMatcher::isDisabled(const ParsedNumber& result) const { + return 0 != (result.flags & FLAG_PERMILLE); +} + +void PermilleMatcher::accept(StringSegment& segment, ParsedNumber& result) const { + result.flags |= FLAG_PERMILLE; + result.setCharsConsumed(segment); +} + + +PlusSignMatcher::PlusSignMatcher(const DecimalFormatSymbols& dfs, bool allowTrailing) + : SymbolMatcher(dfs.getConstSymbol(DecimalFormatSymbols::kPlusSignSymbol), unisets::PLUS_SIGN), + fAllowTrailing(allowTrailing) { +} + +bool PlusSignMatcher::isDisabled(const ParsedNumber& result) const { + return fAllowTrailing ? false : result.seenNumber(); +} + +void PlusSignMatcher::accept(StringSegment& segment, ParsedNumber& result) const { + result.setCharsConsumed(segment); +} + + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/numparse_symbols.h b/icu4c/source/i18n/numparse_symbols.h index d730ef5753..4a17d67a44 100644 --- a/icu4c/source/i18n/numparse_symbols.h +++ b/icu4c/source/i18n/numparse_symbols.h @@ -17,8 +17,6 @@ namespace impl { class SymbolMatcher : public NumberParseMatcher, public UMemory { public: - ~SymbolMatcher() override; - const UnicodeSet* getSet(); bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; @@ -31,13 +29,25 @@ class SymbolMatcher : public NumberParseMatcher, public UMemory { protected: UnicodeString fString; - const UnicodeSet* fUniSet; - bool fOwnsUniSet; + const UnicodeSet* fUniSet; // a reference from numparse_unisets.h; never owned SymbolMatcher(const UnicodeString& symbolString, unisets::Key key); }; +class IgnorablesMatcher : public SymbolMatcher { + public: + explicit IgnorablesMatcher(unisets::Key key); + + bool isFlexible() const override; + + protected: + bool isDisabled(const ParsedNumber& result) const override; + + void accept(StringSegment& segment, ParsedNumber& result) const override; +}; + + class MinusSignMatcher : public SymbolMatcher { public: MinusSignMatcher(const DecimalFormatSymbols& dfs, bool allowTrailing); @@ -52,6 +62,59 @@ class MinusSignMatcher : public SymbolMatcher { }; +class NanMatcher : public SymbolMatcher { + public: + explicit NanMatcher(const DecimalFormatSymbols& dfs); + + const UnicodeSet* getLeadCodePoints() const override; + + protected: + bool isDisabled(const ParsedNumber& result) const override; + + void accept(StringSegment& segment, ParsedNumber& result) const override; +}; + + +class PercentMatcher : public SymbolMatcher { + public: + explicit PercentMatcher(const DecimalFormatSymbols& dfs); + + void postProcess(ParsedNumber& result) const override; + + protected: + bool isDisabled(const ParsedNumber& result) const override; + + void accept(StringSegment& segment, ParsedNumber& result) const override; +}; + + +class PermilleMatcher : public SymbolMatcher { + public: + explicit PermilleMatcher(const DecimalFormatSymbols& dfs); + + void postProcess(ParsedNumber& result) const override; + + protected: + bool isDisabled(const ParsedNumber& result) const override; + + void accept(StringSegment& segment, ParsedNumber& result) const override; +}; + + +class PlusSignMatcher : public SymbolMatcher { + public: + PlusSignMatcher(const DecimalFormatSymbols& dfs, bool allowTrailing); + + protected: + bool isDisabled(const ParsedNumber& result) const override; + + void accept(StringSegment& segment, ParsedNumber& result) const override; + + private: + bool fAllowTrailing; +}; + + } // namespace impl } // namespace numparse U_NAMESPACE_END diff --git a/icu4c/source/i18n/numparse_unisets.cpp b/icu4c/source/i18n/numparse_unisets.cpp index 8477870e29..a7d1fbdba2 100644 --- a/icu4c/source/i18n/numparse_unisets.cpp +++ b/icu4c/source/i18n/numparse_unisets.cpp @@ -58,6 +58,8 @@ void U_CALLCONV initNumberParseUniSets(UErrorCode &status) { ucln_i18n_registerCleanup(UCLN_I18N_NUMPARSE_UNISETS, cleanupNumberParseUnitSets); #define NEW_UNISET(pattern, status) new UnicodeSet(UnicodeString(pattern), status) + gUnicodeSets[EMPTY] = new UnicodeSet(); + // BiDi characters are skipped over and ignored at any point in the string, even in strict mode. gUnicodeSets[BIDI] = NEW_UNISET(u"[[\\u200E\\u200F\\u061C]]", status); diff --git a/icu4c/source/i18n/numparse_unisets.h b/icu4c/source/i18n/numparse_unisets.h index 1d923613e9..27f609dc5d 100644 --- a/icu4c/source/i18n/numparse_unisets.h +++ b/icu4c/source/i18n/numparse_unisets.h @@ -15,6 +15,8 @@ namespace impl { namespace unisets { enum Key { + EMPTY, + // Ignorables BIDI, WHITESPACE, diff --git a/icu4c/source/test/intltest/intltest.cpp b/icu4c/source/test/intltest/intltest.cpp index c45913796a..c7d67565b2 100644 --- a/icu4c/source/test/intltest/intltest.cpp +++ b/icu4c/source/test/intltest/intltest.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include "unicode/ctest.h" // for str_timeDelta #include "unicode/curramt.h" @@ -1998,7 +1999,8 @@ UBool IntlTest::assertEquals(const char* message, UBool IntlTest::assertEquals(const char* message, double expected, double actual) { - if (expected != actual) { + bool bothNaN = std::isnan(expected) && std::isnan(actual); + if (expected != actual && !bothNaN) { errln((UnicodeString)"FAIL: " + message + "; got " + actual + "; expected " + expected); diff --git a/icu4c/source/test/intltest/numbertest_parse.cpp b/icu4c/source/test/intltest/numbertest_parse.cpp index c594a493ad..1fcaa1b0c9 100644 --- a/icu4c/source/test/intltest/numbertest_parse.cpp +++ b/icu4c/source/test/intltest/numbertest_parse.cpp @@ -41,6 +41,9 @@ void NumberParserTest::testBasic() { {3, u"𝟱𝟭𝟰𝟮𝟯x", u"0", 10, 51423.}, {3, u" 𝟱𝟭𝟰𝟮𝟯", u"0", 11, 51423.}, {3, u"𝟱𝟭𝟰𝟮𝟯 ", u"0", 10, 51423.}, + {7, u"51,423", u"#,##,##0", 6, 51423.}, + {7, u" 51,423", u"#,##,##0", 7, 51423.}, + {7, u"51,423 ", u"#,##,##0", 6, 51423.}, {7, u"𝟱𝟭,𝟰𝟮𝟯", u"#,##,##0", 11, 51423.}, {7, u"𝟳,𝟴𝟵,𝟱𝟭,𝟰𝟮𝟯", u"#,##,##0", 19, 78951423.}, {7, u"𝟳𝟴,𝟵𝟱𝟭.𝟰𝟮𝟯", u"#,##,##0", 18, 78951.423}, @@ -48,8 +51,16 @@ void NumberParserTest::testBasic() { {7, u"𝟳𝟴,𝟬𝟬𝟬.𝟬𝟬𝟬", u"#,##,##0", 18, 78000.}, {7, u"𝟳𝟴,𝟬𝟬𝟬.𝟬𝟮𝟯", u"#,##,##0", 18, 78000.023}, {7, u"𝟳𝟴.𝟬𝟬𝟬.𝟬𝟮𝟯", u"#,##,##0", 11, 78.}, - {3, u"-𝟱𝟭𝟰𝟮𝟯", u"0", 11, -51423.}, - {3, u"-𝟱𝟭𝟰𝟮𝟯-", u"0", 11, -51423.}, + {3, u"-51423", u"0", 6, -51423.}, + {3, u"51423-", u"0", 5, 51423.}, // plus and minus sign by default do NOT match after + {3, u"+51423", u"0", 6, 51423.}, + {3, u"51423+", u"0", 5, 51423.}, // plus and minus sign by default do NOT match after + {3, u"%51423", u"0", 6, 514.23}, + {3, u"51423%", u"0", 6, 514.23}, + {3, u"51423%%", u"0", 6, 514.23}, + {3, u"‰51423", u"0", 6, 51.423}, + {3, u"51423‰", u"0", 6, 51.423}, + {3, u"51423‰‰", u"0", 6, 51.423}, // {3, u"a51423US dollars", u"a0¤¤¤", 16, 51423.}, // {3, u"a 51423 US dollars", u"a0¤¤¤", 18, 51423.}, // {3, u"514.23 USD", u"¤0", 10, 514.23}, @@ -77,12 +88,11 @@ void NumberParserTest::testBasic() { // {3, u"a$ b5", u"a ¤ b0", 5, 5.0}, // {3, u"📺1.23", u"📺0;📻0", 6, 1.23}, // {3, u"📻1.23", u"📺0;📻0", 6, -1.23}, -// {3, u".00", u"0", 3, 0.0}, -// {3, u" 0", u"a0", 31, 0.0}, // should not hang -// {3, u"NaN", u"0", 3, NAN}, -// {3, u"NaN E5", u"0", 3, NAN}, -// {3, u"0", u"0", 1, 0.0} - }; + {3, u".00", u"0", 3, 0.0}, + {3, u" 1,234", u"a0", 35, 1234.}, // should not hang + {3, u"NaN", u"0", 3, NAN}, + {3, u"NaN E5", u"0", 3, NAN}, + {3, u"0", u"0", 1, 0.0}}; parse_flags_t parseFlags = PARSE_FLAG_IGNORE_CASE | PARSE_FLAG_INCLUDE_UNPAIRED_AFFIXES; for (auto cas : cases) { diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java index 4f9d6c0f32..ae5650c311 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java @@ -88,6 +88,9 @@ public class NumberParserImpl { parser.addMatcher(ignorables); parser.addMatcher(DecimalMatcher.getInstance(symbols, grouper, parseFlags)); parser.addMatcher(MinusSignMatcher.getInstance(symbols, false)); + parser.addMatcher(PlusSignMatcher.getInstance(symbols, false)); + parser.addMatcher(PercentMatcher.getInstance(symbols)); + parser.addMatcher(PermilleMatcher.getInstance(symbols)); parser.addMatcher(NanMatcher.getInstance(symbols, parseFlags)); parser.addMatcher(ScientificMatcher.getInstance(symbols, grouper)); parser.addMatcher(CurrencyTrieMatcher.getInstance(locale)); diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java index 8c57dd6e1d..541ead1945 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java @@ -47,6 +47,9 @@ public class NumberParserTest { { 3, "𝟱𝟭𝟰𝟮𝟯x", "0", 10, 51423. }, { 3, " 𝟱𝟭𝟰𝟮𝟯", "0", 11, 51423. }, { 3, "𝟱𝟭𝟰𝟮𝟯 ", "0", 10, 51423. }, + { 7, "51,423", "#,##,##0", 6, 51423. }, + { 7, " 51,423", "#,##,##0", 7, 51423. }, + { 7, "51,423 ", "#,##,##0", 6, 51423. }, { 7, "𝟱𝟭,𝟰𝟮𝟯", "#,##,##0", 11, 51423. }, { 7, "𝟳,𝟴𝟵,𝟱𝟭,𝟰𝟮𝟯", "#,##,##0", 19, 78951423. }, { 7, "𝟳𝟴,𝟵𝟱𝟭.𝟰𝟮𝟯", "#,##,##0", 18, 78951.423 }, @@ -54,8 +57,16 @@ public class NumberParserTest { { 7, "𝟳𝟴,𝟬𝟬𝟬.𝟬𝟬𝟬", "#,##,##0", 18, 78000. }, { 7, "𝟳𝟴,𝟬𝟬𝟬.𝟬𝟮𝟯", "#,##,##0", 18, 78000.023 }, { 7, "𝟳𝟴.𝟬𝟬𝟬.𝟬𝟮𝟯", "#,##,##0", 11, 78. }, - { 3, "-𝟱𝟭𝟰𝟮𝟯", "0", 11, -51423. }, - { 3, "-𝟱𝟭𝟰𝟮𝟯-", "0", 11, -51423. }, + { 3, "-51423", "0", 6, -51423. }, + { 3, "51423-", "0", 5, 51423. }, // plus and minus sign by default do NOT match after + { 3, "+51423", "0", 6, 51423. }, + { 3, "51423+", "0", 5, 51423. }, // plus and minus sign by default do NOT match after + { 3, "%51423", "0", 6, 514.23 }, + { 3, "51423%", "0", 6, 514.23 }, + { 3, "51423%%", "0", 6, 514.23 }, + { 3, "‰51423", "0", 6, 51.423 }, + { 3, "51423‰", "0", 6, 51.423 }, + { 3, "51423‰‰", "0", 6, 51.423 }, { 3, "a51423US dollars", "a0¤¤¤", 16, 51423. }, { 3, "a 51423 US dollars", "a0¤¤¤", 18, 51423. }, { 3, "514.23 USD", "¤0", 10, 514.23 }, @@ -84,7 +95,7 @@ public class NumberParserTest { { 3, "📺1.23", "📺0;📻0", 6, 1.23 }, { 3, "📻1.23", "📺0;📻0", 6, -1.23 }, { 3, ".00", "0", 3, 0.0 }, - { 3, " 0", "a0", 31, 0.0 }, // should not hang + { 3, " 1,234", "a0", 35, 1234. }, // should not hang { 3, "NaN", "0", 3, Double.NaN }, { 3, "NaN E5", "0", 3, Double.NaN }, { 3, "0", "0", 1, 0.0 } }; From fb3ff21caf5dc582885bc452ce5398de5da8ebc3 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Fri, 9 Feb 2018 05:47:49 +0000 Subject: [PATCH 007/129] ICU-13574 Switching memory strategy to allocate objects as fields in the main class instead of on the heap. X-SVN-Rev: 40877 --- icu4c/source/i18n/numparse_decimal.h | 2 ++ icu4c/source/i18n/numparse_impl.cpp | 32 +++++++++---------- icu4c/source/i18n/numparse_impl.h | 20 ++++++++++-- icu4c/source/i18n/numparse_symbols.h | 22 ++++++++++--- icu4c/source/i18n/numparse_unisets.cpp | 17 +++++----- .../source/test/intltest/numbertest_parse.cpp | 21 ++++++------ 6 files changed, 72 insertions(+), 42 deletions(-) diff --git a/icu4c/source/i18n/numparse_decimal.h b/icu4c/source/i18n/numparse_decimal.h index b7bda16f58..9423a7786c 100644 --- a/icu4c/source/i18n/numparse_decimal.h +++ b/icu4c/source/i18n/numparse_decimal.h @@ -17,6 +17,8 @@ using ::icu::number::impl::Grouper; class DecimalMatcher : public NumberParseMatcher, public UMemory { public: + DecimalMatcher() = default; // WARNING: Leaves the object in an unusable state + DecimalMatcher(const DecimalFormatSymbols& symbols, const Grouper& grouper, parse_flags_t parseFlags); diff --git a/icu4c/source/i18n/numparse_impl.cpp b/icu4c/source/i18n/numparse_impl.cpp index 99d19df455..1df9c56b43 100644 --- a/icu4c/source/i18n/numparse_impl.cpp +++ b/icu4c/source/i18n/numparse_impl.cpp @@ -29,7 +29,7 @@ NumberParserImpl::createSimpleParser(const Locale& locale, const UnicodeString& auto* parser = new NumberParserImpl(parseFlags, true); DecimalFormatSymbols symbols(locale, status); - IgnorablesMatcher* ignorables = new IgnorablesMatcher(unisets::DEFAULT_IGNORABLES); + parser->fLocalMatchers.ignorables = {unisets::DEFAULT_IGNORABLES}; // MatcherFactory factory = new MatcherFactory(); // factory.currency = Currency.getInstance("USD"); @@ -45,13 +45,13 @@ NumberParserImpl::createSimpleParser(const Locale& locale, const UnicodeString& Grouper grouper = Grouper::forStrategy(UNUM_GROUPING_AUTO); grouper.setLocaleData(patternInfo, locale); - parser->addAndAdoptMatcher(ignorables); - parser->addAndAdoptMatcher(new DecimalMatcher(symbols, grouper, parseFlags)); - parser->addAndAdoptMatcher(new MinusSignMatcher(symbols, false)); - parser->addAndAdoptMatcher(new PlusSignMatcher(symbols, false)); - parser->addAndAdoptMatcher(new PercentMatcher(symbols)); - parser->addAndAdoptMatcher(new PermilleMatcher(symbols)); - parser->addAndAdoptMatcher(new NanMatcher(symbols)); + parser->addMatcher(parser->fLocalMatchers.ignorables); + parser->addMatcher(parser->fLocalMatchers.decimal = {symbols, grouper, parseFlags}); + parser->addMatcher(parser->fLocalMatchers.minusSign = {symbols, false}); + parser->addMatcher(parser->fLocalMatchers.plusSign = {symbols, false}); + parser->addMatcher(parser->fLocalMatchers.percent = {symbols}); + parser->addMatcher(parser->fLocalMatchers.permille = {symbols}); + parser->addMatcher(parser->fLocalMatchers.nan = {symbols}); // parser.addMatcher(ScientificMatcher.getInstance(symbols, grouper, parseFlags)); // parser.addMatcher(CurrencyTrieMatcher.getInstance(locale)); // parser.addMatcher(new RequireNumberMatcher()); @@ -65,16 +65,15 @@ NumberParserImpl::NumberParserImpl(parse_flags_t parseFlags, bool computeLeads) } NumberParserImpl::~NumberParserImpl() { - for (int32_t i = 0; i < fNumMatchers; i++) { - delete (fMatchers[i]); - if (fComputeLeads) { + if (fComputeLeads) { + for (int32_t i = 0; i < fNumMatchers; i++) { delete (fLeads[i]); } } fNumMatchers = 0; } -void NumberParserImpl::addAndAdoptMatcher(const NumberParseMatcher* matcher) { +void NumberParserImpl::addMatcher(const NumberParseMatcher& matcher) { if (fNumMatchers + 1 > fMatchers.getCapacity()) { fMatchers.resize(fNumMatchers * 2, fNumMatchers); if (fComputeLeads) { @@ -84,10 +83,10 @@ void NumberParserImpl::addAndAdoptMatcher(const NumberParseMatcher* matcher) { } } - fMatchers[fNumMatchers] = matcher; + fMatchers[fNumMatchers] = &matcher; if (fComputeLeads) { - fLeads[fNumMatchers] = matcher->getLeadCodePoints(); + fLeads[fNumMatchers] = matcher.getLeadCodePoints(); } fNumMatchers++; @@ -102,9 +101,8 @@ void NumberParserImpl::parse(const UnicodeString& input, bool greedy, ParsedNumb return parse(input, 0, greedy, result, status); } -void -NumberParserImpl::parse(const UnicodeString& input, int32_t start, bool greedy, ParsedNumber& result, - UErrorCode& status) const { +void NumberParserImpl::parse(const UnicodeString& input, int32_t start, bool greedy, ParsedNumber& result, + UErrorCode& status) const { U_ASSERT(fFrozen); // TODO: Check start >= 0 and start < input.length() StringSegment segment(input, fParseFlags); diff --git a/icu4c/source/i18n/numparse_impl.h b/icu4c/source/i18n/numparse_impl.h index adb9294689..105f8c71ab 100644 --- a/icu4c/source/i18n/numparse_impl.h +++ b/icu4c/source/i18n/numparse_impl.h @@ -8,6 +8,8 @@ #define __NUMPARSE_IMPL_H__ #include "numparse_types.h" +#include "numparse_decimal.h" +#include "numparse_symbols.h" #include "unicode/uniset.h" U_NAMESPACE_BEGIN namespace numparse { @@ -15,10 +17,12 @@ namespace impl { class NumberParserImpl { public: + ~NumberParserImpl(); + static NumberParserImpl* createSimpleParser(const Locale& locale, const UnicodeString& patternString, parse_flags_t parseFlags, UErrorCode& status); - void addAndAdoptMatcher(const NumberParseMatcher* matcher); + void addMatcher(const NumberParseMatcher& matcher); void freeze(); @@ -38,9 +42,19 @@ class NumberParserImpl { bool fComputeLeads; bool fFrozen = false; - NumberParserImpl(parse_flags_t parseFlags, bool computeLeads); + // WARNING: All of these matchers start in an uninitialized state. + // You must use an assignment operator on them before using. + struct { + IgnorablesMatcher ignorables; + MinusSignMatcher minusSign; + NanMatcher nan; + PercentMatcher percent; + PermilleMatcher permille; + PlusSignMatcher plusSign; + DecimalMatcher decimal; + } fLocalMatchers; - ~NumberParserImpl(); + NumberParserImpl(parse_flags_t parseFlags, bool computeLeads); void parseGreedyRecursive(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const; diff --git a/icu4c/source/i18n/numparse_symbols.h b/icu4c/source/i18n/numparse_symbols.h index 4a17d67a44..c8ba913911 100644 --- a/icu4c/source/i18n/numparse_symbols.h +++ b/icu4c/source/i18n/numparse_symbols.h @@ -17,6 +17,8 @@ namespace impl { class SymbolMatcher : public NumberParseMatcher, public UMemory { public: + SymbolMatcher() = default; // WARNING: Leaves the object in an unusable state + const UnicodeSet* getSet(); bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; @@ -37,7 +39,9 @@ class SymbolMatcher : public NumberParseMatcher, public UMemory { class IgnorablesMatcher : public SymbolMatcher { public: - explicit IgnorablesMatcher(unisets::Key key); + IgnorablesMatcher() = default; // WARNING: Leaves the object in an unusable state + + IgnorablesMatcher(unisets::Key key); bool isFlexible() const override; @@ -50,6 +54,8 @@ class IgnorablesMatcher : public SymbolMatcher { class MinusSignMatcher : public SymbolMatcher { public: + MinusSignMatcher() = default; // WARNING: Leaves the object in an unusable state + MinusSignMatcher(const DecimalFormatSymbols& dfs, bool allowTrailing); protected: @@ -64,7 +70,9 @@ class MinusSignMatcher : public SymbolMatcher { class NanMatcher : public SymbolMatcher { public: - explicit NanMatcher(const DecimalFormatSymbols& dfs); + NanMatcher() = default; // WARNING: Leaves the object in an unusable state + + NanMatcher(const DecimalFormatSymbols& dfs); const UnicodeSet* getLeadCodePoints() const override; @@ -77,7 +85,9 @@ class NanMatcher : public SymbolMatcher { class PercentMatcher : public SymbolMatcher { public: - explicit PercentMatcher(const DecimalFormatSymbols& dfs); + PercentMatcher() = default; // WARNING: Leaves the object in an unusable state + + PercentMatcher(const DecimalFormatSymbols& dfs); void postProcess(ParsedNumber& result) const override; @@ -90,7 +100,9 @@ class PercentMatcher : public SymbolMatcher { class PermilleMatcher : public SymbolMatcher { public: - explicit PermilleMatcher(const DecimalFormatSymbols& dfs); + PermilleMatcher() = default; // WARNING: Leaves the object in an unusable state + + PermilleMatcher(const DecimalFormatSymbols& dfs); void postProcess(ParsedNumber& result) const override; @@ -103,6 +115,8 @@ class PermilleMatcher : public SymbolMatcher { class PlusSignMatcher : public SymbolMatcher { public: + PlusSignMatcher() = default; // WARNING: Leaves the object in an unusable state + PlusSignMatcher(const DecimalFormatSymbols& dfs, bool allowTrailing); protected: diff --git a/icu4c/source/i18n/numparse_unisets.cpp b/icu4c/source/i18n/numparse_unisets.cpp index a7d1fbdba2..f259f7a646 100644 --- a/icu4c/source/i18n/numparse_unisets.cpp +++ b/icu4c/source/i18n/numparse_unisets.cpp @@ -19,7 +19,7 @@ using namespace icu::numparse::impl::unisets; namespace { -UnicodeSet* gUnicodeSets[COUNT] = {}; +static UnicodeSet* gUnicodeSets[COUNT] = {}; UnicodeSet* computeUnion(Key k1, Key k2) { UnicodeSet* result = new UnicodeSet(); @@ -46,7 +46,7 @@ UnicodeSet* computeUnion(Key k1, Key k2, Key k3) { icu::UInitOnce gNumberParseUniSetsInitOnce = U_INITONCE_INITIALIZER; -UBool U_CALLCONV cleanupNumberParseUnitSets() { +UBool U_CALLCONV cleanupNumberParseUniSets() { for (int32_t i = 0; i < COUNT; i++) { delete gUnicodeSets[i]; gUnicodeSets[i] = nullptr; @@ -54,8 +54,8 @@ UBool U_CALLCONV cleanupNumberParseUnitSets() { return TRUE; } -void U_CALLCONV initNumberParseUniSets(UErrorCode &status) { - ucln_i18n_registerCleanup(UCLN_I18N_NUMPARSE_UNISETS, cleanupNumberParseUnitSets); +void U_CALLCONV initNumberParseUniSets(UErrorCode& status) { + ucln_i18n_registerCleanup(UCLN_I18N_NUMPARSE_UNISETS, cleanupNumberParseUniSets); #define NEW_UNISET(pattern, status) new UnicodeSet(UnicodeString(pattern), status) gUnicodeSets[EMPTY] = new UnicodeSet(); @@ -68,7 +68,7 @@ void U_CALLCONV initNumberParseUniSets(UErrorCode &status) { gUnicodeSets[WHITESPACE] = NEW_UNISET(u"[[:Zs:][\\u0009]]", status); gUnicodeSets[DEFAULT_IGNORABLES] = computeUnion(BIDI, WHITESPACE); - gUnicodeSets[STRICT_IGNORABLES] = gUnicodeSets[BIDI]; + gUnicodeSets[STRICT_IGNORABLES] = new UnicodeSet(*gUnicodeSets[BIDI]); // TODO: Re-generate these sets from the UCD. They probably haven't been updated in a while. gUnicodeSets[COMMA] = NEW_UNISET(u"[,،٫、︐︑﹐﹑,、]", status); @@ -76,7 +76,8 @@ void U_CALLCONV initNumberParseUniSets(UErrorCode &status) { gUnicodeSets[PERIOD] = NEW_UNISET(u"[.․。︒﹒.。]", status); gUnicodeSets[STRICT_PERIOD] = NEW_UNISET(u"[.․﹒.。]", status); gUnicodeSets[OTHER_GROUPING_SEPARATORS] = NEW_UNISET( - u"['٬‘’'\\u0020\\u00A0\\u2000-\\u200A\\u202F\\u205F\\u3000]", status); + u"['٬‘’'\\u0020\\u00A0\\u2000-\\u200A\\u202F\\u205F\\u3000]", + status); gUnicodeSets[ALL_SEPARATORS] = computeUnion(COMMA, PERIOD, OTHER_GROUPING_SEPARATORS); gUnicodeSets[STRICT_ALL_SEPARATORS] = computeUnion( STRICT_COMMA, STRICT_PERIOD, OTHER_GROUPING_SEPARATORS); @@ -89,8 +90,8 @@ void U_CALLCONV initNumberParseUniSets(UErrorCode &status) { gUnicodeSets[INFINITY] = NEW_UNISET(u"[∞]", status); gUnicodeSets[DIGITS] = NEW_UNISET(u"[:digit:]", status); - gUnicodeSets[NAN_LEAD] = NEW_UNISET( - u"[NnТтmeՈոс¤НнчTtsҳ\u975e\u1002\u0e9a\u10d0\u0f68\u0644\u0646]", status); + gUnicodeSets[NAN_LEAD] = NEW_UNISET(u"[NnТтmeՈոс¤НнчTtsҳ\u975e\u1002\u0e9a\u10d0\u0f68\u0644\u0646]", + status); gUnicodeSets[SCIENTIFIC_LEAD] = NEW_UNISET(u"[Ee×·е\u0627]", status); gUnicodeSets[CWCF] = NEW_UNISET(u"[:CWCF:]", status); diff --git a/icu4c/source/test/intltest/numbertest_parse.cpp b/icu4c/source/test/intltest/numbertest_parse.cpp index 1fcaa1b0c9..4e091048d8 100644 --- a/icu4c/source/test/intltest/numbertest_parse.cpp +++ b/icu4c/source/test/intltest/numbertest_parse.cpp @@ -98,8 +98,9 @@ void NumberParserTest::testBasic() { for (auto cas : cases) { UnicodeString inputString(cas.inputString); UnicodeString patternString(cas.patternString); - const NumberParserImpl* parser = NumberParserImpl::createSimpleParser( - Locale("en"), patternString, parseFlags, status); + LocalPointer parser( + NumberParserImpl::createSimpleParser( + Locale("en"), patternString, parseFlags, status)); UnicodeString message = UnicodeString("Input <") + inputString + UnicodeString("> Parser ") + parser->toString(); @@ -111,9 +112,7 @@ void NumberParserTest::testBasic() { assertEquals( "Greedy Parse failed: " + message, cas.expectedCharsConsumed, resultObject.charEnd); assertEquals( - "Greedy Parse failed: " + message, - cas.expectedResultDouble, - resultObject.getDouble()); + "Greedy Parse failed: " + message, cas.expectedResultDouble, resultObject.getDouble()); } if (0 != (cas.flags & 0x02)) { @@ -133,17 +132,19 @@ void NumberParserTest::testBasic() { if (0 != (cas.flags & 0x04)) { // Test with strict separators - parser = NumberParserImpl::createSimpleParser( - Locale("en"), patternString, parseFlags | PARSE_FLAG_STRICT_GROUPING_SIZE, status); + parser.adoptInstead( + NumberParserImpl::createSimpleParser( + Locale("en"), + patternString, + parseFlags | PARSE_FLAG_STRICT_GROUPING_SIZE, + status)); ParsedNumber resultObject; parser->parse(inputString, true, resultObject, status); assertTrue("Strict Parse failed: " + message, resultObject.success()); assertEquals( "Strict Parse failed: " + message, cas.expectedCharsConsumed, resultObject.charEnd); assertEquals( - "Strict Parse failed: " + message, - cas.expectedResultDouble, - resultObject.getDouble()); + "Strict Parse failed: " + message, cas.expectedResultDouble, resultObject.getDouble()); } } } From b8bab89cb5fae5de161dabe06bdce9c7b6aebda2 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Fri, 9 Feb 2018 06:30:40 +0000 Subject: [PATCH 008/129] ICU-13574 Implementing final two SymbolMatchers in ICU4C (infinity and padding). X-SVN-Rev: 40878 --- icu4c/source/i18n/numparse_impl.cpp | 5 +++ icu4c/source/i18n/numparse_impl.h | 2 + icu4c/source/i18n/numparse_symbols.cpp | 30 +++++++++++++ icu4c/source/i18n/numparse_symbols.h | 33 +++++++++++++++ icu4c/source/i18n/numparse_unisets.cpp | 42 ++++++++++--------- .../source/test/intltest/numbertest_parse.cpp | 4 ++ .../impl/number/parse/InfinityMatcher.java | 2 +- .../impl/number/parse/NumberParserImpl.java | 2 + .../icu/impl/number/parse/SymbolMatcher.java | 3 +- .../icu/dev/test/number/NumberParserTest.java | 4 ++ 10 files changed, 105 insertions(+), 22 deletions(-) diff --git a/icu4c/source/i18n/numparse_impl.cpp b/icu4c/source/i18n/numparse_impl.cpp index 1df9c56b43..575e0e1679 100644 --- a/icu4c/source/i18n/numparse_impl.cpp +++ b/icu4c/source/i18n/numparse_impl.cpp @@ -5,6 +5,9 @@ #if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +// Allow implicit conversion from char16_t* to UnicodeString for this file +#define UNISTR_FROM_STRING_EXPLICIT + #include "number_types.h" #include "number_patternstring.h" #include "numparse_types.h" @@ -52,6 +55,8 @@ NumberParserImpl::createSimpleParser(const Locale& locale, const UnicodeString& parser->addMatcher(parser->fLocalMatchers.percent = {symbols}); parser->addMatcher(parser->fLocalMatchers.permille = {symbols}); parser->addMatcher(parser->fLocalMatchers.nan = {symbols}); + parser->addMatcher(parser->fLocalMatchers.infinity = {symbols}); + parser->addMatcher(parser->fLocalMatchers.padding = {u"@"}); // parser.addMatcher(ScientificMatcher.getInstance(symbols, grouper, parseFlags)); // parser.addMatcher(CurrencyTrieMatcher.getInstance(locale)); // parser.addMatcher(new RequireNumberMatcher()); diff --git a/icu4c/source/i18n/numparse_impl.h b/icu4c/source/i18n/numparse_impl.h index 105f8c71ab..3f9b5d4b35 100644 --- a/icu4c/source/i18n/numparse_impl.h +++ b/icu4c/source/i18n/numparse_impl.h @@ -46,8 +46,10 @@ class NumberParserImpl { // You must use an assignment operator on them before using. struct { IgnorablesMatcher ignorables; + InfinityMatcher infinity; MinusSignMatcher minusSign; NanMatcher nan; + PaddingMatcher padding; PercentMatcher percent; PermilleMatcher permille; PlusSignMatcher plusSign; diff --git a/icu4c/source/i18n/numparse_symbols.cpp b/icu4c/source/i18n/numparse_symbols.cpp index 5fabd2fb17..8e192cf773 100644 --- a/icu4c/source/i18n/numparse_symbols.cpp +++ b/icu4c/source/i18n/numparse_symbols.cpp @@ -85,6 +85,20 @@ void IgnorablesMatcher::accept(StringSegment&, ParsedNumber&) const { } +InfinityMatcher::InfinityMatcher(const DecimalFormatSymbols& dfs) + : SymbolMatcher(dfs.getConstSymbol(DecimalFormatSymbols::kNaNSymbol), unisets::INFINITY) { +} + +bool InfinityMatcher::isDisabled(const ParsedNumber& result) const { + return 0 != (result.flags & FLAG_INFINITY); +} + +void InfinityMatcher::accept(StringSegment& segment, ParsedNumber& result) const { + result.flags |= FLAG_INFINITY; + result.setCharsConsumed(segment); +} + + MinusSignMatcher::MinusSignMatcher(const DecimalFormatSymbols& dfs, bool allowTrailing) : SymbolMatcher(dfs.getConstSymbol(DecimalFormatSymbols::kMinusSignSymbol), unisets::MINUS_SIGN), fAllowTrailing(allowTrailing) { @@ -125,6 +139,22 @@ void NanMatcher::accept(StringSegment& segment, ParsedNumber& result) const { } +PaddingMatcher::PaddingMatcher(const UnicodeString& padString) + : SymbolMatcher(padString, unisets::EMPTY) {} + +bool PaddingMatcher::isFlexible() const { + return true; +} + +bool PaddingMatcher::isDisabled(const ParsedNumber& result) const { + return false; +} + +void PaddingMatcher::accept(StringSegment& segment, ParsedNumber& result) const { + // No-op +} + + PercentMatcher::PercentMatcher(const DecimalFormatSymbols& dfs) : SymbolMatcher(dfs.getConstSymbol(DecimalFormatSymbols::kPercentSymbol), unisets::PERCENT_SIGN) { } diff --git a/icu4c/source/i18n/numparse_symbols.h b/icu4c/source/i18n/numparse_symbols.h index c8ba913911..40a57f02ba 100644 --- a/icu4c/source/i18n/numparse_symbols.h +++ b/icu4c/source/i18n/numparse_symbols.h @@ -15,6 +15,11 @@ U_NAMESPACE_BEGIN namespace numparse { namespace impl { +/** + * A base class for many matchers that performs a simple match against a UnicodeString and/or UnicodeSet. + * + * @author sffc + */ class SymbolMatcher : public NumberParseMatcher, public UMemory { public: SymbolMatcher() = default; // WARNING: Leaves the object in an unusable state @@ -52,6 +57,19 @@ class IgnorablesMatcher : public SymbolMatcher { }; +class InfinityMatcher : public SymbolMatcher { + public: + InfinityMatcher() = default; // WARNING: Leaves the object in an unusable state + + InfinityMatcher(const DecimalFormatSymbols& dfs); + + protected: + bool isDisabled(const ParsedNumber& result) const override; + + void accept(StringSegment& segment, ParsedNumber& result) const override; +}; + + class MinusSignMatcher : public SymbolMatcher { public: MinusSignMatcher() = default; // WARNING: Leaves the object in an unusable state @@ -83,6 +101,21 @@ class NanMatcher : public SymbolMatcher { }; +class PaddingMatcher : public SymbolMatcher { + public: + PaddingMatcher() = default; // WARNING: Leaves the object in an unusable state + + PaddingMatcher(const UnicodeString& padString); + + bool isFlexible() const override; + + protected: + bool isDisabled(const ParsedNumber& result) const override; + + void accept(StringSegment& segment, ParsedNumber& result) const override; +}; + + class PercentMatcher : public SymbolMatcher { public: PercentMatcher() = default; // WARNING: Leaves the object in an unusable state diff --git a/icu4c/source/i18n/numparse_unisets.cpp b/icu4c/source/i18n/numparse_unisets.cpp index f259f7a646..625e1ac31d 100644 --- a/icu4c/source/i18n/numparse_unisets.cpp +++ b/icu4c/source/i18n/numparse_unisets.cpp @@ -5,6 +5,10 @@ #if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +// Allow implicit conversion from char16_t* to UnicodeString for this file +// (useful for UnicodeSet constructor) +#define UNISTR_FROM_STRING_EXPLICIT + #include "numparse_unisets.h" #include "numparse_types.h" #include "umutex.h" @@ -56,44 +60,42 @@ UBool U_CALLCONV cleanupNumberParseUniSets() { void U_CALLCONV initNumberParseUniSets(UErrorCode& status) { ucln_i18n_registerCleanup(UCLN_I18N_NUMPARSE_UNISETS, cleanupNumberParseUniSets); -#define NEW_UNISET(pattern, status) new UnicodeSet(UnicodeString(pattern), status) gUnicodeSets[EMPTY] = new UnicodeSet(); // BiDi characters are skipped over and ignored at any point in the string, even in strict mode. - gUnicodeSets[BIDI] = NEW_UNISET(u"[[\\u200E\\u200F\\u061C]]", status); + gUnicodeSets[BIDI] = new UnicodeSet(u"[[\\u200E\\u200F\\u061C]]", status); // This set was decided after discussion with icu-design@. See ticket #13309. // Zs+TAB is "horizontal whitespace" according to UTS #18 (blank property). - gUnicodeSets[WHITESPACE] = NEW_UNISET(u"[[:Zs:][\\u0009]]", status); + gUnicodeSets[WHITESPACE] = new UnicodeSet(u"[[:Zs:][\\u0009]]", status); gUnicodeSets[DEFAULT_IGNORABLES] = computeUnion(BIDI, WHITESPACE); gUnicodeSets[STRICT_IGNORABLES] = new UnicodeSet(*gUnicodeSets[BIDI]); // TODO: Re-generate these sets from the UCD. They probably haven't been updated in a while. - gUnicodeSets[COMMA] = NEW_UNISET(u"[,،٫、︐︑﹐﹑,、]", status); - gUnicodeSets[STRICT_COMMA] = NEW_UNISET(u"[,٫︐﹐,]", status); - gUnicodeSets[PERIOD] = NEW_UNISET(u"[.․。︒﹒.。]", status); - gUnicodeSets[STRICT_PERIOD] = NEW_UNISET(u"[.․﹒.。]", status); - gUnicodeSets[OTHER_GROUPING_SEPARATORS] = NEW_UNISET( - u"['٬‘’'\\u0020\\u00A0\\u2000-\\u200A\\u202F\\u205F\\u3000]", - status); + gUnicodeSets[COMMA] = new UnicodeSet(u"[,،٫、︐︑﹐﹑,、]", status); + gUnicodeSets[STRICT_COMMA] = new UnicodeSet(u"[,٫︐﹐,]", status); + gUnicodeSets[PERIOD] = new UnicodeSet(u"[.․。︒﹒.。]", status); + gUnicodeSets[STRICT_PERIOD] = new UnicodeSet(u"[.․﹒.。]", status); + gUnicodeSets[OTHER_GROUPING_SEPARATORS] = new UnicodeSet( + u"['٬‘’'\\u0020\\u00A0\\u2000-\\u200A\\u202F\\u205F\\u3000]", status); gUnicodeSets[ALL_SEPARATORS] = computeUnion(COMMA, PERIOD, OTHER_GROUPING_SEPARATORS); gUnicodeSets[STRICT_ALL_SEPARATORS] = computeUnion( STRICT_COMMA, STRICT_PERIOD, OTHER_GROUPING_SEPARATORS); - gUnicodeSets[MINUS_SIGN] = NEW_UNISET(u"[-⁻₋−➖﹣-]", status); - gUnicodeSets[PLUS_SIGN] = NEW_UNISET(u"[+⁺₊➕﬩﹢+]", status); + gUnicodeSets[MINUS_SIGN] = new UnicodeSet(u"[-⁻₋−➖﹣-]", status); + gUnicodeSets[PLUS_SIGN] = new UnicodeSet(u"[+⁺₊➕﬩﹢+]", status); - gUnicodeSets[PERCENT_SIGN] = NEW_UNISET(u"[%٪]", status); - gUnicodeSets[PERMILLE_SIGN] = NEW_UNISET(u"[‰؉]", status); - gUnicodeSets[INFINITY] = NEW_UNISET(u"[∞]", status); + gUnicodeSets[PERCENT_SIGN] = new UnicodeSet(u"[%٪]", status); + gUnicodeSets[PERMILLE_SIGN] = new UnicodeSet(u"[‰؉]", status); + gUnicodeSets[INFINITY] = new UnicodeSet(u"[∞]", status); - gUnicodeSets[DIGITS] = NEW_UNISET(u"[:digit:]", status); - gUnicodeSets[NAN_LEAD] = NEW_UNISET(u"[NnТтmeՈոс¤НнчTtsҳ\u975e\u1002\u0e9a\u10d0\u0f68\u0644\u0646]", - status); - gUnicodeSets[SCIENTIFIC_LEAD] = NEW_UNISET(u"[Ee×·е\u0627]", status); - gUnicodeSets[CWCF] = NEW_UNISET(u"[:CWCF:]", status); + gUnicodeSets[DIGITS] = new UnicodeSet(u"[:digit:]", status); + gUnicodeSets[NAN_LEAD] = new UnicodeSet( + u"[NnТтmeՈոс¤НнчTtsҳ\u975e\u1002\u0e9a\u10d0\u0f68\u0644\u0646]", status); + gUnicodeSets[SCIENTIFIC_LEAD] = new UnicodeSet(u"[Ee×·е\u0627]", status); + gUnicodeSets[CWCF] = new UnicodeSet(u"[:CWCF:]", status); gUnicodeSets[DIGITS_OR_ALL_SEPARATORS] = computeUnion(DIGITS, ALL_SEPARATORS); gUnicodeSets[DIGITS_OR_STRICT_ALL_SEPARATORS] = computeUnion(DIGITS, STRICT_ALL_SEPARATORS); diff --git a/icu4c/source/test/intltest/numbertest_parse.cpp b/icu4c/source/test/intltest/numbertest_parse.cpp index 4e091048d8..018296a2c4 100644 --- a/icu4c/source/test/intltest/numbertest_parse.cpp +++ b/icu4c/source/test/intltest/numbertest_parse.cpp @@ -61,6 +61,10 @@ void NumberParserTest::testBasic() { {3, u"‰51423", u"0", 6, 51.423}, {3, u"51423‰", u"0", 6, 51.423}, {3, u"51423‰‰", u"0", 6, 51.423}, + {3, u"∞", u"0", 1, INFINITY}, + {3, u"-∞", u"0", 2, -INFINITY}, + {3, u"@@@123 @@", u"0", 6, 123.}, // TODO: Should padding be strong instead of weak? + {3, u"@@@123@@ ", u"0", 6, 123.}, // TODO: Should padding be strong instead of weak? // {3, u"a51423US dollars", u"a0¤¤¤", 16, 51423.}, // {3, u"a 51423 US dollars", u"a0¤¤¤", 18, 51423.}, // {3, u"514.23 USD", u"¤0", 10, 514.23}, diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/InfinityMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/InfinityMatcher.java index 843a1c7db1..2317435947 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/InfinityMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/InfinityMatcher.java @@ -42,6 +42,6 @@ public class InfinityMatcher extends SymbolMatcher { @Override public String toString() { - return ""; + return ""; } } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java index ae5650c311..5060b9518d 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java @@ -92,6 +92,8 @@ public class NumberParserImpl { parser.addMatcher(PercentMatcher.getInstance(symbols)); parser.addMatcher(PermilleMatcher.getInstance(symbols)); parser.addMatcher(NanMatcher.getInstance(symbols, parseFlags)); + parser.addMatcher(InfinityMatcher.getInstance(symbols)); + parser.addMatcher(PaddingMatcher.getInstance("@")); parser.addMatcher(ScientificMatcher.getInstance(symbols, grouper)); parser.addMatcher(CurrencyTrieMatcher.getInstance(locale)); parser.addMatcher(new RequireNumberMatcher()); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/SymbolMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/SymbolMatcher.java index bf15d726b7..94f3035574 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/SymbolMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/SymbolMatcher.java @@ -5,8 +5,9 @@ package com.ibm.icu.impl.number.parse; import com.ibm.icu.text.UnicodeSet; /** - * @author sffc + * A base class for many matchers that performs a simple match against a UnicodeString and/or UnicodeSet. * + * @author sffc */ public abstract class SymbolMatcher implements NumberParseMatcher { protected final String string; diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java index 541ead1945..912529479a 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java @@ -67,6 +67,10 @@ public class NumberParserTest { { 3, "‰51423", "0", 6, 51.423 }, { 3, "51423‰", "0", 6, 51.423 }, { 3, "51423‰‰", "0", 6, 51.423 }, + { 3, "∞", "0", 1, Double.POSITIVE_INFINITY }, + { 3, "-∞", "0", 2, Double.NEGATIVE_INFINITY }, + { 3, "@@@123 @@", "0", 6, 123. }, // TODO: Should padding be strong instead of weak? + { 3, "@@@123@@ ", "0", 6, 123. }, // TODO: Should padding be strong instead of weak? { 3, "a51423US dollars", "a0¤¤¤", 16, 51423. }, { 3, "a 51423 US dollars", "a0¤¤¤", 18, 51423. }, { 3, "514.23 USD", "¤0", 10, 514.23 }, From e91ff603debb71799d5182fccfd9358326fb8b4d Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Fri, 9 Feb 2018 06:57:37 +0000 Subject: [PATCH 009/129] ICU-13574 Adding scientific matcher to ICU4C. X-SVN-Rev: 40880 --- icu4c/source/i18n/Makefile.in | 2 +- icu4c/source/i18n/numparse_impl.cpp | 2 +- icu4c/source/i18n/numparse_impl.h | 2 + icu4c/source/i18n/numparse_scientific.cpp | 84 +++++++++++++++++++ icu4c/source/i18n/numparse_scientific.h | 41 +++++++++ .../source/test/intltest/numbertest_parse.cpp | 6 +- 6 files changed, 132 insertions(+), 5 deletions(-) create mode 100644 icu4c/source/i18n/numparse_scientific.cpp create mode 100644 icu4c/source/i18n/numparse_scientific.h diff --git a/icu4c/source/i18n/Makefile.in b/icu4c/source/i18n/Makefile.in index 94dac4e235..75c5565d4d 100644 --- a/icu4c/source/i18n/Makefile.in +++ b/icu4c/source/i18n/Makefile.in @@ -109,7 +109,7 @@ number_integerwidth.o number_longnames.o number_modifiers.o number_notation.o \ number_padding.o number_patternmodifier.o number_patternstring.o \ number_rounding.o number_scientific.o number_stringbuilder.o \ numparse_stringsegment.o numparse_unisets.o numparse_parsednumber.o \ -numparse_impl.o numparse_symbols.o numparse_decimal.o +numparse_impl.o numparse_symbols.o numparse_decimal.o numparse_scientific.o ## Header files to install diff --git a/icu4c/source/i18n/numparse_impl.cpp b/icu4c/source/i18n/numparse_impl.cpp index 575e0e1679..68707439fa 100644 --- a/icu4c/source/i18n/numparse_impl.cpp +++ b/icu4c/source/i18n/numparse_impl.cpp @@ -57,7 +57,7 @@ NumberParserImpl::createSimpleParser(const Locale& locale, const UnicodeString& parser->addMatcher(parser->fLocalMatchers.nan = {symbols}); parser->addMatcher(parser->fLocalMatchers.infinity = {symbols}); parser->addMatcher(parser->fLocalMatchers.padding = {u"@"}); -// parser.addMatcher(ScientificMatcher.getInstance(symbols, grouper, parseFlags)); + parser->addMatcher(parser->fLocalMatchers.scientific = {symbols, grouper}); // parser.addMatcher(CurrencyTrieMatcher.getInstance(locale)); // parser.addMatcher(new RequireNumberMatcher()); diff --git a/icu4c/source/i18n/numparse_impl.h b/icu4c/source/i18n/numparse_impl.h index 3f9b5d4b35..4745bf152a 100644 --- a/icu4c/source/i18n/numparse_impl.h +++ b/icu4c/source/i18n/numparse_impl.h @@ -10,6 +10,7 @@ #include "numparse_types.h" #include "numparse_decimal.h" #include "numparse_symbols.h" +#include "numparse_scientific.h" #include "unicode/uniset.h" U_NAMESPACE_BEGIN namespace numparse { @@ -54,6 +55,7 @@ class NumberParserImpl { PermilleMatcher permille; PlusSignMatcher plusSign; DecimalMatcher decimal; + ScientificMatcher scientific; } fLocalMatchers; NumberParserImpl(parse_flags_t parseFlags, bool computeLeads); diff --git a/icu4c/source/i18n/numparse_scientific.cpp b/icu4c/source/i18n/numparse_scientific.cpp new file mode 100644 index 0000000000..3b69dcdc99 --- /dev/null +++ b/icu4c/source/i18n/numparse_scientific.cpp @@ -0,0 +1,84 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT + +#include "numparse_types.h" +#include "numparse_scientific.h" +#include "numparse_unisets.h" + +using namespace icu; +using namespace icu::numparse; +using namespace icu::numparse::impl; + + +ScientificMatcher::ScientificMatcher(const DecimalFormatSymbols& dfs, const Grouper& grouper) + : fExponentSeparatorString(dfs.getConstSymbol(DecimalFormatSymbols::kExponentialSymbol)), + fExponentMatcher(dfs, grouper, PARSE_FLAG_INTEGER_ONLY) { +} + +bool ScientificMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const { + // Only accept scientific notation after the mantissa. + // Most places use result.hasNumber(), but we need a stronger condition here (i.e., exponent is + // not well-defined after NaN or infinity). + if (result.quantity.bogus) { + return false; + } + + // First match the scientific separator, and then match another number after it. + int overlap1 = segment.getCommonPrefixLength(fExponentSeparatorString); + if (overlap1 == fExponentSeparatorString.length()) { + // Full exponent separator match. + + // First attempt to get a code point, returning true if we can't get one. + segment.adjustOffset(overlap1); + if (segment.length() == 0) { + return true; + } + + // Allow a sign, and then try to match digits. + int8_t exponentSign = 1; + if (segment.matches(*unisets::get(unisets::MINUS_SIGN))) { + exponentSign = -1; + segment.adjustOffsetByCodePoint(); + } else if (segment.matches(*unisets::get(unisets::PLUS_SIGN))) { + segment.adjustOffsetByCodePoint(); + } + + int digitsOffset = segment.getOffset(); + bool digitsReturnValue = fExponentMatcher.match(segment, result, exponentSign, status); + if (segment.getOffset() != digitsOffset) { + // At least one exponent digit was matched. + result.flags |= FLAG_HAS_EXPONENT; + } else { + // No exponent digits were matched; un-match the exponent separator. + segment.adjustOffset(-overlap1); + } + return digitsReturnValue; + + } else if (overlap1 == segment.length()) { + // Partial exponent separator match + return true; + } + + // No match + return false; +} + +const UnicodeSet* ScientificMatcher::getLeadCodePoints() const { + UChar32 leadCp = fExponentSeparatorString.char32At(0); + const UnicodeSet* s = unisets::get(unisets::SCIENTIFIC_LEAD); + if (s->contains(leadCp)) { + return new UnicodeSet(*s); + } else { + UnicodeSet* leadCodePoints = new UnicodeSet(); + leadCodePoints->add(leadCp); + leadCodePoints->freeze(); + return leadCodePoints; + } +} + + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/numparse_scientific.h b/icu4c/source/i18n/numparse_scientific.h new file mode 100644 index 0000000000..544386c7c3 --- /dev/null +++ b/icu4c/source/i18n/numparse_scientific.h @@ -0,0 +1,41 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#ifndef __NUMPARSE_SCIENTIFIC_H__ +#define __NUMPARSE_SCIENTIFIC_H__ + +#include "numparse_types.h" +#include "numparse_decimal.h" +#include "unicode/numberformatter.h" + +using icu::number::impl::Grouper; + +U_NAMESPACE_BEGIN namespace numparse { +namespace impl { + + +class ScientificMatcher : public NumberParseMatcher, public UMemory { + public: + ScientificMatcher() = default; // WARNING: Leaves the object in an unusable state + + ScientificMatcher(const DecimalFormatSymbols& dfs, const Grouper& grouper); + + bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; + + const UnicodeSet* getLeadCodePoints() const override; + + private: + UnicodeString fExponentSeparatorString; + DecimalMatcher fExponentMatcher; +}; + + +} // namespace impl +} // namespace numparse +U_NAMESPACE_END + +#endif //__NUMPARSE_SCIENTIFIC_H__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/test/intltest/numbertest_parse.cpp b/icu4c/source/test/intltest/numbertest_parse.cpp index 018296a2c4..76e193a04b 100644 --- a/icu4c/source/test/intltest/numbertest_parse.cpp +++ b/icu4c/source/test/intltest/numbertest_parse.cpp @@ -85,9 +85,9 @@ void NumberParserTest::testBasic() { // {3, u"{𝟱𝟭𝟰𝟮𝟯}", u"{0};{0}", 12, 51423.}, // {1, u"a40b", u"a0'0b'", 3, 40.}, // greedy code path thinks "40" is the number // {2, u"a40b", u"a0'0b'", 4, 4.}, // slow code path finds the suffix "0b" -// {3, u"𝟱.𝟭𝟰𝟮E𝟯", u"0", 12, 5142.}, -// {3, u"𝟱.𝟭𝟰𝟮E-𝟯", u"0", 13, 0.005142}, -// {3, u"𝟱.𝟭𝟰𝟮e-𝟯", u"0", 13, 0.005142}, + {3, u"𝟱.𝟭𝟰𝟮E𝟯", u"0", 12, 5142.}, + {3, u"𝟱.𝟭𝟰𝟮E-𝟯", u"0", 13, 0.005142}, + {3, u"𝟱.𝟭𝟰𝟮e-𝟯", u"0", 13, 0.005142}, // {7, u"5,142.50 Canadian dollars", u"#,##,##0 ¤¤¤", 25, 5142.5}, // {3, u"a$ b5", u"a ¤ b0", 5, 5.0}, // {3, u"📺1.23", u"📺0;📻0", 6, 1.23}, From 852897ba2c13b6309ded2e99b72f64427cc14ddf Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Sat, 10 Feb 2018 02:59:49 +0000 Subject: [PATCH 010/129] ICU-13574 Adding currency names matcher to ICU4C. X-SVN-Rev: 40889 --- icu4c/source/common/ucurr.cpp | 125 +++++++++++++----- icu4c/source/common/ucurrimp.h | 17 +++ icu4c/source/i18n/Makefile.in | 3 +- icu4c/source/i18n/decimfmt.cpp | 3 +- icu4c/source/i18n/numparse_currency.cpp | 67 ++++++++++ icu4c/source/i18n/numparse_currency.h | 47 +++++++ icu4c/source/i18n/numparse_impl.cpp | 18 ++- icu4c/source/i18n/numparse_impl.h | 6 +- icu4c/source/i18n/numparse_parsednumber.cpp | 2 +- icu4c/source/i18n/numparse_types.h | 2 +- .../source/test/intltest/numbertest_parse.cpp | 6 +- 11 files changed, 255 insertions(+), 41 deletions(-) create mode 100644 icu4c/source/i18n/numparse_currency.cpp create mode 100644 icu4c/source/i18n/numparse_currency.h diff --git a/icu4c/source/common/ucurr.cpp b/icu4c/source/common/ucurr.cpp index 37c3d79e4d..6ce53c2d5e 100644 --- a/icu4c/source/common/ucurr.cpp +++ b/icu4c/source/common/ucurr.cpp @@ -16,6 +16,8 @@ #include "unicode/ures.h" #include "unicode/ustring.h" #include "unicode/parsepos.h" +#include "unicode/uniset.h" +#include "unicode/utf16.h" #include "ustr_imp.h" #include "charstr.h" #include "cmemory.h" @@ -1287,17 +1289,28 @@ static void linearSearch(const CurrencyNameStruct* currencyNames, int32_t begin, int32_t end, const UChar* text, int32_t textLen, + int32_t *partialMatchLen, int32_t *maxMatchLen, int32_t* maxMatchIndex) { + int32_t initialPartialMatchLen = *partialMatchLen; for (int32_t index = begin; index <= end; ++index) { int32_t len = currencyNames[index].currencyNameLen; if (len > *maxMatchLen && len <= textLen && uprv_memcmp(currencyNames[index].currencyName, text, len * sizeof(UChar)) == 0) { + *partialMatchLen = MAX(*partialMatchLen, len); *maxMatchIndex = index; *maxMatchLen = len; #ifdef UCURR_DEBUG printf("maxMatchIndex = %d, maxMatchLen = %d\n", *maxMatchIndex, *maxMatchLen); #endif + } else { + // Check for partial matches. + for (int32_t i=initialPartialMatchLen; icurrencyNames; - total_currency_name_count = cacheEntry->totalCurrencyNameCount; - currencySymbols = cacheEntry->currencySymbols; - total_currency_symbol_count = cacheEntry->totalCurrencySymbolCount; ++(cacheEntry->refCount); } umtx_unlock(&gCurrencyCacheMutex); if (found == -1) { collectCurrencyNames(locale, ¤cyNames, &total_currency_name_count, ¤cySymbols, &total_currency_symbol_count, ec); if (U_FAILURE(ec)) { - return; + return NULL; } umtx_lock(&gCurrencyCacheMutex); // check again. @@ -1505,15 +1511,45 @@ uprv_parseCurrency(const char* locale, deleteCurrencyNames(currencyNames, total_currency_name_count); deleteCurrencyNames(currencySymbols, total_currency_symbol_count); cacheEntry = currCache[found]; - currencyNames = cacheEntry->currencyNames; - total_currency_name_count = cacheEntry->totalCurrencyNameCount; - currencySymbols = cacheEntry->currencySymbols; - total_currency_symbol_count = cacheEntry->totalCurrencySymbolCount; ++(cacheEntry->refCount); } umtx_unlock(&gCurrencyCacheMutex); } + return cacheEntry; +} + +static void releaseCacheEntry(CurrencyNameCacheEntry* cacheEntry) { + umtx_lock(&gCurrencyCacheMutex); + --(cacheEntry->refCount); + if (cacheEntry->refCount == 0) { // remove + deleteCacheEntry(cacheEntry); + } + umtx_unlock(&gCurrencyCacheMutex); +} + +U_CAPI void +uprv_parseCurrency(const char* locale, + const icu::UnicodeString& text, + icu::ParsePosition& pos, + int8_t type, + int32_t* partialMatchLen, + UChar* result, + UErrorCode& ec) { + U_NAMESPACE_USE + if (U_FAILURE(ec)) { + return; + } + CurrencyNameCacheEntry* cacheEntry = getCacheEntry(locale, ec); + if (U_FAILURE(ec)) { + return; + } + + int32_t total_currency_name_count = cacheEntry->totalCurrencyNameCount; + CurrencyNameStruct* currencyNames = cacheEntry->currencyNames; + int32_t total_currency_symbol_count = cacheEntry->totalCurrencySymbolCount; + CurrencyNameStruct* currencySymbols = cacheEntry->currencySymbols; + int32_t start = pos.getIndex(); UChar inputText[MAX_CURRENCY_NAME_LEN]; @@ -1523,11 +1559,14 @@ uprv_parseCurrency(const char* locale, UErrorCode ec1 = U_ZERO_ERROR; textLen = u_strToUpper(upperText, MAX_CURRENCY_NAME_LEN, inputText, textLen, locale, &ec1); + // Make sure partialMatchLen is initialized + *partialMatchLen = 0; + int32_t max = 0; int32_t matchIndex = -1; // case in-sensitive comparision against currency names searchCurrencyName(currencyNames, total_currency_name_count, - upperText, textLen, &max, &matchIndex); + upperText, textLen, partialMatchLen, &max, &matchIndex); #ifdef UCURR_DEBUG printf("search in names, max = %d, matchIndex = %d\n", max, matchIndex); @@ -1538,7 +1577,8 @@ uprv_parseCurrency(const char* locale, if (type != UCURR_LONG_NAME) { // not name only // case sensitive comparison against currency symbols and ISO code. searchCurrencyName(currencySymbols, total_currency_symbol_count, - inputText, textLen, + inputText, textLen, + partialMatchLen, &maxInSymbol, &matchIndexInSymbol); } @@ -1555,15 +1595,38 @@ uprv_parseCurrency(const char* locale, } else if (maxInSymbol >= max && matchIndexInSymbol != -1) { u_charsToUChars(currencySymbols[matchIndexInSymbol].IsoCode, result, 4); pos.setIndex(start + maxInSymbol); - } + } // decrease reference count - umtx_lock(&gCurrencyCacheMutex); - --(cacheEntry->refCount); - if (cacheEntry->refCount == 0) { // remove - deleteCacheEntry(cacheEntry); + releaseCacheEntry(cacheEntry); +} + +void uprv_currencyLeads(const char* locale, icu::UnicodeSet& result, UErrorCode& ec) { + U_NAMESPACE_USE + if (U_FAILURE(ec)) { + return; } - umtx_unlock(&gCurrencyCacheMutex); + CurrencyNameCacheEntry* cacheEntry = getCacheEntry(locale, ec); + if (U_FAILURE(ec)) { + return; + } + + for (int32_t i=0; itotalCurrencySymbolCount; i++) { + const CurrencyNameStruct& info = cacheEntry->currencySymbols[i]; + UChar32 cp; + U16_GET(info.currencyName, 0, 0, info.currencyNameLen, cp); + result.add(cp); + } + + for (int32_t i=0; itotalCurrencyNameCount; i++) { + const CurrencyNameStruct& info = cacheEntry->currencyNames[i]; + UChar32 cp; + U16_GET(info.currencyName, 0, 0, info.currencyNameLen, cp); + result.add(cp); + } + + // decrease reference count + releaseCacheEntry(cacheEntry); } diff --git a/icu4c/source/common/ucurrimp.h b/icu4c/source/common/ucurrimp.h index 6e468fd4c9..6d9588295d 100644 --- a/icu4c/source/common/ucurrimp.h +++ b/icu4c/source/common/ucurrimp.h @@ -13,6 +13,7 @@ #include "unicode/utypes.h" #include "unicode/unistr.h" #include "unicode/parsepos.h" +#include "unicode/uniset.h" /** * Internal method. Given a currency ISO code and a locale, return @@ -36,6 +37,8 @@ uprv_getStaticCurrencyName(const UChar* iso, const char* loc, * match, then the display name is preferred, unless it's length * is less than 3. * + * The parameters must not be NULL. + * * @param locale the locale of the display names to match * @param text the text to parse * @param pos input-output position; on input, the position within @@ -43,6 +46,8 @@ uprv_getStaticCurrencyName(const UChar* iso, const char* loc, * on output, the position after the last matched character. If * the parse fails, the position in unchanged upon output. * @param type currency type to parse against, LONG_NAME only or not + * @param partialMatchLen The length of the longest matching prefix; + * this may be nonzero even if no full currency was matched. * @return the ISO 4217 code, as a string, of the best match, or * null if there is no match * @@ -53,9 +58,21 @@ uprv_parseCurrency(const char* locale, const icu::UnicodeString& text, icu::ParsePosition& pos, int8_t type, + int32_t* partialMatchLen, UChar* result, UErrorCode& ec); +/** + * Puts all possible first-characters of a currency into the + * specified UnicodeSet. + * + * @param locale the locale of the display names of interest + * @param result the UnicodeSet to which to add the starting characters + */ +void uprv_currencyLeads(const char* locale, icu::UnicodeSet& result, UErrorCode& ec); + + + #endif /* #ifndef _UCURR_IMP_H_ */ //eof diff --git a/icu4c/source/i18n/Makefile.in b/icu4c/source/i18n/Makefile.in index 75c5565d4d..a24adbeb08 100644 --- a/icu4c/source/i18n/Makefile.in +++ b/icu4c/source/i18n/Makefile.in @@ -109,7 +109,8 @@ number_integerwidth.o number_longnames.o number_modifiers.o number_notation.o \ number_padding.o number_patternmodifier.o number_patternstring.o \ number_rounding.o number_scientific.o number_stringbuilder.o \ numparse_stringsegment.o numparse_unisets.o numparse_parsednumber.o \ -numparse_impl.o numparse_symbols.o numparse_decimal.o numparse_scientific.o +numparse_impl.o numparse_symbols.o numparse_decimal.o numparse_scientific.o \ +numparse_currency.o ## Header files to install diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp index 3861db3df6..713abd7f9f 100644 --- a/icu4c/source/i18n/decimfmt.cpp +++ b/icu4c/source/i18n/decimfmt.cpp @@ -2171,10 +2171,11 @@ int32_t DecimalFormat::compareComplexAffix(const UnicodeString& affixPat, // determine our locale. const char* loc = fCurrencyPluralInfo->getLocale().getName(); ParsePosition ppos(pos); + int32_t currMatchLen = 0; UChar curr[4]; UErrorCode ec = U_ZERO_ERROR; // Delegate parse of display name => ISO code to Currency - uprv_parseCurrency(loc, text, ppos, type, curr, ec); + uprv_parseCurrency(loc, text, ppos, type, &currMatchLen, curr, ec); // If parse succeeds, populate currency[0] if (U_SUCCESS(ec) && ppos.getIndex() != pos) { diff --git a/icu4c/source/i18n/numparse_currency.cpp b/icu4c/source/i18n/numparse_currency.cpp new file mode 100644 index 0000000000..7a78a3bbb7 --- /dev/null +++ b/icu4c/source/i18n/numparse_currency.cpp @@ -0,0 +1,67 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT + +#include "numparse_types.h" +#include "numparse_currency.h" +#include "ucurrimp.h" +#include "unicode/errorcode.h" + +using namespace icu; +using namespace icu::numparse; +using namespace icu::numparse::impl; + + +CurrencyNamesMatcher::CurrencyNamesMatcher(const Locale& locale, UErrorCode& status) + : fLocaleName(locale.getName(), -1, status) {} + +bool CurrencyNamesMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const { + if (result.currencyCode[0] != 0) { + return false; + } + + // NOTE: This requires a new UnicodeString to be allocated, instead of using the StringSegment. + // This should be fixed with #13584. + UnicodeString segmentString = segment.toUnicodeString(); + + // Try to parse the currency + ParsePosition ppos(0); + int32_t partialMatchLen = 0; + uprv_parseCurrency( + fLocaleName.data(), + segmentString, + ppos, + UCURR_SYMBOL_NAME, // checks for both UCURR_SYMBOL_NAME and UCURR_LONG_NAME + &partialMatchLen, + result.currencyCode, + status); + + // Possible partial match + bool partialMatch = partialMatchLen == segment.length(); + + if (U_SUCCESS(status) && ppos.getIndex() != 0) { + // Complete match. + // NOTE: The currency code should already be saved in the ParsedNumber. + segment.adjustOffset(ppos.getIndex()); + result.setCharsConsumed(segment); + } + + return partialMatch; +} + +const UnicodeSet* CurrencyNamesMatcher::getLeadCodePoints() const { + ErrorCode status; + UnicodeSet* leadCodePoints = new UnicodeSet(); + uprv_currencyLeads(fLocaleName.data(), *leadCodePoints, status); + // Always apply case mapping closure for currencies + leadCodePoints->closeOver(USET_ADD_CASE_MAPPINGS); + leadCodePoints->freeze(); + + return leadCodePoints; +} + + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/numparse_currency.h b/icu4c/source/i18n/numparse_currency.h new file mode 100644 index 0000000000..49b367a896 --- /dev/null +++ b/icu4c/source/i18n/numparse_currency.h @@ -0,0 +1,47 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#ifndef __NUMPARSE_CURRENCY_H__ +#define __NUMPARSE_CURRENCY_H__ + +#include "numparse_types.h" +#include "charstr.h" + +U_NAMESPACE_BEGIN namespace numparse { +namespace impl { + + +/** + * Matches currencies according to all available strings in locale data. + * + * The implementation of this class is different between J and C. See #13584 for a follow-up. + * + * @author sffc + */ +class CurrencyNamesMatcher : public NumberParseMatcher, public UMemory { + public: + CurrencyNamesMatcher() = default; // WARNING: Leaves the object in an unusable state + + CurrencyNamesMatcher(const Locale& locale, UErrorCode& status); + + bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; + + const UnicodeSet* getLeadCodePoints() const override; + + private: + // We could use Locale instead of CharString here, but + // Locale has a non-trivial default constructor. + CharString fLocaleName; + +}; + + +} // namespace impl +} // namespace numparse +U_NAMESPACE_END + +#endif //__NUMPARSE_CURRENCY_H__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/numparse_impl.cpp b/icu4c/source/i18n/numparse_impl.cpp index 68707439fa..2fe84fcbc9 100644 --- a/icu4c/source/i18n/numparse_impl.cpp +++ b/icu4c/source/i18n/numparse_impl.cpp @@ -58,7 +58,7 @@ NumberParserImpl::createSimpleParser(const Locale& locale, const UnicodeString& parser->addMatcher(parser->fLocalMatchers.infinity = {symbols}); parser->addMatcher(parser->fLocalMatchers.padding = {u"@"}); parser->addMatcher(parser->fLocalMatchers.scientific = {symbols, grouper}); -// parser.addMatcher(CurrencyTrieMatcher.getInstance(locale)); + parser->addMatcher(parser->fLocalMatchers.currencyNames = {locale, status}); // parser.addMatcher(new RequireNumberMatcher()); parser->freeze(); @@ -91,12 +91,26 @@ void NumberParserImpl::addMatcher(const NumberParseMatcher& matcher) { fMatchers[fNumMatchers] = &matcher; if (fComputeLeads) { - fLeads[fNumMatchers] = matcher.getLeadCodePoints(); + addLeadCodePointsForMatcher(matcher); } fNumMatchers++; } +void NumberParserImpl::addLeadCodePointsForMatcher(const NumberParseMatcher& matcher) { + const UnicodeSet* leadCodePoints = matcher.getLeadCodePoints(); + // TODO: Avoid the clone operation here. + if (0 != (fParseFlags & PARSE_FLAG_IGNORE_CASE)) { + UnicodeSet* copy = static_cast(leadCodePoints->cloneAsThawed()); + delete leadCodePoints; + copy->closeOver(USET_ADD_CASE_MAPPINGS); + copy->freeze(); + fLeads[fNumMatchers] = copy; + } else { + fLeads[fNumMatchers] = leadCodePoints; + } +} + void NumberParserImpl::freeze() { fFrozen = true; } diff --git a/icu4c/source/i18n/numparse_impl.h b/icu4c/source/i18n/numparse_impl.h index 4745bf152a..0fe45fa5f4 100644 --- a/icu4c/source/i18n/numparse_impl.h +++ b/icu4c/source/i18n/numparse_impl.h @@ -12,6 +12,7 @@ #include "numparse_symbols.h" #include "numparse_scientific.h" #include "unicode/uniset.h" +#include "numparse_currency.h" U_NAMESPACE_BEGIN namespace numparse { namespace impl { @@ -43,7 +44,7 @@ class NumberParserImpl { bool fComputeLeads; bool fFrozen = false; - // WARNING: All of these matchers start in an uninitialized state. + // WARNING: All of these matchers start in an undefined state (default-constructed). // You must use an assignment operator on them before using. struct { IgnorablesMatcher ignorables; @@ -56,10 +57,13 @@ class NumberParserImpl { PlusSignMatcher plusSign; DecimalMatcher decimal; ScientificMatcher scientific; + CurrencyNamesMatcher currencyNames; } fLocalMatchers; NumberParserImpl(parse_flags_t parseFlags, bool computeLeads); + void addLeadCodePointsForMatcher(const NumberParseMatcher& matcher); + void parseGreedyRecursive(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const; void parseLongestRecursive(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const; diff --git a/icu4c/source/i18n/numparse_parsednumber.cpp b/icu4c/source/i18n/numparse_parsednumber.cpp index 203383692f..908cffecef 100644 --- a/icu4c/source/i18n/numparse_parsednumber.cpp +++ b/icu4c/source/i18n/numparse_parsednumber.cpp @@ -23,7 +23,7 @@ void ParsedNumber::clear() { flags = 0; prefix.setToBogus(); suffix.setToBogus(); - currencyCode.setToBogus(); + currencyCode[0] = 0; } void ParsedNumber::setCharsConsumed(const StringSegment& segment) { diff --git a/icu4c/source/i18n/numparse_types.h b/icu4c/source/i18n/numparse_types.h index 5280c41fec..30ad92d371 100644 --- a/icu4c/source/i18n/numparse_types.h +++ b/icu4c/source/i18n/numparse_types.h @@ -93,7 +93,7 @@ class ParsedNumber { /** * The currency that got consumed. */ - UnicodeString currencyCode; + UChar currencyCode[4]; ParsedNumber(); diff --git a/icu4c/source/test/intltest/numbertest_parse.cpp b/icu4c/source/test/intltest/numbertest_parse.cpp index 76e193a04b..4140320bc9 100644 --- a/icu4c/source/test/intltest/numbertest_parse.cpp +++ b/icu4c/source/test/intltest/numbertest_parse.cpp @@ -67,8 +67,8 @@ void NumberParserTest::testBasic() { {3, u"@@@123@@ ", u"0", 6, 123.}, // TODO: Should padding be strong instead of weak? // {3, u"a51423US dollars", u"a0¤¤¤", 16, 51423.}, // {3, u"a 51423 US dollars", u"a0¤¤¤", 18, 51423.}, -// {3, u"514.23 USD", u"¤0", 10, 514.23}, -// {3, u"514.23 GBP", u"¤0", 10, 514.23}, + {3, u"514.23 USD", u"¤0", 10, 514.23}, + {3, u"514.23 GBP", u"¤0", 10, 514.23}, // {3, u"a 𝟱𝟭𝟰𝟮𝟯 b", u"a0b", 14, 51423.}, // {3, u"-a 𝟱𝟭𝟰𝟮𝟯 b", u"a0b", 15, -51423.}, // {3, u"a -𝟱𝟭𝟰𝟮𝟯 b", u"a0b", 15, -51423.}, @@ -88,7 +88,7 @@ void NumberParserTest::testBasic() { {3, u"𝟱.𝟭𝟰𝟮E𝟯", u"0", 12, 5142.}, {3, u"𝟱.𝟭𝟰𝟮E-𝟯", u"0", 13, 0.005142}, {3, u"𝟱.𝟭𝟰𝟮e-𝟯", u"0", 13, 0.005142}, -// {7, u"5,142.50 Canadian dollars", u"#,##,##0 ¤¤¤", 25, 5142.5}, + {7, u"5,142.50 Canadian dollars", u"#,##,##0 ¤¤¤", 25, 5142.5}, // {3, u"a$ b5", u"a ¤ b0", 5, 5.0}, // {3, u"📺1.23", u"📺0;📻0", 6, 1.23}, // {3, u"📻1.23", u"📺0;📻0", 6, -1.23}, From 513f123a8c6d3fb415ab8a559e463a0492784b81 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Sat, 10 Feb 2018 06:36:07 +0000 Subject: [PATCH 011/129] ICU-13574 Adding composition matchers (SeriesMatcher and AnyMatcher) to ICU4C in preparation for affix matchers. Also re-working memory management in getLeadCodePoints(). X-SVN-Rev: 40890 --- icu4c/source/i18n/Makefile.in | 2 +- icu4c/source/i18n/numparse_affixes.cpp | 20 ++++ icu4c/source/i18n/numparse_affixes.h | 25 ++++ icu4c/source/i18n/numparse_compositions.cpp | 108 ++++++++++++++++++ icu4c/source/i18n/numparse_compositions.h | 100 ++++++++++++++++ icu4c/source/i18n/numparse_currency.cpp | 96 ++++++++++++++-- icu4c/source/i18n/numparse_currency.h | 42 ++++++- icu4c/source/i18n/numparse_decimal.cpp | 25 ++-- icu4c/source/i18n/numparse_decimal.h | 4 +- icu4c/source/i18n/numparse_impl.cpp | 14 +-- icu4c/source/i18n/numparse_impl.h | 4 +- icu4c/source/i18n/numparse_scientific.cpp | 13 ++- icu4c/source/i18n/numparse_scientific.h | 2 +- icu4c/source/i18n/numparse_symbols.cpp | 31 ++--- icu4c/source/i18n/numparse_symbols.h | 5 +- icu4c/source/i18n/numparse_types.h | 18 ++- icu4c/source/test/intltest/intltest.cpp | 29 +++++ icu4c/source/test/intltest/intltest.h | 3 + icu4c/source/test/intltest/numbertest.h | 1 + .../source/test/intltest/numbertest_parse.cpp | 52 ++++++++- ...atcher.java => CurrencyCustomMatcher.java} | 8 +- ...Matcher.java => CurrencyNamesMatcher.java} | 17 ++- .../icu/impl/number/parse/MatcherFactory.java | 15 +-- .../impl/number/parse/NumberParserImpl.java | 6 +- .../icu/dev/test/number/NumberParserTest.java | 35 ++++++ 25 files changed, 596 insertions(+), 79 deletions(-) create mode 100644 icu4c/source/i18n/numparse_affixes.cpp create mode 100644 icu4c/source/i18n/numparse_affixes.h create mode 100644 icu4c/source/i18n/numparse_compositions.cpp create mode 100644 icu4c/source/i18n/numparse_compositions.h rename icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/{CurrencyMatcher.java => CurrencyCustomMatcher.java} (86%) rename icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/{CurrencyTrieMatcher.java => CurrencyNamesMatcher.java} (77%) diff --git a/icu4c/source/i18n/Makefile.in b/icu4c/source/i18n/Makefile.in index a24adbeb08..d05b907c36 100644 --- a/icu4c/source/i18n/Makefile.in +++ b/icu4c/source/i18n/Makefile.in @@ -110,7 +110,7 @@ number_padding.o number_patternmodifier.o number_patternstring.o \ number_rounding.o number_scientific.o number_stringbuilder.o \ numparse_stringsegment.o numparse_unisets.o numparse_parsednumber.o \ numparse_impl.o numparse_symbols.o numparse_decimal.o numparse_scientific.o \ -numparse_currency.o +numparse_currency.o numparse_affixes.o numparse_compositions.o ## Header files to install diff --git a/icu4c/source/i18n/numparse_affixes.cpp b/icu4c/source/i18n/numparse_affixes.cpp new file mode 100644 index 0000000000..2ac929d37a --- /dev/null +++ b/icu4c/source/i18n/numparse_affixes.cpp @@ -0,0 +1,20 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT + +#include "numparse_types.h" +#include "numparse_affixes.h" + +using namespace icu; +using namespace icu::numparse; +using namespace icu::numparse::impl; + + + + + + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/numparse_affixes.h b/icu4c/source/i18n/numparse_affixes.h new file mode 100644 index 0000000000..677b50cea0 --- /dev/null +++ b/icu4c/source/i18n/numparse_affixes.h @@ -0,0 +1,25 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#ifndef __NUMPARSE_AFFIXES_H__ +#define __NUMPARSE_AFFIXES_H__ + +#include "numparse_types.h" + +U_NAMESPACE_BEGIN +namespace numparse { +namespace impl { + + + + + +} // namespace impl +} // namespace numparse +U_NAMESPACE_END + +#endif //__NUMPARSE_AFFIXES_H__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/numparse_compositions.cpp b/icu4c/source/i18n/numparse_compositions.cpp new file mode 100644 index 0000000000..5d4a92b988 --- /dev/null +++ b/icu4c/source/i18n/numparse_compositions.cpp @@ -0,0 +1,108 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT + +#include "numparse_types.h" +#include "numparse_compositions.h" +#include "unicode/uniset.h" + +using namespace icu; +using namespace icu::numparse; +using namespace icu::numparse::impl; + + +bool AnyMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const { + int32_t initialOffset = segment.getOffset(); + bool maybeMore = false; + + // NOTE: The range-based for loop calls the virtual begin() and end() methods. + for (auto* matcher : *this) { + maybeMore = maybeMore || matcher->match(segment, result, status); + if (segment.getOffset() != initialOffset) { + // Match succeeded. + // NOTE: Except for a couple edge cases, if a matcher accepted string A, then it will + // accept any string starting with A. Therefore, there is no possibility that matchers + // later in the list may be evaluated on longer strings, and we can exit the loop here. + break; + } + } + + // None of the matchers succeeded. + return maybeMore; +} + +void AnyMatcher::postProcess(ParsedNumber& result) const { + // NOTE: The range-based for loop calls the virtual begin() and end() methods. + for (auto* matcher : *this) { + matcher->postProcess(result); + } +} + + +bool SeriesMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const { + ParsedNumber backup(result); + + int32_t initialOffset = segment.getOffset(); + bool maybeMore = true; + for (auto* it = begin(); it < end();) { + const NumberParseMatcher* matcher = *it; + int matcherOffset = segment.getOffset(); + if (segment.length() != 0) { + maybeMore = matcher->match(segment, result, status); + } else { + // Nothing for this matcher to match; ask for more. + maybeMore = true; + } + + bool success = (segment.getOffset() != matcherOffset); + bool isFlexible = matcher->isFlexible(); + if (success && isFlexible) { + // Match succeeded, and this is a flexible matcher. Re-run it. + } else if (success) { + // Match succeeded, and this is NOT a flexible matcher. Proceed to the next matcher. + it++; + } else if (isFlexible) { + // Match failed, and this is a flexible matcher. Try again with the next matcher. + it++; + } else { + // Match failed, and this is NOT a flexible matcher. Exit. + segment.setOffset(initialOffset); + result = backup; + return maybeMore; + } + } + + // All matchers in the series succeeded. + return maybeMore; +} + +void SeriesMatcher::postProcess(ParsedNumber& result) const { + // NOTE: The range-based for loop calls the virtual begin() and end() methods. + for (auto* matcher : *this) { + matcher->postProcess(result); + } +} + + +ArraySeriesMatcher::ArraySeriesMatcher(NumberParseMatcher** matchers, int32_t matchersLen) + : fMatchers(matchers), fMatchersLen(matchersLen) {} + +const UnicodeSet& ArraySeriesMatcher::getLeadCodePoints() { + // SeriesMatchers are never allowed to start with a Flexible matcher. + U_ASSERT(!fMatchers[0]->isFlexible()); + return fMatchers[0]->getLeadCodePoints(); +} + +const NumberParseMatcher* const* ArraySeriesMatcher::begin() const { + return fMatchers.getAlias(); +} + +const NumberParseMatcher* const* ArraySeriesMatcher::end() const { + return fMatchers.getAlias() + fMatchersLen; +} + + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/numparse_compositions.h b/icu4c/source/i18n/numparse_compositions.h new file mode 100644 index 0000000000..b52bb2fd8a --- /dev/null +++ b/icu4c/source/i18n/numparse_compositions.h @@ -0,0 +1,100 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#ifndef __SOURCE_NUMPARSE_COMPOSITIONS__ +#define __SOURCE_NUMPARSE_COMPOSITIONS__ + +#include "numparse_types.h" + +U_NAMESPACE_BEGIN namespace numparse { +namespace impl { + + +/** + * Base class for AnyMatcher and SeriesMatcher. + */ +class CompositionMatcher : public NumberParseMatcher { + protected: + // No construction except by subclasses! + CompositionMatcher() = default; + + // To be overridden by subclasses (used for iteration): + virtual const NumberParseMatcher* const* begin() const = 0; + + // To be overridden by subclasses (used for iteration): + virtual const NumberParseMatcher* const* end() const = 0; +}; + + +/** + * Composes a number of matchers, and succeeds if any of the matchers succeed. Always greedily chooses + * the first matcher in the list to succeed. + * + * NOTE: In C++, this is a base class, unlike ICU4J, which uses a factory-style interface. + * + * @author sffc + * @see SeriesMatcher + */ +class AnyMatcher : public CompositionMatcher { + public: + bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; + + void postProcess(ParsedNumber& result) const override; + + protected: + // No construction except by subclasses! + AnyMatcher() = default; +}; + + +/** + * Composes a number of matchers, running one after another. Matches the input string only if all of the + * matchers in the series succeed. Performs greedy matches within the context of the series. + * + * @author sffc + * @see AnyMatcher + */ +class SeriesMatcher : public CompositionMatcher { + public: + bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; + + void postProcess(ParsedNumber& result) const override; + + protected: + // No construction except by subclasses! + SeriesMatcher() = default; +}; + + +/** + * An implementation of SeriesMatcher that references an array of matchers. + * + * The object adopts the array, but NOT the matchers contained inside the array. + */ +class ArraySeriesMatcher : public SeriesMatcher { + public: + /** The array is adopted, but NOT the matchers inside the array. */ + ArraySeriesMatcher(NumberParseMatcher** matchers, int32_t matchersLen); + + const UnicodeSet& getLeadCodePoints() override; + + protected: + const NumberParseMatcher* const* begin() const override; + + const NumberParseMatcher* const* end() const override; + + private: + LocalArray fMatchers; + int32_t fMatchersLen; +}; + + +} // namespace impl +} // namespace numparse +U_NAMESPACE_END + +#endif //__SOURCE_NUMPARSE_COMPOSITIONS__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/numparse_currency.cpp b/icu4c/source/i18n/numparse_currency.cpp index 7a78a3bbb7..90b6bed6dd 100644 --- a/icu4c/source/i18n/numparse_currency.cpp +++ b/icu4c/source/i18n/numparse_currency.cpp @@ -9,12 +9,23 @@ #include "numparse_currency.h" #include "ucurrimp.h" #include "unicode/errorcode.h" +#include "numparse_utils.h" using namespace icu; using namespace icu::numparse; using namespace icu::numparse::impl; +namespace { + +inline void copyCurrencyCode(UChar* dest, const UChar* src) { + uprv_memcpy(dest, src, sizeof(UChar) * 3); + dest[3] = 0; +} + +} + + CurrencyNamesMatcher::CurrencyNamesMatcher(const Locale& locale, UErrorCode& status) : fLocaleName(locale.getName(), -1, status) {} @@ -52,15 +63,84 @@ bool CurrencyNamesMatcher::match(StringSegment& segment, ParsedNumber& result, U return partialMatch; } -const UnicodeSet* CurrencyNamesMatcher::getLeadCodePoints() const { - ErrorCode status; - UnicodeSet* leadCodePoints = new UnicodeSet(); - uprv_currencyLeads(fLocaleName.data(), *leadCodePoints, status); - // Always apply case mapping closure for currencies - leadCodePoints->closeOver(USET_ADD_CASE_MAPPINGS); - leadCodePoints->freeze(); +const UnicodeSet& CurrencyNamesMatcher::getLeadCodePoints() { + if (fLocalLeadCodePoints.isNull()) { + ErrorCode status; + auto* leadCodePoints = new UnicodeSet(); + uprv_currencyLeads(fLocaleName.data(), *leadCodePoints, status); + // Always apply case mapping closure for currencies + leadCodePoints->closeOver(USET_ADD_CASE_MAPPINGS); + leadCodePoints->freeze(); + fLocalLeadCodePoints.adoptInstead(leadCodePoints); + } + return *fLocalLeadCodePoints; +} - return leadCodePoints; + +CurrencyCustomMatcher::CurrencyCustomMatcher(const char16_t* currencyCode, const UnicodeString& currency1, + const UnicodeString& currency2) + : fCurrency1(currency1), fCurrency2(currency2) { + copyCurrencyCode(fCurrencyCode, currencyCode); +} + +bool CurrencyCustomMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCode&) const { + if (result.currencyCode[0] != 0) { + return false; + } + + int overlap1 = segment.getCommonPrefixLength(fCurrency1); + if (overlap1 == fCurrency1.length()) { + copyCurrencyCode(result.currencyCode, fCurrencyCode); + segment.adjustOffset(overlap1); + result.setCharsConsumed(segment); + } + + int overlap2 = segment.getCommonPrefixLength(fCurrency2); + if (overlap2 == fCurrency2.length()) { + copyCurrencyCode(result.currencyCode, fCurrencyCode); + segment.adjustOffset(overlap2); + result.setCharsConsumed(segment); + } + + return overlap1 == segment.length() || overlap2 == segment.length(); +} + +const UnicodeSet& CurrencyCustomMatcher::getLeadCodePoints() { + if (fLocalLeadCodePoints.isNull()) { + auto* leadCodePoints = new UnicodeSet(); + utils::putLeadCodePoint(fCurrency1, leadCodePoints); + utils::putLeadCodePoint(fCurrency2, leadCodePoints); + leadCodePoints->freeze(); + fLocalLeadCodePoints.adoptInstead(leadCodePoints); + } + return *fLocalLeadCodePoints; +} + + +CurrencyAnyMatcher::CurrencyAnyMatcher(CurrencyNamesMatcher namesMatcher, + CurrencyCustomMatcher customMatcher) + : fNamesMatcher(std::move(namesMatcher)), fCustomMatcher(std::move(customMatcher)) { + fMatcherArray[0] = &fNamesMatcher; + fMatcherArray[1] = &fCustomMatcher; +} + +const UnicodeSet& CurrencyAnyMatcher::getLeadCodePoints() { + if (fLocalLeadCodePoints.isNull()) { + auto* leadCodePoints = new UnicodeSet(); + leadCodePoints->addAll(fNamesMatcher.getLeadCodePoints()); + leadCodePoints->addAll(fCustomMatcher.getLeadCodePoints()); + leadCodePoints->freeze(); + fLocalLeadCodePoints.adoptInstead(leadCodePoints); + } + return *fLocalLeadCodePoints; +} + +const NumberParseMatcher* const* CurrencyAnyMatcher::begin() const { + return fMatcherArray; +} + +const NumberParseMatcher* const* CurrencyAnyMatcher::end() const { + return fMatcherArray + 2; } diff --git a/icu4c/source/i18n/numparse_currency.h b/icu4c/source/i18n/numparse_currency.h index 49b367a896..f5f56c8600 100644 --- a/icu4c/source/i18n/numparse_currency.h +++ b/icu4c/source/i18n/numparse_currency.h @@ -8,6 +8,7 @@ #define __NUMPARSE_CURRENCY_H__ #include "numparse_types.h" +#include "numparse_compositions.h" #include "charstr.h" U_NAMESPACE_BEGIN namespace numparse { @@ -29,7 +30,7 @@ class CurrencyNamesMatcher : public NumberParseMatcher, public UMemory { bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; - const UnicodeSet* getLeadCodePoints() const override; + const UnicodeSet& getLeadCodePoints() override; private: // We could use Locale instead of CharString here, but @@ -39,6 +40,45 @@ class CurrencyNamesMatcher : public NumberParseMatcher, public UMemory { }; +class CurrencyCustomMatcher : public NumberParseMatcher, public UMemory { + public: + CurrencyCustomMatcher(const char16_t* currencyCode, const UnicodeString& currency1, + const UnicodeString& currency2); + + bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; + + const UnicodeSet& getLeadCodePoints() override; + + private: + UChar fCurrencyCode[4]; + UnicodeString fCurrency1; + UnicodeString fCurrency2; +}; + + +/** + * An implementation of AnyMatcher, allowing for either currency data or locale currency matches. + */ +class CurrencyAnyMatcher : public AnyMatcher, public UMemory { + public: + /** Calls std::move on the two arguments. */ + CurrencyAnyMatcher(CurrencyNamesMatcher namesMatcher, CurrencyCustomMatcher customMatcher); + + const UnicodeSet& getLeadCodePoints() override; + + protected: + const NumberParseMatcher* const* begin() const override; + + const NumberParseMatcher* const* end() const override; + + private: + CurrencyNamesMatcher fNamesMatcher; + CurrencyCustomMatcher fCustomMatcher; + + const NumberParseMatcher* fMatcherArray[2]; +}; + + } // namespace impl } // namespace numparse U_NAMESPACE_END diff --git a/icu4c/source/i18n/numparse_decimal.cpp b/icu4c/source/i18n/numparse_decimal.cpp index bfc9c4f8a7..e80014fa59 100644 --- a/icu4c/source/i18n/numparse_decimal.cpp +++ b/icu4c/source/i18n/numparse_decimal.cpp @@ -291,22 +291,25 @@ bool DecimalMatcher::match(StringSegment& segment, ParsedNumber& result, int8_t return segment.length() == 0 || hasPartialPrefix; } -const UnicodeSet* DecimalMatcher::getLeadCodePoints() const { +const UnicodeSet& DecimalMatcher::getLeadCodePoints() { if (fLocalDigitStrings.isNull() && leadSet != nullptr) { - return new UnicodeSet(*leadSet); + return *leadSet; } - auto* leadCodePoints = new UnicodeSet(); - // Assumption: the sets are all single code points. - leadCodePoints->addAll(*unisets::get(unisets::DIGITS)); - leadCodePoints->addAll(*separatorSet); - if (!fLocalDigitStrings.isNull()) { - for (int i = 0; i < 10; i++) { - utils::putLeadCodePoint(fLocalDigitStrings[i], leadCodePoints); + if (fLocalLeadCodePoints.isNull()) { + auto* leadCodePoints = new UnicodeSet(); + // Assumption: the sets are all single code points. + leadCodePoints->addAll(*unisets::get(unisets::DIGITS)); + leadCodePoints->addAll(*separatorSet); + if (!fLocalDigitStrings.isNull()) { + for (int i = 0; i < 10; i++) { + utils::putLeadCodePoint(fLocalDigitStrings[i], leadCodePoints); + } } + leadCodePoints->freeze(); + fLocalLeadCodePoints.adoptInstead(leadCodePoints); } - leadCodePoints->freeze(); - return leadCodePoints; + return *fLocalLeadCodePoints; } diff --git a/icu4c/source/i18n/numparse_decimal.h b/icu4c/source/i18n/numparse_decimal.h index 9423a7786c..203cb66b4b 100644 --- a/icu4c/source/i18n/numparse_decimal.h +++ b/icu4c/source/i18n/numparse_decimal.h @@ -27,7 +27,7 @@ class DecimalMatcher : public NumberParseMatcher, public UMemory { bool match(StringSegment& segment, ParsedNumber& result, int8_t exponentSign, UErrorCode& status) const; - const UnicodeSet* getLeadCodePoints() const override; + const UnicodeSet& getLeadCodePoints() override; private: /** If true, only accept strings whose grouping sizes match the locale */ @@ -56,7 +56,7 @@ class DecimalMatcher : public NumberParseMatcher, public UMemory { const UnicodeSet* leadSet; // Make this class the owner of a few objects that could be allocated. - // The first two LocalPointers are used for assigning ownership only. + // The first three LocalPointers are used for assigning ownership only. LocalPointer fLocalDecimalUniSet; LocalPointer fLocalSeparatorSet; LocalArray fLocalDigitStrings; diff --git a/icu4c/source/i18n/numparse_impl.cpp b/icu4c/source/i18n/numparse_impl.cpp index 2fe84fcbc9..efa9b3cab2 100644 --- a/icu4c/source/i18n/numparse_impl.cpp +++ b/icu4c/source/i18n/numparse_impl.cpp @@ -32,7 +32,7 @@ NumberParserImpl::createSimpleParser(const Locale& locale, const UnicodeString& auto* parser = new NumberParserImpl(parseFlags, true); DecimalFormatSymbols symbols(locale, status); - parser->fLocalMatchers.ignorables = {unisets::DEFAULT_IGNORABLES}; + parser->fLocalMatchers.ignorables = std::move(IgnorablesMatcher(unisets::DEFAULT_IGNORABLES)); // MatcherFactory factory = new MatcherFactory(); // factory.currency = Currency.getInstance("USD"); @@ -78,7 +78,7 @@ NumberParserImpl::~NumberParserImpl() { fNumMatchers = 0; } -void NumberParserImpl::addMatcher(const NumberParseMatcher& matcher) { +void NumberParserImpl::addMatcher(NumberParseMatcher& matcher) { if (fNumMatchers + 1 > fMatchers.getCapacity()) { fMatchers.resize(fNumMatchers * 2, fNumMatchers); if (fComputeLeads) { @@ -97,17 +97,17 @@ void NumberParserImpl::addMatcher(const NumberParseMatcher& matcher) { fNumMatchers++; } -void NumberParserImpl::addLeadCodePointsForMatcher(const NumberParseMatcher& matcher) { - const UnicodeSet* leadCodePoints = matcher.getLeadCodePoints(); +void NumberParserImpl::addLeadCodePointsForMatcher(NumberParseMatcher& matcher) { + const UnicodeSet& leadCodePoints = matcher.getLeadCodePoints(); // TODO: Avoid the clone operation here. if (0 != (fParseFlags & PARSE_FLAG_IGNORE_CASE)) { - UnicodeSet* copy = static_cast(leadCodePoints->cloneAsThawed()); - delete leadCodePoints; + auto* copy = dynamic_cast(leadCodePoints.cloneAsThawed()); copy->closeOver(USET_ADD_CASE_MAPPINGS); copy->freeze(); fLeads[fNumMatchers] = copy; } else { - fLeads[fNumMatchers] = leadCodePoints; + // FIXME: new here because we still take ownership + fLeads[fNumMatchers] = new UnicodeSet(leadCodePoints); } } diff --git a/icu4c/source/i18n/numparse_impl.h b/icu4c/source/i18n/numparse_impl.h index 0fe45fa5f4..abc826f590 100644 --- a/icu4c/source/i18n/numparse_impl.h +++ b/icu4c/source/i18n/numparse_impl.h @@ -24,7 +24,7 @@ class NumberParserImpl { static NumberParserImpl* createSimpleParser(const Locale& locale, const UnicodeString& patternString, parse_flags_t parseFlags, UErrorCode& status); - void addMatcher(const NumberParseMatcher& matcher); + void addMatcher(NumberParseMatcher& matcher); void freeze(); @@ -62,7 +62,7 @@ class NumberParserImpl { NumberParserImpl(parse_flags_t parseFlags, bool computeLeads); - void addLeadCodePointsForMatcher(const NumberParseMatcher& matcher); + void addLeadCodePointsForMatcher(NumberParseMatcher& matcher); void parseGreedyRecursive(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const; diff --git a/icu4c/source/i18n/numparse_scientific.cpp b/icu4c/source/i18n/numparse_scientific.cpp index 3b69dcdc99..18ade048fb 100644 --- a/icu4c/source/i18n/numparse_scientific.cpp +++ b/icu4c/source/i18n/numparse_scientific.cpp @@ -67,17 +67,20 @@ bool ScientificMatcher::match(StringSegment& segment, ParsedNumber& result, UErr return false; } -const UnicodeSet* ScientificMatcher::getLeadCodePoints() const { +const UnicodeSet& ScientificMatcher::getLeadCodePoints() { UChar32 leadCp = fExponentSeparatorString.char32At(0); const UnicodeSet* s = unisets::get(unisets::SCIENTIFIC_LEAD); if (s->contains(leadCp)) { - return new UnicodeSet(*s); - } else { - UnicodeSet* leadCodePoints = new UnicodeSet(); + return *s; + } + + if (fLocalLeadCodePoints.isNull()) { + auto* leadCodePoints = new UnicodeSet(); leadCodePoints->add(leadCp); leadCodePoints->freeze(); - return leadCodePoints; + fLocalLeadCodePoints.adoptInstead(leadCodePoints); } + return *fLocalLeadCodePoints; } diff --git a/icu4c/source/i18n/numparse_scientific.h b/icu4c/source/i18n/numparse_scientific.h index 544386c7c3..2f4118ff61 100644 --- a/icu4c/source/i18n/numparse_scientific.h +++ b/icu4c/source/i18n/numparse_scientific.h @@ -25,7 +25,7 @@ class ScientificMatcher : public NumberParseMatcher, public UMemory { bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; - const UnicodeSet* getLeadCodePoints() const override; + const UnicodeSet& getLeadCodePoints() override; private: UnicodeString fExponentSeparatorString; diff --git a/icu4c/source/i18n/numparse_symbols.cpp b/icu4c/source/i18n/numparse_symbols.cpp index 8e192cf773..6654bea7de 100644 --- a/icu4c/source/i18n/numparse_symbols.cpp +++ b/icu4c/source/i18n/numparse_symbols.cpp @@ -54,17 +54,20 @@ bool SymbolMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCo return overlap == segment.length(); } -const UnicodeSet* SymbolMatcher::getLeadCodePoints() const { +const UnicodeSet& SymbolMatcher::getLeadCodePoints() { if (fString.isEmpty()) { // Assumption: for sets from UnicodeSetStaticCache, uniSet == leadCodePoints. - return new UnicodeSet(*fUniSet); + return *fUniSet; } - UnicodeSet* leadCodePoints = new UnicodeSet(); - utils::putLeadCodePoints(fUniSet, leadCodePoints); - utils::putLeadCodePoint(fString, leadCodePoints); - leadCodePoints->freeze(); - return leadCodePoints; + if (fLocalLeadCodePoints.isNull()) { + auto* leadCodePoints = new UnicodeSet(); + utils::putLeadCodePoints(fUniSet, leadCodePoints); + utils::putLeadCodePoint(fString, leadCodePoints); + leadCodePoints->freeze(); + fLocalLeadCodePoints.adoptInstead(leadCodePoints); + } + return *fLocalLeadCodePoints; } @@ -86,7 +89,7 @@ void IgnorablesMatcher::accept(StringSegment&, ParsedNumber&) const { InfinityMatcher::InfinityMatcher(const DecimalFormatSymbols& dfs) - : SymbolMatcher(dfs.getConstSymbol(DecimalFormatSymbols::kNaNSymbol), unisets::INFINITY) { + : SymbolMatcher(dfs.getConstSymbol(DecimalFormatSymbols::kInfinitySymbol), unisets::INFINITY) { } bool InfinityMatcher::isDisabled(const ParsedNumber& result) const { @@ -118,15 +121,15 @@ NanMatcher::NanMatcher(const DecimalFormatSymbols& dfs) : SymbolMatcher(dfs.getConstSymbol(DecimalFormatSymbols::kNaNSymbol), unisets::EMPTY) { } -const UnicodeSet* NanMatcher::getLeadCodePoints() const { +const UnicodeSet& NanMatcher::getLeadCodePoints() { // Overriding this here to allow use of statically allocated sets int leadCp = fString.char32At(0); const UnicodeSet* s = unisets::get(unisets::NAN_LEAD); if (s->contains(leadCp)) { - return new UnicodeSet(*s); - } else { - return SymbolMatcher::getLeadCodePoints(); + return *s; } + + return SymbolMatcher::getLeadCodePoints(); } bool NanMatcher::isDisabled(const ParsedNumber& result) const { @@ -146,11 +149,11 @@ bool PaddingMatcher::isFlexible() const { return true; } -bool PaddingMatcher::isDisabled(const ParsedNumber& result) const { +bool PaddingMatcher::isDisabled(const ParsedNumber&) const { return false; } -void PaddingMatcher::accept(StringSegment& segment, ParsedNumber& result) const { +void PaddingMatcher::accept(StringSegment&, ParsedNumber&) const { // No-op } diff --git a/icu4c/source/i18n/numparse_symbols.h b/icu4c/source/i18n/numparse_symbols.h index 40a57f02ba..289b8902d9 100644 --- a/icu4c/source/i18n/numparse_symbols.h +++ b/icu4c/source/i18n/numparse_symbols.h @@ -28,7 +28,8 @@ class SymbolMatcher : public NumberParseMatcher, public UMemory { bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; - const UnicodeSet* getLeadCodePoints() const override; + /** NOTE: This method is not guaranteed to be thread-safe. */ + const UnicodeSet& getLeadCodePoints() override; virtual bool isDisabled(const ParsedNumber& result) const = 0; @@ -92,7 +93,7 @@ class NanMatcher : public SymbolMatcher { NanMatcher(const DecimalFormatSymbols& dfs); - const UnicodeSet* getLeadCodePoints() const override; + const UnicodeSet& getLeadCodePoints() override; protected: bool isDisabled(const ParsedNumber& result) const override; diff --git a/icu4c/source/i18n/numparse_types.h b/icu4c/source/i18n/numparse_types.h index 30ad92d371..76aa75e0fc 100644 --- a/icu4c/source/i18n/numparse_types.h +++ b/icu4c/source/i18n/numparse_types.h @@ -244,8 +244,6 @@ class StringSegment : public UMemory, public ::icu::number::impl::CharSequence { */ class NumberParseMatcher { public: - virtual ~NumberParseMatcher() = default; - /** * Matchers can override this method to return true to indicate that they are optional and can be run * repeatedly. Used by SeriesMatcher, primarily in the context of IgnorablesMatcher. @@ -259,6 +257,8 @@ class NumberParseMatcher { * something interesting in the StringSegment, it should update the offset of the StringSegment * corresponding to how many chars were matched. * + * This method is thread-safe. + * * @param segment * The StringSegment to match against. Matches always start at the beginning of the * segment. The segment is guaranteed to contain at least one char. @@ -275,9 +275,12 @@ class NumberParseMatcher { * return value is used to skip this matcher unless a segment begins with a char in this set. To make * this matcher always run, return {@link UnicodeSet#ALL_CODE_POINTS}. * - * The returned UnicodeSet needs adoption! + * The returned UnicodeSet does not need adoption and is guaranteed to be alive for as long as the + * object that returned it. + * + * This method is NOT thread-safe. */ - virtual const UnicodeSet* getLeadCodePoints() const = 0; + virtual const UnicodeSet& getLeadCodePoints() = 0; /** * Method called at the end of a parse, after all matchers have failed to consume any more chars. @@ -290,6 +293,13 @@ class NumberParseMatcher { virtual void postProcess(ParsedNumber&) const { // Default implementation: no-op }; + + protected: + // No construction except by subclasses! + NumberParseMatcher() = default; + + // Optional ownership of the leadCodePoints set + LocalPointer fLocalLeadCodePoints; }; diff --git a/icu4c/source/test/intltest/intltest.cpp b/icu4c/source/test/intltest/intltest.cpp index c7d67565b2..47b220afad 100644 --- a/icu4c/source/test/intltest/intltest.cpp +++ b/icu4c/source/test/intltest/intltest.cpp @@ -238,6 +238,12 @@ UnicodeString toString(UBool b) { return b ? UnicodeString("TRUE"):UnicodeString("FALSE"); } +UnicodeString toString(const UnicodeSet& uniset, UErrorCode& status) { + UnicodeString result; + uniset.toPattern(result, status); + return result; +} + // stephen - cleaned up 05/05/99 UnicodeString operator+(const UnicodeString& left, char num) { return left + (long)num; } @@ -2050,6 +2056,24 @@ UBool IntlTest::assertEquals(const char* message, return TRUE; } +UBool IntlTest::assertEquals(const char* message, + const UnicodeSet& expected, + const UnicodeSet& actual) { + IcuTestErrorCode status(*this, "assertEqualsUniSet"); + if (expected != actual) { + errln((UnicodeString)"FAIL: " + message + "; got " + + toString(actual, status) + + "; expected " + toString(expected, status)); + return FALSE; + } +#ifdef VERBOSE_ASSERTIONS + else { + logln((UnicodeString)"Ok: " + message + "; got " + toString(actual, status)); + } +#endif + return TRUE; +} + #if !UCONFIG_NO_FORMATTING UBool IntlTest::assertEquals(const char* message, @@ -2136,6 +2160,11 @@ UBool IntlTest::assertEquals(const UnicodeString& message, UErrorCode actual) { return assertEquals(extractToAssertBuf(message), expected, actual); } +UBool IntlTest::assertEquals(const UnicodeString& message, + const UnicodeSet& expected, + const UnicodeSet& actual) { + return assertEquals(extractToAssertBuf(message), expected, actual); +} #if !UCONFIG_NO_FORMATTING UBool IntlTest::assertEquals(const UnicodeString& message, diff --git a/icu4c/source/test/intltest/intltest.h b/icu4c/source/test/intltest/intltest.h index 08765b707d..5d4b661f39 100644 --- a/icu4c/source/test/intltest/intltest.h +++ b/icu4c/source/test/intltest/intltest.h @@ -16,6 +16,7 @@ // The following includes utypes.h, uobject.h and unistr.h #include "unicode/fmtable.h" #include "unicode/testlog.h" +#include "unicode/uniset.h" U_NAMESPACE_USE @@ -295,6 +296,7 @@ public: UBool assertEquals(const char* message, int64_t expected, int64_t actual); UBool assertEquals(const char* message, double expected, double actual); UBool assertEquals(const char* message, UErrorCode expected, UErrorCode actual); + UBool assertEquals(const char* message, const UnicodeSet& expected, const UnicodeSet& actual); #if !UCONFIG_NO_FORMATTING UBool assertEquals(const char* message, const Formattable& expected, const Formattable& actual, UBool possibleDataError=FALSE); @@ -312,6 +314,7 @@ public: UBool assertEquals(const UnicodeString& message, int64_t expected, int64_t actual); UBool assertEquals(const UnicodeString& message, double expected, double actual); UBool assertEquals(const UnicodeString& message, UErrorCode expected, UErrorCode actual); + UBool assertEquals(const UnicodeString& message, const UnicodeSet& expected, const UnicodeSet& actual); virtual void runIndexedTest( int32_t index, UBool exec, const char* &name, char* par = NULL ); // overide ! diff --git a/icu4c/source/test/intltest/numbertest.h b/icu4c/source/test/intltest/numbertest.h index 5da55bbe9c..945d76d9b3 100644 --- a/icu4c/source/test/intltest/numbertest.h +++ b/icu4c/source/test/intltest/numbertest.h @@ -212,6 +212,7 @@ class NumberParserTest : public IntlTest { void testBasic(); void testLocaleFi(); void testSeriesMatcher(); + void testCurrencyAnyMatcher(); void testGroupingDisabled(); void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0); diff --git a/icu4c/source/test/intltest/numbertest_parse.cpp b/icu4c/source/test/intltest/numbertest_parse.cpp index 4140320bc9..1dbf73a3d1 100644 --- a/icu4c/source/test/intltest/numbertest_parse.cpp +++ b/icu4c/source/test/intltest/numbertest_parse.cpp @@ -21,6 +21,7 @@ void NumberParserTest::runIndexedTest(int32_t index, UBool exec, const char*& na } TESTCASE_AUTO_BEGIN; TESTCASE_AUTO(testBasic); + TESTCASE_AUTO(testSeriesMatcher); TESTCASE_AUTO_END; } @@ -99,7 +100,7 @@ void NumberParserTest::testBasic() { {3, u"0", u"0", 1, 0.0}}; parse_flags_t parseFlags = PARSE_FLAG_IGNORE_CASE | PARSE_FLAG_INCLUDE_UNPAIRED_AFFIXES; - for (auto cas : cases) { + for (auto& cas : cases) { UnicodeString inputString(cas.inputString); UnicodeString patternString(cas.patternString); LocalPointer parser( @@ -153,5 +154,54 @@ void NumberParserTest::testBasic() { } } +void NumberParserTest::testSeriesMatcher() { + IcuTestErrorCode status(*this, "testSeriesMatcher"); + + DecimalFormatSymbols symbols("en", status); + + PlusSignMatcher m0(symbols, false); + MinusSignMatcher m1(symbols, false); + IgnorablesMatcher m2(unisets::DEFAULT_IGNORABLES); + PercentMatcher m3(symbols); + IgnorablesMatcher m4(unisets::DEFAULT_IGNORABLES); + + ArraySeriesMatcher series(new NumberParseMatcher* [5]{&m0, &m1, &m2, &m3, &m4}, 5); + + assertEquals( + "Lead set should be equal to lead set of lead matcher", + *unisets::get(unisets::PLUS_SIGN), + series.getLeadCodePoints()); + + static const struct TestCase { + const char16_t* input; + int32_t expectedOffset; + bool expectedMaybeMore; + } cases[] = {{u"", 0, true}, + {u" ", 0, false}, + {u"$", 0, false}, + {u"+", 0, true}, + {u" +", 0, false}, + {u"+-", 0, true}, + {u"+ -", 0, false}, + {u"+- ", 0, true}, + {u"+- $", 0, false}, + {u"+-%", 3, true}, + {u" +- % ", 0, false}, + {u"+- % ", 7, true}, + {u"+-%$", 3, false}}; + + for (auto& cas : cases) { + UnicodeString input(cas.input); + + StringSegment segment(input, 0); + ParsedNumber result; + bool actualMaybeMore = series.match(segment, result, status); + int actualOffset = segment.getOffset(); + + assertEquals("'" + input + "'", cas.expectedOffset, actualOffset); + assertEquals("'" + input + "'", cas.expectedMaybeMore, actualMaybeMore); + } +} + #endif diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyCustomMatcher.java similarity index 86% rename from icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyMatcher.java rename to icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyCustomMatcher.java index d81c2e9f81..3df201889b 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyCustomMatcher.java @@ -9,19 +9,19 @@ import com.ibm.icu.util.ULocale; /** * A matcher for a single currency instance (not the full trie). */ -public class CurrencyMatcher implements NumberParseMatcher { +public class CurrencyCustomMatcher implements NumberParseMatcher { private final String isoCode; private final String currency1; private final String currency2; - public static CurrencyMatcher getInstance(Currency currency, ULocale loc) { - return new CurrencyMatcher(currency.getSubtype(), + public static CurrencyCustomMatcher getInstance(Currency currency, ULocale loc) { + return new CurrencyCustomMatcher(currency.getSubtype(), currency.getSymbol(loc), currency.getCurrencyCode()); } - private CurrencyMatcher(String isoCode, String currency1, String currency2) { + private CurrencyCustomMatcher(String isoCode, String currency1, String currency2) { this.isoCode = isoCode; this.currency1 = currency1; this.currency2 = currency2; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyTrieMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyNamesMatcher.java similarity index 77% rename from icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyTrieMatcher.java rename to icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyNamesMatcher.java index 239949ec13..9fdef22504 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyTrieMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyNamesMatcher.java @@ -11,21 +11,24 @@ import com.ibm.icu.util.Currency.CurrencyStringInfo; import com.ibm.icu.util.ULocale; /** - * @author sffc + * Matches currencies according to all available strings in locale data. * + * The implementation of this class is different between J and C. See #13584 for a follow-up. + * + * @author sffc */ -public class CurrencyTrieMatcher implements NumberParseMatcher { +public class CurrencyNamesMatcher implements NumberParseMatcher { private final TextTrieMap longNameTrie; private final TextTrieMap symbolTrie; - public static CurrencyTrieMatcher getInstance(ULocale locale) { + public static CurrencyNamesMatcher getInstance(ULocale locale) { // TODO: Pre-compute some of the more popular locales? - return new CurrencyTrieMatcher(locale); + return new CurrencyNamesMatcher(locale); } - private CurrencyTrieMatcher(ULocale locale) { - // TODO: Currency trie does not currently have an option for case folding. It defaults to use + private CurrencyNamesMatcher(ULocale locale) { + // TODO: Currency trie does not currently have an option for case folding. It defaults to use // case folding on long-names but not symbols. longNameTrie = Currency.getParsingTrie(locale, Currency.LONG_NAME); symbolTrie = Currency.getParsingTrie(locale, Currency.SYMBOL_NAME); @@ -55,6 +58,8 @@ public class CurrencyTrieMatcher implements NumberParseMatcher { UnicodeSet leadCodePoints = new UnicodeSet(); longNameTrie.putLeadCodePoints(leadCodePoints); symbolTrie.putLeadCodePoints(leadCodePoints); + // Always apply case mapping closure for currencies + leadCodePoints.closeOver(UnicodeSet.ADD_CASE_MAPPINGS); return leadCodePoints.freeze(); } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/MatcherFactory.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/MatcherFactory.java index d5640d4aad..63c37b916e 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/MatcherFactory.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/MatcherFactory.java @@ -7,14 +7,15 @@ import com.ibm.icu.util.Currency; import com.ibm.icu.util.ULocale; /** - * @author sffc + * Small helper class that generates matchers for SeriesMatcher. * + * @author sffc */ public class MatcherFactory { - Currency currency; - DecimalFormatSymbols symbols; - IgnorablesMatcher ignorables; - ULocale locale; + public Currency currency; + public DecimalFormatSymbols symbols; + public IgnorablesMatcher ignorables; + public ULocale locale; public MinusSignMatcher minusSign(boolean allowTrailing) { return MinusSignMatcher.getInstance(symbols, allowTrailing); @@ -34,8 +35,8 @@ public class MatcherFactory { public AnyMatcher currency() { AnyMatcher any = new AnyMatcher(); - any.addMatcher(CurrencyMatcher.getInstance(currency, locale)); - any.addMatcher(CurrencyTrieMatcher.getInstance(locale)); + any.addMatcher(CurrencyCustomMatcher.getInstance(currency, locale)); + any.addMatcher(CurrencyNamesMatcher.getInstance(locale)); any.freeze(); return any; } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java index 5060b9518d..ed75d2d514 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java @@ -95,7 +95,7 @@ public class NumberParserImpl { parser.addMatcher(InfinityMatcher.getInstance(symbols)); parser.addMatcher(PaddingMatcher.getInstance("@")); parser.addMatcher(ScientificMatcher.getInstance(symbols, grouper)); - parser.addMatcher(CurrencyTrieMatcher.getInstance(locale)); + parser.addMatcher(CurrencyNamesMatcher.getInstance(locale)); parser.addMatcher(new RequireNumberMatcher()); parser.freeze(); @@ -213,8 +213,8 @@ public class NumberParserImpl { //////////////////////// if (parseCurrency || patternInfo.hasCurrencySign()) { - parser.addMatcher(CurrencyMatcher.getInstance(currency, locale)); - parser.addMatcher(CurrencyTrieMatcher.getInstance(locale)); + parser.addMatcher(CurrencyCustomMatcher.getInstance(currency, locale)); + parser.addMatcher(CurrencyNamesMatcher.getInstance(locale)); } /////////////////////////////// diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java index 912529479a..41312d8399 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java @@ -7,8 +7,11 @@ import static org.junit.Assert.assertTrue; import org.junit.Test; +import com.ibm.icu.impl.number.CustomSymbolCurrency; import com.ibm.icu.impl.number.DecimalFormatProperties; +import com.ibm.icu.impl.number.parse.AnyMatcher; import com.ibm.icu.impl.number.parse.IgnorablesMatcher; +import com.ibm.icu.impl.number.parse.MatcherFactory; import com.ibm.icu.impl.number.parse.MinusSignMatcher; import com.ibm.icu.impl.number.parse.NumberParserImpl; import com.ibm.icu.impl.number.parse.ParsedNumber; @@ -222,6 +225,38 @@ public class NumberParserTest { } } + @Test + public void testCurrencyAnyMatcher() { + MatcherFactory factory = new MatcherFactory(); + factory.locale = ULocale.ENGLISH; + CustomSymbolCurrency currency = new CustomSymbolCurrency("ICU", "IU$", "ICU"); + factory.currency = currency; + AnyMatcher matcher = factory.currency(); + + Object[][] cases = new Object[][] { + { "", null }, + { "FOO", null }, + { "USD", "USD" }, + { "$", "USD" }, + { "US dollars", "USD" }, + { "eu", null }, + { "euros", "EUR" }, + { "ICU", "ICU" }, + { "IU$", "ICU" } }; + for (Object[] cas : cases) { + String input = (String) cas[0]; + String expectedCurrencyCode = (String) cas[1]; + + StringSegment segment = new StringSegment(input, 0); + ParsedNumber result = new ParsedNumber(); + matcher.match(segment, result); + assertEquals("Parsing " + input, expectedCurrencyCode, result.currencyCode); + assertEquals("Whole string on " + input, + expectedCurrencyCode == null ? 0 : input.length(), + result.charEnd); + } + } + @Test public void testGroupingDisabled() { DecimalFormatProperties properties = new DecimalFormatProperties(); From e7a42e17f624ac57754b779cbca6ccf2f87d813f Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Sat, 10 Feb 2018 10:01:46 +0000 Subject: [PATCH 012/129] ICU-13574 Adding initial AffixPatternMatcher to ICU4C. Not completely safe yet. Still needs work. X-SVN-Rev: 40891 --- icu4c/source/i18n/number_affixutils.cpp | 14 ++ icu4c/source/i18n/number_affixutils.h | 11 ++ icu4c/source/i18n/numparse_affixes.cpp | 139 ++++++++++++++++++ icu4c/source/i18n/numparse_affixes.h | 95 +++++++++++- icu4c/source/i18n/numparse_compositions.cpp | 13 +- icu4c/source/i18n/numparse_compositions.h | 14 +- icu4c/source/i18n/numparse_currency.cpp | 21 +-- icu4c/source/i18n/numparse_currency.h | 5 +- icu4c/source/i18n/numparse_symbols.h | 1 - icu4c/source/i18n/numparse_utils.h | 5 + icu4c/source/test/intltest/numbertest.h | 1 + .../source/test/intltest/numbertest_parse.cpp | 50 ++++++- .../icu/impl/number/parse/AffixMatcher.java | 2 +- .../number/parse/AffixPatternMatcher.java | 8 +- ...ory.java => AffixTokenMatcherFactory.java} | 10 +- .../impl/number/parse/NumberParserImpl.java | 4 +- .../icu/dev/test/number/NumberParserTest.java | 45 +++++- 17 files changed, 401 insertions(+), 37 deletions(-) rename icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/{MatcherFactory.java => AffixTokenMatcherFactory.java} (78%) diff --git a/icu4c/source/i18n/number_affixutils.cpp b/icu4c/source/i18n/number_affixutils.cpp index df4b267af5..072edbb2fa 100644 --- a/icu4c/source/i18n/number_affixutils.cpp +++ b/icu4c/source/i18n/number_affixutils.cpp @@ -239,6 +239,20 @@ UnicodeString AffixUtils::replaceType(const CharSequence &affixPattern, AffixPat return output; } +void AffixUtils::iterateWithConsumer(const CharSequence& affixPattern, TokenConsumer& consumer, + UErrorCode& status) { + if (affixPattern.length() == 0) { + return; + }; + AffixTag tag; + while (hasNext(tag, affixPattern)) { + tag = nextToken(tag, affixPattern, status); + if (U_FAILURE(status)) { return; } + consumer.consumeToken(tag.type, tag.codePoint, status); + if (U_FAILURE(status)) { return; } + } +} + AffixTag AffixUtils::nextToken(AffixTag tag, const CharSequence &patternString, UErrorCode &status) { int32_t offset = tag.offset; int32_t state = tag.state; diff --git a/icu4c/source/i18n/number_affixutils.h b/icu4c/source/i18n/number_affixutils.h index fd76c99b97..665a9d8425 100644 --- a/icu4c/source/i18n/number_affixutils.h +++ b/icu4c/source/i18n/number_affixutils.h @@ -46,6 +46,11 @@ struct AffixTag { {} }; +class TokenConsumer { + public: + virtual void consumeToken(AffixPatternType type, UChar32 cp, UErrorCode& status) = 0; +}; + // Exported as U_I18N_API because it is a base class for other exported types class U_I18N_API SymbolProvider { public: @@ -180,6 +185,12 @@ class U_I18N_API AffixUtils { replaceType(const CharSequence &affixPattern, AffixPatternType type, char16_t replacementChar, UErrorCode &status); + /** + * Iterates over the affix pattern, calling the TokenConsumer for each token. + */ + static void iterateWithConsumer(const CharSequence& affixPattern, TokenConsumer& consumer, + UErrorCode& status); + /** * Returns the next token from the affix pattern. * diff --git a/icu4c/source/i18n/numparse_affixes.cpp b/icu4c/source/i18n/numparse_affixes.cpp index 2ac929d37a..84a7f75163 100644 --- a/icu4c/source/i18n/numparse_affixes.cpp +++ b/icu4c/source/i18n/numparse_affixes.cpp @@ -7,14 +7,153 @@ #include "numparse_types.h" #include "numparse_affixes.h" +#include "numparse_utils.h" +#include "number_utils.h" using namespace icu; using namespace icu::numparse; using namespace icu::numparse::impl; +using namespace icu::number; +using namespace icu::number::impl; +AffixPatternMatcherBuilder::AffixPatternMatcherBuilder(const UnicodeString& pattern, + AffixTokenMatcherFactory& factory, + IgnorablesMatcher* ignorables) + : fMatchersLen(0), + fLastTypeOrCp(0), + fCodePointMatchers(new CodePointMatcher[100]), + fCodePointMatchersLen(0), + fPattern(pattern), + fFactory(factory), + fIgnorables(ignorables) {} + +void AffixPatternMatcherBuilder::consumeToken(AffixPatternType type, UChar32 cp, UErrorCode& status) { + // This is called by AffixUtils.iterateWithConsumer() for each token. + + // Add an ignorables matcher between tokens except between two literals, and don't put two + // ignorables matchers in a row. + if (fIgnorables != nullptr && fMatchersLen > 0 && + (fLastTypeOrCp < 0 || !fIgnorables->getSet()->contains(fLastTypeOrCp))) { + addMatcher(*fIgnorables); + } + + if (type != TYPE_CODEPOINT) { + // Case 1: the token is a symbol. + switch (type) { + case TYPE_MINUS_SIGN: + addMatcher(fFactory.minusSign = {fFactory.dfs, true}); + break; + case TYPE_PLUS_SIGN: + addMatcher(fFactory.plusSign = {fFactory.dfs, true}); + break; + case TYPE_PERCENT: + addMatcher(fFactory.percent = {fFactory.dfs}); + break; + case TYPE_PERMILLE: + addMatcher(fFactory.permille = {fFactory.dfs}); + break; + case TYPE_CURRENCY_SINGLE: + case TYPE_CURRENCY_DOUBLE: + case TYPE_CURRENCY_TRIPLE: + case TYPE_CURRENCY_QUAD: + case TYPE_CURRENCY_QUINT: + // All currency symbols use the same matcher + addMatcher( + fFactory.currency = { + CurrencyNamesMatcher( + fFactory.locale, status), CurrencyCustomMatcher( + fFactory.currencyCode, fFactory.currency1, fFactory.currency2)}); + break; + default: + U_ASSERT(FALSE); + } + + } else if (fIgnorables != nullptr && fIgnorables->getSet()->contains(cp)) { + // Case 2: the token is an ignorable literal. + // No action necessary: the ignorables matcher has already been added. + + } else { + // Case 3: the token is a non-ignorable literal. + // TODO: This is really clunky. Just trying to get something that works. + fCodePointMatchers[fCodePointMatchersLen] = {cp}; + addMatcher(fCodePointMatchers[fCodePointMatchersLen]); + fCodePointMatchersLen++; + } + fLastTypeOrCp = type != TYPE_CODEPOINT ? type : cp; +} + +void AffixPatternMatcherBuilder::addMatcher(NumberParseMatcher& matcher) { + if (fMatchersLen >= fMatchers.getCapacity()) { + fMatchers.resize(fMatchersLen * 2, fMatchersLen); + } + fMatchers[fMatchersLen++] = &matcher; +} + +AffixPatternMatcher AffixPatternMatcherBuilder::build() { + return AffixPatternMatcher(fMatchers, fMatchersLen, fPattern, fCodePointMatchers.orphan()); +} +AffixTokenMatcherFactory::AffixTokenMatcherFactory(const UChar* currencyCode, + const UnicodeString& currency1, + const UnicodeString& currency2, + const DecimalFormatSymbols& dfs, + IgnorablesMatcher* ignorables, const Locale& locale) + : currency1(currency1), currency2(currency2), dfs(dfs), ignorables(ignorables), locale(locale) { + utils::copyCurrencyCode(this->currencyCode, currencyCode); +} + + +CodePointMatcher::CodePointMatcher(UChar32 cp) + : fCp(cp) {} + +bool CodePointMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCode&) const { + if (segment.matches(fCp)) { + segment.adjustOffsetByCodePoint(); + result.setCharsConsumed(segment); + } + return false; +} + +const UnicodeSet& CodePointMatcher::getLeadCodePoints() { + if (fLocalLeadCodePoints.isNull()) { + auto* leadCodePoints = new UnicodeSet(); + leadCodePoints->add(fCp); + leadCodePoints->freeze(); + fLocalLeadCodePoints.adoptInstead(leadCodePoints); + } + return *fLocalLeadCodePoints; +} + + +AffixPatternMatcher +AffixPatternMatcher::fromAffixPattern(const UnicodeString& affixPattern, AffixTokenMatcherFactory& factory, + parse_flags_t parseFlags, bool* success, UErrorCode& status) { + if (affixPattern.isEmpty()) { + *success = false; + return {}; + } + *success = true; + + IgnorablesMatcher* ignorables; + if (0 != (parseFlags & PARSE_FLAG_EXACT_AFFIX)) { + ignorables = nullptr; + } else { + ignorables = factory.ignorables; + } + + AffixPatternMatcherBuilder builder(affixPattern, factory, ignorables); + AffixUtils::iterateWithConsumer(UnicodeStringCharSequence(affixPattern), builder, status); + return builder.build(); +} + +AffixPatternMatcher::AffixPatternMatcher(MatcherArray& matchers, int32_t matchersLen, + const UnicodeString& pattern, CodePointMatcher* codePointMatchers) + : ArraySeriesMatcher(matchers, matchersLen), + fPattern(pattern), + fCodePointMatchers(codePointMatchers) { +} #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/numparse_affixes.h b/icu4c/source/i18n/numparse_affixes.h index 677b50cea0..460034e3fa 100644 --- a/icu4c/source/i18n/numparse_affixes.h +++ b/icu4c/source/i18n/numparse_affixes.h @@ -8,13 +8,104 @@ #define __NUMPARSE_AFFIXES_H__ #include "numparse_types.h" +#include "numparse_symbols.h" +#include "numparse_currency.h" +#include "number_affixutils.h" -U_NAMESPACE_BEGIN -namespace numparse { +U_NAMESPACE_BEGIN namespace numparse { namespace impl { +// Forward-declaration of implementation classes for friending +class AffixPatternMatcherBuilder; +class AffixPatternMatcher; + +class AffixTokenMatcherFactory { + public: + AffixTokenMatcherFactory(const UChar* currencyCode, const UnicodeString& currency1, + const UnicodeString& currency2, const DecimalFormatSymbols& dfs, + IgnorablesMatcher* ignorables, const Locale& locale); + + private: + UChar currencyCode[4]; + const UnicodeString& currency1; + const UnicodeString& currency2; + const DecimalFormatSymbols& dfs; + IgnorablesMatcher* ignorables; + const Locale locale; + + // NOTE: These are default-constructed and should not be used until initialized. + MinusSignMatcher minusSign; + PlusSignMatcher plusSign; + PercentMatcher percent; + PermilleMatcher permille; + CurrencyAnyMatcher currency; + + friend class AffixPatternMatcherBuilder; + friend class AffixPatternMatcher; +}; +class CodePointMatcher : public NumberParseMatcher, public UMemory { + public: + CodePointMatcher() = default; // WARNING: Leaves the object in an unusable state + + CodePointMatcher(UChar32 cp); + + bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; + + const UnicodeSet& getLeadCodePoints() override; + + private: + UChar32 fCp; +}; + + +class AffixPatternMatcherBuilder : public ::icu::number::impl::TokenConsumer { + public: + AffixPatternMatcherBuilder(const UnicodeString& pattern, AffixTokenMatcherFactory& factory, + IgnorablesMatcher* ignorables); + + void consumeToken(::icu::number::impl::AffixPatternType type, UChar32 cp, UErrorCode& status) override; + + /** NOTE: You can build only once! */ + AffixPatternMatcher build(); + + private: + ArraySeriesMatcher::MatcherArray fMatchers; + int32_t fMatchersLen; + int32_t fLastTypeOrCp; + + LocalArray fCodePointMatchers; + int32_t fCodePointMatchersLen; + + const UnicodeString& fPattern; + AffixTokenMatcherFactory& fFactory; + IgnorablesMatcher* fIgnorables; + + void addMatcher(NumberParseMatcher& matcher); +}; + + +class AffixPatternMatcher : public ArraySeriesMatcher { + public: + static AffixPatternMatcher fromAffixPattern(const UnicodeString& affixPattern, + AffixTokenMatcherFactory& factory, + parse_flags_t parseFlags, bool* success, + UErrorCode& status); + + private: + UnicodeString fPattern; + + // We need to own the variable number of CodePointMatchers. + LocalArray fCodePointMatchers; + + AffixPatternMatcher() = default; // WARNING: Leaves the object in an unusable state + + AffixPatternMatcher(MatcherArray& matchers, int32_t matchersLen, const UnicodeString& pattern, + CodePointMatcher* codePointMatchers); + + friend class AffixPatternMatcherBuilder; +}; } // namespace impl diff --git a/icu4c/source/i18n/numparse_compositions.cpp b/icu4c/source/i18n/numparse_compositions.cpp index 5d4a92b988..138b45c6da 100644 --- a/icu4c/source/i18n/numparse_compositions.cpp +++ b/icu4c/source/i18n/numparse_compositions.cpp @@ -87,8 +87,13 @@ void SeriesMatcher::postProcess(ParsedNumber& result) const { } -ArraySeriesMatcher::ArraySeriesMatcher(NumberParseMatcher** matchers, int32_t matchersLen) - : fMatchers(matchers), fMatchersLen(matchersLen) {} +ArraySeriesMatcher::ArraySeriesMatcher() + : fMatchersLen(0) { +} + +ArraySeriesMatcher::ArraySeriesMatcher(MatcherArray& matchers, int32_t matchersLen) + : fMatchers(std::move(matchers)), fMatchersLen(matchersLen) { +} const UnicodeSet& ArraySeriesMatcher::getLeadCodePoints() { // SeriesMatchers are never allowed to start with a Flexible matcher. @@ -96,6 +101,10 @@ const UnicodeSet& ArraySeriesMatcher::getLeadCodePoints() { return fMatchers[0]->getLeadCodePoints(); } +int32_t ArraySeriesMatcher::length() const { + return fMatchersLen; +} + const NumberParseMatcher* const* ArraySeriesMatcher::begin() const { return fMatchers.getAlias(); } diff --git a/icu4c/source/i18n/numparse_compositions.h b/icu4c/source/i18n/numparse_compositions.h index b52bb2fd8a..51501a805c 100644 --- a/icu4c/source/i18n/numparse_compositions.h +++ b/icu4c/source/i18n/numparse_compositions.h @@ -63,6 +63,8 @@ class SeriesMatcher : public CompositionMatcher { void postProcess(ParsedNumber& result) const override; + virtual int32_t length() const = 0; + protected: // No construction except by subclasses! SeriesMatcher() = default; @@ -76,18 +78,24 @@ class SeriesMatcher : public CompositionMatcher { */ class ArraySeriesMatcher : public SeriesMatcher { public: - /** The array is adopted, but NOT the matchers inside the array. */ - ArraySeriesMatcher(NumberParseMatcher** matchers, int32_t matchersLen); + ArraySeriesMatcher(); // WARNING: Leaves the object in an unusable state + + typedef MaybeStackArray MatcherArray; + + /** The array is std::move'd */ + ArraySeriesMatcher(MatcherArray& matchers, int32_t matchersLen); const UnicodeSet& getLeadCodePoints() override; + int32_t length() const override; + protected: const NumberParseMatcher* const* begin() const override; const NumberParseMatcher* const* end() const override; private: - LocalArray fMatchers; + MatcherArray fMatchers; int32_t fMatchersLen; }; diff --git a/icu4c/source/i18n/numparse_currency.cpp b/icu4c/source/i18n/numparse_currency.cpp index 90b6bed6dd..1cd730214a 100644 --- a/icu4c/source/i18n/numparse_currency.cpp +++ b/icu4c/source/i18n/numparse_currency.cpp @@ -16,16 +16,6 @@ using namespace icu::numparse; using namespace icu::numparse::impl; -namespace { - -inline void copyCurrencyCode(UChar* dest, const UChar* src) { - uprv_memcpy(dest, src, sizeof(UChar) * 3); - dest[3] = 0; -} - -} - - CurrencyNamesMatcher::CurrencyNamesMatcher(const Locale& locale, UErrorCode& status) : fLocaleName(locale.getName(), -1, status) {} @@ -80,7 +70,7 @@ const UnicodeSet& CurrencyNamesMatcher::getLeadCodePoints() { CurrencyCustomMatcher::CurrencyCustomMatcher(const char16_t* currencyCode, const UnicodeString& currency1, const UnicodeString& currency2) : fCurrency1(currency1), fCurrency2(currency2) { - copyCurrencyCode(fCurrencyCode, currencyCode); + utils::copyCurrencyCode(fCurrencyCode, currencyCode); } bool CurrencyCustomMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCode&) const { @@ -90,14 +80,14 @@ bool CurrencyCustomMatcher::match(StringSegment& segment, ParsedNumber& result, int overlap1 = segment.getCommonPrefixLength(fCurrency1); if (overlap1 == fCurrency1.length()) { - copyCurrencyCode(result.currencyCode, fCurrencyCode); + utils::copyCurrencyCode(result.currencyCode, fCurrencyCode); segment.adjustOffset(overlap1); result.setCharsConsumed(segment); } int overlap2 = segment.getCommonPrefixLength(fCurrency2); if (overlap2 == fCurrency2.length()) { - copyCurrencyCode(result.currencyCode, fCurrencyCode); + utils::copyCurrencyCode(result.currencyCode, fCurrencyCode); segment.adjustOffset(overlap2); result.setCharsConsumed(segment); } @@ -117,6 +107,11 @@ const UnicodeSet& CurrencyCustomMatcher::getLeadCodePoints() { } +CurrencyAnyMatcher::CurrencyAnyMatcher() { + fMatcherArray[0] = &fNamesMatcher; + fMatcherArray[1] = &fCustomMatcher; +} + CurrencyAnyMatcher::CurrencyAnyMatcher(CurrencyNamesMatcher namesMatcher, CurrencyCustomMatcher customMatcher) : fNamesMatcher(std::move(namesMatcher)), fCustomMatcher(std::move(customMatcher)) { diff --git a/icu4c/source/i18n/numparse_currency.h b/icu4c/source/i18n/numparse_currency.h index f5f56c8600..bbfa15094a 100644 --- a/icu4c/source/i18n/numparse_currency.h +++ b/icu4c/source/i18n/numparse_currency.h @@ -42,6 +42,8 @@ class CurrencyNamesMatcher : public NumberParseMatcher, public UMemory { class CurrencyCustomMatcher : public NumberParseMatcher, public UMemory { public: + CurrencyCustomMatcher() = default; // WARNING: Leaves the object in an unusable state + CurrencyCustomMatcher(const char16_t* currencyCode, const UnicodeString& currency1, const UnicodeString& currency2); @@ -61,7 +63,8 @@ class CurrencyCustomMatcher : public NumberParseMatcher, public UMemory { */ class CurrencyAnyMatcher : public AnyMatcher, public UMemory { public: - /** Calls std::move on the two arguments. */ + CurrencyAnyMatcher(); // WARNING: Leaves the object in an unusable state + CurrencyAnyMatcher(CurrencyNamesMatcher namesMatcher, CurrencyCustomMatcher customMatcher); const UnicodeSet& getLeadCodePoints() override; diff --git a/icu4c/source/i18n/numparse_symbols.h b/icu4c/source/i18n/numparse_symbols.h index 289b8902d9..c06724fbe7 100644 --- a/icu4c/source/i18n/numparse_symbols.h +++ b/icu4c/source/i18n/numparse_symbols.h @@ -28,7 +28,6 @@ class SymbolMatcher : public NumberParseMatcher, public UMemory { bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; - /** NOTE: This method is not guaranteed to be thread-safe. */ const UnicodeSet& getLeadCodePoints() override; virtual bool isDisabled(const ParsedNumber& result) const = 0; diff --git a/icu4c/source/i18n/numparse_utils.h b/icu4c/source/i18n/numparse_utils.h index a25f9ef9df..590c7943f3 100644 --- a/icu4c/source/i18n/numparse_utils.h +++ b/icu4c/source/i18n/numparse_utils.h @@ -28,6 +28,11 @@ inline static void putLeadCodePoint(const UnicodeString& input, UnicodeSet* outp } } +inline static void copyCurrencyCode(UChar* dest, const UChar* src) { + uprv_memcpy(dest, src, sizeof(UChar) * 3); + dest[3] = 0; +} + } // namespace utils } // namespace impl diff --git a/icu4c/source/test/intltest/numbertest.h b/icu4c/source/test/intltest/numbertest.h index 945d76d9b3..5d56dab206 100644 --- a/icu4c/source/test/intltest/numbertest.h +++ b/icu4c/source/test/intltest/numbertest.h @@ -213,6 +213,7 @@ class NumberParserTest : public IntlTest { void testLocaleFi(); void testSeriesMatcher(); void testCurrencyAnyMatcher(); + void testAffixPatternMatcher(); void testGroupingDisabled(); void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0); diff --git a/icu4c/source/test/intltest/numbertest_parse.cpp b/icu4c/source/test/intltest/numbertest_parse.cpp index 1dbf73a3d1..776223044b 100644 --- a/icu4c/source/test/intltest/numbertest_parse.cpp +++ b/icu4c/source/test/intltest/numbertest_parse.cpp @@ -12,6 +12,7 @@ #include "unicode/testlog.h" #include +#include using icu::numparse::impl::unisets::get; @@ -22,6 +23,7 @@ void NumberParserTest::runIndexedTest(int32_t index, UBool exec, const char*& na TESTCASE_AUTO_BEGIN; TESTCASE_AUTO(testBasic); TESTCASE_AUTO(testSeriesMatcher); + TESTCASE_AUTO(testAffixPatternMatcher); TESTCASE_AUTO_END; } @@ -165,7 +167,13 @@ void NumberParserTest::testSeriesMatcher() { PercentMatcher m3(symbols); IgnorablesMatcher m4(unisets::DEFAULT_IGNORABLES); - ArraySeriesMatcher series(new NumberParseMatcher* [5]{&m0, &m1, &m2, &m3, &m4}, 5); + ArraySeriesMatcher::MatcherArray matchers(5); + matchers[0] = &m0; + matchers[1] = &m1; + matchers[2] = &m2; + matchers[3] = &m3; + matchers[4] = &m4; + ArraySeriesMatcher series(matchers, 5); assertEquals( "Lead set should be equal to lead set of lead matcher", @@ -203,5 +211,45 @@ void NumberParserTest::testSeriesMatcher() { } } +void NumberParserTest::testAffixPatternMatcher() { + IcuTestErrorCode status(*this, "testAffixPatternMatcher"); + + IgnorablesMatcher ignorables(unisets::DEFAULT_IGNORABLES); + AffixTokenMatcherFactory factory(u"EUR", u"foo", u"bar", {"en", status}, &ignorables, "en"); + + static const struct TestCase { + bool exactMatch; + const char16_t* affixPattern; + int32_t expectedMatcherLength; + const char16_t* sampleParseableString; + } cases[] = {{false, u"-", 1, u"-"}, + {false, u"+-%", 5, u"+-%"}, + {true, u"+-%", 3, u"+-%"}, + {false, u"ab c", 5, u"a bc"}, + {true, u"abc", 3, u"abc"}, + //{false, u"hello-to+this%very¤long‰string", 59, u"hello-to+this%very USD long‰string"} + }; + + for (auto& cas : cases) { + UnicodeString affixPattern(cas.affixPattern); + UnicodeString sampleParseableString(cas.sampleParseableString); + int parseFlags = cas.exactMatch ? PARSE_FLAG_EXACT_AFFIX : 0; + + bool success; + AffixPatternMatcher matcher = AffixPatternMatcher::fromAffixPattern( + affixPattern, factory, parseFlags, &success, status); + assertTrue("Creation should be successful", success); + + // Check that the matcher has the expected number of children + assertEquals(affixPattern + " " + cas.exactMatch, cas.expectedMatcherLength, matcher.length()); + + // Check that the matcher works on a sample string + StringSegment segment(sampleParseableString, 0); + ParsedNumber result; + matcher.match(segment, result, status); + assertEquals(affixPattern + " " + cas.exactMatch, sampleParseableString.length(), result.charEnd); + } +} + #endif diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixMatcher.java index 5104e29b9d..6fccdc2932 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixMatcher.java @@ -78,7 +78,7 @@ public class AffixMatcher implements NumberParseMatcher { public static void createMatchers( AffixPatternProvider patternInfo, NumberParserImpl output, - MatcherFactory factory, + AffixTokenMatcherFactory factory, IgnorablesMatcher ignorables, int parseFlags) { if (!isInteresting(patternInfo, ignorables, parseFlags)) { diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixPatternMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixPatternMatcher.java index 43d3888579..770201533e 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixPatternMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixPatternMatcher.java @@ -15,7 +15,7 @@ public class AffixPatternMatcher extends SeriesMatcher implements AffixUtils.Tok private final String affixPattern; // Used during construction only: - private MatcherFactory factory; + private AffixTokenMatcherFactory factory; private IgnorablesMatcher ignorables; private int lastTypeOrCp; @@ -29,7 +29,7 @@ public class AffixPatternMatcher extends SeriesMatcher implements AffixUtils.Tok */ public static AffixPatternMatcher fromAffixPattern( String affixPattern, - MatcherFactory factory, + AffixTokenMatcherFactory factory, int parseFlags) { if (affixPattern.isEmpty()) { return null; @@ -71,10 +71,10 @@ public class AffixPatternMatcher extends SeriesMatcher implements AffixUtils.Tok // Case 1: the token is a symbol. switch (typeOrCp) { case AffixUtils.TYPE_MINUS_SIGN: - addMatcher(factory.minusSign(true)); + addMatcher(factory.minusSign()); break; case AffixUtils.TYPE_PLUS_SIGN: - addMatcher(factory.plusSign(true)); + addMatcher(factory.plusSign()); break; case AffixUtils.TYPE_PERCENT: addMatcher(factory.percent()); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/MatcherFactory.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixTokenMatcherFactory.java similarity index 78% rename from icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/MatcherFactory.java rename to icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixTokenMatcherFactory.java index 63c37b916e..142b29faef 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/MatcherFactory.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixTokenMatcherFactory.java @@ -11,18 +11,18 @@ import com.ibm.icu.util.ULocale; * * @author sffc */ -public class MatcherFactory { +public class AffixTokenMatcherFactory { public Currency currency; public DecimalFormatSymbols symbols; public IgnorablesMatcher ignorables; public ULocale locale; - public MinusSignMatcher minusSign(boolean allowTrailing) { - return MinusSignMatcher.getInstance(symbols, allowTrailing); + public MinusSignMatcher minusSign() { + return MinusSignMatcher.getInstance(symbols, true); } - public PlusSignMatcher plusSign(boolean allowTrailing) { - return PlusSignMatcher.getInstance(symbols, allowTrailing); + public PlusSignMatcher plusSign() { + return PlusSignMatcher.getInstance(symbols, true); } public PercentMatcher percent() { diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java index ed75d2d514..5698472fa9 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java @@ -74,7 +74,7 @@ public class NumberParserImpl { DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale); IgnorablesMatcher ignorables = IgnorablesMatcher.DEFAULT; - MatcherFactory factory = new MatcherFactory(); + AffixTokenMatcherFactory factory = new AffixTokenMatcherFactory(); factory.currency = Currency.getInstance("USD"); factory.symbols = symbols; factory.ignorables = ignorables; @@ -195,7 +195,7 @@ public class NumberParserImpl { NumberParserImpl parser = new NumberParserImpl(parseFlags); - MatcherFactory factory = new MatcherFactory(); + AffixTokenMatcherFactory factory = new AffixTokenMatcherFactory(); factory.currency = currency; factory.symbols = symbols; factory.ignorables = ignorables; diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java index 41312d8399..c1e5eaf842 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java @@ -9,9 +9,10 @@ import org.junit.Test; import com.ibm.icu.impl.number.CustomSymbolCurrency; import com.ibm.icu.impl.number.DecimalFormatProperties; +import com.ibm.icu.impl.number.parse.AffixPatternMatcher; +import com.ibm.icu.impl.number.parse.AffixTokenMatcherFactory; import com.ibm.icu.impl.number.parse.AnyMatcher; import com.ibm.icu.impl.number.parse.IgnorablesMatcher; -import com.ibm.icu.impl.number.parse.MatcherFactory; import com.ibm.icu.impl.number.parse.MinusSignMatcher; import com.ibm.icu.impl.number.parse.NumberParserImpl; import com.ibm.icu.impl.number.parse.ParsedNumber; @@ -23,6 +24,7 @@ import com.ibm.icu.impl.number.parse.StringSegment; import com.ibm.icu.impl.number.parse.UnicodeSetStaticCache; import com.ibm.icu.impl.number.parse.UnicodeSetStaticCache.Key; import com.ibm.icu.text.DecimalFormatSymbols; +import com.ibm.icu.util.Currency; import com.ibm.icu.util.ULocale; /** @@ -227,7 +229,7 @@ public class NumberParserTest { @Test public void testCurrencyAnyMatcher() { - MatcherFactory factory = new MatcherFactory(); + AffixTokenMatcherFactory factory = new AffixTokenMatcherFactory(); factory.locale = ULocale.ENGLISH; CustomSymbolCurrency currency = new CustomSymbolCurrency("ICU", "IU$", "ICU"); factory.currency = currency; @@ -257,6 +259,45 @@ public class NumberParserTest { } } + @Test + public void testAffixPatternMatcher() { + AffixTokenMatcherFactory factory = new AffixTokenMatcherFactory(); + factory.currency = Currency.getInstance("EUR"); + factory.symbols = DecimalFormatSymbols.getInstance(ULocale.ENGLISH); + factory.ignorables = IgnorablesMatcher.DEFAULT; + factory.locale = ULocale.ENGLISH; + + Object[][] cases = { + { false, "-", 1, "-" }, + { false, "+-%", 5, "+-%" }, + { true, "+-%", 3, "+-%" }, + { false, "ab c", 5, "a bc" }, + { true, "abc", 3, "abc" }, + { false, "hello-to+this%very¤long‰string", 59, "hello-to+this%very USD long‰string" } }; + + for (Object[] cas : cases) { + boolean exactMatch = (Boolean) cas[0]; + String affixPattern = (String) cas[1]; + int expectedMatcherLength = (Integer) cas[2]; + String sampleParseableString = (String) cas[3]; + int parseFlags = exactMatch ? ParsingUtils.PARSE_FLAG_EXACT_AFFIX : 0; + + AffixPatternMatcher matcher = AffixPatternMatcher + .fromAffixPattern(affixPattern, factory, parseFlags); + + // Check that the matcher has the expected number of children + assertEquals(affixPattern + " " + exactMatch, expectedMatcherLength, matcher.length()); + + // Check that the matcher works on a sample string + StringSegment segment = new StringSegment(sampleParseableString, 0); + ParsedNumber result = new ParsedNumber(); + matcher.match(segment, result); + assertEquals(affixPattern + " " + exactMatch, + sampleParseableString.length(), + result.charEnd); + } + } + @Test public void testGroupingDisabled() { DecimalFormatProperties properties = new DecimalFormatProperties(); From 59587ad9dbc2ea554566152d67e9e0db94d7a741 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Sat, 10 Feb 2018 10:57:30 +0000 Subject: [PATCH 013/129] ICU-13574 Improving object lifecycle of AffixPatternMatcher and helper classes. Should be safe now. X-SVN-Rev: 40892 --- icu4c/source/i18n/numparse_affixes.cpp | 89 ++++++++++++------- icu4c/source/i18n/numparse_affixes.h | 89 +++++++++++-------- .../source/test/intltest/numbertest_parse.cpp | 4 +- .../parse/AffixTokenMatcherFactory.java | 2 +- 4 files changed, 114 insertions(+), 70 deletions(-) diff --git a/icu4c/source/i18n/numparse_affixes.cpp b/icu4c/source/i18n/numparse_affixes.cpp index 84a7f75163..2f31704190 100644 --- a/icu4c/source/i18n/numparse_affixes.cpp +++ b/icu4c/source/i18n/numparse_affixes.cpp @@ -18,14 +18,12 @@ using namespace icu::number::impl; AffixPatternMatcherBuilder::AffixPatternMatcherBuilder(const UnicodeString& pattern, - AffixTokenMatcherFactory& factory, + AffixTokenMatcherWarehouse& warehouse, IgnorablesMatcher* ignorables) : fMatchersLen(0), fLastTypeOrCp(0), - fCodePointMatchers(new CodePointMatcher[100]), - fCodePointMatchersLen(0), fPattern(pattern), - fFactory(factory), + fWarehouse(warehouse), fIgnorables(ignorables) {} void AffixPatternMatcherBuilder::consumeToken(AffixPatternType type, UChar32 cp, UErrorCode& status) { @@ -42,16 +40,16 @@ void AffixPatternMatcherBuilder::consumeToken(AffixPatternType type, UChar32 cp, // Case 1: the token is a symbol. switch (type) { case TYPE_MINUS_SIGN: - addMatcher(fFactory.minusSign = {fFactory.dfs, true}); + addMatcher(fWarehouse.minusSign = {fWarehouse.dfs, true}); break; case TYPE_PLUS_SIGN: - addMatcher(fFactory.plusSign = {fFactory.dfs, true}); + addMatcher(fWarehouse.plusSign = {fWarehouse.dfs, true}); break; case TYPE_PERCENT: - addMatcher(fFactory.percent = {fFactory.dfs}); + addMatcher(fWarehouse.percent = {fWarehouse.dfs}); break; case TYPE_PERMILLE: - addMatcher(fFactory.permille = {fFactory.dfs}); + addMatcher(fWarehouse.permille = {fWarehouse.dfs}); break; case TYPE_CURRENCY_SINGLE: case TYPE_CURRENCY_DOUBLE: @@ -60,10 +58,12 @@ void AffixPatternMatcherBuilder::consumeToken(AffixPatternType type, UChar32 cp, case TYPE_CURRENCY_QUINT: // All currency symbols use the same matcher addMatcher( - fFactory.currency = { + fWarehouse.currency = { CurrencyNamesMatcher( - fFactory.locale, status), CurrencyCustomMatcher( - fFactory.currencyCode, fFactory.currency1, fFactory.currency2)}); + fWarehouse.locale, status), CurrencyCustomMatcher( + fWarehouse.currencyCode, + fWarehouse.currency1, + fWarehouse.currency2)}); break; default: U_ASSERT(FALSE); @@ -75,10 +75,7 @@ void AffixPatternMatcherBuilder::consumeToken(AffixPatternType type, UChar32 cp, } else { // Case 3: the token is a non-ignorable literal. - // TODO: This is really clunky. Just trying to get something that works. - fCodePointMatchers[fCodePointMatchersLen] = {cp}; - addMatcher(fCodePointMatchers[fCodePointMatchersLen]); - fCodePointMatchersLen++; + addMatcher(fWarehouse.nextCodePointMatcher(cp)); } fLastTypeOrCp = type != TYPE_CODEPOINT ? type : cp; } @@ -91,19 +88,50 @@ void AffixPatternMatcherBuilder::addMatcher(NumberParseMatcher& matcher) { } AffixPatternMatcher AffixPatternMatcherBuilder::build() { - return AffixPatternMatcher(fMatchers, fMatchersLen, fPattern, fCodePointMatchers.orphan()); + return AffixPatternMatcher(fMatchers, fMatchersLen, fPattern); } -AffixTokenMatcherFactory::AffixTokenMatcherFactory(const UChar* currencyCode, - const UnicodeString& currency1, - const UnicodeString& currency2, - const DecimalFormatSymbols& dfs, - IgnorablesMatcher* ignorables, const Locale& locale) - : currency1(currency1), currency2(currency2), dfs(dfs), ignorables(ignorables), locale(locale) { +AffixTokenMatcherWarehouse::AffixTokenMatcherWarehouse(const UChar* currencyCode, + const UnicodeString& currency1, + const UnicodeString& currency2, + const DecimalFormatSymbols& dfs, + IgnorablesMatcher* ignorables, const Locale& locale) + : currency1(currency1), + currency2(currency2), + dfs(dfs), + ignorables(ignorables), + locale(locale), + codePointCount(0), + codePointNumBatches(0) { utils::copyCurrencyCode(this->currencyCode, currencyCode); } +AffixTokenMatcherWarehouse::~AffixTokenMatcherWarehouse() { + // Delete the variable number of batches of code point matchers + for (int32_t i=0; i= totalCapacity) { + // Need a new batch + auto* nextBatch = new CodePointMatcher[CODE_POINT_BATCH_SIZE]; + if (codePointNumBatches >= codePointsOverflow.getCapacity()) { + // Need more room for storing pointers to batches + codePointsOverflow.resize(codePointNumBatches * 2, codePointNumBatches); + } + codePointsOverflow[codePointNumBatches++] = nextBatch; + } + return codePointsOverflow[codePointNumBatches - 1][(codePointCount++ - CODE_POINT_STACK_CAPACITY) % + CODE_POINT_BATCH_SIZE] = {cp}; +} + CodePointMatcher::CodePointMatcher(UChar32 cp) : fCp(cp) {} @@ -127,9 +155,10 @@ const UnicodeSet& CodePointMatcher::getLeadCodePoints() { } -AffixPatternMatcher -AffixPatternMatcher::fromAffixPattern(const UnicodeString& affixPattern, AffixTokenMatcherFactory& factory, - parse_flags_t parseFlags, bool* success, UErrorCode& status) { +AffixPatternMatcher AffixPatternMatcher::fromAffixPattern(const UnicodeString& affixPattern, + AffixTokenMatcherWarehouse& warehouse, + parse_flags_t parseFlags, bool* success, + UErrorCode& status) { if (affixPattern.isEmpty()) { *success = false; return {}; @@ -140,19 +169,17 @@ AffixPatternMatcher::fromAffixPattern(const UnicodeString& affixPattern, AffixTo if (0 != (parseFlags & PARSE_FLAG_EXACT_AFFIX)) { ignorables = nullptr; } else { - ignorables = factory.ignorables; + ignorables = warehouse.ignorables; } - AffixPatternMatcherBuilder builder(affixPattern, factory, ignorables); + AffixPatternMatcherBuilder builder(affixPattern, warehouse, ignorables); AffixUtils::iterateWithConsumer(UnicodeStringCharSequence(affixPattern), builder, status); return builder.build(); } AffixPatternMatcher::AffixPatternMatcher(MatcherArray& matchers, int32_t matchersLen, - const UnicodeString& pattern, CodePointMatcher* codePointMatchers) - : ArraySeriesMatcher(matchers, matchersLen), - fPattern(pattern), - fCodePointMatchers(codePointMatchers) { + const UnicodeString& pattern) + : ArraySeriesMatcher(matchers, matchersLen), fPattern(pattern) { } diff --git a/icu4c/source/i18n/numparse_affixes.h b/icu4c/source/i18n/numparse_affixes.h index 460034e3fa..77a5aa18da 100644 --- a/icu4c/source/i18n/numparse_affixes.h +++ b/icu4c/source/i18n/numparse_affixes.h @@ -19,31 +19,6 @@ namespace impl { class AffixPatternMatcherBuilder; class AffixPatternMatcher; -class AffixTokenMatcherFactory { - public: - AffixTokenMatcherFactory(const UChar* currencyCode, const UnicodeString& currency1, - const UnicodeString& currency2, const DecimalFormatSymbols& dfs, - IgnorablesMatcher* ignorables, const Locale& locale); - - private: - UChar currencyCode[4]; - const UnicodeString& currency1; - const UnicodeString& currency2; - const DecimalFormatSymbols& dfs; - IgnorablesMatcher* ignorables; - const Locale locale; - - // NOTE: These are default-constructed and should not be used until initialized. - MinusSignMatcher minusSign; - PlusSignMatcher plusSign; - PercentMatcher percent; - PermilleMatcher permille; - CurrencyAnyMatcher currency; - - friend class AffixPatternMatcherBuilder; - friend class AffixPatternMatcher; -}; - class CodePointMatcher : public NumberParseMatcher, public UMemory { public: @@ -60,9 +35,58 @@ class CodePointMatcher : public NumberParseMatcher, public UMemory { }; +/** + * Small helper class that generates matchers for individual tokens for AffixPatternMatcher. + * + * In Java, this is called AffixTokenMatcherFactory (a "factory"). However, in C++, it is called a + * "warehouse", because in addition to generating the matchers, it also retains ownership of them. The + * warehouse must stay in scope for the whole lifespan of the AffixPatternMatcher that uses matchers from + * the warehouse. + * + * @author sffc + */ +class AffixTokenMatcherWarehouse { + private: + static constexpr int32_t CODE_POINT_STACK_CAPACITY = 5; // Number of entries directly on the stack + static constexpr int32_t CODE_POINT_BATCH_SIZE = 10; // Number of entries per heap allocation + + public: + AffixTokenMatcherWarehouse(const UChar* currencyCode, const UnicodeString& currency1, + const UnicodeString& currency2, const DecimalFormatSymbols& dfs, + IgnorablesMatcher* ignorables, const Locale& locale); + + ~AffixTokenMatcherWarehouse(); + + CodePointMatcher& nextCodePointMatcher(UChar32 cp); + + private: + UChar currencyCode[4]; + const UnicodeString& currency1; + const UnicodeString& currency2; + const DecimalFormatSymbols& dfs; + IgnorablesMatcher* ignorables; + const Locale locale; + + // NOTE: These are default-constructed and should not be used until initialized. + MinusSignMatcher minusSign; + PlusSignMatcher plusSign; + PercentMatcher percent; + PermilleMatcher permille; + CurrencyAnyMatcher currency; + + CodePointMatcher codePoints[CODE_POINT_STACK_CAPACITY]; // By value + MaybeStackArray codePointsOverflow; // On heap in "batches" + int32_t codePointCount; // Total for both the ones by value and on heap + int32_t codePointNumBatches; // Number of batches in codePointsOverflow + + friend class AffixPatternMatcherBuilder; + friend class AffixPatternMatcher; +}; + + class AffixPatternMatcherBuilder : public ::icu::number::impl::TokenConsumer { public: - AffixPatternMatcherBuilder(const UnicodeString& pattern, AffixTokenMatcherFactory& factory, + AffixPatternMatcherBuilder(const UnicodeString& pattern, AffixTokenMatcherWarehouse& warehouse, IgnorablesMatcher* ignorables); void consumeToken(::icu::number::impl::AffixPatternType type, UChar32 cp, UErrorCode& status) override; @@ -75,11 +99,8 @@ class AffixPatternMatcherBuilder : public ::icu::number::impl::TokenConsumer { int32_t fMatchersLen; int32_t fLastTypeOrCp; - LocalArray fCodePointMatchers; - int32_t fCodePointMatchersLen; - const UnicodeString& fPattern; - AffixTokenMatcherFactory& fFactory; + AffixTokenMatcherWarehouse& fWarehouse; IgnorablesMatcher* fIgnorables; void addMatcher(NumberParseMatcher& matcher); @@ -89,20 +110,16 @@ class AffixPatternMatcherBuilder : public ::icu::number::impl::TokenConsumer { class AffixPatternMatcher : public ArraySeriesMatcher { public: static AffixPatternMatcher fromAffixPattern(const UnicodeString& affixPattern, - AffixTokenMatcherFactory& factory, + AffixTokenMatcherWarehouse& warehouse, parse_flags_t parseFlags, bool* success, UErrorCode& status); private: UnicodeString fPattern; - // We need to own the variable number of CodePointMatchers. - LocalArray fCodePointMatchers; - AffixPatternMatcher() = default; // WARNING: Leaves the object in an unusable state - AffixPatternMatcher(MatcherArray& matchers, int32_t matchersLen, const UnicodeString& pattern, - CodePointMatcher* codePointMatchers); + AffixPatternMatcher(MatcherArray& matchers, int32_t matchersLen, const UnicodeString& pattern); friend class AffixPatternMatcherBuilder; }; diff --git a/icu4c/source/test/intltest/numbertest_parse.cpp b/icu4c/source/test/intltest/numbertest_parse.cpp index 776223044b..78492aa391 100644 --- a/icu4c/source/test/intltest/numbertest_parse.cpp +++ b/icu4c/source/test/intltest/numbertest_parse.cpp @@ -215,7 +215,7 @@ void NumberParserTest::testAffixPatternMatcher() { IcuTestErrorCode status(*this, "testAffixPatternMatcher"); IgnorablesMatcher ignorables(unisets::DEFAULT_IGNORABLES); - AffixTokenMatcherFactory factory(u"EUR", u"foo", u"bar", {"en", status}, &ignorables, "en"); + AffixTokenMatcherWarehouse warehouse(u"EUR", u"foo", u"bar", {"en", status}, &ignorables, "en"); static const struct TestCase { bool exactMatch; @@ -237,7 +237,7 @@ void NumberParserTest::testAffixPatternMatcher() { bool success; AffixPatternMatcher matcher = AffixPatternMatcher::fromAffixPattern( - affixPattern, factory, parseFlags, &success, status); + affixPattern, warehouse, parseFlags, &success, status); assertTrue("Creation should be successful", success); // Check that the matcher has the expected number of children diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixTokenMatcherFactory.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixTokenMatcherFactory.java index 142b29faef..4c051572aa 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixTokenMatcherFactory.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixTokenMatcherFactory.java @@ -7,7 +7,7 @@ import com.ibm.icu.util.Currency; import com.ibm.icu.util.ULocale; /** - * Small helper class that generates matchers for SeriesMatcher. + * Small helper class that generates matchers for individual tokens for AffixPatternMatcher. * * @author sffc */ From a335b723c742069915ae43d32df5b60a99e4fcb0 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Sat, 10 Feb 2018 11:32:18 +0000 Subject: [PATCH 014/129] ICU-13574 Fixing CurrencyAnyMatcher. Up-to-date with tests. X-SVN-Rev: 40893 --- icu4c/source/i18n/numparse_affixes.cpp | 42 ++++++++++++------- icu4c/source/i18n/numparse_affixes.h | 22 +++++++--- icu4c/source/i18n/numparse_compositions.cpp | 2 +- icu4c/source/i18n/numparse_currency.cpp | 13 ++++++ icu4c/source/i18n/numparse_currency.h | 6 +++ .../source/test/intltest/numbertest_parse.cpp | 36 +++++++++++++++- 6 files changed, 99 insertions(+), 22 deletions(-) diff --git a/icu4c/source/i18n/numparse_affixes.cpp b/icu4c/source/i18n/numparse_affixes.cpp index 2f31704190..66ce2bef8c 100644 --- a/icu4c/source/i18n/numparse_affixes.cpp +++ b/icu4c/source/i18n/numparse_affixes.cpp @@ -40,16 +40,16 @@ void AffixPatternMatcherBuilder::consumeToken(AffixPatternType type, UChar32 cp, // Case 1: the token is a symbol. switch (type) { case TYPE_MINUS_SIGN: - addMatcher(fWarehouse.minusSign = {fWarehouse.dfs, true}); + addMatcher(fWarehouse.minusSign()); break; case TYPE_PLUS_SIGN: - addMatcher(fWarehouse.plusSign = {fWarehouse.dfs, true}); + addMatcher(fWarehouse.plusSign()); break; case TYPE_PERCENT: - addMatcher(fWarehouse.percent = {fWarehouse.dfs}); + addMatcher(fWarehouse.percent()); break; case TYPE_PERMILLE: - addMatcher(fWarehouse.permille = {fWarehouse.dfs}); + addMatcher(fWarehouse.permille()); break; case TYPE_CURRENCY_SINGLE: case TYPE_CURRENCY_DOUBLE: @@ -57,13 +57,7 @@ void AffixPatternMatcherBuilder::consumeToken(AffixPatternType type, UChar32 cp, case TYPE_CURRENCY_QUAD: case TYPE_CURRENCY_QUINT: // All currency symbols use the same matcher - addMatcher( - fWarehouse.currency = { - CurrencyNamesMatcher( - fWarehouse.locale, status), CurrencyCustomMatcher( - fWarehouse.currencyCode, - fWarehouse.currency1, - fWarehouse.currency2)}); + addMatcher(fWarehouse.currency(status)); break; default: U_ASSERT(FALSE); @@ -109,12 +103,32 @@ AffixTokenMatcherWarehouse::AffixTokenMatcherWarehouse(const UChar* currencyCode AffixTokenMatcherWarehouse::~AffixTokenMatcherWarehouse() { // Delete the variable number of batches of code point matchers - for (int32_t i=0; i codePointsOverflow; // On heap in "batches" diff --git a/icu4c/source/i18n/numparse_compositions.cpp b/icu4c/source/i18n/numparse_compositions.cpp index 138b45c6da..6d8d52d2ba 100644 --- a/icu4c/source/i18n/numparse_compositions.cpp +++ b/icu4c/source/i18n/numparse_compositions.cpp @@ -19,7 +19,7 @@ bool AnyMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCode& bool maybeMore = false; // NOTE: The range-based for loop calls the virtual begin() and end() methods. - for (auto* matcher : *this) { + for (auto& matcher : *this) { maybeMore = maybeMore || matcher->match(segment, result, status); if (segment.getOffset() != initialOffset) { // Match succeeded. diff --git a/icu4c/source/i18n/numparse_currency.cpp b/icu4c/source/i18n/numparse_currency.cpp index 1cd730214a..cf56a94f6d 100644 --- a/icu4c/source/i18n/numparse_currency.cpp +++ b/icu4c/source/i18n/numparse_currency.cpp @@ -119,6 +119,19 @@ CurrencyAnyMatcher::CurrencyAnyMatcher(CurrencyNamesMatcher namesMatcher, fMatcherArray[1] = &fCustomMatcher; } +CurrencyAnyMatcher::CurrencyAnyMatcher(CurrencyAnyMatcher&& src) U_NOEXCEPT + : fNamesMatcher(std::move(src.fNamesMatcher)), fCustomMatcher(std::move(src.fCustomMatcher)) { + fMatcherArray[0] = &fNamesMatcher; + fMatcherArray[1] = &fCustomMatcher; +} + +CurrencyAnyMatcher& CurrencyAnyMatcher::operator=(CurrencyAnyMatcher&& src) U_NOEXCEPT { + fNamesMatcher = std::move(src.fNamesMatcher); + fCustomMatcher = std::move(src.fCustomMatcher); + // Note: do NOT move fMatcherArray + return *this; +} + const UnicodeSet& CurrencyAnyMatcher::getLeadCodePoints() { if (fLocalLeadCodePoints.isNull()) { auto* leadCodePoints = new UnicodeSet(); diff --git a/icu4c/source/i18n/numparse_currency.h b/icu4c/source/i18n/numparse_currency.h index bbfa15094a..547f6ee041 100644 --- a/icu4c/source/i18n/numparse_currency.h +++ b/icu4c/source/i18n/numparse_currency.h @@ -67,6 +67,12 @@ class CurrencyAnyMatcher : public AnyMatcher, public UMemory { CurrencyAnyMatcher(CurrencyNamesMatcher namesMatcher, CurrencyCustomMatcher customMatcher); + // Needs custom move constructor/operator since constructor is nontrivial + + CurrencyAnyMatcher(CurrencyAnyMatcher&& src) U_NOEXCEPT; + + CurrencyAnyMatcher& operator=(CurrencyAnyMatcher&& src) U_NOEXCEPT; + const UnicodeSet& getLeadCodePoints() override; protected: diff --git a/icu4c/source/test/intltest/numbertest_parse.cpp b/icu4c/source/test/intltest/numbertest_parse.cpp index 78492aa391..9cb8dd12d4 100644 --- a/icu4c/source/test/intltest/numbertest_parse.cpp +++ b/icu4c/source/test/intltest/numbertest_parse.cpp @@ -23,6 +23,7 @@ void NumberParserTest::runIndexedTest(int32_t index, UBool exec, const char*& na TESTCASE_AUTO_BEGIN; TESTCASE_AUTO(testBasic); TESTCASE_AUTO(testSeriesMatcher); + TESTCASE_AUTO(testCurrencyAnyMatcher); TESTCASE_AUTO(testAffixPatternMatcher); TESTCASE_AUTO_END; } @@ -211,6 +212,39 @@ void NumberParserTest::testSeriesMatcher() { } } +void NumberParserTest::testCurrencyAnyMatcher() { + IcuTestErrorCode status(*this, "testCurrencyAnyMatcher"); + + IgnorablesMatcher ignorables(unisets::DEFAULT_IGNORABLES); + AffixTokenMatcherWarehouse warehouse(u"ICU", u"IU$", u"ICU", {"en",status}, &ignorables, "en"); + NumberParseMatcher& matcher = warehouse.currency(status); + + static const struct TestCase{ + const char16_t* input; + const char16_t* expectedCurrencyCode; + } cases[] { + { u"", u"\x00" }, + { u"FOO", u"\x00" }, + { u"USD", u"USD" }, + { u"$", u"USD" }, + { u"US dollars", u"USD" }, + { u"eu", u"\x00" }, + { u"euros", u"EUR" }, + { u"ICU", u"ICU" }, + { u"IU$", u"ICU" } }; + for (auto& cas : cases) { + UnicodeString input(cas.input); + + StringSegment segment(input, 0); + ParsedNumber result; + matcher.match(segment, result, status); + assertEquals("Parsing " + input, cas.expectedCurrencyCode, result.currencyCode); + assertEquals("Whole string on " + input, + cas.expectedCurrencyCode[0] == 0 ? 0 : input.length(), + result.charEnd); + } +} + void NumberParserTest::testAffixPatternMatcher() { IcuTestErrorCode status(*this, "testAffixPatternMatcher"); @@ -227,7 +261,7 @@ void NumberParserTest::testAffixPatternMatcher() { {true, u"+-%", 3, u"+-%"}, {false, u"ab c", 5, u"a bc"}, {true, u"abc", 3, u"abc"}, - //{false, u"hello-to+this%very¤long‰string", 59, u"hello-to+this%very USD long‰string"} + {false, u"hello-to+this%very¤long‰string", 59, u"hello-to+this%very USD long‰string"} }; for (auto& cas : cases) { From afbb37febdd194f2a32289571a65ae24d40b93cf Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Sat, 10 Feb 2018 14:29:26 +0000 Subject: [PATCH 015/129] ICU-13574 Checkpoint commit. AffixMatcher is mostly implemented. X-SVN-Rev: 40894 --- icu4c/source/i18n/number_affixutils.cpp | 17 ++ icu4c/source/i18n/number_affixutils.h | 47 ++-- icu4c/source/i18n/number_patternmodifier.cpp | 200 ++++++-------- icu4c/source/i18n/number_patternmodifier.h | 36 +-- icu4c/source/i18n/number_patternstring.cpp | 142 +++++++--- icu4c/source/i18n/number_patternstring.h | 89 ++++--- icu4c/source/i18n/number_types.h | 42 +-- icu4c/source/i18n/numparse_affixes.cpp | 244 +++++++++++++++++- icu4c/source/i18n/numparse_affixes.h | 93 ++++++- icu4c/source/i18n/numparse_symbols.cpp | 2 +- icu4c/source/i18n/numparse_symbols.h | 2 +- icu4c/source/i18n/numparse_types.h | 34 ++- .../source/test/intltest/numbertest_parse.cpp | 12 +- 13 files changed, 663 insertions(+), 297 deletions(-) diff --git a/icu4c/source/i18n/number_affixutils.cpp b/icu4c/source/i18n/number_affixutils.cpp index 072edbb2fa..2d08414ddd 100644 --- a/icu4c/source/i18n/number_affixutils.cpp +++ b/icu4c/source/i18n/number_affixutils.cpp @@ -7,6 +7,7 @@ #include "number_affixutils.h" #include "unicode/utf16.h" +#include "unicode/uniset.h" using namespace icu; using namespace icu::number; @@ -239,6 +240,22 @@ UnicodeString AffixUtils::replaceType(const CharSequence &affixPattern, AffixPat return output; } +bool AffixUtils::containsOnlySymbolsAndIgnorables(const CharSequence& affixPattern, + const UnicodeSet& ignorables, UErrorCode& status) { + if (affixPattern.length() == 0) { + return true; + }; + AffixTag tag; + while (hasNext(tag, affixPattern)) { + tag = nextToken(tag, affixPattern, status); + if (U_FAILURE(status)) { return false; } + if (tag.type == TYPE_CODEPOINT && !ignorables.contains(tag.codePoint)) { + return false; + } + } + return true; +} + void AffixUtils::iterateWithConsumer(const CharSequence& affixPattern, TokenConsumer& consumer, UErrorCode& status) { if (affixPattern.length() == 0) { diff --git a/icu4c/source/i18n/number_affixutils.h b/icu4c/source/i18n/number_affixutils.h index 665a9d8425..d8b525a6e1 100644 --- a/icu4c/source/i18n/number_affixutils.h +++ b/icu4c/source/i18n/number_affixutils.h @@ -37,13 +37,14 @@ struct AffixTag { AffixPatternState state; AffixPatternType type; - AffixTag() : offset(0), state(STATE_BASE) {} + AffixTag() + : offset(0), state(STATE_BASE) {} - AffixTag(int32_t offset) : offset(offset) {} + AffixTag(int32_t offset) + : offset(offset) {} AffixTag(int32_t offset, UChar32 codePoint, AffixPatternState state, AffixPatternType type) - : offset(offset), codePoint(codePoint), state(state), type(type) - {} + : offset(offset), codePoint(codePoint), state(state), type(type) {} }; class TokenConsumer { @@ -112,7 +113,7 @@ class U_I18N_API AffixUtils { * @param patternString The original string whose width will be estimated. * @return The length of the unescaped string. */ - static int32_t estimateLength(const CharSequence &patternString, UErrorCode &status); + static int32_t estimateLength(const CharSequence& patternString, UErrorCode& status); /** * Takes a string and escapes (quotes) characters that have special meaning in the affix pattern @@ -123,7 +124,7 @@ class U_I18N_API AffixUtils { * @param input The string to be escaped. * @return The resulting UnicodeString. */ - static UnicodeString escape(const CharSequence &input); + static UnicodeString escape(const CharSequence& input); static Field getFieldForType(AffixPatternType type); @@ -139,9 +140,8 @@ class U_I18N_API AffixUtils { * @param position The index into the NumberStringBuilder to insert the string. * @param provider An object to generate locale symbols. */ - static int32_t - unescape(const CharSequence &affixPattern, NumberStringBuilder &output, int32_t position, - const SymbolProvider &provider, UErrorCode &status); + static int32_t unescape(const CharSequence& affixPattern, NumberStringBuilder& output, + int32_t position, const SymbolProvider& provider, UErrorCode& status); /** * Sames as {@link #unescape}, but only calculates the code point count. More efficient than {@link #unescape} @@ -151,8 +151,8 @@ class U_I18N_API AffixUtils { * @param provider An object to generate locale symbols. * @return The same return value as if you called {@link #unescape}. */ - static int32_t unescapedCodePointCount(const CharSequence &affixPattern, - const SymbolProvider &provider, UErrorCode &status); + static int32_t unescapedCodePointCount(const CharSequence& affixPattern, + const SymbolProvider& provider, UErrorCode& status); /** * Checks whether the given affix pattern contains at least one token of the given type, which is @@ -162,8 +162,7 @@ class U_I18N_API AffixUtils { * @param type The token type. * @return true if the affix pattern contains the given token type; false otherwise. */ - static bool - containsType(const CharSequence &affixPattern, AffixPatternType type, UErrorCode &status); + static bool containsType(const CharSequence& affixPattern, AffixPatternType type, UErrorCode& status); /** * Checks whether the specified affix pattern has any unquoted currency symbols ("¤"). @@ -171,7 +170,7 @@ class U_I18N_API AffixUtils { * @param affixPattern The string to check for currency symbols. * @return true if the literal has at least one unquoted currency symbol; false otherwise. */ - static bool hasCurrencySymbols(const CharSequence &affixPattern, UErrorCode &status); + static bool hasCurrencySymbols(const CharSequence& affixPattern, UErrorCode& status); /** * Replaces all occurrences of tokens with the given type with the given replacement char. @@ -181,9 +180,15 @@ class U_I18N_API AffixUtils { * @param replacementChar The char to substitute in place of chars of the given token type. * @return A string containing the new affix pattern. */ - static UnicodeString - replaceType(const CharSequence &affixPattern, AffixPatternType type, char16_t replacementChar, - UErrorCode &status); + static UnicodeString replaceType(const CharSequence& affixPattern, AffixPatternType type, + char16_t replacementChar, UErrorCode& status); + + /** + * Returns whether the given affix pattern contains only symbols and ignorables as defined by the + * given ignorables set. + */ + static bool containsOnlySymbolsAndIgnorables(const CharSequence& affixPattern, + const UnicodeSet& ignorables, UErrorCode& status); /** * Iterates over the affix pattern, calling the TokenConsumer for each token. @@ -201,7 +206,7 @@ class U_I18N_API AffixUtils { * (never negative), or -1 if there were no more tokens in the affix pattern. * @see #hasNext */ - static AffixTag nextToken(AffixTag tag, const CharSequence &patternString, UErrorCode &status); + static AffixTag nextToken(AffixTag tag, const CharSequence& patternString, UErrorCode& status); /** * Returns whether the affix pattern string has any more tokens to be retrieved from a call to @@ -211,7 +216,7 @@ class U_I18N_API AffixUtils { * @param string The affix pattern. * @return true if there are more tokens to consume; false otherwise. */ - static bool hasNext(const AffixTag &tag, const CharSequence &string); + static bool hasNext(const AffixTag& tag, const CharSequence& string); private: /** @@ -219,8 +224,8 @@ class U_I18N_API AffixUtils { * The order of the arguments is consistent with Java, but the order of the stored * fields is not necessarily the same. */ - static inline AffixTag - makeTag(int32_t offset, AffixPatternType type, AffixPatternState state, UChar32 cp) { + static inline AffixTag makeTag(int32_t offset, AffixPatternType type, AffixPatternState state, + UChar32 cp) { return {offset, cp, state, type}; } }; diff --git a/icu4c/source/i18n/number_patternmodifier.cpp b/icu4c/source/i18n/number_patternmodifier.cpp index e182104c91..b77f559a26 100644 --- a/icu4c/source/i18n/number_patternmodifier.cpp +++ b/icu4c/source/i18n/number_patternmodifier.cpp @@ -15,9 +15,10 @@ using namespace icu; using namespace icu::number; using namespace icu::number::impl; -MutablePatternModifier::MutablePatternModifier(bool isStrong) : fStrong(isStrong) {} +MutablePatternModifier::MutablePatternModifier(bool isStrong) + : fStrong(isStrong) {} -void MutablePatternModifier::setPatternInfo(const AffixPatternProvider *patternInfo) { +void MutablePatternModifier::setPatternInfo(const AffixPatternProvider* patternInfo) { this->patternInfo = patternInfo; } @@ -26,12 +27,11 @@ void MutablePatternModifier::setPatternAttributes(UNumberSignDisplay signDisplay this->perMilleReplacesPercent = perMille; } -void -MutablePatternModifier::setSymbols(const DecimalFormatSymbols *symbols, const CurrencyUnit ¤cy, - const UNumberUnitWidth unitWidth, const PluralRules *rules) { +void MutablePatternModifier::setSymbols(const DecimalFormatSymbols* symbols, const CurrencyUnit& currency, + const UNumberUnitWidth unitWidth, const PluralRules* rules) { U_ASSERT((rules != nullptr) == needsPlurals()); this->symbols = symbols; - uprv_memcpy(static_cast(this->currencyCode), + uprv_memcpy(static_cast(this->currencyCode), currency.getISOCurrency(), sizeof(char16_t) * 4); this->unitWidth = unitWidth; @@ -49,12 +49,12 @@ bool MutablePatternModifier::needsPlurals() const { // Silently ignore any error codes. } -ImmutablePatternModifier *MutablePatternModifier::createImmutable(UErrorCode &status) { +ImmutablePatternModifier* MutablePatternModifier::createImmutable(UErrorCode& status) { return createImmutableAndChain(nullptr, status); } -ImmutablePatternModifier * -MutablePatternModifier::createImmutableAndChain(const MicroPropsGenerator *parent, UErrorCode &status) { +ImmutablePatternModifier* +MutablePatternModifier::createImmutableAndChain(const MicroPropsGenerator* parent, UErrorCode& status) { // TODO: Move StandardPlural VALUES to standardplural.h static const StandardPlural::Form STANDARD_PLURAL_VALUES[] = { @@ -89,11 +89,11 @@ MutablePatternModifier::createImmutableAndChain(const MicroPropsGenerator *paren } else { // Faster path when plural keyword is not needed. setNumberProperties(1, StandardPlural::Form::COUNT); - Modifier *positive = createConstantModifier(status); + Modifier* positive = createConstantModifier(status); setNumberProperties(0, StandardPlural::Form::COUNT); - Modifier *zero = createConstantModifier(status); + Modifier* zero = createConstantModifier(status); setNumberProperties(-1, StandardPlural::Form::COUNT); - Modifier *negative = createConstantModifier(status); + Modifier* negative = createConstantModifier(status); pm->adoptPositiveNegativeModifiers(positive, zero, negative); if (U_FAILURE(status)) { delete pm; @@ -103,29 +103,30 @@ MutablePatternModifier::createImmutableAndChain(const MicroPropsGenerator *paren } } -ConstantMultiFieldModifier *MutablePatternModifier::createConstantModifier(UErrorCode &status) { +ConstantMultiFieldModifier* MutablePatternModifier::createConstantModifier(UErrorCode& status) { NumberStringBuilder a; NumberStringBuilder b; insertPrefix(a, 0, status); insertSuffix(b, 0, status); if (patternInfo->hasCurrencySign()) { - return new CurrencySpacingEnabledModifier(a, b, !patternInfo->hasBody(), fStrong, *symbols, status); + return new CurrencySpacingEnabledModifier( + a, b, !patternInfo->hasBody(), fStrong, *symbols, status); } else { return new ConstantMultiFieldModifier(a, b, !patternInfo->hasBody(), fStrong); } } -ImmutablePatternModifier::ImmutablePatternModifier(ParameterizedModifier *pm, const PluralRules *rules, - const MicroPropsGenerator *parent) +ImmutablePatternModifier::ImmutablePatternModifier(ParameterizedModifier* pm, const PluralRules* rules, + const MicroPropsGenerator* parent) : pm(pm), rules(rules), parent(parent) {} -void ImmutablePatternModifier::processQuantity(DecimalQuantity &quantity, MicroProps µs, - UErrorCode &status) const { +void ImmutablePatternModifier::processQuantity(DecimalQuantity& quantity, MicroProps& micros, + UErrorCode& status) const { parent->processQuantity(quantity, micros, status); applyToMicros(micros, quantity); } -void ImmutablePatternModifier::applyToMicros(MicroProps µs, DecimalQuantity &quantity) const { +void ImmutablePatternModifier::applyToMicros(MicroProps& micros, DecimalQuantity& quantity) const { if (rules == nullptr) { micros.modMiddle = pm->getModifier(quantity.signum()); } else { @@ -138,17 +139,17 @@ void ImmutablePatternModifier::applyToMicros(MicroProps µs, DecimalQuantity } /** Used by the unsafe code path. */ -MicroPropsGenerator &MutablePatternModifier::addToChain(const MicroPropsGenerator *parent) { +MicroPropsGenerator& MutablePatternModifier::addToChain(const MicroPropsGenerator* parent) { this->parent = parent; return *this; } -void MutablePatternModifier::processQuantity(DecimalQuantity &fq, MicroProps µs, - UErrorCode &status) const { +void MutablePatternModifier::processQuantity(DecimalQuantity& fq, MicroProps& micros, + UErrorCode& status) const { parent->processQuantity(fq, micros, status); // The unsafe code path performs self-mutation, so we need a const_cast. // This method needs to be const because it overrides a const method in the parent class. - auto nonConstThis = const_cast(this); + auto nonConstThis = const_cast(this); if (needsPlurals()) { // TODO: Fix this. Avoid the copy. DecimalQuantity copy(fq); @@ -160,20 +161,24 @@ void MutablePatternModifier::processQuantity(DecimalQuantity &fq, MicroProps &mi micros.modMiddle = this; } -int32_t MutablePatternModifier::apply(NumberStringBuilder &output, int32_t leftIndex, int32_t rightIndex, - UErrorCode &status) const { +int32_t MutablePatternModifier::apply(NumberStringBuilder& output, int32_t leftIndex, int32_t rightIndex, + UErrorCode& status) const { // The unsafe code path performs self-mutation, so we need a const_cast. // This method needs to be const because it overrides a const method in the parent class. - auto nonConstThis = const_cast(this); + auto nonConstThis = const_cast(this); int32_t prefixLen = nonConstThis->insertPrefix(output, leftIndex, status); int32_t suffixLen = nonConstThis->insertSuffix(output, rightIndex + prefixLen, status); // If the pattern had no decimal stem body (like #,##0.00), overwrite the value. int32_t overwriteLen = 0; if (!patternInfo->hasBody()) { overwriteLen = output.splice( - leftIndex + prefixLen, rightIndex + prefixLen, - UnicodeString(), 0, 0, UNUM_FIELD_COUNT, - status); + leftIndex + prefixLen, + rightIndex + prefixLen, + UnicodeString(), + 0, + 0, + UNUM_FIELD_COUNT, + status); } CurrencySpacingEnabledModifier::applyCurrencySpacing( output, @@ -186,30 +191,36 @@ int32_t MutablePatternModifier::apply(NumberStringBuilder &output, int32_t leftI return prefixLen + overwriteLen + suffixLen; } -int32_t MutablePatternModifier::getPrefixLength(UErrorCode &status) const { +int32_t MutablePatternModifier::getPrefixLength(UErrorCode& status) const { // The unsafe code path performs self-mutation, so we need a const_cast. // This method needs to be const because it overrides a const method in the parent class. - auto nonConstThis = const_cast(this); + auto nonConstThis = const_cast(this); // Enter and exit CharSequence Mode to get the length. - nonConstThis->enterCharSequenceMode(true); - int result = AffixUtils::unescapedCodePointCount(*this, *this, status); // prefix length - nonConstThis->exitCharSequenceMode(); + nonConstThis->prepareAffix(true); + int result = AffixUtils::unescapedCodePointCount( + UnicodeStringCharSequence(currentAffix), + *this, + status); // prefix length return result; } -int32_t MutablePatternModifier::getCodePointCount(UErrorCode &status) const { +int32_t MutablePatternModifier::getCodePointCount(UErrorCode& status) const { // The unsafe code path performs self-mutation, so we need a const_cast. // This method needs to be const because it overrides a const method in the parent class. - auto nonConstThis = const_cast(this); + auto nonConstThis = const_cast(this); - // Enter and exit CharSequence Mode to get the length. - nonConstThis->enterCharSequenceMode(true); - int result = AffixUtils::unescapedCodePointCount(*this, *this, status); // prefix length - nonConstThis->exitCharSequenceMode(); - nonConstThis->enterCharSequenceMode(false); - result += AffixUtils::unescapedCodePointCount(*this, *this, status); // suffix length - nonConstThis->exitCharSequenceMode(); + // Render the affixes to get the length + nonConstThis->prepareAffix(true); + int result = AffixUtils::unescapedCodePointCount( + UnicodeStringCharSequence(currentAffix), + *this, + status); // prefix length + nonConstThis->prepareAffix(false); + result += AffixUtils::unescapedCodePointCount( + UnicodeStringCharSequence(currentAffix), + *this, + status); // suffix length return result; } @@ -217,20 +228,26 @@ bool MutablePatternModifier::isStrong() const { return fStrong; } -int32_t MutablePatternModifier::insertPrefix(NumberStringBuilder &sb, int position, UErrorCode &status) { - enterCharSequenceMode(true); - int length = AffixUtils::unescape(*this, sb, position, *this, status); - exitCharSequenceMode(); +int32_t MutablePatternModifier::insertPrefix(NumberStringBuilder& sb, int position, UErrorCode& status) { + prepareAffix(true); + int length = AffixUtils::unescape( + UnicodeStringCharSequence(currentAffix), sb, position, *this, status); return length; } -int32_t MutablePatternModifier::insertSuffix(NumberStringBuilder &sb, int position, UErrorCode &status) { - enterCharSequenceMode(false); - int length = AffixUtils::unescape(*this, sb, position, *this, status); - exitCharSequenceMode(); +int32_t MutablePatternModifier::insertSuffix(NumberStringBuilder& sb, int position, UErrorCode& status) { + prepareAffix(false); + int length = AffixUtils::unescape( + UnicodeStringCharSequence(currentAffix), sb, position, *this, status); return length; } +/** This method contains the heart of the logic for rendering LDML affix strings. */ +void MutablePatternModifier::prepareAffix(bool isPrefix) { + PatternStringUtils::patternInfoToStringBuilder( + *patternInfo, isPrefix, signum, signDisplay, plural, perMilleReplacesPercent, currentAffix); +} + UnicodeString MutablePatternModifier::getSymbol(AffixPatternType type) const { switch (type) { case AffixPatternType::TYPE_MINUS_SIGN: @@ -249,12 +266,12 @@ UnicodeString MutablePatternModifier::getSymbol(AffixPatternType type) const { return UnicodeString(); } else { UCurrNameStyle selector = (unitWidth == UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW) - ? UCurrNameStyle::UCURR_NARROW_SYMBOL_NAME - : UCurrNameStyle::UCURR_SYMBOL_NAME; + ? UCurrNameStyle::UCURR_NARROW_SYMBOL_NAME + : UCurrNameStyle::UCURR_SYMBOL_NAME; UErrorCode status = U_ZERO_ERROR; UBool isChoiceFormat = FALSE; int32_t symbolLen = 0; - const char16_t *symbol = ucurr_getName( + const char16_t* symbol = ucurr_getName( currencyCode, symbols->getLocale().getName(), selector, @@ -274,7 +291,7 @@ UnicodeString MutablePatternModifier::getSymbol(AffixPatternType type) const { UErrorCode status = U_ZERO_ERROR; UBool isChoiceFormat = FALSE; int32_t symbolLen = 0; - const char16_t *symbol = ucurr_getPluralName( + const char16_t* symbol = ucurr_getPluralName( currencyCode, symbols->getLocale().getName(), &isChoiceFormat, @@ -293,79 +310,6 @@ UnicodeString MutablePatternModifier::getSymbol(AffixPatternType type) const { } } -/** This method contains the heart of the logic for rendering LDML affix strings. */ -void MutablePatternModifier::enterCharSequenceMode(bool isPrefix) { - U_ASSERT(!inCharSequenceMode); - inCharSequenceMode = true; - - // Should the output render '+' where '-' would normally appear in the pattern? - 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() && ( - signum == -1 || (patternInfo->negativeHasMinusSign() && plusReplacesMinusSign)); - - // Resolve the flags for the affix pattern. - fFlags = 0; - if (useNegativeAffixPattern) { - fFlags |= AffixPatternProvider::AFFIX_NEGATIVE_SUBPATTERN; - } - if (isPrefix) { - fFlags |= AffixPatternProvider::AFFIX_PREFIX; - } - if (plural != StandardPlural::Form::COUNT) { - U_ASSERT(plural == (AffixPatternProvider::AFFIX_PLURAL_MASK & plural)); - fFlags |= plural; - } - - // Should we prepend a sign to the pattern? - if (!isPrefix || useNegativeAffixPattern) { - prependSign = false; - } else if (signum == -1) { - prependSign = signDisplay != UNUM_SIGN_NEVER; - } else { - prependSign = plusReplacesMinusSign; - } - - // Finally, compute the length of the affix pattern. - fLength = patternInfo->length(fFlags) + (prependSign ? 1 : 0); -} - -void MutablePatternModifier::exitCharSequenceMode() { - U_ASSERT(inCharSequenceMode); - inCharSequenceMode = false; -} - -int32_t MutablePatternModifier::length() const { - U_ASSERT(inCharSequenceMode); - return fLength; -} - -char16_t MutablePatternModifier::charAt(int32_t index) const { - U_ASSERT(inCharSequenceMode); - char16_t candidate; - if (prependSign && index == 0) { - candidate = u'-'; - } else if (prependSign) { - candidate = patternInfo->charAt(fFlags, index - 1); - } else { - candidate = patternInfo->charAt(fFlags, index); - } - if (plusReplacesMinusSign && candidate == u'-') { - return u'+'; - } - if (perMilleReplacesPercent && candidate == u'%') { - return u'‰'; - } - return candidate; -} - UnicodeString MutablePatternModifier::toUnicodeString() const { // Never called by AffixUtils U_ASSERT(false); diff --git a/icu4c/source/i18n/number_patternmodifier.h b/icu4c/source/i18n/number_patternmodifier.h index 9c8b95f776..ddce46337e 100644 --- a/icu4c/source/i18n/number_patternmodifier.h +++ b/icu4c/source/i18n/number_patternmodifier.h @@ -35,20 +35,21 @@ class MutablePatternModifier; // Exported as U_I18N_API because it is needed for the unit test PatternModifierTest class U_I18N_API ImmutablePatternModifier : public MicroPropsGenerator, public UMemory { public: - ~ImmutablePatternModifier() U_OVERRIDE = default; + ~ImmutablePatternModifier() U_OVERRIDE = default; - void processQuantity(DecimalQuantity &, MicroProps µs, UErrorCode &status) const U_OVERRIDE; + void processQuantity(DecimalQuantity&, MicroProps& micros, UErrorCode& status) const U_OVERRIDE; - void applyToMicros(MicroProps µs, DecimalQuantity &quantity) const; + void applyToMicros(MicroProps& micros, DecimalQuantity& quantity) const; private: - ImmutablePatternModifier(ParameterizedModifier *pm, const PluralRules *rules, const MicroPropsGenerator *parent); + ImmutablePatternModifier(ParameterizedModifier* pm, const PluralRules* rules, + const MicroPropsGenerator* parent); const LocalPointer pm; - const PluralRules *rules; - const MicroPropsGenerator *parent; + const PluralRules* rules; + const MicroPropsGenerator* parent; - friend class MutablePatternModifier; + friend class MutablePatternModifier; }; /** @@ -74,7 +75,6 @@ class U_I18N_API MutablePatternModifier : public MicroPropsGenerator, public Modifier, public SymbolProvider, - public CharSequence, public UMemory { public: @@ -187,13 +187,7 @@ class U_I18N_API MutablePatternModifier */ UnicodeString getSymbol(AffixPatternType type) const U_OVERRIDE; - int32_t length() const U_OVERRIDE; - - char16_t charAt(int32_t index) const U_OVERRIDE; - - // Use default implementation of codePointAt - - UnicodeString toUnicodeString() const U_OVERRIDE; + UnicodeString toUnicodeString() const; private: // Modifier details (initialized in constructor) @@ -217,12 +211,8 @@ class U_I18N_API MutablePatternModifier // QuantityChain details (initialized in addToChain) const MicroPropsGenerator *parent; - // Transient CharSequence fields (initialized in enterCharSequenceMode) - bool inCharSequenceMode = false; - int32_t fFlags; - int32_t fLength; - bool prependSign; - bool plusReplacesMinusSign; + // Transient fields for rendering + UnicodeString currentAffix; /** * Uses the current properties to create a single {@link ConstantMultiFieldModifier} with currency spacing support @@ -244,9 +234,7 @@ class U_I18N_API MutablePatternModifier int32_t insertSuffix(NumberStringBuilder &sb, int position, UErrorCode &status); - void enterCharSequenceMode(bool isPrefix); - - void exitCharSequenceMode(); + void prepareAffix(bool isPrefix); }; diff --git a/icu4c/source/i18n/number_patternstring.cpp b/icu4c/source/i18n/number_patternstring.cpp index 20178824b0..68f55001bb 100644 --- a/icu4c/source/i18n/number_patternstring.cpp +++ b/icu4c/source/i18n/number_patternstring.cpp @@ -14,25 +14,27 @@ using namespace icu; using namespace icu::number; using namespace icu::number::impl; -void PatternParser::parseToPatternInfo(const UnicodeString& patternString, ParsedPatternInfo& patternInfo, UErrorCode &status) { +void PatternParser::parseToPatternInfo(const UnicodeString& patternString, ParsedPatternInfo& patternInfo, + UErrorCode& status) { patternInfo.consumePattern(patternString, status); } DecimalFormatProperties PatternParser::parseToProperties(const UnicodeString& pattern, IgnoreRounding ignoreRounding, - UErrorCode &status) { + UErrorCode& status) { DecimalFormatProperties properties; parseToExistingPropertiesImpl(pattern, properties, ignoreRounding, status); return properties; } -void PatternParser::parseToExistingProperties(const UnicodeString& pattern, DecimalFormatProperties& properties, - IgnoreRounding ignoreRounding, UErrorCode &status) { +void +PatternParser::parseToExistingProperties(const UnicodeString& pattern, DecimalFormatProperties& properties, + IgnoreRounding ignoreRounding, UErrorCode& status) { parseToExistingPropertiesImpl(pattern, properties, ignoreRounding, status); } char16_t ParsedPatternInfo::charAt(int32_t flags, int32_t index) const { - const Endpoints &endpoints = getEndpoints(flags); + const Endpoints& endpoints = getEndpoints(flags); if (index < 0 || index >= endpoints.end - endpoints.start) { U_ASSERT(false); } @@ -43,12 +45,12 @@ int32_t ParsedPatternInfo::length(int32_t flags) const { return getLengthFromEndpoints(getEndpoints(flags)); } -int32_t ParsedPatternInfo::getLengthFromEndpoints(const Endpoints &endpoints) { +int32_t ParsedPatternInfo::getLengthFromEndpoints(const Endpoints& endpoints) { return endpoints.end - endpoints.start; } UnicodeString ParsedPatternInfo::getString(int32_t flags) const { - const Endpoints &endpoints = getEndpoints(flags); + const Endpoints& endpoints = getEndpoints(flags); if (endpoints.start == endpoints.end) { return UnicodeString(); } @@ -56,7 +58,7 @@ UnicodeString ParsedPatternInfo::getString(int32_t flags) const { return UnicodeString(pattern, endpoints.start, endpoints.end - endpoints.start); } -const Endpoints &ParsedPatternInfo::getEndpoints(int32_t flags) const { +const Endpoints& ParsedPatternInfo::getEndpoints(int32_t flags) const { bool prefix = (flags & AFFIX_PREFIX) != 0; bool isNegative = (flags & AFFIX_NEGATIVE_SUBPATTERN) != 0; bool padding = (flags & AFFIX_PADDING) != 0; @@ -91,7 +93,7 @@ bool ParsedPatternInfo::hasCurrencySign() const { return positive.hasCurrencySign || (fHasNegativeSubpattern && negative.hasCurrencySign); } -bool ParsedPatternInfo::containsSymbolType(AffixPatternType type, UErrorCode &status) const { +bool ParsedPatternInfo::containsSymbolType(AffixPatternType type, UErrorCode& status) const { return AffixUtils::containsType(UnicodeStringCharSequence(pattern), type, status); } @@ -117,7 +119,7 @@ UChar32 ParsedPatternInfo::ParserState::next() { return codePoint; } -void ParsedPatternInfo::consumePattern(const UnicodeString& patternString, UErrorCode &status) { +void ParsedPatternInfo::consumePattern(const UnicodeString& patternString, UErrorCode& status) { if (U_FAILURE(status)) { return; } this->pattern = patternString; @@ -141,7 +143,7 @@ void ParsedPatternInfo::consumePattern(const UnicodeString& patternString, UErro } } -void ParsedPatternInfo::consumeSubpattern(UErrorCode &status) { +void ParsedPatternInfo::consumeSubpattern(UErrorCode& status) { // subpattern := literals? number exponent? literals? consumePadding(PadPosition::UNUM_PAD_BEFORE_PREFIX, status); if (U_FAILURE(status)) { return; } @@ -161,7 +163,7 @@ void ParsedPatternInfo::consumeSubpattern(UErrorCode &status) { if (U_FAILURE(status)) { return; } } -void ParsedPatternInfo::consumePadding(PadPosition paddingLocation, UErrorCode &status) { +void ParsedPatternInfo::consumePadding(PadPosition paddingLocation, UErrorCode& status) { if (state.peek() != u'*') { return; } @@ -177,7 +179,7 @@ void ParsedPatternInfo::consumePadding(PadPosition paddingLocation, UErrorCode & currentSubpattern->paddingEndpoints.end = state.offset; } -void ParsedPatternInfo::consumeAffix(Endpoints &endpoints, UErrorCode &status) { +void ParsedPatternInfo::consumeAffix(Endpoints& endpoints, UErrorCode& status) { // literals := { literal } endpoints.start = state.offset; while (true) { @@ -233,7 +235,7 @@ void ParsedPatternInfo::consumeAffix(Endpoints &endpoints, UErrorCode &status) { endpoints.end = state.offset; } -void ParsedPatternInfo::consumeLiteral(UErrorCode &status) { +void ParsedPatternInfo::consumeLiteral(UErrorCode& status) { if (state.peek() == -1) { state.toParseException(u"Expected unquoted literal but found EOL"); status = U_PATTERN_SYNTAX_ERROR; @@ -256,7 +258,7 @@ void ParsedPatternInfo::consumeLiteral(UErrorCode &status) { } } -void ParsedPatternInfo::consumeFormat(UErrorCode &status) { +void ParsedPatternInfo::consumeFormat(UErrorCode& status) { consumeIntegerFormat(status); if (U_FAILURE(status)) { return; } if (state.peek() == u'.') { @@ -268,9 +270,9 @@ void ParsedPatternInfo::consumeFormat(UErrorCode &status) { } } -void ParsedPatternInfo::consumeIntegerFormat(UErrorCode &status) { +void ParsedPatternInfo::consumeIntegerFormat(UErrorCode& status) { // Convenience reference: - ParsedSubpatternInfo &result = *currentSubpattern; + ParsedSubpatternInfo& result = *currentSubpattern; while (true) { switch (state.peek()) { @@ -359,9 +361,9 @@ void ParsedPatternInfo::consumeIntegerFormat(UErrorCode &status) { } } -void ParsedPatternInfo::consumeFractionFormat(UErrorCode &status) { +void ParsedPatternInfo::consumeFractionFormat(UErrorCode& status) { // Convenience reference: - ParsedSubpatternInfo &result = *currentSubpattern; + ParsedSubpatternInfo& result = *currentSubpattern; int32_t zeroCounter = 0; while (true) { @@ -407,9 +409,9 @@ void ParsedPatternInfo::consumeFractionFormat(UErrorCode &status) { } } -void ParsedPatternInfo::consumeExponent(UErrorCode &status) { +void ParsedPatternInfo::consumeExponent(UErrorCode& status) { // Convenience reference: - ParsedSubpatternInfo &result = *currentSubpattern; + ParsedSubpatternInfo& result = *currentSubpattern; if (state.peek() != u'E') { return; @@ -437,9 +439,9 @@ void ParsedPatternInfo::consumeExponent(UErrorCode &status) { /// END RECURSIVE DESCENT PARSER IMPLEMENTATION /// /////////////////////////////////////////////////// -void -PatternParser::parseToExistingPropertiesImpl(const UnicodeString& pattern, DecimalFormatProperties &properties, - IgnoreRounding ignoreRounding, UErrorCode &status) { +void PatternParser::parseToExistingPropertiesImpl(const UnicodeString& pattern, + DecimalFormatProperties& properties, + IgnoreRounding ignoreRounding, UErrorCode& status) { if (pattern.length() == 0) { // Backwards compatibility requires that we reset to the default values. // TODO: Only overwrite the properties that "saveToProperties" normally touches? @@ -453,13 +455,13 @@ PatternParser::parseToExistingPropertiesImpl(const UnicodeString& pattern, Decim patternInfoToProperties(properties, patternInfo, ignoreRounding, status); } -void PatternParser::patternInfoToProperties(DecimalFormatProperties &properties, - ParsedPatternInfo& patternInfo, - IgnoreRounding _ignoreRounding, UErrorCode &status) { +void +PatternParser::patternInfoToProperties(DecimalFormatProperties& properties, ParsedPatternInfo& patternInfo, + IgnoreRounding _ignoreRounding, UErrorCode& status) { // Translate from PatternParseResult to Properties. // Note that most data from "negative" is ignored per the specification of DecimalFormat. - const ParsedSubpatternInfo &positive = patternInfo.positive; + const ParsedSubpatternInfo& positive = patternInfo.positive; bool ignoreRounding; if (_ignoreRounding == IGNORE_ROUNDING_NEVER) { @@ -508,8 +510,7 @@ void PatternParser::patternInfoToProperties(DecimalFormatProperties &properties, properties.maximumFractionDigits = -1; properties.roundingIncrement = 0.0; properties.minimumSignificantDigits = positive.integerAtSigns; - properties.maximumSignificantDigits = - positive.integerAtSigns + positive.integerTrailingHashSigns; + properties.maximumSignificantDigits = positive.integerAtSigns + positive.integerTrailingHashSigns; } else if (!positive.rounding.isZero()) { if (!ignoreRounding) { properties.minimumFractionDigits = minFrac; @@ -570,9 +571,9 @@ void PatternParser::patternInfoToProperties(DecimalFormatProperties &properties, // Padding settings if (!positive.paddingLocation.isNull()) { // The width of the positive prefix and suffix templates are included in the padding - int paddingWidth = - positive.widthExceptAffixes + AffixUtils::estimateLength(UnicodeStringCharSequence(posPrefix), status) + - AffixUtils::estimateLength(UnicodeStringCharSequence(posSuffix), status); + int paddingWidth = positive.widthExceptAffixes + + AffixUtils::estimateLength(UnicodeStringCharSequence(posPrefix), status) + + AffixUtils::estimateLength(UnicodeStringCharSequence(posSuffix), status); properties.formatWidth = paddingWidth; UnicodeString rawPaddingString = patternInfo.getString(AffixPatternProvider::AFFIX_PADDING); if (rawPaddingString.length() == 1) { @@ -622,8 +623,8 @@ void PatternParser::patternInfoToProperties(DecimalFormatProperties &properties, /// End PatternStringParser.java; begin PatternStringUtils.java /// /////////////////////////////////////////////////////////////////// -UnicodeString PatternStringUtils::propertiesToPatternString(const DecimalFormatProperties &properties, - UErrorCode &status) { +UnicodeString PatternStringUtils::propertiesToPatternString(const DecimalFormatProperties& properties, + UErrorCode& status) { UnicodeString sb; // Convenience references @@ -632,7 +633,7 @@ UnicodeString PatternStringUtils::propertiesToPatternString(const DecimalFormatP int groupingSize = uprv_min(properties.secondaryGroupingSize, dosMax); int firstGroupingSize = uprv_min(properties.groupingSize, dosMax); int paddingWidth = uprv_min(properties.formatWidth, dosMax); - NullableValue paddingLocation = properties.padPosition; + NullableValue paddingLocation = properties.padPosition; UnicodeString paddingString = properties.padString; int minInt = uprv_max(uprv_min(properties.minimumIntegerDigits, dosMax), 0); int maxInt = uprv_min(properties.maximumIntegerDigits, dosMax); @@ -809,8 +810,8 @@ UnicodeString PatternStringUtils::propertiesToPatternString(const DecimalFormatP } int PatternStringUtils::escapePaddingString(UnicodeString input, UnicodeString& output, int startIndex, - UErrorCode &status) { - (void)status; + UErrorCode& status) { + (void) status; if (input.length() == 0) { input.setTo(kFallbackPaddingString, -1); } @@ -840,4 +841,69 @@ int PatternStringUtils::escapePaddingString(UnicodeString input, UnicodeString& return output.length() - startLength; } +void PatternStringUtils::patternInfoToStringBuilder(const AffixPatternProvider& patternInfo, bool isPrefix, + int8_t signum, UNumberSignDisplay signDisplay, + StandardPlural::Form plural, + bool perMilleReplacesPercent, UnicodeString& output) { + + // Should the output render '+' where '-' would normally appear in the pattern? + bool 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() && ( + signum == -1 || (patternInfo.negativeHasMinusSign() && plusReplacesMinusSign)); + + // Resolve the flags for the affix pattern. + int flags = 0; + if (useNegativeAffixPattern) { + flags |= AffixPatternProvider::AFFIX_NEGATIVE_SUBPATTERN; + } + if (isPrefix) { + flags |= AffixPatternProvider::AFFIX_PREFIX; + } + if (plural != StandardPlural::Form::COUNT) { + U_ASSERT(plural == (AffixPatternProvider::AFFIX_PLURAL_MASK & plural)); + flags |= plural; + } + + // Should we prepend a sign to the pattern? + bool prependSign; + if (!isPrefix || useNegativeAffixPattern) { + prependSign = false; + } else if (signum == -1) { + prependSign = signDisplay != UNUM_SIGN_NEVER; + } else { + prependSign = plusReplacesMinusSign; + } + + // Compute the length of the affix pattern. + int length = patternInfo.length(flags) + (prependSign ? 1 : 0); + + // Finally, set the result into the StringBuilder. + output.remove(); + for (int index = 0; index < length; index++) { + char16_t candidate; + if (prependSign && index == 0) { + candidate = u'-'; + } else if (prependSign) { + candidate = patternInfo.charAt(flags, index - 1); + } else { + candidate = patternInfo.charAt(flags, index); + } + if (plusReplacesMinusSign && candidate == u'-') { + candidate = u'+'; + } + if (perMilleReplacesPercent && candidate == u'%') { + candidate = u'‰'; + } + output.append(candidate); + } +} + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/number_patternstring.h b/icu4c/source/i18n/number_patternstring.h index ec44290d66..0a343f6378 100644 --- a/icu4c/source/i18n/number_patternstring.h +++ b/icu4c/source/i18n/number_patternstring.h @@ -62,17 +62,18 @@ struct U_I18N_API ParsedPatternInfo : public AffixPatternProvider, public UMemor ParsedSubpatternInfo positive; ParsedSubpatternInfo negative; - ParsedPatternInfo() : state(this->pattern), currentSubpattern(nullptr) {} + ParsedPatternInfo() + : state(this->pattern), currentSubpattern(nullptr) {} ~ParsedPatternInfo() U_OVERRIDE = default; - static int32_t getLengthFromEndpoints(const Endpoints &endpoints); + static int32_t getLengthFromEndpoints(const Endpoints& endpoints); char16_t charAt(int32_t flags, int32_t index) const U_OVERRIDE; int32_t length(int32_t flags) const U_OVERRIDE; - UnicodeString getString(int32_t flags) const; + UnicodeString getString(int32_t flags) const U_OVERRIDE; bool positiveHasPlusSign() const U_OVERRIDE; @@ -82,16 +83,17 @@ struct U_I18N_API ParsedPatternInfo : public AffixPatternProvider, public UMemor bool hasCurrencySign() const U_OVERRIDE; - bool containsSymbolType(AffixPatternType type, UErrorCode &status) const U_OVERRIDE; + bool containsSymbolType(AffixPatternType type, UErrorCode& status) const U_OVERRIDE; bool hasBody() const U_OVERRIDE; private: struct U_I18N_API ParserState { - const UnicodeString &pattern; // reference to the parent + const UnicodeString& pattern; // reference to the parent int32_t offset = 0; - explicit ParserState(const UnicodeString &_pattern) : pattern(_pattern) {}; + explicit ParserState(const UnicodeString& _pattern) + : pattern(_pattern) {}; UChar32 peek(); @@ -99,41 +101,40 @@ struct U_I18N_API ParsedPatternInfo : public AffixPatternProvider, public UMemor // TODO: We don't currently do anything with the message string. // This method is here as a shell for Java compatibility. - inline void toParseException(const char16_t *message) { (void)message; } - } - state; + inline void toParseException(const char16_t* message) { (void) message; } + } state; // NOTE: In Java, these are written as pure functions. // In C++, they're written as methods. // The behavior is the same. // Mutable transient pointer: - ParsedSubpatternInfo *currentSubpattern; + ParsedSubpatternInfo* currentSubpattern; // In Java, "negative == null" tells us whether or not we had a negative subpattern. // In C++, we need to remember in another boolean. bool fHasNegativeSubpattern = false; - const Endpoints &getEndpoints(int32_t flags) const; + const Endpoints& getEndpoints(int32_t flags) const; /** Run the recursive descent parser. */ - void consumePattern(const UnicodeString &patternString, UErrorCode &status); + void consumePattern(const UnicodeString& patternString, UErrorCode& status); - void consumeSubpattern(UErrorCode &status); + void consumeSubpattern(UErrorCode& status); - void consumePadding(PadPosition paddingLocation, UErrorCode &status); + void consumePadding(PadPosition paddingLocation, UErrorCode& status); - void consumeAffix(Endpoints &endpoints, UErrorCode &status); + void consumeAffix(Endpoints& endpoints, UErrorCode& status); - void consumeLiteral(UErrorCode &status); + void consumeLiteral(UErrorCode& status); - void consumeFormat(UErrorCode &status); + void consumeFormat(UErrorCode& status); - void consumeIntegerFormat(UErrorCode &status); + void consumeIntegerFormat(UErrorCode& status); - void consumeFractionFormat(UErrorCode &status); + void consumeFractionFormat(UErrorCode& status); - void consumeExponent(UErrorCode &status); + void consumeExponent(UErrorCode& status); friend class PatternParser; }; @@ -153,8 +154,8 @@ class U_I18N_API PatternParser { * The LDML decimal format pattern (Excel-style pattern) to parse. * @return The results of the parse. */ - static void - parseToPatternInfo(const UnicodeString& patternString, ParsedPatternInfo &patternInfo, UErrorCode &status); + static void parseToPatternInfo(const UnicodeString& patternString, ParsedPatternInfo& patternInfo, + UErrorCode& status); enum IgnoreRounding { IGNORE_ROUNDING_NEVER = 0, IGNORE_ROUNDING_IF_CURRENCY = 1, IGNORE_ROUNDING_ALWAYS = 2 @@ -173,8 +174,8 @@ class U_I18N_API PatternParser { * @throws IllegalArgumentException * If there is a syntax error in the pattern string. */ - static DecimalFormatProperties - parseToProperties(const UnicodeString& pattern, IgnoreRounding ignoreRounding, UErrorCode &status); + static DecimalFormatProperties parseToProperties(const UnicodeString& pattern, + IgnoreRounding ignoreRounding, UErrorCode& status); /** * Parses a pattern string into an existing property bag. All properties that can be encoded into a pattern string @@ -190,18 +191,19 @@ class U_I18N_API PatternParser { * @throws IllegalArgumentException * If there was a syntax error in the pattern string. */ - static void parseToExistingProperties(const UnicodeString& pattern, DecimalFormatProperties& properties, - IgnoreRounding ignoreRounding, UErrorCode &status); + static void parseToExistingProperties(const UnicodeString& pattern, + DecimalFormatProperties& properties, + IgnoreRounding ignoreRounding, UErrorCode& status); private: - static void - parseToExistingPropertiesImpl(const UnicodeString& pattern, DecimalFormatProperties &properties, - IgnoreRounding ignoreRounding, UErrorCode &status); + static void parseToExistingPropertiesImpl(const UnicodeString& pattern, + DecimalFormatProperties& properties, + IgnoreRounding ignoreRounding, UErrorCode& status); /** Finalizes the temporary data stored in the ParsedPatternInfo to the Properties. */ - static void - patternInfoToProperties(DecimalFormatProperties &properties, ParsedPatternInfo& patternInfo, - IgnoreRounding _ignoreRounding, UErrorCode &status); + static void patternInfoToProperties(DecimalFormatProperties& properties, + ParsedPatternInfo& patternInfo, IgnoreRounding _ignoreRounding, + UErrorCode& status); }; class U_I18N_API PatternStringUtils { @@ -217,8 +219,8 @@ class U_I18N_API PatternStringUtils { * The property bag to serialize. * @return A pattern string approximately serializing the property bag. */ - static UnicodeString - propertiesToPatternString(const DecimalFormatProperties &properties, UErrorCode &status); + static UnicodeString propertiesToPatternString(const DecimalFormatProperties& properties, + UErrorCode& status); /** @@ -248,14 +250,23 @@ class U_I18N_API PatternStringUtils { * notation. * @return The pattern expressed in the other notation. */ - static UnicodeString - convertLocalized(UnicodeString input, DecimalFormatSymbols symbols, bool toLocalized, - UErrorCode &status); + static UnicodeString convertLocalized(UnicodeString input, DecimalFormatSymbols symbols, + bool toLocalized, UErrorCode& status); + + /** + * This method contains the heart of the logic for rendering LDML affix strings. It handles + * sign-always-shown resolution, whether to use the positive or negative subpattern, permille + * substitution, and plural forms for CurrencyPluralInfo. + */ + static void patternInfoToStringBuilder(const AffixPatternProvider& patternInfo, bool isPrefix, + int8_t signum, UNumberSignDisplay signDisplay, + StandardPlural::Form plural, bool perMilleReplacesPercent, + UnicodeString& output); private: /** @return The number of chars inserted. */ - static int - escapePaddingString(UnicodeString input, UnicodeString &output, int startIndex, UErrorCode &status); + static int escapePaddingString(UnicodeString input, UnicodeString& output, int startIndex, + UErrorCode& status); }; } // namespace impl diff --git a/icu4c/source/i18n/number_types.h b/icu4c/source/i18n/number_types.h index c01765e2ce..3e83312538 100644 --- a/icu4c/source/i18n/number_types.h +++ b/icu4c/source/i18n/number_types.h @@ -16,8 +16,7 @@ #include "uassert.h" #include "unicode/platform.h" -U_NAMESPACE_BEGIN -namespace number { +U_NAMESPACE_BEGIN namespace number { namespace impl { // Typedef several enums for brevity and for easier comparison to Java. @@ -87,15 +86,14 @@ enum AffixPatternType { }; enum CompactType { - TYPE_DECIMAL, - TYPE_CURRENCY + TYPE_DECIMAL, TYPE_CURRENCY }; // TODO: Should this be moved somewhere else, maybe where other ICU classes can use it? // Exported as U_I18N_API because it is a base class for other exported types class U_I18N_API CharSequence { -public: + public: virtual ~CharSequence() = default; virtual int32_t length() const = 0; @@ -123,12 +121,20 @@ class U_I18N_API AffixPatternProvider { static const int32_t AFFIX_NEGATIVE_SUBPATTERN = 0x200; static const int32_t AFFIX_PADDING = 0x400; + // Convenience compound flags + static const int32_t AFFIX_POS_PREFIX = AFFIX_PREFIX; + static const int32_t AFFIX_POS_SUFFIX = 0; + static const int32_t AFFIX_NEG_PREFIX = AFFIX_PREFIX | AFFIX_NEGATIVE_SUBPATTERN; + static const int32_t AFFIX_NEG_SUFFIX = AFFIX_NEGATIVE_SUBPATTERN; + virtual ~AffixPatternProvider() = default; virtual char16_t charAt(int flags, int i) const = 0; virtual int length(int flags) const = 0; + virtual UnicodeString getString(int flags) const = 0; + virtual bool hasCurrencySign() const = 0; virtual bool positiveHasPlusSign() const = 0; @@ -137,7 +143,7 @@ class U_I18N_API AffixPatternProvider { virtual bool negativeHasMinusSign() const = 0; - virtual bool containsSymbolType(AffixPatternType, UErrorCode &) const = 0; + virtual bool containsSymbolType(AffixPatternType, UErrorCode&) const = 0; /** * True if the pattern has a number placeholder like "0" or "#,##0.00"; false if the pattern does not @@ -173,8 +179,8 @@ class U_I18N_API Modifier { * formatted. * @return The number of characters (UTF-16 code units) that were added to the string builder. */ - virtual int32_t - apply(NumberStringBuilder &output, int leftIndex, int rightIndex, UErrorCode &status) const = 0; + virtual int32_t apply(NumberStringBuilder& output, int leftIndex, int rightIndex, + UErrorCode& status) const = 0; /** * Gets the length of the prefix. This information can be used in combination with {@link #apply} to extract the @@ -187,7 +193,7 @@ class U_I18N_API Modifier { /** * Returns the number of code points in the modifier, prefix plus suffix. */ - virtual int32_t getCodePointCount(UErrorCode &status) const = 0; + virtual int32_t getCodePointCount(UErrorCode& status) const = 0; /** * Whether this modifier is strong. If a modifier is strong, it should always be applied immediately and not allowed @@ -230,7 +236,8 @@ class U_I18N_API MicroPropsGenerator { * The MicroProps instance to populate. * @return A MicroProps instance resolved for the quantity. */ - virtual void processQuantity(DecimalQuantity& quantity, MicroProps& micros, UErrorCode& status) const = 0; + virtual void processQuantity(DecimalQuantity& quantity, MicroProps& micros, + UErrorCode& status) const = 0; }; /** @@ -255,24 +262,25 @@ class MultiplierProducer { template class U_I18N_API NullableValue { public: - NullableValue() : fNull(true) {} + NullableValue() + : fNull(true) {} - NullableValue(const NullableValue &other) = default; + NullableValue(const NullableValue& other) = default; - explicit NullableValue(const T &other) { + explicit NullableValue(const T& other) { fValue = other; fNull = false; } - NullableValue &operator=(const NullableValue &other) = default; + NullableValue& operator=(const NullableValue& other) = default; - NullableValue &operator=(const T &other) { + NullableValue& operator=(const T& other) { fValue = other; fNull = false; return *this; } - bool operator==(const NullableValue &other) const { + bool operator==(const NullableValue& other) const { // "fValue == other.fValue" returns UBool, not bool (causes compiler warnings) return fNull ? other.fNull : (other.fNull ? false : static_cast(fValue == other.fValue)); } @@ -286,7 +294,7 @@ class U_I18N_API NullableValue { return fNull; } - T get(UErrorCode &status) const { + T get(UErrorCode& status) const { if (fNull) { status = U_UNDEFINED_VARIABLE; } diff --git a/icu4c/source/i18n/numparse_affixes.cpp b/icu4c/source/i18n/numparse_affixes.cpp index 66ce2bef8c..a164f0b70e 100644 --- a/icu4c/source/i18n/numparse_affixes.cpp +++ b/icu4c/source/i18n/numparse_affixes.cpp @@ -87,10 +87,10 @@ AffixPatternMatcher AffixPatternMatcherBuilder::build() { AffixTokenMatcherWarehouse::AffixTokenMatcherWarehouse(const UChar* currencyCode, - const UnicodeString& currency1, - const UnicodeString& currency2, - const DecimalFormatSymbols& dfs, - IgnorablesMatcher* ignorables, const Locale& locale) + const UnicodeString* currency1, + const UnicodeString* currency2, + const DecimalFormatSymbols* dfs, + IgnorablesMatcher* ignorables, const Locale* locale) : currency1(currency1), currency2(currency2), dfs(dfs), @@ -109,23 +109,23 @@ AffixTokenMatcherWarehouse::~AffixTokenMatcherWarehouse() { } NumberParseMatcher& AffixTokenMatcherWarehouse::minusSign() { - return fMinusSign = {dfs, true}; + return fMinusSign = {*dfs, true}; } NumberParseMatcher& AffixTokenMatcherWarehouse::plusSign() { - return fPlusSign = {dfs, true}; + return fPlusSign = {*dfs, true}; } NumberParseMatcher& AffixTokenMatcherWarehouse::percent() { - return fPercent = {dfs}; + return fPercent = {*dfs}; } NumberParseMatcher& AffixTokenMatcherWarehouse::permille() { - return fPermille = {dfs}; + return fPermille = {*dfs}; } NumberParseMatcher& AffixTokenMatcherWarehouse::currency(UErrorCode& status) { - return fCurrency = {{locale, status}, {currencyCode, currency1, currency2}}; + return fCurrency = {{*locale, status}, {currencyCode, *currency1, *currency2}}; } NumberParseMatcher& AffixTokenMatcherWarehouse::nextCodePointMatcher(UChar32 cp) { @@ -193,8 +193,232 @@ AffixPatternMatcher AffixPatternMatcher::fromAffixPattern(const UnicodeString& a AffixPatternMatcher::AffixPatternMatcher(MatcherArray& matchers, int32_t matchersLen, const UnicodeString& pattern) - : ArraySeriesMatcher(matchers, matchersLen), fPattern(pattern) { + : ArraySeriesMatcher(matchers, matchersLen), fPattern(pattern) {} + +UnicodeString AffixPatternMatcher::getPattern() const { + return fPattern.toAliasedUnicodeString(); +} + +bool AffixPatternMatcher::operator==(const AffixPatternMatcher& other) const { + return fPattern == other.fPattern; +} + + +AffixMatcherWarehouse::AffixMatcherWarehouse(const AffixPatternProvider& patternInfo, + NumberParserImpl& output, + AffixTokenMatcherWarehouse& warehouse, + const IgnorablesMatcher& ignorables, parse_flags_t parseFlags, + UErrorCode& status) + : fAffixTokenMatcherWarehouse(std::move(warehouse)) { + if (!isInteresting(patternInfo, ignorables, parseFlags, status)) { + return; + } + + // The affixes have interesting characters, or we are in strict mode. + // Use initial capacity of 6, the highest possible number of AffixMatchers. + UnicodeString sb; + bool includeUnpaired = 0 != (parseFlags & PARSE_FLAG_INCLUDE_UNPAIRED_AFFIXES); + UNumberSignDisplay signDisplay = (0 != (parseFlags & PARSE_FLAG_PLUS_SIGN_ALLOWED)) ? UNUM_SIGN_ALWAYS + : UNUM_SIGN_NEVER; + + int32_t numAffixMatchers = 0; + int32_t numAffixPatternMatchers = 0; + + AffixPatternMatcher* posPrefix = nullptr; + AffixPatternMatcher* posSuffix = nullptr; + + // Pre-process the affix strings to resolve LDML rules like sign display. + for (int8_t signum = 1; signum >= -1; signum--) { + // Generate Prefix + bool hasPrefix = false; + PatternStringUtils::patternInfoToStringBuilder( + patternInfo, true, signum, signDisplay, StandardPlural::OTHER, false, sb); + fAffixPatternMatchers[numAffixPatternMatchers] = AffixPatternMatcher::fromAffixPattern( + sb, warehouse, parseFlags, &hasPrefix, status); + AffixPatternMatcher* prefix = hasPrefix ? &fAffixPatternMatchers[numAffixPatternMatchers++] + : nullptr; + + // Generate Suffix + bool hasSuffix = false; + PatternStringUtils::patternInfoToStringBuilder( + patternInfo, false, signum, signDisplay, StandardPlural::OTHER, false, sb); + fAffixPatternMatchers[numAffixPatternMatchers] = AffixPatternMatcher::fromAffixPattern( + sb, warehouse, parseFlags, &hasSuffix, status); + AffixPatternMatcher* suffix = hasSuffix ? &fAffixPatternMatchers[numAffixPatternMatchers++] + : nullptr; + + if (signum == 1) { + posPrefix = prefix; + posSuffix = suffix; + } else if (equals(prefix, posPrefix) && equals(suffix, posSuffix)) { + // Skip adding these matchers (we already have equivalents) + continue; + } + + // Flags for setting in the ParsedNumber + int flags = (signum == -1) ? FLAG_NEGATIVE : 0; + + // Note: it is indeed possible for posPrefix and posSuffix to both be null. + // We still need to add that matcher for strict mode to work. + fAffixMatchers[numAffixMatchers++] = {prefix, suffix, flags}; + if (includeUnpaired && prefix != nullptr && suffix != nullptr) { + // The following if statements are designed to prevent adding two identical matchers. + if (signum == 1 || equals(prefix, posPrefix)) { + fAffixMatchers[numAffixMatchers++] = {prefix, nullptr, flags}; + } + if (signum == 1 || equals(suffix, posSuffix)) { + fAffixMatchers[numAffixMatchers++] = {nullptr, suffix, flags}; + } + } + } + + // Put the AffixMatchers in order, and then add them to the output. + // TODO +// Collections.sort(matchers, COMPARATOR); +// output.addMatchers(matchers); +} + +bool AffixMatcherWarehouse::isInteresting(const AffixPatternProvider& patternInfo, + const IgnorablesMatcher& ignorables, parse_flags_t parseFlags, + UErrorCode& status) { + UnicodeStringCharSequence posPrefixString(patternInfo.getString(AffixPatternProvider::AFFIX_POS_PREFIX)); + UnicodeStringCharSequence posSuffixString(patternInfo.getString(AffixPatternProvider::AFFIX_POS_SUFFIX)); + UnicodeStringCharSequence negPrefixString(UnicodeString(u"")); + UnicodeStringCharSequence negSuffixString(UnicodeString(u"")); + if (patternInfo.hasNegativeSubpattern()) { + negPrefixString = UnicodeStringCharSequence(patternInfo.getString(AffixPatternProvider::AFFIX_NEG_PREFIX)); + negSuffixString = UnicodeStringCharSequence(patternInfo.getString(AffixPatternProvider::AFFIX_NEG_SUFFIX)); + } + + if (0 == (parseFlags & PARSE_FLAG_USE_FULL_AFFIXES) && + AffixUtils::containsOnlySymbolsAndIgnorables(posPrefixString, *ignorables.getSet(), status) && + AffixUtils::containsOnlySymbolsAndIgnorables(posSuffixString, *ignorables.getSet(), status) && + AffixUtils::containsOnlySymbolsAndIgnorables(negPrefixString, *ignorables.getSet(), status) && + AffixUtils::containsOnlySymbolsAndIgnorables(negSuffixString, *ignorables.getSet(), status) + // HACK: Plus and minus sign are a special case: we accept them trailing only if they are + // trailing in the pattern string. + && !AffixUtils::containsType(posSuffixString, TYPE_PLUS_SIGN, status) && + !AffixUtils::containsType(posSuffixString, TYPE_MINUS_SIGN, status) && + !AffixUtils::containsType(negSuffixString, TYPE_PLUS_SIGN, status) && + !AffixUtils::containsType(negSuffixString, TYPE_MINUS_SIGN, status)) { + // The affixes contain only symbols and ignorables. + // No need to generate affix matchers. + return false; + } + return true; +} + +bool AffixMatcherWarehouse::equals(const AffixPatternMatcher* lhs, const AffixPatternMatcher* rhs) { + if (lhs == nullptr && rhs == nullptr) { + return true; + } + if (lhs == nullptr || rhs == nullptr) { + return false; + } + return *lhs == *rhs; +} + + +AffixMatcher::AffixMatcher(AffixPatternMatcher* prefix, AffixPatternMatcher* suffix, result_flags_t flags) + : fPrefix(prefix), fSuffix(suffix), fFlags(flags) {} + +bool AffixMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const { + if (!result.seenNumber()) { + // Prefix + // Do not match if: + // 1. We have already seen a prefix (result.prefix != null) + // 2. The prefix in this AffixMatcher is empty (prefix == null) + if (!result.prefix.isBogus() || fPrefix == nullptr) { + return false; + } + + // Attempt to match the prefix. + int initialOffset = segment.getOffset(); + bool maybeMore = fPrefix->match(segment, result, status); + if (initialOffset != segment.getOffset()) { + result.prefix = fPrefix->getPattern(); + } + return maybeMore; + + } else { + // Suffix + // Do not match if: + // 1. We have already seen a suffix (result.suffix != null) + // 2. The suffix in this AffixMatcher is empty (suffix == null) + // 3. The matched prefix does not equal this AffixMatcher's prefix + if (!result.suffix.isBogus() || fSuffix == nullptr || !matched(fPrefix, result.prefix)) { + return false; + } + + // Attempt to match the suffix. + int initialOffset = segment.getOffset(); + bool maybeMore = fSuffix->match(segment, result, status); + if (initialOffset != segment.getOffset()) { + result.suffix = fSuffix->getPattern(); + } + return maybeMore; + } +} + +const UnicodeSet& AffixMatcher::getLeadCodePoints() { + if (fLocalLeadCodePoints.isNull()) { + auto* leadCodePoints = new UnicodeSet(); + if (fPrefix != nullptr) { + leadCodePoints->addAll(fPrefix->getLeadCodePoints()); + } + if (fSuffix != nullptr) { + leadCodePoints->addAll(fSuffix->getLeadCodePoints()); + } + leadCodePoints->freeze(); + fLocalLeadCodePoints.adoptInstead(leadCodePoints); + } + return *fLocalLeadCodePoints; +} + +void AffixMatcher::postProcess(ParsedNumber& result) const { + // Check to see if our affix is the one that was matched. If so, set the flags in the result. + if (matched(fPrefix, result.prefix) && matched(fSuffix, result.suffix)) { + // Fill in the result prefix and suffix with non-null values (empty string). + // Used by strict mode to determine whether an entire affix pair was matched. + if (result.prefix.isBogus()) { + result.prefix = UnicodeString(); + } + if (result.suffix.isBogus()) { + result.suffix = UnicodeString(); + } + result.flags |= fFlags; + } +} + +bool AffixMatcher::matched(const AffixPatternMatcher* affix, const UnicodeString& patternString) { + return (affix == nullptr && patternString.isBogus()) || + (affix != nullptr && affix->getPattern() == patternString); } #endif /* #if !UCONFIG_NO_FORMATTING */ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icu4c/source/i18n/numparse_affixes.h b/icu4c/source/i18n/numparse_affixes.h index fa11ab78d7..69c68227de 100644 --- a/icu4c/source/i18n/numparse_affixes.h +++ b/icu4c/source/i18n/numparse_affixes.h @@ -19,6 +19,9 @@ namespace impl { class AffixPatternMatcherBuilder; class AffixPatternMatcher; +using ::icu::number::impl::AffixPatternProvider; +using ::icu::number::impl::TokenConsumer; + class CodePointMatcher : public NumberParseMatcher, public UMemory { public: @@ -51,9 +54,13 @@ class AffixTokenMatcherWarehouse { static constexpr int32_t CODE_POINT_BATCH_SIZE = 10; // Number of entries per heap allocation public: - AffixTokenMatcherWarehouse(const UChar* currencyCode, const UnicodeString& currency1, - const UnicodeString& currency2, const DecimalFormatSymbols& dfs, - IgnorablesMatcher* ignorables, const Locale& locale); + AffixTokenMatcherWarehouse() = default; // WARNING: Leaves the object in an unusable state + + AffixTokenMatcherWarehouse(const UChar* currencyCode, const UnicodeString* currency1, + const UnicodeString* currency2, const DecimalFormatSymbols* dfs, + IgnorablesMatcher* ignorables, const Locale* locale); + + AffixTokenMatcherWarehouse(AffixTokenMatcherWarehouse&& src) = default; ~AffixTokenMatcherWarehouse(); @@ -70,12 +77,13 @@ class AffixTokenMatcherWarehouse { NumberParseMatcher& nextCodePointMatcher(UChar32 cp); private: + // NOTE: The following fields may be unsafe to access after construction is done! UChar currencyCode[4]; - const UnicodeString& currency1; - const UnicodeString& currency2; - const DecimalFormatSymbols& dfs; + const UnicodeString* currency1; + const UnicodeString* currency2; + const DecimalFormatSymbols* dfs; IgnorablesMatcher* ignorables; - const Locale locale; + const Locale* locale; // NOTE: These are default-constructed and should not be used until initialized. MinusSignMatcher fMinusSign; @@ -94,7 +102,7 @@ class AffixTokenMatcherWarehouse { }; -class AffixPatternMatcherBuilder : public ::icu::number::impl::TokenConsumer { +class AffixPatternMatcherBuilder : public TokenConsumer { public: AffixPatternMatcherBuilder(const UnicodeString& pattern, AffixTokenMatcherWarehouse& warehouse, IgnorablesMatcher* ignorables); @@ -119,15 +127,19 @@ class AffixPatternMatcherBuilder : public ::icu::number::impl::TokenConsumer { class AffixPatternMatcher : public ArraySeriesMatcher { public: + AffixPatternMatcher() = default; // WARNING: Leaves the object in an unusable state + static AffixPatternMatcher fromAffixPattern(const UnicodeString& affixPattern, AffixTokenMatcherWarehouse& warehouse, parse_flags_t parseFlags, bool* success, UErrorCode& status); - private: - UnicodeString fPattern; + UnicodeString getPattern() const; - AffixPatternMatcher() = default; // WARNING: Leaves the object in an unusable state + bool operator==(const AffixPatternMatcher& other) const; + + private: + CompactUnicodeString<4> fPattern; AffixPatternMatcher(MatcherArray& matchers, int32_t matchersLen, const UnicodeString& pattern); @@ -135,6 +147,65 @@ class AffixPatternMatcher : public ArraySeriesMatcher { }; +class AffixMatcher : public NumberParseMatcher, public UMemory { + public: + AffixMatcher() = default; // WARNING: Leaves the object in an unusable state + + AffixMatcher(AffixPatternMatcher* prefix, AffixPatternMatcher* suffix, result_flags_t flags); + + // static void createMatchers() is the constructor for AffixMatcherWarehouse in C++ + + bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; + + void postProcess(ParsedNumber& result) const override; + + const UnicodeSet& getLeadCodePoints() override; + + private: + AffixPatternMatcher* fPrefix; + AffixPatternMatcher* fSuffix; + result_flags_t fFlags; + + /** + * Helper method to return whether the given AffixPatternMatcher equals the given pattern string. + * Either both arguments must be null or the pattern string inside the AffixPatternMatcher must equal + * the given pattern string. + */ + static bool matched(const AffixPatternMatcher* affix, const UnicodeString& patternString); +}; + + +/** + * A C++-only class to retain ownership of the AffixMatchers needed for parsing. + */ +class AffixMatcherWarehouse { + public: + AffixMatcherWarehouse() = default; // WARNING: Leaves the object in an unusable state + + // in Java, this is AffixMatcher#createMatchers() + AffixMatcherWarehouse(const AffixPatternProvider& patternInfo, NumberParserImpl& output, + AffixTokenMatcherWarehouse& warehouse, const IgnorablesMatcher& ignorables, + parse_flags_t parseFlags, UErrorCode& status); + + private: + // 9 is the limit: positive, zero, and negative, each with prefix, suffix, and prefix+suffix + AffixMatcher fAffixMatchers[9]; + // 6 is the limit: positive, zero, and negative, a prefix and a suffix for each + AffixPatternMatcher fAffixPatternMatchers[6]; + // Store all the tokens used by the AffixPatternMatchers + AffixTokenMatcherWarehouse fAffixTokenMatcherWarehouse; + + static bool isInteresting(const AffixPatternProvider& patternInfo, const IgnorablesMatcher& ignorables, + parse_flags_t parseFlags, UErrorCode& status); + + /** + * Helper method to return whether (1) both lhs and rhs are null/invalid, or (2) if they are both + * valid, whether they are equal according to operator==. Similar to Java Objects.equals() + */ + static bool equals(const AffixPatternMatcher* lhs, const AffixPatternMatcher* rhs); +}; + + } // namespace impl } // namespace numparse U_NAMESPACE_END diff --git a/icu4c/source/i18n/numparse_symbols.cpp b/icu4c/source/i18n/numparse_symbols.cpp index 6654bea7de..3ba12a68df 100644 --- a/icu4c/source/i18n/numparse_symbols.cpp +++ b/icu4c/source/i18n/numparse_symbols.cpp @@ -23,7 +23,7 @@ SymbolMatcher::SymbolMatcher(const UnicodeString& symbolString, unisets::Key key } } -const UnicodeSet* SymbolMatcher::getSet() { +const UnicodeSet* SymbolMatcher::getSet() const { return fUniSet; } diff --git a/icu4c/source/i18n/numparse_symbols.h b/icu4c/source/i18n/numparse_symbols.h index c06724fbe7..cf5b8d8668 100644 --- a/icu4c/source/i18n/numparse_symbols.h +++ b/icu4c/source/i18n/numparse_symbols.h @@ -24,7 +24,7 @@ class SymbolMatcher : public NumberParseMatcher, public UMemory { public: SymbolMatcher() = default; // WARNING: Leaves the object in an unusable state - const UnicodeSet* getSet(); + const UnicodeSet* getSet() const; bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; diff --git a/icu4c/source/i18n/numparse_types.h b/icu4c/source/i18n/numparse_types.h index 76aa75e0fc..8a92dc93fb 100644 --- a/icu4c/source/i18n/numparse_types.h +++ b/icu4c/source/i18n/numparse_types.h @@ -48,11 +48,35 @@ enum ParseFlags { PARSE_FLAG_PLUS_SIGN_ALLOWED = 0x0400, }; -//template -//struct MaybeNeedsAdoption { -// T* ptr; -// bool needsAdoption; -//}; + +// TODO: Is this class worthwhile? +template +class CompactUnicodeString { + public: + CompactUnicodeString() { + static_assert(stackCapacity > 0, "cannot have zero space on stack"); + fBuffer[0] = 0; + } + + CompactUnicodeString(const UnicodeString& text) + : fBuffer(text.length() + 1) { + memcpy(fBuffer.getAlias(), text.getBuffer(), sizeof(UChar) * text.length()); + fBuffer[text.length()] = 0; + } + + inline UnicodeString toAliasedUnicodeString() const { + return UnicodeString(TRUE, fBuffer.getAlias(), -1); + } + + bool operator==(const CompactUnicodeString& other) const { + // Use the alias-only constructor and then call UnicodeString operator== + return toAliasedUnicodeString() == other.toAliasedUnicodeString(); + } + + private: + MaybeStackArray fBuffer; +}; + /** * Struct-like class to hold the results of a parsing routine. diff --git a/icu4c/source/test/intltest/numbertest_parse.cpp b/icu4c/source/test/intltest/numbertest_parse.cpp index 9cb8dd12d4..4fc4da370d 100644 --- a/icu4c/source/test/intltest/numbertest_parse.cpp +++ b/icu4c/source/test/intltest/numbertest_parse.cpp @@ -215,8 +215,12 @@ void NumberParserTest::testSeriesMatcher() { void NumberParserTest::testCurrencyAnyMatcher() { IcuTestErrorCode status(*this, "testCurrencyAnyMatcher"); + UnicodeString currency1(u"IU$"); + UnicodeString currency2(u"ICU"); + DecimalFormatSymbols symbols("en", status); IgnorablesMatcher ignorables(unisets::DEFAULT_IGNORABLES); - AffixTokenMatcherWarehouse warehouse(u"ICU", u"IU$", u"ICU", {"en",status}, &ignorables, "en"); + Locale locale("en"); + AffixTokenMatcherWarehouse warehouse(u"ICU", ¤cy1, ¤cy2, &symbols, &ignorables, &locale); NumberParseMatcher& matcher = warehouse.currency(status); static const struct TestCase{ @@ -248,8 +252,12 @@ void NumberParserTest::testCurrencyAnyMatcher() { void NumberParserTest::testAffixPatternMatcher() { IcuTestErrorCode status(*this, "testAffixPatternMatcher"); + UnicodeString currency1(u"foo"); + UnicodeString currency2(u"bar"); + DecimalFormatSymbols symbols("en", status); IgnorablesMatcher ignorables(unisets::DEFAULT_IGNORABLES); - AffixTokenMatcherWarehouse warehouse(u"EUR", u"foo", u"bar", {"en", status}, &ignorables, "en"); + Locale locale("en"); + AffixTokenMatcherWarehouse warehouse(u"EUR", ¤cy1, ¤cy2, &symbols, &ignorables, &locale); static const struct TestCase { bool exactMatch; From 7b1857d0f3fbbaac537c762d134d746ce0120f34 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Sat, 10 Feb 2018 15:49:02 +0000 Subject: [PATCH 016/129] ICU-13574 Trying to get std::move operator to work on AffixMatcherWarehouse. No luck yet. X-SVN-Rev: 40895 --- icu4c/source/i18n/numparse_affixes.cpp | 221 +++++++++++------- icu4c/source/i18n/numparse_affixes.h | 38 ++- icu4c/source/i18n/numparse_impl.cpp | 25 +- icu4c/source/i18n/numparse_impl.h | 8 +- icu4c/source/i18n/numparse_types.h | 11 + .../source/test/intltest/numbertest_parse.cpp | 8 +- .../icu/impl/number/parse/AffixMatcher.java | 14 +- 7 files changed, 200 insertions(+), 125 deletions(-) diff --git a/icu4c/source/i18n/numparse_affixes.cpp b/icu4c/source/i18n/numparse_affixes.cpp index a164f0b70e..b5d447f192 100644 --- a/icu4c/source/i18n/numparse_affixes.cpp +++ b/icu4c/source/i18n/numparse_affixes.cpp @@ -17,6 +17,42 @@ using namespace icu::number; using namespace icu::number::impl; +namespace { + +/** + * Helper method to return whether the given AffixPatternMatcher equals the given pattern string. + * Either both arguments must be null or the pattern string inside the AffixPatternMatcher must equal + * the given pattern string. + */ +static bool matched(const AffixPatternMatcher* affix, const UnicodeString& patternString) { + return (affix == nullptr && patternString.isBogus()) || + (affix != nullptr && affix->getPattern() == patternString); +} + +/** + * Helper method to return the length of the given AffixPatternMatcher. Returns 0 for null. + */ +static int32_t length(const AffixPatternMatcher* matcher) { + return matcher == nullptr ? 0 : matcher->getPattern().length(); +} + +/** + * Helper method to return whether (1) both lhs and rhs are null/invalid, or (2) if they are both + * valid, whether they are equal according to operator==. Similar to Java Objects.equals() + */ +static bool equals(const AffixPatternMatcher* lhs, const AffixPatternMatcher* rhs) { + if (lhs == nullptr && rhs == nullptr) { + return true; + } + if (lhs == nullptr || rhs == nullptr) { + return false; + } + return *lhs == *rhs; +} + +} + + AffixPatternMatcherBuilder::AffixPatternMatcherBuilder(const UnicodeString& pattern, AffixTokenMatcherWarehouse& warehouse, IgnorablesMatcher* ignorables) @@ -101,6 +137,9 @@ AffixTokenMatcherWarehouse::AffixTokenMatcherWarehouse(const UChar* currencyCode utils::copyCurrencyCode(this->currencyCode, currencyCode); } +AffixTokenMatcherWarehouse::AffixTokenMatcherWarehouse( + AffixTokenMatcherWarehouse&& src) U_NOEXCEPT = default; + AffixTokenMatcherWarehouse::~AffixTokenMatcherWarehouse() { // Delete the variable number of batches of code point matchers for (int32_t i = 0; i < codePointNumBatches; i++) { @@ -204,79 +243,10 @@ bool AffixPatternMatcher::operator==(const AffixPatternMatcher& other) const { } -AffixMatcherWarehouse::AffixMatcherWarehouse(const AffixPatternProvider& patternInfo, - NumberParserImpl& output, - AffixTokenMatcherWarehouse& warehouse, - const IgnorablesMatcher& ignorables, parse_flags_t parseFlags, - UErrorCode& status) - : fAffixTokenMatcherWarehouse(std::move(warehouse)) { - if (!isInteresting(patternInfo, ignorables, parseFlags, status)) { - return; - } +AffixMatcherWarehouse::AffixMatcherWarehouse(AffixTokenMatcherWarehouse& warehouse) + : fAffixTokenMatcherWarehouse(std::move(warehouse)) {} - // The affixes have interesting characters, or we are in strict mode. - // Use initial capacity of 6, the highest possible number of AffixMatchers. - UnicodeString sb; - bool includeUnpaired = 0 != (parseFlags & PARSE_FLAG_INCLUDE_UNPAIRED_AFFIXES); - UNumberSignDisplay signDisplay = (0 != (parseFlags & PARSE_FLAG_PLUS_SIGN_ALLOWED)) ? UNUM_SIGN_ALWAYS - : UNUM_SIGN_NEVER; - - int32_t numAffixMatchers = 0; - int32_t numAffixPatternMatchers = 0; - - AffixPatternMatcher* posPrefix = nullptr; - AffixPatternMatcher* posSuffix = nullptr; - - // Pre-process the affix strings to resolve LDML rules like sign display. - for (int8_t signum = 1; signum >= -1; signum--) { - // Generate Prefix - bool hasPrefix = false; - PatternStringUtils::patternInfoToStringBuilder( - patternInfo, true, signum, signDisplay, StandardPlural::OTHER, false, sb); - fAffixPatternMatchers[numAffixPatternMatchers] = AffixPatternMatcher::fromAffixPattern( - sb, warehouse, parseFlags, &hasPrefix, status); - AffixPatternMatcher* prefix = hasPrefix ? &fAffixPatternMatchers[numAffixPatternMatchers++] - : nullptr; - - // Generate Suffix - bool hasSuffix = false; - PatternStringUtils::patternInfoToStringBuilder( - patternInfo, false, signum, signDisplay, StandardPlural::OTHER, false, sb); - fAffixPatternMatchers[numAffixPatternMatchers] = AffixPatternMatcher::fromAffixPattern( - sb, warehouse, parseFlags, &hasSuffix, status); - AffixPatternMatcher* suffix = hasSuffix ? &fAffixPatternMatchers[numAffixPatternMatchers++] - : nullptr; - - if (signum == 1) { - posPrefix = prefix; - posSuffix = suffix; - } else if (equals(prefix, posPrefix) && equals(suffix, posSuffix)) { - // Skip adding these matchers (we already have equivalents) - continue; - } - - // Flags for setting in the ParsedNumber - int flags = (signum == -1) ? FLAG_NEGATIVE : 0; - - // Note: it is indeed possible for posPrefix and posSuffix to both be null. - // We still need to add that matcher for strict mode to work. - fAffixMatchers[numAffixMatchers++] = {prefix, suffix, flags}; - if (includeUnpaired && prefix != nullptr && suffix != nullptr) { - // The following if statements are designed to prevent adding two identical matchers. - if (signum == 1 || equals(prefix, posPrefix)) { - fAffixMatchers[numAffixMatchers++] = {prefix, nullptr, flags}; - } - if (signum == 1 || equals(suffix, posSuffix)) { - fAffixMatchers[numAffixMatchers++] = {nullptr, suffix, flags}; - } - } - } - - // Put the AffixMatchers in order, and then add them to the output. - // TODO -// Collections.sort(matchers, COMPARATOR); -// output.addMatchers(matchers); -} +AffixMatcherWarehouse& AffixMatcherWarehouse::operator=(AffixMatcherWarehouse&& src) = default; bool AffixMatcherWarehouse::isInteresting(const AffixPatternProvider& patternInfo, const IgnorablesMatcher& ignorables, parse_flags_t parseFlags, @@ -308,14 +278,97 @@ bool AffixMatcherWarehouse::isInteresting(const AffixPatternProvider& patternInf return true; } -bool AffixMatcherWarehouse::equals(const AffixPatternMatcher* lhs, const AffixPatternMatcher* rhs) { - if (lhs == nullptr && rhs == nullptr) { - return true; +AffixMatcherWarehouse AffixMatcherWarehouse::createAffixMatchers(const AffixPatternProvider& patternInfo, + MutableMatcherCollection& output, + AffixTokenMatcherWarehouse tokenWarehouse, + const IgnorablesMatcher& ignorables, + parse_flags_t parseFlags, + UErrorCode& status) { + if (!isInteresting(patternInfo, ignorables, parseFlags, status)) { + return {}; } - if (lhs == nullptr || rhs == nullptr) { - return false; + + AffixMatcherWarehouse warehouse(tokenWarehouse); + + // The affixes have interesting characters, or we are in strict mode. + // Use initial capacity of 6, the highest possible number of AffixMatchers. + UnicodeString sb; + bool includeUnpaired = 0 != (parseFlags & PARSE_FLAG_INCLUDE_UNPAIRED_AFFIXES); + UNumberSignDisplay signDisplay = (0 != (parseFlags & PARSE_FLAG_PLUS_SIGN_ALLOWED)) ? UNUM_SIGN_ALWAYS + : UNUM_SIGN_NEVER; + + int32_t numAffixMatchers = 0; + int32_t numAffixPatternMatchers = 0; + + AffixPatternMatcher* posPrefix = nullptr; + AffixPatternMatcher* posSuffix = nullptr; + + // Pre-process the affix strings to resolve LDML rules like sign display. + for (int8_t signum = 1; signum >= -1; signum--) { + // Generate Prefix + bool hasPrefix = false; + PatternStringUtils::patternInfoToStringBuilder( + patternInfo, true, signum, signDisplay, StandardPlural::OTHER, false, sb); + warehouse.fAffixPatternMatchers[numAffixPatternMatchers] = AffixPatternMatcher::fromAffixPattern( + sb, tokenWarehouse, parseFlags, &hasPrefix, status); + AffixPatternMatcher* prefix = hasPrefix + ? &warehouse.fAffixPatternMatchers[numAffixPatternMatchers++] + : nullptr; + + // Generate Suffix + bool hasSuffix = false; + PatternStringUtils::patternInfoToStringBuilder( + patternInfo, false, signum, signDisplay, StandardPlural::OTHER, false, sb); + warehouse.fAffixPatternMatchers[numAffixPatternMatchers] = AffixPatternMatcher::fromAffixPattern( + sb, tokenWarehouse, parseFlags, &hasSuffix, status); + AffixPatternMatcher* suffix = hasSuffix + ? &warehouse.fAffixPatternMatchers[numAffixPatternMatchers++] + : nullptr; + + if (signum == 1) { + posPrefix = prefix; + posSuffix = suffix; + } else if (equals(prefix, posPrefix) && equals(suffix, posSuffix)) { + // Skip adding these matchers (we already have equivalents) + continue; + } + + // Flags for setting in the ParsedNumber + int flags = (signum == -1) ? FLAG_NEGATIVE : 0; + + // Note: it is indeed possible for posPrefix and posSuffix to both be null. + // We still need to add that matcher for strict mode to work. + warehouse.fAffixMatchers[numAffixMatchers++] = {prefix, suffix, flags}; + if (includeUnpaired && prefix != nullptr && suffix != nullptr) { + // The following if statements are designed to prevent adding two identical matchers. + if (signum == 1 || equals(prefix, posPrefix)) { + warehouse.fAffixMatchers[numAffixMatchers++] = {prefix, nullptr, flags}; + } + if (signum == 1 || equals(suffix, posSuffix)) { + warehouse.fAffixMatchers[numAffixMatchers++] = {nullptr, suffix, flags}; + } + } } - return *lhs == *rhs; + + // Put the AffixMatchers in order, and then add them to the output. + // Since there are at most 9 elements, do a simple-to-implement bubble sort. + bool madeChanges; + do { + madeChanges = false; + for (int32_t i = 1; i < numAffixMatchers; i++) { + if (warehouse.fAffixMatchers[i - 1].compareTo(warehouse.fAffixMatchers[i]) > 0) { + madeChanges = true; + AffixMatcher temp = std::move(warehouse.fAffixMatchers[i - 1]); + warehouse.fAffixMatchers[i - 1] = std::move(warehouse.fAffixMatchers[i]); + warehouse.fAffixMatchers[i] = std::move(temp); + } + } + } while (madeChanges); + for (int32_t i = 0; i < numAffixMatchers; i++) { + output.addMatcher(warehouse.fAffixMatchers[i]); + } + + return warehouse; } @@ -390,9 +443,15 @@ void AffixMatcher::postProcess(ParsedNumber& result) const { } } -bool AffixMatcher::matched(const AffixPatternMatcher* affix, const UnicodeString& patternString) { - return (affix == nullptr && patternString.isBogus()) || - (affix != nullptr && affix->getPattern() == patternString); +int8_t AffixMatcher::compareTo(const AffixMatcher& rhs) const { + const AffixMatcher& lhs = *this; + if (length(lhs.fPrefix) != length(rhs.fPrefix)) { + return length(lhs.fPrefix) > length(rhs.fPrefix) ? -1 : 1; + } else if (length(lhs.fSuffix) != length(rhs.fSuffix)) { + return length(lhs.fSuffix) > length(rhs.fSuffix) ? -1 : 1; + } else { + return 0; + } } diff --git a/icu4c/source/i18n/numparse_affixes.h b/icu4c/source/i18n/numparse_affixes.h index 69c68227de..17175ce7d9 100644 --- a/icu4c/source/i18n/numparse_affixes.h +++ b/icu4c/source/i18n/numparse_affixes.h @@ -60,7 +60,7 @@ class AffixTokenMatcherWarehouse { const UnicodeString* currency2, const DecimalFormatSymbols* dfs, IgnorablesMatcher* ignorables, const Locale* locale); - AffixTokenMatcherWarehouse(AffixTokenMatcherWarehouse&& src) = default; + AffixTokenMatcherWarehouse(AffixTokenMatcherWarehouse&& src) U_NOEXCEPT; ~AffixTokenMatcherWarehouse(); @@ -102,7 +102,7 @@ class AffixTokenMatcherWarehouse { }; -class AffixPatternMatcherBuilder : public TokenConsumer { +class AffixPatternMatcherBuilder : public TokenConsumer, public MutableMatcherCollection { public: AffixPatternMatcherBuilder(const UnicodeString& pattern, AffixTokenMatcherWarehouse& warehouse, IgnorablesMatcher* ignorables); @@ -121,7 +121,7 @@ class AffixPatternMatcherBuilder : public TokenConsumer { AffixTokenMatcherWarehouse& fWarehouse; IgnorablesMatcher* fIgnorables; - void addMatcher(NumberParseMatcher& matcher); + void addMatcher(NumberParseMatcher& matcher) override; }; @@ -153,25 +153,18 @@ class AffixMatcher : public NumberParseMatcher, public UMemory { AffixMatcher(AffixPatternMatcher* prefix, AffixPatternMatcher* suffix, result_flags_t flags); - // static void createMatchers() is the constructor for AffixMatcherWarehouse in C++ - bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; void postProcess(ParsedNumber& result) const override; const UnicodeSet& getLeadCodePoints() override; + int8_t compareTo(const AffixMatcher& rhs) const; + private: AffixPatternMatcher* fPrefix; AffixPatternMatcher* fSuffix; result_flags_t fFlags; - - /** - * Helper method to return whether the given AffixPatternMatcher equals the given pattern string. - * Either both arguments must be null or the pattern string inside the AffixPatternMatcher must equal - * the given pattern string. - */ - static bool matched(const AffixPatternMatcher* affix, const UnicodeString& patternString); }; @@ -182,10 +175,15 @@ class AffixMatcherWarehouse { public: AffixMatcherWarehouse() = default; // WARNING: Leaves the object in an unusable state - // in Java, this is AffixMatcher#createMatchers() - AffixMatcherWarehouse(const AffixPatternProvider& patternInfo, NumberParserImpl& output, - AffixTokenMatcherWarehouse& warehouse, const IgnorablesMatcher& ignorables, - parse_flags_t parseFlags, UErrorCode& status); + AffixMatcherWarehouse(AffixTokenMatcherWarehouse& warehouse); + + AffixMatcherWarehouse& operator=(AffixMatcherWarehouse&& src); + + static AffixMatcherWarehouse createAffixMatchers(const AffixPatternProvider& patternInfo, + MutableMatcherCollection& output, + AffixTokenMatcherWarehouse tokenWarehouse, + const IgnorablesMatcher& ignorables, + parse_flags_t parseFlags, UErrorCode& status); private: // 9 is the limit: positive, zero, and negative, each with prefix, suffix, and prefix+suffix @@ -195,14 +193,10 @@ class AffixMatcherWarehouse { // Store all the tokens used by the AffixPatternMatchers AffixTokenMatcherWarehouse fAffixTokenMatcherWarehouse; + friend class AffixMatcher; + static bool isInteresting(const AffixPatternProvider& patternInfo, const IgnorablesMatcher& ignorables, parse_flags_t parseFlags, UErrorCode& status); - - /** - * Helper method to return whether (1) both lhs and rhs are null/invalid, or (2) if they are both - * valid, whether they are equal according to operator==. Similar to Java Objects.equals() - */ - static bool equals(const AffixPatternMatcher* lhs, const AffixPatternMatcher* rhs); }; diff --git a/icu4c/source/i18n/numparse_impl.cpp b/icu4c/source/i18n/numparse_impl.cpp index efa9b3cab2..5bf373de58 100644 --- a/icu4c/source/i18n/numparse_impl.cpp +++ b/icu4c/source/i18n/numparse_impl.cpp @@ -32,18 +32,27 @@ NumberParserImpl::createSimpleParser(const Locale& locale, const UnicodeString& auto* parser = new NumberParserImpl(parseFlags, true); DecimalFormatSymbols symbols(locale, status); - parser->fLocalMatchers.ignorables = std::move(IgnorablesMatcher(unisets::DEFAULT_IGNORABLES)); + parser->fLocalMatchers.ignorables = {unisets::DEFAULT_IGNORABLES}; + IgnorablesMatcher& ignorables = parser->fLocalMatchers.ignorables; -// MatcherFactory factory = new MatcherFactory(); -// factory.currency = Currency.getInstance("USD"); -// factory.symbols = symbols; -// factory.ignorables = ignorables; -// factory.locale = locale; -// factory.parseFlags = parseFlags; + UnicodeString currency1(u"IU$"); + UnicodeString currency2(u"ICU"); ParsedPatternInfo patternInfo; PatternParser::parseToPatternInfo(patternString, patternInfo, status); -// AffixMatcher.createMatchers(patternInfo, parser, factory, ignorables, parseFlags); + + // The following statement sets up the affix matchers. +// AffixMatcherWarehouse warehouse = ; + + parser->fLocalMatchers.affixMatcherWarehouse = std::move(AffixMatcherWarehouse::createAffixMatchers( + patternInfo, + *parser, + AffixTokenMatcherWarehouse( + u"USD", ¤cy1, ¤cy2, &symbols, &ignorables, &locale), + ignorables, + parseFlags, + status)); + Grouper grouper = Grouper::forStrategy(UNUM_GROUPING_AUTO); grouper.setLocaleData(patternInfo, locale); diff --git a/icu4c/source/i18n/numparse_impl.h b/icu4c/source/i18n/numparse_impl.h index abc826f590..cfae156d56 100644 --- a/icu4c/source/i18n/numparse_impl.h +++ b/icu4c/source/i18n/numparse_impl.h @@ -13,18 +13,19 @@ #include "numparse_scientific.h" #include "unicode/uniset.h" #include "numparse_currency.h" +#include "numparse_affixes.h" U_NAMESPACE_BEGIN namespace numparse { namespace impl { -class NumberParserImpl { +class NumberParserImpl : public MutableMatcherCollection { public: - ~NumberParserImpl(); + virtual ~NumberParserImpl(); static NumberParserImpl* createSimpleParser(const Locale& locale, const UnicodeString& patternString, parse_flags_t parseFlags, UErrorCode& status); - void addMatcher(NumberParseMatcher& matcher); + void addMatcher(NumberParseMatcher& matcher) override; void freeze(); @@ -58,6 +59,7 @@ class NumberParserImpl { DecimalMatcher decimal; ScientificMatcher scientific; CurrencyNamesMatcher currencyNames; + AffixMatcherWarehouse affixMatcherWarehouse; } fLocalMatchers; NumberParserImpl(parse_flags_t parseFlags, bool computeLeads); diff --git a/icu4c/source/i18n/numparse_types.h b/icu4c/source/i18n/numparse_types.h index 8a92dc93fb..d958a97b9d 100644 --- a/icu4c/source/i18n/numparse_types.h +++ b/icu4c/source/i18n/numparse_types.h @@ -327,6 +327,17 @@ class NumberParseMatcher { }; +/** + * Interface for use in arguments. + */ +class MutableMatcherCollection { + public: + virtual ~MutableMatcherCollection() = default; + + virtual void addMatcher(NumberParseMatcher& matcher) = 0; +}; + + } // namespace impl } // namespace numparse U_NAMESPACE_END diff --git a/icu4c/source/test/intltest/numbertest_parse.cpp b/icu4c/source/test/intltest/numbertest_parse.cpp index 4fc4da370d..15cfb40a05 100644 --- a/icu4c/source/test/intltest/numbertest_parse.cpp +++ b/icu4c/source/test/intltest/numbertest_parse.cpp @@ -76,10 +76,10 @@ void NumberParserTest::testBasic() { // {3, u"a 𝟱𝟭𝟰𝟮𝟯 b", u"a0b", 14, 51423.}, // {3, u"-a 𝟱𝟭𝟰𝟮𝟯 b", u"a0b", 15, -51423.}, // {3, u"a -𝟱𝟭𝟰𝟮𝟯 b", u"a0b", 15, -51423.}, -// {3, u"𝟱𝟭𝟰𝟮𝟯", u"[0];(0)", 10, 51423.}, -// {3, u"[𝟱𝟭𝟰𝟮𝟯", u"[0];(0)", 11, 51423.}, -// {3, u"𝟱𝟭𝟰𝟮𝟯]", u"[0];(0)", 11, 51423.}, -// {3, u"[𝟱𝟭𝟰𝟮𝟯]", u"[0];(0)", 12, 51423.}, + {3, u"𝟱𝟭𝟰𝟮𝟯", u"[0];(0)", 10, 51423.}, + {3, u"[𝟱𝟭𝟰𝟮𝟯", u"[0];(0)", 11, 51423.}, + {3, u"𝟱𝟭𝟰𝟮𝟯]", u"[0];(0)", 11, 51423.}, + {3, u"[𝟱𝟭𝟰𝟮𝟯]", u"[0];(0)", 12, 51423.}, // {3, u"(𝟱𝟭𝟰𝟮𝟯", u"[0];(0)", 11, -51423.}, // {3, u"𝟱𝟭𝟰𝟮𝟯)", u"[0];(0)", 11, -51423.}, // {3, u"(𝟱𝟭𝟰𝟮𝟯)", u"[0];(0)", 12, -51423.}, diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixMatcher.java index 6fccdc2932..c749cd409f 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixMatcher.java @@ -29,15 +29,15 @@ public class AffixMatcher implements NumberParseMatcher { */ public static final Comparator COMPARATOR = new Comparator() { @Override - public int compare(AffixMatcher o1, AffixMatcher o2) { - if (length(o1.prefix) != length(o2.prefix)) { - return length(o1.prefix) > length(o2.prefix) ? -1 : 1; - } else if (length(o1.suffix) != length(o2.suffix)) { - return length(o1.suffix) > length(o2.suffix) ? -1 : 1; - } else if (!o1.equals(o2)) { + public int compare(AffixMatcher lhs, AffixMatcher rhs) { + if (length(lhs.prefix) != length(rhs.prefix)) { + return length(lhs.prefix) > length(rhs.prefix) ? -1 : 1; + } else if (length(lhs.suffix) != length(rhs.suffix)) { + return length(lhs.suffix) > length(rhs.suffix) ? -1 : 1; + } else if (!lhs.equals(rhs)) { // If the prefix and suffix are the same length, arbitrarily break ties. // We can't return zero unless the elements are equal. - return o1.hashCode() > o2.hashCode() ? -1 : 1; + return lhs.hashCode() > rhs.hashCode() ? -1 : 1; } else { return 0; } From 1ed7deaa8cc6d617ad773832292f2ad320780773 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Tue, 13 Feb 2018 02:23:52 +0000 Subject: [PATCH 017/129] ICU-13574 AffixMatcher is working. All simple parsing tests are passing. X-SVN-Rev: 40903 --- icu4c/source/i18n/numparse_affixes.cpp | 175 ++++++++++-------- icu4c/source/i18n/numparse_affixes.h | 97 ++++++---- icu4c/source/i18n/numparse_compositions.cpp | 8 + icu4c/source/i18n/numparse_compositions.h | 2 + icu4c/source/i18n/numparse_currency.cpp | 16 ++ icu4c/source/i18n/numparse_currency.h | 6 + icu4c/source/i18n/numparse_decimal.cpp | 8 + icu4c/source/i18n/numparse_decimal.h | 2 + icu4c/source/i18n/numparse_impl.cpp | 28 +-- icu4c/source/i18n/numparse_impl.h | 1 + icu4c/source/i18n/numparse_parsednumber.cpp | 10 +- icu4c/source/i18n/numparse_scientific.cpp | 8 + icu4c/source/i18n/numparse_scientific.h | 2 + icu4c/source/i18n/numparse_stringsegment.cpp | 4 + icu4c/source/i18n/numparse_symbols.cpp | 13 ++ icu4c/source/i18n/numparse_symbols.h | 4 + icu4c/source/i18n/numparse_types.h | 3 + icu4c/source/i18n/numparse_unisets.cpp | 4 +- .../source/test/intltest/numbertest_parse.cpp | 88 ++++----- .../icu/impl/number/parse/StringSegment.java | 2 + 20 files changed, 313 insertions(+), 168 deletions(-) diff --git a/icu4c/source/i18n/numparse_affixes.cpp b/icu4c/source/i18n/numparse_affixes.cpp index b5d447f192..97ba4a1c66 100644 --- a/icu4c/source/i18n/numparse_affixes.cpp +++ b/icu4c/source/i18n/numparse_affixes.cpp @@ -5,6 +5,10 @@ #if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + #include "numparse_types.h" #include "numparse_affixes.h" #include "numparse_utils.h" @@ -122,52 +126,32 @@ AffixPatternMatcher AffixPatternMatcherBuilder::build() { } -AffixTokenMatcherWarehouse::AffixTokenMatcherWarehouse(const UChar* currencyCode, - const UnicodeString* currency1, - const UnicodeString* currency2, - const DecimalFormatSymbols* dfs, - IgnorablesMatcher* ignorables, const Locale* locale) - : currency1(currency1), - currency2(currency2), - dfs(dfs), - ignorables(ignorables), - locale(locale), - codePointCount(0), - codePointNumBatches(0) { - utils::copyCurrencyCode(this->currencyCode, currencyCode); -} +CodePointMatcherWarehouse::CodePointMatcherWarehouse() + : codePointCount(0), codePointNumBatches(0) {} -AffixTokenMatcherWarehouse::AffixTokenMatcherWarehouse( - AffixTokenMatcherWarehouse&& src) U_NOEXCEPT = default; - -AffixTokenMatcherWarehouse::~AffixTokenMatcherWarehouse() { +CodePointMatcherWarehouse::~CodePointMatcherWarehouse() { // Delete the variable number of batches of code point matchers for (int32_t i = 0; i < codePointNumBatches; i++) { delete[] codePointsOverflow[i]; } } -NumberParseMatcher& AffixTokenMatcherWarehouse::minusSign() { - return fMinusSign = {*dfs, true}; +CodePointMatcherWarehouse::CodePointMatcherWarehouse(CodePointMatcherWarehouse&& src) U_NOEXCEPT + : codePoints(std::move(src.codePoints)), + codePointsOverflow(std::move(src.codePointsOverflow)), + codePointCount(src.codePointCount), + codePointNumBatches(src.codePointNumBatches) {} + +CodePointMatcherWarehouse& +CodePointMatcherWarehouse::operator=(CodePointMatcherWarehouse&& src) U_NOEXCEPT { + codePoints = std::move(src.codePoints); + codePointsOverflow = std::move(src.codePointsOverflow); + codePointCount = src.codePointCount; + codePointNumBatches = src.codePointNumBatches; + return *this; } -NumberParseMatcher& AffixTokenMatcherWarehouse::plusSign() { - return fPlusSign = {*dfs, true}; -} - -NumberParseMatcher& AffixTokenMatcherWarehouse::percent() { - return fPercent = {*dfs}; -} - -NumberParseMatcher& AffixTokenMatcherWarehouse::permille() { - return fPermille = {*dfs}; -} - -NumberParseMatcher& AffixTokenMatcherWarehouse::currency(UErrorCode& status) { - return fCurrency = {{*locale, status}, {currencyCode, *currency1, *currency2}}; -} - -NumberParseMatcher& AffixTokenMatcherWarehouse::nextCodePointMatcher(UChar32 cp) { +NumberParseMatcher& CodePointMatcherWarehouse::nextCodePointMatcher(UChar32 cp) { if (codePointCount < CODE_POINT_STACK_CAPACITY) { return codePoints[codePointCount++] = {cp}; } @@ -186,6 +170,39 @@ NumberParseMatcher& AffixTokenMatcherWarehouse::nextCodePointMatcher(UChar32 cp) } +AffixTokenMatcherWarehouse::AffixTokenMatcherWarehouse(const AffixTokenMatcherSetupData* setupData) + : fSetupData(setupData) {} + +NumberParseMatcher& AffixTokenMatcherWarehouse::minusSign() { + return fMinusSign = {fSetupData->dfs, true}; +} + +NumberParseMatcher& AffixTokenMatcherWarehouse::plusSign() { + return fPlusSign = {fSetupData->dfs, true}; +} + +NumberParseMatcher& AffixTokenMatcherWarehouse::percent() { + return fPercent = {fSetupData->dfs}; +} + +NumberParseMatcher& AffixTokenMatcherWarehouse::permille() { + return fPermille = {fSetupData->dfs}; +} + +NumberParseMatcher& AffixTokenMatcherWarehouse::currency(UErrorCode& status) { + return fCurrency = {{fSetupData->locale, status}, + {fSetupData->currencyCode, fSetupData->currency1, fSetupData->currency2}}; +} + +IgnorablesMatcher& AffixTokenMatcherWarehouse::ignorables() { + return fSetupData->ignorables; +} + +NumberParseMatcher& AffixTokenMatcherWarehouse::nextCodePointMatcher(UChar32 cp) { + return fCodePoints.nextCodePointMatcher(cp); +} + + CodePointMatcher::CodePointMatcher(UChar32 cp) : fCp(cp) {} @@ -207,9 +224,13 @@ const UnicodeSet& CodePointMatcher::getLeadCodePoints() { return *fLocalLeadCodePoints; } +UnicodeString CodePointMatcher::toString() const { + return u""; +} + AffixPatternMatcher AffixPatternMatcher::fromAffixPattern(const UnicodeString& affixPattern, - AffixTokenMatcherWarehouse& warehouse, + AffixTokenMatcherWarehouse& tokenWarehouse, parse_flags_t parseFlags, bool* success, UErrorCode& status) { if (affixPattern.isEmpty()) { @@ -222,10 +243,10 @@ AffixPatternMatcher AffixPatternMatcher::fromAffixPattern(const UnicodeString& a if (0 != (parseFlags & PARSE_FLAG_EXACT_AFFIX)) { ignorables = nullptr; } else { - ignorables = warehouse.ignorables; + ignorables = &tokenWarehouse.ignorables(); } - AffixPatternMatcherBuilder builder(affixPattern, warehouse, ignorables); + AffixPatternMatcherBuilder builder(affixPattern, tokenWarehouse, ignorables); AffixUtils::iterateWithConsumer(UnicodeStringCharSequence(affixPattern), builder, status); return builder.build(); } @@ -243,10 +264,9 @@ bool AffixPatternMatcher::operator==(const AffixPatternMatcher& other) const { } -AffixMatcherWarehouse::AffixMatcherWarehouse(AffixTokenMatcherWarehouse& warehouse) - : fAffixTokenMatcherWarehouse(std::move(warehouse)) {} - -AffixMatcherWarehouse& AffixMatcherWarehouse::operator=(AffixMatcherWarehouse&& src) = default; +AffixMatcherWarehouse::AffixMatcherWarehouse(AffixTokenMatcherWarehouse* tokenWarehouse) + : fTokenWarehouse(tokenWarehouse) { +} bool AffixMatcherWarehouse::isInteresting(const AffixPatternProvider& patternInfo, const IgnorablesMatcher& ignorables, parse_flags_t parseFlags, @@ -278,18 +298,14 @@ bool AffixMatcherWarehouse::isInteresting(const AffixPatternProvider& patternInf return true; } -AffixMatcherWarehouse AffixMatcherWarehouse::createAffixMatchers(const AffixPatternProvider& patternInfo, - MutableMatcherCollection& output, - AffixTokenMatcherWarehouse tokenWarehouse, - const IgnorablesMatcher& ignorables, - parse_flags_t parseFlags, - UErrorCode& status) { +void AffixMatcherWarehouse::createAffixMatchers(const AffixPatternProvider& patternInfo, + MutableMatcherCollection& output, + const IgnorablesMatcher& ignorables, + parse_flags_t parseFlags, UErrorCode& status) { if (!isInteresting(patternInfo, ignorables, parseFlags, status)) { - return {}; + return; } - AffixMatcherWarehouse warehouse(tokenWarehouse); - // The affixes have interesting characters, or we are in strict mode. // Use initial capacity of 6, the highest possible number of AffixMatchers. UnicodeString sb; @@ -309,21 +325,19 @@ AffixMatcherWarehouse AffixMatcherWarehouse::createAffixMatchers(const AffixPatt bool hasPrefix = false; PatternStringUtils::patternInfoToStringBuilder( patternInfo, true, signum, signDisplay, StandardPlural::OTHER, false, sb); - warehouse.fAffixPatternMatchers[numAffixPatternMatchers] = AffixPatternMatcher::fromAffixPattern( - sb, tokenWarehouse, parseFlags, &hasPrefix, status); - AffixPatternMatcher* prefix = hasPrefix - ? &warehouse.fAffixPatternMatchers[numAffixPatternMatchers++] - : nullptr; + fAffixPatternMatchers[numAffixPatternMatchers] = AffixPatternMatcher::fromAffixPattern( + sb, *fTokenWarehouse, parseFlags, &hasPrefix, status); + AffixPatternMatcher* prefix = hasPrefix ? &fAffixPatternMatchers[numAffixPatternMatchers++] + : nullptr; // Generate Suffix bool hasSuffix = false; PatternStringUtils::patternInfoToStringBuilder( patternInfo, false, signum, signDisplay, StandardPlural::OTHER, false, sb); - warehouse.fAffixPatternMatchers[numAffixPatternMatchers] = AffixPatternMatcher::fromAffixPattern( - sb, tokenWarehouse, parseFlags, &hasSuffix, status); - AffixPatternMatcher* suffix = hasSuffix - ? &warehouse.fAffixPatternMatchers[numAffixPatternMatchers++] - : nullptr; + fAffixPatternMatchers[numAffixPatternMatchers] = AffixPatternMatcher::fromAffixPattern( + sb, *fTokenWarehouse, parseFlags, &hasSuffix, status); + AffixPatternMatcher* suffix = hasSuffix ? &fAffixPatternMatchers[numAffixPatternMatchers++] + : nullptr; if (signum == 1) { posPrefix = prefix; @@ -338,14 +352,14 @@ AffixMatcherWarehouse AffixMatcherWarehouse::createAffixMatchers(const AffixPatt // Note: it is indeed possible for posPrefix and posSuffix to both be null. // We still need to add that matcher for strict mode to work. - warehouse.fAffixMatchers[numAffixMatchers++] = {prefix, suffix, flags}; + fAffixMatchers[numAffixMatchers++] = {prefix, suffix, flags}; if (includeUnpaired && prefix != nullptr && suffix != nullptr) { // The following if statements are designed to prevent adding two identical matchers. - if (signum == 1 || equals(prefix, posPrefix)) { - warehouse.fAffixMatchers[numAffixMatchers++] = {prefix, nullptr, flags}; + if (signum == 1 || !equals(prefix, posPrefix)) { + fAffixMatchers[numAffixMatchers++] = {prefix, nullptr, flags}; } - if (signum == 1 || equals(suffix, posSuffix)) { - warehouse.fAffixMatchers[numAffixMatchers++] = {nullptr, suffix, flags}; + if (signum == 1 || !equals(suffix, posSuffix)) { + fAffixMatchers[numAffixMatchers++] = {nullptr, suffix, flags}; } } } @@ -356,19 +370,20 @@ AffixMatcherWarehouse AffixMatcherWarehouse::createAffixMatchers(const AffixPatt do { madeChanges = false; for (int32_t i = 1; i < numAffixMatchers; i++) { - if (warehouse.fAffixMatchers[i - 1].compareTo(warehouse.fAffixMatchers[i]) > 0) { + if (fAffixMatchers[i - 1].compareTo(fAffixMatchers[i]) > 0) { madeChanges = true; - AffixMatcher temp = std::move(warehouse.fAffixMatchers[i - 1]); - warehouse.fAffixMatchers[i - 1] = std::move(warehouse.fAffixMatchers[i]); - warehouse.fAffixMatchers[i] = std::move(temp); + AffixMatcher temp = std::move(fAffixMatchers[i - 1]); + fAffixMatchers[i - 1] = std::move(fAffixMatchers[i]); + fAffixMatchers[i] = std::move(temp); } } } while (madeChanges); - for (int32_t i = 0; i < numAffixMatchers; i++) { - output.addMatcher(warehouse.fAffixMatchers[i]); - } - return warehouse; + for (int32_t i = 0; i < numAffixMatchers; i++) { + // Enable the following line to debug affixes + //std::cout << "Adding affix matcher: " << CStr(fAffixMatchers[i].toString())() << std::endl; + output.addMatcher(fAffixMatchers[i]); + } } @@ -454,6 +469,14 @@ int8_t AffixMatcher::compareTo(const AffixMatcher& rhs) const { } } +UnicodeString AffixMatcher::toString() const { + bool isNegative = 0 != (fFlags & FLAG_NEGATIVE); + return UnicodeString(u"getPattern() : u"null") + u"#" + + (fSuffix ? fSuffix->getPattern() : u"null") + u">"; + +} + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/numparse_affixes.h b/icu4c/source/i18n/numparse_affixes.h index 17175ce7d9..59789e0395 100644 --- a/icu4c/source/i18n/numparse_affixes.h +++ b/icu4c/source/i18n/numparse_affixes.h @@ -12,6 +12,8 @@ #include "numparse_currency.h" #include "number_affixutils.h" +#include + U_NAMESPACE_BEGIN namespace numparse { namespace impl { @@ -33,11 +35,57 @@ class CodePointMatcher : public NumberParseMatcher, public UMemory { const UnicodeSet& getLeadCodePoints() override; + UnicodeString toString() const override; + private: UChar32 fCp; }; +/** + * A warehouse to retain ownership of CodePointMatchers. + */ +class CodePointMatcherWarehouse : public UMemory { + private: + static constexpr int32_t CODE_POINT_STACK_CAPACITY = 5; // Number of entries directly on the stack + static constexpr int32_t CODE_POINT_BATCH_SIZE = 10; // Number of entries per heap allocation + + public: + CodePointMatcherWarehouse(); + + // A custom destructor is needed to free the memory from MaybeStackArray. + // A custom move constructor and move assignment seem to be needed because of the custom destructor. + + ~CodePointMatcherWarehouse(); + + CodePointMatcherWarehouse(CodePointMatcherWarehouse&& src) U_NOEXCEPT; + + CodePointMatcherWarehouse& operator=(CodePointMatcherWarehouse&& src) U_NOEXCEPT; + + NumberParseMatcher& nextCodePointMatcher(UChar32 cp); + + private: + std::array codePoints; // By value + MaybeStackArray codePointsOverflow; // On heap in "batches" + int32_t codePointCount; // Total for both the ones by value and on heap + int32_t codePointNumBatches; // Number of batches in codePointsOverflow +}; + + +struct AffixTokenMatcherSetupData { + const UChar* currencyCode; + const UnicodeString& currency1; + const UnicodeString& currency2; + const DecimalFormatSymbols& dfs; + IgnorablesMatcher& ignorables; + const Locale& locale; + +// const UChar* currencyCode, const UnicodeString* currency1, +// const UnicodeString* currency2, const DecimalFormatSymbols* dfs, +// IgnorablesMatcher* ignorables, const Locale* locale +}; + + /** * Small helper class that generates matchers for individual tokens for AffixPatternMatcher. * @@ -48,21 +96,11 @@ class CodePointMatcher : public NumberParseMatcher, public UMemory { * * @author sffc */ -class AffixTokenMatcherWarehouse { - private: - static constexpr int32_t CODE_POINT_STACK_CAPACITY = 5; // Number of entries directly on the stack - static constexpr int32_t CODE_POINT_BATCH_SIZE = 10; // Number of entries per heap allocation - +class AffixTokenMatcherWarehouse : public UMemory { public: AffixTokenMatcherWarehouse() = default; // WARNING: Leaves the object in an unusable state - AffixTokenMatcherWarehouse(const UChar* currencyCode, const UnicodeString* currency1, - const UnicodeString* currency2, const DecimalFormatSymbols* dfs, - IgnorablesMatcher* ignorables, const Locale* locale); - - AffixTokenMatcherWarehouse(AffixTokenMatcherWarehouse&& src) U_NOEXCEPT; - - ~AffixTokenMatcherWarehouse(); + AffixTokenMatcherWarehouse(const AffixTokenMatcherSetupData* setupData); NumberParseMatcher& minusSign(); @@ -74,16 +112,13 @@ class AffixTokenMatcherWarehouse { NumberParseMatcher& currency(UErrorCode& status); + IgnorablesMatcher& ignorables(); + NumberParseMatcher& nextCodePointMatcher(UChar32 cp); private: - // NOTE: The following fields may be unsafe to access after construction is done! - UChar currencyCode[4]; - const UnicodeString* currency1; - const UnicodeString* currency2; - const DecimalFormatSymbols* dfs; - IgnorablesMatcher* ignorables; - const Locale* locale; + // NOTE: The following field may be unsafe to access after construction is done! + const AffixTokenMatcherSetupData* fSetupData; // NOTE: These are default-constructed and should not be used until initialized. MinusSignMatcher fMinusSign; @@ -92,10 +127,8 @@ class AffixTokenMatcherWarehouse { PermilleMatcher fPermille; CurrencyAnyMatcher fCurrency; - CodePointMatcher codePoints[CODE_POINT_STACK_CAPACITY]; // By value - MaybeStackArray codePointsOverflow; // On heap in "batches" - int32_t codePointCount; // Total for both the ones by value and on heap - int32_t codePointNumBatches; // Number of batches in codePointsOverflow + // Use a child class for code point matchers, since it requires non-default operators. + CodePointMatcherWarehouse fCodePoints; friend class AffixPatternMatcherBuilder; friend class AffixPatternMatcher; @@ -161,6 +194,8 @@ class AffixMatcher : public NumberParseMatcher, public UMemory { int8_t compareTo(const AffixMatcher& rhs) const; + UnicodeString toString() const override; + private: AffixPatternMatcher* fPrefix; AffixPatternMatcher* fSuffix; @@ -175,23 +210,19 @@ class AffixMatcherWarehouse { public: AffixMatcherWarehouse() = default; // WARNING: Leaves the object in an unusable state - AffixMatcherWarehouse(AffixTokenMatcherWarehouse& warehouse); + AffixMatcherWarehouse(AffixTokenMatcherWarehouse* tokenWarehouse); - AffixMatcherWarehouse& operator=(AffixMatcherWarehouse&& src); - - static AffixMatcherWarehouse createAffixMatchers(const AffixPatternProvider& patternInfo, - MutableMatcherCollection& output, - AffixTokenMatcherWarehouse tokenWarehouse, - const IgnorablesMatcher& ignorables, - parse_flags_t parseFlags, UErrorCode& status); + void createAffixMatchers(const AffixPatternProvider& patternInfo, MutableMatcherCollection& output, + const IgnorablesMatcher& ignorables, parse_flags_t parseFlags, + UErrorCode& status); private: // 9 is the limit: positive, zero, and negative, each with prefix, suffix, and prefix+suffix AffixMatcher fAffixMatchers[9]; // 6 is the limit: positive, zero, and negative, a prefix and a suffix for each AffixPatternMatcher fAffixPatternMatchers[6]; - // Store all the tokens used by the AffixPatternMatchers - AffixTokenMatcherWarehouse fAffixTokenMatcherWarehouse; + // Reference to the warehouse for tokens used by the AffixPatternMatchers + AffixTokenMatcherWarehouse* fTokenWarehouse; friend class AffixMatcher; diff --git a/icu4c/source/i18n/numparse_compositions.cpp b/icu4c/source/i18n/numparse_compositions.cpp index 6d8d52d2ba..11e1cbcdf0 100644 --- a/icu4c/source/i18n/numparse_compositions.cpp +++ b/icu4c/source/i18n/numparse_compositions.cpp @@ -5,6 +5,10 @@ #if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + #include "numparse_types.h" #include "numparse_compositions.h" #include "unicode/uniset.h" @@ -113,5 +117,9 @@ const NumberParseMatcher* const* ArraySeriesMatcher::end() const { return fMatchers.getAlias() + fMatchersLen; } +UnicodeString ArraySeriesMatcher::toString() const { + return u""; +} + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/numparse_compositions.h b/icu4c/source/i18n/numparse_compositions.h index 51501a805c..91db6fa2ca 100644 --- a/icu4c/source/i18n/numparse_compositions.h +++ b/icu4c/source/i18n/numparse_compositions.h @@ -87,6 +87,8 @@ class ArraySeriesMatcher : public SeriesMatcher { const UnicodeSet& getLeadCodePoints() override; + UnicodeString toString() const override; + int32_t length() const override; protected: diff --git a/icu4c/source/i18n/numparse_currency.cpp b/icu4c/source/i18n/numparse_currency.cpp index cf56a94f6d..b3a317ef71 100644 --- a/icu4c/source/i18n/numparse_currency.cpp +++ b/icu4c/source/i18n/numparse_currency.cpp @@ -5,6 +5,10 @@ #if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + #include "numparse_types.h" #include "numparse_currency.h" #include "ucurrimp.h" @@ -66,6 +70,10 @@ const UnicodeSet& CurrencyNamesMatcher::getLeadCodePoints() { return *fLocalLeadCodePoints; } +UnicodeString CurrencyNamesMatcher::toString() const { + return u""; +} + CurrencyCustomMatcher::CurrencyCustomMatcher(const char16_t* currencyCode, const UnicodeString& currency1, const UnicodeString& currency2) @@ -106,6 +114,10 @@ const UnicodeSet& CurrencyCustomMatcher::getLeadCodePoints() { return *fLocalLeadCodePoints; } +UnicodeString CurrencyCustomMatcher::toString() const { + return u""; +} + CurrencyAnyMatcher::CurrencyAnyMatcher() { fMatcherArray[0] = &fNamesMatcher; @@ -151,5 +163,9 @@ const NumberParseMatcher* const* CurrencyAnyMatcher::end() const { return fMatcherArray + 2; } +UnicodeString CurrencyAnyMatcher::toString() const { + return u""; +} + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/numparse_currency.h b/icu4c/source/i18n/numparse_currency.h index 547f6ee041..3d8cb3a2bf 100644 --- a/icu4c/source/i18n/numparse_currency.h +++ b/icu4c/source/i18n/numparse_currency.h @@ -32,6 +32,8 @@ class CurrencyNamesMatcher : public NumberParseMatcher, public UMemory { const UnicodeSet& getLeadCodePoints() override; + UnicodeString toString() const override; + private: // We could use Locale instead of CharString here, but // Locale has a non-trivial default constructor. @@ -51,6 +53,8 @@ class CurrencyCustomMatcher : public NumberParseMatcher, public UMemory { const UnicodeSet& getLeadCodePoints() override; + UnicodeString toString() const override; + private: UChar fCurrencyCode[4]; UnicodeString fCurrency1; @@ -75,6 +79,8 @@ class CurrencyAnyMatcher : public AnyMatcher, public UMemory { const UnicodeSet& getLeadCodePoints() override; + UnicodeString toString() const override; + protected: const NumberParseMatcher* const* begin() const override; diff --git a/icu4c/source/i18n/numparse_decimal.cpp b/icu4c/source/i18n/numparse_decimal.cpp index e80014fa59..33b6821ff2 100644 --- a/icu4c/source/i18n/numparse_decimal.cpp +++ b/icu4c/source/i18n/numparse_decimal.cpp @@ -5,6 +5,10 @@ #if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + #include "numparse_types.h" #include "numparse_decimal.h" #include "numparse_unisets.h" @@ -312,5 +316,9 @@ const UnicodeSet& DecimalMatcher::getLeadCodePoints() { return *fLocalLeadCodePoints; } +UnicodeString DecimalMatcher::toString() const { + return u""; +} + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/numparse_decimal.h b/icu4c/source/i18n/numparse_decimal.h index 203cb66b4b..78ad074f19 100644 --- a/icu4c/source/i18n/numparse_decimal.h +++ b/icu4c/source/i18n/numparse_decimal.h @@ -29,6 +29,8 @@ class DecimalMatcher : public NumberParseMatcher, public UMemory { const UnicodeSet& getLeadCodePoints() override; + UnicodeString toString() const override; + private: /** If true, only accept strings whose grouping sizes match the locale */ bool requireGroupingMatch; diff --git a/icu4c/source/i18n/numparse_impl.cpp b/icu4c/source/i18n/numparse_impl.cpp index 5bf373de58..06efe9d1b1 100644 --- a/icu4c/source/i18n/numparse_impl.cpp +++ b/icu4c/source/i18n/numparse_impl.cpp @@ -5,7 +5,8 @@ #if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT -// Allow implicit conversion from char16_t* to UnicodeString for this file +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. #define UNISTR_FROM_STRING_EXPLICIT #include "number_types.h" @@ -17,6 +18,9 @@ #include "unicode/numberformatter.h" #include +#include +#include +#include "cstr.h" using namespace icu; using namespace icu::number; @@ -35,24 +39,20 @@ NumberParserImpl::createSimpleParser(const Locale& locale, const UnicodeString& parser->fLocalMatchers.ignorables = {unisets::DEFAULT_IGNORABLES}; IgnorablesMatcher& ignorables = parser->fLocalMatchers.ignorables; + const UChar currencyCode[] = u"USD"; UnicodeString currency1(u"IU$"); UnicodeString currency2(u"ICU"); ParsedPatternInfo patternInfo; PatternParser::parseToPatternInfo(patternString, patternInfo, status); - // The following statement sets up the affix matchers. -// AffixMatcherWarehouse warehouse = ; - - parser->fLocalMatchers.affixMatcherWarehouse = std::move(AffixMatcherWarehouse::createAffixMatchers( - patternInfo, - *parser, - AffixTokenMatcherWarehouse( - u"USD", ¤cy1, ¤cy2, &symbols, &ignorables, &locale), - ignorables, - parseFlags, - status)); - + // The following statements set up the affix matchers. + AffixTokenMatcherSetupData affixSetupData = { + currencyCode, currency1, currency2, symbols, ignorables, locale}; + parser->fLocalMatchers.affixTokenMatcherWarehouse = {&affixSetupData}; + parser->fLocalMatchers.affixMatcherWarehouse = {&parser->fLocalMatchers.affixTokenMatcherWarehouse}; + parser->fLocalMatchers.affixMatcherWarehouse.createAffixMatchers( + patternInfo, *parser, ignorables, parseFlags, status); Grouper grouper = Grouper::forStrategy(UNUM_GROUPING_AUTO); grouper.setLocaleData(patternInfo, locale); @@ -233,7 +233,7 @@ UnicodeString NumberParserImpl::toString() const { UnicodeString result(u"toString()); } result.append(u" ]>", -1); return result; diff --git a/icu4c/source/i18n/numparse_impl.h b/icu4c/source/i18n/numparse_impl.h index cfae156d56..210dcf4754 100644 --- a/icu4c/source/i18n/numparse_impl.h +++ b/icu4c/source/i18n/numparse_impl.h @@ -60,6 +60,7 @@ class NumberParserImpl : public MutableMatcherCollection { ScientificMatcher scientific; CurrencyNamesMatcher currencyNames; AffixMatcherWarehouse affixMatcherWarehouse; + AffixTokenMatcherWarehouse affixTokenMatcherWarehouse; } fLocalMatchers; NumberParserImpl(parse_flags_t parseFlags, bool computeLeads); diff --git a/icu4c/source/i18n/numparse_parsednumber.cpp b/icu4c/source/i18n/numparse_parsednumber.cpp index 908cffecef..4cf85a0f43 100644 --- a/icu4c/source/i18n/numparse_parsednumber.cpp +++ b/icu4c/source/i18n/numparse_parsednumber.cpp @@ -5,6 +5,10 @@ #if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + #include "numparse_types.h" #include @@ -67,7 +71,11 @@ double ParsedNumber::getDouble() const { } // TODO: MIN_LONG - return quantity.toDouble(); + double d = quantity.toDouble(); + if (0 != (flags & FLAG_NEGATIVE)) { + d *= -1; + } + return d; } bool ParsedNumber::isBetterThan(const ParsedNumber& other) { diff --git a/icu4c/source/i18n/numparse_scientific.cpp b/icu4c/source/i18n/numparse_scientific.cpp index 18ade048fb..bcb777101a 100644 --- a/icu4c/source/i18n/numparse_scientific.cpp +++ b/icu4c/source/i18n/numparse_scientific.cpp @@ -5,6 +5,10 @@ #if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + #include "numparse_types.h" #include "numparse_scientific.h" #include "numparse_unisets.h" @@ -83,5 +87,9 @@ const UnicodeSet& ScientificMatcher::getLeadCodePoints() { return *fLocalLeadCodePoints; } +UnicodeString ScientificMatcher::toString() const { + return u""; +} + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/numparse_scientific.h b/icu4c/source/i18n/numparse_scientific.h index 2f4118ff61..0265c10608 100644 --- a/icu4c/source/i18n/numparse_scientific.h +++ b/icu4c/source/i18n/numparse_scientific.h @@ -27,6 +27,8 @@ class ScientificMatcher : public NumberParseMatcher, public UMemory { const UnicodeSet& getLeadCodePoints() override; + UnicodeString toString() const override; + private: UnicodeString fExponentSeparatorString; DecimalMatcher fExponentMatcher; diff --git a/icu4c/source/i18n/numparse_stringsegment.cpp b/icu4c/source/i18n/numparse_stringsegment.cpp index 3683890090..b2dcdc3642 100644 --- a/icu4c/source/i18n/numparse_stringsegment.cpp +++ b/icu4c/source/i18n/numparse_stringsegment.cpp @@ -5,6 +5,10 @@ #if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + #include "numparse_types.h" #include "numparse_stringsegment.h" #include "putilimp.h" diff --git a/icu4c/source/i18n/numparse_symbols.cpp b/icu4c/source/i18n/numparse_symbols.cpp index 3ba12a68df..6492f1321f 100644 --- a/icu4c/source/i18n/numparse_symbols.cpp +++ b/icu4c/source/i18n/numparse_symbols.cpp @@ -5,6 +5,10 @@ #if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + #include "numparse_types.h" #include "numparse_symbols.h" #include "numparse_utils.h" @@ -70,6 +74,11 @@ const UnicodeSet& SymbolMatcher::getLeadCodePoints() { return *fLocalLeadCodePoints; } +UnicodeString SymbolMatcher::toString() const { + // TODO: Customize output for each symbol + return u""; +} + IgnorablesMatcher::IgnorablesMatcher(unisets::Key key) : SymbolMatcher({}, key) { @@ -79,6 +88,10 @@ bool IgnorablesMatcher::isFlexible() const { return true; } +UnicodeString IgnorablesMatcher::toString() const { + return u""; +} + bool IgnorablesMatcher::isDisabled(const ParsedNumber&) const { return false; } diff --git a/icu4c/source/i18n/numparse_symbols.h b/icu4c/source/i18n/numparse_symbols.h index cf5b8d8668..2cc107101d 100644 --- a/icu4c/source/i18n/numparse_symbols.h +++ b/icu4c/source/i18n/numparse_symbols.h @@ -30,6 +30,8 @@ class SymbolMatcher : public NumberParseMatcher, public UMemory { const UnicodeSet& getLeadCodePoints() override; + UnicodeString toString() const override; + virtual bool isDisabled(const ParsedNumber& result) const = 0; virtual void accept(StringSegment& segment, ParsedNumber& result) const = 0; @@ -50,6 +52,8 @@ class IgnorablesMatcher : public SymbolMatcher { bool isFlexible() const override; + UnicodeString toString() const override; + protected: bool isDisabled(const ParsedNumber& result) const override; diff --git a/icu4c/source/i18n/numparse_types.h b/icu4c/source/i18n/numparse_types.h index d958a97b9d..3f27da05e2 100644 --- a/icu4c/source/i18n/numparse_types.h +++ b/icu4c/source/i18n/numparse_types.h @@ -318,6 +318,9 @@ class NumberParseMatcher { // Default implementation: no-op }; + // String for debugging + virtual UnicodeString toString() const = 0; + protected: // No construction except by subclasses! NumberParseMatcher() = default; diff --git a/icu4c/source/i18n/numparse_unisets.cpp b/icu4c/source/i18n/numparse_unisets.cpp index 625e1ac31d..fc0274f2a3 100644 --- a/icu4c/source/i18n/numparse_unisets.cpp +++ b/icu4c/source/i18n/numparse_unisets.cpp @@ -5,8 +5,8 @@ #if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT -// Allow implicit conversion from char16_t* to UnicodeString for this file -// (useful for UnicodeSet constructor) +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. #define UNISTR_FROM_STRING_EXPLICIT #include "numparse_unisets.h" diff --git a/icu4c/source/test/intltest/numbertest_parse.cpp b/icu4c/source/test/intltest/numbertest_parse.cpp index 15cfb40a05..16323b52db 100644 --- a/icu4c/source/test/intltest/numbertest_parse.cpp +++ b/icu4c/source/test/intltest/numbertest_parse.cpp @@ -69,33 +69,33 @@ void NumberParserTest::testBasic() { {3, u"-∞", u"0", 2, -INFINITY}, {3, u"@@@123 @@", u"0", 6, 123.}, // TODO: Should padding be strong instead of weak? {3, u"@@@123@@ ", u"0", 6, 123.}, // TODO: Should padding be strong instead of weak? -// {3, u"a51423US dollars", u"a0¤¤¤", 16, 51423.}, -// {3, u"a 51423 US dollars", u"a0¤¤¤", 18, 51423.}, + {3, u"a51423US dollars", u"a0¤¤¤", 16, 51423.}, + {3, u"a 51423 US dollars", u"a0¤¤¤", 18, 51423.}, {3, u"514.23 USD", u"¤0", 10, 514.23}, {3, u"514.23 GBP", u"¤0", 10, 514.23}, -// {3, u"a 𝟱𝟭𝟰𝟮𝟯 b", u"a0b", 14, 51423.}, -// {3, u"-a 𝟱𝟭𝟰𝟮𝟯 b", u"a0b", 15, -51423.}, -// {3, u"a -𝟱𝟭𝟰𝟮𝟯 b", u"a0b", 15, -51423.}, + {3, u"a 𝟱𝟭𝟰𝟮𝟯 b", u"a0b", 14, 51423.}, + {3, u"-a 𝟱𝟭𝟰𝟮𝟯 b", u"a0b", 15, -51423.}, + {3, u"a -𝟱𝟭𝟰𝟮𝟯 b", u"a0b", 15, -51423.}, {3, u"𝟱𝟭𝟰𝟮𝟯", u"[0];(0)", 10, 51423.}, {3, u"[𝟱𝟭𝟰𝟮𝟯", u"[0];(0)", 11, 51423.}, {3, u"𝟱𝟭𝟰𝟮𝟯]", u"[0];(0)", 11, 51423.}, {3, u"[𝟱𝟭𝟰𝟮𝟯]", u"[0];(0)", 12, 51423.}, -// {3, u"(𝟱𝟭𝟰𝟮𝟯", u"[0];(0)", 11, -51423.}, -// {3, u"𝟱𝟭𝟰𝟮𝟯)", u"[0];(0)", 11, -51423.}, -// {3, u"(𝟱𝟭𝟰𝟮𝟯)", u"[0];(0)", 12, -51423.}, -// {3, u"𝟱𝟭𝟰𝟮𝟯", u"{0};{0}", 10, 51423.}, -// {3, u"{𝟱𝟭𝟰𝟮𝟯", u"{0};{0}", 11, 51423.}, -// {3, u"𝟱𝟭𝟰𝟮𝟯}", u"{0};{0}", 11, 51423.}, -// {3, u"{𝟱𝟭𝟰𝟮𝟯}", u"{0};{0}", 12, 51423.}, -// {1, u"a40b", u"a0'0b'", 3, 40.}, // greedy code path thinks "40" is the number -// {2, u"a40b", u"a0'0b'", 4, 4.}, // slow code path finds the suffix "0b" + {3, u"(𝟱𝟭𝟰𝟮𝟯", u"[0];(0)", 11, -51423.}, + {3, u"𝟱𝟭𝟰𝟮𝟯)", u"[0];(0)", 11, -51423.}, + {3, u"(𝟱𝟭𝟰𝟮𝟯)", u"[0];(0)", 12, -51423.}, + {3, u"𝟱𝟭𝟰𝟮𝟯", u"{0};{0}", 10, 51423.}, + {3, u"{𝟱𝟭𝟰𝟮𝟯", u"{0};{0}", 11, 51423.}, + {3, u"𝟱𝟭𝟰𝟮𝟯}", u"{0};{0}", 11, 51423.}, + {3, u"{𝟱𝟭𝟰𝟮𝟯}", u"{0};{0}", 12, 51423.}, + {1, u"a40b", u"a0'0b'", 3, 40.}, // greedy code path thinks "40" is the number + {2, u"a40b", u"a0'0b'", 4, 4.}, // slow code path finds the suffix "0b" {3, u"𝟱.𝟭𝟰𝟮E𝟯", u"0", 12, 5142.}, {3, u"𝟱.𝟭𝟰𝟮E-𝟯", u"0", 13, 0.005142}, {3, u"𝟱.𝟭𝟰𝟮e-𝟯", u"0", 13, 0.005142}, {7, u"5,142.50 Canadian dollars", u"#,##,##0 ¤¤¤", 25, 5142.5}, -// {3, u"a$ b5", u"a ¤ b0", 5, 5.0}, -// {3, u"📺1.23", u"📺0;📻0", 6, 1.23}, -// {3, u"📻1.23", u"📺0;📻0", 6, -1.23}, + {3, u"a$ b5", u"a ¤ b0", 5, 5.0}, + {3, u"📺1.23", u"📺0;📻0", 6, 1.23}, + {3, u"📻1.23", u"📺0;📻0", 6, -1.23}, {3, u".00", u"0", 3, 0.0}, {3, u" 1,234", u"a0", 35, 1234.}, // should not hang {3, u"NaN", u"0", 3, NAN}, @@ -215,27 +215,29 @@ void NumberParserTest::testSeriesMatcher() { void NumberParserTest::testCurrencyAnyMatcher() { IcuTestErrorCode status(*this, "testCurrencyAnyMatcher"); - UnicodeString currency1(u"IU$"); - UnicodeString currency2(u"ICU"); - DecimalFormatSymbols symbols("en", status); IgnorablesMatcher ignorables(unisets::DEFAULT_IGNORABLES); - Locale locale("en"); - AffixTokenMatcherWarehouse warehouse(u"ICU", ¤cy1, ¤cy2, &symbols, &ignorables, &locale); + AffixTokenMatcherSetupData affixSetupData = { + u"ICU", + u"IU$", + u"ICU", + {"en", status}, + ignorables, + "en"}; + AffixTokenMatcherWarehouse warehouse(&affixSetupData); NumberParseMatcher& matcher = warehouse.currency(status); - static const struct TestCase{ + static const struct TestCase { const char16_t* input; const char16_t* expectedCurrencyCode; - } cases[] { - { u"", u"\x00" }, - { u"FOO", u"\x00" }, - { u"USD", u"USD" }, - { u"$", u"USD" }, - { u"US dollars", u"USD" }, - { u"eu", u"\x00" }, - { u"euros", u"EUR" }, - { u"ICU", u"ICU" }, - { u"IU$", u"ICU" } }; + } cases[]{{u"", u"\x00"}, + {u"FOO", u"\x00"}, + {u"USD", u"USD"}, + {u"$", u"USD"}, + {u"US dollars", u"USD"}, + {u"eu", u"\x00"}, + {u"euros", u"EUR"}, + {u"ICU", u"ICU"}, + {u"IU$", u"ICU"}}; for (auto& cas : cases) { UnicodeString input(cas.input); @@ -243,7 +245,8 @@ void NumberParserTest::testCurrencyAnyMatcher() { ParsedNumber result; matcher.match(segment, result, status); assertEquals("Parsing " + input, cas.expectedCurrencyCode, result.currencyCode); - assertEquals("Whole string on " + input, + assertEquals( + "Whole string on " + input, cas.expectedCurrencyCode[0] == 0 ? 0 : input.length(), result.charEnd); } @@ -251,13 +254,15 @@ void NumberParserTest::testCurrencyAnyMatcher() { void NumberParserTest::testAffixPatternMatcher() { IcuTestErrorCode status(*this, "testAffixPatternMatcher"); - - UnicodeString currency1(u"foo"); - UnicodeString currency2(u"bar"); - DecimalFormatSymbols symbols("en", status); IgnorablesMatcher ignorables(unisets::DEFAULT_IGNORABLES); - Locale locale("en"); - AffixTokenMatcherWarehouse warehouse(u"EUR", ¤cy1, ¤cy2, &symbols, &ignorables, &locale); + AffixTokenMatcherSetupData affixSetupData = { + u"USD", + u"foo", + u"bar", + {"en", status}, + ignorables, + "en"}; + AffixTokenMatcherWarehouse warehouse(&affixSetupData); static const struct TestCase { bool exactMatch; @@ -269,8 +274,7 @@ void NumberParserTest::testAffixPatternMatcher() { {true, u"+-%", 3, u"+-%"}, {false, u"ab c", 5, u"a bc"}, {true, u"abc", 3, u"abc"}, - {false, u"hello-to+this%very¤long‰string", 59, u"hello-to+this%very USD long‰string"} - }; + {false, u"hello-to+this%very¤long‰string", 59, u"hello-to+this%very USD long‰string"}}; for (auto& cas : cases) { UnicodeString affixPattern(cas.affixPattern); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/StringSegment.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/StringSegment.java index 39416fd753..95d94bb373 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/StringSegment.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/StringSegment.java @@ -9,6 +9,8 @@ import com.ibm.icu.text.UnicodeSet; * A mutable class allowing for a String with a variable offset and length. The charAt, length, and * subSequence methods all operate relative to the fixed offset into the String. * + * TODO: Make sure that this operates only on code point boundaries. + * * @author sffc */ public class StringSegment implements CharSequence { From fb26c75df0162289cb278455eca0a328bcc92784 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Tue, 13 Feb 2018 02:28:00 +0000 Subject: [PATCH 018/129] ICU-13585 Adding std::move operators to CharString and MaybeStackArray. X-SVN-Rev: 40904 --- icu4c/source/common/charstr.cpp | 12 +++++++ icu4c/source/common/charstr.h | 12 +++++++ icu4c/source/common/cmemory.h | 45 ++++++++++++++++++++++++-- icu4c/source/common/unicode/unistr.h | 6 ++-- icu4c/source/test/intltest/strtest.cpp | 22 +++++++++++++ 5 files changed, 91 insertions(+), 6 deletions(-) diff --git a/icu4c/source/common/charstr.cpp b/icu4c/source/common/charstr.cpp index 8bacd20ddc..353f1d5254 100644 --- a/icu4c/source/common/charstr.cpp +++ b/icu4c/source/common/charstr.cpp @@ -23,6 +23,18 @@ U_NAMESPACE_BEGIN +CharString::CharString(CharString&& src) U_NOEXCEPT + : buffer(std::move(src.buffer)), len(src.len) { + src.len = 0; // not strictly necessary because we make no guarantees on the source string +} + +CharString& CharString::operator=(CharString&& src) U_NOEXCEPT { + buffer = std::move(src.buffer); + len = src.len; + src.len = 0; // not strictly necessary because we make no guarantees on the source string + return *this; +} + CharString &CharString::copyFrom(const CharString &s, UErrorCode &errorCode) { if(U_SUCCESS(errorCode) && this!=&s && ensureCapacity(s.len+1, 0, errorCode)) { len=s.len; diff --git a/icu4c/source/common/charstr.h b/icu4c/source/common/charstr.h index 3cfdf6a897..86f69c383a 100644 --- a/icu4c/source/common/charstr.h +++ b/icu4c/source/common/charstr.h @@ -55,6 +55,18 @@ public: } ~CharString() {} + /** + * Move constructor; might leave src in an undefined state. + * This string will have the same contents and state that the source string had. + */ + CharString(CharString &&src) U_NOEXCEPT; + /** + * Move assignment operator; might leave src in an undefined state. + * This string will have the same contents and state that the source string had. + * The behavior is undefined if *this and src are the same object. + */ + CharString &operator=(CharString &&src) U_NOEXCEPT; + /** * Replaces this string's contents with the other string's contents. * CharString does not support the standard copy constructor nor diff --git a/icu4c/source/common/cmemory.h b/icu4c/source/common/cmemory.h index 5cb52993ab..e3532c759e 100644 --- a/icu4c/source/common/cmemory.h +++ b/icu4c/source/common/cmemory.h @@ -299,6 +299,14 @@ public: * Destructor deletes the array (if owned). */ ~MaybeStackArray() { releaseArray(); } + /** + * Move constructor: transfers ownership or copies the stack array. + */ + MaybeStackArray(MaybeStackArray &&src) U_NOEXCEPT; + /** + * Move assignment: transfers ownership or copies the stack array. + */ + MaybeStackArray &operator=(MaybeStackArray &&src) U_NOEXCEPT; /** * Returns the array capacity (number of T items). * @return array capacity @@ -376,6 +384,11 @@ private: uprv_free(ptr); } } + void resetToStackArray() { + ptr=stackArray; + capacity=stackCapacity; + needToRelease=FALSE; + } /* No comparison operators with other MaybeStackArray's. */ bool operator==(const MaybeStackArray & /*other*/) {return FALSE;} bool operator!=(const MaybeStackArray & /*other*/) {return TRUE;} @@ -398,6 +411,34 @@ private: #endif }; +template +icu::MaybeStackArray::MaybeStackArray( + MaybeStackArray && src) U_NOEXCEPT + : ptr(src.ptr), capacity(src.capacity), needToRelease(src.needToRelease) { + if (src.ptr == src.stackArray) { + ptr = stackArray; + uprv_memcpy(stackArray, src.stackArray, sizeof(T) * src.capacity); + } else { + src.resetToStackArray(); // take ownership away from src + } +} + +template +inline MaybeStackArray & +MaybeStackArray::operator=(MaybeStackArray && src) U_NOEXCEPT { + releaseArray(); // in case this instance had its own memory allocated + capacity = src.capacity; + needToRelease = src.needToRelease; + if (src.ptr == src.stackArray) { + ptr = stackArray; + uprv_memcpy(stackArray, src.stackArray, sizeof(T) * src.capacity); + } else { + ptr = src.ptr; + src.resetToStackArray(); // take ownership away from src + } + return *this; +} + template inline T *MaybeStackArray::resize(int32_t newCapacity, int32_t length) { if(newCapacity>0) { @@ -447,9 +488,7 @@ inline T *MaybeStackArray::orphanOrClone(int32_t length, int32 uprv_memcpy(p, ptr, (size_t)length*sizeof(T)); } resultCapacity=length; - ptr=stackArray; - capacity=stackCapacity; - needToRelease=FALSE; + resetToStackArray(); return p; } diff --git a/icu4c/source/common/unicode/unistr.h b/icu4c/source/common/unicode/unistr.h index b99a686126..5920d3fdd1 100644 --- a/icu4c/source/common/unicode/unistr.h +++ b/icu4c/source/common/unicode/unistr.h @@ -1892,7 +1892,7 @@ public: UnicodeString &fastCopyFrom(const UnicodeString &src); /** - * Move assignment operator, might leave src in bogus state. + * Move assignment operator; might leave src in bogus state. * This string will have the same contents and state that the source string had. * The behavior is undefined if *this and src are the same object. * @param src source string @@ -1905,7 +1905,7 @@ public: // do not use #ifndef U_HIDE_DRAFT_API for moveFrom, needed by non-draft API /** - * Move assignment, might leave src in bogus state. + * Move assignment; might leave src in bogus state. * This string will have the same contents and state that the source string had. * The behavior is undefined if *this and src are the same object. * @@ -3350,7 +3350,7 @@ public: UnicodeString(const UnicodeString& that); /** - * Move constructor, might leave src in bogus state. + * Move constructor; might leave src in bogus state. * This string will have the same contents and state that the source string had. * @param src source string * @stable ICU 56 diff --git a/icu4c/source/test/intltest/strtest.cpp b/icu4c/source/test/intltest/strtest.cpp index d8fd7a0042..b95b525296 100644 --- a/icu4c/source/test/intltest/strtest.cpp +++ b/icu4c/source/test/intltest/strtest.cpp @@ -551,6 +551,28 @@ StringTest::TestCharString() { if (chStr.length() != 0) { errln("%s:%d expected length() = 0, got %d", __FILE__, __LINE__, chStr.length()); } + + { + CharString s1("Short string", errorCode); + CharString s2(std::move(s1)); + assertEquals("s2 should have content of s1", "Short string", s2.data()); + CharString s3("Dummy", errorCode); + s3 = std::move(s2); + assertEquals("s3 should have content of s2", "Short string", s3.data()); + } + + { + CharString s1("Long string over 40 characters to trigger heap allocation", errorCode); + CharString s2(std::move(s1)); + assertEquals("s2 should have content of s1", + "Long string over 40 characters to trigger heap allocation", + s2.data()); + CharString s3("Dummy string with over 40 characters to trigger heap allocation", errorCode); + s3 = std::move(s2); + assertEquals("s3 should have content of s2", + "Long string over 40 characters to trigger heap allocation", + s3.data()); + } } void From 9ae7e8eba17f23cdb9d27167d9862d320c7c8afe Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Wed, 28 Feb 2018 03:42:32 +0000 Subject: [PATCH 019/129] ICU-13084 Updating set of ignorable control characters to [:DI:]. X-SVN-Rev: 41002 --- icu4c/source/i18n/numparse_unisets.cpp | 5 ++-- .../number/parse/UnicodeSetStaticCache.java | 5 ++-- .../src/com/ibm/icu/text/DecimalFormat.java | 3 ++- .../icu/dev/test/format/NumberFormatTest.java | 23 ++++++++++++++++++- 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/icu4c/source/i18n/numparse_unisets.cpp b/icu4c/source/i18n/numparse_unisets.cpp index fc0274f2a3..0a8ec2bebb 100644 --- a/icu4c/source/i18n/numparse_unisets.cpp +++ b/icu4c/source/i18n/numparse_unisets.cpp @@ -63,8 +63,9 @@ void U_CALLCONV initNumberParseUniSets(UErrorCode& status) { gUnicodeSets[EMPTY] = new UnicodeSet(); - // BiDi characters are skipped over and ignored at any point in the string, even in strict mode. - gUnicodeSets[BIDI] = new UnicodeSet(u"[[\\u200E\\u200F\\u061C]]", status); + // These characters are skipped over and ignored at any point in the string, even in strict mode. + // See ticket #13084. + gUnicodeSets[BIDI] = new UnicodeSet(u"[[:DI:]]", status); // This set was decided after discussion with icu-design@. See ticket #13309. // Zs+TAB is "horizontal whitespace" according to UTS #18 (blank property). diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/UnicodeSetStaticCache.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/UnicodeSetStaticCache.java index 5ab7081704..edc0e99114 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/UnicodeSetStaticCache.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/UnicodeSetStaticCache.java @@ -81,8 +81,9 @@ public class UnicodeSetStaticCache { } static { - // BiDi characters are skipped over and ignored at any point in the string, even in strict mode. - unicodeSets.put(Key.BIDI, new UnicodeSet("[[\\u200E\\u200F\\u061C]]").freeze()); + // These characters are skipped over and ignored at any point in the string, even in strict mode. + // See ticket #13084. + unicodeSets.put(Key.BIDI, new UnicodeSet("[[:DI:]]").freeze()); // This set was decided after discussion with icu-design@. See ticket #13309. // Zs+TAB is "horizontal whitespace" according to UTS #18 (blank property). diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java index 5f68fe6046..37e4064666 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java @@ -198,7 +198,7 @@ import com.ibm.icu.util.ULocale.Category; * example, a formatter instance gotten from NumberFormat.getInstance(ULocale, * NumberFormat.CURRENCYSTYLE) can parse both "USD1.00" and "3.00 US dollars". * - *

Whitespace characters (lenient mode) and bidi control characters (lenient and strict mode), + *

Whitespace characters (lenient mode) and control characters (lenient and strict mode), * collectively called "ignorables", do not need to match in identity or quantity between the * pattern string and the input string. For example, the pattern "# %" matches "35 %" (with a single * space), "35%" (with no space), "35 %" (with a non-breaking space), and "35  %" (with @@ -206,6 +206,7 @@ import com.ibm.icu.util.ULocale.Category; * number: prefix, number, exponent separator, and suffix. Ignorable whitespace characters are those * having the Unicode "blank" property for regular expressions, defined in UTS #18 Annex C, which is * "horizontal" whitespace, like spaces and tabs, but not "vertical" whitespace, like line breaks. + * Ignorable control characters are those in the Unicode set [:Default_Ignorable_Code_Point:]. * *

If {@link #parse(String, ParsePosition)} fails to parse a string, it returns null * and leaves the parse position unchanged. The convenience method {@link #parse(String)} indicates diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java index 4697abd5da..370a843f51 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java @@ -1722,11 +1722,32 @@ public class NumberFormatTest extends TestFmwk { // Test all characters in the UTS 18 "blank" set stated in the API docstring. UnicodeSet blanks = new UnicodeSet("[[:Zs:][\\u0009]]").freeze(); for (String space : blanks) { - String str = "a " + space + " b1234"; + String str = "a " + space + " b1234c "; + expect(fmt, str, n); + } + + // Arbitrary whitespace is not accepted in strict mode. + fmt.setParseStrict(true); + for (String space : blanks) { + String str = "a " + space + " b1234c "; + expectParseException(fmt, str, n); + } + + // Test default ignorable characters. These should work in both lenient and strict. + UnicodeSet defaultIgnorables = new UnicodeSet("[[:Default_Ignorable_Code_Point:]]").freeze(); + fmt.setParseStrict(false); + for (String ignorable : defaultIgnorables) { + String str = "a b " + ignorable + "1234c "; + expect(fmt, str, n); + } + fmt.setParseStrict(true); + for (String ignorable : defaultIgnorables) { + String str = "a b " + ignorable + "1234c "; expect(fmt, str, n); } // Test that other whitespace characters do not work + fmt.setParseStrict(false); UnicodeSet otherWhitespace = new UnicodeSet("[[:whitespace:]]").removeAll(blanks).freeze(); for (String space : otherWhitespace) { String str = "a " + space + " b1234"; From 19270f5476c37485eabd82a3897f10964292ce7e Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Wed, 28 Feb 2018 05:48:40 +0000 Subject: [PATCH 020/129] ICU-13574 Deleting file that should have been deleted by merge X-SVN-Rev: 41005 --- .../icu/impl/number/parse/StringSegment.java | 195 ------------------ 1 file changed, 195 deletions(-) delete mode 100644 icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/StringSegment.java diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/StringSegment.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/StringSegment.java deleted file mode 100644 index 31a9c62dfb..0000000000 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/StringSegment.java +++ /dev/null @@ -1,195 +0,0 @@ -// © 2017 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html#License -package com.ibm.icu.impl.number.parse; - -import com.ibm.icu.lang.UCharacter; -import com.ibm.icu.text.UnicodeSet; - -/** - * A mutable class allowing for a String with a variable offset and length. The charAt, length, and - * subSequence methods all operate relative to the fixed offset into the String. - * - * TODO: Make sure that this operates only on code point boundaries. - * - * @author sffc - */ -public class StringSegment implements CharSequence { - private final String str; - private int start; - private int end; - private boolean foldCase; - - public StringSegment(String str, int parseFlags) { - this.str = str; - this.start = 0; - this.end = str.length(); - this.foldCase = 0 != (parseFlags & ParsingUtils.PARSE_FLAG_IGNORE_CASE); - } - - public int getOffset() { - return start; - } - - public void setOffset(int start) { - assert start <= end; - this.start = start; - } - - /** - * Equivalent to setOffset(getOffset()+delta). - * - *

- * This method is usually called by a Matcher to register that a char was consumed. If the char is - * strong (it usually is, except for things like whitespace), follow this with a call to - * {@link ParsedNumber#setCharsConsumed}. For more information on strong chars, see that method. - */ - public void adjustOffset(int delta) { - assert start + delta >= 0; - assert start + delta <= end; - start += delta; - } - - /** - * Adjusts the offset by the width of the current code point, either 1 or 2 chars. - */ - public void adjustOffsetByCodePoint() { - start += Character.charCount(getCodePoint()); - } - - public void setLength(int length) { - assert length >= 0; - assert start + length <= str.length(); - end = start + length; - } - - public void resetLength() { - end = str.length(); - } - - @Override - public int length() { - return end - start; - } - - @Override - public char charAt(int index) { - return str.charAt(index + start); - } - - public int codePointAt(int index) { - return str.codePointAt(index + start); - } - - @Override - public CharSequence subSequence(int start, int end) { - throw new AssertionError(); // Never used - // Possible implementation: - // return str.subSequence(start + this.start, end + this.start); - } - - /** - * Returns the first code point in the string segment, or -1 if the string starts with an invalid - * code point. - * - *

- * Important: Most of the time, you should use {@link #matches}, which handles case - * folding logic, instead of this method. - */ - public int getCodePoint() { - assert start < end; - char lead = str.charAt(start); - char trail; - if (Character.isHighSurrogate(lead) - && start + 1 < end - && Character.isLowSurrogate(trail = str.charAt(start + 1))) { - return Character.toCodePoint(lead, trail); - } - return lead; - } - - /** - * Returns true if the first code point of this StringSegment equals the given code point. - * - *

- * This method will perform case folding if case folding is enabled for the parser. - */ - public boolean matches(int otherCp) { - return codePointsEqual(getCodePoint(), otherCp, foldCase); - } - - /** - * Returns true if the first code point of this StringSegment is in the given UnicodeSet. - */ - public boolean matches(UnicodeSet uniset) { - // TODO: Move UnicodeSet case-folding logic here. - // TODO: Handle string matches here instead of separately. - int cp = getCodePoint(); - if (cp == -1) { - return false; - } - return uniset.contains(cp); - } - - /** - * Returns the length of the prefix shared by this StringSegment and the given CharSequence. For - * example, if this string segment is "aab", and the char sequence is "aac", this method returns 2, - * since the first 2 characters are the same. - * - *

- * This method will perform case folding if case folding is enabled for the parser. - */ - public int getCommonPrefixLength(CharSequence other) { - return getPrefixLengthInternal(other, foldCase); - } - - /** - * Like {@link #getCommonPrefixLength}, but never performs case folding, even if case folding is - * enabled for the parser. - */ - public int getCaseSensitivePrefixLength(CharSequence other) { - return getPrefixLengthInternal(other, false); - } - - private int getPrefixLengthInternal(CharSequence other, boolean foldCase) { - int offset = 0; - for (; offset < Math.min(length(), other.length());) { - // TODO: case-fold code points, not chars - char c1 = charAt(offset); - char c2 = other.charAt(offset); - if (!codePointsEqual(c1, c2, foldCase)) { - break; - } - offset++; - } - return offset; - } - - // /** - // * Case-folds the string if IGNORE_CASE flag is set; otherwise, returns the same string. - // */ - // public static String maybeFold(String input, int parseFlags) { - // UnicodeSet cwcf = UnicodeSetStaticCache.get(UnicodeSetStaticCache.Key.CWCF); - // if (0 != (parseFlags & ParsingUtils.PARSE_FLAG_IGNORE_CASE) && cwcf.containsSome(input)) { - // return UCharacter.foldCase(input, true); - // } else { - // return input; - // } - // } - - private static final boolean codePointsEqual(int cp1, int cp2, boolean foldCase) { - if (cp1 == cp2) { - return true; - } - if (!foldCase) { - return false; - } - cp1 = UCharacter.foldCase(cp1, true); - cp2 = UCharacter.foldCase(cp2, true); - return cp1 == cp2; - } - - @Override - public String toString() { - return str.substring(0, start) + "[" + str.substring(start, end) + "]" + str.substring(end); - } -} From f133914b97bc5d2a6ba5d6a19354a6266047c0fb Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Wed, 28 Feb 2018 05:52:41 +0000 Subject: [PATCH 021/129] ICU-13574 Fixing build errors after merge. X-SVN-Rev: 41007 --- .../classes/core/src/com/ibm/icu/impl/StringSegment.java | 7 +++++++ .../ibm/icu/impl/number/parse/CurrencyCustomMatcher.java | 1 + .../ibm/icu/impl/number/parse/CurrencyNamesMatcher.java | 1 + .../src/com/ibm/icu/dev/test/number/NumberParserTest.java | 4 ++-- 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/StringSegment.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/StringSegment.java index 138abe8239..5a1f6bd62c 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/StringSegment.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/StringSegment.java @@ -106,6 +106,13 @@ public class StringSegment implements CharSequence { return lead; } + /** + * Returns the code point at the given index relative to the current offset. + */ + public int codePointAt(int index) { + return str.codePointAt(start + index); + } + /** * Returns true if the first code point of this StringSegment equals the given code point. * diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyCustomMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyCustomMatcher.java index 3df201889b..d008a0686c 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyCustomMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyCustomMatcher.java @@ -2,6 +2,7 @@ // License & terms of use: http://www.unicode.org/copyright.html#License package com.ibm.icu.impl.number.parse; +import com.ibm.icu.impl.StringSegment; import com.ibm.icu.text.UnicodeSet; import com.ibm.icu.util.Currency; import com.ibm.icu.util.ULocale; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyNamesMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyNamesMatcher.java index 9fdef22504..0fcadbb9cb 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyNamesMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyNamesMatcher.java @@ -4,6 +4,7 @@ package com.ibm.icu.impl.number.parse; import java.util.Iterator; +import com.ibm.icu.impl.StringSegment; import com.ibm.icu.impl.TextTrieMap; import com.ibm.icu.text.UnicodeSet; import com.ibm.icu.util.Currency; diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java index cd6915c2eb..a522a28997 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java @@ -249,7 +249,7 @@ public class NumberParserTest { String input = (String) cas[0]; String expectedCurrencyCode = (String) cas[1]; - StringSegment segment = new StringSegment(input, 0); + StringSegment segment = new StringSegment(input, true); ParsedNumber result = new ParsedNumber(); matcher.match(segment, result); assertEquals("Parsing " + input, expectedCurrencyCode, result.currencyCode); @@ -289,7 +289,7 @@ public class NumberParserTest { assertEquals(affixPattern + " " + exactMatch, expectedMatcherLength, matcher.length()); // Check that the matcher works on a sample string - StringSegment segment = new StringSegment(sampleParseableString, 0); + StringSegment segment = new StringSegment(sampleParseableString, true); ParsedNumber result = new ParsedNumber(); matcher.match(segment, result); assertEquals(affixPattern + " " + exactMatch, From abb8788d23d2042aecc2ec553239abc213d94119 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Wed, 28 Feb 2018 08:06:42 +0000 Subject: [PATCH 022/129] ICU-8610 Adds basic support for number skeletons. Includes skeleton support for rounding strategy. X-SVN-Rev: 41013 --- .../com/ibm/icu/number/NumberFormatter.java | 14 + .../icu/number/NumberFormatterSettings.java | 12 + .../ibm/icu/number/NumberSkeletonImpl.java | 490 ++++++++++++++++++ .../core/src/com/ibm/icu/number/Rounder.java | 7 +- .../icu/number/SkeletonSyntaxException.java | 20 + .../test/number/NumberFormatterApiTest.java | 68 +-- .../dev/test/number/NumberSkeletonTest.java | 70 +++ 7 files changed, 648 insertions(+), 33 deletions(-) create mode 100644 icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java create mode 100644 icu4j/main/classes/core/src/com/ibm/icu/number/SkeletonSyntaxException.java create mode 100644 icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java 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 dd7651bd82..552873fc31 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 @@ -453,6 +453,20 @@ public final class NumberFormatter { return BASE.locale(locale); } + /** + * Call this method at the beginning of a NumberFormatter fluent chain to create an instance based + * on a given number skeleton string. + * + * @param skeleton + * The skeleton string off of which to base this NumberFormatter. + * @return An {@link UnlocalizedNumberFormatter}, to be used for chaining. + * @draft ICU 62 + * @provisional This API might change or be removed in a future release. + */ + public static UnlocalizedNumberFormatter fromSkeleton(String skeleton) { + return NumberSkeletonImpl.getOrCreate(skeleton); + } + /** * @internal * @deprecated ICU 60 This API is ICU internal only. diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java index 0143f2e48b..22175dfd35 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java @@ -475,6 +475,18 @@ public abstract class NumberFormatterSettings stemsToTypes; + final Map stemsToValues; + final Map valuesToStems; + + SkeletonDataStructure() { + stemsToTypes = new HashMap(); + stemsToValues = new HashMap(); + valuesToStems = new HashMap(); + } + + public void put(StemType stemType, String content, Object value) { + stemsToTypes.put(content, stemType); + stemsToValues.put(content, value); + valuesToStems.put(value, content); + } + + public StemType stemToType(CharSequence content) { + return stemsToTypes.get(content); + } + + public Object stemToValue(CharSequence content) { + return stemsToValues.get(content); + } + + public String valueToStem(Object value) { + return valuesToStems.get(value); + } + } + + static final SkeletonDataStructure skeletonData = new SkeletonDataStructure(); + + static { + skeletonData.put(StemType.ROUNDER, "round-integer", Rounder.integer()); + skeletonData.put(StemType.ROUNDER, "round-unlimited", Rounder.unlimited()); + skeletonData.put(StemType.ROUNDER, + "round-currency-standard", + Rounder.currency(CurrencyUsage.STANDARD)); + skeletonData.put(StemType.ROUNDER, "round-currency-cash", Rounder.currency(CurrencyUsage.CASH)); + } + + private static final Map cache = new ConcurrentHashMap(); + + /** + * Gets the number formatter for the given number skeleton string from the cache, creating it if it + * does not exist in the cache. + * + * @param skeletonString + * A number skeleton string, possibly not in its shortest form. + * @return An UnlocalizedNumberFormatter with behavior defined by the given skeleton string. + */ + public static UnlocalizedNumberFormatter getOrCreate(String skeletonString) { + String unNormalized = skeletonString; // more appropriate variable name for the implementation + + // First try: look up the un-normalized skeleton. + UnlocalizedNumberFormatter formatter = cache.get(unNormalized); + if (formatter != null) { + return formatter; + } + + // Second try: normalize the skeleton, and then access the cache. + // Store the un-normalized form for a faster lookup next time. + // Synchronize because we need a transaction with multiple queries to the cache. + String normalized = normalizeSkeleton(unNormalized); + if (cache.containsKey(normalized)) { + synchronized (cache) { + formatter = cache.get(normalized); + if (formatter != null) { + cache.putIfAbsent(unNormalized, formatter); + } + } + } + if (formatter != null) { + return formatter; + } + + // Third try: create the formatter, store it in the cache, and return it. + formatter = create(normalized); + + // Synchronize because we need a transaction with multiple queries to the cache. + synchronized (cache) { + if (cache.containsKey(normalized)) { + formatter = cache.get(normalized); + } else { + cache.put(normalized, formatter); + } + cache.putIfAbsent(unNormalized, formatter); + } + return formatter; + } + + /** + * Creates a NumberFormatter corresponding to the given skeleton string. + * + * @param skeletonString + * A number skeleton string, possibly not in its shortest form. + * @return An UnlocalizedNumberFormatter with behavior defined by the given skeleton string. + */ + public static UnlocalizedNumberFormatter create(String skeletonString) { + MacroProps macros = parseSkeleton(skeletonString); + return NumberFormatter.with().macros(macros); + } + + public static String generate(MacroProps macros) { + StringBuilder sb = new StringBuilder(); + generateSkeleton(macros, sb); + return sb.toString(); + } + + /** + * Normalizes a number skeleton string to the shortest equivalent form. + * + * @param skeletonString + * A number skeleton string, possibly not in its shortest form. + * @return An equivalent and possibly simplified skeleton string. + */ + public static String normalizeSkeleton(String skeletonString) { + // FIXME + return skeletonString; + } + + ///// + + private static MacroProps parseSkeleton(String skeletonString) { + MacroProps macros = new MacroProps(); + StringSegment segment = new StringSegment(skeletonString + " ", false); + StemType stem = null; + int offset = 0; + while (offset < segment.length()) { + int cp = segment.codePointAt(offset); + boolean isWhiteSpace = PatternProps.isWhiteSpace(cp); + if (offset > 0 && (isWhiteSpace || cp == '/')) { + segment.setLength(offset); + if (stem == null) { + stem = parseStem(segment, macros); + } else { + stem = parseOption(stem, segment, macros); + } + segment.resetLength(); + segment.adjustOffset(offset + 1); + offset = 0; + } else { + offset += Character.charCount(cp); + } + if (isWhiteSpace && stem != null) { + // Check for stems that require an option + switch (stem) { + case MAYBE_INCREMENT_ROUNDER: + throw new SkeletonSyntaxException("Stem requires an option", segment); + default: + break; + } + stem = null; + } + } + assert stem == null; + return macros; + } + + private static StemType parseStem(CharSequence content, MacroProps macros) { + // First try: exact match with a literal stem + StemType stem = skeletonData.stemToType(content); + if (stem != null) { + Object value = skeletonData.stemToValue(content); + switch (stem) { + case ROUNDER: + checkNull(macros.rounder, content); + macros.rounder = (Rounder) value; + break; + default: + assert false; + } + return stem; + } + + // Second try: literal stems that require an option + if (content.equals("round-increment")) { + return StemType.MAYBE_INCREMENT_ROUNDER; + } + + // Second try: stem "blueprint" syntax + switch (content.charAt(0)) { + case '.': + stem = StemType.FRACTION_ROUNDER; + parseFractionStem(content, macros); + break; + case '@': + stem = StemType.ROUNDER; + parseDigitsStem(content, macros); + break; + } + if (stem != null) { + return stem; + } + + // Still no hits: throw an exception + throw new SkeletonSyntaxException("Unknown stem", content); + } + + private static StemType parseOption(StemType stem, CharSequence content, MacroProps macros) { + // Frac-sig option + switch (stem) { + case FRACTION_ROUNDER: + if (parseFracSigOption(content, macros)) { + return StemType.ROUNDER; + } + } + + // Increment option + switch (stem) { + case MAYBE_INCREMENT_ROUNDER: + // The increment option is required. + parseIncrementOption(content, macros); + return StemType.ROUNDER; + } + + // Rounding mode option + switch (stem) { + case ROUNDER: + case FRACTION_ROUNDER: + case CURRENCY_ROUNDER: + if (parseRoundingModeOption(content, macros)) { + break; + } + } + + // Unknown option + throw new SkeletonSyntaxException("Unknown option", content); + } + + ///// + + private static void generateSkeleton(MacroProps macros, StringBuilder sb) { + if (macros.rounder != null) { + generateRoundingValue(macros, sb); + sb.append(' '); + } + + // Remove the trailing space + if (sb.length() > 0) { + sb.setLength(sb.length() - 1); + } + } + + ///// + + private static void parseFractionStem(CharSequence content, MacroProps macros) { + assert content.charAt(0) == '.'; + int offset = 1; + int minFrac = 0; + int maxFrac; + for (; offset < content.length(); offset++) { + if (content.charAt(offset) == '0') { + minFrac++; + } else { + break; + } + } + if (offset < content.length()) { + if (content.charAt(offset) == '+') { + maxFrac = -1; + offset++; + } else { + maxFrac = minFrac; + for (; offset < content.length(); offset++) { + if (content.charAt(offset) == '#') { + maxFrac++; + } else { + break; + } + } + } + } else { + maxFrac = minFrac; + } + if (offset < content.length()) { + throw new SkeletonSyntaxException("Invalid fraction stem", content); + } + // Use the public APIs to enforce bounds checking + if (maxFrac == -1) { + macros.rounder = Rounder.minFraction(minFrac); + } else { + macros.rounder = Rounder.minMaxFraction(minFrac, maxFrac); + } + } + + private static void generateFractionStem(int minFrac, int maxFrac, StringBuilder sb) { + if (minFrac == 0 && maxFrac == 0) { + sb.append("round-integer"); + return; + } + sb.append('.'); + appendMultiple(sb, '0', minFrac); + if (maxFrac == -1) { + sb.append('+'); + } else { + appendMultiple(sb, '#', maxFrac - minFrac); + } + } + + private static void parseDigitsStem(CharSequence content, MacroProps macros) { + assert content.charAt(0) == '@'; + int offset = 0; + int minSig = 0; + int maxSig; + for (; offset < content.length(); offset++) { + if (content.charAt(offset) == '@') { + minSig++; + } else { + break; + } + } + if (offset < content.length()) { + if (content.charAt(offset) == '+') { + maxSig = -1; + offset++; + } else { + maxSig = minSig; + for (; offset < content.length(); offset++) { + if (content.charAt(offset) == '#') { + maxSig++; + } else { + break; + } + } + } + } else { + maxSig = minSig; + } + if (offset < content.length()) { + throw new SkeletonSyntaxException("Invalid significant digits stem", content); + } + // Use the public APIs to enforce bounds checking + if (maxSig == -1) { + macros.rounder = Rounder.minDigits(minSig); + } else { + macros.rounder = Rounder.minMaxDigits(minSig, maxSig); + } + } + + private static void generateDigitsStem(int minSig, int maxSig, StringBuilder sb) { + appendMultiple(sb, '@', minSig); + if (maxSig == -1) { + sb.append('+'); + } else { + appendMultiple(sb, '#', maxSig - minSig); + } + } + + private static boolean parseFracSigOption(CharSequence content, MacroProps macros) { + if (content.charAt(0) != '@') { + return false; + } + FractionRounder oldRounder = (FractionRounder) macros.rounder; + // A little bit of a hack: parse the option as a digits stem, and extract the min/max sig from + // the new Rounder saved into the macros + parseDigitsStem(content, macros); + Rounder.SignificantRounderImpl intermediate = (Rounder.SignificantRounderImpl) macros.rounder; + if (intermediate.maxSig == -1) { + macros.rounder = oldRounder.withMinDigits(intermediate.minSig); + } else { + macros.rounder = oldRounder.withMaxDigits(intermediate.maxSig); + } + return true; + } + + private static void parseIncrementOption(CharSequence content, MacroProps macros) { + // Clunkilly convert the CharSequence to a char array for the BigDecimal constructor. + // We can't use content.toString() because that doesn't create a clean string. + char[] chars = new char[content.length()]; + for (int i = 0; i < content.length(); i++) { + chars[i] = content.charAt(i); + } + BigDecimal increment; + try { + increment = new BigDecimal(chars); + } catch (NumberFormatException e) { + throw new SkeletonSyntaxException("Invalid rounding increment", content, e); + } + macros.rounder = Rounder.increment(increment); + } + + private static void generateIncrementOption(BigDecimal increment, StringBuilder sb) { + sb.append(increment.toPlainString()); + } + + private static boolean parseRoundingModeOption(CharSequence content, MacroProps macros) { + // Iterate over int modes instead of enum modes for performance + for (int rm = 0; rm <= BigDecimal.ROUND_UNNECESSARY; rm++) { + RoundingMode mode = RoundingMode.valueOf(rm); + if (content.equals(mode.toString())) { + macros.rounder = macros.rounder.withMode(mode); + return true; + } + } + return false; + } + + private static void generateRoundingModeOption(RoundingMode mode, StringBuilder sb) { + sb.append(mode.toString()); + } + + ///// + + private static void generateRoundingValue(MacroProps macros, StringBuilder sb) { + // Check for literals + String literal = skeletonData.valueToStem(macros.rounder); + if (literal != null) { + sb.append(literal); + return; + } + + // Generate the stem + if (macros.rounder instanceof Rounder.InfiniteRounderImpl) { + sb.append("round-unlimited"); + } else if (macros.rounder instanceof Rounder.FractionRounderImpl) { + Rounder.FractionRounderImpl impl = (Rounder.FractionRounderImpl) macros.rounder; + generateFractionStem(impl.minFrac, impl.maxFrac, sb); + } else if (macros.rounder instanceof Rounder.SignificantRounderImpl) { + Rounder.SignificantRounderImpl impl = (Rounder.SignificantRounderImpl) macros.rounder; + generateDigitsStem(impl.minSig, impl.maxSig, sb); + } else if (macros.rounder instanceof Rounder.FracSigRounderImpl) { + Rounder.FracSigRounderImpl impl = (Rounder.FracSigRounderImpl) macros.rounder; + generateFractionStem(impl.minFrac, impl.maxFrac, sb); + sb.append('/'); + if (impl.minSig == -1) { + generateDigitsStem(1, impl.maxSig, sb); + } else { + generateDigitsStem(impl.minSig, -1, sb); + } + } else if (macros.rounder instanceof Rounder.IncrementRounderImpl) { + Rounder.IncrementRounderImpl impl = (Rounder.IncrementRounderImpl) macros.rounder; + sb.append("round-increment/"); + generateIncrementOption(impl.increment, sb); + } else { + assert macros.rounder instanceof Rounder.CurrencyRounderImpl; + Rounder.CurrencyRounderImpl impl = (Rounder.CurrencyRounderImpl) macros.rounder; + if (impl.usage == CurrencyUsage.STANDARD) { + sb.append("round-currency-standard"); + } else { + sb.append("round-currency-cash"); + } + } + + // Generate the options + if (macros.rounder.mathContext != Rounder.DEFAULT_MATH_CONTEXT) { + sb.append('/'); + generateRoundingModeOption(macros.rounder.mathContext.getRoundingMode(), sb); + } + } + + ///// + + private static void checkNull(Object value, CharSequence content) { + if (value != null) { + throw new SkeletonSyntaxException("Duplicated setting", content); + } + } + + private static void appendMultiple(StringBuilder sb, int cp, int count) { + for (int i = 0; i < count; i++) { + sb.appendCodePoint(cp); + } + } +} diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/Rounder.java b/icu4j/main/classes/core/src/com/ibm/icu/number/Rounder.java index 783adf35c5..fbae054d47 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/Rounder.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/Rounder.java @@ -26,8 +26,11 @@ public abstract class Rounder implements Cloneable { /* package-private final */ MathContext mathContext; + /* package-private */ static final MathContext DEFAULT_MATH_CONTEXT = RoundingUtils + .mathContextUnlimited(RoundingUtils.DEFAULT_ROUNDING_MODE); + /* package-private */ Rounder() { - mathContext = RoundingUtils.mathContextUnlimited(RoundingUtils.DEFAULT_ROUNDING_MODE); + mathContext = DEFAULT_MATH_CONTEXT; } /** @@ -245,7 +248,7 @@ public abstract class Rounder implements Cloneable { */ public static Rounder maxDigits(int maxSignificantDigits) { if (maxSignificantDigits >= 1 && maxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) { - return constructSignificant(0, maxSignificantDigits); + return constructSignificant(1, maxSignificantDigits); } else { throw new IllegalArgumentException("Significant digits must be between 1 and " + RoundingUtils.MAX_INT_FRAC_SIG diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/SkeletonSyntaxException.java b/icu4j/main/classes/core/src/com/ibm/icu/number/SkeletonSyntaxException.java new file mode 100644 index 0000000000..92ba548eb4 --- /dev/null +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/SkeletonSyntaxException.java @@ -0,0 +1,20 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html#License +package com.ibm.icu.number; + +/** + * Exception used for illegal number skeleton strings. + * + * @author sffc + */ +public class SkeletonSyntaxException extends IllegalArgumentException { + private static final long serialVersionUID = 7733971331648360554L; + + public SkeletonSyntaxException(String message, CharSequence token) { + super("Syntax error in skeleton string: " + message + ": " + token); + } + + public SkeletonSyntaxException(String message, CharSequence token, Throwable cause) { + super("Syntax error in skeleton string: " + message + ": " + token, cause); + } +} 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 ec4ff7e0fc..d6c8a8c552 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 @@ -770,7 +770,7 @@ public class NumberFormatterApiTest { public void roundingFraction() { assertFormatDescending( "Integer", - "F0", + "round-integer", NumberFormatter.with().rounding(Rounder.integer()), ULocale.ENGLISH, "87,650", @@ -785,7 +785,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Fixed Fraction", - "F3", + ".000", NumberFormatter.with().rounding(Rounder.fixedFraction(3)), ULocale.ENGLISH, "87,650.000", @@ -800,7 +800,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Min Fraction", - "F1-", + ".0+", NumberFormatter.with().rounding(Rounder.minFraction(1)), ULocale.ENGLISH, "87,650.0", @@ -815,7 +815,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Max Fraction", - "F-1", + ".#", NumberFormatter.with().rounding(Rounder.maxFraction(1)), ULocale.ENGLISH, "87,650", @@ -830,7 +830,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Min/Max Fraction", - "F1-3", + ".0##", NumberFormatter.with().rounding(Rounder.minMaxFraction(1, 3)), ULocale.ENGLISH, "87,650.0", @@ -848,7 +848,7 @@ public class NumberFormatterApiTest { public void roundingFigures() { assertFormatSingle( "Fixed Significant", - "S3", + "@@@", NumberFormatter.with().rounding(Rounder.fixedDigits(3)), ULocale.ENGLISH, -98, @@ -856,7 +856,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Fixed Significant Rounding", - "S3", + "@@@", NumberFormatter.with().rounding(Rounder.fixedDigits(3)), ULocale.ENGLISH, -98.7654321, @@ -864,7 +864,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Fixed Significant Zero", - "S3", + "@@@", NumberFormatter.with().rounding(Rounder.fixedDigits(3)), ULocale.ENGLISH, 0, @@ -872,7 +872,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Min Significant", - "S2-", + "@@+", NumberFormatter.with().rounding(Rounder.minDigits(2)), ULocale.ENGLISH, -9, @@ -880,7 +880,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Max Significant", - "S-4", + "@###", NumberFormatter.with().rounding(Rounder.maxDigits(4)), ULocale.ENGLISH, 98.7654321, @@ -888,7 +888,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Min/Max Significant", - "S3-4", + "@@@#", NumberFormatter.with().rounding(Rounder.minMaxDigits(3, 4)), ULocale.ENGLISH, 9.99999, @@ -899,7 +899,7 @@ public class NumberFormatterApiTest { public void roundingFractionFigures() { assertFormatDescending( "Basic Significant", // for comparison - "S-2", + "@#", NumberFormatter.with().rounding(Rounder.maxDigits(2)), ULocale.ENGLISH, "88,000", @@ -914,7 +914,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "FracSig minMaxFrac minSig", - "F1-2>3", + ".0#/@@@+", NumberFormatter.with().rounding(Rounder.minMaxFraction(1, 2).withMinDigits(3)), ULocale.ENGLISH, "87,650.0", @@ -929,7 +929,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "FracSig minMaxFrac maxSig A", - "F1-3<2", + ".0##/@#", NumberFormatter.with().rounding(Rounder.minMaxFraction(1, 3).withMaxDigits(2)), ULocale.ENGLISH, "88,000.0", // maxSig beats maxFrac @@ -944,7 +944,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "FracSig minMaxFrac maxSig B", - "F2<2", + ".00/@#", NumberFormatter.with().rounding(Rounder.fixedFraction(2).withMaxDigits(2)), ULocale.ENGLISH, "88,000.00", // maxSig beats maxFrac @@ -959,7 +959,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "FracSig with trailing zeros A", - "", + ".00/@@@+", NumberFormatter.with().rounding(Rounder.fixedFraction(2).withMinDigits(3)), ULocale.ENGLISH, 0.1, @@ -967,7 +967,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "FracSig with trailing zeros B", - "", + ".00/@@@+", NumberFormatter.with().rounding(Rounder.fixedFraction(2).withMinDigits(3)), ULocale.ENGLISH, 0.0999999, @@ -978,7 +978,7 @@ public class NumberFormatterApiTest { public void roundingOther() { assertFormatDescending( "Rounding None", - "Y", + "round-unlimited", NumberFormatter.with().rounding(Rounder.unlimited()), ULocale.ENGLISH, "87,650", @@ -993,7 +993,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Increment", - "M0.5", + "round-increment/0.5", NumberFormatter.with().rounding(Rounder.increment(BigDecimal.valueOf(0.5))), ULocale.ENGLISH, "87,650.0", @@ -1008,7 +1008,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Increment with Min Fraction", - "M0.5", + "round-increment/0.50", NumberFormatter.with().rounding(Rounder.increment(new BigDecimal("0.50"))), ULocale.ENGLISH, "87,650.00", @@ -1023,7 +1023,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Currency Standard", - "$CZK GSTANDARD", + "round-currency-standard", NumberFormatter.with().rounding(Rounder.currency(CurrencyUsage.STANDARD)).unit(CZK), ULocale.ENGLISH, "CZK 87,650.00", @@ -1038,7 +1038,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Currency Cash", - "$CZK GCASH", + "round-currency-cash", NumberFormatter.with().rounding(Rounder.currency(CurrencyUsage.CASH)).unit(CZK), ULocale.ENGLISH, "CZK 87,650", @@ -1053,7 +1053,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Currency Cash with Nickel Rounding", - "$CAD GCASH", + "round-currency-cash", NumberFormatter.with().rounding(Rounder.currency(CurrencyUsage.CASH)).unit(CAD), ULocale.ENGLISH, "CA$87,650.00", @@ -1068,7 +1068,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Currency not in top-level fluent chain", - "F0", + "round-currency-cash/CZK", NumberFormatter.with().rounding(Rounder.currency(CurrencyUsage.CASH).withCurrency(CZK)), ULocale.ENGLISH, "87,650", @@ -1083,7 +1083,7 @@ public class NumberFormatterApiTest { // NOTE: Other tests cover the behavior of the other rounding modes. assertFormatDescending( - "Rounding Mode CEILING", + "round-integer/CEILING", "", NumberFormatter.with().rounding(Rounder.integer().withMode(RoundingMode.CEILING)), ULocale.ENGLISH, @@ -2036,16 +2036,18 @@ public class NumberFormatterApiTest { double[] inputs, String... expected) { assert expected.length == 9; - // TODO: Add a check for skeleton. - // assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton()); + assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton()); LocalizedNumberFormatter l1 = f.threshold(0L).locale(locale); // no self-regulation LocalizedNumberFormatter l2 = f.threshold(1L).locale(locale); // all self-regulation + LocalizedNumberFormatter l3 = NumberFormatter.fromSkeleton(skeleton).locale(locale); for (int i = 0; i < 9; i++) { double d = inputs[i]; String actual1 = l1.format(d).toString(); assertEquals(message + ": Unsafe Path: " + d, expected[i], actual1); String actual2 = l2.format(d).toString(); assertEquals(message + ": Safe Path: " + d, expected[i], actual2); + String actual3 = l3.format(d).toString(); + assertEquals(message + ": Skeleton Path: " + d, expected[i], actual3); } } @@ -2056,14 +2058,16 @@ public class NumberFormatterApiTest { ULocale locale, Number input, String expected) { - // TODO: Add a check for skeleton. - // assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton()); + assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton()); LocalizedNumberFormatter l1 = f.threshold(0L).locale(locale); // no self-regulation LocalizedNumberFormatter l2 = f.threshold(1L).locale(locale); // all self-regulation + LocalizedNumberFormatter l3 = NumberFormatter.fromSkeleton(skeleton).locale(locale); String actual1 = l1.format(input).toString(); assertEquals(message + ": Unsafe Path: " + input, expected, actual1); String actual2 = l2.format(input).toString(); assertEquals(message + ": Safe Path: " + input, expected, actual2); + String actual3 = l3.format(input).toString(); + assertEquals(message + ": Skeleton Path: " + input, expected, actual3); } private static void assertFormatSingleMeasure( @@ -2073,13 +2077,15 @@ public class NumberFormatterApiTest { ULocale locale, Measure input, String expected) { - // TODO: Add a check for skeleton. - // assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton()); + assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton()); LocalizedNumberFormatter l1 = f.threshold(0L).locale(locale); // no self-regulation LocalizedNumberFormatter l2 = f.threshold(1L).locale(locale); // all self-regulation + LocalizedNumberFormatter l3 = NumberFormatter.fromSkeleton(skeleton).locale(locale); String actual1 = l1.format(input).toString(); assertEquals(message + ": Unsafe Path: " + input, expected, actual1); String actual2 = l2.format(input).toString(); assertEquals(message + ": Safe Path: " + input, expected, actual2); + String actual3 = l3.format(input).toString(); + assertEquals(message + ": Skeleton Path: " + input, expected, actual3); } } 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 new file mode 100644 index 0000000000..6735271ab4 --- /dev/null +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java @@ -0,0 +1,70 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html#License +package com.ibm.icu.dev.test.number; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import org.junit.Test; + +import com.ibm.icu.number.NumberFormatter; +import com.ibm.icu.number.SkeletonSyntaxException; + +/** + * @author sffc + * + */ +public class NumberSkeletonTest { + + @Test + public void duplicateValues() { + try { + NumberFormatter.fromSkeleton("round-integer round-integer"); + fail(); + } catch (SkeletonSyntaxException expected) { + assertTrue(expected.getMessage(), expected.getMessage().contains("Duplicated setting")); + } + } + + @Test + public void invalidTokens() { + String[] cases = { + ".00x", + ".00##0", + ".##+", + ".0#+", + "@@x", + "@@##0", + "@#+", + "round-increment/xxx", + "round-increment/0.1.2", + }; + + for (String cas : cases) { + try { + NumberFormatter.fromSkeleton(cas); + fail(); + } catch (SkeletonSyntaxException expected) { + assertTrue(expected.getMessage(), expected.getMessage().contains("Invalid")); + } + } + } + + @Test + public void stemsRequiringOption() { + String[] cases = { + "round-increment", + "round-increment/", + "round-increment scientific", + }; + + for (String cas : cases) { + try { + NumberFormatter.fromSkeleton(cas); + fail(); + } catch (SkeletonSyntaxException expected) { + assertTrue(expected.getMessage(), expected.getMessage().contains("requires an option")); + } + } + } +} From 59e4fc517279d50c8269520c551931f5df69cd2e Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Wed, 28 Feb 2018 09:29:33 +0000 Subject: [PATCH 023/129] ICU-8610 Adds skeleton support for measure units. X-SVN-Rev: 41014 --- .../src/com/ibm/icu/impl/StringSegment.java | 4 +- .../ibm/icu/number/NumberSkeletonImpl.java | 95 +++++++++++++++++-- .../test/number/NumberFormatterApiTest.java | 6 +- 3 files changed, 91 insertions(+), 14 deletions(-) diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/StringSegment.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/StringSegment.java index 5a1f6bd62c..750ad1d4bd 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/StringSegment.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/StringSegment.java @@ -81,9 +81,7 @@ public class StringSegment implements CharSequence { @Override public CharSequence subSequence(int start, int end) { - throw new AssertionError(); // Never used - // Possible implementation: - // return str.subSequence(start + this.start, end + this.start); + return str.subSequence(start + this.start, end + this.start); } /** 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 3eaef0978b..1841ad700c 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 @@ -6,12 +6,17 @@ import java.math.BigDecimal; import java.math.RoundingMode; import java.util.HashMap; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import com.ibm.icu.impl.PatternProps; import com.ibm.icu.impl.StringSegment; import com.ibm.icu.impl.number.MacroProps; +import com.ibm.icu.number.NumberFormatter.UnitWidth; +import com.ibm.icu.util.Currency; import com.ibm.icu.util.Currency.CurrencyUsage; +import com.ibm.icu.util.MeasureUnit; +import com.ibm.icu.util.NoUnit; /** * @author sffc @@ -20,7 +25,7 @@ import com.ibm.icu.util.Currency.CurrencyUsage; class NumberSkeletonImpl { static enum StemType { - ROUNDER, FRACTION_ROUNDER, MAYBE_INCREMENT_ROUNDER, CURRENCY_ROUNDER + OTHER, ROUNDER, FRACTION_ROUNDER, MAYBE_INCREMENT_ROUNDER, CURRENCY_ROUNDER, MEASURE_UNIT, UNIT_WIDTH } static class SkeletonDataStructure { @@ -62,6 +67,12 @@ class NumberSkeletonImpl { "round-currency-standard", Rounder.currency(CurrencyUsage.STANDARD)); skeletonData.put(StemType.ROUNDER, "round-currency-cash", Rounder.currency(CurrencyUsage.CASH)); + + skeletonData.put(StemType.UNIT_WIDTH, "unit-width-narrow", UnitWidth.NARROW); + skeletonData.put(StemType.UNIT_WIDTH, "unit-width-short", UnitWidth.SHORT); + skeletonData.put(StemType.UNIT_WIDTH, "unit-width-full-name", UnitWidth.FULL_NAME); + skeletonData.put(StemType.UNIT_WIDTH, "unit-width-iso-code", UnitWidth.ISO_CODE); + skeletonData.put(StemType.UNIT_WIDTH, "unit-width-hidden", UnitWidth.HIDDEN); } private static final Map cache = new ConcurrentHashMap(); @@ -171,6 +182,7 @@ class NumberSkeletonImpl { // Check for stems that require an option switch (stem) { case MAYBE_INCREMENT_ROUNDER: + case MEASURE_UNIT: throw new SkeletonSyntaxException("Stem requires an option", segment); default: break; @@ -192,6 +204,10 @@ class NumberSkeletonImpl { checkNull(macros.rounder, content); macros.rounder = (Rounder) value; break; + case UNIT_WIDTH: + checkNull(macros.unitWidth, content); + macros.unitWidth = (UnitWidth) value; + break; default: assert false; } @@ -201,6 +217,8 @@ class NumberSkeletonImpl { // Second try: literal stems that require an option if (content.equals("round-increment")) { return StemType.MAYBE_INCREMENT_ROUNDER; + } else if (content.equals("measure-unit")) { + return StemType.MEASURE_UNIT; } // Second try: stem "blueprint" syntax @@ -249,6 +267,14 @@ class NumberSkeletonImpl { } } + // Measure unit option + switch (stem) { + case MEASURE_UNIT: + // The measure unit option is required. + parseMeasureUnitOption(content, macros); + return StemType.OTHER; + } + // Unknown option throw new SkeletonSyntaxException("Unknown option", content); } @@ -260,6 +286,14 @@ class NumberSkeletonImpl { generateRoundingValue(macros, sb); sb.append(' '); } + if (macros.unit != null) { + generateUnitValue(macros, sb); + sb.append(' '); + } + if (macros.unitWidth != null) { + generateUnitWidthValue(macros, sb); + sb.append(' '); + } // Remove the trailing space if (sb.length() > 0) { @@ -390,15 +424,11 @@ class NumberSkeletonImpl { } private static void parseIncrementOption(CharSequence content, MacroProps macros) { - // Clunkilly convert the CharSequence to a char array for the BigDecimal constructor. - // We can't use content.toString() because that doesn't create a clean string. - char[] chars = new char[content.length()]; - for (int i = 0; i < content.length(); i++) { - chars[i] = content.charAt(i); - } + // Call content.subSequence() because content.toString() doesn't create a clean string. + String str = content.subSequence(0, content.length()).toString(); BigDecimal increment; try { - increment = new BigDecimal(chars); + increment = new BigDecimal(str); } catch (NumberFormatException e) { throw new SkeletonSyntaxException("Invalid rounding increment", content, e); } @@ -425,6 +455,29 @@ class NumberSkeletonImpl { sb.append(mode.toString()); } + private static void parseMeasureUnitOption(CharSequence content, MacroProps macros) { + // NOTE: The category (type) of the unit is guaranteed to be a valid subtag (alphanumeric) + // http://unicode.org/reports/tr35/#Validity_Data + int firstHyphen = 0; + while (firstHyphen < content.length() && content.charAt(firstHyphen) != '-') { + firstHyphen++; + } + String type = content.subSequence(0, firstHyphen).toString(); + String subType = content.subSequence(firstHyphen + 1, content.length()).toString(); + Set units = MeasureUnit.getAvailable(type); + for (MeasureUnit unit : units) { + if (subType.equals(unit.getSubtype())) { + macros.unit = unit; + return; + } + } + throw new SkeletonSyntaxException("Unknown unit", content); + } + + private static void generateMeasureUnitOption(MeasureUnit unit, StringBuilder sb) { + sb.append(unit.getType() + "-" + unit.getSubtype()); + } + ///// private static void generateRoundingValue(MacroProps macros, StringBuilder sb) { @@ -474,6 +527,32 @@ class NumberSkeletonImpl { } } + private static void generateUnitValue(MacroProps macros, StringBuilder sb) { + // Check for literals + String literal = skeletonData.valueToStem(macros.unit); + if (literal != null) { + sb.append(literal); + return; + } + + // Generate the stem + if (macros.unit instanceof Currency) { + // TODO + } else if (macros.unit instanceof NoUnit) { + // TODO + } else { + sb.append("measure-unit/"); + generateMeasureUnitOption(macros.unit, sb); + } + } + + private static void generateUnitWidthValue(MacroProps macros, StringBuilder sb) { + // There should be a literal. + String literal = skeletonData.valueToStem(macros.unitWidth); + assert literal != null; + sb.append(literal); + } + ///// private static void checkNull(Object value, CharSequence content) { 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 d6c8a8c552..49569d73d4 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 @@ -377,7 +377,7 @@ public class NumberFormatterApiTest { public void unitMeasure() { assertFormatDescending( "Meters Short", - "U:length:meter", + "measure-unit/length-meter", NumberFormatter.with().unit(MeasureUnit.METER), ULocale.ENGLISH, "87,650 m", @@ -392,7 +392,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Meters Long", - "U:length:meter unit-width=FULL_NAME", + "measure-unit/length-meter unit-width-full-name", NumberFormatter.with().unit(MeasureUnit.METER).unitWidth(UnitWidth.FULL_NAME), ULocale.ENGLISH, "87,650 meters", @@ -407,7 +407,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Compact Meters Long", - "CC U:length:meter unit-width=FULL_NAME", + "compact-long measure-unit/length-meter unit-width-full-name", NumberFormatter.with().notation(Notation.compactLong()).unit(MeasureUnit.METER) .unitWidth(UnitWidth.FULL_NAME), ULocale.ENGLISH, From c5e86f87c8c6570c5dde79f591a1cae3be2742be Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Thu, 1 Mar 2018 09:24:37 +0000 Subject: [PATCH 024/129] ICU-8610 Full support for skeletons in ICU4J. Needs a few more tests. X-SVN-Rev: 41038 --- .../ibm/icu/number/NumberSkeletonImpl.java | 464 +++++++++++++++--- .../test/number/NumberFormatterApiTest.java | 287 +++++------ .../dev/test/number/NumberSkeletonTest.java | 34 +- 3 files changed, 580 insertions(+), 205 deletions(-) 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 1841ad700c..896c245eb6 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 @@ -12,7 +12,11 @@ import java.util.concurrent.ConcurrentHashMap; import com.ibm.icu.impl.PatternProps; import com.ibm.icu.impl.StringSegment; import com.ibm.icu.impl.number.MacroProps; +import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay; +import com.ibm.icu.number.NumberFormatter.GroupingStrategy; +import com.ibm.icu.number.NumberFormatter.SignDisplay; import com.ibm.icu.number.NumberFormatter.UnitWidth; +import com.ibm.icu.text.NumberingSystem; import com.ibm.icu.util.Currency; import com.ibm.icu.util.Currency.CurrencyUsage; import com.ibm.icu.util.MeasureUnit; @@ -25,7 +29,25 @@ import com.ibm.icu.util.NoUnit; class NumberSkeletonImpl { static enum StemType { - OTHER, ROUNDER, FRACTION_ROUNDER, MAYBE_INCREMENT_ROUNDER, CURRENCY_ROUNDER, MEASURE_UNIT, UNIT_WIDTH + OTHER, + COMPACT_NOTATION, + SCIENTIFIC_NOTATION, + SIMPLE_NOTATION, + NO_UNIT, + CURRENCY, + MEASURE_UNIT, + PER_MEASURE_UNIT, + ROUNDER, + FRACTION_ROUNDER, + MAYBE_INCREMENT_ROUNDER, + CURRENCY_ROUNDER, + GROUPING, + INTEGER_WIDTH, + LATIN, + NUMBERING_SYSTEM, + UNIT_WIDTH, + SIGN_DISPLAY, + DECIMAL_DISPLAY } static class SkeletonDataStructure { @@ -61,6 +83,16 @@ class NumberSkeletonImpl { static final SkeletonDataStructure skeletonData = new SkeletonDataStructure(); static { + skeletonData.put(StemType.COMPACT_NOTATION, "compact-short", Notation.compactShort()); + skeletonData.put(StemType.COMPACT_NOTATION, "compact-long", Notation.compactLong()); + skeletonData.put(StemType.SCIENTIFIC_NOTATION, "scientific", Notation.scientific()); + skeletonData.put(StemType.SCIENTIFIC_NOTATION, "engineering", Notation.engineering()); + skeletonData.put(StemType.SIMPLE_NOTATION, "simple-notation", Notation.simple()); + + skeletonData.put(StemType.NO_UNIT, "base-unit", NoUnit.BASE); + skeletonData.put(StemType.NO_UNIT, "percent", NoUnit.PERCENT); + skeletonData.put(StemType.NO_UNIT, "permille", NoUnit.PERMILLE); + skeletonData.put(StemType.ROUNDER, "round-integer", Rounder.integer()); skeletonData.put(StemType.ROUNDER, "round-unlimited", Rounder.unlimited()); skeletonData.put(StemType.ROUNDER, @@ -68,11 +100,32 @@ class NumberSkeletonImpl { Rounder.currency(CurrencyUsage.STANDARD)); skeletonData.put(StemType.ROUNDER, "round-currency-cash", Rounder.currency(CurrencyUsage.CASH)); + skeletonData.put(StemType.GROUPING, "group-off", GroupingStrategy.OFF); + skeletonData.put(StemType.GROUPING, "group-min2", GroupingStrategy.MIN2); + skeletonData.put(StemType.GROUPING, "group-auto", GroupingStrategy.AUTO); + skeletonData.put(StemType.GROUPING, "group-on-aligned", GroupingStrategy.ON_ALIGNED); + skeletonData.put(StemType.GROUPING, "group-thousands", GroupingStrategy.THOUSANDS); + + skeletonData.put(StemType.LATIN, "latin", NumberingSystem.LATIN); + skeletonData.put(StemType.UNIT_WIDTH, "unit-width-narrow", UnitWidth.NARROW); skeletonData.put(StemType.UNIT_WIDTH, "unit-width-short", UnitWidth.SHORT); skeletonData.put(StemType.UNIT_WIDTH, "unit-width-full-name", UnitWidth.FULL_NAME); skeletonData.put(StemType.UNIT_WIDTH, "unit-width-iso-code", UnitWidth.ISO_CODE); skeletonData.put(StemType.UNIT_WIDTH, "unit-width-hidden", UnitWidth.HIDDEN); + + skeletonData.put(StemType.SIGN_DISPLAY, "sign-auto", SignDisplay.AUTO); + skeletonData.put(StemType.SIGN_DISPLAY, "sign-always", SignDisplay.ALWAYS); + skeletonData.put(StemType.SIGN_DISPLAY, "sign-never", SignDisplay.NEVER); + skeletonData.put(StemType.SIGN_DISPLAY, "sign-accounting", SignDisplay.ACCOUNTING); + skeletonData.put(StemType.SIGN_DISPLAY, "sign-accounting-always", SignDisplay.ACCOUNTING_ALWAYS); + skeletonData.put(StemType.SIGN_DISPLAY, "sign-except-zero", SignDisplay.EXCEPT_ZERO); + skeletonData.put(StemType.SIGN_DISPLAY, + "sign-accounting-except-zero", + SignDisplay.ACCOUNTING_EXCEPT_ZERO); + + skeletonData.put(StemType.DECIMAL_DISPLAY, "decimal-auto", DecimalSeparatorDisplay.AUTO); + skeletonData.put(StemType.DECIMAL_DISPLAY, "decimal-always", DecimalSeparatorDisplay.ALWAYS); } private static final Map cache = new ConcurrentHashMap(); @@ -183,6 +236,10 @@ class NumberSkeletonImpl { switch (stem) { case MAYBE_INCREMENT_ROUNDER: case MEASURE_UNIT: + case PER_MEASURE_UNIT: + case CURRENCY: + case INTEGER_WIDTH: + case NUMBERING_SYSTEM: throw new SkeletonSyntaxException("Stem requires an option", segment); default: break; @@ -200,14 +257,40 @@ class NumberSkeletonImpl { if (stem != null) { Object value = skeletonData.stemToValue(content); switch (stem) { + case COMPACT_NOTATION: + case SCIENTIFIC_NOTATION: + case SIMPLE_NOTATION: + checkNull(macros.notation, content); + macros.notation = (Notation) value; + break; + case NO_UNIT: + checkNull(macros.unit, content); + macros.unit = (NoUnit) value; + break; case ROUNDER: checkNull(macros.rounder, content); macros.rounder = (Rounder) value; break; + case GROUPING: + checkNull(macros.grouping, content); + macros.grouping = value; + break; + case LATIN: + checkNull(macros.symbols, content); + macros.symbols = value; + break; case UNIT_WIDTH: checkNull(macros.unitWidth, content); macros.unitWidth = (UnitWidth) value; break; + case SIGN_DISPLAY: + checkNull(macros.sign, content); + macros.sign = (SignDisplay) value; + break; + case DECIMAL_DISPLAY: + checkNull(macros.decimal, content); + macros.decimal = (DecimalSeparatorDisplay) value; + break; default: assert false; } @@ -216,19 +299,35 @@ class NumberSkeletonImpl { // Second try: literal stems that require an option if (content.equals("round-increment")) { + checkNull(macros.rounder, content); return StemType.MAYBE_INCREMENT_ROUNDER; } else if (content.equals("measure-unit")) { + checkNull(macros.unit, content); return StemType.MEASURE_UNIT; + } else if (content.equals("per-measure-unit")) { + checkNull(macros.perUnit, content); + return StemType.PER_MEASURE_UNIT; + } else if (content.equals("currency")) { + checkNull(macros.unit, content); + return StemType.CURRENCY; + } else if (content.equals("integer-width")) { + checkNull(macros.integerWidth, content); + return StemType.INTEGER_WIDTH; + } else if (content.equals("numbering-system")) { + checkNull(macros.symbols, content); + return StemType.NUMBERING_SYSTEM; } - // Second try: stem "blueprint" syntax + // Third try: stem "blueprint" syntax switch (content.charAt(0)) { case '.': stem = StemType.FRACTION_ROUNDER; + checkNull(macros.rounder, content); parseFractionStem(content, macros); break; case '@': stem = StemType.ROUNDER; + checkNull(macros.rounder, content); parseDigitsStem(content, macros); break; } @@ -241,6 +340,43 @@ class NumberSkeletonImpl { } private static StemType parseOption(StemType stem, CharSequence content, MacroProps macros) { + + ///// Required options: ///// + + switch (stem) { + case CURRENCY: + parseCurrencyOption(content, macros); + return StemType.OTHER; + case MEASURE_UNIT: + parseMeasureUnitOption(content, macros); + return StemType.OTHER; + case PER_MEASURE_UNIT: + parseMeasurePerUnitOption(content, macros); + return StemType.OTHER; + case MAYBE_INCREMENT_ROUNDER: + parseIncrementOption(content, macros); + return StemType.ROUNDER; + case INTEGER_WIDTH: + parseIntegerWidthOption(content, macros); + return StemType.OTHER; + case NUMBERING_SYSTEM: + parseNumberingSystemOption(content, macros); + return StemType.OTHER; + } + + ///// Non-required options: ///// + + // Scientific options + switch (stem) { + case SCIENTIFIC_NOTATION: + if (parseExponentWidthOption(content, macros)) { + return StemType.SCIENTIFIC_NOTATION; + } + if (parseExponentSignOption(content, macros)) { + return StemType.SCIENTIFIC_NOTATION; + } + } + // Frac-sig option switch (stem) { case FRACTION_ROUNDER: @@ -249,51 +385,61 @@ class NumberSkeletonImpl { } } - // Increment option - switch (stem) { - case MAYBE_INCREMENT_ROUNDER: - // The increment option is required. - parseIncrementOption(content, macros); - return StemType.ROUNDER; - } - // Rounding mode option switch (stem) { case ROUNDER: case FRACTION_ROUNDER: case CURRENCY_ROUNDER: if (parseRoundingModeOption(content, macros)) { - break; + return StemType.ROUNDER; } } - // Measure unit option - switch (stem) { - case MEASURE_UNIT: - // The measure unit option is required. - parseMeasureUnitOption(content, macros); - return StemType.OTHER; - } - // Unknown option throw new SkeletonSyntaxException("Unknown option", content); } - ///// - private static void generateSkeleton(MacroProps macros, StringBuilder sb) { - if (macros.rounder != null) { - generateRoundingValue(macros, sb); + if (macros.notation != null) { + generateNotationValue(macros, sb); sb.append(' '); } if (macros.unit != null) { generateUnitValue(macros, sb); sb.append(' '); } + if (macros.perUnit != null) { + generatePerUnitValue(macros, sb); + sb.append(' '); + } + if (macros.rounder != null) { + generateRoundingValue(macros, sb); + sb.append(' '); + } + if (macros.grouping != null) { + generateGroupingValue(macros, sb); + sb.append(' '); + } + if (macros.integerWidth != null) { + generateIntegerWidthValue(macros, sb); + sb.append(' '); + } + if (macros.symbols != null) { + generateSymbolsValue(macros, sb); + sb.append(' '); + } if (macros.unitWidth != null) { generateUnitWidthValue(macros, sb); sb.append(' '); } + if (macros.sign != null) { + generateSignValue(macros, sb); + sb.append(' '); + } + if (macros.decimal != null) { + generateDecimalValue(macros, sb); + sb.append(' '); + } // Remove the trailing space if (sb.length() > 0) { @@ -303,6 +449,90 @@ class NumberSkeletonImpl { ///// + private static boolean parseExponentWidthOption(CharSequence content, MacroProps macros) { + if (content.charAt(0) != '+') { + return false; + } + int offset = 1; + int minExp = 0; + for (; offset < content.length(); offset++) { + if (content.charAt(offset) == 'e') { + minExp++; + } else { + break; + } + } + if (offset < content.length()) { + return false; + } + // Use the public APIs to enforce bounds checking + macros.notation = ((ScientificNotation) macros.notation).withMinExponentDigits(minExp); + return true; + } + + private static void generateExponentWidthOption(int minInt, int maxInt, StringBuilder sb) { + sb.append('+'); + appendMultiple(sb, 'e', minInt); + } + + private static boolean parseExponentSignOption(CharSequence content, MacroProps macros) { + Object value = skeletonData.stemToValue(content); + if (value != null && value instanceof SignDisplay) { + macros.notation = ((ScientificNotation) macros.notation) + .withExponentSignDisplay((SignDisplay) value); + return true; + } + return false; + } + + private static void generateCurrencyOption(Currency currency, StringBuilder sb) { + sb.append(currency.getCurrencyCode()); + } + + private static void parseCurrencyOption(CharSequence content, MacroProps macros) { + String currencyCode = content.subSequence(0, content.length()).toString(); + try { + macros.unit = Currency.getInstance(currencyCode); + } catch (IllegalArgumentException e) { + throw new SkeletonSyntaxException("Invalid currency", content, e); + } + } + + private static void parseMeasureUnitOption(CharSequence content, MacroProps macros) { + // NOTE: The category (type) of the unit is guaranteed to be a valid subtag (alphanumeric) + // http://unicode.org/reports/tr35/#Validity_Data + int firstHyphen = 0; + while (firstHyphen < content.length() && content.charAt(firstHyphen) != '-') { + firstHyphen++; + } + if (firstHyphen == content.length()) { + throw new SkeletonSyntaxException("Invalid measure unit option", content); + } + String type = content.subSequence(0, firstHyphen).toString(); + String subType = content.subSequence(firstHyphen + 1, content.length()).toString(); + Set units = MeasureUnit.getAvailable(type); + for (MeasureUnit unit : units) { + if (subType.equals(unit.getSubtype())) { + macros.unit = unit; + return; + } + } + throw new SkeletonSyntaxException("Unknown measure unit", content); + } + + private static void generateMeasureUnitOption(MeasureUnit unit, StringBuilder sb) { + sb.append(unit.getType() + "-" + unit.getSubtype()); + } + + private static void parseMeasurePerUnitOption(CharSequence content, MacroProps macros) { + // A little bit of a hack: safe the current unit (numerator), call the main measure unit parsing + // code, put back the numerator unit, and put the new unit into per-unit. + MeasureUnit numerator = macros.unit; + parseMeasureUnitOption(content, macros); + macros.perUnit = macros.unit; + macros.unit = numerator; + } + private static void parseFractionStem(CharSequence content, MacroProps macros) { assert content.charAt(0) == '.'; int offset = 1; @@ -412,7 +642,7 @@ class NumberSkeletonImpl { } FractionRounder oldRounder = (FractionRounder) macros.rounder; // A little bit of a hack: parse the option as a digits stem, and extract the min/max sig from - // the new Rounder saved into the macros + // the new Rounder saved into the macros. parseDigitsStem(content, macros); Rounder.SignificantRounderImpl intermediate = (Rounder.SignificantRounderImpl) macros.rounder; if (intermediate.maxSig == -1) { @@ -455,31 +685,134 @@ class NumberSkeletonImpl { sb.append(mode.toString()); } - private static void parseMeasureUnitOption(CharSequence content, MacroProps macros) { - // NOTE: The category (type) of the unit is guaranteed to be a valid subtag (alphanumeric) - // http://unicode.org/reports/tr35/#Validity_Data - int firstHyphen = 0; - while (firstHyphen < content.length() && content.charAt(firstHyphen) != '-') { - firstHyphen++; + private static void parseIntegerWidthOption(CharSequence content, MacroProps macros) { + int offset = 0; + int minInt = 0; + int maxInt; + if (content.charAt(0) == '+') { + maxInt = -1; + offset++; + } else { + maxInt = 0; } - String type = content.subSequence(0, firstHyphen).toString(); - String subType = content.subSequence(firstHyphen + 1, content.length()).toString(); - Set units = MeasureUnit.getAvailable(type); - for (MeasureUnit unit : units) { - if (subType.equals(unit.getSubtype())) { - macros.unit = unit; - return; + for (; offset < content.length(); offset++) { + if (content.charAt(offset) == '#') { + maxInt++; + } else { + break; } } - throw new SkeletonSyntaxException("Unknown unit", content); + if (offset < content.length()) { + for (; offset < content.length(); offset++) { + if (content.charAt(offset) == '0') { + minInt++; + } else { + break; + } + } + } + if (maxInt != -1) { + maxInt += minInt; + } + if (offset < content.length()) { + throw new SkeletonSyntaxException("Invalid integer width stem", content); + } + // Use the public APIs to enforce bounds checking + if (maxInt == -1) { + macros.integerWidth = IntegerWidth.zeroFillTo(minInt); + } else { + macros.integerWidth = IntegerWidth.zeroFillTo(minInt).truncateAt(maxInt); + } } - private static void generateMeasureUnitOption(MeasureUnit unit, StringBuilder sb) { - sb.append(unit.getType() + "-" + unit.getSubtype()); + private static void generateIntegerWidthOption(int minInt, int maxInt, StringBuilder sb) { + if (maxInt == -1) { + sb.append('+'); + } else { + appendMultiple(sb, '#', maxInt - minInt); + } + appendMultiple(sb, '0', minInt); + } + + private static void parseNumberingSystemOption(CharSequence content, MacroProps macros) { + String nsName = content.subSequence(0, content.length()).toString(); + NumberingSystem ns = NumberingSystem.getInstanceByName(nsName); + if (ns == null) { + throw new SkeletonSyntaxException("Unknown numbering system", content); + } + macros.symbols = ns; + } + + private static void generateNumberingSystemOption(NumberingSystem ns, StringBuilder sb) { + sb.append(ns.getName()); } ///// + private static void generateNotationValue(MacroProps macros, StringBuilder sb) { + // Check for literals + String literal = skeletonData.valueToStem(macros.notation); + if (literal != null) { + sb.append(literal); + return; + } + + // Generate the stem + if (macros.notation instanceof CompactNotation) { + // Compact notation generated from custom data (not supported in skeleton) + // The other compact notations are literals + } else if (macros.notation instanceof ScientificNotation) { + ScientificNotation impl = (ScientificNotation) macros.notation; + if (impl.engineeringInterval == 3) { + sb.append("engineering"); + } else { + sb.append("scientific"); + } + if (impl.minExponentDigits > 1) { + sb.append('/'); + generateExponentWidthOption(impl.minExponentDigits, -1, sb); + } + if (impl.exponentSignDisplay != SignDisplay.AUTO) { + sb.append('/'); + sb.append(skeletonData.valueToStem(impl.exponentSignDisplay)); + } + } else { + assert macros.notation instanceof SimpleNotation; + sb.append("notation-simple"); + } + } + + private static void generateUnitValue(MacroProps macros, StringBuilder sb) { + // Check for literals + String literal = skeletonData.valueToStem(macros.unit); + if (literal != null) { + sb.append(literal); + return; + } + + // Generate the stem + if (macros.unit instanceof Currency) { + sb.append("currency/"); + generateCurrencyOption((Currency) macros.unit, sb); + } else if (macros.unit instanceof NoUnit) { + // This should be taken care of by the literals. + assert false; + } else { + sb.append("measure-unit/"); + generateMeasureUnitOption(macros.unit, sb); + } + } + + private static void generatePerUnitValue(MacroProps macros, StringBuilder sb) { + // Per-units are currently expected to be only MeasureUnits. + if (macros.unit instanceof Currency || macros.unit instanceof NoUnit) { + assert false; + } else { + sb.append("per-measure-unit/"); + generateMeasureUnitOption(macros.perUnit, sb); + } + } + private static void generateRoundingValue(MacroProps macros, StringBuilder sb) { // Check for literals String literal = skeletonData.valueToStem(macros.rounder); @@ -527,30 +860,39 @@ class NumberSkeletonImpl { } } - private static void generateUnitValue(MacroProps macros, StringBuilder sb) { - // Check for literals - String literal = skeletonData.valueToStem(macros.unit); - if (literal != null) { - sb.append(literal); - return; - } + private static void generateGroupingValue(MacroProps macros, StringBuilder sb) { + appendExpectedLiteral(macros.grouping, sb); + } - // Generate the stem - if (macros.unit instanceof Currency) { - // TODO - } else if (macros.unit instanceof NoUnit) { - // TODO + private static void generateIntegerWidthValue(MacroProps macros, StringBuilder sb) { + sb.append("integer-width/"); + generateIntegerWidthOption(macros.integerWidth.minInt, macros.integerWidth.maxInt, sb); + } + + private static void generateSymbolsValue(MacroProps macros, StringBuilder sb) { + if (macros.symbols instanceof NumberingSystem) { + NumberingSystem ns = (NumberingSystem) macros.symbols; + if (ns.getName().equals("latn")) { + sb.append("latin"); + } else { + sb.append("numbering-system/"); + generateNumberingSystemOption(ns, sb); + } } else { - sb.append("measure-unit/"); - generateMeasureUnitOption(macros.unit, sb); + // DecimalFormatSymbols (not supported in skeleton) } } private static void generateUnitWidthValue(MacroProps macros, StringBuilder sb) { - // There should be a literal. - String literal = skeletonData.valueToStem(macros.unitWidth); - assert literal != null; - sb.append(literal); + appendExpectedLiteral(macros.unitWidth, sb); + } + + private static void generateSignValue(MacroProps macros, StringBuilder sb) { + appendExpectedLiteral(macros.sign, sb); + } + + private static void generateDecimalValue(MacroProps macros, StringBuilder sb) { + appendExpectedLiteral(macros.decimal, sb); } ///// @@ -566,4 +908,10 @@ class NumberSkeletonImpl { sb.appendCodePoint(cp); } } + + private static void appendExpectedLiteral(Object value, StringBuilder sb) { + String literal = skeletonData.valueToStem(value); + assert literal != null; + sb.append(literal); + } } 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 49569d73d4..050e60dfd5 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 @@ -75,7 +75,7 @@ public class NumberFormatterApiTest { assertFormatDescendingBig( "Big Simple", - "", + "simple-notation", NumberFormatter.with().notation(Notation.simple()), ULocale.ENGLISH, "87,650,000", @@ -101,7 +101,7 @@ public class NumberFormatterApiTest { public void notationScientific() { assertFormatDescending( "Scientific", - "E", + "scientific", NumberFormatter.with().notation(Notation.scientific()), ULocale.ENGLISH, "8.765E4", @@ -116,7 +116,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Engineering", - "E3", + "engineering", NumberFormatter.with().notation(Notation.engineering()), ULocale.ENGLISH, "87.65E3", @@ -131,7 +131,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Scientific sign always shown", - "E+", + "scientific/sign-always", NumberFormatter.with().notation(Notation.scientific().withExponentSignDisplay(SignDisplay.ALWAYS)), ULocale.ENGLISH, "8.765E+4", @@ -146,7 +146,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Scientific min exponent digits", - "E00", + "scientific/+ee", NumberFormatter.with().notation(Notation.scientific().withMinExponentDigits(2)), ULocale.ENGLISH, "8.765E04", @@ -161,7 +161,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Scientific Negative", - "E", + "scientific", NumberFormatter.with().notation(Notation.scientific()), ULocale.ENGLISH, -1000000, @@ -172,7 +172,7 @@ public class NumberFormatterApiTest { public void notationCompact() { assertFormatDescendingBig( "Compact Short", - "C", + "compact-short", NumberFormatter.with().notation(Notation.compactShort()), ULocale.ENGLISH, "88M", @@ -187,7 +187,7 @@ public class NumberFormatterApiTest { assertFormatDescendingBig( "Compact Long", - "CC", + "compact-long", NumberFormatter.with().notation(Notation.compactLong()), ULocale.ENGLISH, "88 million", @@ -202,7 +202,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Compact Short Currency", - "C $USD", + "compact-short currency/USD", NumberFormatter.with().notation(Notation.compactShort()).unit(USD), ULocale.ENGLISH, "$88K", @@ -217,7 +217,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Compact Short with ISO Currency", - "C $USD unit-width=ISO_CODE", + "compact-short currency/USD unit-width-iso-code", NumberFormatter.with().notation(Notation.compactShort()).unit(USD).unitWidth(UnitWidth.ISO_CODE), ULocale.ENGLISH, "USD 88K", @@ -232,7 +232,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Compact Short with Long Name Currency", - "C $USD unit-width=FULL_NAME", + "compact-short currency/USD unit-width-full-name", NumberFormatter.with().notation(Notation.compactShort()).unit(USD).unitWidth(UnitWidth.FULL_NAME), ULocale.ENGLISH, "88K US dollars", @@ -249,7 +249,7 @@ public class NumberFormatterApiTest { // This test case should be fixed when proper compact long currency patterns are added. assertFormatDescending( "Compact Long Currency", - "CC $USD", + "compact-long currency/USD", NumberFormatter.with().notation(Notation.compactLong()).unit(USD), ULocale.ENGLISH, "$88K", // should be something like "$88 thousand" @@ -266,7 +266,7 @@ public class NumberFormatterApiTest { // This test case should be fixed when proper compact long currency patterns are added. assertFormatDescending( "Compact Long with ISO Currency", - "CC $USD unit-width=ISO_CODE", + "compact-long currency/USD unit-width-iso-code", NumberFormatter.with().notation(Notation.compactLong()).unit(USD).unitWidth(UnitWidth.ISO_CODE), ULocale.ENGLISH, "USD 88K", // should be something like "USD 88 thousand" @@ -282,7 +282,7 @@ public class NumberFormatterApiTest { // TODO: This behavior could be improved and should be revisited. assertFormatDescending( "Compact Long with Long Name Currency", - "CC $USD unit-width=FULL_NAME", + "compact-long currency/USD unit-width-full-name", NumberFormatter.with().notation(Notation.compactLong()).unit(USD).unitWidth(UnitWidth.FULL_NAME), ULocale.ENGLISH, "88 thousand US dollars", @@ -297,7 +297,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Compact Plural One", - "CC", + "compact-long", NumberFormatter.with().notation(Notation.compactLong()), ULocale.forLanguageTag("es"), 1000000, @@ -305,7 +305,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Compact Plural Other", - "CC", + "compact-long", NumberFormatter.with().notation(Notation.compactLong()), ULocale.forLanguageTag("es"), 2000000, @@ -313,7 +313,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Compact with Negative Sign", - "C", + "compact-short", NumberFormatter.with().notation(Notation.compactShort()), ULocale.ENGLISH, -9876543.21, @@ -321,7 +321,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Compact Rounding", - "C", + "compact-short", NumberFormatter.with().notation(Notation.compactShort()), ULocale.ENGLISH, 990000, @@ -329,7 +329,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Compact Rounding", - "C", + "compact-short", NumberFormatter.with().notation(Notation.compactShort()), ULocale.ENGLISH, 999000, @@ -337,7 +337,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Compact Rounding", - "C", + "compact-short", NumberFormatter.with().notation(Notation.compactShort()), ULocale.ENGLISH, 999900, @@ -345,7 +345,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Compact Rounding", - "C", + "compact-short", NumberFormatter.with().notation(Notation.compactShort()), ULocale.ENGLISH, 9900000, @@ -353,7 +353,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Compact Rounding", - "C", + "compact-short", NumberFormatter.with().notation(Notation.compactShort()), ULocale.ENGLISH, 9990000, @@ -366,7 +366,7 @@ public class NumberFormatterApiTest { compactCustomData.put("1000", entry); assertFormatSingle( "Compact Somali No Figure", - "", + null, // feature not supported in skeleton NumberFormatter.with().notation(CompactNotation.forCustomData(compactCustomData)), ULocale.ENGLISH, 1000, @@ -423,7 +423,7 @@ public class NumberFormatterApiTest { assertFormatSingleMeasure( "Meters with Measure Input", - "unit-width=FULL_NAME", + "unit-width-full-name", NumberFormatter.with().unitWidth(UnitWidth.FULL_NAME), ULocale.ENGLISH, new Measure(5.43, MeasureUnit.METER), @@ -431,7 +431,7 @@ public class NumberFormatterApiTest { assertFormatSingleMeasure( "Measure format method takes precedence over fluent chain", - "U:length:meter", + "measure-unit/length-meter", NumberFormatter.with().unit(MeasureUnit.METER), ULocale.ENGLISH, new Measure(5.43, USD), @@ -439,7 +439,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Meters with Negative Sign", - "U:length:meter", + "measure-unit/length-meter", NumberFormatter.with().unit(MeasureUnit.METER), ULocale.ENGLISH, -9876543.21, @@ -448,7 +448,7 @@ public class NumberFormatterApiTest { // The locale string "सान" appears only in brx.txt: assertFormatSingle( "Interesting Data Fallback 1", - "U:duration:day unit-width=FULL_NAME", + "measure-unit/duration-day unit-width-full-name", NumberFormatter.with().unit(MeasureUnit.DAY).unitWidth(UnitWidth.FULL_NAME), ULocale.forLanguageTag("brx"), 5.43, @@ -457,7 +457,7 @@ public class NumberFormatterApiTest { // Requires following the alias from unitsNarrow to unitsShort: assertFormatSingle( "Interesting Data Fallback 2", - "U:duration:day unit-width=NARROW", + "measure-unit/duration-day unit-width-narrow", NumberFormatter.with().unit(MeasureUnit.DAY).unitWidth(UnitWidth.NARROW), ULocale.forLanguageTag("brx"), 5.43, @@ -467,7 +467,7 @@ public class NumberFormatterApiTest { // requiring fallback to the root. assertFormatSingle( "Interesting Data Fallback 3", - "U:area:square-meter unit-width=NARROW", + "measure-unit/area-square-meter unit-width-narrow", NumberFormatter.with().unit(MeasureUnit.SQUARE_METER).unitWidth(UnitWidth.NARROW), ULocale.forLanguageTag("en-GB"), 5.43, @@ -477,7 +477,7 @@ public class NumberFormatterApiTest { // NOTE: This example is in the documentation. assertFormatSingle( "MeasureUnit Difference between Narrow and Short (Narrow Version)", - "", + "measure-unit/temperature-fahrenheit unit-width-narrow", NumberFormatter.with().unit(MeasureUnit.FAHRENHEIT).unitWidth(UnitWidth.NARROW), ULocale.forLanguageTag("es-US"), 5.43, @@ -485,7 +485,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "MeasureUnit Difference between Narrow and Short (Short Version)", - "", + "measure-unit/temperature-fahrenheit unit-width-short", NumberFormatter.with().unit(MeasureUnit.FAHRENHEIT).unitWidth(UnitWidth.SHORT), ULocale.forLanguageTag("es-US"), 5.43, @@ -493,7 +493,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "MeasureUnit form without {0} in CLDR pattern", - "", + "measure-unit/temperature-kelvin unit-width-full-name", NumberFormatter.with().unit(MeasureUnit.KELVIN).unitWidth(UnitWidth.FULL_NAME), ULocale.forLanguageTag("es-MX"), 1, @@ -501,9 +501,9 @@ public class NumberFormatterApiTest { assertFormatSingle( "MeasureUnit form without {0} in CLDR pattern and wide base form", - "", + "measure-unit/temperature-kelvin .0000000000 unit-width-full-name", NumberFormatter.with() - .rounding(Rounder.fixedFraction(20)) + .rounding(Rounder.fixedFraction(10)) .unit(MeasureUnit.KELVIN) .unitWidth(UnitWidth.FULL_NAME), ULocale.forLanguageTag("es-MX"), @@ -515,7 +515,7 @@ public class NumberFormatterApiTest { public void unitCompoundMeasure() { assertFormatDescending( "Meters Per Second Short (unit that simplifies)", - "", + "measure-unit/length-meter per-measure-unit/duration-second", NumberFormatter.with().unit(MeasureUnit.METER).perUnit(MeasureUnit.SECOND), ULocale.ENGLISH, "87,650 m/s", @@ -530,7 +530,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Pounds Per Square Mile Short (secondary unit has per-format)", - "", + "measure-unit/mass-pound per-measure-unit/area-square-mile", NumberFormatter.with().unit(MeasureUnit.POUND).perUnit(MeasureUnit.SQUARE_MILE), ULocale.ENGLISH, "87,650 lb/mi²", @@ -545,7 +545,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Joules Per Furlong Short (unit with no simplifications or special patterns)", - "", + "measure-unit/energy-joule per-measure-unit/length-furlong", NumberFormatter.with().unit(MeasureUnit.JOULE).perUnit(MeasureUnit.FURLONG), ULocale.ENGLISH, "87,650 J/fur", @@ -563,7 +563,7 @@ public class NumberFormatterApiTest { public void unitCurrency() { assertFormatDescending( "Currency", - "$GBP", + "currency/GBP", NumberFormatter.with().unit(GBP), ULocale.ENGLISH, "£87,650.00", @@ -578,7 +578,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Currency ISO", - "$GBP unit-width=ISO_CODE", + "currency/GBP unit-width-iso-code", NumberFormatter.with().unit(GBP).unitWidth(UnitWidth.ISO_CODE), ULocale.ENGLISH, "GBP 87,650.00", @@ -593,7 +593,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Currency Long Name", - "$GBP unit-width=FULL_NAME", + "currency/GBP unit-width-full-name", NumberFormatter.with().unit(GBP).unitWidth(UnitWidth.FULL_NAME), ULocale.ENGLISH, "87,650.00 British pounds", @@ -608,7 +608,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Currency Hidden", - "$GBP unit-width=HIDDEN", + "currency/GBP unit-width-hidden", NumberFormatter.with().unit(GBP).unitWidth(UnitWidth.HIDDEN), ULocale.ENGLISH, "87,650.00", @@ -631,7 +631,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Currency Long Name from Pattern Syntax", - "$GBP F0 grouping=none integer-width=1- symbols=loc:en sign=AUTO decimal=AUTO", + null, NumberFormatter.fromDecimalFormat( PatternStringParser.parseToProperties("0 ¤¤¤"), DecimalFormatSymbols.getInstance(ULocale.ENGLISH), @@ -642,7 +642,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Currency with Negative Sign", - "$GBP", + "currency/GBP", NumberFormatter.with().unit(GBP), ULocale.ENGLISH, -9876543.21, @@ -652,7 +652,7 @@ public class NumberFormatterApiTest { // NOTE: This example is in the documentation. assertFormatSingle( "Currency Difference between Narrow and Short (Narrow Version)", - "", + "currency/USD unit-width-narrow", NumberFormatter.with().unit(USD).unitWidth(UnitWidth.NARROW), ULocale.forLanguageTag("en-CA"), 5.43, @@ -660,7 +660,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Currency Difference between Narrow and Short (Short Version)", - "", + "currency/USD unit-width-short", NumberFormatter.with().unit(USD).unitWidth(UnitWidth.SHORT), ULocale.forLanguageTag("en-CA"), 5.43, @@ -668,7 +668,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Currency-dependent format (Control)", - "", + "currency/USD unit-width-short", NumberFormatter.with().unit(USD).unitWidth(UnitWidth.SHORT), ULocale.forLanguageTag("ca"), 444444.55, @@ -676,7 +676,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Currency-dependent format (Test)", - "", + "currency/ESP unit-width-short", NumberFormatter.with().unit(ESP).unitWidth(UnitWidth.SHORT), ULocale.forLanguageTag("ca"), 444444.55, @@ -684,7 +684,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Currency-dependent symbols (Control)", - "", + "currency/USD unit-width-short", NumberFormatter.with().unit(USD).unitWidth(UnitWidth.SHORT), ULocale.forLanguageTag("pt-PT"), 444444.55, @@ -694,7 +694,7 @@ public class NumberFormatterApiTest { // width space), and they set the decimal separator to the $ symbol. assertFormatSingle( "Currency-dependent symbols (Test)", - "", + "currency/PTE unit-width-short", NumberFormatter.with().unit(PTE).unitWidth(UnitWidth.SHORT), ULocale.forLanguageTag("pt-PT"), 444444.55, @@ -702,7 +702,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Currency-dependent symbols (Test)", - "", + "currency/PTE unit-width-narrow", NumberFormatter.with().unit(PTE).unitWidth(UnitWidth.NARROW), ULocale.forLanguageTag("pt-PT"), 444444.55, @@ -710,7 +710,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Currency-dependent symbols (Test)", - "", + "currency/PTE unit-width-iso-code", NumberFormatter.with().unit(PTE).unitWidth(UnitWidth.ISO_CODE), ULocale.forLanguageTag("pt-PT"), 444444.55, @@ -721,7 +721,7 @@ public class NumberFormatterApiTest { public void unitPercent() { assertFormatDescending( "Percent", - "%", + "percent", NumberFormatter.with().unit(NoUnit.PERCENT), ULocale.ENGLISH, "87,650%", @@ -736,7 +736,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Permille", - "%%", + "permille", NumberFormatter.with().unit(NoUnit.PERMILLE), ULocale.ENGLISH, "87,650‰", @@ -751,7 +751,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "NoUnit Base", - "B", + "base-unit", NumberFormatter.with().unit(NoUnit.BASE), ULocale.ENGLISH, 51423, @@ -759,7 +759,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Percent with Negative Sign", - "%", + "percent", NumberFormatter.with().unit(NoUnit.PERCENT), ULocale.ENGLISH, -98.7654321, @@ -1023,7 +1023,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Currency Standard", - "round-currency-standard", + "currency/CZK round-currency-standard", NumberFormatter.with().rounding(Rounder.currency(CurrencyUsage.STANDARD)).unit(CZK), ULocale.ENGLISH, "CZK 87,650.00", @@ -1038,7 +1038,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Currency Cash", - "round-currency-cash", + "currency/CZK round-currency-cash", NumberFormatter.with().rounding(Rounder.currency(CurrencyUsage.CASH)).unit(CZK), ULocale.ENGLISH, "CZK 87,650", @@ -1053,7 +1053,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Currency Cash with Nickel Rounding", - "round-currency-cash", + "currency/CAD round-currency-cash", NumberFormatter.with().rounding(Rounder.currency(CurrencyUsage.CASH)).unit(CAD), ULocale.ENGLISH, "CA$87,650.00", @@ -1068,7 +1068,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Currency not in top-level fluent chain", - "round-currency-cash/CZK", + "round-integer", // calling .withCurrency() applies currency rounding rules immediately NumberFormatter.with().rounding(Rounder.currency(CurrencyUsage.CASH).withCurrency(CZK)), ULocale.ENGLISH, "87,650", @@ -1083,8 +1083,8 @@ public class NumberFormatterApiTest { // NOTE: Other tests cover the behavior of the other rounding modes. assertFormatDescending( + "Rounding Mode CEILING", "round-integer/CEILING", - "", NumberFormatter.with().rounding(Rounder.integer().withMode(RoundingMode.CEILING)), ULocale.ENGLISH, "87,650", @@ -1102,7 +1102,7 @@ public class NumberFormatterApiTest { public void grouping() { assertFormatDescendingBig( "Western Grouping", - "grouping=defaults", + "group-auto", NumberFormatter.with().grouping(GroupingStrategy.AUTO), ULocale.ENGLISH, "87,650,000", @@ -1117,7 +1117,7 @@ public class NumberFormatterApiTest { assertFormatDescendingBig( "Indic Grouping", - "grouping=defaults", + "group-auto", NumberFormatter.with().grouping(GroupingStrategy.AUTO), new ULocale("en-IN"), "8,76,50,000", @@ -1132,7 +1132,7 @@ public class NumberFormatterApiTest { assertFormatDescendingBig( "Western Grouping, Min 2", - "grouping=min2", + "group-min2", NumberFormatter.with().grouping(GroupingStrategy.MIN2), ULocale.ENGLISH, "87,650,000", @@ -1147,7 +1147,7 @@ public class NumberFormatterApiTest { assertFormatDescendingBig( "Indic Grouping, Min 2", - "grouping=min2", + "group-min2", NumberFormatter.with().grouping(GroupingStrategy.MIN2), new ULocale("en-IN"), "8,76,50,000", @@ -1162,7 +1162,7 @@ public class NumberFormatterApiTest { assertFormatDescendingBig( "No Grouping", - "grouping=none", + "group-off", NumberFormatter.with().grouping(GroupingStrategy.OFF), new ULocale("en-IN"), "87650000", @@ -1177,7 +1177,7 @@ public class NumberFormatterApiTest { assertFormatDescendingBig( "Indic locale with THOUSANDS grouping", - "", + "group-thousands", NumberFormatter.with().grouping(GroupingStrategy.THOUSANDS), new ULocale("en-IN"), "87,650,000", @@ -1194,7 +1194,7 @@ public class NumberFormatterApiTest { // If this test breaks due to data changes, find another locale that has minimumGroupingDigits. assertFormatDescendingBig( "Hungarian Grouping", - "", + "group-auto", NumberFormatter.with().grouping(GroupingStrategy.AUTO), new ULocale("hu"), "87 650 000", @@ -1209,7 +1209,7 @@ public class NumberFormatterApiTest { assertFormatDescendingBig( "Hungarian Grouping, Min 2", - "", + "group-min2", NumberFormatter.with().grouping(GroupingStrategy.MIN2), new ULocale("hu"), "87 650 000", @@ -1224,7 +1224,7 @@ public class NumberFormatterApiTest { assertFormatDescendingBig( "Hungarian Grouping, Always", - "", + "group-on-aligned", NumberFormatter.with().grouping(GroupingStrategy.ON_ALIGNED), new ULocale("hu"), "87 650 000", @@ -1241,7 +1241,7 @@ public class NumberFormatterApiTest { // If this test breaks due to data changes, find another locale that has no default grouping. assertFormatDescendingBig( "Bulgarian Currency Grouping", - "", + "currency/USD group-auto", NumberFormatter.with().grouping(GroupingStrategy.AUTO).unit(USD), new ULocale("bg"), "87650000,00 щ.д.", @@ -1256,7 +1256,7 @@ public class NumberFormatterApiTest { assertFormatDescendingBig( "Bulgarian Currency Grouping, Always", - "", + "currency/USD group-on-aligned", NumberFormatter.with().grouping(GroupingStrategy.ON_ALIGNED).unit(USD), new ULocale("bg"), "87 650 000,00 щ.д.", @@ -1273,7 +1273,7 @@ public class NumberFormatterApiTest { macros.grouping = Grouper.getInstance((short) 4, (short) 1, (short) 3); assertFormatDescendingBig( "Custom Grouping via Internal API", - "", + null, NumberFormatter.with().macros(macros), ULocale.ENGLISH, "8,7,6,5,0000", @@ -1291,7 +1291,7 @@ public class NumberFormatterApiTest { public void padding() { assertFormatDescending( "Padding", - "", + null, NumberFormatter.with().padding(Padder.none()), ULocale.ENGLISH, "87,650", @@ -1306,7 +1306,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Padding", - "", + null, NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.AFTER_PREFIX)), ULocale.ENGLISH, "**87,650", @@ -1321,7 +1321,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Padding with code points", - "", + null, NumberFormatter.with().padding(Padder.codePoints(0x101E4, 8, PadPosition.AFTER_PREFIX)), ULocale.ENGLISH, "𐇤𐇤87,650", @@ -1336,7 +1336,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Padding with wide digits", - "symbols=ns:mathsanb", + null, NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.AFTER_PREFIX)) .symbols(NumberingSystem.getInstanceByName("mathsanb")), ULocale.ENGLISH, @@ -1352,7 +1352,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Padding with currency spacing", - "$GBP unit-width=ISO_CODE", + null, NumberFormatter.with().padding(Padder.codePoints('*', 10, PadPosition.AFTER_PREFIX)).unit(GBP) .unitWidth(UnitWidth.ISO_CODE), ULocale.ENGLISH, @@ -1368,7 +1368,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Pad Before Prefix", - "", + null, NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.BEFORE_PREFIX)), ULocale.ENGLISH, -88.88, @@ -1376,7 +1376,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Pad After Prefix", - "", + null, NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.AFTER_PREFIX)), ULocale.ENGLISH, -88.88, @@ -1384,7 +1384,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Pad Before Suffix", - "%", + null, NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.BEFORE_SUFFIX)) .unit(NoUnit.PERCENT), ULocale.ENGLISH, @@ -1393,7 +1393,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Pad After Suffix", - "%", + null, NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.AFTER_SUFFIX)) .unit(NoUnit.PERCENT), ULocale.ENGLISH, @@ -1402,7 +1402,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Currency Spacing with Zero Digit Padding Broken", - "$GBP unit-width=ISO_CODE", + null, NumberFormatter.with().padding(Padder.codePoints('0', 12, PadPosition.AFTER_PREFIX)).unit(GBP) .unitWidth(UnitWidth.ISO_CODE), ULocale.ENGLISH, @@ -1414,7 +1414,7 @@ public class NumberFormatterApiTest { public void integerWidth() { assertFormatDescending( "Integer Width Default", - "integer-width=1-", + "integer-width/+0", NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(1)), ULocale.ENGLISH, "87,650", @@ -1429,7 +1429,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Integer Width Zero Fill 0", - "integer-width=0-", + "integer-width/+", NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(0)), ULocale.ENGLISH, "87,650", @@ -1444,7 +1444,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Integer Width Zero Fill 3", - "integer-width=3-", + "integer-width/+000", NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(3)), ULocale.ENGLISH, "87,650", @@ -1459,7 +1459,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Integer Width Max 3", - "integer-width=1-3", + "integer-width/##0", NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(1).truncateAt(3)), ULocale.ENGLISH, "650", @@ -1474,7 +1474,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Integer Width Fixed 2", - "integer-width=2", + "integer-width/00", NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(2).truncateAt(2)), ULocale.ENGLISH, "50", @@ -1492,7 +1492,7 @@ public class NumberFormatterApiTest { public void symbols() { assertFormatDescending( "French Symbols with Japanese Data 1", - "symbols=loc:fr", + null, NumberFormatter.with().symbols(DecimalFormatSymbols.getInstance(ULocale.FRENCH)), ULocale.JAPAN, "87 650", @@ -1507,7 +1507,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "French Symbols with Japanese Data 2", - "C symbols=loc:fr", + null, NumberFormatter.with().notation(Notation.compactShort()) .symbols(DecimalFormatSymbols.getInstance(ULocale.FRENCH)), ULocale.JAPAN, @@ -1516,7 +1516,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Latin Numbering System with Arabic Data", - "$USD symbols=ns:latn", + "currency/USD latin", NumberFormatter.with().symbols(NumberingSystem.LATIN).unit(USD), new ULocale("ar"), "US$ 87,650.00", @@ -1531,7 +1531,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Math Numbering System with French Data", - "symbols=ns:mathsanb", + "numbering-system/mathsanb", NumberFormatter.with().symbols(NumberingSystem.getInstanceByName("mathsanb")), ULocale.FRENCH, "𝟴𝟳 𝟲𝟱𝟬", @@ -1546,7 +1546,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Swiss Symbols (used in documentation)", - "symbols=loc:de_CH", + null, NumberFormatter.with().symbols(DecimalFormatSymbols.getInstance(new ULocale("de-CH"))), ULocale.ENGLISH, 12345.67, @@ -1554,7 +1554,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Myanmar Symbols (used in documentation)", - "symbols=loc:my_MY", + null, NumberFormatter.with().symbols(DecimalFormatSymbols.getInstance(new ULocale("my_MY"))), ULocale.ENGLISH, 12345.67, @@ -1564,7 +1564,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Currency symbol should precede number in ar with NS latn", - "", + "currency/USD latin", NumberFormatter.with().symbols(NumberingSystem.LATIN).unit(USD), new ULocale("ar"), 12345.67, @@ -1572,7 +1572,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Currency symbol should precede number in ar@numbers=latn", - "", + "currency/USD", NumberFormatter.with().unit(USD), new ULocale("ar@numbers=latn"), 12345.67, @@ -1580,7 +1580,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Currency symbol should follow number in ar-EG with NS arab", - "", + "currency/USD", NumberFormatter.with().unit(USD), new ULocale("ar-EG"), 12345.67, @@ -1588,7 +1588,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Currency symbol should follow number in ar@numbers=arab", - "", + "currency/USD", NumberFormatter.with().unit(USD), new ULocale("ar@numbers=arab"), 12345.67, @@ -1596,7 +1596,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "NumberingSystem in API should win over @numbers keyword", - "", + "currency/USD latin", NumberFormatter.with().symbols(NumberingSystem.LATIN).unit(USD), new ULocale("ar@numbers=arab"), 12345.67, @@ -1615,7 +1615,7 @@ public class NumberFormatterApiTest { symbols.setGroupingSeparatorString("!"); assertFormatSingle( "Symbols object should be copied", - "symbols=loc:de_CH", + null, f, ULocale.ENGLISH, 12345.67, @@ -1623,7 +1623,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "The last symbols setter wins", - "symbols=ns:latn", + "latin", NumberFormatter.with().symbols(symbols).symbols(NumberingSystem.LATIN), ULocale.ENGLISH, 12345.67, @@ -1631,7 +1631,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "The last symbols setter wins", - "symbols=loc:de_CH", + null, NumberFormatter.with().symbols(NumberingSystem.LATIN).symbols(symbols), ULocale.ENGLISH, 12345.67, @@ -1657,7 +1657,7 @@ public class NumberFormatterApiTest { public void sign() { assertFormatSingle( "Sign Auto Positive", - "sign=AUTO", + "sign-auto", NumberFormatter.with().sign(SignDisplay.AUTO), ULocale.ENGLISH, 444444, @@ -1665,7 +1665,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Auto Negative", - "sign=AUTO", + "sign-auto", NumberFormatter.with().sign(SignDisplay.AUTO), ULocale.ENGLISH, -444444, @@ -1673,7 +1673,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Auto Zero", - "", + "sign-auto", NumberFormatter.with().sign(SignDisplay.AUTO), ULocale.ENGLISH, 0, @@ -1681,7 +1681,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Always Positive", - "sign=ALWAYS", + "sign-always", NumberFormatter.with().sign(SignDisplay.ALWAYS), ULocale.ENGLISH, 444444, @@ -1689,7 +1689,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Always Negative", - "sign=ALWAYS", + "sign-always", NumberFormatter.with().sign(SignDisplay.ALWAYS), ULocale.ENGLISH, -444444, @@ -1697,7 +1697,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Always Zero", - "", + "sign-always", NumberFormatter.with().sign(SignDisplay.ALWAYS), ULocale.ENGLISH, 0, @@ -1705,7 +1705,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Never Positive", - "sign=NEVER", + "sign-never", NumberFormatter.with().sign(SignDisplay.NEVER), ULocale.ENGLISH, 444444, @@ -1713,7 +1713,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Never Negative", - "sign=NEVER", + "sign-never", NumberFormatter.with().sign(SignDisplay.NEVER), ULocale.ENGLISH, -444444, @@ -1721,7 +1721,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Never Zero", - "", + "sign-never", NumberFormatter.with().sign(SignDisplay.NEVER), ULocale.ENGLISH, 0, @@ -1729,7 +1729,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Accounting Positive", - "$USD sign=ACCOUNTING", + "currency/USD sign-accounting", NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD), ULocale.ENGLISH, 444444, @@ -1737,7 +1737,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Accounting Negative", - "$USD sign=ACCOUNTING", + "currency/USD sign-accounting", NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD), ULocale.ENGLISH, -444444, @@ -1745,7 +1745,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Accounting Zero", - "", + "currency/USD sign-accounting", NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD), ULocale.ENGLISH, 0, @@ -1753,7 +1753,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Accounting-Always Positive", - "$USD sign=ACCOUNTING_ALWAYS", + "currency/USD sign-accounting-always", NumberFormatter.with().sign(SignDisplay.ACCOUNTING_ALWAYS).unit(USD), ULocale.ENGLISH, 444444, @@ -1761,7 +1761,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Accounting-Always Negative", - "$USD sign=ACCOUNTING_ALWAYS", + "currency/USD sign-accounting-always", NumberFormatter.with().sign(SignDisplay.ACCOUNTING_ALWAYS).unit(USD), ULocale.ENGLISH, -444444, @@ -1769,7 +1769,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Accounting-Always Zero", - "", + "currency/USD sign-accounting-always", NumberFormatter.with().sign(SignDisplay.ACCOUNTING_ALWAYS).unit(USD), ULocale.ENGLISH, 0, @@ -1777,7 +1777,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Except-Zero Positive", - "", + "sign-except-zero", NumberFormatter.with().sign(SignDisplay.EXCEPT_ZERO), ULocale.ENGLISH, 444444, @@ -1785,7 +1785,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Always Negative", - "", + "sign-except-zero", NumberFormatter.with().sign(SignDisplay.EXCEPT_ZERO), ULocale.ENGLISH, -444444, @@ -1793,7 +1793,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Except-Zero Zero", - "", + "sign-except-zero", NumberFormatter.with().sign(SignDisplay.EXCEPT_ZERO), ULocale.ENGLISH, 0, @@ -1801,7 +1801,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Accounting-Except-Zero Positive", - "$USD sign=ACCOUNTING_ALWAYS", + "currency/USD sign-accounting-except-zero", NumberFormatter.with().sign(SignDisplay.ACCOUNTING_EXCEPT_ZERO).unit(USD), ULocale.ENGLISH, 444444, @@ -1809,7 +1809,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Accounting-Except-Zero Negative", - "$USD sign=ACCOUNTING_ALWAYS", + "currency/USD sign-accounting-except-zero", NumberFormatter.with().sign(SignDisplay.ACCOUNTING_EXCEPT_ZERO).unit(USD), ULocale.ENGLISH, -444444, @@ -1817,7 +1817,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Accounting-Except-Zero Zero", - "", + "currency/USD sign-accounting-except-zero", NumberFormatter.with().sign(SignDisplay.ACCOUNTING_EXCEPT_ZERO).unit(USD), ULocale.ENGLISH, 0, @@ -1825,7 +1825,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Accounting Negative Hidden", - "$USD unit-width=HIDDEN sign=ACCOUNTING", + "currency/USD unit-width-hidden sign-accounting", NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD).unitWidth(UnitWidth.HIDDEN), ULocale.ENGLISH, -444444, @@ -1836,7 +1836,7 @@ public class NumberFormatterApiTest { public void decimal() { assertFormatDescending( "Decimal Default", - "decimal=AUTO", + "decimal-auto", NumberFormatter.with().decimal(DecimalSeparatorDisplay.AUTO), ULocale.ENGLISH, "87,650", @@ -1851,7 +1851,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Decimal Always Shown", - "decimal=ALWAYS", + "decimal-always", NumberFormatter.with().decimal(DecimalSeparatorDisplay.ALWAYS), ULocale.ENGLISH, "87,650.", @@ -1880,7 +1880,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Plural 1", - "$USD F0 unit-width=FULL_NAME", + "currency/USD round-integer unit-width-full-name", NumberFormatter.with().unit(USD).unitWidth(UnitWidth.FULL_NAME).rounding(Rounder.fixedFraction(0)), ULocale.ENGLISH, 1, @@ -1888,7 +1888,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Plural 1.00", - "$USD F2 unit-width=FULL_NAME", + "currency/USD .00 unit-width-full-name", NumberFormatter.with().unit(USD).unitWidth(UnitWidth.FULL_NAME).rounding(Rounder.fixedFraction(2)), ULocale.ENGLISH, 1, @@ -2036,18 +2036,23 @@ public class NumberFormatterApiTest { double[] inputs, String... expected) { assert expected.length == 9; - assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton()); LocalizedNumberFormatter l1 = f.threshold(0L).locale(locale); // no self-regulation LocalizedNumberFormatter l2 = f.threshold(1L).locale(locale); // all self-regulation - LocalizedNumberFormatter l3 = NumberFormatter.fromSkeleton(skeleton).locale(locale); for (int i = 0; i < 9; i++) { double d = inputs[i]; String actual1 = l1.format(d).toString(); assertEquals(message + ": Unsafe Path: " + d, expected[i], actual1); String actual2 = l2.format(d).toString(); assertEquals(message + ": Safe Path: " + d, expected[i], actual2); - String actual3 = l3.format(d).toString(); - assertEquals(message + ": Skeleton Path: " + d, expected[i], actual3); + } + if (skeleton != null) { // if null, skeleton is declared as undefined. + assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton()); + LocalizedNumberFormatter l3 = NumberFormatter.fromSkeleton(skeleton).locale(locale); + for (int i = 0; i < 9; i++) { + double d = inputs[i]; + String actual3 = l3.format(d).toString(); + assertEquals(message + ": Skeleton Path: " + d, expected[i], actual3); + } } } @@ -2058,16 +2063,18 @@ public class NumberFormatterApiTest { ULocale locale, Number input, String expected) { - assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton()); LocalizedNumberFormatter l1 = f.threshold(0L).locale(locale); // no self-regulation LocalizedNumberFormatter l2 = f.threshold(1L).locale(locale); // all self-regulation - LocalizedNumberFormatter l3 = NumberFormatter.fromSkeleton(skeleton).locale(locale); String actual1 = l1.format(input).toString(); assertEquals(message + ": Unsafe Path: " + input, expected, actual1); String actual2 = l2.format(input).toString(); assertEquals(message + ": Safe Path: " + input, expected, actual2); - String actual3 = l3.format(input).toString(); - assertEquals(message + ": Skeleton Path: " + input, expected, actual3); + if (skeleton != null) { // if null, skeleton is declared as undefined. + assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton()); + LocalizedNumberFormatter l3 = NumberFormatter.fromSkeleton(skeleton).locale(locale); + String actual3 = l3.format(input).toString(); + assertEquals(message + ": Skeleton Path: " + input, expected, actual3); + } } private static void assertFormatSingleMeasure( @@ -2077,15 +2084,17 @@ public class NumberFormatterApiTest { ULocale locale, Measure input, String expected) { - assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton()); LocalizedNumberFormatter l1 = f.threshold(0L).locale(locale); // no self-regulation LocalizedNumberFormatter l2 = f.threshold(1L).locale(locale); // all self-regulation - LocalizedNumberFormatter l3 = NumberFormatter.fromSkeleton(skeleton).locale(locale); String actual1 = l1.format(input).toString(); assertEquals(message + ": Unsafe Path: " + input, expected, actual1); String actual2 = l2.format(input).toString(); assertEquals(message + ": Safe Path: " + input, expected, actual2); - String actual3 = l3.format(input).toString(); - assertEquals(message + ": Skeleton Path: " + input, expected, actual3); + if (skeleton != null) { // if null, skeleton is declared as undefined. + assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton()); + LocalizedNumberFormatter l3 = NumberFormatter.fromSkeleton(skeleton).locale(locale); + String actual3 = l3.format(input).toString(); + assertEquals(message + ": Skeleton Path: " + input, expected, actual3); + } } } 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 6735271ab4..6bb084c48b 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 @@ -38,7 +38,11 @@ public class NumberSkeletonTest { "@#+", "round-increment/xxx", "round-increment/0.1.2", - }; + "currency/dummy", + "measure-unit/foo", + "integer-width/xxx", + "integer-width/0+", + "integer-width/+0#", }; for (String cas : cases) { try { @@ -51,19 +55,33 @@ public class NumberSkeletonTest { } @Test - public void stemsRequiringOption() { - String[] cases = { - "round-increment", - "round-increment/", - "round-increment scientific", - }; + public void unknownTokens() { + String[] cases = { "measure-unit/foo-bar", "numbering-system/dummy" }; for (String cas : cases) { try { NumberFormatter.fromSkeleton(cas); fail(); } catch (SkeletonSyntaxException expected) { - assertTrue(expected.getMessage(), expected.getMessage().contains("requires an option")); + assertTrue(expected.getMessage(), expected.getMessage().contains("Unknown")); + } + } + } + + @Test + public void stemsRequiringOption() { + String[] stems = { "round-increment", "currency", "measure-unit", "integer-width", }; + String[] suffixes = { "", "/", " scientific", "/ scientific" }; + + for (String stem : stems) { + for (String suffix : suffixes) { + try { + NumberFormatter.fromSkeleton(stem + suffix); + fail(); + } catch (SkeletonSyntaxException expected) { + assertTrue(expected.getMessage(), + expected.getMessage().contains("requires an option")); + } } } } From c2fa8cacad7fdf0c929b5686306c68cd88360837 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Sat, 3 Mar 2018 02:54:24 +0000 Subject: [PATCH 025/129] ICU-8610 Adding more tests; normalized skeleton implementation; minor tweaks. X-SVN-Rev: 41054 --- .../src/com/ibm/icu/number/IntegerWidth.java | 2 + .../com/ibm/icu/number/NumberFormatter.java | 1 + .../ibm/icu/number/NumberFormatterImpl.java | 2 +- .../icu/number/NumberFormatterSettings.java | 9 + .../ibm/icu/number/NumberSkeletonImpl.java | 347 ++++++++++-------- .../core/src/com/ibm/icu/number/Rounder.java | 4 +- .../test/number/NumberFormatterApiTest.java | 39 +- .../dev/test/number/NumberSkeletonTest.java | 120 +++++- 8 files changed, 369 insertions(+), 155 deletions(-) diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/IntegerWidth.java b/icu4j/main/classes/core/src/com/ibm/icu/number/IntegerWidth.java index db59f234c9..b01a42cb7c 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/IntegerWidth.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/IntegerWidth.java @@ -70,6 +70,8 @@ public class IntegerWidth { return this; } else if (maxInt >= 0 && maxInt <= RoundingUtils.MAX_INT_FRAC_SIG && maxInt >= minInt) { return new IntegerWidth(minInt, maxInt); + } else if (minInt == 1 && maxInt == -1) { + return DEFAULT; } else if (maxInt == -1) { return new IntegerWidth(minInt, -1); } else { 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 552873fc31..d44d6f21f3 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 @@ -460,6 +460,7 @@ public final class NumberFormatter { * @param skeleton * The skeleton string off of which to base this NumberFormatter. * @return An {@link UnlocalizedNumberFormatter}, to be used for chaining. + * @throws SkeletonSyntaxException If the given string is not a valid number formatting skeleton. * @draft ICU 62 * @provisional This API might change or be removed in a future release. */ 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 c0cf83f2de..e8d8d238b1 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 @@ -194,7 +194,7 @@ class NumberFormatterImpl { } else if (isCurrency) { micros.rounding = Rounder.MONETARY_STANDARD; } else { - micros.rounding = Rounder.MAX_FRAC_6; + micros.rounding = Rounder.DEFAULT_MAX_FRAC_6; } micros.rounding = micros.rounding.withLocaleData(currency); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java index 22175dfd35..88033d70a3 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java @@ -478,8 +478,17 @@ public abstract class NumberFormatterSettings + * Not all options are capable of being represented in the skeleton string; for example, a + * DecimalFormatSymbols object. If any such option is encountered, an + * {@link UnsupportedOperationException} is thrown. + *

+ * The returned skeleton is in normalized form, such that two number formatters with equivalent + * behavior should produce the same skeleton. * * @return A number skeleton string with behavior corresponding to this number formatter. + * @throws UnsupportedOperationException + * If the number formatter has an option that cannot be represented in a skeleton string. * @draft ICU 62 * @provisional This API might change or be removed in a future release. */ 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 896c245eb6..378087288c 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 @@ -7,15 +7,17 @@ import java.math.RoundingMode; import java.util.HashMap; import java.util.Map; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import com.ibm.icu.impl.CacheBase; import com.ibm.icu.impl.PatternProps; +import com.ibm.icu.impl.SoftCache; import com.ibm.icu.impl.StringSegment; import com.ibm.icu.impl.number.MacroProps; import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay; import com.ibm.icu.number.NumberFormatter.GroupingStrategy; import com.ibm.icu.number.NumberFormatter.SignDisplay; import com.ibm.icu.number.NumberFormatter.UnitWidth; +import com.ibm.icu.text.DecimalFormatSymbols; import com.ibm.icu.text.NumberingSystem; import com.ibm.icu.util.Currency; import com.ibm.icu.util.Currency.CurrencyUsage; @@ -83,52 +85,64 @@ class NumberSkeletonImpl { static final SkeletonDataStructure skeletonData = new SkeletonDataStructure(); static { - skeletonData.put(StemType.COMPACT_NOTATION, "compact-short", Notation.compactShort()); - skeletonData.put(StemType.COMPACT_NOTATION, "compact-long", Notation.compactLong()); - skeletonData.put(StemType.SCIENTIFIC_NOTATION, "scientific", Notation.scientific()); - skeletonData.put(StemType.SCIENTIFIC_NOTATION, "engineering", Notation.engineering()); - skeletonData.put(StemType.SIMPLE_NOTATION, "simple-notation", Notation.simple()); + SkeletonDataStructure d = skeletonData; // abbreviate for shorter lines + d.put(StemType.COMPACT_NOTATION, "compact-short", Notation.compactShort()); + d.put(StemType.COMPACT_NOTATION, "compact-long", Notation.compactLong()); + d.put(StemType.SCIENTIFIC_NOTATION, "scientific", Notation.scientific()); + d.put(StemType.SCIENTIFIC_NOTATION, "engineering", Notation.engineering()); + d.put(StemType.SIMPLE_NOTATION, "notation-simple", Notation.simple()); - skeletonData.put(StemType.NO_UNIT, "base-unit", NoUnit.BASE); - skeletonData.put(StemType.NO_UNIT, "percent", NoUnit.PERCENT); - skeletonData.put(StemType.NO_UNIT, "permille", NoUnit.PERMILLE); + d.put(StemType.NO_UNIT, "base-unit", NoUnit.BASE); + d.put(StemType.NO_UNIT, "percent", NoUnit.PERCENT); + d.put(StemType.NO_UNIT, "permille", NoUnit.PERMILLE); - skeletonData.put(StemType.ROUNDER, "round-integer", Rounder.integer()); - skeletonData.put(StemType.ROUNDER, "round-unlimited", Rounder.unlimited()); - skeletonData.put(StemType.ROUNDER, - "round-currency-standard", - Rounder.currency(CurrencyUsage.STANDARD)); - skeletonData.put(StemType.ROUNDER, "round-currency-cash", Rounder.currency(CurrencyUsage.CASH)); + d.put(StemType.ROUNDER, "round-integer", Rounder.integer()); + d.put(StemType.ROUNDER, "round-unlimited", Rounder.unlimited()); + d.put(StemType.ROUNDER, "round-currency-standard", Rounder.currency(CurrencyUsage.STANDARD)); + d.put(StemType.ROUNDER, "round-currency-cash", Rounder.currency(CurrencyUsage.CASH)); - skeletonData.put(StemType.GROUPING, "group-off", GroupingStrategy.OFF); - skeletonData.put(StemType.GROUPING, "group-min2", GroupingStrategy.MIN2); - skeletonData.put(StemType.GROUPING, "group-auto", GroupingStrategy.AUTO); - skeletonData.put(StemType.GROUPING, "group-on-aligned", GroupingStrategy.ON_ALIGNED); - skeletonData.put(StemType.GROUPING, "group-thousands", GroupingStrategy.THOUSANDS); + d.put(StemType.GROUPING, "group-off", GroupingStrategy.OFF); + d.put(StemType.GROUPING, "group-min2", GroupingStrategy.MIN2); + d.put(StemType.GROUPING, "group-auto", GroupingStrategy.AUTO); + d.put(StemType.GROUPING, "group-on-aligned", GroupingStrategy.ON_ALIGNED); + d.put(StemType.GROUPING, "group-thousands", GroupingStrategy.THOUSANDS); - skeletonData.put(StemType.LATIN, "latin", NumberingSystem.LATIN); + d.put(StemType.LATIN, "latin", NumberingSystem.LATIN); - skeletonData.put(StemType.UNIT_WIDTH, "unit-width-narrow", UnitWidth.NARROW); - skeletonData.put(StemType.UNIT_WIDTH, "unit-width-short", UnitWidth.SHORT); - skeletonData.put(StemType.UNIT_WIDTH, "unit-width-full-name", UnitWidth.FULL_NAME); - skeletonData.put(StemType.UNIT_WIDTH, "unit-width-iso-code", UnitWidth.ISO_CODE); - skeletonData.put(StemType.UNIT_WIDTH, "unit-width-hidden", UnitWidth.HIDDEN); + d.put(StemType.UNIT_WIDTH, "unit-width-narrow", UnitWidth.NARROW); + d.put(StemType.UNIT_WIDTH, "unit-width-short", UnitWidth.SHORT); + d.put(StemType.UNIT_WIDTH, "unit-width-full-name", UnitWidth.FULL_NAME); + d.put(StemType.UNIT_WIDTH, "unit-width-iso-code", UnitWidth.ISO_CODE); + d.put(StemType.UNIT_WIDTH, "unit-width-hidden", UnitWidth.HIDDEN); - skeletonData.put(StemType.SIGN_DISPLAY, "sign-auto", SignDisplay.AUTO); - skeletonData.put(StemType.SIGN_DISPLAY, "sign-always", SignDisplay.ALWAYS); - skeletonData.put(StemType.SIGN_DISPLAY, "sign-never", SignDisplay.NEVER); - skeletonData.put(StemType.SIGN_DISPLAY, "sign-accounting", SignDisplay.ACCOUNTING); - skeletonData.put(StemType.SIGN_DISPLAY, "sign-accounting-always", SignDisplay.ACCOUNTING_ALWAYS); - skeletonData.put(StemType.SIGN_DISPLAY, "sign-except-zero", SignDisplay.EXCEPT_ZERO); - skeletonData.put(StemType.SIGN_DISPLAY, - "sign-accounting-except-zero", - SignDisplay.ACCOUNTING_EXCEPT_ZERO); + d.put(StemType.SIGN_DISPLAY, "sign-auto", SignDisplay.AUTO); + d.put(StemType.SIGN_DISPLAY, "sign-always", SignDisplay.ALWAYS); + d.put(StemType.SIGN_DISPLAY, "sign-never", SignDisplay.NEVER); + d.put(StemType.SIGN_DISPLAY, "sign-accounting", SignDisplay.ACCOUNTING); + d.put(StemType.SIGN_DISPLAY, "sign-accounting-always", SignDisplay.ACCOUNTING_ALWAYS); + d.put(StemType.SIGN_DISPLAY, "sign-except-zero", SignDisplay.EXCEPT_ZERO); + d.put(StemType.SIGN_DISPLAY, "sign-accounting-except-zero", SignDisplay.ACCOUNTING_EXCEPT_ZERO); - skeletonData.put(StemType.DECIMAL_DISPLAY, "decimal-auto", DecimalSeparatorDisplay.AUTO); - skeletonData.put(StemType.DECIMAL_DISPLAY, "decimal-always", DecimalSeparatorDisplay.ALWAYS); + d.put(StemType.DECIMAL_DISPLAY, "decimal-auto", DecimalSeparatorDisplay.AUTO); + d.put(StemType.DECIMAL_DISPLAY, "decimal-always", DecimalSeparatorDisplay.ALWAYS); } - private static final Map cache = new ConcurrentHashMap(); + static final String[] ROUNDING_MODE_STRINGS = { + "up", + "down", + "ceiling", + "floor", + "half-up", + "half-down", + "half-even", + "unnecessary" }; + + private static final CacheBase cache = new SoftCache() { + @Override + protected UnlocalizedNumberFormatter createInstance(String skeletonString, Void unused) { + return create(skeletonString); + } + }; /** * Gets the number formatter for the given number skeleton string from the cache, creating it if it @@ -139,43 +153,9 @@ class NumberSkeletonImpl { * @return An UnlocalizedNumberFormatter with behavior defined by the given skeleton string. */ public static UnlocalizedNumberFormatter getOrCreate(String skeletonString) { - String unNormalized = skeletonString; // more appropriate variable name for the implementation - - // First try: look up the un-normalized skeleton. - UnlocalizedNumberFormatter formatter = cache.get(unNormalized); - if (formatter != null) { - return formatter; - } - - // Second try: normalize the skeleton, and then access the cache. - // Store the un-normalized form for a faster lookup next time. - // Synchronize because we need a transaction with multiple queries to the cache. - String normalized = normalizeSkeleton(unNormalized); - if (cache.containsKey(normalized)) { - synchronized (cache) { - formatter = cache.get(normalized); - if (formatter != null) { - cache.putIfAbsent(unNormalized, formatter); - } - } - } - if (formatter != null) { - return formatter; - } - - // Third try: create the formatter, store it in the cache, and return it. - formatter = create(normalized); - - // Synchronize because we need a transaction with multiple queries to the cache. - synchronized (cache) { - if (cache.containsKey(normalized)) { - formatter = cache.get(normalized); - } else { - cache.put(normalized, formatter); - } - cache.putIfAbsent(unNormalized, formatter); - } - return formatter; + // TODO: This does not currently check the cache for the normalized form of the skeleton. + // A new cache implementation would be required for that to work. + return cache.getInstance(skeletonString, null); } /** @@ -190,24 +170,19 @@ class NumberSkeletonImpl { return NumberFormatter.with().macros(macros); } + /** + * Create a skeleton string corresponding to the given NumberFormatter. + * + * @param macros + * The NumberFormatter options object. + * @return A skeleton string in normalized form. + */ public static String generate(MacroProps macros) { StringBuilder sb = new StringBuilder(); generateSkeleton(macros, sb); return sb.toString(); } - /** - * Normalizes a number skeleton string to the shortest equivalent form. - * - * @param skeletonString - * A number skeleton string, possibly not in its shortest form. - * @return An equivalent and possibly simplified skeleton string. - */ - public static String normalizeSkeleton(String skeletonString) { - // FIXME - return skeletonString; - } - ///// private static MacroProps parseSkeleton(String skeletonString) { @@ -396,51 +371,60 @@ class NumberSkeletonImpl { } // Unknown option - throw new SkeletonSyntaxException("Unknown option", content); + throw new SkeletonSyntaxException("Invalid option", content); } private static void generateSkeleton(MacroProps macros, StringBuilder sb) { - if (macros.notation != null) { - generateNotationValue(macros, sb); + // Supported options + if (macros.notation != null && generateNotationValue(macros, sb)) { sb.append(' '); } - if (macros.unit != null) { - generateUnitValue(macros, sb); + if (macros.unit != null && generateUnitValue(macros, sb)) { sb.append(' '); } - if (macros.perUnit != null) { - generatePerUnitValue(macros, sb); + if (macros.perUnit != null && generatePerUnitValue(macros, sb)) { sb.append(' '); } - if (macros.rounder != null) { - generateRoundingValue(macros, sb); + if (macros.rounder != null && generateRoundingValue(macros, sb)) { sb.append(' '); } - if (macros.grouping != null) { - generateGroupingValue(macros, sb); + if (macros.grouping != null && generateGroupingValue(macros, sb)) { sb.append(' '); } - if (macros.integerWidth != null) { - generateIntegerWidthValue(macros, sb); + if (macros.integerWidth != null && generateIntegerWidthValue(macros, sb)) { sb.append(' '); } - if (macros.symbols != null) { - generateSymbolsValue(macros, sb); + if (macros.symbols != null && generateSymbolsValue(macros, sb)) { sb.append(' '); } - if (macros.unitWidth != null) { - generateUnitWidthValue(macros, sb); + if (macros.unitWidth != null && generateUnitWidthValue(macros, sb)) { sb.append(' '); } - if (macros.sign != null) { - generateSignValue(macros, sb); + if (macros.sign != null && generateSignValue(macros, sb)) { sb.append(' '); } - if (macros.decimal != null) { - generateDecimalValue(macros, sb); + if (macros.decimal != null && generateDecimalValue(macros, sb)) { sb.append(' '); } + // Unsupported options + if (macros.padder != null) { + throw new UnsupportedOperationException( + "Cannot generate number skeleton with custom padder"); + } + if (macros.affixProvider != null) { + throw new UnsupportedOperationException( + "Cannot generate number skeleton with custom affix provider"); + } + if (macros.multiplier != null) { + throw new UnsupportedOperationException( + "Cannot generate number skeleton with custom multiplier"); + } + if (macros.rules != null) { + throw new UnsupportedOperationException( + "Cannot generate number skeleton with custom plural rules"); + } + // Remove the trailing space if (sb.length() > 0) { sb.setLength(sb.length() - 1); @@ -640,15 +624,51 @@ class NumberSkeletonImpl { if (content.charAt(0) != '@') { return false; } - FractionRounder oldRounder = (FractionRounder) macros.rounder; - // A little bit of a hack: parse the option as a digits stem, and extract the min/max sig from - // the new Rounder saved into the macros. - parseDigitsStem(content, macros); - Rounder.SignificantRounderImpl intermediate = (Rounder.SignificantRounderImpl) macros.rounder; - if (intermediate.maxSig == -1) { - macros.rounder = oldRounder.withMinDigits(intermediate.minSig); + int offset = 0; + int minSig = 0; + int maxSig; + for (; offset < content.length(); offset++) { + if (content.charAt(offset) == '@') { + minSig++; + } else { + break; + } + } + // For the frac-sig option, there must be minSig or maxSig but not both. + // Valid: @+, @@+, @@@+ + // Valid: @#, @##, @### + // Invalid: @, @@, @@@ + // Invalid: @@#, @@##, @@@# + if (offset < content.length()) { + if (content.charAt(offset) == '+') { + maxSig = -1; + offset++; + } else if (minSig > 1) { + // @@#, @@##, @@@# + throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", content); + } else { + maxSig = minSig; + for (; offset < content.length(); offset++) { + if (content.charAt(offset) == '#') { + maxSig++; + } else { + break; + } + } + } } else { - macros.rounder = oldRounder.withMaxDigits(intermediate.maxSig); + // @, @@, @@@ + throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", content); + } + if (offset < content.length()) { + throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", content); + } + + FractionRounder oldRounder = (FractionRounder) macros.rounder; + if (maxSig == -1) { + macros.rounder = oldRounder.withMinDigits(minSig); + } else { + macros.rounder = oldRounder.withMaxDigits(maxSig); } return true; } @@ -670,11 +690,9 @@ class NumberSkeletonImpl { } private static boolean parseRoundingModeOption(CharSequence content, MacroProps macros) { - // Iterate over int modes instead of enum modes for performance - for (int rm = 0; rm <= BigDecimal.ROUND_UNNECESSARY; rm++) { - RoundingMode mode = RoundingMode.valueOf(rm); - if (content.equals(mode.toString())) { - macros.rounder = macros.rounder.withMode(mode); + for (int rm = 0; rm < ROUNDING_MODE_STRINGS.length; rm++) { + if (content.equals(ROUNDING_MODE_STRINGS[rm])) { + macros.rounder = macros.rounder.withMode(RoundingMode.valueOf(rm)); return true; } } @@ -682,7 +700,8 @@ class NumberSkeletonImpl { } private static void generateRoundingModeOption(RoundingMode mode, StringBuilder sb) { - sb.append(mode.toString()); + String option = ROUNDING_MODE_STRINGS[mode.ordinal()]; + sb.append(option); } private static void parseIntegerWidthOption(CharSequence content, MacroProps macros) { @@ -749,18 +768,22 @@ class NumberSkeletonImpl { ///// - private static void generateNotationValue(MacroProps macros, StringBuilder sb) { + private static boolean generateNotationValue(MacroProps macros, StringBuilder sb) { // Check for literals String literal = skeletonData.valueToStem(macros.notation); - if (literal != null) { + if ("notation-simple".equals(literal)) { + return false; // Default value + } else if (literal != null) { sb.append(literal); - return; + return true; } // Generate the stem if (macros.notation instanceof CompactNotation) { // Compact notation generated from custom data (not supported in skeleton) // The other compact notations are literals + throw new UnsupportedOperationException( + "Cannot generate number skeleton with custom compact data"); } else if (macros.notation instanceof ScientificNotation) { ScientificNotation impl = (ScientificNotation) macros.notation; if (impl.engineeringInterval == 3) { @@ -776,49 +799,56 @@ class NumberSkeletonImpl { sb.append('/'); sb.append(skeletonData.valueToStem(impl.exponentSignDisplay)); } + return true; } else { - assert macros.notation instanceof SimpleNotation; - sb.append("notation-simple"); + // SimpleNotation should be handled by a literal + throw new AssertionError(); } } - private static void generateUnitValue(MacroProps macros, StringBuilder sb) { + private static boolean generateUnitValue(MacroProps macros, StringBuilder sb) { // Check for literals String literal = skeletonData.valueToStem(macros.unit); - if (literal != null) { + if ("base-unit".equals(literal)) { + return false; // Default value + } else if (literal != null) { sb.append(literal); - return; + return true; } // Generate the stem if (macros.unit instanceof Currency) { sb.append("currency/"); generateCurrencyOption((Currency) macros.unit, sb); + return true; } else if (macros.unit instanceof NoUnit) { // This should be taken care of by the literals. - assert false; + throw new AssertionError(); } else { sb.append("measure-unit/"); generateMeasureUnitOption(macros.unit, sb); + return true; } } - private static void generatePerUnitValue(MacroProps macros, StringBuilder sb) { + private static boolean generatePerUnitValue(MacroProps macros, StringBuilder sb) { // Per-units are currently expected to be only MeasureUnits. if (macros.unit instanceof Currency || macros.unit instanceof NoUnit) { - assert false; + throw new UnsupportedOperationException( + "Cannot generate number skeleton with per-unit that is not a standard measure unit"); } else { sb.append("per-measure-unit/"); generateMeasureUnitOption(macros.perUnit, sb); + return true; } } - private static void generateRoundingValue(MacroProps macros, StringBuilder sb) { + private static boolean generateRoundingValue(MacroProps macros, StringBuilder sb) { // Check for literals String literal = skeletonData.valueToStem(macros.rounder); if (literal != null) { sb.append(literal); - return; + return true; } // Generate the stem @@ -858,18 +888,34 @@ class NumberSkeletonImpl { sb.append('/'); generateRoundingModeOption(macros.rounder.mathContext.getRoundingMode(), sb); } + + // NOTE: Always return true for rounding because the default value depends on other options. + return true; } - private static void generateGroupingValue(MacroProps macros, StringBuilder sb) { - appendExpectedLiteral(macros.grouping, sb); + private static boolean generateGroupingValue(MacroProps macros, StringBuilder sb) { + if (macros.grouping instanceof GroupingStrategy) { + if (macros.grouping == GroupingStrategy.AUTO) { + return false; // Default value + } + appendExpectedLiteral(macros.grouping, sb); + return true; + } else { + throw new UnsupportedOperationException( + "Cannot generate number skeleton with custom Grouper"); + } } - private static void generateIntegerWidthValue(MacroProps macros, StringBuilder sb) { + private static boolean generateIntegerWidthValue(MacroProps macros, StringBuilder sb) { + if (macros.integerWidth.equals(IntegerWidth.DEFAULT)) { + return false; // Default + } sb.append("integer-width/"); generateIntegerWidthOption(macros.integerWidth.minInt, macros.integerWidth.maxInt, sb); + return true; } - private static void generateSymbolsValue(MacroProps macros, StringBuilder sb) { + private static boolean generateSymbolsValue(MacroProps macros, StringBuilder sb) { if (macros.symbols instanceof NumberingSystem) { NumberingSystem ns = (NumberingSystem) macros.symbols; if (ns.getName().equals("latn")) { @@ -878,21 +924,36 @@ class NumberSkeletonImpl { sb.append("numbering-system/"); generateNumberingSystemOption(ns, sb); } + return true; } else { - // DecimalFormatSymbols (not supported in skeleton) + assert macros.symbols instanceof DecimalFormatSymbols; + throw new UnsupportedOperationException( + "Cannot generate number skeleton with custom DecimalFormatSymbols"); } } - private static void generateUnitWidthValue(MacroProps macros, StringBuilder sb) { + private static boolean generateUnitWidthValue(MacroProps macros, StringBuilder sb) { + if (macros.unitWidth == UnitWidth.SHORT) { + return false; // Default value + } appendExpectedLiteral(macros.unitWidth, sb); + return true; } - private static void generateSignValue(MacroProps macros, StringBuilder sb) { + private static boolean generateSignValue(MacroProps macros, StringBuilder sb) { + if (macros.sign == SignDisplay.AUTO) { + return false; // Default value + } appendExpectedLiteral(macros.sign, sb); + return true; } - private static void generateDecimalValue(MacroProps macros, StringBuilder sb) { + private static boolean generateDecimalValue(MacroProps macros, StringBuilder sb) { + if (macros.decimal == DecimalSeparatorDisplay.AUTO) { + return false; // Default value + } appendExpectedLiteral(macros.decimal, sb); + return true; } ///// diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/Rounder.java b/icu4j/main/classes/core/src/com/ibm/icu/number/Rounder.java index fbae054d47..9cec17b8af 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/Rounder.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/Rounder.java @@ -402,7 +402,7 @@ public abstract class Rounder implements Cloneable { static final FractionRounderImpl FIXED_FRAC_0 = new FractionRounderImpl(0, 0); static final FractionRounderImpl FIXED_FRAC_2 = new FractionRounderImpl(2, 2); - static final FractionRounderImpl MAX_FRAC_6 = new FractionRounderImpl(0, 6); + static final FractionRounderImpl DEFAULT_MAX_FRAC_6 = new FractionRounderImpl(0, 6); static final SignificantRounderImpl FIXED_SIG_2 = new SignificantRounderImpl(2, 2); static final SignificantRounderImpl FIXED_SIG_3 = new SignificantRounderImpl(3, 3); @@ -427,7 +427,7 @@ public abstract class Rounder implements Cloneable { } else if (minFrac == 2 && maxFrac == 2) { return FIXED_FRAC_2; } else if (minFrac == 0 && maxFrac == 6) { - return MAX_FRAC_6; + return DEFAULT_MAX_FRAC_6; } else { return new FractionRounderImpl(minFrac, maxFrac); } 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 050e60dfd5..6668ea0ae1 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 @@ -5,6 +5,7 @@ package com.ibm.icu.dev.test.number; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -75,7 +76,7 @@ public class NumberFormatterApiTest { assertFormatDescendingBig( "Big Simple", - "simple-notation", + "notation-simple", NumberFormatter.with().notation(Notation.simple()), ULocale.ENGLISH, "87,650,000", @@ -1084,7 +1085,7 @@ public class NumberFormatterApiTest { // NOTE: Other tests cover the behavior of the other rounding modes. assertFormatDescending( "Rounding Mode CEILING", - "round-integer/CEILING", + "round-integer/ceiling", NumberFormatter.with().rounding(Rounder.integer().withMode(RoundingMode.CEILING)), ULocale.ENGLISH, "87,650", @@ -2046,13 +2047,18 @@ public class NumberFormatterApiTest { assertEquals(message + ": Safe Path: " + d, expected[i], actual2); } if (skeleton != null) { // if null, skeleton is declared as undefined. - assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton()); - LocalizedNumberFormatter l3 = NumberFormatter.fromSkeleton(skeleton).locale(locale); + // Only compare normalized skeletons: the tests need not provide the normalized forms. + // Use the normalized form to construct the testing formatter to guarantee no loss of info. + String normalized = NumberFormatter.fromSkeleton(skeleton).toSkeleton(); + assertEquals(message + ": Skeleton:", normalized, f.toSkeleton()); + LocalizedNumberFormatter l3 = NumberFormatter.fromSkeleton(normalized).locale(locale); for (int i = 0; i < 9; i++) { double d = inputs[i]; String actual3 = l3.format(d).toString(); assertEquals(message + ": Skeleton Path: " + d, expected[i], actual3); } + } else { + assertUndefinedSkeleton(f); } } @@ -2070,10 +2076,15 @@ public class NumberFormatterApiTest { String actual2 = l2.format(input).toString(); assertEquals(message + ": Safe Path: " + input, expected, actual2); if (skeleton != null) { // if null, skeleton is declared as undefined. - assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton()); - LocalizedNumberFormatter l3 = NumberFormatter.fromSkeleton(skeleton).locale(locale); + // Only compare normalized skeletons: the tests need not provide the normalized forms. + // Use the normalized form to construct the testing formatter to ensure no loss of info. + String normalized = NumberFormatter.fromSkeleton(skeleton).toSkeleton(); + assertEquals(message + ": Skeleton:", normalized, f.toSkeleton()); + LocalizedNumberFormatter l3 = NumberFormatter.fromSkeleton(normalized).locale(locale); String actual3 = l3.format(input).toString(); assertEquals(message + ": Skeleton Path: " + input, expected, actual3); + } else { + assertUndefinedSkeleton(f); } } @@ -2091,10 +2102,22 @@ public class NumberFormatterApiTest { String actual2 = l2.format(input).toString(); assertEquals(message + ": Safe Path: " + input, expected, actual2); if (skeleton != null) { // if null, skeleton is declared as undefined. - assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton()); - LocalizedNumberFormatter l3 = NumberFormatter.fromSkeleton(skeleton).locale(locale); + // Only compare normalized skeletons: the tests need not provide the normalized forms. + // Use the normalized form to construct the testing formatter to ensure no loss of info. + String normalized = NumberFormatter.fromSkeleton(skeleton).toSkeleton(); + assertEquals(message + ": Skeleton:", normalized, f.toSkeleton()); + LocalizedNumberFormatter l3 = NumberFormatter.fromSkeleton(normalized).locale(locale); String actual3 = l3.format(input).toString(); assertEquals(message + ": Skeleton Path: " + input, expected, actual3); + } else { + assertUndefinedSkeleton(f); } } + + private static void assertUndefinedSkeleton(UnlocalizedNumberFormatter f) { + try { + String skeleton = f.toSkeleton(); + fail("Expected toSkeleton to fail, but it passed, producing: " + skeleton); + } catch (UnsupportedOperationException expected) {} + } } 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 6bb084c48b..78756355f2 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 @@ -2,12 +2,16 @@ // License & terms of use: http://www.unicode.org/copyright.html#License package com.ibm.icu.dev.test.number; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.math.RoundingMode; + import org.junit.Test; import com.ibm.icu.number.NumberFormatter; +import com.ibm.icu.number.Rounder; import com.ibm.icu.number.SkeletonSyntaxException; /** @@ -26,18 +30,101 @@ public class NumberSkeletonTest { } } + @Test + public void validTokens() { + // This tests only if the tokens are valid, not their behavior. + // Most of these are from the design doc. + String[] cases = { + "round-integer", + "round-unlimited", + "@@@##", + "@@+", + ".000##", + ".00+", + ".", + ".+", + ".######", + ".00/@@+", + ".00/@##", + "round-increment/3.14", + "round-currency-standard", + "round-integer/half-up", + ".00#/ceiling", + ".00/@@+/floor", + "scientific", + "scientific/+ee", + "scientific/sign-always", + "scientific/+ee/sign-always", + "scientific/sign-always/+ee", + "scientific/sign-except-zero", + "engineering", + "engineering/+eee", + "compact-short", + "compact-long", + "notation-simple", + "percent", + "permille", + "measure-unit/length-meter", + "measure-unit/area-square-meter", + "measure-unit/energy-joule per-measure-unit/length-meter", + "currency/XXX", + "group-off", + "group-min2", + "group-auto", + "group-on-aligned", + "group-thousands", + "integer-width/00", + "integer-width/#0", + "integer-width/+00", + "sign-always", + "sign-auto", + "sign-never", + "sign-accounting", + "sign-accounting-always", + "sign-except-zero", + "sign-accounting-except-zero", + "unit-width-narrow", + "unit-width-short", + "unit-width-iso-code", + "unit-width-full-name", + "unit-width-hidden", + "decimal-auto", + "decimal-always", + "latin", + "numbering-system/arab", + "numbering-system/latn" }; + + for (String cas : cases) { + try { + NumberFormatter.fromSkeleton(cas); + } catch (SkeletonSyntaxException e) { + fail(e.getMessage()); + } + } + } + @Test public void invalidTokens() { String[] cases = { ".00x", ".00##0", ".##+", + ".00##+", ".0#+", "@@x", "@@##0", "@#+", + ".00/@", + ".00/@@", + ".00/@@x", + ".00/@@#", + ".00/@@#+", + ".00/floor/@@+", // wrong order + "round-currency-cash/XXX", + "scientific/ee", "round-increment/xxx", "round-increment/0.1.2", + "group-thousands/foo", "currency/dummy", "measure-unit/foo", "integer-width/xxx", @@ -47,7 +134,7 @@ public class NumberSkeletonTest { for (String cas : cases) { try { NumberFormatter.fromSkeleton(cas); - fail(); + fail("Skeleton parses, but it should have failed: " + cas); } catch (SkeletonSyntaxException expected) { assertTrue(expected.getMessage(), expected.getMessage().contains("Invalid")); } @@ -85,4 +172,35 @@ public class NumberSkeletonTest { } } } + + @Test + public void defaultTokens() { + String[] cases = { + "notation-simple", + "base-unit", + "group-auto", + "integer-width/+0", + "sign-auto", + "unit-width-short", + "decimal-auto" }; + + for (String skeleton : cases) { + String normalized = NumberFormatter.fromSkeleton(skeleton).toSkeleton(); + assertEquals("Skeleton should become empty when normalized: " + skeleton, "", normalized); + } + } + + @Test + public void roundingModeNames() { + for (RoundingMode mode : RoundingMode.values()) { + if (mode == RoundingMode.HALF_EVEN) { + // This rounding mode is not printed in the skeleton since it is the default + continue; + } + String skeleton = NumberFormatter.with().rounding(Rounder.integer().withMode(mode)) + .toSkeleton(); + String modeString = mode.toString().toLowerCase().replace('_', '-'); + assertEquals(mode.toString(), modeString, skeleton.substring(14)); + } + } } From afa9ac8b774fe6a708ae412b103844aa7113449b Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Sat, 3 Mar 2018 05:25:58 +0000 Subject: [PATCH 026/129] ICU-13620 Making MeasureUnit.getAvailable(type) return a Collection-based instead of Set-based data structure for better performance. X-SVN-Rev: 41055 --- .../src/com/ibm/icu/impl/CollectionSet.java | 85 +++++++++++++++++++ .../src/com/ibm/icu/util/MeasureUnit.java | 3 +- 2 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 icu4j/main/classes/core/src/com/ibm/icu/impl/CollectionSet.java diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/CollectionSet.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/CollectionSet.java new file mode 100644 index 0000000000..94e4a981f9 --- /dev/null +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/CollectionSet.java @@ -0,0 +1,85 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html#License +package com.ibm.icu.impl; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; + +/** + * A wrapper around java.util.CollectionSet that implements java.util.Set. This class keeps a pointer to + * the CollectionSet and does not persist any data on its own. + */ +public class CollectionSet implements Set { + + private final Collection data; + + public CollectionSet(Collection data) { + this.data = data; + } + + @Override + public int size() { + return data.size(); + } + + @Override + public boolean isEmpty() { + return data.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return data.contains(o); + } + + @Override + public Iterator iterator() { + return data.iterator(); + } + + @Override + public Object[] toArray() { + return data.toArray(); + } + + @Override + public T[] toArray(T[] a) { + return data.toArray(a); + } + + @Override + public boolean add(E e) { + return data.add(e); + } + + @Override + public boolean remove(Object o) { + return data.remove(o); + } + + @Override + public boolean containsAll(Collection c) { + return data.containsAll(c); + } + + @Override + public boolean addAll(Collection c) { + return data.addAll(c); + } + + @Override + public boolean retainAll(Collection c) { + return data.retainAll(c); + } + + @Override + public boolean removeAll(Collection c) { + return data.removeAll(c); + } + + @Override + public void clear() { + data.clear(); + } +} diff --git a/icu4j/main/classes/core/src/com/ibm/icu/util/MeasureUnit.java b/icu4j/main/classes/core/src/com/ibm/icu/util/MeasureUnit.java index 50ebcc1e63..d35c8dd8c0 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/util/MeasureUnit.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/util/MeasureUnit.java @@ -20,6 +20,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; +import com.ibm.icu.impl.CollectionSet; import com.ibm.icu.impl.ICUData; import com.ibm.icu.impl.ICUResourceBundle; import com.ibm.icu.impl.Pair; @@ -150,7 +151,7 @@ public class MeasureUnit implements Serializable { // Train users not to modify returned set from the start giving us more // flexibility for implementation. return units == null ? Collections.emptySet() - : Collections.unmodifiableSet(new HashSet(units.values())); + : Collections.unmodifiableSet(new CollectionSet(units.values())); } /** From d3aecc8bf4056a08faf030ce0a83cdf946b26840 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Sat, 3 Mar 2018 05:28:05 +0000 Subject: [PATCH 027/129] ICU-13620 Fix typo. X-SVN-Rev: 41056 --- .../main/classes/core/src/com/ibm/icu/impl/CollectionSet.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/CollectionSet.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/CollectionSet.java index 94e4a981f9..4f7eb9ce78 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/CollectionSet.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/CollectionSet.java @@ -7,8 +7,8 @@ import java.util.Iterator; import java.util.Set; /** - * A wrapper around java.util.CollectionSet that implements java.util.Set. This class keeps a pointer to - * the CollectionSet and does not persist any data on its own. + * A wrapper around java.util.Collection that implements java.util.Set. This class keeps a pointer to the + * Collection and does not persist any data on its own. */ public class CollectionSet implements Set { From 6b8d9a56ed37e43087474f297dd2b77d15f12998 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Sat, 3 Mar 2018 05:35:31 +0000 Subject: [PATCH 028/129] ICU-13620 Adding comment. X-SVN-Rev: 41057 --- icu4j/main/classes/core/src/com/ibm/icu/util/MeasureUnit.java | 1 + 1 file changed, 1 insertion(+) diff --git a/icu4j/main/classes/core/src/com/ibm/icu/util/MeasureUnit.java b/icu4j/main/classes/core/src/com/ibm/icu/util/MeasureUnit.java index d35c8dd8c0..95b6c3e27d 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/util/MeasureUnit.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/util/MeasureUnit.java @@ -150,6 +150,7 @@ public class MeasureUnit implements Serializable { Map units = cache.get(type); // Train users not to modify returned set from the start giving us more // flexibility for implementation. + // Use CollectionSet instead of HashSet for better performance. return units == null ? Collections.emptySet() : Collections.unmodifiableSet(new CollectionSet(units.values())); } From ef04891b49f8770d3cd0e9affbca1b870d1ce742 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Sat, 3 Mar 2018 07:42:26 +0000 Subject: [PATCH 029/129] ICU-13574 Adding U_OVERRIDE to decimfmt.h and other assorted changes in that file. X-SVN-Rev: 41062 --- icu4c/source/i18n/unicode/decimfmt.h | 166 ++++++++++++++------------- 1 file changed, 87 insertions(+), 79 deletions(-) diff --git a/icu4c/source/i18n/unicode/decimfmt.h b/icu4c/source/i18n/unicode/decimfmt.h index dcd4b7a329..7e39239964 100644 --- a/icu4c/source/i18n/unicode/decimfmt.h +++ b/icu4c/source/i18n/unicode/decimfmt.h @@ -42,6 +42,7 @@ #include "unicode/stringpiece.h" #include "unicode/curramt.h" #include "unicode/enumset.h" +#include "unicode/numberformatter.h" #ifndef U_HIDE_INTERNAL_API /** @@ -698,7 +699,7 @@ public: * pattern is invalid this will be set to a failure code. * @stable ICU 2.0 */ - DecimalFormat(UErrorCode& status); + explicit DecimalFormat(UErrorCode& status); /** * Create a DecimalFormat from the given pattern and the symbols @@ -800,7 +801,7 @@ public: * @see getGroupingUsed * @stable ICU 53 */ - virtual void setGroupingUsed(UBool newValue); + void setGroupingUsed(UBool newValue) U_OVERRIDE; /** * Sets whether or not numbers should be parsed as integers only. @@ -809,7 +810,7 @@ public: * @see isParseIntegerOnly * @stable ICU 53 */ - virtual void setParseIntegerOnly(UBool value); + void setParseIntegerOnly(UBool value) U_OVERRIDE; /** * Set a particular UDisplayContext value in the formatter, such as @@ -820,7 +821,7 @@ public: * updated with any new status from the function. * @stable ICU 53 */ - virtual void setContext(UDisplayContext value, UErrorCode& status); + void setContext(UDisplayContext value, UErrorCode& status) U_OVERRIDE; /** * Create a DecimalFormat from the given pattern and symbols. @@ -886,7 +887,7 @@ public: * Destructor. * @stable ICU 2.0 */ - virtual ~DecimalFormat(); + ~DecimalFormat() U_OVERRIDE; /** * Clone this Format object polymorphically. The caller owns the @@ -895,7 +896,7 @@ public: * @return a polymorphic copy of this DecimalFormat. * @stable ICU 2.0 */ - virtual Format* clone(void) const; + Format* clone(void) const U_OVERRIDE; /** * Return true if the given Format objects are semantically equal. @@ -905,7 +906,7 @@ public: * @return true if the given Format objects are semantically equal. * @stable ICU 2.0 */ - virtual UBool operator==(const Format& other) const; + UBool operator==(const Format& other) const U_OVERRIDE; using NumberFormat::format; @@ -921,9 +922,9 @@ public: * @return Reference to 'appendTo' parameter. * @stable ICU 2.0 */ - virtual UnicodeString& format(double number, - UnicodeString& appendTo, - FieldPosition& pos) const; + UnicodeString& format(double number, + UnicodeString& appendTo, + FieldPosition& pos) const U_OVERRIDE; /** @@ -938,10 +939,10 @@ public: * @return Reference to 'appendTo' parameter. * @internal */ - virtual UnicodeString& format(double number, - UnicodeString& appendTo, - FieldPosition& pos, - UErrorCode &status) const; + UnicodeString& format(double number, + UnicodeString& appendTo, + FieldPosition& pos, + UErrorCode &status) const U_OVERRIDE; /** * Format a double or long number using base-10 representation. @@ -956,10 +957,10 @@ public: * @return Reference to 'appendTo' parameter. * @stable ICU 4.4 */ - virtual UnicodeString& format(double number, - UnicodeString& appendTo, - FieldPositionIterator* posIter, - UErrorCode& status) const; + UnicodeString& format(double number, + UnicodeString& appendTo, + FieldPositionIterator* posIter, + UErrorCode& status) const U_OVERRIDE; /** * Format a long number using base-10 representation. @@ -972,9 +973,9 @@ public: * @return Reference to 'appendTo' parameter. * @stable ICU 2.0 */ - virtual UnicodeString& format(int32_t number, - UnicodeString& appendTo, - FieldPosition& pos) const; + UnicodeString& format(int32_t number, + UnicodeString& appendTo, + FieldPosition& pos) const U_OVERRIDE; /** * Format a long number using base-10 representation. @@ -987,10 +988,10 @@ public: * @return Reference to 'appendTo' parameter. * @internal */ - virtual UnicodeString& format(int32_t number, - UnicodeString& appendTo, - FieldPosition& pos, - UErrorCode &status) const; + UnicodeString& format(int32_t number, + UnicodeString& appendTo, + FieldPosition& pos, + UErrorCode &status) const U_OVERRIDE; /** * Format a long number using base-10 representation. @@ -1005,10 +1006,10 @@ public: * @return Reference to 'appendTo' parameter. * @stable ICU 4.4 */ - virtual UnicodeString& format(int32_t number, - UnicodeString& appendTo, - FieldPositionIterator* posIter, - UErrorCode& status) const; + UnicodeString& format(int32_t number, + UnicodeString& appendTo, + FieldPositionIterator* posIter, + UErrorCode& status) const U_OVERRIDE; /** * Format an int64 number using base-10 representation. @@ -1021,9 +1022,9 @@ public: * @return Reference to 'appendTo' parameter. * @stable ICU 2.8 */ - virtual UnicodeString& format(int64_t number, - UnicodeString& appendTo, - FieldPosition& pos) const; + UnicodeString& format(int64_t number, + UnicodeString& appendTo, + FieldPosition& pos) const U_OVERRIDE; /** * Format an int64 number using base-10 representation. @@ -1036,10 +1037,10 @@ public: * @return Reference to 'appendTo' parameter. * @internal */ - virtual UnicodeString& format(int64_t number, - UnicodeString& appendTo, - FieldPosition& pos, - UErrorCode &status) const; + UnicodeString& format(int64_t number, + UnicodeString& appendTo, + FieldPosition& pos, + UErrorCode &status) const U_OVERRIDE; /** * Format an int64 number using base-10 representation. @@ -1054,10 +1055,10 @@ public: * @return Reference to 'appendTo' parameter. * @stable ICU 4.4 */ - virtual UnicodeString& format(int64_t number, - UnicodeString& appendTo, - FieldPositionIterator* posIter, - UErrorCode& status) const; + UnicodeString& format(int64_t number, + UnicodeString& appendTo, + FieldPositionIterator* posIter, + UErrorCode& status) const U_OVERRIDE; /** * Format a decimal number. @@ -1075,10 +1076,10 @@ public: * @return Reference to 'appendTo' parameter. * @stable ICU 4.4 */ - virtual UnicodeString& format(StringPiece number, - UnicodeString& appendTo, - FieldPositionIterator* posIter, - UErrorCode& status) const; + UnicodeString& format(StringPiece number, + UnicodeString& appendTo, + FieldPositionIterator* posIter, + UErrorCode& status) const U_OVERRIDE; /** @@ -1096,10 +1097,10 @@ public: * @return Reference to 'appendTo' parameter. * @internal */ - virtual UnicodeString& format(const DigitList &number, - UnicodeString& appendTo, - FieldPositionIterator* posIter, - UErrorCode& status) const; + UnicodeString& format(const DigitList &number, + UnicodeString& appendTo, + FieldPositionIterator* posIter, + UErrorCode& status) const U_OVERRIDE; /** * Format a decimal number. @@ -1112,11 +1113,10 @@ public: * @return Reference to 'appendTo' parameter. * @internal */ - virtual UnicodeString& format( - const VisibleDigitsWithExponent &number, - UnicodeString& appendTo, - FieldPosition& pos, - UErrorCode& status) const; + virtual UnicodeString& format(const VisibleDigitsWithExponent &number, + UnicodeString& appendTo, + FieldPosition& pos, + UErrorCode& status) const; /** * Format a decimal number. @@ -1129,11 +1129,10 @@ public: * @return Reference to 'appendTo' parameter. * @internal */ - virtual UnicodeString& format( - const VisibleDigitsWithExponent &number, - UnicodeString& appendTo, - FieldPositionIterator* posIter, - UErrorCode& status) const; + virtual UnicodeString& format(const VisibleDigitsWithExponent &number, + UnicodeString& appendTo, + FieldPositionIterator* posIter, + UErrorCode& status) const; /** * Format a decimal number. @@ -1150,10 +1149,10 @@ public: * @return Reference to 'appendTo' parameter. * @internal */ - virtual UnicodeString& format(const DigitList &number, - UnicodeString& appendTo, - FieldPosition& pos, - UErrorCode& status) const; + UnicodeString& format(const DigitList &number, + UnicodeString& appendTo, + FieldPosition& pos, + UErrorCode& status) const U_OVERRIDE; using NumberFormat::parse; @@ -1176,9 +1175,9 @@ public: * @see Formattable * @stable ICU 2.0 */ - virtual void parse(const UnicodeString& text, - Formattable& result, - ParsePosition& parsePosition) const; + void parse(const UnicodeString& text, + Formattable& result, + ParsePosition& parsePosition) const U_OVERRIDE; /** * Parses text from the given string as a currency amount. Unlike @@ -1199,8 +1198,8 @@ public: * the parsed currency; if parse fails, this is NULL. * @stable ICU 49 */ - virtual CurrencyAmount* parseCurrency(const UnicodeString& text, - ParsePosition& pos) const; + CurrencyAmount* parseCurrency(const UnicodeString& text, + ParsePosition& pos) const U_OVERRIDE; /** * Returns the decimal format symbols, which is generally not changed @@ -1385,7 +1384,7 @@ public: * @see #setRoundingMode * @stable ICU 2.0 */ - virtual ERoundingMode getRoundingMode(void) const; + virtual ERoundingMode getRoundingMode(void) const U_OVERRIDE; /** * Set the rounding mode. @@ -1395,7 +1394,7 @@ public: * @see #getRoundingMode * @stable ICU 2.0 */ - virtual void setRoundingMode(ERoundingMode roundingMode); + virtual void setRoundingMode(ERoundingMode roundingMode) U_OVERRIDE; /** * Get the width to which the output of format() is padded. @@ -1767,8 +1766,8 @@ public: * @stable ICU 2.0 */ virtual void applyPattern(const UnicodeString& pattern, - UParseError& parseError, - UErrorCode& status); + UParseError& parseError, + UErrorCode& status); /** * Sets the pattern. * @param pattern The pattern to be applied. @@ -1778,7 +1777,7 @@ public: * @stable ICU 2.0 */ virtual void applyPattern(const UnicodeString& pattern, - UErrorCode& status); + UErrorCode& status); /** * Apply the given pattern to this Format object. The pattern @@ -1836,7 +1835,7 @@ public: * @see NumberFormat#setMaximumIntegerDigits * @stable ICU 2.0 */ - virtual void setMaximumIntegerDigits(int32_t newValue); + void setMaximumIntegerDigits(int32_t newValue) U_OVERRIDE; /** * Sets the minimum number of digits allowed in the integer portion of a @@ -1847,7 +1846,7 @@ public: * @see NumberFormat#setMinimumIntegerDigits * @stable ICU 2.0 */ - virtual void setMinimumIntegerDigits(int32_t newValue); + void setMinimumIntegerDigits(int32_t newValue) U_OVERRIDE; /** * Sets the maximum number of digits allowed in the fraction portion of a @@ -1858,7 +1857,7 @@ public: * @see NumberFormat#setMaximumFractionDigits * @stable ICU 2.0 */ - virtual void setMaximumFractionDigits(int32_t newValue); + void setMaximumFractionDigits(int32_t newValue) U_OVERRIDE; /** * Sets the minimum number of digits allowed in the fraction portion of a @@ -1869,7 +1868,7 @@ public: * @see NumberFormat#setMinimumFractionDigits * @stable ICU 2.0 */ - virtual void setMinimumFractionDigits(int32_t newValue); + void setMinimumFractionDigits(int32_t newValue) U_OVERRIDE; /** * Returns the minimum number of significant digits that will be @@ -1945,7 +1944,7 @@ public: * @param ec input-output error code * @stable ICU 3.0 */ - virtual void setCurrency(const char16_t* theCurrency, UErrorCode& ec); + void setCurrency(const char16_t* theCurrency, UErrorCode& ec) U_OVERRIDE; /** * Sets the currency used to display currency amounts. See @@ -2040,6 +2039,15 @@ public: public: + /** + * Converts this DecimalFormat to a NumberFormatter. Starting in ICU 60, + * NumberFormatter is the recommended way to format numbers. + * + * @return An instance of LocalizedNumberFormatter with the same behavior as this DecimalFormat. + * @draft ICU 62 + */ + number::LocalizedNumberFormatter getNumberFormatter() const; + /** * Return the class ID for this class. This is useful only for * comparing to a return value from getDynamicClassID(). For example: @@ -2064,7 +2072,7 @@ public: * other classes have different class IDs. * @stable ICU 2.0 */ - virtual UClassID getDynamicClassID(void) const; + virtual UClassID getDynamicClassID(void) const U_OVERRIDE; private: @@ -2253,7 +2261,7 @@ protected: * have a capacity of at least 4 * @internal */ - virtual void getEffectiveCurrency(char16_t* result, UErrorCode& ec) const; + void getEffectiveCurrency(char16_t* result, UErrorCode& ec) const U_OVERRIDE; /** number of integer digits * @stable ICU 2.4 From 3681a6803b16a908c3e94f075244eb010cb4ce3e Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Sat, 3 Mar 2018 08:26:58 +0000 Subject: [PATCH 030/129] ICU-13574 Replacing decimfmt.cpp and compactdecimalformat.cpp with new, empty files. Removing most private and internal functions from decimfmt.h, and updating call sites to use newer API. X-SVN-Rev: 41063 --- icu4c/source/i18n/compactdecimalformat.cpp | 1010 +----- icu4c/source/i18n/decimfmt.cpp | 3289 +------------------ icu4c/source/i18n/msgfmt.cpp | 9 +- icu4c/source/i18n/plurfmt.cpp | 26 +- icu4c/source/i18n/plurrule.cpp | 7 +- icu4c/source/i18n/quantityformatter.cpp | 9 +- icu4c/source/i18n/unicode/decimfmt.h | 284 +- icu4c/source/i18n/unicode/numberformatter.h | 12 + 8 files changed, 55 insertions(+), 4591 deletions(-) diff --git a/icu4c/source/i18n/compactdecimalformat.cpp b/icu4c/source/i18n/compactdecimalformat.cpp index bb2e541cd8..99fbfb54bd 100644 --- a/icu4c/source/i18n/compactdecimalformat.cpp +++ b/icu4c/source/i18n/compactdecimalformat.cpp @@ -1,1013 +1,19 @@ -// © 2016 and later: Unicode, Inc. and others. +// © 2018 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* Copyright (C) 1997-2015, International Business Machines Corporation and * -* others. All Rights Reserved. * -******************************************************************************* -* -* File COMPACTDECIMALFORMAT.CPP -* -******************************************************************************** -*/ + #include "unicode/utypes.h" -#if !UCONFIG_NO_FORMATTING +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT -#include "charstr.h" -#include "cstring.h" -#include "digitlst.h" -#include "mutex.h" -#include "unicode/compactdecimalformat.h" -#include "unicode/numsys.h" -#include "unicode/plurrule.h" -#include "unicode/ures.h" -#include "ucln_in.h" -#include "uhash.h" -#include "umutex.h" -#include "unicode/ures.h" -#include "uresimp.h" +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT -// Maps locale name to CDFLocaleData struct. -static UHashtable* gCompactDecimalData = NULL; -static UMutex gCompactDecimalMetaLock = U_MUTEX_INITIALIZER; +using namespace icu; -U_NAMESPACE_BEGIN -static const int32_t MAX_DIGITS = 15; -static const char gOther[] = "other"; -static const char gLatnTag[] = "latn"; -static const char gNumberElementsTag[] = "NumberElements"; -static const char gDecimalFormatTag[] = "decimalFormat"; -static const char gPatternsShort[] = "patternsShort"; -static const char gPatternsLong[] = "patternsLong"; -static const char gLatnPath[] = "NumberElements/latn"; -static const UChar u_0 = 0x30; -static const UChar u_apos = 0x27; -static const UChar kZero[] = {u_0}; -// Used to unescape single quotes. -enum QuoteState { - OUTSIDE, - INSIDE_EMPTY, - INSIDE_FULL -}; -enum FallbackFlags { - ANY = 0, - MUST = 1, - NOT_ROOT = 2 - // Next one will be 4 then 6 etc. -}; - - -// CDFUnit represents a prefix-suffix pair for a particular variant -// and log10 value. -struct CDFUnit : public UMemory { - UnicodeString prefix; - UnicodeString suffix; - inline CDFUnit() : prefix(), suffix() { - prefix.setToBogus(); - } - inline ~CDFUnit() {} - inline UBool isSet() const { - return !prefix.isBogus(); - } - inline void markAsSet() { - prefix.remove(); - } -}; - -// CDFLocaleStyleData contains formatting data for a particular locale -// and style. -class CDFLocaleStyleData : public UMemory { - public: - // What to divide by for each log10 value when formatting. These values - // will be powers of 10. For English, would be: - // 1, 1, 1, 1000, 1000, 1000, 1000000, 1000000, 1000000, 1000000000 ... - double divisors[MAX_DIGITS]; - // Maps plural variants to CDFUnit[MAX_DIGITS] arrays. - // To format a number x, - // first compute log10(x). Compute displayNum = (x / divisors[log10(x)]). - // Compute the plural variant for displayNum - // (e.g zero, one, two, few, many, other). - // Compute cdfUnits = unitsByVariant[pluralVariant]. - // Prefix and suffix to use at cdfUnits[log10(x)] - UHashtable* unitsByVariant; - // A flag for whether or not this CDFLocaleStyleData was loaded from the - // Latin numbering system as a fallback from the locale numbering system. - // This value is meaningless if the object is bogus or empty. - UBool fromFallback; - inline CDFLocaleStyleData() : unitsByVariant(NULL), fromFallback(FALSE) { - uprv_memset(divisors, 0, sizeof(divisors)); - } - ~CDFLocaleStyleData(); - // Init initializes this object. - void Init(UErrorCode& status); - inline UBool isBogus() const { - return unitsByVariant == NULL; - } - void setToBogus(); - UBool isEmpty() { - return unitsByVariant == NULL || unitsByVariant->count == 0; - } - private: - CDFLocaleStyleData(const CDFLocaleStyleData&); - CDFLocaleStyleData& operator=(const CDFLocaleStyleData&); -}; - -// CDFLocaleData contains formatting data for a particular locale. -struct CDFLocaleData : public UMemory { - CDFLocaleStyleData shortData; - CDFLocaleStyleData longData; - inline CDFLocaleData() : shortData(), longData() { } - inline ~CDFLocaleData() { } - // Init initializes this object. - void Init(UErrorCode& status); -}; - -U_NAMESPACE_END - -U_CDECL_BEGIN - -static UBool U_CALLCONV cdf_cleanup(void) { - if (gCompactDecimalData != NULL) { - uhash_close(gCompactDecimalData); - gCompactDecimalData = NULL; - } - return TRUE; -} - -static void U_CALLCONV deleteCDFUnits(void* ptr) { - delete [] (icu::CDFUnit*) ptr; -} - -static void U_CALLCONV deleteCDFLocaleData(void* ptr) { - delete (icu::CDFLocaleData*) ptr; -} - -U_CDECL_END - -U_NAMESPACE_BEGIN - -static UBool divisors_equal(const double* lhs, const double* rhs); -static const CDFLocaleStyleData* getCDFLocaleStyleData(const Locale& inLocale, UNumberCompactStyle style, UErrorCode& status); - -static const CDFLocaleStyleData* extractDataByStyleEnum(const CDFLocaleData& data, UNumberCompactStyle style, UErrorCode& status); -static CDFLocaleData* loadCDFLocaleData(const Locale& inLocale, UErrorCode& status); -static void load(const Locale& inLocale, CDFLocaleData* result, UErrorCode& status); -static int32_t populatePrefixSuffix(const char* variant, int32_t log10Value, const UnicodeString& formatStr, UHashtable* result, UBool overwrite, UErrorCode& status); -static double calculateDivisor(double power10, int32_t numZeros); -static UBool onlySpaces(UnicodeString u); -static void fixQuotes(UnicodeString& s); -static void checkForOtherVariants(CDFLocaleStyleData* result, UErrorCode& status); -static void fillInMissing(CDFLocaleStyleData* result); -static int32_t computeLog10(double x, UBool inRange); -static CDFUnit* createCDFUnit(const char* variant, int32_t log10Value, UHashtable* table, UErrorCode& status); -static const CDFUnit* getCDFUnitFallback(const UHashtable* table, const UnicodeString& variant, int32_t log10Value); - -UOBJECT_DEFINE_RTTI_IMPLEMENTATION(CompactDecimalFormat) - -CompactDecimalFormat::CompactDecimalFormat( - const DecimalFormat& decimalFormat, - const UHashtable* unitsByVariant, - const double* divisors, - PluralRules* pluralRules) - : DecimalFormat(decimalFormat), _unitsByVariant(unitsByVariant), _divisors(divisors), _pluralRules(pluralRules) { -} - -CompactDecimalFormat::CompactDecimalFormat(const CompactDecimalFormat& source) - : DecimalFormat(source), _unitsByVariant(source._unitsByVariant), _divisors(source._divisors), _pluralRules(source._pluralRules->clone()) { -} - -CompactDecimalFormat* U_EXPORT2 -CompactDecimalFormat::createInstance( - const Locale& inLocale, UNumberCompactStyle style, UErrorCode& status) { - LocalPointer decfmt((DecimalFormat*) NumberFormat::makeInstance(inLocale, UNUM_DECIMAL, TRUE, status)); - if (U_FAILURE(status)) { - return NULL; - } - LocalPointer pluralRules(PluralRules::forLocale(inLocale, status)); - if (U_FAILURE(status)) { - return NULL; - } - const CDFLocaleStyleData* data = getCDFLocaleStyleData(inLocale, style, status); - if (U_FAILURE(status)) { - return NULL; - } - CompactDecimalFormat* result = - new CompactDecimalFormat(*decfmt, data->unitsByVariant, data->divisors, pluralRules.getAlias()); - if (result == NULL) { - status = U_MEMORY_ALLOCATION_ERROR; - return NULL; - } - pluralRules.orphan(); - result->setMaximumSignificantDigits(3); - result->setSignificantDigitsUsed(TRUE); - result->setGroupingUsed(FALSE); - return result; -} - -CompactDecimalFormat& -CompactDecimalFormat::operator=(const CompactDecimalFormat& rhs) { - if (this != &rhs) { - DecimalFormat::operator=(rhs); - _unitsByVariant = rhs._unitsByVariant; - _divisors = rhs._divisors; - delete _pluralRules; - _pluralRules = rhs._pluralRules->clone(); - } - return *this; -} - -CompactDecimalFormat::~CompactDecimalFormat() { - delete _pluralRules; -} - - -Format* -CompactDecimalFormat::clone(void) const { - return new CompactDecimalFormat(*this); -} - -UBool -CompactDecimalFormat::operator==(const Format& that) const { - if (this == &that) { - return TRUE; - } - return (DecimalFormat::operator==(that) && eqHelper((const CompactDecimalFormat&) that)); -} - -UBool -CompactDecimalFormat::eqHelper(const CompactDecimalFormat& that) const { - return uhash_equals(_unitsByVariant, that._unitsByVariant) && divisors_equal(_divisors, that._divisors) && (*_pluralRules == *that._pluralRules); -} - -UnicodeString& -CompactDecimalFormat::format( - double number, - UnicodeString& appendTo, - FieldPosition& pos) const { - UErrorCode status = U_ZERO_ERROR; - return format(number, appendTo, pos, status); -} - -UnicodeString& -CompactDecimalFormat::format( - double number, - UnicodeString& appendTo, - FieldPosition& pos, - UErrorCode &status) const { - if (U_FAILURE(status)) { - return appendTo; - } - DigitList orig, rounded; - orig.set(number); - UBool isNegative; - _round(orig, rounded, isNegative, status); - if (U_FAILURE(status)) { - return appendTo; - } - double roundedDouble = rounded.getDouble(); - if (isNegative) { - roundedDouble = -roundedDouble; - } - int32_t baseIdx = computeLog10(roundedDouble, TRUE); - double numberToFormat = roundedDouble / _divisors[baseIdx]; - UnicodeString variant = _pluralRules->select(numberToFormat); - if (isNegative) { - numberToFormat = -numberToFormat; - } - const CDFUnit* unit = getCDFUnitFallback(_unitsByVariant, variant, baseIdx); - appendTo += unit->prefix; - DecimalFormat::format(numberToFormat, appendTo, pos); - appendTo += unit->suffix; - return appendTo; -} - -UnicodeString& -CompactDecimalFormat::format( - double /* number */, - UnicodeString& appendTo, - FieldPositionIterator* /* posIter */, - UErrorCode& status) const { - status = U_UNSUPPORTED_ERROR; - return appendTo; -} - -UnicodeString& -CompactDecimalFormat::format( - int32_t number, - UnicodeString& appendTo, - FieldPosition& pos) const { - return format((double) number, appendTo, pos); -} - -UnicodeString& -CompactDecimalFormat::format( - int32_t number, - UnicodeString& appendTo, - FieldPosition& pos, - UErrorCode &status) const { - return format((double) number, appendTo, pos, status); -} - -UnicodeString& -CompactDecimalFormat::format( - int32_t /* number */, - UnicodeString& appendTo, - FieldPositionIterator* /* posIter */, - UErrorCode& status) const { - status = U_UNSUPPORTED_ERROR; - return appendTo; -} - -UnicodeString& -CompactDecimalFormat::format( - int64_t number, - UnicodeString& appendTo, - FieldPosition& pos) const { - return format((double) number, appendTo, pos); -} - -UnicodeString& -CompactDecimalFormat::format( - int64_t number, - UnicodeString& appendTo, - FieldPosition& pos, - UErrorCode &status) const { - return format((double) number, appendTo, pos, status); -} - -UnicodeString& -CompactDecimalFormat::format( - int64_t /* number */, - UnicodeString& appendTo, - FieldPositionIterator* /* posIter */, - UErrorCode& status) const { - status = U_UNSUPPORTED_ERROR; - return appendTo; -} - -UnicodeString& -CompactDecimalFormat::format( - StringPiece /* number */, - UnicodeString& appendTo, - FieldPositionIterator* /* posIter */, - UErrorCode& status) const { - status = U_UNSUPPORTED_ERROR; - return appendTo; -} - -UnicodeString& -CompactDecimalFormat::format( - const DigitList& /* number */, - UnicodeString& appendTo, - FieldPositionIterator* /* posIter */, - UErrorCode& status) const { - status = U_UNSUPPORTED_ERROR; - return appendTo; -} - -UnicodeString& -CompactDecimalFormat::format(const DigitList& /* number */, - UnicodeString& appendTo, - FieldPosition& /* pos */, - UErrorCode& status) const { - status = U_UNSUPPORTED_ERROR; - return appendTo; -} - -void -CompactDecimalFormat::parse( - const UnicodeString& /* text */, - Formattable& /* result */, - ParsePosition& /* parsePosition */) const { -} - -void -CompactDecimalFormat::parse( - const UnicodeString& /* text */, - Formattable& /* result */, - UErrorCode& status) const { - status = U_UNSUPPORTED_ERROR; -} - -CurrencyAmount* -CompactDecimalFormat::parseCurrency( - const UnicodeString& /* text */, - ParsePosition& /* pos */) const { - return NULL; -} - -void CDFLocaleStyleData::Init(UErrorCode& status) { - if (unitsByVariant != NULL) { - return; - } - unitsByVariant = uhash_open(uhash_hashChars, uhash_compareChars, NULL, &status); - if (U_FAILURE(status)) { - return; - } - uhash_setKeyDeleter(unitsByVariant, uprv_free); - uhash_setValueDeleter(unitsByVariant, deleteCDFUnits); -} - -CDFLocaleStyleData::~CDFLocaleStyleData() { - setToBogus(); -} - -void CDFLocaleStyleData::setToBogus() { - if (unitsByVariant != NULL) { - uhash_close(unitsByVariant); - unitsByVariant = NULL; - } -} - -void CDFLocaleData::Init(UErrorCode& status) { - shortData.Init(status); - if (U_FAILURE(status)) { - return; - } - longData.Init(status); -} - -// Helper method for operator= -static UBool divisors_equal(const double* lhs, const double* rhs) { - for (int32_t i = 0; i < MAX_DIGITS; ++i) { - if (lhs[i] != rhs[i]) { - return FALSE; - } - } - return TRUE; -} - -// getCDFLocaleStyleData returns pointer to formatting data for given locale and -// style within the global cache. On cache miss, getCDFLocaleStyleData loads -// the data from CLDR into the global cache before returning the pointer. If a -// UNUM_LONG data is requested for a locale, and that locale does not have -// UNUM_LONG data, getCDFLocaleStyleData will fall back to UNUM_SHORT data for -// that locale. -static const CDFLocaleStyleData* getCDFLocaleStyleData(const Locale& inLocale, UNumberCompactStyle style, UErrorCode& status) { - if (U_FAILURE(status)) { - return NULL; - } - CDFLocaleData* result = NULL; - const char* key = inLocale.getName(); - { - Mutex lock(&gCompactDecimalMetaLock); - if (gCompactDecimalData == NULL) { - gCompactDecimalData = uhash_open(uhash_hashChars, uhash_compareChars, NULL, &status); - if (U_FAILURE(status)) { - return NULL; - } - uhash_setKeyDeleter(gCompactDecimalData, uprv_free); - uhash_setValueDeleter(gCompactDecimalData, deleteCDFLocaleData); - ucln_i18n_registerCleanup(UCLN_I18N_CDFINFO, cdf_cleanup); - } else { - result = (CDFLocaleData*) uhash_get(gCompactDecimalData, key); - } - } - if (result != NULL) { - return extractDataByStyleEnum(*result, style, status); - } - - result = loadCDFLocaleData(inLocale, status); - if (U_FAILURE(status)) { - return NULL; - } - - { - Mutex lock(&gCompactDecimalMetaLock); - CDFLocaleData* temp = (CDFLocaleData*) uhash_get(gCompactDecimalData, key); - if (temp != NULL) { - delete result; - result = temp; - } else { - uhash_put(gCompactDecimalData, uprv_strdup(key), (void*) result, &status); - if (U_FAILURE(status)) { - return NULL; - } - } - } - return extractDataByStyleEnum(*result, style, status); -} - -static const CDFLocaleStyleData* extractDataByStyleEnum(const CDFLocaleData& data, UNumberCompactStyle style, UErrorCode& status) { - switch (style) { - case UNUM_SHORT: - return &data.shortData; - case UNUM_LONG: - if (!data.longData.isBogus()) { - return &data.longData; - } - return &data.shortData; - default: - status = U_ILLEGAL_ARGUMENT_ERROR; - return NULL; - } -} - -// loadCDFLocaleData loads formatting data from CLDR for a given locale. The -// caller owns the returned pointer. -static CDFLocaleData* loadCDFLocaleData(const Locale& inLocale, UErrorCode& status) { - if (U_FAILURE(status)) { - return NULL; - } - CDFLocaleData* result = new CDFLocaleData; - if (result == NULL) { - status = U_MEMORY_ALLOCATION_ERROR; - return NULL; - } - result->Init(status); - if (U_FAILURE(status)) { - delete result; - return NULL; - } - - load(inLocale, result, status); - - if (U_FAILURE(status)) { - delete result; - return NULL; - } - return result; -} - -namespace { - -struct CmptDecDataSink : public ResourceSink { - - CDFLocaleData& dataBundle; // Where to save values when they are read - UBool isLatin; // Whether or not we are traversing the Latin tree - UBool isFallback; // Whether or not we are traversing the Latin tree as fallback - - enum EPatternsTableKey { PATTERNS_SHORT, PATTERNS_LONG }; - enum EFormatsTableKey { DECIMAL_FORMAT, CURRENCY_FORMAT }; - - /* - * NumberElements{ <-- top (numbering system table) - * latn{ <-- patternsTable (one per numbering system) - * patternsLong{ <-- formatsTable (one per pattern) - * decimalFormat{ <-- powersOfTenTable (one per format) - * 1000{ <-- pluralVariantsTable (one per power of ten) - * one{"0 thousand"} <-- plural variant and template - */ - - CmptDecDataSink(CDFLocaleData& _dataBundle) - : dataBundle(_dataBundle), isLatin(FALSE), isFallback(FALSE) {} - virtual ~CmptDecDataSink(); - - virtual void put(const char *key, ResourceValue &value, UBool isRoot, UErrorCode &errorCode) { - // SPECIAL CASE: Don't consume root in the non-Latin numbering system - if (isRoot && !isLatin) { return; } - - ResourceTable patternsTable = value.getTable(errorCode); - if (U_FAILURE(errorCode)) { return; } - for (int i1 = 0; patternsTable.getKeyAndValue(i1, key, value); ++i1) { - - // Check for patternsShort or patternsLong - EPatternsTableKey patternsTableKey; - if (uprv_strcmp(key, gPatternsShort) == 0) { - patternsTableKey = PATTERNS_SHORT; - } else if (uprv_strcmp(key, gPatternsLong) == 0) { - patternsTableKey = PATTERNS_LONG; - } else { - continue; - } - - // Traverse into the formats table - ResourceTable formatsTable = value.getTable(errorCode); - if (U_FAILURE(errorCode)) { return; } - for (int i2 = 0; formatsTable.getKeyAndValue(i2, key, value); ++i2) { - - // Check for decimalFormat or currencyFormat - EFormatsTableKey formatsTableKey; - if (uprv_strcmp(key, gDecimalFormatTag) == 0) { - formatsTableKey = DECIMAL_FORMAT; - // TODO: Enable this statement when currency support is added - // } else if (uprv_strcmp(key, gCurrencyFormat) == 0) { - // formatsTableKey = CURRENCY_FORMAT; - } else { - continue; - } - - // Set the current style and destination based on the two keys - UNumberCompactStyle style; - CDFLocaleStyleData* destination = NULL; - if (patternsTableKey == PATTERNS_LONG - && formatsTableKey == DECIMAL_FORMAT) { - style = UNUM_LONG; - destination = &dataBundle.longData; - } else if (patternsTableKey == PATTERNS_SHORT - && formatsTableKey == DECIMAL_FORMAT) { - style = UNUM_SHORT; - destination = &dataBundle.shortData; - // TODO: Enable the following statements when currency support is added - // } else if (patternsTableKey == PATTERNS_SHORT - // && formatsTableKey == CURRENCY_FORMAT) { - // style = UNUM_SHORT_CURRENCY; // or whatever the enum gets named - // destination = &dataBundle.shortCurrencyData; - // } else { - // // Silently ignore this case - // continue; - } - - // SPECIAL CASE: RULES FOR WHETHER OR NOT TO CONSUME THIS TABLE: - // 1) Don't consume longData if shortData was consumed from the non-Latin - // locale numbering system - // 2) Don't consume longData for the first time if this is the root bundle and - // shortData is already populated from a more specific locale. Note that if - // both longData and shortData are both only in root, longData will be - // consumed since it is alphabetically before shortData in the bundle. - if (isFallback - && style == UNUM_LONG - && !dataBundle.shortData.isEmpty() - && !dataBundle.shortData.fromFallback) { - continue; - } - if (isRoot - && style == UNUM_LONG - && dataBundle.longData.isEmpty() - && !dataBundle.shortData.isEmpty()) { - continue; - } - - // Set the "fromFallback" flag on the data object - destination->fromFallback = isFallback; - - // Traverse into the powers of ten table - ResourceTable powersOfTenTable = value.getTable(errorCode); - if (U_FAILURE(errorCode)) { return; } - for (int i3 = 0; powersOfTenTable.getKeyAndValue(i3, key, value); ++i3) { - - // The key will always be some even power of 10. e.g 10000. - char* endPtr = NULL; - double power10 = uprv_strtod(key, &endPtr); - if (*endPtr != 0) { - errorCode = U_INTERNAL_PROGRAM_ERROR; - return; - } - int32_t log10Value = computeLog10(power10, FALSE); - - // Silently ignore divisors that are too big. - if (log10Value >= MAX_DIGITS) continue; - - // Iterate over the plural variants ("one", "other", etc) - ResourceTable pluralVariantsTable = value.getTable(errorCode); - if (U_FAILURE(errorCode)) { return; } - for (int i4 = 0; pluralVariantsTable.getKeyAndValue(i4, key, value); ++i4) { - const char* pluralVariant = key; - const UnicodeString formatStr = value.getUnicodeString(errorCode); - - // Copy the data into the in-memory data bundle (do not overwrite - // existing values) - int32_t numZeros = populatePrefixSuffix( - pluralVariant, log10Value, formatStr, - destination->unitsByVariant, FALSE, errorCode); - - // If populatePrefixSuffix returns -1, it means that this key has been - // encountered already. - if (numZeros < 0) { - continue; - } - - // Set the divisor, which is based on the number of zeros in the template - // string. If the divisor from here is different from the one previously - // stored, it means that the number of zeros in different plural variants - // differs; throw an exception. - // TODO: How should I check for floating-point errors here? - // Is there a good reason why "divisor" is double and not long like Java? - double divisor = calculateDivisor(power10, numZeros); - if (destination->divisors[log10Value] != 0.0 - && destination->divisors[log10Value] != divisor) { - errorCode = U_INTERNAL_PROGRAM_ERROR; - return; - } - destination->divisors[log10Value] = divisor; - } - } - } - } - } -}; - -// Virtual destructors must be defined out of line. -CmptDecDataSink::~CmptDecDataSink() {} - -} // namespace - -static void load(const Locale& inLocale, CDFLocaleData* result, UErrorCode& status) { - LocalPointer ns(NumberingSystem::createInstance(inLocale, status)); - if (U_FAILURE(status)) { - return; - } - const char* nsName = ns->getName(); - - LocalUResourceBundlePointer resource(ures_open(NULL, inLocale.getName(), &status)); - if (U_FAILURE(status)) { - return; - } - CmptDecDataSink sink(*result); - sink.isFallback = FALSE; - - // First load the number elements data if nsName is not Latin. - if (uprv_strcmp(nsName, gLatnTag) != 0) { - sink.isLatin = FALSE; - CharString path; - path.append(gNumberElementsTag, status) - .append('/', status) - .append(nsName, status); - ures_getAllItemsWithFallback(resource.getAlias(), path.data(), sink, status); - if (status == U_MISSING_RESOURCE_ERROR) { - // Silently ignore and use Latin - status = U_ZERO_ERROR; - } else if (U_FAILURE(status)) { - return; - } - sink.isFallback = TRUE; - } - - // Now load Latin. - sink.isLatin = TRUE; - ures_getAllItemsWithFallback(resource.getAlias(), gLatnPath, sink, status); - if (U_FAILURE(status)) return; - - // If longData is empty, default it to be equal to shortData - if (result->longData.isEmpty()) { - result->longData.setToBogus(); - } - - // Check for "other" variants in each of the three data classes, and resolve missing elements. - - if (!result->longData.isBogus()) { - checkForOtherVariants(&result->longData, status); - if (U_FAILURE(status)) return; - fillInMissing(&result->longData); - } - - checkForOtherVariants(&result->shortData, status); - if (U_FAILURE(status)) return; - fillInMissing(&result->shortData); - - // TODO: Enable this statement when currency support is added - // checkForOtherVariants(&result->shortCurrencyData, status); - // if (U_FAILURE(status)) return; - // fillInMissing(&result->shortCurrencyData); -} - -// populatePrefixSuffix Adds a specific prefix-suffix pair to result for a -// given variant and log10 value. -// variant is 'zero', 'one', 'two', 'few', 'many', or 'other'. -// formatStr is the format string from which the prefix and suffix are -// extracted. It is usually of form 'Pefix 000 suffix'. -// populatePrefixSuffix returns the number of 0's found in formatStr -// before the decimal point. -// In the special case that formatStr contains only spaces for prefix -// and suffix, populatePrefixSuffix returns log10Value + 1. -static int32_t populatePrefixSuffix( - const char* variant, int32_t log10Value, const UnicodeString& formatStr, UHashtable* result, UBool overwrite, UErrorCode& status) { - if (U_FAILURE(status)) { - return 0; - } - - // ICU 59 HACK: Ignore negative part of format string, mimicking ICU 58 behavior. - // TODO(sffc): Make sure this is fixed during the overhaul port in ICU 60. - int32_t semiPos = formatStr.indexOf(';', 0); - if (semiPos == -1) { - semiPos = formatStr.length(); - } - UnicodeString positivePart = formatStr.tempSubString(0, semiPos); - - int32_t firstIdx = positivePart.indexOf(kZero, UPRV_LENGTHOF(kZero), 0); - // We must have 0's in format string. - if (firstIdx == -1) { - status = U_INTERNAL_PROGRAM_ERROR; - return 0; - } - int32_t lastIdx = positivePart.lastIndexOf(kZero, UPRV_LENGTHOF(kZero), firstIdx); - CDFUnit* unit = createCDFUnit(variant, log10Value, result, status); - if (U_FAILURE(status)) { - return 0; - } - - // Return -1 if we are not overwriting an existing value - if (unit->isSet() && !overwrite) { - return -1; - } - unit->markAsSet(); - - // Everything up to first 0 is the prefix - unit->prefix = positivePart.tempSubString(0, firstIdx); - fixQuotes(unit->prefix); - // Everything beyond the last 0 is the suffix - unit->suffix = positivePart.tempSubString(lastIdx + 1); - fixQuotes(unit->suffix); - - // If there is effectively no prefix or suffix, ignore the actual number of - // 0's and act as if the number of 0's matches the size of the number. - if (onlySpaces(unit->prefix) && onlySpaces(unit->suffix)) { - return log10Value + 1; - } - - // Calculate number of zeros before decimal point - int32_t idx = firstIdx + 1; - while (idx <= lastIdx && positivePart.charAt(idx) == u_0) { - ++idx; - } - return (idx - firstIdx); -} - -// Calculate a divisor based on the magnitude and number of zeros in the -// template string. -static double calculateDivisor(double power10, int32_t numZeros) { - double divisor = power10; - for (int32_t i = 1; i < numZeros; ++i) { - divisor /= 10.0; - } - return divisor; -} - -static UBool onlySpaces(UnicodeString u) { - return u.trim().length() == 0; -} - -// fixQuotes unescapes single quotes. Don''t -> Don't. Letter 'j' -> Letter j. -// Modifies s in place. -static void fixQuotes(UnicodeString& s) { - QuoteState state = OUTSIDE; - int32_t len = s.length(); - int32_t dest = 0; - for (int32_t i = 0; i < len; ++i) { - UChar ch = s.charAt(i); - if (ch == u_apos) { - if (state == INSIDE_EMPTY) { - s.setCharAt(dest, ch); - ++dest; - } - } else { - s.setCharAt(dest, ch); - ++dest; - } - - // Update state - switch (state) { - case OUTSIDE: - state = ch == u_apos ? INSIDE_EMPTY : OUTSIDE; - break; - case INSIDE_EMPTY: - case INSIDE_FULL: - state = ch == u_apos ? OUTSIDE : INSIDE_FULL; - break; - default: - break; - } - } - s.truncate(dest); -} - -// Checks to make sure that an "other" variant is present in all -// powers of 10. -static void checkForOtherVariants(CDFLocaleStyleData* result, - UErrorCode& status) { - if (result == NULL || result->unitsByVariant == NULL) { - return; - } - - const CDFUnit* otherByBase = - (const CDFUnit*) uhash_get(result->unitsByVariant, gOther); - if (otherByBase == NULL) { - status = U_INTERNAL_PROGRAM_ERROR; - return; - } - - // Check all other plural variants, and make sure that if - // any of them are populated, then other is also populated - int32_t pos = UHASH_FIRST; - const UHashElement* element; - while ((element = uhash_nextElement(result->unitsByVariant, &pos)) != NULL) { - CDFUnit* variantsByBase = (CDFUnit*) element->value.pointer; - if (variantsByBase == otherByBase) continue; - for (int32_t log10Value = 0; log10Value < MAX_DIGITS; ++log10Value) { - if (variantsByBase[log10Value].isSet() - && !otherByBase[log10Value].isSet()) { - status = U_INTERNAL_PROGRAM_ERROR; - return; - } - } - } -} - -// fillInMissing ensures that the data in result is complete. -// result data is complete if for each variant in result, there exists -// a prefix-suffix pair for each log10 value and there also exists -// a divisor for each log10 value. -// -// First this function figures out for which log10 values, the other -// variant already had data. These are the same log10 values defined -// in CLDR. -// -// For each log10 value not defined in CLDR, it uses the divisor for -// the last defined log10 value or 1. -// -// Then for each variant, it does the following. For each log10 -// value not defined in CLDR, copy the prefix-suffix pair from the -// previous log10 value. If log10 value is defined in CLDR but is -// missing from given variant, copy the prefix-suffix pair for that -// log10 value from the 'other' variant. -static void fillInMissing(CDFLocaleStyleData* result) { - const CDFUnit* otherUnits = - (const CDFUnit*) uhash_get(result->unitsByVariant, gOther); - UBool definedInCLDR[MAX_DIGITS]; - double lastDivisor = 1.0; - for (int32_t i = 0; i < MAX_DIGITS; ++i) { - if (!otherUnits[i].isSet()) { - result->divisors[i] = lastDivisor; - definedInCLDR[i] = FALSE; - } else { - lastDivisor = result->divisors[i]; - definedInCLDR[i] = TRUE; - } - } - // Iterate over each variant. - int32_t pos = UHASH_FIRST; - const UHashElement* element = uhash_nextElement(result->unitsByVariant, &pos); - for (;element != NULL; element = uhash_nextElement(result->unitsByVariant, &pos)) { - CDFUnit* units = (CDFUnit*) element->value.pointer; - for (int32_t i = 0; i < MAX_DIGITS; ++i) { - if (definedInCLDR[i]) { - if (!units[i].isSet()) { - units[i] = otherUnits[i]; - } - } else { - if (i == 0) { - units[0].markAsSet(); - } else { - units[i] = units[i - 1]; - } - } - } - } -} - -// computeLog10 computes floor(log10(x)). If inRange is TRUE, the biggest -// value computeLog10 will return MAX_DIGITS -1 even for -// numbers > 10^MAX_DIGITS. If inRange is FALSE, computeLog10 will return -// up to MAX_DIGITS. -static int32_t computeLog10(double x, UBool inRange) { - int32_t result = 0; - int32_t max = inRange ? MAX_DIGITS - 1 : MAX_DIGITS; - while (x >= 10.0) { - x /= 10.0; - ++result; - if (result == max) { - break; - } - } - return result; -} - -// createCDFUnit returns a pointer to the prefix-suffix pair for a given -// variant and log10 value within table. If no such prefix-suffix pair is -// stored in table, one is created within table before returning pointer. -static CDFUnit* createCDFUnit(const char* variant, int32_t log10Value, UHashtable* table, UErrorCode& status) { - if (U_FAILURE(status)) { - return NULL; - } - CDFUnit *cdfUnit = (CDFUnit*) uhash_get(table, variant); - if (cdfUnit == NULL) { - cdfUnit = new CDFUnit[MAX_DIGITS]; - if (cdfUnit == NULL) { - status = U_MEMORY_ALLOCATION_ERROR; - return NULL; - } - uhash_put(table, uprv_strdup(variant), cdfUnit, &status); - if (U_FAILURE(status)) { - return NULL; - } - } - CDFUnit* result = &cdfUnit[log10Value]; - return result; -} - -// getCDFUnitFallback returns a pointer to the prefix-suffix pair for a given -// variant and log10 value within table. If the given variant doesn't exist, it -// falls back to the OTHER variant. Therefore, this method will always return -// some non-NULL value. -static const CDFUnit* getCDFUnitFallback(const UHashtable* table, const UnicodeString& variant, int32_t log10Value) { - CharString cvariant; - UErrorCode status = U_ZERO_ERROR; - const CDFUnit *cdfUnit = NULL; - cvariant.appendInvariantChars(variant, status); - if (!U_FAILURE(status)) { - cdfUnit = (const CDFUnit*) uhash_get(table, cvariant.data()); - } - if (cdfUnit == NULL) { - cdfUnit = (const CDFUnit*) uhash_get(table, gOther); - } - return &cdfUnit[log10Value]; -} - -U_NAMESPACE_END -#endif +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp index 713abd7f9f..99fbfb54bd 100644 --- a/icu4c/source/i18n/decimfmt.cpp +++ b/icu4c/source/i18n/decimfmt.cpp @@ -1,3296 +1,19 @@ -// © 2016 and later: Unicode, Inc. and others. +// © 2018 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* Copyright (C) 1997-2015, International Business Machines Corporation and * -* others. All Rights Reserved. * -******************************************************************************* -* -* File DECIMFMT.CPP -* -* Modification History: -* -* Date Name Description -* 02/19/97 aliu Converted from java. -* 03/20/97 clhuang Implemented with new APIs. -* 03/31/97 aliu Moved isLONG_MIN to DigitList, and fixed it. -* 04/3/97 aliu Rewrote parsing and formatting completely, and -* cleaned up and debugged. Actually works now. -* Implemented NAN and INF handling, for both parsing -* and formatting. Extensive testing & debugging. -* 04/10/97 aliu Modified to compile on AIX. -* 04/16/97 aliu Rewrote to use DigitList, which has been resurrected. -* Changed DigitCount to int per code review. -* 07/09/97 helena Made ParsePosition into a class. -* 08/26/97 aliu Extensive changes to applyPattern; completely -* rewritten from the Java. -* 09/09/97 aliu Ported over support for exponential formats. -* 07/20/98 stephen JDK 1.2 sync up. -* Various instances of '0' replaced with 'NULL' -* Check for grouping size in subFormat() -* Brought subParse() in line with Java 1.2 -* Added method appendAffix() -* 08/24/1998 srl Removed Mutex calls. This is not a thread safe class! -* 02/22/99 stephen Removed character literals for EBCDIC safety -* 06/24/99 helena Integrated Alan's NF enhancements and Java2 bug fixes -* 06/28/99 stephen Fixed bugs in toPattern(). -* 06/29/99 stephen Fixed operator= to copy fFormatWidth, fPad, -* fPadPosition -******************************************************************************** -*/ #include "unicode/utypes.h" -#if !UCONFIG_NO_FORMATTING +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT -#include "unicode/uniset.h" -#include "unicode/currpinf.h" -#include "unicode/plurrule.h" -#include "unicode/utf16.h" -#include "unicode/numsys.h" -#include "unicode/localpointer.h" -#include "unicode/ustring.h" -#include "uresimp.h" -#include "ucurrimp.h" -#include "charstr.h" -#include "patternprops.h" -#include "cstring.h" -#include "uassert.h" -#include "hash.h" -#include "decfmtst.h" -#include "plurrule_impl.h" -#include "decimalformatpattern.h" -#include "fmtableimp.h" -#include "decimfmtimpl.h" -#include "visibledigits.h" +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT -/* - * On certain platforms, round is a macro defined in math.h - * This undefine is to avoid conflict between the macro and - * the function defined below. - */ -#ifdef round -#undef round -#endif +using namespace icu; -U_NAMESPACE_BEGIN -#ifdef FMT_DEBUG -#include -static void _debugout(const char *f, int l, const UnicodeString& s) { - char buf[2000]; - s.extract((int32_t) 0, s.length(), buf, "utf-8"); - printf("%s:%d: %s\n", f,l, buf); -} -#define debugout(x) _debugout(__FILE__,__LINE__,x) -#define debug(x) printf("%s:%d: %s\n", __FILE__,__LINE__, x); -static const UnicodeString dbg_null("",""); -#define DEREFSTR(x) ((x!=NULL)?(*x):(dbg_null)) -#else -#define debugout(x) -#define debug(x) -#endif -/* For currency parsing purose, - * Need to remember all prefix patterns and suffix patterns of - * every currency format pattern, - * including the pattern of default currecny style - * and plural currency style. And the patterns are set through applyPattern. - */ -struct AffixPatternsForCurrency : public UMemory { - // negative prefix pattern - UnicodeString negPrefixPatternForCurrency; - // negative suffix pattern - UnicodeString negSuffixPatternForCurrency; - // positive prefix pattern - UnicodeString posPrefixPatternForCurrency; - // positive suffix pattern - UnicodeString posSuffixPatternForCurrency; - int8_t patternType; - - AffixPatternsForCurrency(const UnicodeString& negPrefix, - const UnicodeString& negSuffix, - const UnicodeString& posPrefix, - const UnicodeString& posSuffix, - int8_t type) { - negPrefixPatternForCurrency = negPrefix; - negSuffixPatternForCurrency = negSuffix; - posPrefixPatternForCurrency = posPrefix; - posSuffixPatternForCurrency = posSuffix; - patternType = type; - } -#ifdef FMT_DEBUG - void dump() const { - debugout( UnicodeString("AffixPatternsForCurrency( -=\"") + - negPrefixPatternForCurrency + (UnicodeString)"\"/\"" + - negSuffixPatternForCurrency + (UnicodeString)"\" +=\"" + - posPrefixPatternForCurrency + (UnicodeString)"\"/\"" + - posSuffixPatternForCurrency + (UnicodeString)"\" )"); - } -#endif -}; - -/* affix for currency formatting when the currency sign in the pattern - * equals to 3, such as the pattern contains 3 currency sign or - * the formatter style is currency plural format style. - */ -struct AffixesForCurrency : public UMemory { - // negative prefix - UnicodeString negPrefixForCurrency; - // negative suffix - UnicodeString negSuffixForCurrency; - // positive prefix - UnicodeString posPrefixForCurrency; - // positive suffix - UnicodeString posSuffixForCurrency; - - int32_t formatWidth; - - AffixesForCurrency(const UnicodeString& negPrefix, - const UnicodeString& negSuffix, - const UnicodeString& posPrefix, - const UnicodeString& posSuffix) { - negPrefixForCurrency = negPrefix; - negSuffixForCurrency = negSuffix; - posPrefixForCurrency = posPrefix; - posSuffixForCurrency = posSuffix; - } -#ifdef FMT_DEBUG - void dump() const { - debugout( UnicodeString("AffixesForCurrency( -=\"") + - negPrefixForCurrency + (UnicodeString)"\"/\"" + - negSuffixForCurrency + (UnicodeString)"\" +=\"" + - posPrefixForCurrency + (UnicodeString)"\"/\"" + - posSuffixForCurrency + (UnicodeString)"\" )"); - } -#endif -}; - -U_CDECL_BEGIN - -/** - * @internal ICU 4.2 - */ -static UBool U_CALLCONV decimfmtAffixPatternValueComparator(UHashTok val1, UHashTok val2); - - -static UBool -U_CALLCONV decimfmtAffixPatternValueComparator(UHashTok val1, UHashTok val2) { - const AffixPatternsForCurrency* affix_1 = - (AffixPatternsForCurrency*)val1.pointer; - const AffixPatternsForCurrency* affix_2 = - (AffixPatternsForCurrency*)val2.pointer; - return affix_1->negPrefixPatternForCurrency == - affix_2->negPrefixPatternForCurrency && - affix_1->negSuffixPatternForCurrency == - affix_2->negSuffixPatternForCurrency && - affix_1->posPrefixPatternForCurrency == - affix_2->posPrefixPatternForCurrency && - affix_1->posSuffixPatternForCurrency == - affix_2->posSuffixPatternForCurrency && - affix_1->patternType == affix_2->patternType; -} - -U_CDECL_END - - - - -// ***************************************************************************** -// class DecimalFormat -// ***************************************************************************** - -UOBJECT_DEFINE_RTTI_IMPLEMENTATION(DecimalFormat) - -// Constants for characters used in programmatic (unlocalized) patterns. -#define kPatternZeroDigit ((UChar)0x0030) /*'0'*/ -#define kPatternSignificantDigit ((UChar)0x0040) /*'@'*/ -#define kPatternGroupingSeparator ((UChar)0x002C) /*','*/ -#define kPatternDecimalSeparator ((UChar)0x002E) /*'.'*/ -#define kPatternPerMill ((UChar)0x2030) -#define kPatternPercent ((UChar)0x0025) /*'%'*/ -#define kPatternDigit ((UChar)0x0023) /*'#'*/ -#define kPatternSeparator ((UChar)0x003B) /*';'*/ -#define kPatternExponent ((UChar)0x0045) /*'E'*/ -#define kPatternPlus ((UChar)0x002B) /*'+'*/ -#define kPatternMinus ((UChar)0x002D) /*'-'*/ -#define kPatternPadEscape ((UChar)0x002A) /*'*'*/ -#define kQuote ((UChar)0x0027) /*'\''*/ -/** - * The CURRENCY_SIGN is the standard Unicode symbol for currency. It - * is used in patterns and substitued with either the currency symbol, - * or if it is doubled, with the international currency symbol. If the - * CURRENCY_SIGN is seen in a pattern, then the decimal separator is - * replaced with the monetary decimal separator. - */ -#define kCurrencySign ((UChar)0x00A4) -#define kDefaultPad ((UChar)0x0020) /* */ - -const int32_t DecimalFormat::kDoubleIntegerDigits = 309; -const int32_t DecimalFormat::kDoubleFractionDigits = 340; - -const int32_t DecimalFormat::kMaxScientificIntegerDigits = 8; - -/** - * These are the tags we expect to see in normal resource bundle files associated - * with a locale. - */ -const char DecimalFormat::fgNumberPatterns[]="NumberPatterns"; // Deprecated - not used -static const char fgNumberElements[]="NumberElements"; -static const char fgLatn[]="latn"; -static const char fgPatterns[]="patterns"; -static const char fgDecimalFormat[]="decimalFormat"; -static const char fgCurrencyFormat[]="currencyFormat"; - -inline int32_t _min(int32_t a, int32_t b) { return (a adoptedSymbols(symbolsToAdopt); - if (U_FAILURE(status)) - return; - - if (adoptedSymbols.isNull()) - { - adoptedSymbols.adoptInstead( - new DecimalFormatSymbols(Locale::getDefault(), status)); - if (adoptedSymbols.isNull() && U_SUCCESS(status)) { - status = U_MEMORY_ALLOCATION_ERROR; - } - if (U_FAILURE(status)) { - return; - } - } - fStaticSets = DecimalFormatStaticSets::getStaticSets(status); - if (U_FAILURE(status)) { - return; - } - - UnicodeString str; - // Uses the default locale's number format pattern if there isn't - // one specified. - if (pattern == NULL) - { - UErrorCode nsStatus = U_ZERO_ERROR; - LocalPointer ns( - NumberingSystem::createInstance(nsStatus)); - if (U_FAILURE(nsStatus)) { - status = nsStatus; - return; - } - - int32_t len = 0; - UResourceBundle *top = ures_open(NULL, Locale::getDefault().getName(), &status); - - UResourceBundle *resource = ures_getByKeyWithFallback(top, fgNumberElements, NULL, &status); - resource = ures_getByKeyWithFallback(resource, ns->getName(), resource, &status); - resource = ures_getByKeyWithFallback(resource, fgPatterns, resource, &status); - const UChar *resStr = ures_getStringByKeyWithFallback(resource, fgDecimalFormat, &len, &status); - if ( status == U_MISSING_RESOURCE_ERROR && uprv_strcmp(fgLatn,ns->getName())) { - status = U_ZERO_ERROR; - resource = ures_getByKeyWithFallback(top, fgNumberElements, resource, &status); - resource = ures_getByKeyWithFallback(resource, fgLatn, resource, &status); - resource = ures_getByKeyWithFallback(resource, fgPatterns, resource, &status); - resStr = ures_getStringByKeyWithFallback(resource, fgDecimalFormat, &len, &status); - } - str.setTo(TRUE, resStr, len); - pattern = &str; - ures_close(resource); - ures_close(top); - } - - fImpl = new DecimalFormatImpl(this, *pattern, adoptedSymbols.getAlias(), parseErr, status); - if (fImpl) { - adoptedSymbols.orphan(); - } else if (U_SUCCESS(status)) { - status = U_MEMORY_ALLOCATION_ERROR; - } - if (U_FAILURE(status)) { - return; - } - - if (U_FAILURE(status)) - { - return; - } - - const UnicodeString* patternUsed; - UnicodeString currencyPluralPatternForOther; - // apply pattern - if (fStyle == UNUM_CURRENCY_PLURAL) { - fCurrencyPluralInfo = new CurrencyPluralInfo(fImpl->fSymbols->getLocale(), status); - if (U_FAILURE(status)) { - return; - } - - // the pattern used in format is not fixed until formatting, - // in which, the number is known and - // will be used to pick the right pattern based on plural count. - // Here, set the pattern as the pattern of plural count == "other". - // For most locale, the patterns are probably the same for all - // plural count. If not, the right pattern need to be re-applied - // during format. - fCurrencyPluralInfo->getCurrencyPluralPattern(UNICODE_STRING("other", 5), currencyPluralPatternForOther); - // TODO(refactor): Revisit, we are setting the pattern twice. - fImpl->applyPatternFavorCurrencyPrecision( - currencyPluralPatternForOther, status); - patternUsed = ¤cyPluralPatternForOther; - - } else { - patternUsed = pattern; - } - - if (patternUsed->indexOf(kCurrencySign) != -1) { - // initialize for currency, not only for plural format, - // but also for mix parsing - handleCurrencySignInPattern(status); - } -} - -void -DecimalFormat::handleCurrencySignInPattern(UErrorCode& status) { - // initialize for currency, not only for plural format, - // but also for mix parsing - if (U_FAILURE(status)) { - return; - } - if (fCurrencyPluralInfo == NULL) { - fCurrencyPluralInfo = new CurrencyPluralInfo(fImpl->fSymbols->getLocale(), status); - if (U_FAILURE(status)) { - return; - } - } - // need it for mix parsing - if (fAffixPatternsForCurrency == NULL) { - setupCurrencyAffixPatterns(status); - } -} - -static void -applyPatternWithNoSideEffects( - const UnicodeString& pattern, - UParseError& parseError, - UnicodeString &negPrefix, - UnicodeString &negSuffix, - UnicodeString &posPrefix, - UnicodeString &posSuffix, - UErrorCode& status) { - if (U_FAILURE(status)) - { - return; - } - DecimalFormatPatternParser patternParser; - DecimalFormatPattern out; - patternParser.applyPatternWithoutExpandAffix( - pattern, - out, - parseError, - status); - if (U_FAILURE(status)) { - return; - } - negPrefix = out.fNegPrefixPattern; - negSuffix = out.fNegSuffixPattern; - posPrefix = out.fPosPrefixPattern; - posSuffix = out.fPosSuffixPattern; -} - -void -DecimalFormat::setupCurrencyAffixPatterns(UErrorCode& status) { - if (U_FAILURE(status)) { - return; - } - UParseError parseErr; - fAffixPatternsForCurrency = initHashForAffixPattern(status); - if (U_FAILURE(status)) { - return; - } - - NumberingSystem *ns = NumberingSystem::createInstance(fImpl->fSymbols->getLocale(),status); - if (U_FAILURE(status)) { - return; - } - - // Save the default currency patterns of this locale. - // Here, chose onlyApplyPatternWithoutExpandAffix without - // expanding the affix patterns into affixes. - UnicodeString currencyPattern; - UErrorCode error = U_ZERO_ERROR; - - UResourceBundle *resource = ures_open(NULL, fImpl->fSymbols->getLocale().getName(), &error); - UResourceBundle *numElements = ures_getByKeyWithFallback(resource, fgNumberElements, NULL, &error); - resource = ures_getByKeyWithFallback(numElements, ns->getName(), resource, &error); - resource = ures_getByKeyWithFallback(resource, fgPatterns, resource, &error); - int32_t patLen = 0; - const UChar *patResStr = ures_getStringByKeyWithFallback(resource, fgCurrencyFormat, &patLen, &error); - if ( error == U_MISSING_RESOURCE_ERROR && uprv_strcmp(ns->getName(),fgLatn)) { - error = U_ZERO_ERROR; - resource = ures_getByKeyWithFallback(numElements, fgLatn, resource, &error); - resource = ures_getByKeyWithFallback(resource, fgPatterns, resource, &error); - patResStr = ures_getStringByKeyWithFallback(resource, fgCurrencyFormat, &patLen, &error); - } - ures_close(numElements); - ures_close(resource); - delete ns; - - if (U_SUCCESS(error)) { - UnicodeString negPrefix; - UnicodeString negSuffix; - UnicodeString posPrefix; - UnicodeString posSuffix; - applyPatternWithNoSideEffects(UnicodeString(patResStr, patLen), - parseErr, - negPrefix, negSuffix, posPrefix, posSuffix, status); - AffixPatternsForCurrency* affixPtn = new AffixPatternsForCurrency( - negPrefix, - negSuffix, - posPrefix, - posSuffix, - UCURR_SYMBOL_NAME); - fAffixPatternsForCurrency->put(UNICODE_STRING("default", 7), affixPtn, status); - } - - // save the unique currency plural patterns of this locale. - Hashtable* pluralPtn = fCurrencyPluralInfo->fPluralCountToCurrencyUnitPattern; - const UHashElement* element = NULL; - int32_t pos = UHASH_FIRST; - Hashtable pluralPatternSet; - while ((element = pluralPtn->nextElement(pos)) != NULL) { - const UHashTok valueTok = element->value; - const UnicodeString* value = (UnicodeString*)valueTok.pointer; - const UHashTok keyTok = element->key; - const UnicodeString* key = (UnicodeString*)keyTok.pointer; - if (pluralPatternSet.geti(*value) != 1) { - UnicodeString negPrefix; - UnicodeString negSuffix; - UnicodeString posPrefix; - UnicodeString posSuffix; - pluralPatternSet.puti(*value, 1, status); - applyPatternWithNoSideEffects( - *value, parseErr, - negPrefix, negSuffix, posPrefix, posSuffix, status); - AffixPatternsForCurrency* affixPtn = new AffixPatternsForCurrency( - negPrefix, - negSuffix, - posPrefix, - posSuffix, - UCURR_LONG_NAME); - fAffixPatternsForCurrency->put(*key, affixPtn, status); - } - } -} - - -//------------------------------------------------------------------------------ - -DecimalFormat::~DecimalFormat() -{ - deleteHashForAffixPattern(); - delete fCurrencyPluralInfo; - delete fImpl; -} - -//------------------------------------------------------------------------------ -// copy constructor - -DecimalFormat::DecimalFormat(const DecimalFormat &source) : - NumberFormat(source) { - init(); - *this = source; -} - -//------------------------------------------------------------------------------ -// assignment operator - -template -static void _clone_ptr(T** pdest, const T* source) { - delete *pdest; - if (source == NULL) { - *pdest = NULL; - } else { - *pdest = static_cast(source->clone()); - } -} - -DecimalFormat& -DecimalFormat::operator=(const DecimalFormat& rhs) -{ - if(this != &rhs) { - UErrorCode status = U_ZERO_ERROR; - NumberFormat::operator=(rhs); - if (fImpl == NULL) { - fImpl = new DecimalFormatImpl(this, *rhs.fImpl, status); - } else { - fImpl->assign(*rhs.fImpl, status); - } - fStaticSets = DecimalFormatStaticSets::getStaticSets(status); - fStyle = rhs.fStyle; - _clone_ptr(&fCurrencyPluralInfo, rhs.fCurrencyPluralInfo); - deleteHashForAffixPattern(); - if (rhs.fAffixPatternsForCurrency) { - UErrorCode status = U_ZERO_ERROR; - fAffixPatternsForCurrency = initHashForAffixPattern(status); - copyHashForAffixPattern(rhs.fAffixPatternsForCurrency, - fAffixPatternsForCurrency, status); - } - } - - return *this; -} - -//------------------------------------------------------------------------------ - -UBool -DecimalFormat::operator==(const Format& that) const -{ - if (this == &that) - return TRUE; - - // NumberFormat::operator== guarantees this cast is safe - const DecimalFormat* other = (DecimalFormat*)&that; - - return ( - NumberFormat::operator==(that) && - fBoolFlags.getAll() == other->fBoolFlags.getAll() && - *fImpl == *other->fImpl); - -} - -//------------------------------------------------------------------------------ - -Format* -DecimalFormat::clone() const -{ - return new DecimalFormat(*this); -} - - -FixedDecimal -DecimalFormat::getFixedDecimal(double number, UErrorCode &status) const { - VisibleDigitsWithExponent digits; - initVisibleDigitsWithExponent(number, digits, status); - if (U_FAILURE(status)) { - return FixedDecimal(); - } - return FixedDecimal(digits.getMantissa()); -} - -VisibleDigitsWithExponent & -DecimalFormat::initVisibleDigitsWithExponent( - double number, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const { - return fImpl->initVisibleDigitsWithExponent(number, digits, status); -} - -FixedDecimal -DecimalFormat::getFixedDecimal(const Formattable &number, UErrorCode &status) const { - VisibleDigitsWithExponent digits; - initVisibleDigitsWithExponent(number, digits, status); - if (U_FAILURE(status)) { - return FixedDecimal(); - } - return FixedDecimal(digits.getMantissa()); -} - -VisibleDigitsWithExponent & -DecimalFormat::initVisibleDigitsWithExponent( - const Formattable &number, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const { - if (U_FAILURE(status)) { - return digits; - } - if (!number.isNumeric()) { - status = U_ILLEGAL_ARGUMENT_ERROR; - return digits; - } - - DigitList *dl = number.getDigitList(); - if (dl != NULL) { - DigitList dlCopy(*dl); - return fImpl->initVisibleDigitsWithExponent( - dlCopy, digits, status); - } - - Formattable::Type type = number.getType(); - if (type == Formattable::kDouble || type == Formattable::kLong) { - return fImpl->initVisibleDigitsWithExponent( - number.getDouble(status), digits, status); - } - return fImpl->initVisibleDigitsWithExponent( - number.getInt64(), digits, status); -} - - -// Create a fixed decimal from a DigitList. -// The digit list may be modified. -// Internal function only. -FixedDecimal -DecimalFormat::getFixedDecimal(DigitList &number, UErrorCode &status) const { - VisibleDigitsWithExponent digits; - initVisibleDigitsWithExponent(number, digits, status); - if (U_FAILURE(status)) { - return FixedDecimal(); - } - return FixedDecimal(digits.getMantissa()); -} - -VisibleDigitsWithExponent & -DecimalFormat::initVisibleDigitsWithExponent( - DigitList &number, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const { - return fImpl->initVisibleDigitsWithExponent( - number, digits, status); -} - - -//------------------------------------------------------------------------------ - -UnicodeString& -DecimalFormat::format(int32_t number, - UnicodeString& appendTo, - FieldPosition& fieldPosition) const -{ - UErrorCode status = U_ZERO_ERROR; - return fImpl->format(number, appendTo, fieldPosition, status); -} - -UnicodeString& -DecimalFormat::format(int32_t number, - UnicodeString& appendTo, - FieldPosition& fieldPosition, - UErrorCode& status) const -{ - return fImpl->format(number, appendTo, fieldPosition, status); -} - -UnicodeString& -DecimalFormat::format(int32_t number, - UnicodeString& appendTo, - FieldPositionIterator* posIter, - UErrorCode& status) const -{ - return fImpl->format(number, appendTo, posIter, status); -} - - -//------------------------------------------------------------------------------ - -UnicodeString& -DecimalFormat::format(int64_t number, - UnicodeString& appendTo, - FieldPosition& fieldPosition) const -{ - UErrorCode status = U_ZERO_ERROR; /* ignored */ - return fImpl->format(number, appendTo, fieldPosition, status); -} - -UnicodeString& -DecimalFormat::format(int64_t number, - UnicodeString& appendTo, - FieldPosition& fieldPosition, - UErrorCode& status) const -{ - return fImpl->format(number, appendTo, fieldPosition, status); -} - -UnicodeString& -DecimalFormat::format(int64_t number, - UnicodeString& appendTo, - FieldPositionIterator* posIter, - UErrorCode& status) const -{ - return fImpl->format(number, appendTo, posIter, status); -} - -//------------------------------------------------------------------------------ - -UnicodeString& -DecimalFormat::format( double number, - UnicodeString& appendTo, - FieldPosition& fieldPosition) const -{ - UErrorCode status = U_ZERO_ERROR; /* ignored */ - return fImpl->format(number, appendTo, fieldPosition, status); -} - -UnicodeString& -DecimalFormat::format( double number, - UnicodeString& appendTo, - FieldPosition& fieldPosition, - UErrorCode& status) const -{ - return fImpl->format(number, appendTo, fieldPosition, status); -} - -UnicodeString& -DecimalFormat::format( double number, - UnicodeString& appendTo, - FieldPositionIterator* posIter, - UErrorCode& status) const -{ - return fImpl->format(number, appendTo, posIter, status); -} - -//------------------------------------------------------------------------------ - - -UnicodeString& -DecimalFormat::format(StringPiece number, - UnicodeString &toAppendTo, - FieldPositionIterator *posIter, - UErrorCode &status) const -{ - return fImpl->format(number, toAppendTo, posIter, status); -} - - -UnicodeString& -DecimalFormat::format(const DigitList &number, - UnicodeString &appendTo, - FieldPositionIterator *posIter, - UErrorCode &status) const { - return fImpl->format(number, appendTo, posIter, status); -} - - -UnicodeString& -DecimalFormat::format(const DigitList &number, - UnicodeString& appendTo, - FieldPosition& pos, - UErrorCode &status) const { - return fImpl->format(number, appendTo, pos, status); -} - -UnicodeString& -DecimalFormat::format(const VisibleDigitsWithExponent &number, - UnicodeString &appendTo, - FieldPositionIterator *posIter, - UErrorCode &status) const { - return fImpl->format(number, appendTo, posIter, status); -} - - -UnicodeString& -DecimalFormat::format(const VisibleDigitsWithExponent &number, - UnicodeString& appendTo, - FieldPosition& pos, - UErrorCode &status) const { - return fImpl->format(number, appendTo, pos, status); -} - -DigitList& -DecimalFormat::_round(const DigitList& number, DigitList& adjustedNum, UBool& isNegative, UErrorCode& status) const { - adjustedNum = number; - fImpl->round(adjustedNum, status); - isNegative = !adjustedNum.isPositive(); - return adjustedNum; -} - -void -DecimalFormat::parse(const UnicodeString& text, - Formattable& result, - ParsePosition& parsePosition) const { - parse(text, result, parsePosition, NULL); -} - -CurrencyAmount* DecimalFormat::parseCurrency(const UnicodeString& text, - ParsePosition& pos) const { - Formattable parseResult; - int32_t start = pos.getIndex(); - UChar curbuf[4] = {}; - parse(text, parseResult, pos, curbuf); - if (pos.getIndex() != start) { - UErrorCode ec = U_ZERO_ERROR; - LocalPointer currAmt(new CurrencyAmount(parseResult, curbuf, ec), ec); - if (U_FAILURE(ec)) { - pos.setIndex(start); // indicate failure - } else { - return currAmt.orphan(); - } - } - return NULL; -} - -/** - * Parses the given text as a number, optionally providing a currency amount. - * @param text the string to parse - * @param result output parameter for the numeric result. - * @param parsePosition input-output position; on input, the - * position within text to match; must have 0 <= pos.getIndex() < - * text.length(); on output, the position after the last matched - * character. If the parse fails, the position in unchanged upon - * output. - * @param currency if non-NULL, it should point to a 4-UChar buffer. - * In this case the text is parsed as a currency format, and the - * ISO 4217 code for the parsed currency is put into the buffer. - * Otherwise the text is parsed as a non-currency format. - */ -void DecimalFormat::parse(const UnicodeString& text, - Formattable& result, - ParsePosition& parsePosition, - UChar* currency) const { - int32_t startIdx, backup; - int32_t i = startIdx = backup = parsePosition.getIndex(); - - // clear any old contents in the result. In particular, clears any DigitList - // that it may be holding. - result.setLong(0); - if (currency != NULL) { - for (int32_t ci=0; ci<4; ci++) { - currency[ci] = 0; - } - } - - // Handle NaN as a special case: - int32_t formatWidth = fImpl->getOldFormatWidth(); - - // Skip padding characters, if around prefix - if (formatWidth > 0 && ( - fImpl->fAffixes.fPadPosition == DigitAffixesAndPadding::kPadBeforePrefix || - fImpl->fAffixes.fPadPosition == DigitAffixesAndPadding::kPadAfterPrefix)) { - i = skipPadding(text, i); - } - - if (isLenient()) { - // skip any leading whitespace - i = backup = skipUWhiteSpace(text, i); - } - - // If the text is composed of the representation of NaN, returns NaN.length - const UnicodeString *nan = &fImpl->getConstSymbol(DecimalFormatSymbols::kNaNSymbol); - int32_t nanLen = (text.compare(i, nan->length(), *nan) - ? 0 : nan->length()); - if (nanLen) { - i += nanLen; - if (formatWidth > 0 && (fImpl->fAffixes.fPadPosition == DigitAffixesAndPadding::kPadBeforeSuffix || fImpl->fAffixes.fPadPosition == DigitAffixesAndPadding::kPadAfterSuffix)) { - i = skipPadding(text, i); - } - parsePosition.setIndex(i); - result.setDouble(uprv_getNaN()); - return; - } - - // NaN parse failed; start over - i = backup; - parsePosition.setIndex(i); - - // status is used to record whether a number is infinite. - UBool status[fgStatusLength]; - - DigitList *digits = result.getInternalDigitList(); // get one from the stack buffer - if (digits == NULL) { - return; // no way to report error from here. - } - - if (fImpl->fMonetary) { - if (!parseForCurrency(text, parsePosition, *digits, - status, currency)) { - return; - } - } else { - if (!subparse(text, - &fImpl->fAffixes.fNegativePrefix.getOtherVariant().toString(), - &fImpl->fAffixes.fNegativeSuffix.getOtherVariant().toString(), - &fImpl->fAffixes.fPositivePrefix.getOtherVariant().toString(), - &fImpl->fAffixes.fPositiveSuffix.getOtherVariant().toString(), - FALSE, UCURR_SYMBOL_NAME, - parsePosition, *digits, status, currency)) { - debug("!subparse(...) - rewind"); - parsePosition.setIndex(startIdx); - return; - } - } - - // Handle infinity - if (status[fgStatusInfinite]) { - double inf = uprv_getInfinity(); - result.setDouble(digits->isPositive() ? inf : -inf); - // TODO: set the dl to infinity, and let it fall into the code below. - } - - else { - - if (!fImpl->fMultiplier.isZero()) { - UErrorCode ec = U_ZERO_ERROR; - digits->div(fImpl->fMultiplier, ec); - } - - if (fImpl->fScale != 0) { - DigitList ten; - ten.set((int32_t)10); - if (fImpl->fScale > 0) { - for (int32_t i = fImpl->fScale; i > 0; i--) { - UErrorCode ec = U_ZERO_ERROR; - digits->div(ten,ec); - } - } else { - for (int32_t i = fImpl->fScale; i < 0; i++) { - UErrorCode ec = U_ZERO_ERROR; - digits->mult(ten,ec); - } - } - } - - // Negative zero special case: - // if parsing integerOnly, change to +0, which goes into an int32 in a Formattable. - // if not parsing integerOnly, leave as -0, which a double can represent. - if (digits->isZero() && !digits->isPositive() && isParseIntegerOnly()) { - digits->setPositive(TRUE); - } - result.adoptDigitList(digits); - } -} - - - -UBool -DecimalFormat::parseForCurrency(const UnicodeString& text, - ParsePosition& parsePosition, - DigitList& digits, - UBool* status, - UChar* currency) const { - UnicodeString positivePrefix; - UnicodeString positiveSuffix; - UnicodeString negativePrefix; - UnicodeString negativeSuffix; - fImpl->fPositivePrefixPattern.toString(positivePrefix); - fImpl->fPositiveSuffixPattern.toString(positiveSuffix); - fImpl->fNegativePrefixPattern.toString(negativePrefix); - fImpl->fNegativeSuffixPattern.toString(negativeSuffix); - - int origPos = parsePosition.getIndex(); - int maxPosIndex = origPos; - int maxErrorPos = -1; - // First, parse against current pattern. - // Since current pattern could be set by applyPattern(), - // it could be an arbitrary pattern, and it may not be the one - // defined in current locale. - UBool tmpStatus[fgStatusLength]; - ParsePosition tmpPos(origPos); - DigitList tmpDigitList; - UBool found; - if (fStyle == UNUM_CURRENCY_PLURAL) { - found = subparse(text, - &negativePrefix, &negativeSuffix, - &positivePrefix, &positiveSuffix, - TRUE, UCURR_LONG_NAME, - tmpPos, tmpDigitList, tmpStatus, currency); - } else { - found = subparse(text, - &negativePrefix, &negativeSuffix, - &positivePrefix, &positiveSuffix, - TRUE, UCURR_SYMBOL_NAME, - tmpPos, tmpDigitList, tmpStatus, currency); - } - if (found) { - if (tmpPos.getIndex() > maxPosIndex) { - maxPosIndex = tmpPos.getIndex(); - for (int32_t i = 0; i < fgStatusLength; ++i) { - status[i] = tmpStatus[i]; - } - digits = tmpDigitList; - } - } else { - maxErrorPos = tmpPos.getErrorIndex(); - } - // Then, parse against affix patterns. - // Those are currency patterns and currency plural patterns. - int32_t pos = UHASH_FIRST; - const UHashElement* element = NULL; - while ( (element = fAffixPatternsForCurrency->nextElement(pos)) != NULL ) { - const UHashTok valueTok = element->value; - const AffixPatternsForCurrency* affixPtn = (AffixPatternsForCurrency*)valueTok.pointer; - UBool tmpStatus[fgStatusLength]; - ParsePosition tmpPos(origPos); - DigitList tmpDigitList; - -#ifdef FMT_DEBUG - debug("trying affix for currency.."); - affixPtn->dump(); -#endif - - UBool result = subparse(text, - &affixPtn->negPrefixPatternForCurrency, - &affixPtn->negSuffixPatternForCurrency, - &affixPtn->posPrefixPatternForCurrency, - &affixPtn->posSuffixPatternForCurrency, - TRUE, affixPtn->patternType, - tmpPos, tmpDigitList, tmpStatus, currency); - if (result) { - found = true; - if (tmpPos.getIndex() > maxPosIndex) { - maxPosIndex = tmpPos.getIndex(); - for (int32_t i = 0; i < fgStatusLength; ++i) { - status[i] = tmpStatus[i]; - } - digits = tmpDigitList; - } - } else { - maxErrorPos = (tmpPos.getErrorIndex() > maxErrorPos) ? - tmpPos.getErrorIndex() : maxErrorPos; - } - } - // Finally, parse against simple affix to find the match. - // For example, in TestMonster suite, - // if the to-be-parsed text is "-\u00A40,00". - // complexAffixCompare will not find match, - // since there is no ISO code matches "\u00A4", - // and the parse stops at "\u00A4". - // We will just use simple affix comparison (look for exact match) - // to pass it. - // - // TODO: We should parse against simple affix first when - // output currency is not requested. After the complex currency - // parsing implementation was introduced, the default currency - // instance parsing slowed down because of the new code flow. - // I filed #10312 - Yoshito - UBool tmpStatus_2[fgStatusLength]; - ParsePosition tmpPos_2(origPos); - DigitList tmpDigitList_2; - - // Disable complex currency parsing and try it again. - UBool result = subparse(text, - &fImpl->fAffixes.fNegativePrefix.getOtherVariant().toString(), - &fImpl->fAffixes.fNegativeSuffix.getOtherVariant().toString(), - &fImpl->fAffixes.fPositivePrefix.getOtherVariant().toString(), - &fImpl->fAffixes.fPositiveSuffix.getOtherVariant().toString(), - FALSE /* disable complex currency parsing */, UCURR_SYMBOL_NAME, - tmpPos_2, tmpDigitList_2, tmpStatus_2, - currency); - if (result) { - if (tmpPos_2.getIndex() > maxPosIndex) { - maxPosIndex = tmpPos_2.getIndex(); - for (int32_t i = 0; i < fgStatusLength; ++i) { - status[i] = tmpStatus_2[i]; - } - digits = tmpDigitList_2; - } - found = true; - } else { - maxErrorPos = (tmpPos_2.getErrorIndex() > maxErrorPos) ? - tmpPos_2.getErrorIndex() : maxErrorPos; - } - - if (!found) { - //parsePosition.setIndex(origPos); - parsePosition.setErrorIndex(maxErrorPos); - } else { - parsePosition.setIndex(maxPosIndex); - parsePosition.setErrorIndex(-1); - } - return found; -} - - -/** - * Parse the given text into a number. The text is parsed beginning at - * parsePosition, until an unparseable character is seen. - * @param text the string to parse. - * @param negPrefix negative prefix. - * @param negSuffix negative suffix. - * @param posPrefix positive prefix. - * @param posSuffix positive suffix. - * @param complexCurrencyParsing whether it is complex currency parsing or not. - * @param type the currency type to parse against, LONG_NAME only or not. - * @param parsePosition The position at which to being parsing. Upon - * return, the first unparsed character. - * @param digits the DigitList to set to the parsed value. - * @param status output param containing boolean status flags indicating - * whether the value was infinite and whether it was positive. - * @param currency return value for parsed currency, for generic - * currency parsing mode, or NULL for normal parsing. In generic - * currency parsing mode, any currency is parsed, not just the - * currency that this formatter is set to. - */ -UBool DecimalFormat::subparse(const UnicodeString& text, - const UnicodeString* negPrefix, - const UnicodeString* negSuffix, - const UnicodeString* posPrefix, - const UnicodeString* posSuffix, - UBool complexCurrencyParsing, - int8_t type, - ParsePosition& parsePosition, - DigitList& digits, UBool* status, - UChar* currency) const -{ - // The parsing process builds up the number as char string, in the neutral format that - // will be acceptable to the decNumber library, then at the end passes that string - // off for conversion to a decNumber. - UErrorCode err = U_ZERO_ERROR; - CharString parsedNum; - digits.setToZero(); - - int32_t position = parsePosition.getIndex(); - int32_t oldStart = position; - int32_t textLength = text.length(); // One less pointer to follow - UBool strictParse = !isLenient(); - UChar32 zero = fImpl->getConstSymbol(DecimalFormatSymbols::kZeroDigitSymbol).char32At(0); - const UnicodeString *groupingString = &fImpl->getConstSymbol( - !fImpl->fMonetary ? - DecimalFormatSymbols::kGroupingSeparatorSymbol : DecimalFormatSymbols::kMonetaryGroupingSeparatorSymbol); - UChar32 groupingChar = groupingString->char32At(0); - int32_t groupingStringLength = groupingString->length(); - int32_t groupingCharLength = U16_LENGTH(groupingChar); - UBool groupingUsed = isGroupingUsed(); -#ifdef FMT_DEBUG - UChar dbgbuf[300]; - UnicodeString s(dbgbuf,0,300);; - s.append((UnicodeString)"PARSE \"").append(text.tempSubString(position)).append((UnicodeString)"\" " ); -#define DBGAPPD(x) if(x) { s.append(UnicodeString(#x "=")); if(x->isEmpty()) { s.append(UnicodeString("")); } else { s.append(*x); } s.append(UnicodeString(" ")); } else { s.append(UnicodeString(#x "=NULL ")); } - DBGAPPD(negPrefix); - DBGAPPD(negSuffix); - DBGAPPD(posPrefix); - DBGAPPD(posSuffix); - debugout(s); -#endif - - UBool fastParseOk = false; /* TRUE iff fast parse is OK */ - // UBool fastParseHadDecimal = FALSE; /* true if fast parse saw a decimal point. */ - if((fImpl->isParseFastpath()) && !fImpl->fMonetary && - text.length()>0 && - text.length()<32 && - (posPrefix==NULL||posPrefix->isEmpty()) && - (posSuffix==NULL||posSuffix->isEmpty()) && - // (negPrefix==NULL||negPrefix->isEmpty()) && - // (negSuffix==NULL||(negSuffix->isEmpty()) ) && - TRUE) { // optimized path - int j=position; - int l=text.length(); - int digitCount=0; - UChar32 ch = text.char32At(j); - const UnicodeString *decimalString = &fImpl->getConstSymbol(DecimalFormatSymbols::kDecimalSeparatorSymbol); - UChar32 decimalChar = 0; - UBool intOnly = FALSE; - UChar32 lookForGroup = (groupingUsed&&intOnly&&strictParse)?groupingChar:0; - - int32_t decimalCount = decimalString->countChar32(0,3); - if(isParseIntegerOnly()) { - decimalChar = 0; // not allowed - intOnly = TRUE; // Don't look for decimals. - } else if(decimalCount==1) { - decimalChar = decimalString->char32At(0); // Look for this decimal - } else if(decimalCount==0) { - decimalChar=0; // NO decimal set - } else { - j=l+1;//Set counter to end of line, so that we break. Unknown decimal situation. - } - -#ifdef FMT_DEBUG - printf("Preparing to do fastpath parse: decimalChar=U+%04X, groupingChar=U+%04X, first ch=U+%04X intOnly=%c strictParse=%c\n", - decimalChar, groupingChar, ch, - (intOnly)?'y':'n', - (strictParse)?'y':'n'); -#endif - if(ch==0x002D) { // '-' - j=l+1;//=break - negative number. - - /* - parsedNum.append('-',err); - j+=U16_LENGTH(ch); - if(j=0 && digit <= 9) { - parsedNum.append((char)(digit + '0'), err); - if((digitCount>0) || digit!=0 || j==(l-1)) { - digitCount++; - } - } else if(ch == 0) { // break out - digitCount=-1; - break; - } else if(ch == decimalChar) { - parsedNum.append((char)('.'), err); - decimalChar=0; // no more decimals. - // fastParseHadDecimal=TRUE; - } else if(ch == lookForGroup) { - // ignore grouping char. No decimals, so it has to be an ignorable grouping sep - } else if(intOnly && (lookForGroup!=0) && !u_isdigit(ch)) { - // parsing integer only and can fall through - } else { - digitCount=-1; // fail - fall through to slow parse - break; - } - j+=U16_LENGTH(ch); - ch = text.char32At(j); // for next - } - if( - ((j==l)||intOnly) // end OR only parsing integer - && (digitCount>0)) { // and have at least one digit - fastParseOk=true; // Fast parse OK! - -#ifdef SKIP_OPT - debug("SKIP_OPT"); - /* for testing, try it the slow way. also */ - fastParseOk=false; - parsedNum.clear(); -#else - parsePosition.setIndex(position=j); - status[fgStatusInfinite]=false; -#endif - } else { - // was not OK. reset, retry -#ifdef FMT_DEBUG - printf("Fall through: j=%d, l=%d, digitCount=%d\n", j, l, digitCount); -#endif - parsedNum.clear(); - } - } else { -#ifdef FMT_DEBUG - printf("Could not fastpath parse. "); - printf("text.length()=%d ", text.length()); - printf("posPrefix=%p posSuffix=%p ", posPrefix, posSuffix); - - printf("\n"); -#endif - } - - UnicodeString formatPattern; - toPattern(formatPattern); - - if(!fastParseOk -#if UCONFIG_HAVE_PARSEALLINPUT - && fParseAllInput!=UNUM_YES -#endif - ) - { - int32_t formatWidth = fImpl->getOldFormatWidth(); - // Match padding before prefix - if (formatWidth > 0 && fImpl->fAffixes.fPadPosition == DigitAffixesAndPadding::kPadBeforePrefix) { - position = skipPadding(text, position); - } - - // Match positive and negative prefixes; prefer longest match. - int32_t posMatch = compareAffix(text, position, FALSE, TRUE, posPrefix, complexCurrencyParsing, type, currency); - int32_t negMatch = compareAffix(text, position, TRUE, TRUE, negPrefix, complexCurrencyParsing, type, currency); - if (posMatch >= 0 && negMatch >= 0) { - if (posMatch > negMatch) { - negMatch = -1; - } else if (negMatch > posMatch) { - posMatch = -1; - } - } - if (posMatch >= 0) { - position += posMatch; - parsedNum.append('+', err); - } else if (negMatch >= 0) { - position += negMatch; - parsedNum.append('-', err); - } else if (strictParse){ - parsePosition.setErrorIndex(position); - return FALSE; - } else { - // Temporary set positive. This might be changed after checking suffix - parsedNum.append('+', err); - } - - // Match padding before prefix - if (formatWidth > 0 && fImpl->fAffixes.fPadPosition == DigitAffixesAndPadding::kPadAfterPrefix) { - position = skipPadding(text, position); - } - - if (! strictParse) { - position = skipUWhiteSpace(text, position); - } - - // process digits or Inf, find decimal position - const UnicodeString *inf = &fImpl->getConstSymbol(DecimalFormatSymbols::kInfinitySymbol); - int32_t infLen = (text.compare(position, inf->length(), *inf) - ? 0 : inf->length()); - position += infLen; // infLen is non-zero when it does equal to infinity - status[fgStatusInfinite] = infLen != 0; - - if (infLen != 0) { - parsedNum.append("Infinity", err); - } else { - // We now have a string of digits, possibly with grouping symbols, - // and decimal points. We want to process these into a DigitList. - // We don't want to put a bunch of leading zeros into the DigitList - // though, so we keep track of the location of the decimal point, - // put only significant digits into the DigitList, and adjust the - // exponent as needed. - - - UBool strictFail = FALSE; // did we exit with a strict parse failure? - int32_t lastGroup = -1; // after which digit index did we last see a grouping separator? - int32_t currGroup = -1; // for temporary storage the digit index of the current grouping separator - int32_t gs2 = fImpl->fEffGrouping.fGrouping2 == 0 ? fImpl->fEffGrouping.fGrouping : fImpl->fEffGrouping.fGrouping2; - - const UnicodeString *decimalString; - if (fImpl->fMonetary) { - decimalString = &fImpl->getConstSymbol(DecimalFormatSymbols::kMonetarySeparatorSymbol); - } else { - decimalString = &fImpl->getConstSymbol(DecimalFormatSymbols::kDecimalSeparatorSymbol); - } - UChar32 decimalChar = decimalString->char32At(0); - int32_t decimalStringLength = decimalString->length(); - int32_t decimalCharLength = U16_LENGTH(decimalChar); - - UBool sawDecimal = FALSE; - UChar32 sawDecimalChar = 0xFFFF; - UBool sawGrouping = FALSE; - UChar32 sawGroupingChar = 0xFFFF; - UBool sawDigit = FALSE; - int32_t backup = -1; - int32_t digit; - - // equivalent grouping and decimal support - const UnicodeSet *decimalSet = NULL; - const UnicodeSet *groupingSet = NULL; - - if (decimalCharLength == decimalStringLength) { - decimalSet = DecimalFormatStaticSets::getSimilarDecimals(decimalChar, strictParse); - } - - if (groupingCharLength == groupingStringLength) { - if (strictParse) { - groupingSet = fStaticSets->fStrictDefaultGroupingSeparators; - } else { - groupingSet = fStaticSets->fDefaultGroupingSeparators; - } - } - - // We need to test groupingChar and decimalChar separately from groupingSet and decimalSet, if the sets are even initialized. - // If sawDecimal is TRUE, only consider sawDecimalChar and NOT decimalSet - // If a character matches decimalSet, don't consider it to be a member of the groupingSet. - - // We have to track digitCount ourselves, because digits.fCount will - // pin when the maximum allowable digits is reached. - int32_t digitCount = 0; - int32_t integerDigitCount = 0; - - for (; position < textLength; ) - { - UChar32 ch = text.char32At(position); - - /* We recognize all digit ranges, not only the Latin digit range - * '0'..'9'. We do so by using the Character.digit() method, - * which converts a valid Unicode digit to the range 0..9. - * - * The character 'ch' may be a digit. If so, place its value - * from 0 to 9 in 'digit'. First try using the locale digit, - * which may or MAY NOT be a standard Unicode digit range. If - * this fails, try using the standard Unicode digit ranges by - * calling Character.digit(). If this also fails, digit will - * have a value outside the range 0..9. - */ - digit = ch - zero; - if (digit < 0 || digit > 9) - { - digit = u_charDigitValue(ch); - } - - // As a last resort, look through the localized digits if the zero digit - // is not a "standard" Unicode digit. - if ( (digit < 0 || digit > 9) && u_charDigitValue(zero) != 0) { - digit = 0; - if ( fImpl->getConstSymbol((DecimalFormatSymbols::ENumberFormatSymbol)(DecimalFormatSymbols::kZeroDigitSymbol)).char32At(0) == ch ) { - break; - } - for (digit = 1 ; digit < 10 ; digit++ ) { - if ( fImpl->getConstSymbol((DecimalFormatSymbols::ENumberFormatSymbol)(DecimalFormatSymbols::kOneDigitSymbol+digit-1)).char32At(0) == ch ) { - break; - } - } - } - - if (digit >= 0 && digit <= 9) - { - if (strictParse && backup != -1) { - // comma followed by digit, so group before comma is a - // secondary group. If there was a group separator - // before that, the group must == the secondary group - // length, else it can be <= the the secondary group - // length. - if ((lastGroup != -1 && currGroup - lastGroup != gs2) || - (lastGroup == -1 && digitCount - 1 > gs2)) { - strictFail = TRUE; - break; - } - - lastGroup = currGroup; - } - - // Cancel out backup setting (see grouping handler below) - currGroup = -1; - backup = -1; - sawDigit = TRUE; - - // Note: this will append leading zeros - parsedNum.append((char)(digit + '0'), err); - - // count any digit that's not a leading zero - if (digit > 0 || digitCount > 0 || sawDecimal) { - digitCount += 1; - - // count any integer digit that's not a leading zero - if (! sawDecimal) { - integerDigitCount += 1; - } - } - - position += U16_LENGTH(ch); - } - else if (groupingStringLength > 0 && - matchGrouping(groupingChar, sawGrouping, sawGroupingChar, groupingSet, - decimalChar, decimalSet, - ch) && groupingUsed) - { - if (sawDecimal) { - break; - } - - if (strictParse) { - if ((!sawDigit || backup != -1)) { - // leading group, or two group separators in a row - strictFail = TRUE; - break; - } - } - - // Ignore grouping characters, if we are using them, but require - // that they be followed by a digit. Otherwise we backup and - // reprocess them. - currGroup = digitCount; - backup = position; - position += groupingStringLength; - sawGrouping=TRUE; - // Once we see a grouping character, we only accept that grouping character from then on. - sawGroupingChar=ch; - } - else if (matchDecimal(decimalChar,sawDecimal,sawDecimalChar, decimalSet, ch)) - { - if (strictParse) { - if (backup != -1 || - (lastGroup != -1 && digitCount - lastGroup != fImpl->fEffGrouping.fGrouping)) { - strictFail = TRUE; - break; - } - } - - // If we're only parsing integers, or if we ALREADY saw the - // decimal, then don't parse this one. - if (isParseIntegerOnly() || sawDecimal) { - break; - } - - parsedNum.append('.', err); - position += decimalStringLength; - sawDecimal = TRUE; - // Once we see a decimal character, we only accept that decimal character from then on. - sawDecimalChar=ch; - // decimalSet is considered to consist of (ch,ch) - } - else { - - if(!fBoolFlags.contains(UNUM_PARSE_NO_EXPONENT) || // don't parse if this is set unless.. - isScientificNotation()) { // .. it's an exponent format - ignore setting and parse anyways - const UnicodeString *tmp; - tmp = &fImpl->getConstSymbol(DecimalFormatSymbols::kExponentialSymbol); - // TODO: CASE - if (!text.caseCompare(position, tmp->length(), *tmp, U_FOLD_CASE_DEFAULT)) // error code is set below if !sawDigit - { - // Parse sign, if present - int32_t pos = position + tmp->length(); - char exponentSign = '+'; - - if (pos < textLength) - { - tmp = &fImpl->getConstSymbol(DecimalFormatSymbols::kPlusSignSymbol); - if (!text.compare(pos, tmp->length(), *tmp)) - { - pos += tmp->length(); - } - else { - tmp = &fImpl->getConstSymbol(DecimalFormatSymbols::kMinusSignSymbol); - if (!text.compare(pos, tmp->length(), *tmp)) - { - exponentSign = '-'; - pos += tmp->length(); - } - } - } - - UBool sawExponentDigit = FALSE; - while (pos < textLength) { - ch = text.char32At(pos); - digit = ch - zero; - - if (digit < 0 || digit > 9) { - digit = u_charDigitValue(ch); - } - if (0 <= digit && digit <= 9) { - if (!sawExponentDigit) { - parsedNum.append('E', err); - parsedNum.append(exponentSign, err); - sawExponentDigit = TRUE; - } - pos += U16_LENGTH(ch); - parsedNum.append((char)(digit + '0'), err); - } else { - break; - } - } - - if (sawExponentDigit) { - position = pos; // Advance past the exponent - } - - break; // Whether we fail or succeed, we exit this loop - } else { - break; - } - } else { // not parsing exponent - break; - } - } - } - - // if we didn't see a decimal and it is required, check to see if the pattern had one - if(!sawDecimal && isDecimalPatternMatchRequired()) - { - if(formatPattern.indexOf(kPatternDecimalSeparator) != -1) - { - parsePosition.setIndex(oldStart); - parsePosition.setErrorIndex(position); - debug("decimal point match required fail!"); - return FALSE; - } - } - - if (backup != -1) - { - position = backup; - } - - if (strictParse && !sawDecimal) { - if (lastGroup != -1 && digitCount - lastGroup != fImpl->fEffGrouping.fGrouping) { - strictFail = TRUE; - } - } - - if (strictFail) { - // only set with strictParse and a grouping separator error - - parsePosition.setIndex(oldStart); - parsePosition.setErrorIndex(position); - debug("strictFail!"); - return FALSE; - } - - // If there was no decimal point we have an integer - - // If none of the text string was recognized. For example, parse - // "x" with pattern "#0.00" (return index and error index both 0) - // parse "$" with pattern "$#0.00". (return index 0 and error index - // 1). - if (!sawDigit && digitCount == 0) { -#ifdef FMT_DEBUG - debug("none of text rec"); - printf("position=%d\n",position); -#endif - parsePosition.setIndex(oldStart); - parsePosition.setErrorIndex(oldStart); - return FALSE; - } - } - - // Match padding before suffix - if (formatWidth > 0 && fImpl->fAffixes.fPadPosition == DigitAffixesAndPadding::kPadBeforeSuffix) { - position = skipPadding(text, position); - } - - int32_t posSuffixMatch = -1, negSuffixMatch = -1; - - // Match positive and negative suffixes; prefer longest match. - if (posMatch >= 0 || (!strictParse && negMatch < 0)) { - posSuffixMatch = compareAffix(text, position, FALSE, FALSE, posSuffix, complexCurrencyParsing, type, currency); - } - if (negMatch >= 0) { - negSuffixMatch = compareAffix(text, position, TRUE, FALSE, negSuffix, complexCurrencyParsing, type, currency); - } - if (posSuffixMatch >= 0 && negSuffixMatch >= 0) { - if (posSuffixMatch > negSuffixMatch) { - negSuffixMatch = -1; - } else if (negSuffixMatch > posSuffixMatch) { - posSuffixMatch = -1; - } - } - - // Fail if neither or both - if (strictParse && ((posSuffixMatch >= 0) == (negSuffixMatch >= 0))) { - parsePosition.setErrorIndex(position); - debug("neither or both"); - return FALSE; - } - - position += (posSuffixMatch >= 0 ? posSuffixMatch : (negSuffixMatch >= 0 ? negSuffixMatch : 0)); - - // Match padding before suffix - if (formatWidth > 0 && fImpl->fAffixes.fPadPosition == DigitAffixesAndPadding::kPadAfterSuffix) { - position = skipPadding(text, position); - } - - parsePosition.setIndex(position); - - parsedNum.data()[0] = (posSuffixMatch >= 0 || (!strictParse && negMatch < 0 && negSuffixMatch < 0)) ? '+' : '-'; -#ifdef FMT_DEBUG -printf("PP -> %d, SLOW = [%s]! pp=%d, os=%d, err=%s\n", position, parsedNum.data(), parsePosition.getIndex(),oldStart,u_errorName(err)); -#endif - } /* end SLOW parse */ - if(parsePosition.getIndex() == oldStart) - { -#ifdef FMT_DEBUG - printf(" PP didnt move, err\n"); -#endif - parsePosition.setErrorIndex(position); - return FALSE; - } -#if UCONFIG_HAVE_PARSEALLINPUT - else if (fParseAllInput==UNUM_YES&&parsePosition.getIndex()!=textLength) - { -#ifdef FMT_DEBUG - printf(" PP didnt consume all (UNUM_YES), err\n"); -#endif - parsePosition.setErrorIndex(position); - return FALSE; - } -#endif - // uint32_t bits = (fastParseOk?kFastpathOk:0) | - // (fastParseHadDecimal?0:kNoDecimal); - //printf("FPOK=%d, FPHD=%d, bits=%08X\n", fastParseOk, fastParseHadDecimal, bits); - digits.set(parsedNum.toStringPiece(), - err, - 0//bits - ); - - if (U_FAILURE(err)) { -#ifdef FMT_DEBUG - printf(" err setting %s\n", u_errorName(err)); -#endif - parsePosition.setErrorIndex(position); - return FALSE; - } - - // check if we missed a required decimal point - if(fastParseOk && isDecimalPatternMatchRequired()) - { - if(formatPattern.indexOf(kPatternDecimalSeparator) != -1) - { - parsePosition.setIndex(oldStart); - parsePosition.setErrorIndex(position); - debug("decimal point match required fail!"); - return FALSE; - } - } - - - return TRUE; -} - -/** - * Starting at position, advance past a run of pad characters, if any. - * Return the index of the first character after position that is not a pad - * character. Result is >= position. - */ -int32_t DecimalFormat::skipPadding(const UnicodeString& text, int32_t position) const { - int32_t padLen = U16_LENGTH(fImpl->fAffixes.fPadChar); - while (position < text.length() && - text.char32At(position) == fImpl->fAffixes.fPadChar) { - position += padLen; - } - return position; -} - -/** - * Return the length matched by the given affix, or -1 if none. - * Runs of white space in the affix, match runs of white space in - * the input. Pattern white space and input white space are - * determined differently; see code. - * @param text input text - * @param pos offset into input at which to begin matching - * @param isNegative - * @param isPrefix - * @param affixPat affix pattern used for currency affix comparison. - * @param complexCurrencyParsing whether it is currency parsing or not - * @param type the currency type to parse against, LONG_NAME only or not. - * @param currency return value for parsed currency, for generic - * currency parsing mode, or null for normal parsing. In generic - * currency parsing mode, any currency is parsed, not just the - * currency that this formatter is set to. - * @return length of input that matches, or -1 if match failure - */ -int32_t DecimalFormat::compareAffix(const UnicodeString& text, - int32_t pos, - UBool isNegative, - UBool isPrefix, - const UnicodeString* affixPat, - UBool complexCurrencyParsing, - int8_t type, - UChar* currency) const -{ - const UnicodeString *patternToCompare; - if (currency != NULL || - (fImpl->fMonetary && complexCurrencyParsing)) { - - if (affixPat != NULL) { - return compareComplexAffix(*affixPat, text, pos, type, currency); - } - } - - if (isNegative) { - if (isPrefix) { - patternToCompare = &fImpl->fAffixes.fNegativePrefix.getOtherVariant().toString(); - } - else { - patternToCompare = &fImpl->fAffixes.fNegativeSuffix.getOtherVariant().toString(); - } - } - else { - if (isPrefix) { - patternToCompare = &fImpl->fAffixes.fPositivePrefix.getOtherVariant().toString(); - } - else { - patternToCompare = &fImpl->fAffixes.fPositiveSuffix.getOtherVariant().toString(); - } - } - return compareSimpleAffix(*patternToCompare, text, pos, isLenient()); -} - -UBool DecimalFormat::equalWithSignCompatibility(UChar32 lhs, UChar32 rhs) const { - if (lhs == rhs) { - return TRUE; - } - U_ASSERT(fStaticSets != NULL); // should already be loaded - const UnicodeSet *minusSigns = fStaticSets->fMinusSigns; - const UnicodeSet *plusSigns = fStaticSets->fPlusSigns; - return (minusSigns->contains(lhs) && minusSigns->contains(rhs)) || - (plusSigns->contains(lhs) && plusSigns->contains(rhs)); -} - -// check for LRM 0x200E, RLM 0x200F, ALM 0x061C -#define IS_BIDI_MARK(c) (c==0x200E || c==0x200F || c==0x061C) - -#define TRIM_BUFLEN 32 -UnicodeString& DecimalFormat::trimMarksFromAffix(const UnicodeString& affix, UnicodeString& trimmedAffix) { - UChar trimBuf[TRIM_BUFLEN]; - int32_t affixLen = affix.length(); - int32_t affixPos, trimLen = 0; - - for (affixPos = 0; affixPos < affixLen; affixPos++) { - UChar c = affix.charAt(affixPos); - if (!IS_BIDI_MARK(c)) { - if (trimLen < TRIM_BUFLEN) { - trimBuf[trimLen++] = c; - } else { - trimLen = 0; - break; - } - } - } - return (trimLen > 0)? trimmedAffix.setTo(trimBuf, trimLen): trimmedAffix.setTo(affix); -} - -/** - * Return the length matched by the given affix, or -1 if none. - * Runs of white space in the affix, match runs of white space in - * the input. Pattern white space and input white space are - * determined differently; see code. - * @param affix pattern string, taken as a literal - * @param input input text - * @param pos offset into input at which to begin matching - * @return length of input that matches, or -1 if match failure - */ -int32_t DecimalFormat::compareSimpleAffix(const UnicodeString& affix, - const UnicodeString& input, - int32_t pos, - UBool lenient) const { - int32_t start = pos; - UnicodeString trimmedAffix; - // For more efficiency we should keep lazily-created trimmed affixes around in - // instance variables instead of trimming each time they are used (the next step) - trimMarksFromAffix(affix, trimmedAffix); - UChar32 affixChar = trimmedAffix.char32At(0); - int32_t affixLength = trimmedAffix.length(); - int32_t inputLength = input.length(); - int32_t affixCharLength = U16_LENGTH(affixChar); - UnicodeSet *affixSet; - UErrorCode status = U_ZERO_ERROR; - - U_ASSERT(fStaticSets != NULL); // should already be loaded - - if (U_FAILURE(status)) { - return -1; - } - if (!lenient) { - affixSet = fStaticSets->fStrictDashEquivalents; - - // If the trimmedAffix is exactly one character long and that character - // is in the dash set and the very next input character is also - // in the dash set, return a match. - if (affixCharLength == affixLength && affixSet->contains(affixChar)) { - UChar32 ic = input.char32At(pos); - if (affixSet->contains(ic)) { - pos += U16_LENGTH(ic); - pos = skipBidiMarks(input, pos); // skip any trailing bidi marks - return pos - start; - } - } - - for (int32_t i = 0; i < affixLength; ) { - UChar32 c = trimmedAffix.char32At(i); - int32_t len = U16_LENGTH(c); - if (PatternProps::isWhiteSpace(c)) { - // We may have a pattern like: \u200F \u0020 - // and input text like: \u200F \u0020 - // Note that U+200F and U+0020 are Pattern_White_Space but only - // U+0020 is UWhiteSpace. So we have to first do a direct - // match of the run of Pattern_White_Space in the pattern, - // then match any extra characters. - UBool literalMatch = FALSE; - while (pos < inputLength) { - UChar32 ic = input.char32At(pos); - if (ic == c) { - literalMatch = TRUE; - i += len; - pos += len; - if (i == affixLength) { - break; - } - c = trimmedAffix.char32At(i); - len = U16_LENGTH(c); - if (!PatternProps::isWhiteSpace(c)) { - break; - } - } else if (IS_BIDI_MARK(ic)) { - pos ++; // just skip over this input text - } else { - break; - } - } - - // Advance over run in pattern - i = skipPatternWhiteSpace(trimmedAffix, i); - - // Advance over run in input text - // Must see at least one white space char in input, - // unless we've already matched some characters literally. - int32_t s = pos; - pos = skipUWhiteSpace(input, pos); - if (pos == s && !literalMatch) { - return -1; - } - - // If we skip UWhiteSpace in the input text, we need to skip it in the pattern. - // Otherwise, the previous lines may have skipped over text (such as U+00A0) that - // is also in the trimmedAffix. - i = skipUWhiteSpace(trimmedAffix, i); - } else { - UBool match = FALSE; - while (pos < inputLength) { - UChar32 ic = input.char32At(pos); - if (!match && ic == c) { - i += len; - pos += len; - match = TRUE; - } else if (IS_BIDI_MARK(ic)) { - pos++; // just skip over this input text - } else { - break; - } - } - if (!match) { - return -1; - } - } - } - } else { - UBool match = FALSE; - - affixSet = fStaticSets->fDashEquivalents; - - if (affixCharLength == affixLength && affixSet->contains(affixChar)) { - pos = skipUWhiteSpaceAndMarks(input, pos); - UChar32 ic = input.char32At(pos); - - if (affixSet->contains(ic)) { - pos += U16_LENGTH(ic); - pos = skipBidiMarks(input, pos); - return pos - start; - } - } - - for (int32_t i = 0; i < affixLength; ) - { - //i = skipRuleWhiteSpace(trimmedAffix, i); - i = skipUWhiteSpace(trimmedAffix, i); - pos = skipUWhiteSpaceAndMarks(input, pos); - - if (i >= affixLength || pos >= inputLength) { - break; - } - - UChar32 c = trimmedAffix.char32At(i); - UChar32 ic = input.char32At(pos); - - if (!equalWithSignCompatibility(ic, c)) { - return -1; - } - - match = TRUE; - i += U16_LENGTH(c); - pos += U16_LENGTH(ic); - pos = skipBidiMarks(input, pos); - } - - if (affixLength > 0 && ! match) { - return -1; - } - } - return pos - start; -} - -/** - * Skip over a run of zero or more Pattern_White_Space characters at - * pos in text. - */ -int32_t DecimalFormat::skipPatternWhiteSpace(const UnicodeString& text, int32_t pos) { - const UChar* s = text.getBuffer(); - return (int32_t)(PatternProps::skipWhiteSpace(s + pos, text.length() - pos) - s); -} - -/** - * Skip over a run of zero or more isUWhiteSpace() characters at pos - * in text. - */ -int32_t DecimalFormat::skipUWhiteSpace(const UnicodeString& text, int32_t pos) { - while (pos < text.length()) { - UChar32 c = text.char32At(pos); - if (!u_isUWhiteSpace(c)) { - break; - } - pos += U16_LENGTH(c); - } - return pos; -} - -/** - * Skip over a run of zero or more isUWhiteSpace() characters or bidi marks at pos - * in text. - */ -int32_t DecimalFormat::skipUWhiteSpaceAndMarks(const UnicodeString& text, int32_t pos) { - while (pos < text.length()) { - UChar32 c = text.char32At(pos); - if (!u_isUWhiteSpace(c) && !IS_BIDI_MARK(c)) { // u_isUWhiteSpace doesn't include LRM,RLM,ALM - break; - } - pos += U16_LENGTH(c); - } - return pos; -} - -/** - * Skip over a run of zero or more bidi marks at pos in text. - */ -int32_t DecimalFormat::skipBidiMarks(const UnicodeString& text, int32_t pos) { - while (pos < text.length()) { - UChar c = text.charAt(pos); - if (!IS_BIDI_MARK(c)) { - break; - } - pos++; - } - return pos; -} - -/** - * Return the length matched by the given affix, or -1 if none. - * @param affixPat pattern string - * @param input input text - * @param pos offset into input at which to begin matching - * @param type the currency type to parse against, LONG_NAME only or not. - * @param currency return value for parsed currency, for generic - * currency parsing mode, or null for normal parsing. In generic - * currency parsing mode, any currency is parsed, not just the - * currency that this formatter is set to. - * @return length of input that matches, or -1 if match failure - */ -int32_t DecimalFormat::compareComplexAffix(const UnicodeString& affixPat, - const UnicodeString& text, - int32_t pos, - int8_t type, - UChar* currency) const -{ - int32_t start = pos; - U_ASSERT(currency != NULL || fImpl->fMonetary); - - for (int32_t i=0; - i= 0; ) { - UChar32 c = affixPat.char32At(i); - i += U16_LENGTH(c); - - if (c == kQuote) { - U_ASSERT(i <= affixPat.length()); - c = affixPat.char32At(i); - i += U16_LENGTH(c); - - const UnicodeString* affix = NULL; - - switch (c) { - case kCurrencySign: { - // since the currency names in choice format is saved - // the same way as other currency names, - // do not need to do currency choice parsing here. - // the general currency parsing parse against all names, - // including names in choice format. - UBool intl = igetLocale().getName(); - ParsePosition ppos(pos); - int32_t currMatchLen = 0; - UChar curr[4]; - UErrorCode ec = U_ZERO_ERROR; - // Delegate parse of display name => ISO code to Currency - uprv_parseCurrency(loc, text, ppos, type, &currMatchLen, curr, ec); - - // If parse succeeds, populate currency[0] - if (U_SUCCESS(ec) && ppos.getIndex() != pos) { - if (currency) { - u_strcpy(currency, curr); - } else { - // The formatter is currency-style but the client has not requested - // the value of the parsed currency. In this case, if that value does - // not match the formatter's current value, then the parse fails. - UChar effectiveCurr[4]; - getEffectiveCurrency(effectiveCurr, ec); - if ( U_FAILURE(ec) || u_strncmp(curr,effectiveCurr,4) != 0 ) { - pos = -1; - continue; - } - } - pos = ppos.getIndex(); - } else if (!isLenient()){ - pos = -1; - } - continue; - } - case kPatternPercent: - affix = &fImpl->getConstSymbol(DecimalFormatSymbols::kPercentSymbol); - break; - case kPatternPerMill: - affix = &fImpl->getConstSymbol(DecimalFormatSymbols::kPerMillSymbol); - break; - case kPatternPlus: - affix = &fImpl->getConstSymbol(DecimalFormatSymbols::kPlusSignSymbol); - break; - case kPatternMinus: - affix = &fImpl->getConstSymbol(DecimalFormatSymbols::kMinusSignSymbol); - break; - default: - // fall through to affix!=0 test, which will fail - break; - } - - if (affix != NULL) { - pos = match(text, pos, *affix); - continue; - } - } - - pos = match(text, pos, c); - if (PatternProps::isWhiteSpace(c)) { - i = skipPatternWhiteSpace(affixPat, i); - } - } - return pos - start; -} - -/** - * Match a single character at text[pos] and return the index of the - * next character upon success. Return -1 on failure. If - * ch is a Pattern_White_Space then match a run of white space in text. - */ -int32_t DecimalFormat::match(const UnicodeString& text, int32_t pos, UChar32 ch) { - if (PatternProps::isWhiteSpace(ch)) { - // Advance over run of white space in input text - // Must see at least one white space char in input - int32_t s = pos; - pos = skipPatternWhiteSpace(text, pos); - if (pos == s) { - return -1; - } - return pos; - } - return (pos >= 0 && text.char32At(pos) == ch) ? - (pos + U16_LENGTH(ch)) : -1; -} - -/** - * Match a string at text[pos] and return the index of the next - * character upon success. Return -1 on failure. Match a run of - * white space in str with a run of white space in text. - */ -int32_t DecimalFormat::match(const UnicodeString& text, int32_t pos, const UnicodeString& str) { - for (int32_t i=0; i= 0; ) { - UChar32 ch = str.char32At(i); - i += U16_LENGTH(ch); - if (PatternProps::isWhiteSpace(ch)) { - i = skipPatternWhiteSpace(str, i); - } - pos = match(text, pos, ch); - } - return pos; -} - -UBool DecimalFormat::matchSymbol(const UnicodeString &text, int32_t position, int32_t length, const UnicodeString &symbol, - UnicodeSet *sset, UChar32 schar) -{ - if (sset != NULL) { - return sset->contains(schar); - } - - return text.compare(position, length, symbol) == 0; -} - -UBool DecimalFormat::matchDecimal(UChar32 symbolChar, - UBool sawDecimal, UChar32 sawDecimalChar, - const UnicodeSet *sset, UChar32 schar) { - if(sawDecimal) { - return schar==sawDecimalChar; - } else if(schar==symbolChar) { - return TRUE; - } else if(sset!=NULL) { - return sset->contains(schar); - } else { - return FALSE; - } -} - -UBool DecimalFormat::matchGrouping(UChar32 groupingChar, - UBool sawGrouping, UChar32 sawGroupingChar, - const UnicodeSet *sset, - UChar32 /*decimalChar*/, const UnicodeSet *decimalSet, - UChar32 schar) { - if(sawGrouping) { - return schar==sawGroupingChar; // previously found - } else if(schar==groupingChar) { - return TRUE; // char from symbols - } else if(sset!=NULL) { - return sset->contains(schar) && // in groupingSet but... - ((decimalSet==NULL) || !decimalSet->contains(schar)); // Exclude decimalSet from groupingSet - } else { - return FALSE; - } -} - - - -//------------------------------------------------------------------------------ -// Gets the pointer to the localized decimal format symbols - -const DecimalFormatSymbols* -DecimalFormat::getDecimalFormatSymbols() const -{ - return &fImpl->getDecimalFormatSymbols(); -} - -//------------------------------------------------------------------------------ -// De-owning the current localized symbols and adopt the new symbols. - -void -DecimalFormat::adoptDecimalFormatSymbols(DecimalFormatSymbols* symbolsToAdopt) -{ - if (symbolsToAdopt == NULL) { - return; // do not allow caller to set fSymbols to NULL - } - fImpl->adoptDecimalFormatSymbols(symbolsToAdopt); -} -//------------------------------------------------------------------------------ -// Setting the symbols is equlivalent to adopting a newly created localized -// symbols. - -void -DecimalFormat::setDecimalFormatSymbols(const DecimalFormatSymbols& symbols) -{ - adoptDecimalFormatSymbols(new DecimalFormatSymbols(symbols)); -} - - -const CurrencyPluralInfo* -DecimalFormat::getCurrencyPluralInfo(void) const -{ - return fCurrencyPluralInfo; -} - - -void -DecimalFormat::adoptCurrencyPluralInfo(CurrencyPluralInfo* toAdopt) -{ - if (toAdopt != NULL) { - delete fCurrencyPluralInfo; - fCurrencyPluralInfo = toAdopt; - // re-set currency affix patterns and currency affixes. - if (fImpl->fMonetary) { - UErrorCode status = U_ZERO_ERROR; - if (fAffixPatternsForCurrency) { - deleteHashForAffixPattern(); - } - setupCurrencyAffixPatterns(status); - } - } -} - -void -DecimalFormat::setCurrencyPluralInfo(const CurrencyPluralInfo& info) -{ - adoptCurrencyPluralInfo(info.clone()); -} - - -//------------------------------------------------------------------------------ -// Gets the positive prefix of the number pattern. - -UnicodeString& -DecimalFormat::getPositivePrefix(UnicodeString& result) const -{ - return fImpl->getPositivePrefix(result); -} - -//------------------------------------------------------------------------------ -// Sets the positive prefix of the number pattern. - -void -DecimalFormat::setPositivePrefix(const UnicodeString& newValue) -{ - fImpl->setPositivePrefix(newValue); -} - -//------------------------------------------------------------------------------ -// Gets the negative prefix of the number pattern. - -UnicodeString& -DecimalFormat::getNegativePrefix(UnicodeString& result) const -{ - return fImpl->getNegativePrefix(result); -} - -//------------------------------------------------------------------------------ -// Gets the negative prefix of the number pattern. - -void -DecimalFormat::setNegativePrefix(const UnicodeString& newValue) -{ - fImpl->setNegativePrefix(newValue); -} - -//------------------------------------------------------------------------------ -// Gets the positive suffix of the number pattern. - -UnicodeString& -DecimalFormat::getPositiveSuffix(UnicodeString& result) const -{ - return fImpl->getPositiveSuffix(result); -} - -//------------------------------------------------------------------------------ -// Sets the positive suffix of the number pattern. - -void -DecimalFormat::setPositiveSuffix(const UnicodeString& newValue) -{ - fImpl->setPositiveSuffix(newValue); -} - -//------------------------------------------------------------------------------ -// Gets the negative suffix of the number pattern. - -UnicodeString& -DecimalFormat::getNegativeSuffix(UnicodeString& result) const -{ - return fImpl->getNegativeSuffix(result); -} - -//------------------------------------------------------------------------------ -// Sets the negative suffix of the number pattern. - -void -DecimalFormat::setNegativeSuffix(const UnicodeString& newValue) -{ - fImpl->setNegativeSuffix(newValue); -} - -//------------------------------------------------------------------------------ -// Gets the multiplier of the number pattern. -// Multipliers are stored as decimal numbers (DigitLists) because that -// is the most convenient for muliplying or dividing the numbers to be formatted. -// A NULL multiplier implies one, and the scaling operations are skipped. - -int32_t -DecimalFormat::getMultiplier() const -{ - return fImpl->getMultiplier(); -} - -//------------------------------------------------------------------------------ -// Sets the multiplier of the number pattern. -void -DecimalFormat::setMultiplier(int32_t newValue) -{ - fImpl->setMultiplier(newValue); -} - -/** - * Get the rounding increment. - * @return A positive rounding increment, or 0.0 if rounding - * is not in effect. - * @see #setRoundingIncrement - * @see #getRoundingMode - * @see #setRoundingMode - */ -double DecimalFormat::getRoundingIncrement() const { - return fImpl->getRoundingIncrement(); -} - -/** - * Set the rounding increment. This method also controls whether - * rounding is enabled. - * @param newValue A positive rounding increment, or 0.0 to disable rounding. - * Negative increments are equivalent to 0.0. - * @see #getRoundingIncrement - * @see #getRoundingMode - * @see #setRoundingMode - */ -void DecimalFormat::setRoundingIncrement(double newValue) { - fImpl->setRoundingIncrement(newValue); -} - -/** - * Get the rounding mode. - * @return A rounding mode - * @see #setRoundingIncrement - * @see #getRoundingIncrement - * @see #setRoundingMode - */ -DecimalFormat::ERoundingMode DecimalFormat::getRoundingMode() const { - return fImpl->getRoundingMode(); -} - -/** - * Set the rounding mode. This has no effect unless the rounding - * increment is greater than zero. - * @param roundingMode A rounding mode - * @see #setRoundingIncrement - * @see #getRoundingIncrement - * @see #getRoundingMode - */ -void DecimalFormat::setRoundingMode(ERoundingMode roundingMode) { - fImpl->setRoundingMode(roundingMode); -} - -/** - * Get the width to which the output of format() is padded. - * @return the format width, or zero if no padding is in effect - * @see #setFormatWidth - * @see #getPadCharacter - * @see #setPadCharacter - * @see #getPadPosition - * @see #setPadPosition - */ -int32_t DecimalFormat::getFormatWidth() const { - return fImpl->getFormatWidth(); -} - -/** - * Set the width to which the output of format() is padded. - * This method also controls whether padding is enabled. - * @param width the width to which to pad the result of - * format(), or zero to disable padding. A negative - * width is equivalent to 0. - * @see #getFormatWidth - * @see #getPadCharacter - * @see #setPadCharacter - * @see #getPadPosition - * @see #setPadPosition - */ -void DecimalFormat::setFormatWidth(int32_t width) { - int32_t formatWidth = (width > 0) ? width : 0; - fImpl->setFormatWidth(formatWidth); -} - -UnicodeString DecimalFormat::getPadCharacterString() const { - return UnicodeString(fImpl->getPadCharacter()); -} - -void DecimalFormat::setPadCharacter(const UnicodeString &padChar) { - UChar32 pad; - if (padChar.length() > 0) { - pad = padChar.char32At(0); - } - else { - pad = kDefaultPad; - } - fImpl->setPadCharacter(pad); -} - -static DecimalFormat::EPadPosition fromPadPosition(DigitAffixesAndPadding::EPadPosition padPos) { - switch (padPos) { - case DigitAffixesAndPadding::kPadBeforePrefix: - return DecimalFormat::kPadBeforePrefix; - case DigitAffixesAndPadding::kPadAfterPrefix: - return DecimalFormat::kPadAfterPrefix; - case DigitAffixesAndPadding::kPadBeforeSuffix: - return DecimalFormat::kPadBeforeSuffix; - case DigitAffixesAndPadding::kPadAfterSuffix: - return DecimalFormat::kPadAfterSuffix; - default: - U_ASSERT(FALSE); - break; - } - return DecimalFormat::kPadBeforePrefix; -} - -/** - * Get the position at which padding will take place. This is the location - * at which padding will be inserted if the result of format() - * is shorter than the format width. - * @return the pad position, one of kPadBeforePrefix, - * kPadAfterPrefix, kPadBeforeSuffix, or - * kPadAfterSuffix. - * @see #setFormatWidth - * @see #getFormatWidth - * @see #setPadCharacter - * @see #getPadCharacter - * @see #setPadPosition - * @see #kPadBeforePrefix - * @see #kPadAfterPrefix - * @see #kPadBeforeSuffix - * @see #kPadAfterSuffix - */ -DecimalFormat::EPadPosition DecimalFormat::getPadPosition() const { - return fromPadPosition(fImpl->getPadPosition()); -} - -static DigitAffixesAndPadding::EPadPosition toPadPosition(DecimalFormat::EPadPosition padPos) { - switch (padPos) { - case DecimalFormat::kPadBeforePrefix: - return DigitAffixesAndPadding::kPadBeforePrefix; - case DecimalFormat::kPadAfterPrefix: - return DigitAffixesAndPadding::kPadAfterPrefix; - case DecimalFormat::kPadBeforeSuffix: - return DigitAffixesAndPadding::kPadBeforeSuffix; - case DecimalFormat::kPadAfterSuffix: - return DigitAffixesAndPadding::kPadAfterSuffix; - default: - U_ASSERT(FALSE); - break; - } - return DigitAffixesAndPadding::kPadBeforePrefix; -} - -/** - * NEW - * Set the position at which padding will take place. This is the location - * at which padding will be inserted if the result of format() - * is shorter than the format width. This has no effect unless padding is - * enabled. - * @param padPos the pad position, one of kPadBeforePrefix, - * kPadAfterPrefix, kPadBeforeSuffix, or - * kPadAfterSuffix. - * @see #setFormatWidth - * @see #getFormatWidth - * @see #setPadCharacter - * @see #getPadCharacter - * @see #getPadPosition - * @see #kPadBeforePrefix - * @see #kPadAfterPrefix - * @see #kPadBeforeSuffix - * @see #kPadAfterSuffix - */ -void DecimalFormat::setPadPosition(EPadPosition padPos) { - fImpl->setPadPosition(toPadPosition(padPos)); -} - -/** - * Return whether or not scientific notation is used. - * @return TRUE if this object formats and parses scientific notation - * @see #setScientificNotation - * @see #getMinimumExponentDigits - * @see #setMinimumExponentDigits - * @see #isExponentSignAlwaysShown - * @see #setExponentSignAlwaysShown - */ -UBool DecimalFormat::isScientificNotation() const { - return fImpl->isScientificNotation(); -} - -/** - * Set whether or not scientific notation is used. - * @param useScientific TRUE if this object formats and parses scientific - * notation - * @see #isScientificNotation - * @see #getMinimumExponentDigits - * @see #setMinimumExponentDigits - * @see #isExponentSignAlwaysShown - * @see #setExponentSignAlwaysShown - */ -void DecimalFormat::setScientificNotation(UBool useScientific) { - fImpl->setScientificNotation(useScientific); -} - -/** - * Return the minimum exponent digits that will be shown. - * @return the minimum exponent digits that will be shown - * @see #setScientificNotation - * @see #isScientificNotation - * @see #setMinimumExponentDigits - * @see #isExponentSignAlwaysShown - * @see #setExponentSignAlwaysShown - */ -int8_t DecimalFormat::getMinimumExponentDigits() const { - return fImpl->getMinimumExponentDigits(); -} - -/** - * Set the minimum exponent digits that will be shown. This has no - * effect unless scientific notation is in use. - * @param minExpDig a value >= 1 indicating the fewest exponent digits - * that will be shown. Values less than 1 will be treated as 1. - * @see #setScientificNotation - * @see #isScientificNotation - * @see #getMinimumExponentDigits - * @see #isExponentSignAlwaysShown - * @see #setExponentSignAlwaysShown - */ -void DecimalFormat::setMinimumExponentDigits(int8_t minExpDig) { - int32_t minExponentDigits = (int8_t)((minExpDig > 0) ? minExpDig : 1); - fImpl->setMinimumExponentDigits(minExponentDigits); -} - -/** - * Return whether the exponent sign is always shown. - * @return TRUE if the exponent is always prefixed with either the - * localized minus sign or the localized plus sign, false if only negative - * exponents are prefixed with the localized minus sign. - * @see #setScientificNotation - * @see #isScientificNotation - * @see #setMinimumExponentDigits - * @see #getMinimumExponentDigits - * @see #setExponentSignAlwaysShown - */ -UBool DecimalFormat::isExponentSignAlwaysShown() const { - return fImpl->isExponentSignAlwaysShown(); -} - -/** - * Set whether the exponent sign is always shown. This has no effect - * unless scientific notation is in use. - * @param expSignAlways TRUE if the exponent is always prefixed with either - * the localized minus sign or the localized plus sign, false if only - * negative exponents are prefixed with the localized minus sign. - * @see #setScientificNotation - * @see #isScientificNotation - * @see #setMinimumExponentDigits - * @see #getMinimumExponentDigits - * @see #isExponentSignAlwaysShown - */ -void DecimalFormat::setExponentSignAlwaysShown(UBool expSignAlways) { - fImpl->setExponentSignAlwaysShown(expSignAlways); -} - -//------------------------------------------------------------------------------ -// Gets the grouping size of the number pattern. For example, thousand or 10 -// thousand groupings. - -int32_t -DecimalFormat::getGroupingSize() const -{ - return fImpl->getGroupingSize(); -} - -//------------------------------------------------------------------------------ -// Gets the grouping size of the number pattern. - -void -DecimalFormat::setGroupingSize(int32_t newValue) -{ - fImpl->setGroupingSize(newValue); -} - -//------------------------------------------------------------------------------ - -int32_t -DecimalFormat::getSecondaryGroupingSize() const -{ - return fImpl->getSecondaryGroupingSize(); -} - -//------------------------------------------------------------------------------ - -void -DecimalFormat::setSecondaryGroupingSize(int32_t newValue) -{ - fImpl->setSecondaryGroupingSize(newValue); -} - -//------------------------------------------------------------------------------ - -int32_t -DecimalFormat::getMinimumGroupingDigits() const -{ - return fImpl->getMinimumGroupingDigits(); -} - -//------------------------------------------------------------------------------ - -void -DecimalFormat::setMinimumGroupingDigits(int32_t newValue) -{ - fImpl->setMinimumGroupingDigits(newValue); -} - -//------------------------------------------------------------------------------ -// Checks if to show the decimal separator. - -UBool -DecimalFormat::isDecimalSeparatorAlwaysShown() const -{ - return fImpl->isDecimalSeparatorAlwaysShown(); -} - -//------------------------------------------------------------------------------ -// Sets to always show the decimal separator. - -void -DecimalFormat::setDecimalSeparatorAlwaysShown(UBool newValue) -{ - fImpl->setDecimalSeparatorAlwaysShown(newValue); -} - -//------------------------------------------------------------------------------ -// Checks if decimal point pattern match is required -UBool -DecimalFormat::isDecimalPatternMatchRequired(void) const -{ - return static_cast(fBoolFlags.contains(UNUM_PARSE_DECIMAL_MARK_REQUIRED)); -} - -//------------------------------------------------------------------------------ -// Checks if decimal point pattern match is required - -void -DecimalFormat::setDecimalPatternMatchRequired(UBool newValue) -{ - fBoolFlags.set(UNUM_PARSE_DECIMAL_MARK_REQUIRED, newValue); -} - - -//------------------------------------------------------------------------------ -// Emits the pattern of this DecimalFormat instance. - -UnicodeString& -DecimalFormat::toPattern(UnicodeString& result) const -{ - return fImpl->toPattern(result); -} - -//------------------------------------------------------------------------------ -// Emits the localized pattern this DecimalFormat instance. - -UnicodeString& -DecimalFormat::toLocalizedPattern(UnicodeString& result) const -{ - // toLocalizedPattern is deprecated, so we just make it the same as - // toPattern. - return fImpl->toPattern(result); -} - -//------------------------------------------------------------------------------ - -void -DecimalFormat::applyPattern(const UnicodeString& pattern, UErrorCode& status) -{ - if (pattern.indexOf(kCurrencySign) != -1) { - handleCurrencySignInPattern(status); - } - fImpl->applyPattern(pattern, status); -} - -//------------------------------------------------------------------------------ - -void -DecimalFormat::applyPattern(const UnicodeString& pattern, - UParseError& parseError, - UErrorCode& status) -{ - if (pattern.indexOf(kCurrencySign) != -1) { - handleCurrencySignInPattern(status); - } - fImpl->applyPattern(pattern, parseError, status); -} -//------------------------------------------------------------------------------ - -void -DecimalFormat::applyLocalizedPattern(const UnicodeString& pattern, UErrorCode& status) -{ - if (pattern.indexOf(kCurrencySign) != -1) { - handleCurrencySignInPattern(status); - } - fImpl->applyLocalizedPattern(pattern, status); -} - -//------------------------------------------------------------------------------ - -void -DecimalFormat::applyLocalizedPattern(const UnicodeString& pattern, - UParseError& parseError, - UErrorCode& status) -{ - if (pattern.indexOf(kCurrencySign) != -1) { - handleCurrencySignInPattern(status); - } - fImpl->applyLocalizedPattern(pattern, parseError, status); -} - -//------------------------------------------------------------------------------ - -/** - * Sets the maximum number of digits allowed in the integer portion of a - * number. - * @see NumberFormat#setMaximumIntegerDigits - */ -void DecimalFormat::setMaximumIntegerDigits(int32_t newValue) { - newValue = _min(newValue, gDefaultMaxIntegerDigits); - NumberFormat::setMaximumIntegerDigits(newValue); - fImpl->updatePrecision(); -} - -/** - * Sets the minimum number of digits allowed in the integer portion of a - * number. This override limits the integer digit count to 309. - * @see NumberFormat#setMinimumIntegerDigits - */ -void DecimalFormat::setMinimumIntegerDigits(int32_t newValue) { - newValue = _min(newValue, kDoubleIntegerDigits); - NumberFormat::setMinimumIntegerDigits(newValue); - fImpl->updatePrecision(); -} - -/** - * Sets the maximum number of digits allowed in the fraction portion of a - * number. This override limits the fraction digit count to 340. - * @see NumberFormat#setMaximumFractionDigits - */ -void DecimalFormat::setMaximumFractionDigits(int32_t newValue) { - newValue = _min(newValue, kDoubleFractionDigits); - NumberFormat::setMaximumFractionDigits(newValue); - fImpl->updatePrecision(); -} - -/** - * Sets the minimum number of digits allowed in the fraction portion of a - * number. This override limits the fraction digit count to 340. - * @see NumberFormat#setMinimumFractionDigits - */ -void DecimalFormat::setMinimumFractionDigits(int32_t newValue) { - newValue = _min(newValue, kDoubleFractionDigits); - NumberFormat::setMinimumFractionDigits(newValue); - fImpl->updatePrecision(); -} - -int32_t DecimalFormat::getMinimumSignificantDigits() const { - return fImpl->getMinimumSignificantDigits(); -} - -int32_t DecimalFormat::getMaximumSignificantDigits() const { - return fImpl->getMaximumSignificantDigits(); -} - -void DecimalFormat::setMinimumSignificantDigits(int32_t min) { - if (min < 1) { - min = 1; - } - // pin max sig dig to >= min - int32_t max = _max(fImpl->fMaxSigDigits, min); - fImpl->setMinMaxSignificantDigits(min, max); -} - -void DecimalFormat::setMaximumSignificantDigits(int32_t max) { - if (max < 1) { - max = 1; - } - // pin min sig dig to 1..max - U_ASSERT(fImpl->fMinSigDigits >= 1); - int32_t min = _min(fImpl->fMinSigDigits, max); - fImpl->setMinMaxSignificantDigits(min, max); -} - -UBool DecimalFormat::areSignificantDigitsUsed() const { - return fImpl->areSignificantDigitsUsed(); -} - -void DecimalFormat::setSignificantDigitsUsed(UBool useSignificantDigits) { - fImpl->setSignificantDigitsUsed(useSignificantDigits); -} - -void DecimalFormat::setCurrency(const UChar* theCurrency, UErrorCode& ec) { - // set the currency before compute affixes to get the right currency names - NumberFormat::setCurrency(theCurrency, ec); - fImpl->updateCurrency(ec); -} - -void DecimalFormat::setCurrencyUsage(UCurrencyUsage newContext, UErrorCode* ec){ - fImpl->setCurrencyUsage(newContext, *ec); -} - -UCurrencyUsage DecimalFormat::getCurrencyUsage() const { - return fImpl->getCurrencyUsage(); -} - -// Deprecated variant with no UErrorCode parameter -void DecimalFormat::setCurrency(const UChar* theCurrency) { - UErrorCode ec = U_ZERO_ERROR; - setCurrency(theCurrency, ec); -} - -void DecimalFormat::getEffectiveCurrency(UChar* result, UErrorCode& ec) const { - if (fImpl->fSymbols == NULL) { - ec = U_MEMORY_ALLOCATION_ERROR; - return; - } - ec = U_ZERO_ERROR; - const UChar* c = getCurrency(); - if (*c == 0) { - const UnicodeString &intl = - fImpl->getConstSymbol(DecimalFormatSymbols::kIntlCurrencySymbol); - c = intl.getBuffer(); // ok for intl to go out of scope - } - u_strncpy(result, c, 3); - result[3] = 0; -} - -Hashtable* -DecimalFormat::initHashForAffixPattern(UErrorCode& status) { - if ( U_FAILURE(status) ) { - return NULL; - } - Hashtable* hTable; - if ( (hTable = new Hashtable(TRUE, status)) == NULL ) { - status = U_MEMORY_ALLOCATION_ERROR; - return NULL; - } - if ( U_FAILURE(status) ) { - delete hTable; - return NULL; - } - hTable->setValueComparator(decimfmtAffixPatternValueComparator); - return hTable; -} - -void -DecimalFormat::deleteHashForAffixPattern() -{ - if ( fAffixPatternsForCurrency == NULL ) { - return; - } - int32_t pos = UHASH_FIRST; - const UHashElement* element = NULL; - while ( (element = fAffixPatternsForCurrency->nextElement(pos)) != NULL ) { - const UHashTok valueTok = element->value; - const AffixPatternsForCurrency* value = (AffixPatternsForCurrency*)valueTok.pointer; - delete value; - } - delete fAffixPatternsForCurrency; - fAffixPatternsForCurrency = NULL; -} - - -void -DecimalFormat::copyHashForAffixPattern(const Hashtable* source, - Hashtable* target, - UErrorCode& status) { - if ( U_FAILURE(status) ) { - return; - } - int32_t pos = UHASH_FIRST; - const UHashElement* element = NULL; - if ( source ) { - while ( (element = source->nextElement(pos)) != NULL ) { - const UHashTok keyTok = element->key; - const UnicodeString* key = (UnicodeString*)keyTok.pointer; - const UHashTok valueTok = element->value; - const AffixPatternsForCurrency* value = (AffixPatternsForCurrency*)valueTok.pointer; - AffixPatternsForCurrency* copy = new AffixPatternsForCurrency( - value->negPrefixPatternForCurrency, - value->negSuffixPatternForCurrency, - value->posPrefixPatternForCurrency, - value->posSuffixPatternForCurrency, - value->patternType); - target->put(UnicodeString(*key), copy, status); - if ( U_FAILURE(status) ) { - return; - } - } - } -} - -void -DecimalFormat::setGroupingUsed(UBool newValue) { - NumberFormat::setGroupingUsed(newValue); - fImpl->updateGrouping(); -} - -void -DecimalFormat::setParseIntegerOnly(UBool newValue) { - NumberFormat::setParseIntegerOnly(newValue); -} - -void -DecimalFormat::setContext(UDisplayContext value, UErrorCode& status) { - NumberFormat::setContext(value, status); -} - -DecimalFormat& DecimalFormat::setAttribute( UNumberFormatAttribute attr, - int32_t newValue, - UErrorCode &status) { - if(U_FAILURE(status)) return *this; - - switch(attr) { - case UNUM_LENIENT_PARSE: - setLenient(newValue!=0); - break; - - case UNUM_PARSE_INT_ONLY: - setParseIntegerOnly(newValue!=0); - break; - - case UNUM_GROUPING_USED: - setGroupingUsed(newValue!=0); - break; - - case UNUM_DECIMAL_ALWAYS_SHOWN: - setDecimalSeparatorAlwaysShown(newValue!=0); - break; - - case UNUM_MAX_INTEGER_DIGITS: - setMaximumIntegerDigits(newValue); - break; - - case UNUM_MIN_INTEGER_DIGITS: - setMinimumIntegerDigits(newValue); - break; - - case UNUM_INTEGER_DIGITS: - setMinimumIntegerDigits(newValue); - setMaximumIntegerDigits(newValue); - break; - - case UNUM_MAX_FRACTION_DIGITS: - setMaximumFractionDigits(newValue); - break; - - case UNUM_MIN_FRACTION_DIGITS: - setMinimumFractionDigits(newValue); - break; - - case UNUM_FRACTION_DIGITS: - setMinimumFractionDigits(newValue); - setMaximumFractionDigits(newValue); - break; - - case UNUM_SIGNIFICANT_DIGITS_USED: - setSignificantDigitsUsed(newValue!=0); - break; - - case UNUM_MAX_SIGNIFICANT_DIGITS: - setMaximumSignificantDigits(newValue); - break; - - case UNUM_MIN_SIGNIFICANT_DIGITS: - setMinimumSignificantDigits(newValue); - break; - - case UNUM_MULTIPLIER: - setMultiplier(newValue); - break; - - case UNUM_GROUPING_SIZE: - setGroupingSize(newValue); - break; - - case UNUM_ROUNDING_MODE: - setRoundingMode((DecimalFormat::ERoundingMode)newValue); - break; - - case UNUM_FORMAT_WIDTH: - setFormatWidth(newValue); - break; - - case UNUM_PADDING_POSITION: - /** The position at which padding will take place. */ - setPadPosition((DecimalFormat::EPadPosition)newValue); - break; - - case UNUM_SECONDARY_GROUPING_SIZE: - setSecondaryGroupingSize(newValue); - break; - -#if UCONFIG_HAVE_PARSEALLINPUT - case UNUM_PARSE_ALL_INPUT: - setParseAllInput((UNumberFormatAttributeValue)newValue); - break; -#endif - - /* These are stored in fBoolFlags */ - case UNUM_PARSE_NO_EXPONENT: - case UNUM_FORMAT_FAIL_IF_MORE_THAN_MAX_DIGITS: - case UNUM_PARSE_DECIMAL_MARK_REQUIRED: - if(!fBoolFlags.isValidValue(newValue)) { - status = U_ILLEGAL_ARGUMENT_ERROR; - } else { - if (attr == UNUM_FORMAT_FAIL_IF_MORE_THAN_MAX_DIGITS) { - fImpl->setFailIfMoreThanMaxDigits((UBool) newValue); - } - fBoolFlags.set(attr, newValue); - } - break; - - case UNUM_SCALE: - fImpl->setScale(newValue); - break; - - case UNUM_CURRENCY_USAGE: - setCurrencyUsage((UCurrencyUsage)newValue, &status); - break; - - case UNUM_MINIMUM_GROUPING_DIGITS: - setMinimumGroupingDigits(newValue); - break; - - default: - status = U_UNSUPPORTED_ERROR; - break; - } - return *this; -} - -int32_t DecimalFormat::getAttribute( UNumberFormatAttribute attr, - UErrorCode &status ) const { - if(U_FAILURE(status)) return -1; - switch(attr) { - case UNUM_LENIENT_PARSE: - return isLenient(); - - case UNUM_PARSE_INT_ONLY: - return isParseIntegerOnly(); - - case UNUM_GROUPING_USED: - return isGroupingUsed(); - - case UNUM_DECIMAL_ALWAYS_SHOWN: - return isDecimalSeparatorAlwaysShown(); - - case UNUM_MAX_INTEGER_DIGITS: - return getMaximumIntegerDigits(); - - case UNUM_MIN_INTEGER_DIGITS: - return getMinimumIntegerDigits(); - - case UNUM_INTEGER_DIGITS: - // TBD: what should this return? - return getMinimumIntegerDigits(); - - case UNUM_MAX_FRACTION_DIGITS: - return getMaximumFractionDigits(); - - case UNUM_MIN_FRACTION_DIGITS: - return getMinimumFractionDigits(); - - case UNUM_FRACTION_DIGITS: - // TBD: what should this return? - return getMinimumFractionDigits(); - - case UNUM_SIGNIFICANT_DIGITS_USED: - return areSignificantDigitsUsed(); - - case UNUM_MAX_SIGNIFICANT_DIGITS: - return getMaximumSignificantDigits(); - - case UNUM_MIN_SIGNIFICANT_DIGITS: - return getMinimumSignificantDigits(); - - case UNUM_MULTIPLIER: - return getMultiplier(); - - case UNUM_GROUPING_SIZE: - return getGroupingSize(); - - case UNUM_ROUNDING_MODE: - return getRoundingMode(); - - case UNUM_FORMAT_WIDTH: - return getFormatWidth(); - - case UNUM_PADDING_POSITION: - return getPadPosition(); - - case UNUM_SECONDARY_GROUPING_SIZE: - return getSecondaryGroupingSize(); - - /* These are stored in fBoolFlags */ - case UNUM_PARSE_NO_EXPONENT: - case UNUM_FORMAT_FAIL_IF_MORE_THAN_MAX_DIGITS: - case UNUM_PARSE_DECIMAL_MARK_REQUIRED: - return fBoolFlags.get(attr); - - case UNUM_SCALE: - return fImpl->fScale; - - case UNUM_CURRENCY_USAGE: - return fImpl->getCurrencyUsage(); - - case UNUM_MINIMUM_GROUPING_DIGITS: - return getMinimumGroupingDigits(); - - default: - status = U_UNSUPPORTED_ERROR; - break; - } - - return -1; /* undefined */ -} - -#if UCONFIG_HAVE_PARSEALLINPUT -void DecimalFormat::setParseAllInput(UNumberFormatAttributeValue value) { - fParseAllInput = value; -} -#endif - -U_NAMESPACE_END #endif /* #if !UCONFIG_NO_FORMATTING */ - -//eof diff --git a/icu4c/source/i18n/msgfmt.cpp b/icu4c/source/i18n/msgfmt.cpp index 064585665a..c2be3f7bd1 100644 --- a/icu4c/source/i18n/msgfmt.cpp +++ b/icu4c/source/i18n/msgfmt.cpp @@ -1959,14 +1959,15 @@ UnicodeString MessageFormat::PluralSelectorProvider::select(void *ctx, double nu return UnicodeString(FALSE, OTHER_STRING, 5); } context.formatter->format(context.number, context.numberString, ec); - const DecimalFormat *decFmt = dynamic_cast(context.formatter); + auto* decFmt = dynamic_cast(context.formatter); if(decFmt != NULL) { - VisibleDigitsWithExponent digits; - decFmt->initVisibleDigitsWithExponent(context.number, digits, ec); + const IFixedDecimal& dec = decFmt->toNumberFormatter() + .formatDouble(context.number.getDouble(ec), ec) + .getFixedDecimal(ec); if (U_FAILURE(ec)) { return UnicodeString(FALSE, OTHER_STRING, 5); } - return rules->select(digits); + return rules->select(dec); } else { return rules->select(number); } diff --git a/icu4c/source/i18n/plurfmt.cpp b/icu4c/source/i18n/plurfmt.cpp index e14ef6d831..f0e18f9cc0 100644 --- a/icu4c/source/i18n/plurfmt.cpp +++ b/icu4c/source/i18n/plurfmt.cpp @@ -272,12 +272,13 @@ PluralFormat::format(const Formattable& numberObject, double number, if (offset == 0) { DecimalFormat *decFmt = dynamic_cast(numberFormat); if(decFmt != NULL) { - decFmt->initVisibleDigitsWithExponent( - numberObject, dec, status); - if (U_FAILURE(status)) { - return appendTo; - } - decFmt->format(dec, numberString, ignorePos, status); +// decFmt->initVisibleDigitsWithExponent( +// numberObject, dec, status); +// if (U_FAILURE(status)) { +// return appendTo; +// } +// decFmt->format(dec, numberString, ignorePos, status); + decFmt->format(numberObject, numberString, ignorePos, status); } else { numberFormat->format( numberObject, numberString, ignorePos, status); // could be BigDecimal etc. @@ -285,12 +286,13 @@ PluralFormat::format(const Formattable& numberObject, double number, } else { DecimalFormat *decFmt = dynamic_cast(numberFormat); if(decFmt != NULL) { - decFmt->initVisibleDigitsWithExponent( - numberMinusOffset, dec, status); - if (U_FAILURE(status)) { - return appendTo; - } - decFmt->format(dec, numberString, ignorePos, status); +// decFmt->initVisibleDigitsWithExponent( +// numberMinusOffset, dec, status); +// if (U_FAILURE(status)) { +// return appendTo; +// } +// decFmt->format(dec, numberString, ignorePos, status); + decFmt->format(numberObject, numberString, ignorePos, status); } else { numberFormat->format( numberMinusOffset, numberString, ignorePos, status); diff --git a/icu4c/source/i18n/plurrule.cpp b/icu4c/source/i18n/plurrule.cpp index 14b5fe6d9d..3da36d64b5 100644 --- a/icu4c/source/i18n/plurrule.cpp +++ b/icu4c/source/i18n/plurrule.cpp @@ -254,10 +254,11 @@ PluralRules::select(const Formattable& obj, const NumberFormat& fmt, UErrorCode& if (U_SUCCESS(status)) { const DecimalFormat *decFmt = dynamic_cast(&fmt); if (decFmt != NULL) { - VisibleDigitsWithExponent digits; - decFmt->initVisibleDigitsWithExponent(obj, digits, status); + const IFixedDecimal& dec = decFmt->toNumberFormatter() + .formatDouble(obj.getDouble(status), status) + .getFixedDecimal(status); if (U_SUCCESS(status)) { - return select(digits); + return select(dec); } } else { double number = obj.getDouble(status); diff --git a/icu4c/source/i18n/quantityformatter.cpp b/icu4c/source/i18n/quantityformatter.cpp index 208e064700..eaf9221275 100644 --- a/icu4c/source/i18n/quantityformatter.cpp +++ b/icu4c/source/i18n/quantityformatter.cpp @@ -149,15 +149,16 @@ StandardPlural::Form QuantityFormatter::selectPlural( return StandardPlural::OTHER; } UnicodeString pluralKeyword; - VisibleDigitsWithExponent digits; const DecimalFormat *decFmt = dynamic_cast(&fmt); if (decFmt != NULL) { - decFmt->initVisibleDigitsWithExponent(number, digits, status); + const IFixedDecimal& dec = decFmt->toNumberFormatter() + .formatDouble(number.getDouble(status), status) + .getFixedDecimal(status); if (U_FAILURE(status)) { return StandardPlural::OTHER; } - pluralKeyword = rules.select(digits); - decFmt->format(digits, formattedNumber, pos, status); + pluralKeyword = rules.select(dec); + decFmt->format(number, formattedNumber, pos, status); } else { if (number.getType() == Formattable::kDouble) { pluralKeyword = rules.select(number.getDouble()); diff --git a/icu4c/source/i18n/unicode/decimfmt.h b/icu4c/source/i18n/unicode/decimfmt.h index 7e39239964..dbd01a347d 100644 --- a/icu4c/source/i18n/unicode/decimfmt.h +++ b/icu4c/source/i18n/unicode/decimfmt.h @@ -1931,7 +1931,6 @@ public: */ void setSignificantDigitsUsed(UBool useSignificantDigits); - public: /** * Sets the currency used to display currency * amounts. This takes effect immediately, if this format is a @@ -1968,77 +1967,6 @@ public: */ UCurrencyUsage getCurrencyUsage() const; - - /** - * The resource tags we use to retrieve decimal format data from - * locale resource bundles. - * @deprecated ICU 3.4. This string has no public purpose. Please don't use it. - */ - static const char fgNumberPatterns[]; - -#ifndef U_HIDE_INTERNAL_API - /** - * Get a FixedDecimal corresponding to a double as it would be - * formatted by this DecimalFormat. - * Internal, not intended for public use. - * @internal - */ - FixedDecimal getFixedDecimal(double number, UErrorCode &status) const; - - /** - * Get a FixedDecimal corresponding to a formattable as it would be - * formatted by this DecimalFormat. - * Internal, not intended for public use. - * @internal - */ - FixedDecimal getFixedDecimal(const Formattable &number, UErrorCode &status) const; - - /** - * Get a FixedDecimal corresponding to a DigitList as it would be - * formatted by this DecimalFormat. Note: the DigitList may be modified. - * Internal, not intended for public use. - * @internal - */ - FixedDecimal getFixedDecimal(DigitList &number, UErrorCode &status) const; - - /** - * Get a VisibleDigitsWithExponent corresponding to a double - * as it would be formatted by this DecimalFormat. - * Internal, not intended for public use. - * @internal - */ - VisibleDigitsWithExponent &initVisibleDigitsWithExponent( - double number, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const; - - /** - * Get a VisibleDigitsWithExponent corresponding to a formattable - * as it would be formatted by this DecimalFormat. - * Internal, not intended for public use. - * @internal - */ - VisibleDigitsWithExponent &initVisibleDigitsWithExponent( - const Formattable &number, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const; - - /** - * Get a VisibleDigitsWithExponent corresponding to a DigitList - * as it would be formatted by this DecimalFormat. - * Note: the DigitList may be modified. - * Internal, not intended for public use. - * @internal - */ - VisibleDigitsWithExponent &initVisibleDigitsWithExponent( - DigitList &number, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const; - -#endif /* U_HIDE_INTERNAL_API */ - -public: - /** * Converts this DecimalFormat to a NumberFormatter. Starting in ICU 60, * NumberFormatter is the recommended way to format numbers. @@ -2046,7 +1974,7 @@ public: * @return An instance of LocalizedNumberFormatter with the same behavior as this DecimalFormat. * @draft ICU 62 */ - number::LocalizedNumberFormatter getNumberFormatter() const; + number::LocalizedNumberFormatter toNumberFormatter() const; /** * Return the class ID for this class. This is useful only for @@ -2074,216 +2002,6 @@ public: */ virtual UClassID getDynamicClassID(void) const U_OVERRIDE; -private: - - DecimalFormat(); // default constructor not implemented - - /** - * Initialize all fields of a new DecimalFormatter to a safe default value. - * Common code for use by constructors. - */ - void init(); - - /** - * Do real work of constructing a new DecimalFormat. - */ - void construct(UErrorCode& status, - UParseError& parseErr, - const UnicodeString* pattern = 0, - DecimalFormatSymbols* symbolsToAdopt = 0 - ); - - void handleCurrencySignInPattern(UErrorCode& status); - - void parse(const UnicodeString& text, - Formattable& result, - ParsePosition& pos, - char16_t* currency) const; - - enum { - fgStatusInfinite, - fgStatusLength // Leave last in list. - } StatusFlags; - - UBool subparse(const UnicodeString& text, - const UnicodeString* negPrefix, - const UnicodeString* negSuffix, - const UnicodeString* posPrefix, - const UnicodeString* posSuffix, - UBool complexCurrencyParsing, - int8_t type, - ParsePosition& parsePosition, - DigitList& digits, UBool* status, - char16_t* currency) const; - - // Mixed style parsing for currency. - // It parses against the current currency pattern - // using complex affix comparison - // parses against the currency plural patterns using complex affix comparison, - // and parses against the current pattern using simple affix comparison. - UBool parseForCurrency(const UnicodeString& text, - ParsePosition& parsePosition, - DigitList& digits, - UBool* status, - char16_t* currency) const; - - int32_t skipPadding(const UnicodeString& text, int32_t position) const; - - int32_t compareAffix(const UnicodeString& input, - int32_t pos, - UBool isNegative, - UBool isPrefix, - const UnicodeString* affixPat, - UBool complexCurrencyParsing, - int8_t type, - char16_t* currency) const; - - static UnicodeString& trimMarksFromAffix(const UnicodeString& affix, UnicodeString& trimmedAffix); - - UBool equalWithSignCompatibility(UChar32 lhs, UChar32 rhs) const; - - int32_t compareSimpleAffix(const UnicodeString& affix, - const UnicodeString& input, - int32_t pos, - UBool lenient) const; - - static int32_t skipPatternWhiteSpace(const UnicodeString& text, int32_t pos); - - static int32_t skipUWhiteSpace(const UnicodeString& text, int32_t pos); - - static int32_t skipUWhiteSpaceAndMarks(const UnicodeString& text, int32_t pos); - - static int32_t skipBidiMarks(const UnicodeString& text, int32_t pos); - - int32_t compareComplexAffix(const UnicodeString& affixPat, - const UnicodeString& input, - int32_t pos, - int8_t type, - char16_t* currency) const; - - static int32_t match(const UnicodeString& text, int32_t pos, UChar32 ch); - - static int32_t match(const UnicodeString& text, int32_t pos, const UnicodeString& str); - - static UBool matchSymbol(const UnicodeString &text, int32_t position, int32_t length, const UnicodeString &symbol, - UnicodeSet *sset, UChar32 schar); - - static UBool matchDecimal(UChar32 symbolChar, - UBool sawDecimal, UChar32 sawDecimalChar, - const UnicodeSet *sset, UChar32 schar); - - static UBool matchGrouping(UChar32 groupingChar, - UBool sawGrouping, UChar32 sawGroupingChar, - const UnicodeSet *sset, - UChar32 decimalChar, const UnicodeSet *decimalSet, - UChar32 schar); - - // set up currency affix patterns for mix parsing. - // The patterns saved here are the affix patterns of default currency - // pattern and the unique affix patterns of the plural currency patterns. - // Those patterns are used by parseForCurrency(). - void setupCurrencyAffixPatterns(UErrorCode& status); - - // get the currency rounding with respect to currency usage - double getCurrencyRounding(const char16_t* currency, - UErrorCode* ec) const; - - // get the currency fraction with respect to currency usage - int getCurrencyFractionDigits(const char16_t* currency, - UErrorCode* ec) const; - - // hashtable operations - Hashtable* initHashForAffixPattern(UErrorCode& status); - - void deleteHashForAffixPattern(); - - void copyHashForAffixPattern(const Hashtable* source, - Hashtable* target, UErrorCode& status); - - DecimalFormatImpl *fImpl; - - /** - * Constants. - */ - - - EnumSet - fBoolFlags; - - - // style is only valid when decimal formatter is constructed by - // DecimalFormat(pattern, decimalFormatSymbol, style) - int fStyle; - - - // Affix pattern set for currency. - // It is a set of AffixPatternsForCurrency, - // each element of the set saves the negative prefix pattern, - // negative suffix pattern, positive prefix pattern, - // and positive suffix pattern of a pattern. - // It is used for currency mixed style parsing. - // It is actually is a set. - // The set contains the default currency pattern from the locale, - // and the currency plural patterns. - // Since it is a set, it does not contain duplicated items. - // For example, if 2 currency plural patterns are the same, only one pattern - // is included in the set. When parsing, we do not check whether the plural - // count match or not. - Hashtable* fAffixPatternsForCurrency; - - // Information needed for DecimalFormat to format/parse currency plural. - CurrencyPluralInfo* fCurrencyPluralInfo; - -#if UCONFIG_HAVE_PARSEALLINPUT - UNumberFormatAttributeValue fParseAllInput; -#endif - - // Decimal Format Static Sets singleton. - const DecimalFormatStaticSets *fStaticSets; - -protected: - -#ifndef U_HIDE_INTERNAL_API - /** - * Rounds a value according to the rules of this object. - * @internal - */ - DigitList& _round(const DigitList& number, DigitList& adjustedNum, UBool& isNegative, UErrorCode& status) const; -#endif /* U_HIDE_INTERNAL_API */ - - /** - * Returns the currency in effect for this formatter. Subclasses - * should override this method as needed. Unlike getCurrency(), - * this method should never return "". - * @result output parameter for null-terminated result, which must - * have a capacity of at least 4 - * @internal - */ - void getEffectiveCurrency(char16_t* result, UErrorCode& ec) const U_OVERRIDE; - - /** number of integer digits - * @stable ICU 2.4 - */ - static const int32_t kDoubleIntegerDigits; - /** number of fraction digits - * @stable ICU 2.4 - */ - static const int32_t kDoubleFractionDigits; - - /** - * When someone turns on scientific mode, we assume that more than this - * number of digits is due to flipping from some other mode that didn't - * restrict the maximum, and so we force 1 integer digit. We don't bother - * to track and see if someone is using exponential notation with more than - * this number, it wouldn't make sense anyway, and this is just to make sure - * that someone turning on scientific mode with default settings doesn't - * end up with lots of zeroes. - * @stable ICU 2.8 - */ - static const int32_t kMaxScientificIntegerDigits; - }; U_NAMESPACE_END diff --git a/icu4c/source/i18n/unicode/numberformatter.h b/icu4c/source/i18n/unicode/numberformatter.h index f83f973880..878b6089a0 100644 --- a/icu4c/source/i18n/unicode/numberformatter.h +++ b/icu4c/source/i18n/unicode/numberformatter.h @@ -381,6 +381,9 @@ typedef enum UNumberDecimalSeparatorDisplay { U_NAMESPACE_BEGIN +// Forward declarations: +class IFixedDecimal; + namespace numparse { namespace impl { @@ -2126,6 +2129,15 @@ class U_I18N_API FormattedNumber : public UMemory { */ void populateFieldPositionIterator(FieldPositionIterator &iterator, UErrorCode &status); +#ifndef U_HIDE_INTERNAL_API + /** + * Get an IFixedDecimal for plural rule selection. + * Internal, not intended for public use. + * @internal + */ + const IFixedDecimal& getFixedDecimal(UErrorCode &status) const; +#endif + /** * Destruct an instance of FormattedNumber, cleaning up any memory it might own. * @draft ICU 60 From 94427dc200decb832411919eec298e5fe62b6ad3 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Sat, 3 Mar 2018 10:53:01 +0000 Subject: [PATCH 031/129] ICU-13574 Replacing DigitList with DecimalQuantity through most of the code base. X-SVN-Rev: 41064 --- icu4c/source/i18n/decimfmt.cpp | 218 ++++++++++ icu4c/source/i18n/fmtable.cpp | 112 ++--- icu4c/source/i18n/fmtableimp.h | 4 +- icu4c/source/i18n/msgfmt.cpp | 8 +- icu4c/source/i18n/nfsubs.cpp | 20 +- icu4c/source/i18n/number_decimalquantity.h | 4 + icu4c/source/i18n/numfmt.cpp | 18 +- icu4c/source/i18n/plurrule.cpp | 16 +- icu4c/source/i18n/plurrule_impl.h | 4 + icu4c/source/i18n/quantityformatter.cpp | 8 +- icu4c/source/i18n/rbnf.cpp | 54 +-- .../i18n/unicode/compactdecimalformat.h | 294 +------------ icu4c/source/i18n/unicode/decimfmt.h | 69 ++- icu4c/source/i18n/unicode/fmtable.h | 25 +- icu4c/source/i18n/unicode/numfmt.h | 12 +- icu4c/source/i18n/unicode/rbnf.h | 4 +- icu4c/source/test/intltest/dcfmapts.cpp | 392 +++++++++--------- .../test/intltest/numberformattesttuple.cpp | 1 - icu4c/source/test/intltest/numfmtst.cpp | 45 +- icu4c/source/test/intltest/plurults.cpp | 10 +- icu4c/source/test/intltest/uobjtest.cpp | 1 - 21 files changed, 614 insertions(+), 705 deletions(-) diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp index 99fbfb54bd..c50c2ca083 100644 --- a/icu4c/source/i18n/decimfmt.cpp +++ b/icu4c/source/i18n/decimfmt.cpp @@ -9,11 +9,229 @@ // Helpful in toString methods and elsewhere. #define UNISTR_FROM_STRING_EXPLICIT +#include "unicode/decimfmt.h" +#include "number_decimalquantity.h" + using namespace icu; +using namespace icu::number; +using namespace icu::number::impl; +using ERoundingMode = icu::DecimalFormat::ERoundingMode; +using EPadPosition = icu::DecimalFormat::EPadPosition; +DecimalFormat::DecimalFormat(UErrorCode& status) {} +DecimalFormat::DecimalFormat(const UnicodeString& pattern, UErrorCode& status) {} +DecimalFormat::DecimalFormat(const UnicodeString& pattern, DecimalFormatSymbols* symbolsToAdopt, + UErrorCode& status) {} + +DecimalFormat::DecimalFormat(const UnicodeString& pattern, DecimalFormatSymbols* symbolsToAdopt, + UNumberFormatStyle style, UErrorCode& status) {} + +void DecimalFormat::setParseAllInput(UNumberFormatAttributeValue value) {} + +DecimalFormat& +DecimalFormat::setAttribute(UNumberFormatAttribute attr, int32_t newvalue, UErrorCode& status) {} + +int32_t DecimalFormat::getAttribute(UNumberFormatAttribute attr, UErrorCode& status) const {} + +void DecimalFormat::setGroupingUsed(UBool newValue) {} + +void DecimalFormat::setParseIntegerOnly(UBool value) {} + +void DecimalFormat::setContext(UDisplayContext value, UErrorCode& status) {} + +DecimalFormat::DecimalFormat(const UnicodeString& pattern, DecimalFormatSymbols* symbolsToAdopt, + UParseError& parseError, UErrorCode& status) {} + +DecimalFormat::DecimalFormat(const UnicodeString& pattern, const DecimalFormatSymbols& symbols, + UErrorCode& status) {} + +DecimalFormat::DecimalFormat(const DecimalFormat& source) {} + +DecimalFormat& DecimalFormat::operator=(const DecimalFormat& rhs) {} + +DecimalFormat::~DecimalFormat() = default; + +Format* DecimalFormat::clone() const {} + +UBool DecimalFormat::operator==(const Format& other) const {} + +UnicodeString& DecimalFormat::format(double number, UnicodeString& appendTo, FieldPosition& pos) const {} + +UnicodeString& DecimalFormat::format(double number, UnicodeString& appendTo, FieldPosition& pos, + UErrorCode& status) const {} + +UnicodeString& +DecimalFormat::format(double number, UnicodeString& appendTo, FieldPositionIterator* posIter, + UErrorCode& status) const {} + +UnicodeString& DecimalFormat::format(int32_t number, UnicodeString& appendTo, FieldPosition& pos) const {} + +UnicodeString& DecimalFormat::format(int32_t number, UnicodeString& appendTo, FieldPosition& pos, + UErrorCode& status) const {} + +UnicodeString& +DecimalFormat::format(int32_t number, UnicodeString& appendTo, FieldPositionIterator* posIter, + UErrorCode& status) const {} + +UnicodeString& DecimalFormat::format(int64_t number, UnicodeString& appendTo, FieldPosition& pos) const {} + +UnicodeString& DecimalFormat::format(int64_t number, UnicodeString& appendTo, FieldPosition& pos, + UErrorCode& status) const {} + +UnicodeString& +DecimalFormat::format(int64_t number, UnicodeString& appendTo, FieldPositionIterator* posIter, + UErrorCode& status) const {} + +UnicodeString& +DecimalFormat::format(StringPiece number, UnicodeString& appendTo, FieldPositionIterator* posIter, + UErrorCode& status) const {} + +UnicodeString& +DecimalFormat::format(const DecimalQuantity& number, UnicodeString& appendTo, FieldPositionIterator* posIter, + UErrorCode& status) const {} + +UnicodeString& DecimalFormat::format(const DecimalQuantity& number, UnicodeString& appendTo, FieldPosition& pos, + UErrorCode& status) const {} + +void +DecimalFormat::parse(const UnicodeString& text, Formattable& result, ParsePosition& parsePosition) const {} + +CurrencyAmount* DecimalFormat::parseCurrency(const UnicodeString& text, ParsePosition& pos) const {} + +const DecimalFormatSymbols* DecimalFormat::getDecimalFormatSymbols(void) const {} + +void DecimalFormat::adoptDecimalFormatSymbols(DecimalFormatSymbols* symbolsToAdopt) {} + +void DecimalFormat::setDecimalFormatSymbols(const DecimalFormatSymbols& symbols) {} + +const CurrencyPluralInfo* DecimalFormat::getCurrencyPluralInfo(void) const {} + +void DecimalFormat::adoptCurrencyPluralInfo(CurrencyPluralInfo* toAdopt) {} + +void DecimalFormat::setCurrencyPluralInfo(const CurrencyPluralInfo& info) {} + +UnicodeString& DecimalFormat::getPositivePrefix(UnicodeString& result) const {} + +void DecimalFormat::setPositivePrefix(const UnicodeString& newValue) {} + +UnicodeString& DecimalFormat::getNegativePrefix(UnicodeString& result) const {} + +void DecimalFormat::setNegativePrefix(const UnicodeString& newValue) {} + +UnicodeString& DecimalFormat::getPositiveSuffix(UnicodeString& result) const {} + +void DecimalFormat::setPositiveSuffix(const UnicodeString& newValue) {} + +UnicodeString& DecimalFormat::getNegativeSuffix(UnicodeString& result) const {} + +void DecimalFormat::setNegativeSuffix(const UnicodeString& newValue) {} + +int32_t DecimalFormat::getMultiplier(void) const {} + +void DecimalFormat::setMultiplier(int32_t newValue) {} + +double DecimalFormat::getRoundingIncrement(void) const {} + +void DecimalFormat::setRoundingIncrement(double newValue) {} + +ERoundingMode DecimalFormat::getRoundingMode(void) const {} + +void DecimalFormat::setRoundingMode(ERoundingMode roundingMode) {} + +int32_t DecimalFormat::getFormatWidth(void) const {} + +void DecimalFormat::setFormatWidth(int32_t width) {} + +UnicodeString DecimalFormat::getPadCharacterString() const {} + +void DecimalFormat::setPadCharacter(const UnicodeString& padChar) {} + +EPadPosition DecimalFormat::getPadPosition(void) const {} + +void DecimalFormat::setPadPosition(EPadPosition padPos) {} + +UBool DecimalFormat::isScientificNotation(void) const {} + +void DecimalFormat::setScientificNotation(UBool useScientific) {} + +int8_t DecimalFormat::getMinimumExponentDigits(void) const {} + +void DecimalFormat::setMinimumExponentDigits(int8_t minExpDig) {} + +UBool DecimalFormat::isExponentSignAlwaysShown(void) const {} + +void DecimalFormat::setExponentSignAlwaysShown(UBool expSignAlways) {} + +int32_t DecimalFormat::getGroupingSize(void) const {} + +void DecimalFormat::setGroupingSize(int32_t newValue) {} + +int32_t DecimalFormat::getSecondaryGroupingSize(void) const {} + +void DecimalFormat::setSecondaryGroupingSize(int32_t newValue) {} + +int32_t DecimalFormat::getMinimumGroupingDigits() const {} + +void DecimalFormat::setMinimumGroupingDigits(int32_t newValue) {} + +UBool DecimalFormat::isDecimalSeparatorAlwaysShown(void) const {} + +void DecimalFormat::setDecimalSeparatorAlwaysShown(UBool newValue) {} + +UBool DecimalFormat::isDecimalPatternMatchRequired(void) const {} + +void DecimalFormat::setDecimalPatternMatchRequired(UBool newValue) {} + +UnicodeString& DecimalFormat::toPattern(UnicodeString& result) const {} + +UnicodeString& DecimalFormat::toLocalizedPattern(UnicodeString& result) const {} + +void +DecimalFormat::applyPattern(const UnicodeString& pattern, UParseError& parseError, UErrorCode& status) {} + +void DecimalFormat::applyPattern(const UnicodeString& pattern, UErrorCode& status) {} + +void DecimalFormat::applyLocalizedPattern(const UnicodeString& pattern, UParseError& parseError, + UErrorCode& status) {} + +void DecimalFormat::applyLocalizedPattern(const UnicodeString& pattern, UErrorCode& status) {} + +void DecimalFormat::setMaximumIntegerDigits(int32_t newValue) {} + +void DecimalFormat::setMinimumIntegerDigits(int32_t newValue) {} + +void DecimalFormat::setMaximumFractionDigits(int32_t newValue) {} + +void DecimalFormat::setMinimumFractionDigits(int32_t newValue) {} + +int32_t DecimalFormat::getMinimumSignificantDigits() const {} + +int32_t DecimalFormat::getMaximumSignificantDigits() const {} + +void DecimalFormat::setMinimumSignificantDigits(int32_t min) {} + +void DecimalFormat::setMaximumSignificantDigits(int32_t max) {} + +UBool DecimalFormat::areSignificantDigitsUsed() const {} + +void DecimalFormat::setSignificantDigitsUsed(UBool useSignificantDigits) {} + +void DecimalFormat::setCurrency(const char16_t* theCurrency, UErrorCode& ec) {} + +void DecimalFormat::setCurrency(const char16_t* theCurrency) {} + +void DecimalFormat::setCurrencyUsage(UCurrencyUsage newUsage, UErrorCode* ec) {} + +UCurrencyUsage DecimalFormat::getCurrencyUsage() const {} + +number::LocalizedNumberFormatter DecimalFormat::toNumberFormatter() const {} + +UClassID DecimalFormat::getStaticClassID() {} + +UClassID DecimalFormat::getDynamicClassID() const {} #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/fmtable.cpp b/icu4c/source/i18n/fmtable.cpp index c2a398d847..051eccf33d 100644 --- a/icu4c/source/i18n/fmtable.cpp +++ b/icu4c/source/i18n/fmtable.cpp @@ -29,8 +29,8 @@ #include "cmemory.h" #include "cstring.h" #include "decNumber.h" -#include "digitlst.h" #include "fmtableimp.h" +#include "number_decimalquantity.h" // ***************************************************************************** // class Formattable @@ -40,6 +40,8 @@ U_NAMESPACE_BEGIN UOBJECT_DEFINE_RTTI_IMPLEMENTATION(Formattable) +using number::impl::DecimalQuantity; + //-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. @@ -103,7 +105,7 @@ void Formattable::init() { fValue.fInt64 = 0; fType = kLong; fDecimalStr = NULL; - fDecimalNum = NULL; + fDecimalQuantity = NULL; fBogus.setToBogus(); } @@ -257,8 +259,8 @@ Formattable::operator=(const Formattable& source) } UErrorCode status = U_ZERO_ERROR; - if (source.fDecimalNum != NULL) { - fDecimalNum = new DigitList(*source.fDecimalNum); // TODO: use internal digit list + if (source.fDecimalQuantity != NULL) { + fDecimalQuantity = new DecimalQuantity(*source.fDecimalQuantity); } if (source.fDecimalStr != NULL) { fDecimalStr = new CharString(*source.fDecimalStr, status); @@ -356,14 +358,9 @@ void Formattable::dispose() delete fDecimalStr; fDecimalStr = NULL; - - FmtStackData *stackData = (FmtStackData*)fStackData; - if(fDecimalNum != &(stackData->stackDecimalNum)) { - delete fDecimalNum; - } else { - fDecimalNum->~DigitList(); // destruct, don't deallocate - } - fDecimalNum = NULL; + + delete fDecimalQuantity; + fDecimalQuantity = NULL; } Formattable * @@ -465,8 +462,8 @@ Formattable::getInt64(UErrorCode& status) const } else if (fValue.fDouble < (double)U_INT64_MIN) { status = U_INVALID_FORMAT_ERROR; return U_INT64_MIN; - } else if (fabs(fValue.fDouble) > U_DOUBLE_MAX_EXACT_INT && fDecimalNum != NULL) { - int64_t val = fDecimalNum->getInt64(); + } else if (fabs(fValue.fDouble) > U_DOUBLE_MAX_EXACT_INT && fDecimalQuantity != NULL) { + int64_t val = fDecimalQuantity->toLong(); if (val != 0) { return val; } else { @@ -714,27 +711,27 @@ StringPiece Formattable::getDecimalNumber(UErrorCode &status) { CharString *Formattable::internalGetCharString(UErrorCode &status) { if(fDecimalStr == NULL) { - if (fDecimalNum == NULL) { + if (fDecimalQuantity == NULL) { // No decimal number for the formattable yet. Which means the value was // set directly by the user as an int, int64 or double. If the value came // from parsing, or from the user setting a decimal number, fDecimalNum // would already be set. // - fDecimalNum = new DigitList; // TODO: use internal digit list - if (fDecimalNum == NULL) { + fDecimalQuantity = new DecimalQuantity(); // TODO: use internal digit list + if (fDecimalQuantity == NULL) { status = U_MEMORY_ALLOCATION_ERROR; return NULL; } switch (fType) { case kDouble: - fDecimalNum->set(this->getDouble()); + fDecimalQuantity->setToDouble(this->getDouble()); break; case kLong: - fDecimalNum->set(this->getLong()); + fDecimalQuantity->setToInt(this->getLong()); break; case kInt64: - fDecimalNum->set(this->getInt64()); + fDecimalQuantity->setToLong(this->getInt64()); break; default: // The formattable's value is not a numeric type. @@ -743,55 +740,48 @@ CharString *Formattable::internalGetCharString(UErrorCode &status) { } } - fDecimalStr = new CharString; + fDecimalStr = new CharString(); if (fDecimalStr == NULL) { status = U_MEMORY_ALLOCATION_ERROR; return NULL; } - fDecimalNum->getDecimal(*fDecimalStr, status); + UnicodeString result = fDecimalQuantity->toNumberString(); + for (int32_t i=0; iappend(static_cast(result[i]), status); + if (U_FAILURE(status)) { + return NULL; + } + } } return fDecimalStr; } - -DigitList * -Formattable::getInternalDigitList() { - FmtStackData *stackData = (FmtStackData*)fStackData; - if(fDecimalNum != &(stackData->stackDecimalNum)) { - delete fDecimalNum; - fDecimalNum = new (&(stackData->stackDecimalNum), kOnStack) DigitList(); - } else { - fDecimalNum->clear(); - } - return fDecimalNum; -} - // --------------------------------------- void -Formattable::adoptDigitList(DigitList *dl) { - if(fDecimalNum==dl) { - fDecimalNum = NULL; // don't delete - } - dispose(); - - fDecimalNum = dl; - - if(dl==NULL) { // allow adoptDigitList(NULL) to clear - return; - } +Formattable::adoptDecimalQuantity(DecimalQuantity *dq) { + if (fDecimalQuantity != NULL) { + delete fDecimalQuantity; + } + fDecimalQuantity = dq; + if (dq == NULL) { // allow adoptDigitList(NULL) to clear + return; + } // Set the value into the Union of simple type values. // Cannot use the set() functions because they would delete the fDecimalNum value, - - if (fDecimalNum->fitsIntoLong(FALSE)) { + // TODO: fDecimalQuantity->fitsInInt() to kLong type. + /* + if (fDecimalQuantity->fitsIntoLong(FALSE)) { fType = kLong; fValue.fInt64 = fDecimalNum->getLong(); - } else if (fDecimalNum->fitsIntoInt64(FALSE)) { + } else + */ + if (fDecimalQuantity->fitsInLong()) { fType = kInt64; - fValue.fInt64 = fDecimalNum->getInt64(); + fValue.fInt64 = fDecimalQuantity->toLong(); } else { fType = kDouble; - fValue.fDouble = fDecimalNum->getDouble(); + fValue.fDouble = fDecimalQuantity->toDouble(); } } @@ -804,24 +794,12 @@ Formattable::setDecimalNumber(StringPiece numberString, UErrorCode &status) { } dispose(); - // Copy the input string and nul-terminate it. - // The decNumber library requires nul-terminated input. StringPiece input - // is not guaranteed nul-terminated. Too bad. - // CharString automatically adds the nul. - DigitList *dnum = new DigitList(); // TODO: use getInternalDigitList - if (dnum == NULL) { - status = U_MEMORY_ALLOCATION_ERROR; - return; - } - dnum->set(CharString(numberString, status).toStringPiece(), status); - if (U_FAILURE(status)) { - delete dnum; - return; // String didn't contain a decimal number. - } - adoptDigitList(dnum); + DecimalQuantity* dq = new DecimalQuantity(); + dq->setToDecNumber(numberString); + adoptDecimalQuantity(dq); // Note that we do not hang on to the caller's input string. - // If we are asked for the string, we will regenerate one from fDecimalNum. + // If we are asked for the string, we will regenerate one from fDecimalQuantity. } #if 0 diff --git a/icu4c/source/i18n/fmtableimp.h b/icu4c/source/i18n/fmtableimp.h index 12cea9a440..2d18c6af7d 100644 --- a/icu4c/source/i18n/fmtableimp.h +++ b/icu4c/source/i18n/fmtableimp.h @@ -10,7 +10,7 @@ #ifndef FMTABLEIMP_H #define FMTABLEIMP_H -#include "digitlst.h" +#include "number_decimalquantity.h" #if !UCONFIG_NO_FORMATTING @@ -20,7 +20,7 @@ U_NAMESPACE_BEGIN * @internal */ struct FmtStackData { - DigitList stackDecimalNum; // 128 + icu::number::impl::DecimalQuantity stackDecimalNum; // 128 //CharString stackDecimalStr; // 64 // ----- // 192 total diff --git a/icu4c/source/i18n/msgfmt.cpp b/icu4c/source/i18n/msgfmt.cpp index c2be3f7bd1..57faaa9821 100644 --- a/icu4c/source/i18n/msgfmt.cpp +++ b/icu4c/source/i18n/msgfmt.cpp @@ -49,6 +49,7 @@ #include "util.h" #include "uvector.h" #include "visibledigits.h" +#include "number_decimalquantity.h" // ***************************************************************************** // class MessageFormat @@ -1961,13 +1962,12 @@ UnicodeString MessageFormat::PluralSelectorProvider::select(void *ctx, double nu context.formatter->format(context.number, context.numberString, ec); auto* decFmt = dynamic_cast(context.formatter); if(decFmt != NULL) { - const IFixedDecimal& dec = decFmt->toNumberFormatter() - .formatDouble(context.number.getDouble(ec), ec) - .getFixedDecimal(ec); + number::impl::DecimalQuantity dq; + decFmt->formatToDecimalQuantity(number, dq, ec); if (U_FAILURE(ec)) { return UnicodeString(FALSE, OTHER_STRING, 5); } - return rules->select(dec); + return rules->select(dq); } else { return rules->select(number); } diff --git a/icu4c/source/i18n/nfsubs.cpp b/icu4c/source/i18n/nfsubs.cpp index 4c17aa2818..81aa2e5ffd 100644 --- a/icu4c/source/i18n/nfsubs.cpp +++ b/icu4c/source/i18n/nfsubs.cpp @@ -19,8 +19,8 @@ #include "utypeinfo.h" // for 'typeid' to work #include "nfsubs.h" -#include "digitlst.h" #include "fmtableimp.h" +#include "number_decimalquantity.h" #if U_HAVE_RBNF @@ -47,6 +47,8 @@ static const UChar gGreaterGreaterThan[] = U_NAMESPACE_BEGIN +using number::impl::DecimalQuantity; + class SameValueSubstitution : public NFSubstitution { public: SameValueSubstitution(int32_t pos, @@ -1069,13 +1071,12 @@ FractionalPartSubstitution::doSubstitution(double number, UnicodeString& toInser // numberToFormat /= 10; // } - DigitList dl; - dl.set(number); - dl.roundFixedPoint(20); // round to 20 fraction digits. - dl.reduce(); // Removes any trailing zeros. + DecimalQuantity dl; + dl.setToDouble(number); + dl.roundToMagnitude(-20, UNUM_ROUND_HALFEVEN, status); // round to 20 fraction digits. UBool pad = FALSE; - for (int32_t didx = dl.getCount()-1; didx>=dl.getDecimalAt(); didx--) { + for (int32_t didx = dl.getLowerDisplayMagnitude(); didx<0; didx++) { // Loop iterates over fraction digits, starting with the LSD. // include both real digits from the number, and zeros // to the left of the MSD but to the right of the decimal point. @@ -1142,7 +1143,7 @@ FractionalPartSubstitution::doParse(const UnicodeString& text, int32_t digit; // double p10 = 0.1; - DigitList dl; + DecimalQuantity dl; NumberFormat* fmt = NULL; while (workText.length() > 0 && workPos.getIndex() != 0) { workPos.setIndex(0); @@ -1170,7 +1171,8 @@ FractionalPartSubstitution::doParse(const UnicodeString& text, } if (workPos.getIndex() != 0) { - dl.append((char)('0' + digit)); + // TODO(sffc): Make sure this is doing what it is supposed to do. + dl.appendDigit(static_cast(digit), 0, true); // result += digit * p10; // p10 /= 10; parsePosition.setIndex(parsePosition.getIndex() + workPos.getIndex()); @@ -1183,7 +1185,7 @@ FractionalPartSubstitution::doParse(const UnicodeString& text, } delete fmt; - result = dl.getCount() == 0 ? 0 : dl.getDouble(); + result = dl.toDouble(); result = composeRuleValue(result, baseValue); resVal.setDouble(result); return TRUE; diff --git a/icu4c/source/i18n/number_decimalquantity.h b/icu4c/source/i18n/number_decimalquantity.h index a104cc2d1f..155fdfef59 100644 --- a/icu4c/source/i18n/number_decimalquantity.h +++ b/icu4c/source/i18n/number_decimalquantity.h @@ -241,6 +241,10 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { /** Visible for testing */ inline bool isExplicitExactDouble() { return explicitExactDouble; }; + bool operator==(const DecimalQuantity& other) const; + + bool operator!=(const DecimalQuantity& other) const; + /** * Bogus flag for when a DecimalQuantity is stored on the stack. */ diff --git a/icu4c/source/i18n/numfmt.cpp b/icu4c/source/i18n/numfmt.cpp index 8ed71a580e..301b9855b1 100644 --- a/icu4c/source/i18n/numfmt.cpp +++ b/icu4c/source/i18n/numfmt.cpp @@ -51,10 +51,10 @@ #include "uassert.h" #include "umutex.h" #include "mutex.h" -#include "digitlst.h" #include #include "sharednumberformat.h" #include "unifiedcache.h" +#include "number_decimalquantity.h" //#define FMT_DEBUG @@ -524,7 +524,7 @@ ArgExtractor::ArgExtractor(const NumberFormat& /*nf*/, const Formattable& obj, U ArgExtractor::~ArgExtractor() { } -UnicodeString& NumberFormat::format(const DigitList &number, +UnicodeString& NumberFormat::format(const number::impl::DecimalQuantity &number, UnicodeString& appendTo, FieldPositionIterator* posIter, UErrorCode& status) const { @@ -534,7 +534,7 @@ UnicodeString& NumberFormat::format(const DigitList &number, if (U_FAILURE(status)) { return appendTo; } - double dnum = number.getDouble(); + double dnum = number.toDouble(); format(dnum, appendTo, posIter, status); return appendTo; } @@ -542,7 +542,7 @@ UnicodeString& NumberFormat::format(const DigitList &number, UnicodeString& -NumberFormat::format(const DigitList &number, +NumberFormat::format(const number::impl::DecimalQuantity &number, UnicodeString& appendTo, FieldPosition& pos, UErrorCode &status) const { @@ -552,7 +552,7 @@ NumberFormat::format(const DigitList &number, if (U_FAILURE(status)) { return appendTo; } - double dnum = number.getDouble(); + double dnum = number.toDouble(); format(dnum, appendTo, pos, status); return appendTo; } @@ -578,7 +578,7 @@ NumberFormat::format(const Formattable& obj, return cloneFmt->format(*n, appendTo, pos, status); } - if (n->isNumeric() && n->getDigitList() != NULL) { + if (n->isNumeric() && n->getDecimalQuantity() != NULL) { // Decimal Number. We will have a DigitList available if the value was // set to a decimal number, or if the value originated with a parse. // @@ -587,7 +587,7 @@ NumberFormat::format(const Formattable& obj, // know about DigitList to continue to operate as they had. // // DecimalFormat overrides the DigitList formatting functions. - format(*n->getDigitList(), appendTo, pos, status); + format(*n->getDecimalQuantity(), appendTo, pos, status); } else { switch (n->getType()) { case Formattable::kDouble: @@ -633,9 +633,9 @@ NumberFormat::format(const Formattable& obj, return cloneFmt->format(*n, appendTo, posIter, status); } - if (n->isNumeric() && n->getDigitList() != NULL) { + if (n->isNumeric() && n->getDecimalQuantity() != NULL) { // Decimal Number - format(*n->getDigitList(), appendTo, posIter, status); + format(*n->getDecimalQuantity(), appendTo, posIter, status); } else { switch (n->getType()) { case Formattable::kDouble: diff --git a/icu4c/source/i18n/plurrule.cpp b/icu4c/source/i18n/plurrule.cpp index 3da36d64b5..36d7bc7f98 100644 --- a/icu4c/source/i18n/plurrule.cpp +++ b/icu4c/source/i18n/plurrule.cpp @@ -22,7 +22,6 @@ #include "charstr.h" #include "cmemory.h" #include "cstring.h" -#include "digitlst.h" #include "hash.h" #include "locutil.h" #include "mutex.h" @@ -37,12 +36,14 @@ #include "unifiedcache.h" #include "digitinterval.h" #include "visibledigits.h" +#include "number_decimalquantity.h" #if !UCONFIG_NO_FORMATTING U_NAMESPACE_BEGIN using namespace icu::pluralimpl; +using icu::number::impl::DecimalQuantity; static const UChar PLURAL_KEYWORD_OTHER[]={LOW_O,LOW_T,LOW_H,LOW_E,LOW_R,0}; static const UChar PLURAL_DEFAULT_RULE[]={LOW_O,LOW_T,LOW_H,LOW_E,LOW_R,COLON,SPACE,LOW_N,0}; @@ -254,11 +255,10 @@ PluralRules::select(const Formattable& obj, const NumberFormat& fmt, UErrorCode& if (U_SUCCESS(status)) { const DecimalFormat *decFmt = dynamic_cast(&fmt); if (decFmt != NULL) { - const IFixedDecimal& dec = decFmt->toNumberFormatter() - .formatDouble(obj.getDouble(status), status) - .getFixedDecimal(status); + number::impl::DecimalQuantity dq; + decFmt->formatToDecimalQuantity(obj, dq, status); if (U_SUCCESS(status)) { - return select(dec); + return select(dq); } } else { double number = obj.getDouble(status); @@ -1477,14 +1477,14 @@ FixedDecimal::FixedDecimal() { FixedDecimal::FixedDecimal(const UnicodeString &num, UErrorCode &status) { CharString cs; cs.appendInvariantChars(num, status); - DigitList dl; - dl.set(cs.toStringPiece(), status); + DecimalQuantity dl; + dl.setToDecNumber(cs.toStringPiece()); if (U_FAILURE(status)) { init(0, 0, 0); return; } int32_t decimalPoint = num.indexOf(DOT); - double n = dl.getDouble(); + double n = dl.toDouble(); if (decimalPoint == -1) { init(n, 0, 0); } else { diff --git a/icu4c/source/i18n/plurrule_impl.h b/icu4c/source/i18n/plurrule_impl.h index 152c33e862..a07fc23e03 100644 --- a/icu4c/source/i18n/plurrule_impl.h +++ b/icu4c/source/i18n/plurrule_impl.h @@ -248,6 +248,10 @@ class U_I18N_API IFixedDecimal { virtual bool isNaN() const = 0; virtual bool isInfinite() const = 0; + + virtual bool hasIntegerValue() { + return getPluralOperand(PLURAL_OPERAND_N) == getPluralOperand(PLURAL_OPERAND_I); + } }; /** diff --git a/icu4c/source/i18n/quantityformatter.cpp b/icu4c/source/i18n/quantityformatter.cpp index eaf9221275..9ef607e50a 100644 --- a/icu4c/source/i18n/quantityformatter.cpp +++ b/icu4c/source/i18n/quantityformatter.cpp @@ -25,6 +25,7 @@ #include "standardplural.h" #include "visibledigits.h" #include "uassert.h" +#include "number_decimalquantity.h" U_NAMESPACE_BEGIN @@ -151,13 +152,12 @@ StandardPlural::Form QuantityFormatter::selectPlural( UnicodeString pluralKeyword; const DecimalFormat *decFmt = dynamic_cast(&fmt); if (decFmt != NULL) { - const IFixedDecimal& dec = decFmt->toNumberFormatter() - .formatDouble(number.getDouble(status), status) - .getFixedDecimal(status); + number::impl::DecimalQuantity dq; + decFmt->formatToDecimalQuantity(number, dq, status); if (U_FAILURE(status)) { return StandardPlural::OTHER; } - pluralKeyword = rules.select(dec); + pluralKeyword = rules.select(dq); decFmt->format(number, formattedNumber, pos, status); } else { if (number.getType() == Formattable::kDouble) { diff --git a/icu4c/source/i18n/rbnf.cpp b/icu4c/source/i18n/rbnf.cpp index 1b75e5ee1b..3cc208585f 100644 --- a/icu4c/source/i18n/rbnf.cpp +++ b/icu4c/source/i18n/rbnf.cpp @@ -34,7 +34,7 @@ #include "patternprops.h" #include "uresimp.h" #include "nfrs.h" -#include "digitlst.h" +#include "number_decimalquantity.h" // debugging // #define RBNF_DEBUG @@ -68,6 +68,8 @@ static const UChar gSemiPercent[] = U_NAMESPACE_BEGIN +using number::impl::DecimalQuantity; + UOBJECT_DEFINE_RTTI_IMPLEMENTATION(RuleBasedNumberFormat) /* @@ -1109,21 +1111,21 @@ RuleBasedNumberFormat::findRuleSet(const UnicodeString& name, UErrorCode& status } UnicodeString& -RuleBasedNumberFormat::format(const DigitList &number, +RuleBasedNumberFormat::format(const DecimalQuantity &number, UnicodeString &appendTo, FieldPositionIterator *posIter, UErrorCode &status) const { if (U_FAILURE(status)) { return appendTo; } - DigitList copy(number); - if (copy.fitsIntoInt64(false)) { - format(((DigitList &)number).getInt64(), appendTo, posIter, status); + DecimalQuantity copy(number); + if (copy.fitsInLong()) { + format(((DecimalQuantity &)number).toLong(), appendTo, posIter, status); } else { - copy.roundAtExponent(0); - if (copy.fitsIntoInt64(false)) { - format(number.getDouble(), appendTo, posIter, status); + copy.roundToMagnitude(0, number::impl::RoundingMode::UNUM_ROUND_HALFEVEN, status); + if (copy.fitsInLong()) { + format(number.toLong(), appendTo, posIter, status); } else { // We're outside of our normal range that this framework can handle. @@ -1132,7 +1134,7 @@ RuleBasedNumberFormat::format(const DigitList &number, // TODO this section should probably be optimized. The DecimalFormat is shared in ICU4J. NumberFormat *decimalFormat = NumberFormat::createInstance(locale, UNUM_DECIMAL, status); Formattable f; - f.adoptDigitList(new DigitList(number)); + f.adoptDecimalQuantity(new DecimalQuantity(number)); decimalFormat->format(f, appendTo, posIter, status); delete decimalFormat; } @@ -1142,21 +1144,21 @@ RuleBasedNumberFormat::format(const DigitList &number, UnicodeString& -RuleBasedNumberFormat::format(const DigitList &number, +RuleBasedNumberFormat::format(const DecimalQuantity &number, UnicodeString& appendTo, FieldPosition& pos, UErrorCode &status) const { if (U_FAILURE(status)) { return appendTo; } - DigitList copy(number); - if (copy.fitsIntoInt64(false)) { - format(((DigitList &)number).getInt64(), appendTo, pos, status); + DecimalQuantity copy(number); + if (copy.fitsInLong()) { + format(((DecimalQuantity &)number).toLong(), appendTo, pos, status); } else { - copy.roundAtExponent(0); - if (copy.fitsIntoInt64(false)) { - format(number.getDouble(), appendTo, pos, status); + copy.roundToMagnitude(0, number::impl::RoundingMode::UNUM_ROUND_HALFEVEN, status); + if (copy.fitsInLong()) { + format(number.toLong(), appendTo, pos, status); } else { // We're outside of our normal range that this framework can handle. @@ -1165,7 +1167,7 @@ RuleBasedNumberFormat::format(const DigitList &number, // TODO this section should probably be optimized. The DecimalFormat is shared in ICU4J. NumberFormat *decimalFormat = NumberFormat::createInstance(locale, UNUM_DECIMAL, status); Formattable f; - f.adoptDigitList(new DigitList(number)); + f.adoptDecimalQuantity(new DecimalQuantity(number)); decimalFormat->format(f, appendTo, pos, status); delete decimalFormat; } @@ -1270,11 +1272,13 @@ RuleBasedNumberFormat::format(double number, { int32_t startPos = toAppendTo.length(); if (getRoundingMode() != DecimalFormat::ERoundingMode::kRoundUnnecessary && !uprv_isNaN(number) && !uprv_isInfinite(number)) { - DigitList digitList; - digitList.set(number); - digitList.setRoundingMode(getRoundingMode()); - digitList.roundFixedPoint(getMaximumFractionDigits()); - number = digitList.getDouble(); + DecimalQuantity digitList; + digitList.setToDouble(number); + digitList.roundToMagnitude( + -getMaximumFractionDigits(), + static_cast(getRoundingMode()), + status); + number = digitList.toDouble(); } rs.format(number, toAppendTo, toAppendTo.length(), 0, status); adjustForCapitalizationContext(startPos, toAppendTo, status); @@ -1310,9 +1314,9 @@ RuleBasedNumberFormat::format(int64_t number, NFRuleSet *ruleSet, UnicodeString& NumberFormat *decimalFormat = NumberFormat::createInstance(locale, UNUM_DECIMAL, status); Formattable f; FieldPosition pos(FieldPosition::DONT_CARE); - DigitList *digitList = new DigitList(); - digitList->set(number); - f.adoptDigitList(digitList); + DecimalQuantity *digitList = new DecimalQuantity(); + digitList->setToLong(number); + f.adoptDecimalQuantity(digitList); decimalFormat->format(f, toAppendTo, pos, status); delete decimalFormat; } diff --git a/icu4c/source/i18n/unicode/compactdecimalformat.h b/icu4c/source/i18n/unicode/compactdecimalformat.h index 3fbe5da9ce..f5911176c8 100644 --- a/icu4c/source/i18n/unicode/compactdecimalformat.h +++ b/icu4c/source/i18n/unicode/compactdecimalformat.h @@ -76,7 +76,7 @@ public: * Destructor. * @stable ICU 51 */ - virtual ~CompactDecimalFormat(); + ~CompactDecimalFormat() U_OVERRIDE; /** * Assignment operator. @@ -86,289 +86,8 @@ public: */ CompactDecimalFormat& operator=(const CompactDecimalFormat& rhs); - /** - * Clone this Format object polymorphically. The caller owns the - * result and should delete it when done. - * - * @return a polymorphic copy of this CompactDecimalFormat. - * @stable ICU 51 - */ - virtual Format* clone() const; - - /** - * Return TRUE if the given Format objects are semantically equal. - * Objects of different subclasses are considered unequal. - * - * @param other the object to be compared with. - * @return TRUE if the given Format objects are semantically equal. - * @stable ICU 51 - */ - virtual UBool operator==(const Format& other) const; - - using DecimalFormat::format; - /** - * Format a double or long number using base-10 representation. - * - * @param number The value to be formatted. - * @param appendTo Output parameter to receive result. - * Result is appended to existing contents. - * @param pos On input: an alignment field, if desired. - * On output: the offsets of the alignment field. - * @return Reference to 'appendTo' parameter. - * @stable ICU 51 - */ - virtual UnicodeString& format(double number, - UnicodeString& appendTo, - FieldPosition& pos) const; - - /** - * Format a double or long number using base-10 representation. - * - * @param number The value to be formatted. - * @param appendTo Output parameter to receive result. - * Result is appended to existing contents. - * @param pos On input: an alignment field, if desired. - * On output: the offsets of the alignment field. - * @param status - * @return Reference to 'appendTo' parameter. - * @internal - */ - virtual UnicodeString& format(double number, - UnicodeString& appendTo, - FieldPosition& pos, - UErrorCode &status) const; - - /** - * Format a double or long number using base-10 representation. - * Currently sets status to U_UNSUPPORTED_ERROR. - * - * @param number The value to be formatted. - * @param appendTo Output parameter to receive result. - * Result is appended to existing contents. - * @param posIter On return, can be used to iterate over positions - * of fields generated by this format call. - * Can be NULL. - * @param status Output param filled with success/failure status. - * @return Reference to 'appendTo' parameter. - * @internal - */ - virtual UnicodeString& format(double number, - UnicodeString& appendTo, - FieldPositionIterator* posIter, - UErrorCode& status) const; - - /** - * Format a long number using base-10 representation. - * - * @param number The value to be formatted. - * @param appendTo Output parameter to receive result. - * Result is appended to existing contents. - * @param pos On input: an alignment field, if desired. - * On output: the offsets of the alignment field. - * @return Reference to 'appendTo' parameter. - * @stable ICU 56 - */ - virtual UnicodeString& format(int32_t number, - UnicodeString& appendTo, - FieldPosition& pos) const; - - /** - * Format a long number using base-10 representation. - * - * @param number The value to be formatted. - * @param appendTo Output parameter to receive result. - * Result is appended to existing contents. - * @param pos On input: an alignment field, if desired. - * On output: the offsets of the alignment field. - * @return Reference to 'appendTo' parameter. - * @internal - */ - virtual UnicodeString& format(int32_t number, - UnicodeString& appendTo, - FieldPosition& pos, - UErrorCode &status) const; - - /** - * Format a long number using base-10 representation. - * Currently sets status to U_UNSUPPORTED_ERROR - * - * @param number The value to be formatted. - * @param appendTo Output parameter to receive result. - * Result is appended to existing contents. - * @param posIter On return, can be used to iterate over positions - * of fields generated by this format call. - * Can be NULL. - * @param status Output param filled with success/failure status. - * @return Reference to 'appendTo' parameter. - * @internal - */ - virtual UnicodeString& format(int32_t number, - UnicodeString& appendTo, - FieldPositionIterator* posIter, - UErrorCode& status) const; - - /** - * Format an int64 number using base-10 representation. - * - * @param number The value to be formatted. - * @param appendTo Output parameter to receive result. - * Result is appended to existing contents. - * @param pos On input: an alignment field, if desired. - * On output: the offsets of the alignment field. - * @return Reference to 'appendTo' parameter. - * @stable ICU 51 - */ - virtual UnicodeString& format(int64_t number, - UnicodeString& appendTo, - FieldPosition& pos) const; - - /** - * Format an int64 number using base-10 representation. - * - * @param number The value to be formatted. - * @param appendTo Output parameter to receive result. - * Result is appended to existing contents. - * @param pos On input: an alignment field, if desired. - * On output: the offsets of the alignment field. - * @return Reference to 'appendTo' parameter. - * @internal - */ - virtual UnicodeString& format(int64_t number, - UnicodeString& appendTo, - FieldPosition& pos, - UErrorCode &status) const; - - /** - * Format an int64 number using base-10 representation. - * Currently sets status to U_UNSUPPORTED_ERROR - * - * @param number The value to be formatted. - * @param appendTo Output parameter to receive result. - * Result is appended to existing contents. - * @param posIter On return, can be used to iterate over positions - * of fields generated by this format call. - * Can be NULL. - * @param status Output param filled with success/failure status. - * @return Reference to 'appendTo' parameter. - * @internal - */ - virtual UnicodeString& format(int64_t number, - UnicodeString& appendTo, - FieldPositionIterator* posIter, - UErrorCode& status) const; - - /** - * Format a decimal number. Currently sets status to U_UNSUPPORTED_ERROR - * The syntax of the unformatted number is a "numeric string" - * as defined in the Decimal Arithmetic Specification, available at - * http://speleotrove.com/decimal - * - * @param number The unformatted number, as a string. - * @param appendTo Output parameter to receive result. - * Result is appended to existing contents. - * @param posIter On return, can be used to iterate over positions - * of fields generated by this format call. - * Can be NULL. - * @param status Output param filled with success/failure status. - * @return Reference to 'appendTo' parameter. - * @internal - */ - virtual UnicodeString& format(StringPiece number, - UnicodeString& appendTo, - FieldPositionIterator* posIter, - UErrorCode& status) const; - - /** - * Format a decimal number. Currently sets status to U_UNSUPPORTED_ERROR - * The number is a DigitList wrapper onto a floating point decimal number. - * The default implementation in NumberFormat converts the decimal number - * to a double and formats that. - * - * @param number The number, a DigitList format Decimal Floating Point. - * @param appendTo Output parameter to receive result. - * Result is appended to existing contents. - * @param posIter On return, can be used to iterate over positions - * of fields generated by this format call. - * @param status Output param filled with success/failure status. - * @return Reference to 'appendTo' parameter. - * @internal - */ - virtual UnicodeString& format(const DigitList &number, - UnicodeString& appendTo, - FieldPositionIterator* posIter, - UErrorCode& status) const; - - /** - * Format a decimal number. Currently sets status to U_UNSUPPORTED_ERROR. - * The number is a DigitList wrapper onto a floating point decimal number. - * The default implementation in NumberFormat converts the decimal number - * to a double and formats that. - * - * @param number The number, a DigitList format Decimal Floating Point. - * @param appendTo Output parameter to receive result. - * Result is appended to existing contents. - * @param pos On input: an alignment field, if desired. - * On output: the offsets of the alignment field. - * @param status Output param filled with success/failure status. - * @return Reference to 'appendTo' parameter. - * @internal - */ - virtual UnicodeString& format(const DigitList &number, - UnicodeString& appendTo, - FieldPosition& pos, - UErrorCode& status) const; - - /** - * CompactDecimalFormat does not support parsing. This implementation - * does nothing. - * @param text Unused. - * @param result Does not change. - * @param parsePosition Does not change. - * @see Formattable - * @stable ICU 51 - */ - virtual void parse(const UnicodeString& text, - Formattable& result, - ParsePosition& parsePosition) const; - - /** - * CompactDecimalFormat does not support parsing. This implementation - * sets status to U_UNSUPPORTED_ERROR - * - * @param text Unused. - * @param result Does not change. - * @param status Always set to U_UNSUPPORTED_ERROR. - * @stable ICU 51 - */ - virtual void parse(const UnicodeString& text, - Formattable& result, - UErrorCode& status) const; - - /** - * Parses text from the given string as a currency amount. Unlike - * the parse() method, this method will attempt to parse a generic - * currency name, searching for a match of this object's locale's - * currency display names, or for a 3-letter ISO currency code. - * This method will fail if this format is not a currency format, - * that is, if it does not contain the currency pattern symbol - * (U+00A4) in its prefix or suffix. This implementation always returns - * NULL. - * - * @param text the string to parse - * @param pos input-output position; on input, the position within text - * to match; must have 0 <= pos.getIndex() < text.length(); - * on output, the position after the last matched character. - * If the parse fails, the position in unchanged upon output. - * @return if parse succeeds, a pointer to a newly-created CurrencyAmount - * object (owned by the caller) containing information about - * the parsed currency; if parse fails, this is NULL. - * @internal - */ - virtual CurrencyAmount* parseCurrency(const UnicodeString& text, - ParsePosition& pos) const; - /** * Return the class ID for this class. This is useful only for * comparing to a return value from getDynamicClassID(). For example: @@ -394,17 +113,6 @@ public: * @stable ICU 51 */ virtual UClassID getDynamicClassID() const; - -private: - - const UHashtable* _unitsByVariant; - const double* _divisors; - PluralRules* _pluralRules; - - // Default constructor not implemented. - CompactDecimalFormat(const DecimalFormat &, const UHashtable* unitsByVariant, const double* divisors, PluralRules* pluralRules); - - UBool eqHelper(const CompactDecimalFormat& that) const; }; U_NAMESPACE_END diff --git a/icu4c/source/i18n/unicode/decimfmt.h b/icu4c/source/i18n/unicode/decimfmt.h index dbd01a347d..1fe5d99d32 100644 --- a/icu4c/source/i18n/unicode/decimfmt.h +++ b/icu4c/source/i18n/unicode/decimfmt.h @@ -67,6 +67,12 @@ class DecimalFormatImpl; class PluralRules; class VisibleDigitsWithExponent; +namespace number { +namespace impl { +class DecimalQuantity; +} +} + // explicit template instantiation. see digitlst.h // (When building DLLs for Windows this is required.) #if U_PF_WINDOWS <= U_PLATFORM && U_PLATFORM <= U_PF_CYGWIN @@ -1084,11 +1090,11 @@ public: /** * Format a decimal number. - * The number is a DigitList wrapper onto a floating point decimal number. + * The number is a DecimalQuantity wrapper onto a floating point decimal number. * The default implementation in NumberFormat converts the decimal number * to a double and formats that. * - * @param number The number, a DigitList format Decimal Floating Point. + * @param number The number, a DecimalQuantity format Decimal Floating Point. * @param appendTo Output parameter to receive result. * Result is appended to existing contents. * @param posIter On return, can be used to iterate over positions @@ -1097,50 +1103,18 @@ public: * @return Reference to 'appendTo' parameter. * @internal */ - UnicodeString& format(const DigitList &number, + UnicodeString& format(const number::impl::DecimalQuantity &number, UnicodeString& appendTo, FieldPositionIterator* posIter, UErrorCode& status) const U_OVERRIDE; /** * Format a decimal number. - * @param number The number - * @param appendTo Output parameter to receive result. - * Result is appended to existing contents. - * @param pos On input: an alignment field, if desired. - * On output: the offsets of the alignment field. - * @param status Output param filled with success/failure status. - * @return Reference to 'appendTo' parameter. - * @internal - */ - virtual UnicodeString& format(const VisibleDigitsWithExponent &number, - UnicodeString& appendTo, - FieldPosition& pos, - UErrorCode& status) const; - - /** - * Format a decimal number. - * @param number The number - * @param appendTo Output parameter to receive result. - * Result is appended to existing contents. - * @param posIter On return, can be used to iterate over positions - * of fields generated by this format call. - * @param status Output param filled with success/failure status. - * @return Reference to 'appendTo' parameter. - * @internal - */ - virtual UnicodeString& format(const VisibleDigitsWithExponent &number, - UnicodeString& appendTo, - FieldPositionIterator* posIter, - UErrorCode& status) const; - - /** - * Format a decimal number. - * The number is a DigitList wrapper onto a floating point decimal number. + * The number is a DecimalQuantity wrapper onto a floating point decimal number. * The default implementation in NumberFormat converts the decimal number * to a double and formats that. * - * @param number The number, a DigitList format Decimal Floating Point. + * @param number The number, a DecimalQuantity format Decimal Floating Point. * @param appendTo Output parameter to receive result. * Result is appended to existing contents. * @param pos On input: an alignment field, if desired. @@ -1149,7 +1123,7 @@ public: * @return Reference to 'appendTo' parameter. * @internal */ - UnicodeString& format(const DigitList &number, + UnicodeString& format(const number::impl::DecimalQuantity &number, UnicodeString& appendTo, FieldPosition& pos, UErrorCode& status) const U_OVERRIDE; @@ -1967,6 +1941,25 @@ public: */ UCurrencyUsage getCurrencyUsage() const; +#ifndef U_HIDE_INTERNAL_API + /** + * Format a number and save it into the given DecimalQuantity. + * Internal, not intended for public use. + * @internal + */ + void formatToDecimalQuantity(double number, number::impl::DecimalQuantity& output, + UErrorCode& status) const; + + /** + * Get a DecimalQuantity corresponding to a formattable as it would be + * formatted by this DecimalFormat. + * Internal, not intended for public use. + * @internal + */ + void formatToDecimalQuantity(const Formattable& number, number::impl::DecimalQuantity& output, + UErrorCode& status) const; +#endif + /** * Converts this DecimalFormat to a NumberFormatter. Starting in ICU 60, * NumberFormatter is the recommended way to format numbers. diff --git a/icu4c/source/i18n/unicode/fmtable.h b/icu4c/source/i18n/unicode/fmtable.h index 766a71969d..6727803911 100644 --- a/icu4c/source/i18n/unicode/fmtable.h +++ b/icu4c/source/i18n/unicode/fmtable.h @@ -33,7 +33,11 @@ U_NAMESPACE_BEGIN class CharString; -class DigitList; +namespace number { +namespace impl { +class DecimalQuantity; +} +} /** * \def UNUM_INTERNAL_STACKARRAY_SIZE @@ -649,24 +653,19 @@ public: * Internal function, do not use. * TODO: figure out how to make this be non-public. * NumberFormat::format(Formattable, ... - * needs to get at the DigitList, if it exists, for + * needs to get at the DecimalQuantity, if it exists, for * big decimal formatting. * @internal */ - DigitList *getDigitList() const { return fDecimalNum;} + number::impl::DecimalQuantity *getDecimalQuantity() const { return fDecimalQuantity;} /** - * @internal - */ - DigitList *getInternalDigitList(); - - /** - * Adopt, and set value from, a DigitList + * Adopt, and set value from, a DecimalQuantity * Internal Function, do not use. - * @param dl the Digit List to be adopted + * @param dl the DecimalQuantity to be adopted * @internal */ - void adoptDigitList(DigitList *dl); + void adoptDecimalQuantity(number::impl::DecimalQuantity *dq); /** * Internal function to return the CharString pointer. @@ -706,9 +705,7 @@ private: CharString *fDecimalStr; - DigitList *fDecimalNum; - - char fStackData[UNUM_INTERNAL_STACKARRAY_SIZE]; // must be big enough for DigitList + number::impl::DecimalQuantity *fDecimalQuantity; Type fType; UnicodeString fBogus; // Bogus string when it's needed. diff --git a/icu4c/source/i18n/unicode/numfmt.h b/icu4c/source/i18n/unicode/numfmt.h index 4a8e049f93..8184812ef7 100644 --- a/icu4c/source/i18n/unicode/numfmt.h +++ b/icu4c/source/i18n/unicode/numfmt.h @@ -558,13 +558,13 @@ public: public: /** * Format a decimal number. - * The number is a DigitList wrapper onto a floating point decimal number. + * The number is a DecimalQuantity wrapper onto a floating point decimal number. * The default implementation in NumberFormat converts the decimal number * to a double and formats that. Subclasses of NumberFormat that want * to specifically handle big decimal numbers must override this method. * class DecimalFormat does so. * - * @param number The number, a DigitList format Decimal Floating Point. + * @param number The number, a DecimalQuantity format Decimal Floating Point. * @param appendTo Output parameter to receive result. * Result is appended to existing contents. * @param posIter On return, can be used to iterate over positions @@ -573,20 +573,20 @@ public: * @return Reference to 'appendTo' parameter. * @internal */ - virtual UnicodeString& format(const DigitList &number, + virtual UnicodeString& format(const number::impl::DecimalQuantity &number, UnicodeString& appendTo, FieldPositionIterator* posIter, UErrorCode& status) const; /** * Format a decimal number. - * The number is a DigitList wrapper onto a floating point decimal number. + * The number is a DecimalQuantity wrapper onto a floating point decimal number. * The default implementation in NumberFormat converts the decimal number * to a double and formats that. Subclasses of NumberFormat that want * to specifically handle big decimal numbers must override this method. * class DecimalFormat does so. * - * @param number The number, a DigitList format Decimal Floating Point. + * @param number The number, a DecimalQuantity format Decimal Floating Point. * @param appendTo Output parameter to receive result. * Result is appended to existing contents. * @param pos On input: an alignment field, if desired. @@ -595,7 +595,7 @@ public: * @return Reference to 'appendTo' parameter. * @internal */ - virtual UnicodeString& format(const DigitList &number, + virtual UnicodeString& format(const number::impl::DecimalQuantity &number, UnicodeString& appendTo, FieldPosition& pos, UErrorCode& status) const; diff --git a/icu4c/source/i18n/unicode/rbnf.h b/icu4c/source/i18n/unicode/rbnf.h index 12925443b2..ab5a019de3 100644 --- a/icu4c/source/i18n/unicode/rbnf.h +++ b/icu4c/source/i18n/unicode/rbnf.h @@ -884,7 +884,7 @@ protected: * @return Reference to 'appendTo' parameter. * @internal */ - virtual UnicodeString& format(const DigitList &number, + virtual UnicodeString& format(const number::impl::DecimalQuantity &number, UnicodeString& appendTo, FieldPositionIterator* posIter, UErrorCode& status) const; @@ -906,7 +906,7 @@ protected: * @return Reference to 'appendTo' parameter. * @internal */ - virtual UnicodeString& format(const DigitList &number, + virtual UnicodeString& format(const number::impl::DecimalQuantity &number, UnicodeString& appendTo, FieldPosition& pos, UErrorCode& status) const; diff --git a/icu4c/source/test/intltest/dcfmapts.cpp b/icu4c/source/test/intltest/dcfmapts.cpp index b4639f73a4..cf854de406 100644 --- a/icu4c/source/test/intltest/dcfmapts.cpp +++ b/icu4c/source/test/intltest/dcfmapts.cpp @@ -23,6 +23,7 @@ #include "putilimp.h" #include "plurrule_impl.h" #include +#include // This is an API test, not a unit test. It doesn't test very many cases, and doesn't // try to test the full functionality. It just calls each function in the class and @@ -613,171 +614,172 @@ void IntlTestDecimalFormatAPI::TestFixedDecimal() { if (status == U_MISSING_RESOURCE_ERROR) { return; } - FixedDecimal fd = df->getFixedDecimal(44, status); + number::impl::DecimalQuantity fd; + df->formatToDecimalQuantity(44, fd, status); TEST_ASSERT_STATUS(status); - ASSERT_EQUAL(44, fd.source); - ASSERT_EQUAL(0, fd.visibleDecimalDigitCount); - ASSERT_EQUAL(FALSE, fd.isNegative); + ASSERT_EQUAL(44, fd.getPluralOperand(PLURAL_OPERAND_N)); + ASSERT_EQUAL(0, fd.getPluralOperand(PLURAL_OPERAND_V)); + ASSERT_EQUAL(FALSE, fd.isNegative()); - fd = df->getFixedDecimal(-44, status); + df->formatToDecimalQuantity(-44, fd, status); TEST_ASSERT_STATUS(status); - ASSERT_EQUAL(44, fd.source); - ASSERT_EQUAL(0, fd.visibleDecimalDigitCount); - ASSERT_EQUAL(TRUE, fd.isNegative); + ASSERT_EQUAL(44, fd.getPluralOperand(PLURAL_OPERAND_N)); + ASSERT_EQUAL(0, fd.getPluralOperand(PLURAL_OPERAND_V)); + ASSERT_EQUAL(TRUE, fd.isNegative()); df.adoptInsteadAndCheckErrorCode(new DecimalFormat("###.00##", status), status); TEST_ASSERT_STATUS(status); - fd = df->getFixedDecimal(123.456, status); + df->formatToDecimalQuantity(123.456, fd, status); TEST_ASSERT_STATUS(status); - ASSERT_EQUAL(3, fd.visibleDecimalDigitCount); // v - ASSERT_EQUAL(456, fd.decimalDigits); // f - ASSERT_EQUAL(456, fd.decimalDigitsWithoutTrailingZeros); // t - ASSERT_EQUAL(123, fd.intValue); // i - ASSERT_EQUAL(123.456, fd.source); // n - ASSERT_EQUAL(FALSE, fd.hasIntegerValue); - ASSERT_EQUAL(FALSE, fd.isNegative); + ASSERT_EQUAL(3, fd.getPluralOperand(PLURAL_OPERAND_V)); // v + ASSERT_EQUAL(456, fd.getPluralOperand(PLURAL_OPERAND_F)); // f + ASSERT_EQUAL(456, fd.getPluralOperand(PLURAL_OPERAND_T)); // t + ASSERT_EQUAL(123, fd.getPluralOperand(PLURAL_OPERAND_I)); // i + ASSERT_EQUAL(123.456, fd.getPluralOperand(PLURAL_OPERAND_N)); // n + ASSERT_EQUAL(FALSE, fd.hasIntegerValue()); + ASSERT_EQUAL(FALSE, fd.isNegative()); - fd = df->getFixedDecimal(-123.456, status); + df->formatToDecimalQuantity(-123.456, fd, status); TEST_ASSERT_STATUS(status); - ASSERT_EQUAL(3, fd.visibleDecimalDigitCount); // v - ASSERT_EQUAL(456, fd.decimalDigits); // f - ASSERT_EQUAL(456, fd.decimalDigitsWithoutTrailingZeros); // t - ASSERT_EQUAL(123, fd.intValue); // i - ASSERT_EQUAL(123.456, fd.source); // n - ASSERT_EQUAL(FALSE, fd.hasIntegerValue); - ASSERT_EQUAL(TRUE, fd.isNegative); + ASSERT_EQUAL(3, fd.getPluralOperand(PLURAL_OPERAND_V)); // v + ASSERT_EQUAL(456, fd.getPluralOperand(PLURAL_OPERAND_F)); // f + ASSERT_EQUAL(456, fd.getPluralOperand(PLURAL_OPERAND_T)); // t + ASSERT_EQUAL(123, fd.getPluralOperand(PLURAL_OPERAND_I)); // i + ASSERT_EQUAL(123.456, fd.getPluralOperand(PLURAL_OPERAND_N)); // n + ASSERT_EQUAL(FALSE, fd.hasIntegerValue()); + ASSERT_EQUAL(TRUE, fd.isNegative()); // test max int digits df->setMaximumIntegerDigits(2); - fd = df->getFixedDecimal(123.456, status); + df->formatToDecimalQuantity(123.456, fd, status); TEST_ASSERT_STATUS(status); - ASSERT_EQUAL(3, fd.visibleDecimalDigitCount); // v - ASSERT_EQUAL(456, fd.decimalDigits); // f - ASSERT_EQUAL(456, fd.decimalDigitsWithoutTrailingZeros); // t - ASSERT_EQUAL(23, fd.intValue); // i - ASSERT_EQUAL(23.456, fd.source); // n - ASSERT_EQUAL(FALSE, fd.hasIntegerValue); - ASSERT_EQUAL(FALSE, fd.isNegative); + ASSERT_EQUAL(3, fd.getPluralOperand(PLURAL_OPERAND_V)); // v + ASSERT_EQUAL(456, fd.getPluralOperand(PLURAL_OPERAND_F)); // f + ASSERT_EQUAL(456, fd.getPluralOperand(PLURAL_OPERAND_T)); // t + ASSERT_EQUAL(23, fd.getPluralOperand(PLURAL_OPERAND_I)); // i + ASSERT_EQUAL(23.456, fd.getPluralOperand(PLURAL_OPERAND_N)); // n + ASSERT_EQUAL(FALSE, fd.hasIntegerValue()); + ASSERT_EQUAL(FALSE, fd.isNegative()); - fd = df->getFixedDecimal(-123.456, status); + df->formatToDecimalQuantity(-123.456, fd, status); TEST_ASSERT_STATUS(status); - ASSERT_EQUAL(3, fd.visibleDecimalDigitCount); // v - ASSERT_EQUAL(456, fd.decimalDigits); // f - ASSERT_EQUAL(456, fd.decimalDigitsWithoutTrailingZeros); // t - ASSERT_EQUAL(23, fd.intValue); // i - ASSERT_EQUAL(23.456, fd.source); // n - ASSERT_EQUAL(FALSE, fd.hasIntegerValue); - ASSERT_EQUAL(TRUE, fd.isNegative); + ASSERT_EQUAL(3, fd.getPluralOperand(PLURAL_OPERAND_V)); // v + ASSERT_EQUAL(456, fd.getPluralOperand(PLURAL_OPERAND_F)); // f + ASSERT_EQUAL(456, fd.getPluralOperand(PLURAL_OPERAND_T)); // t + ASSERT_EQUAL(23, fd.getPluralOperand(PLURAL_OPERAND_I)); // i + ASSERT_EQUAL(23.456, fd.getPluralOperand(PLURAL_OPERAND_N)); // n + ASSERT_EQUAL(FALSE, fd.hasIntegerValue()); + ASSERT_EQUAL(TRUE, fd.isNegative()); // test max fraction digits df->setMaximumIntegerDigits(2000000000); df->setMaximumFractionDigits(2); - fd = df->getFixedDecimal(123.456, status); + df->formatToDecimalQuantity(123.456, fd, status); TEST_ASSERT_STATUS(status); - ASSERT_EQUAL(2, fd.visibleDecimalDigitCount); // v - ASSERT_EQUAL(46, fd.decimalDigits); // f - ASSERT_EQUAL(46, fd.decimalDigitsWithoutTrailingZeros); // t - ASSERT_EQUAL(123, fd.intValue); // i - ASSERT_EQUAL(123.46, fd.source); // n - ASSERT_EQUAL(FALSE, fd.hasIntegerValue); - ASSERT_EQUAL(FALSE, fd.isNegative); + ASSERT_EQUAL(2, fd.getPluralOperand(PLURAL_OPERAND_V)); // v + ASSERT_EQUAL(46, fd.getPluralOperand(PLURAL_OPERAND_F)); // f + ASSERT_EQUAL(46, fd.getPluralOperand(PLURAL_OPERAND_T)); // t + ASSERT_EQUAL(123, fd.getPluralOperand(PLURAL_OPERAND_I)); // i + ASSERT_EQUAL(123.46, fd.getPluralOperand(PLURAL_OPERAND_N)); // n + ASSERT_EQUAL(FALSE, fd.hasIntegerValue()); + ASSERT_EQUAL(FALSE, fd.isNegative()); - fd = df->getFixedDecimal(-123.456, status); + df->formatToDecimalQuantity(-123.456, fd, status); TEST_ASSERT_STATUS(status); - ASSERT_EQUAL(2, fd.visibleDecimalDigitCount); // v - ASSERT_EQUAL(46, fd.decimalDigits); // f - ASSERT_EQUAL(46, fd.decimalDigitsWithoutTrailingZeros); // t - ASSERT_EQUAL(123, fd.intValue); // i - ASSERT_EQUAL(123.46, fd.source); // n - ASSERT_EQUAL(FALSE, fd.hasIntegerValue); - ASSERT_EQUAL(TRUE, fd.isNegative); + ASSERT_EQUAL(2, fd.getPluralOperand(PLURAL_OPERAND_V)); // v + ASSERT_EQUAL(46, fd.getPluralOperand(PLURAL_OPERAND_F)); // f + ASSERT_EQUAL(46, fd.getPluralOperand(PLURAL_OPERAND_T)); // t + ASSERT_EQUAL(123, fd.getPluralOperand(PLURAL_OPERAND_I)); // i + ASSERT_EQUAL(123.46, fd.getPluralOperand(PLURAL_OPERAND_N)); // n + ASSERT_EQUAL(FALSE, fd.hasIntegerValue()); + ASSERT_EQUAL(TRUE, fd.isNegative()); // test esoteric rounding df->setMaximumFractionDigits(6); df->setRoundingIncrement(7.3); - fd = df->getFixedDecimal(30.0, status); + df->formatToDecimalQuantity(30.0, fd, status); TEST_ASSERT_STATUS(status); - ASSERT_EQUAL(2, fd.visibleDecimalDigitCount); // v - ASSERT_EQUAL(20, fd.decimalDigits); // f - ASSERT_EQUAL(2, fd.decimalDigitsWithoutTrailingZeros); // t - ASSERT_EQUAL(29, fd.intValue); // i - ASSERT_EQUAL(29.2, fd.source); // n - ASSERT_EQUAL(FALSE, fd.hasIntegerValue); - ASSERT_EQUAL(FALSE, fd.isNegative); + ASSERT_EQUAL(2, fd.getPluralOperand(PLURAL_OPERAND_V)); // v + ASSERT_EQUAL(20, fd.getPluralOperand(PLURAL_OPERAND_F)); // f + ASSERT_EQUAL(2, fd.getPluralOperand(PLURAL_OPERAND_T)); // t + ASSERT_EQUAL(29, fd.getPluralOperand(PLURAL_OPERAND_I)); // i + ASSERT_EQUAL(29.2, fd.getPluralOperand(PLURAL_OPERAND_N)); // n + ASSERT_EQUAL(FALSE, fd.hasIntegerValue()); + ASSERT_EQUAL(FALSE, fd.isNegative()); - fd = df->getFixedDecimal(-30.0, status); + df->formatToDecimalQuantity(-30.0, fd, status); TEST_ASSERT_STATUS(status); - ASSERT_EQUAL(2, fd.visibleDecimalDigitCount); // v - ASSERT_EQUAL(20, fd.decimalDigits); // f - ASSERT_EQUAL(2, fd.decimalDigitsWithoutTrailingZeros); // t - ASSERT_EQUAL(29, fd.intValue); // i - ASSERT_EQUAL(29.2, fd.source); // n - ASSERT_EQUAL(FALSE, fd.hasIntegerValue); - ASSERT_EQUAL(TRUE, fd.isNegative); + ASSERT_EQUAL(2, fd.getPluralOperand(PLURAL_OPERAND_V)); // v + ASSERT_EQUAL(20, fd.getPluralOperand(PLURAL_OPERAND_F)); // f + ASSERT_EQUAL(2, fd.getPluralOperand(PLURAL_OPERAND_T)); // t + ASSERT_EQUAL(29, fd.getPluralOperand(PLURAL_OPERAND_I)); // i + ASSERT_EQUAL(29.2, fd.getPluralOperand(PLURAL_OPERAND_N)); // n + ASSERT_EQUAL(FALSE, fd.hasIntegerValue()); + ASSERT_EQUAL(TRUE, fd.isNegative()); df.adoptInsteadAndCheckErrorCode(new DecimalFormat("###", status), status); TEST_ASSERT_STATUS(status); - fd = df->getFixedDecimal(123.456, status); + df->formatToDecimalQuantity(123.456, fd, status); TEST_ASSERT_STATUS(status); - ASSERT_EQUAL(0, fd.visibleDecimalDigitCount); - ASSERT_EQUAL(0, fd.decimalDigits); - ASSERT_EQUAL(0, fd.decimalDigitsWithoutTrailingZeros); - ASSERT_EQUAL(123, fd.intValue); - ASSERT_EQUAL(TRUE, fd.hasIntegerValue); - ASSERT_EQUAL(FALSE, fd.isNegative); + ASSERT_EQUAL(0, fd.getPluralOperand(PLURAL_OPERAND_V)); + ASSERT_EQUAL(0, fd.getPluralOperand(PLURAL_OPERAND_F)); + ASSERT_EQUAL(0, fd.getPluralOperand(PLURAL_OPERAND_T)); + ASSERT_EQUAL(123, fd.getPluralOperand(PLURAL_OPERAND_I)); + ASSERT_EQUAL(TRUE, fd.hasIntegerValue()); + ASSERT_EQUAL(FALSE, fd.isNegative()); df.adoptInsteadAndCheckErrorCode(new DecimalFormat("###.0", status), status); TEST_ASSERT_STATUS(status); - fd = df->getFixedDecimal(123.01, status); + df->formatToDecimalQuantity(123.01, fd, status); TEST_ASSERT_STATUS(status); - ASSERT_EQUAL(1, fd.visibleDecimalDigitCount); - ASSERT_EQUAL(0, fd.decimalDigits); - ASSERT_EQUAL(0, fd.decimalDigitsWithoutTrailingZeros); - ASSERT_EQUAL(123, fd.intValue); - ASSERT_EQUAL(TRUE, fd.hasIntegerValue); - ASSERT_EQUAL(FALSE, fd.isNegative); + ASSERT_EQUAL(1, fd.getPluralOperand(PLURAL_OPERAND_V)); + ASSERT_EQUAL(0, fd.getPluralOperand(PLURAL_OPERAND_F)); + ASSERT_EQUAL(0, fd.getPluralOperand(PLURAL_OPERAND_T)); + ASSERT_EQUAL(123, fd.getPluralOperand(PLURAL_OPERAND_I)); + ASSERT_EQUAL(TRUE, fd.hasIntegerValue()); + ASSERT_EQUAL(FALSE, fd.isNegative()); df.adoptInsteadAndCheckErrorCode(new DecimalFormat("###.0", status), status); TEST_ASSERT_STATUS(status); - fd = df->getFixedDecimal(123.06, status); + df->formatToDecimalQuantity(123.06, fd, status); TEST_ASSERT_STATUS(status); - ASSERT_EQUAL(1, fd.visibleDecimalDigitCount); - ASSERT_EQUAL(1, fd.decimalDigits); - ASSERT_EQUAL(1, fd.decimalDigitsWithoutTrailingZeros); - ASSERT_EQUAL(123, fd.intValue); - ASSERT_EQUAL(FALSE, fd.hasIntegerValue); - ASSERT_EQUAL(FALSE, fd.isNegative); + ASSERT_EQUAL(1, fd.getPluralOperand(PLURAL_OPERAND_V)); + ASSERT_EQUAL(1, fd.getPluralOperand(PLURAL_OPERAND_F)); + ASSERT_EQUAL(1, fd.getPluralOperand(PLURAL_OPERAND_T)); + ASSERT_EQUAL(123, fd.getPluralOperand(PLURAL_OPERAND_I)); + ASSERT_EQUAL(FALSE, fd.hasIntegerValue()); + ASSERT_EQUAL(FALSE, fd.isNegative()); df.adoptInsteadAndCheckErrorCode(new DecimalFormat("@@@@@", status), status); // Significant Digits TEST_ASSERT_STATUS(status); - fd = df->getFixedDecimal(123, status); + df->formatToDecimalQuantity(123, fd, status); TEST_ASSERT_STATUS(status); - ASSERT_EQUAL(2, fd.visibleDecimalDigitCount); - ASSERT_EQUAL(0, fd.decimalDigits); - ASSERT_EQUAL(0, fd.decimalDigitsWithoutTrailingZeros); - ASSERT_EQUAL(123, fd.intValue); - ASSERT_EQUAL(TRUE, fd.hasIntegerValue); - ASSERT_EQUAL(FALSE, fd.isNegative); + ASSERT_EQUAL(2, fd.getPluralOperand(PLURAL_OPERAND_V)); + ASSERT_EQUAL(0, fd.getPluralOperand(PLURAL_OPERAND_F)); + ASSERT_EQUAL(0, fd.getPluralOperand(PLURAL_OPERAND_T)); + ASSERT_EQUAL(123, fd.getPluralOperand(PLURAL_OPERAND_I)); + ASSERT_EQUAL(TRUE, fd.hasIntegerValue()); + ASSERT_EQUAL(FALSE, fd.isNegative()); df.adoptInsteadAndCheckErrorCode(new DecimalFormat("@@@@@", status), status); // Significant Digits TEST_ASSERT_STATUS(status); - fd = df->getFixedDecimal(1.23, status); + df->formatToDecimalQuantity(1.23, fd, status); TEST_ASSERT_STATUS(status); - ASSERT_EQUAL(4, fd.visibleDecimalDigitCount); - ASSERT_EQUAL(2300, fd.decimalDigits); - ASSERT_EQUAL(23, fd.decimalDigitsWithoutTrailingZeros); - ASSERT_EQUAL(1, fd.intValue); - ASSERT_EQUAL(FALSE, fd.hasIntegerValue); - ASSERT_EQUAL(FALSE, fd.isNegative); + ASSERT_EQUAL(4, fd.getPluralOperand(PLURAL_OPERAND_V)); + ASSERT_EQUAL(2300, fd.getPluralOperand(PLURAL_OPERAND_F)); + ASSERT_EQUAL(23, fd.getPluralOperand(PLURAL_OPERAND_T)); + ASSERT_EQUAL(1, fd.getPluralOperand(PLURAL_OPERAND_I)); + ASSERT_EQUAL(FALSE, fd.hasIntegerValue()); + ASSERT_EQUAL(FALSE, fd.isNegative()); - fd = df->getFixedDecimal(uprv_getInfinity(), status); + df->formatToDecimalQuantity(uprv_getInfinity(), fd, status); TEST_ASSERT_STATUS(status); - ASSERT_EQUAL(TRUE, fd.isNanOrInfinity()); - fd = df->getFixedDecimal(0.0, status); - ASSERT_EQUAL(FALSE, fd.isNanOrInfinity()); - fd = df->getFixedDecimal(uprv_getNaN(), status); - ASSERT_EQUAL(TRUE, fd.isNanOrInfinity()); + ASSERT_EQUAL(TRUE, fd.isNaN() || fd.isInfinite()); + df->formatToDecimalQuantity(0.0, fd, status); + ASSERT_EQUAL(FALSE, fd.isNaN() || fd.isInfinite()); + df->formatToDecimalQuantity(uprv_getNaN(), fd, status); + ASSERT_EQUAL(TRUE, fd.isNaN() || fd.isInfinite()); TEST_ASSERT_STATUS(status); // Test Big Decimal input. @@ -788,135 +790,141 @@ void IntlTestDecimalFormatAPI::TestFixedDecimal() { TEST_ASSERT_STATUS(status); Formattable fable("12.34", status); TEST_ASSERT_STATUS(status); - fd = df->getFixedDecimal(fable, status); + df->formatToDecimalQuantity(fable, fd, status); TEST_ASSERT_STATUS(status); - ASSERT_EQUAL(2, fd.visibleDecimalDigitCount); - ASSERT_EQUAL(34, fd.decimalDigits); - ASSERT_EQUAL(34, fd.decimalDigitsWithoutTrailingZeros); - ASSERT_EQUAL(12, fd.intValue); - ASSERT_EQUAL(FALSE, fd.hasIntegerValue); - ASSERT_EQUAL(FALSE, fd.isNegative); + ASSERT_EQUAL(2, fd.getPluralOperand(PLURAL_OPERAND_V)); + ASSERT_EQUAL(34, fd.getPluralOperand(PLURAL_OPERAND_F)); + ASSERT_EQUAL(34, fd.getPluralOperand(PLURAL_OPERAND_T)); + ASSERT_EQUAL(12, fd.getPluralOperand(PLURAL_OPERAND_I)); + ASSERT_EQUAL(FALSE, fd.hasIntegerValue()); + ASSERT_EQUAL(FALSE, fd.isNegative()); fable.setDecimalNumber("12.345678901234567890123456789", status); TEST_ASSERT_STATUS(status); - fd = df->getFixedDecimal(fable, status); + df->formatToDecimalQuantity(fable, fd, status); TEST_ASSERT_STATUS(status); - ASSERT_EQUAL(22, fd.visibleDecimalDigitCount); - ASSERT_EQUAL(345678901234567890LL, fd.decimalDigits); - ASSERT_EQUAL(34567890123456789LL, fd.decimalDigitsWithoutTrailingZeros); - ASSERT_EQUAL(12, fd.intValue); - ASSERT_EQUAL(FALSE, fd.hasIntegerValue); - ASSERT_EQUAL(FALSE, fd.isNegative); + ASSERT_EQUAL(22, fd.getPluralOperand(PLURAL_OPERAND_V)); + ASSERT_EQUAL(345678901234567890LL, fd.getPluralOperand(PLURAL_OPERAND_F)); + ASSERT_EQUAL(34567890123456789LL, fd.getPluralOperand(PLURAL_OPERAND_T)); + ASSERT_EQUAL(12, fd.getPluralOperand(PLURAL_OPERAND_I)); + ASSERT_EQUAL(FALSE, fd.hasIntegerValue()); + ASSERT_EQUAL(FALSE, fd.isNegative()); // On field overflow, Integer part is truncated on the left, fraction part on the right. fable.setDecimalNumber("123456789012345678901234567890.123456789012345678901234567890", status); TEST_ASSERT_STATUS(status); - fd = df->getFixedDecimal(fable, status); + df->formatToDecimalQuantity(fable, fd, status); TEST_ASSERT_STATUS(status); - ASSERT_EQUAL(22, fd.visibleDecimalDigitCount); - ASSERT_EQUAL(123456789012345678LL, fd.decimalDigits); - ASSERT_EQUAL(123456789012345678LL, fd.decimalDigitsWithoutTrailingZeros); - ASSERT_EQUAL(345678901234567890LL, fd.intValue); - ASSERT_EQUAL(FALSE, fd.hasIntegerValue); - ASSERT_EQUAL(FALSE, fd.isNegative); + ASSERT_EQUAL(22, fd.getPluralOperand(PLURAL_OPERAND_V)); + ASSERT_EQUAL(123456789012345678LL, fd.getPluralOperand(PLURAL_OPERAND_F)); + ASSERT_EQUAL(123456789012345678LL, fd.getPluralOperand(PLURAL_OPERAND_T)); + ASSERT_EQUAL(345678901234567890LL, fd.getPluralOperand(PLURAL_OPERAND_I)); + ASSERT_EQUAL(FALSE, fd.hasIntegerValue()); + ASSERT_EQUAL(FALSE, fd.isNegative()); // Digits way to the right of the decimal but within the format's precision aren't truncated fable.setDecimalNumber("1.0000000000000000000012", status); TEST_ASSERT_STATUS(status); - fd = df->getFixedDecimal(fable, status); + df->formatToDecimalQuantity(fable, fd, status); TEST_ASSERT_STATUS(status); - ASSERT_EQUAL(22, fd.visibleDecimalDigitCount); - ASSERT_EQUAL(12, fd.decimalDigits); - ASSERT_EQUAL(12, fd.decimalDigitsWithoutTrailingZeros); - ASSERT_EQUAL(1, fd.intValue); - ASSERT_EQUAL(FALSE, fd.hasIntegerValue); - ASSERT_EQUAL(FALSE, fd.isNegative); + ASSERT_EQUAL(22, fd.getPluralOperand(PLURAL_OPERAND_V)); + ASSERT_EQUAL(12, fd.getPluralOperand(PLURAL_OPERAND_F)); + ASSERT_EQUAL(12, fd.getPluralOperand(PLURAL_OPERAND_T)); + ASSERT_EQUAL(1, fd.getPluralOperand(PLURAL_OPERAND_I)); + ASSERT_EQUAL(FALSE, fd.hasIntegerValue()); + ASSERT_EQUAL(FALSE, fd.isNegative()); // Digits beyond the precision of the format are rounded away fable.setDecimalNumber("1.000000000000000000000012", status); TEST_ASSERT_STATUS(status); - fd = df->getFixedDecimal(fable, status); + df->formatToDecimalQuantity(fable, fd, status); TEST_ASSERT_STATUS(status); - ASSERT_EQUAL(2, fd.visibleDecimalDigitCount); - ASSERT_EQUAL(0, fd.decimalDigits); - ASSERT_EQUAL(0, fd.decimalDigitsWithoutTrailingZeros); - ASSERT_EQUAL(1, fd.intValue); - ASSERT_EQUAL(TRUE, fd.hasIntegerValue); - ASSERT_EQUAL(FALSE, fd.isNegative); + ASSERT_EQUAL(2, fd.getPluralOperand(PLURAL_OPERAND_V)); + ASSERT_EQUAL(0, fd.getPluralOperand(PLURAL_OPERAND_F)); + ASSERT_EQUAL(0, fd.getPluralOperand(PLURAL_OPERAND_T)); + ASSERT_EQUAL(1, fd.getPluralOperand(PLURAL_OPERAND_I)); + ASSERT_EQUAL(TRUE, fd.hasIntegerValue()); + ASSERT_EQUAL(FALSE, fd.isNegative()); // Negative numbers come through fable.setDecimalNumber("-1.0000000000000000000012", status); TEST_ASSERT_STATUS(status); - fd = df->getFixedDecimal(fable, status); + df->formatToDecimalQuantity(fable, fd, status); TEST_ASSERT_STATUS(status); - ASSERT_EQUAL(22, fd.visibleDecimalDigitCount); - ASSERT_EQUAL(12, fd.decimalDigits); - ASSERT_EQUAL(12, fd.decimalDigitsWithoutTrailingZeros); - ASSERT_EQUAL(1, fd.intValue); - ASSERT_EQUAL(FALSE, fd.hasIntegerValue); - ASSERT_EQUAL(TRUE, fd.isNegative); + ASSERT_EQUAL(22, fd.getPluralOperand(PLURAL_OPERAND_V)); + ASSERT_EQUAL(12, fd.getPluralOperand(PLURAL_OPERAND_F)); + ASSERT_EQUAL(12, fd.getPluralOperand(PLURAL_OPERAND_T)); + ASSERT_EQUAL(1, fd.getPluralOperand(PLURAL_OPERAND_I)); + ASSERT_EQUAL(FALSE, fd.hasIntegerValue()); + ASSERT_EQUAL(TRUE, fd.isNegative()); // MinFractionDigits from format larger than from number. fable.setDecimalNumber("1000000000000000000000.3", status); TEST_ASSERT_STATUS(status); - fd = df->getFixedDecimal(fable, status); + df->formatToDecimalQuantity(fable, fd, status); TEST_ASSERT_STATUS(status); - ASSERT_EQUAL(2, fd.visibleDecimalDigitCount); - ASSERT_EQUAL(30, fd.decimalDigits); - ASSERT_EQUAL(3, fd.decimalDigitsWithoutTrailingZeros); - ASSERT_EQUAL(100000000000000000LL, fd.intValue); - ASSERT_EQUAL(FALSE, fd.hasIntegerValue); - ASSERT_EQUAL(FALSE, fd.isNegative); + ASSERT_EQUAL(2, fd.getPluralOperand(PLURAL_OPERAND_V)); + ASSERT_EQUAL(30, fd.getPluralOperand(PLURAL_OPERAND_F)); + ASSERT_EQUAL(3, fd.getPluralOperand(PLURAL_OPERAND_T)); + ASSERT_EQUAL(100000000000000000LL, fd.getPluralOperand(PLURAL_OPERAND_I)); + ASSERT_EQUAL(FALSE, fd.hasIntegerValue()); + ASSERT_EQUAL(FALSE, fd.isNegative()); fable.setDecimalNumber("1000000000000000050000.3", status); TEST_ASSERT_STATUS(status); - fd = df->getFixedDecimal(fable, status); + df->formatToDecimalQuantity(fable, fd, status); TEST_ASSERT_STATUS(status); - ASSERT_EQUAL(2, fd.visibleDecimalDigitCount); - ASSERT_EQUAL(30, fd.decimalDigits); - ASSERT_EQUAL(3, fd.decimalDigitsWithoutTrailingZeros); - ASSERT_EQUAL(50000LL, fd.intValue); - ASSERT_EQUAL(FALSE, fd.hasIntegerValue); - ASSERT_EQUAL(FALSE, fd.isNegative); + ASSERT_EQUAL(2, fd.getPluralOperand(PLURAL_OPERAND_V)); + ASSERT_EQUAL(30, fd.getPluralOperand(PLURAL_OPERAND_F)); + ASSERT_EQUAL(3, fd.getPluralOperand(PLURAL_OPERAND_T)); + ASSERT_EQUAL(50000LL, fd.getPluralOperand(PLURAL_OPERAND_I)); + ASSERT_EQUAL(FALSE, fd.hasIntegerValue()); + ASSERT_EQUAL(FALSE, fd.isNegative()); // Test some int64_t values that are out of the range of a double fable.setInt64(4503599627370496LL); TEST_ASSERT_STATUS(status); - fd = df->getFixedDecimal(fable, status); + df->formatToDecimalQuantity(fable, fd, status); TEST_ASSERT_STATUS(status); - ASSERT_EQUAL(2, fd.visibleDecimalDigitCount); - ASSERT_EQUAL(0, fd.decimalDigits); - ASSERT_EQUAL(0, fd.decimalDigitsWithoutTrailingZeros); - ASSERT_EQUAL(4503599627370496LL, fd.intValue); - ASSERT_EQUAL(TRUE, fd.hasIntegerValue); - ASSERT_EQUAL(FALSE, fd.isNegative); + ASSERT_EQUAL(2, fd.getPluralOperand(PLURAL_OPERAND_V)); + ASSERT_EQUAL(0, fd.getPluralOperand(PLURAL_OPERAND_F)); + ASSERT_EQUAL(0, fd.getPluralOperand(PLURAL_OPERAND_T)); + ASSERT_EQUAL(4503599627370496LL, fd.getPluralOperand(PLURAL_OPERAND_I)); + ASSERT_EQUAL(TRUE, fd.hasIntegerValue()); + ASSERT_EQUAL(FALSE, fd.isNegative()); fable.setInt64(4503599627370497LL); TEST_ASSERT_STATUS(status); - fd = df->getFixedDecimal(fable, status); + df->formatToDecimalQuantity(fable, fd, status); TEST_ASSERT_STATUS(status); - ASSERT_EQUAL(2, fd.visibleDecimalDigitCount); - ASSERT_EQUAL(0, fd.decimalDigits); - ASSERT_EQUAL(0, fd.decimalDigitsWithoutTrailingZeros); - ASSERT_EQUAL(4503599627370497LL, fd.intValue); - ASSERT_EQUAL(TRUE, fd.hasIntegerValue); - ASSERT_EQUAL(FALSE, fd.isNegative); + ASSERT_EQUAL(2, fd.getPluralOperand(PLURAL_OPERAND_V)); + ASSERT_EQUAL(0, fd.getPluralOperand(PLURAL_OPERAND_F)); + ASSERT_EQUAL(0, fd.getPluralOperand(PLURAL_OPERAND_T)); + ASSERT_EQUAL(4503599627370497LL, fd.getPluralOperand(PLURAL_OPERAND_I)); + ASSERT_EQUAL(TRUE, fd.hasIntegerValue()); + ASSERT_EQUAL(FALSE, fd.isNegative()); fable.setInt64(9223372036854775807LL); TEST_ASSERT_STATUS(status); - fd = df->getFixedDecimal(fable, status); + df->formatToDecimalQuantity(fable, fd, status); TEST_ASSERT_STATUS(status); - ASSERT_EQUAL(2, fd.visibleDecimalDigitCount); - ASSERT_EQUAL(0, fd.decimalDigits); - ASSERT_EQUAL(0, fd.decimalDigitsWithoutTrailingZeros); + ASSERT_EQUAL(2, fd.getPluralOperand(PLURAL_OPERAND_V)); + ASSERT_EQUAL(0, fd.getPluralOperand(PLURAL_OPERAND_F)); + ASSERT_EQUAL(0, fd.getPluralOperand(PLURAL_OPERAND_T)); // note: going through DigitList path to FixedDecimal, which is trimming // int64_t fields to 18 digits. See ticket Ticket #10374 - // ASSERT_EQUAL(223372036854775807LL, fd.intValue); - if (!(fd.intValue == 223372036854775807LL || fd.intValue == 9223372036854775807LL)) { - dataerrln("File %s, Line %d, fd.intValue = %lld", __FILE__, __LINE__, fd.intValue); + // ASSERT_EQUAL(223372036854775807LL, fd.getPluralOperand(PLURAL_OPERAND_I); + if (!( + fd.getPluralOperand(PLURAL_OPERAND_I) == 223372036854775807LL || + fd.getPluralOperand(PLURAL_OPERAND_I) == 9223372036854775807LL)) { + dataerrln( + "File %s, Line %d, fd.getPluralOperand(PLURAL_OPERAND_I = %lld", + __FILE__, + __LINE__, + fd.getPluralOperand(PLURAL_OPERAND_I)); } - ASSERT_EQUAL(TRUE, fd.hasIntegerValue); - ASSERT_EQUAL(FALSE, fd.isNegative); + ASSERT_EQUAL(TRUE, fd.hasIntegerValue()); + ASSERT_EQUAL(FALSE, fd.isNegative()); } diff --git a/icu4c/source/test/intltest/numberformattesttuple.cpp b/icu4c/source/test/intltest/numberformattesttuple.cpp index 496aaeccde..d862d068cd 100644 --- a/icu4c/source/test/intltest/numberformattesttuple.cpp +++ b/icu4c/source/test/intltest/numberformattesttuple.cpp @@ -15,7 +15,6 @@ #include "charstr.h" #include "cstring.h" #include "cmemory.h" -#include "digitlst.h" static NumberFormatTestTuple *gNullPtr = NULL; diff --git a/icu4c/source/test/intltest/numfmtst.cpp b/icu4c/source/test/intltest/numfmtst.cpp index a72567903f..a257e12f8b 100644 --- a/icu4c/source/test/intltest/numfmtst.cpp +++ b/icu4c/source/test/intltest/numfmtst.cpp @@ -23,7 +23,6 @@ #include "unicode/measfmt.h" #include "unicode/curramt.h" #include "unicode/strenum.h" -#include "digitlst.h" #include "textfile.h" #include "tokiter.h" #include "charstr.h" @@ -40,6 +39,7 @@ #include "numberformattesttuple.h" #include "datadrivennumberformattestsuite.h" #include "unicode/msgfmt.h" +#include "number_decimalquantity.h" #if (U_PLATFORM == U_PF_AIX) || (U_PLATFORM == U_PF_OS390) // These should not be macros. If they are, @@ -62,6 +62,7 @@ namespace std { #endif #endif +using icu::number::impl::DecimalQuantity; class NumberFormatTestDataDriven : public DataDrivenNumberFormatTestSuite { protected: @@ -83,34 +84,34 @@ UBool isParseCurrencyPass( UErrorCode &status); }; -static DigitList &strToDigitList( +static DecimalQuantity &strToDigitList( const UnicodeString &str, - DigitList &digitList, + DecimalQuantity &digitList, UErrorCode &status) { if (U_FAILURE(status)) { return digitList; } if (str == "NaN") { - digitList.set(uprv_getNaN()); + digitList.setToDouble(uprv_getNaN()); return digitList; } if (str == "-Inf") { - digitList.set(-1*uprv_getInfinity()); + digitList.setToDouble(-1*uprv_getInfinity()); return digitList; } if (str == "Inf") { - digitList.set(uprv_getInfinity()); + digitList.setToDouble(uprv_getInfinity()); return digitList; } CharString formatValue; formatValue.appendInvariantChars(str, status); - digitList.set(StringPiece(formatValue.data()), status, 0); + digitList.setToDecNumber(StringPiece(formatValue.data())); return digitList; } static UnicodeString &format( const DecimalFormat &fmt, - const DigitList &digitList, + const DecimalQuantity &digitList, UnicodeString &appendTo, UErrorCode &status) { if (U_FAILURE(status)) { @@ -315,7 +316,7 @@ UBool NumberFormatTestDataDriven::isFormatPass( if (appendErrorMessage.length() > 0) { return FALSE; } - DigitList digitList; + DecimalQuantity digitList; strToDigitList(tuple.format, digitList, status); { UnicodeString appendTo; @@ -330,7 +331,7 @@ UBool NumberFormatTestDataDriven::isFormatPass( return FALSE; } } - double doubleVal = digitList.getDouble(); + double doubleVal = digitList.toDouble(); { UnicodeString appendTo; format(*fmtPtr, doubleVal, appendTo, status); @@ -345,7 +346,7 @@ UBool NumberFormatTestDataDriven::isFormatPass( } } if (!uprv_isNaN(doubleVal) && !uprv_isInfinite(doubleVal) && doubleVal == uprv_floor(doubleVal)) { - int64_t intVal = digitList.getInt64(); + int64_t intVal = digitList.toLong(); { UnicodeString appendTo; format(*fmtPtr, intVal, appendTo, status); @@ -446,13 +447,13 @@ UBool NumberFormatTestDataDriven::isParsePass( } return TRUE; } - DigitList expected; + DecimalQuantity expected; strToDigitList(tuple.output, expected, status); if (U_FAILURE(status)) { appendErrorMessage.append("Error parsing."); return FALSE; } - if (expected != *result.getDigitList()) { + if (expected != *result.getDecimalQuantity()) { appendErrorMessage.append(UnicodeString("Expected: ") + tuple.output + ", but got: " + resultStr + " (" + ppos.getIndex() + ":" + ppos.getErrorIndex() + ")"); return FALSE; } @@ -490,13 +491,13 @@ UBool NumberFormatTestDataDriven::isParseCurrencyPass( appendErrorMessage.append(UnicodeString("Parse succeeded: ") + resultStr + ", but was expected to fail."); return TRUE; // TRUE because failure handling is in the test suite } - DigitList expected; + DecimalQuantity expected; strToDigitList(tuple.output, expected, status); if (U_FAILURE(status)) { appendErrorMessage.append("Error parsing."); return FALSE; } - if (expected != *currAmt->getNumber().getDigitList()) { + if (expected != *currAmt->getNumber().getDecimalQuantity()) { appendErrorMessage.append(UnicodeString("Expected: ") + tuple.output + ", but got: " + resultStr + " (" + ppos.getIndex() + ":" + ppos.getErrorIndex() + ")"); return FALSE; } @@ -7024,9 +7025,9 @@ void NumberFormatTest::TestDecimal() { dataerrln("Unable to create NumberFormat"); } else { UnicodeString formattedResult; - DigitList dl; + DecimalQuantity dl; StringPiece num("123.4566666666666666666666666666666666621E+40"); - dl.set(num, status); + dl.setToDecNumber(num); ASSERT_SUCCESS(status); fmtr->format(dl, formattedResult, NULL, status); ASSERT_SUCCESS(status); @@ -7034,7 +7035,7 @@ void NumberFormatTest::TestDecimal() { status = U_ZERO_ERROR; num.set("666.666"); - dl.set(num, status); + dl.setToDecNumber(num); FieldPosition pos(NumberFormat::FRACTION_FIELD); ASSERT_SUCCESS(status); formattedResult.remove(); @@ -8671,14 +8672,6 @@ void NumberFormatTest::Test11868() { } } -void NumberFormatTest::Test10727_RoundingZero() { - DigitList d; - d.set(-0.0); - assertFalse("", d.isPositive()); - d.round(3); - assertFalse("", d.isPositive()); -} - void NumberFormatTest::Test11376_getAndSetPositivePrefix() { { const UChar USD[] = {0x55, 0x53, 0x44, 0x0}; diff --git a/icu4c/source/test/intltest/plurults.cpp b/icu4c/source/test/intltest/plurults.cpp index 7d342287d9..5722126303 100644 --- a/icu4c/source/test/intltest/plurults.cpp +++ b/icu4c/source/test/intltest/plurults.cpp @@ -24,10 +24,12 @@ #include "unicode/stringpiece.h" #include "cmemory.h" -#include "digitlst.h" #include "plurrule_impl.h" #include "plurults.h" #include "uhash.h" +#include "number_decimalquantity.h" + +using icu::number::impl::DecimalQuantity; void setupResult(const int32_t testSource[], char result[], int32_t* max); UBool checkEqual(const PluralRules &test, char *result, int32_t max); @@ -633,14 +635,14 @@ void PluralRulesTest::checkSelect(const LocalPointer &rules, UError } // DigitList is a convenient way to parse the decimal number string and get a double. - DigitList dl; - dl.set(StringPiece(num), status); + DecimalQuantity dl; + dl.setToDecNumber(StringPiece(num)); if (U_FAILURE(status)) { errln("file %s, line %d, ICU error status: %s.", __FILE__, line, u_errorName(status)); status = U_ZERO_ERROR; continue; } - double numDbl = dl.getDouble(); + double numDbl = dl.toDouble(); const char *decimalPoint = strchr(num, '.'); int fractionDigitCount = decimalPoint == NULL ? 0 : (num + strlen(num) - 1) - decimalPoint; int fractionDigits = fractionDigitCount == 0 ? 0 : atoi(decimalPoint + 1); diff --git a/icu4c/source/test/intltest/uobjtest.cpp b/icu4c/source/test/intltest/uobjtest.cpp index d58e8c25d3..d55ba7e5a1 100644 --- a/icu4c/source/test/intltest/uobjtest.cpp +++ b/icu4c/source/test/intltest/uobjtest.cpp @@ -215,7 +215,6 @@ UObject *UObjectTest::testClassNoClassID(UObject *obj, const char *className, co #include "rbt_data.h" #include "nultrans.h" #include "anytrans.h" -#include "digitlst.h" #include "esctrn.h" #include "funcrepl.h" #include "servnotf.h" From 3487cc4331628e438e81b6cdb2fd5c28fe5738a9 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Thu, 8 Mar 2018 07:15:29 +0000 Subject: [PATCH 032/129] ICU-13634 Adding docstring to CompactData.java X-SVN-Rev: 41085 --- .../classes/core/src/com/ibm/icu/impl/number/CompactData.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CompactData.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CompactData.java index 8b66e18fd6..23259df57e 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CompactData.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CompactData.java @@ -15,6 +15,9 @@ import com.ibm.icu.util.ICUException; import com.ibm.icu.util.ULocale; import com.ibm.icu.util.UResourceBundle; +/** + * Datatype for compact notation data. Includes logic for data loading. + */ public class CompactData implements MultiplierProducer { public enum CompactType { From 453788ddfd120d986db1572de4172cd54c9a0877 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Tue, 13 Mar 2018 08:12:05 +0000 Subject: [PATCH 033/129] ICU-13634 Temporarily deleting all old number formatting implementation code. Pieces will be restored as needed. ICU4C builds, including tests, but won't run. Changes in plurfmt.cpp and plurrule.cpp are not tested. numberformat2test.cpp is unlinked but not deleted. X-SVN-Rev: 41098 --- icu4c/source/i18n/Makefile.in | 11 +- icu4c/source/i18n/affixpatternparser.cpp | 698 ----- icu4c/source/i18n/affixpatternparser.h | 402 --- icu4c/source/i18n/compactdecimalformat.cpp | 13 + icu4c/source/i18n/decimalformatpattern.cpp | 656 ----- icu4c/source/i18n/decimalformatpattern.h | 106 - icu4c/source/i18n/decimalformatpatternimpl.h | 35 - icu4c/source/i18n/decimfmt.cpp | 16 +- icu4c/source/i18n/decimfmtimpl.cpp | 1597 ---------- icu4c/source/i18n/decimfmtimpl.h | 549 ---- icu4c/source/i18n/digitaffix.cpp | 109 - icu4c/source/i18n/digitaffix.h | 104 - icu4c/source/i18n/digitaffixesandpadding.cpp | 175 -- icu4c/source/i18n/digitaffixesandpadding.h | 179 -- icu4c/source/i18n/digitformatter.cpp | 417 --- icu4c/source/i18n/digitformatter.h | 288 -- icu4c/source/i18n/digitgrouping.cpp | 59 - icu4c/source/i18n/digitgrouping.h | 112 - icu4c/source/i18n/digitinterval.cpp | 56 - icu4c/source/i18n/digitinterval.h | 159 - icu4c/source/i18n/digitlst.cpp | 1143 ------- icu4c/source/i18n/digitlst.h | 529 ---- icu4c/source/i18n/msgfmt.cpp | 1 - icu4c/source/i18n/number_decimalquantity.cpp | 5 + icu4c/source/i18n/number_decimalquantity.h | 4 +- icu4c/source/i18n/pluralaffix.cpp | 104 - icu4c/source/i18n/pluralaffix.h | 177 -- icu4c/source/i18n/plurfmt.cpp | 21 +- icu4c/source/i18n/plurrule.cpp | 60 +- icu4c/source/i18n/plurrule_impl.h | 1 - icu4c/source/i18n/precision.cpp | 444 --- icu4c/source/i18n/precision.h | 323 -- icu4c/source/i18n/quantityformatter.cpp | 1 - icu4c/source/i18n/smallintformatter.cpp | 2623 ----------------- icu4c/source/i18n/smallintformatter.h | 90 - icu4c/source/i18n/unicode/plurrule.h | 5 - icu4c/source/i18n/valueformatter.cpp | 223 -- icu4c/source/i18n/valueformatter.h | 161 - icu4c/source/i18n/visibledigits.cpp | 186 -- icu4c/source/i18n/visibledigits.h | 162 - icu4c/source/test/intltest/Makefile.in | 2 +- .../datadrivennumberformattestsuite.h | 1 + icu4c/source/test/intltest/itformat.cpp | 12 +- .../test/intltest/numberformattesttuple.h | 1 - icu4c/source/test/intltest/numfmtst.cpp | 2 +- 45 files changed, 68 insertions(+), 11954 deletions(-) delete mode 100644 icu4c/source/i18n/affixpatternparser.cpp delete mode 100644 icu4c/source/i18n/affixpatternparser.h delete mode 100644 icu4c/source/i18n/decimalformatpattern.cpp delete mode 100644 icu4c/source/i18n/decimalformatpattern.h delete mode 100644 icu4c/source/i18n/decimalformatpatternimpl.h delete mode 100644 icu4c/source/i18n/decimfmtimpl.cpp delete mode 100644 icu4c/source/i18n/decimfmtimpl.h delete mode 100644 icu4c/source/i18n/digitaffix.cpp delete mode 100644 icu4c/source/i18n/digitaffix.h delete mode 100644 icu4c/source/i18n/digitaffixesandpadding.cpp delete mode 100644 icu4c/source/i18n/digitaffixesandpadding.h delete mode 100644 icu4c/source/i18n/digitformatter.cpp delete mode 100644 icu4c/source/i18n/digitformatter.h delete mode 100644 icu4c/source/i18n/digitgrouping.cpp delete mode 100644 icu4c/source/i18n/digitgrouping.h delete mode 100644 icu4c/source/i18n/digitinterval.cpp delete mode 100644 icu4c/source/i18n/digitinterval.h delete mode 100644 icu4c/source/i18n/digitlst.cpp delete mode 100644 icu4c/source/i18n/digitlst.h delete mode 100644 icu4c/source/i18n/pluralaffix.cpp delete mode 100644 icu4c/source/i18n/pluralaffix.h delete mode 100644 icu4c/source/i18n/precision.cpp delete mode 100644 icu4c/source/i18n/precision.h delete mode 100644 icu4c/source/i18n/smallintformatter.cpp delete mode 100644 icu4c/source/i18n/smallintformatter.h delete mode 100644 icu4c/source/i18n/valueformatter.cpp delete mode 100644 icu4c/source/i18n/valueformatter.h delete mode 100644 icu4c/source/i18n/visibledigits.cpp delete mode 100644 icu4c/source/i18n/visibledigits.h diff --git a/icu4c/source/i18n/Makefile.in b/icu4c/source/i18n/Makefile.in index 6bfbaed62c..83c0e3c69d 100644 --- a/icu4c/source/i18n/Makefile.in +++ b/icu4c/source/i18n/Makefile.in @@ -65,8 +65,8 @@ LDFLAGS += $(LDFLAGSICUI18N) LIBS = $(LIBICUUC) $(DEFAULT_LIBS) OBJECTS = ucln_in.o \ -fmtable.o format.o msgfmt.o umsg.o numfmt.o unum.o decimfmt.o decimalformatpattern.o dcfmtsym.o \ -digitlst.o fmtable_cnv.o \ +fmtable.o format.o msgfmt.o umsg.o numfmt.o unum.o decimfmt.o dcfmtsym.o \ +fmtable_cnv.o \ choicfmt.o datefmt.o smpdtfmt.o reldtfmt.o dtfmtsym.o udat.o dtptngen.o udatpg.o \ nfrs.o nfrule.o nfsubs.o rbnf.o numsys.o unumsys.o ucsdet.o \ ucal.o calendar.o gregocal.o timezone.o simpletz.o olsontz.o \ @@ -97,12 +97,7 @@ ztrans.o zrule.o vzone.o fphdlimp.o fpositer.o ufieldpositer.o \ decNumber.o decContext.o alphaindex.o tznames.o tznames_impl.o tzgnames.o \ tzfmt.o compactdecimalformat.o gender.o region.o scriptset.o \ uregion.o reldatefmt.o quantityformatter.o measunit.o \ -sharedbreakiterator.o scientificnumberformatter.o digitgrouping.o \ -digitinterval.o digitformatter.o digitaffix.o valueformatter.o \ -digitaffixesandpadding.o pluralaffix.o precision.o \ -affixpatternparser.o smallintformatter.o decimfmtimpl.o \ -visibledigits.o dayperiodrules.o \ -nounit.o \ +sharedbreakiterator.o scientificnumberformatter.o dayperiodrules.o nounit.o \ number_affixutils.o number_compact.o number_decimalquantity.o \ number_decimfmtprops.o number_fluent.o number_formatimpl.o number_grouping.o \ number_integerwidth.o number_longnames.o number_modifiers.o number_notation.o \ diff --git a/icu4c/source/i18n/affixpatternparser.cpp b/icu4c/source/i18n/affixpatternparser.cpp deleted file mode 100644 index c15f3871f3..0000000000 --- a/icu4c/source/i18n/affixpatternparser.cpp +++ /dev/null @@ -1,698 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* - * Copyright (C) 2015, International Business Machines - * Corporation and others. All Rights Reserved. - * - * file name: affixpatternparser.cpp - */ - -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING - -#include "unicode/dcfmtsym.h" -#include "unicode/plurrule.h" -#include "unicode/strenum.h" -#include "unicode/ucurr.h" -#include "unicode/ustring.h" -#include "affixpatternparser.h" -#include "charstr.h" -#include "precision.h" -#include "uassert.h" -#include "unistrappender.h" - -static const UChar gDefaultSymbols[] = {0xa4, 0xa4, 0xa4}; - -static const UChar gPercent = 0x25; -static const UChar gPerMill = 0x2030; -static const UChar gNegative = 0x2D; -static const UChar gPositive = 0x2B; - -#define PACK_TOKEN_AND_LENGTH(t, l) ((UChar) (((t) << 8) | (l & 0xFF))) - -#define UNPACK_TOKEN(c) ((AffixPattern::ETokenType) (((c) >> 8) & 0x7F)) - -#define UNPACK_LONG(c) (((c) >> 8) & 0x80) - -#define UNPACK_LENGTH(c) ((c) & 0xFF) - -U_NAMESPACE_BEGIN - -static int32_t -nextToken(const UChar *buffer, int32_t idx, int32_t len, UChar *token) { - if (buffer[idx] != 0x27 || idx + 1 == len) { - *token = buffer[idx]; - return 1; - } - *token = buffer[idx + 1]; - if (buffer[idx + 1] == 0xA4) { - int32_t i = 2; - for (; idx + i < len && i < 4 && buffer[idx + i] == buffer[idx + 1]; ++i) - ; - return i; - } - return 2; -} - -static int32_t -nextUserToken(const UChar *buffer, int32_t idx, int32_t len, UChar *token) { - *token = buffer[idx]; - int32_t max; - switch (buffer[idx]) { - case 0x27: - max = 2; - break; - case 0xA4: - max = 3; - break; - default: - max = 1; - break; - } - int32_t i = 1; - for (; idx + i < len && i < max && buffer[idx + i] == buffer[idx]; ++i) - ; - return i; -} - -CurrencyAffixInfo::CurrencyAffixInfo() - : fSymbol(gDefaultSymbols, 1), - fISO(gDefaultSymbols, 2), - fLong(DigitAffix(gDefaultSymbols, 3)), - fIsDefault(TRUE) { -} - -void -CurrencyAffixInfo::set( - const char *locale, - const PluralRules *rules, - const UChar *currency, - UErrorCode &status) { - if (U_FAILURE(status)) { - return; - } - fIsDefault = FALSE; - if (currency == NULL) { - fSymbol.setTo(gDefaultSymbols, 1); - fISO.setTo(gDefaultSymbols, 2); - fLong.remove(); - fLong.append(gDefaultSymbols, 3); - fIsDefault = TRUE; - return; - } - int32_t len; - UBool unusedIsChoice; - const UChar *symbol = ucurr_getName( - currency, locale, UCURR_SYMBOL_NAME, &unusedIsChoice, - &len, &status); - if (U_FAILURE(status)) { - return; - } - fSymbol.setTo(symbol, len); - fISO.setTo(currency, u_strlen(currency)); - fLong.remove(); - StringEnumeration* keywords = rules->getKeywords(status); - if (U_FAILURE(status)) { - return; - } - const UnicodeString* pluralCount; - while ((pluralCount = keywords->snext(status)) != NULL) { - CharString pCount; - pCount.appendInvariantChars(*pluralCount, status); - const UChar *pluralName = ucurr_getPluralName( - currency, locale, &unusedIsChoice, pCount.data(), - &len, &status); - fLong.setVariant(pCount.data(), UnicodeString(pluralName, len), status); - } - delete keywords; -} - -void -CurrencyAffixInfo::adjustPrecision( - const UChar *currency, const UCurrencyUsage usage, - FixedPrecision &precision, UErrorCode &status) { - if (U_FAILURE(status)) { - return; - } - - int32_t digitCount = ucurr_getDefaultFractionDigitsForUsage( - currency, usage, &status); - precision.fMin.setFracDigitCount(digitCount); - precision.fMax.setFracDigitCount(digitCount); - double increment = ucurr_getRoundingIncrementForUsage( - currency, usage, &status); - if (increment == 0.0) { - precision.fRoundingIncrement.clear(); - } else { - precision.fRoundingIncrement.set(increment); - // guard against round-off error - precision.fRoundingIncrement.round(6); - } -} - -void -AffixPattern::addLiteral( - const UChar *literal, int32_t start, int32_t len) { - char32Count += u_countChar32(literal + start, len); - literals.append(literal, start, len); - int32_t tlen = tokens.length(); - // Takes 4 UChars to encode maximum literal length. - UChar *tokenChars = tokens.getBuffer(tlen + 4); - - // find start of literal size. May be tlen if there is no literal. - // While finding start of literal size, compute literal length - int32_t literalLength = 0; - int32_t tLiteralStart = tlen; - while (tLiteralStart > 0 && UNPACK_TOKEN(tokenChars[tLiteralStart - 1]) == kLiteral) { - tLiteralStart--; - literalLength <<= 8; - literalLength |= UNPACK_LENGTH(tokenChars[tLiteralStart]); - } - // Add number of chars we just added to literal - literalLength += len; - - // Now encode the new length starting at tLiteralStart - tlen = tLiteralStart; - tokenChars[tlen++] = PACK_TOKEN_AND_LENGTH(kLiteral, literalLength & 0xFF); - literalLength >>= 8; - while (literalLength) { - tokenChars[tlen++] = PACK_TOKEN_AND_LENGTH(kLiteral | 0x80, literalLength & 0xFF); - literalLength >>= 8; - } - tokens.releaseBuffer(tlen); -} - -void -AffixPattern::add(ETokenType t) { - add(t, 1); -} - -void -AffixPattern::addCurrency(uint8_t count) { - add(kCurrency, count); -} - -void -AffixPattern::add(ETokenType t, uint8_t count) { - U_ASSERT(t != kLiteral); - char32Count += count; - switch (t) { - case kCurrency: - hasCurrencyToken = TRUE; - break; - case kPercent: - hasPercentToken = TRUE; - break; - case kPerMill: - hasPermillToken = TRUE; - break; - default: - // Do nothing - break; - } - tokens.append(PACK_TOKEN_AND_LENGTH(t, count)); -} - -AffixPattern & -AffixPattern::append(const AffixPattern &other) { - AffixPatternIterator iter; - other.iterator(iter); - UnicodeString literal; - while (iter.nextToken()) { - switch (iter.getTokenType()) { - case kLiteral: - iter.getLiteral(literal); - addLiteral(literal.getBuffer(), 0, literal.length()); - break; - case kCurrency: - addCurrency(static_cast(iter.getTokenLength())); - break; - default: - add(iter.getTokenType()); - break; - } - } - return *this; -} - -void -AffixPattern::remove() { - tokens.remove(); - literals.remove(); - hasCurrencyToken = FALSE; - hasPercentToken = FALSE; - hasPermillToken = FALSE; - char32Count = 0; -} - -// escapes literals for strings where special characters are NOT escaped -// except for apostrophe. -static void escapeApostropheInLiteral( - const UnicodeString &literal, UnicodeStringAppender &appender) { - int32_t len = literal.length(); - const UChar *buffer = literal.getBuffer(); - for (int32_t i = 0; i < len; ++i) { - UChar ch = buffer[i]; - switch (ch) { - case 0x27: - appender.append((UChar) 0x27); - appender.append((UChar) 0x27); - break; - default: - appender.append(ch); - break; - } - } -} - - -// escapes literals for user strings where special characters in literals -// are escaped with apostrophe. -static void escapeLiteral( - const UnicodeString &literal, UnicodeStringAppender &appender) { - int32_t len = literal.length(); - const UChar *buffer = literal.getBuffer(); - for (int32_t i = 0; i < len; ++i) { - UChar ch = buffer[i]; - switch (ch) { - case 0x27: - appender.append((UChar) 0x27); - appender.append((UChar) 0x27); - break; - case 0x25: - appender.append((UChar) 0x27); - appender.append((UChar) 0x25); - appender.append((UChar) 0x27); - break; - case 0x2030: - appender.append((UChar) 0x27); - appender.append((UChar) 0x2030); - appender.append((UChar) 0x27); - break; - case 0xA4: - appender.append((UChar) 0x27); - appender.append((UChar) 0xA4); - appender.append((UChar) 0x27); - break; - case 0x2D: - appender.append((UChar) 0x27); - appender.append((UChar) 0x2D); - appender.append((UChar) 0x27); - break; - case 0x2B: - appender.append((UChar) 0x27); - appender.append((UChar) 0x2B); - appender.append((UChar) 0x27); - break; - default: - appender.append(ch); - break; - } - } -} - -UnicodeString & -AffixPattern::toString(UnicodeString &appendTo) const { - AffixPatternIterator iter; - iterator(iter); - UnicodeStringAppender appender(appendTo); - UnicodeString literal; - while (iter.nextToken()) { - switch (iter.getTokenType()) { - case kLiteral: - escapeApostropheInLiteral(iter.getLiteral(literal), appender); - break; - case kPercent: - appender.append((UChar) 0x27); - appender.append((UChar) 0x25); - break; - case kPerMill: - appender.append((UChar) 0x27); - appender.append((UChar) 0x2030); - break; - case kCurrency: - { - appender.append((UChar) 0x27); - int32_t cl = iter.getTokenLength(); - for (int32_t i = 0; i < cl; ++i) { - appender.append((UChar) 0xA4); - } - } - break; - case kNegative: - appender.append((UChar) 0x27); - appender.append((UChar) 0x2D); - break; - case kPositive: - appender.append((UChar) 0x27); - appender.append((UChar) 0x2B); - break; - default: - U_ASSERT(FALSE); - break; - } - } - return appendTo; -} - -UnicodeString & -AffixPattern::toUserString(UnicodeString &appendTo) const { - AffixPatternIterator iter; - iterator(iter); - UnicodeStringAppender appender(appendTo); - UnicodeString literal; - while (iter.nextToken()) { - switch (iter.getTokenType()) { - case kLiteral: - escapeLiteral(iter.getLiteral(literal), appender); - break; - case kPercent: - appender.append((UChar) 0x25); - break; - case kPerMill: - appender.append((UChar) 0x2030); - break; - case kCurrency: - { - int32_t cl = iter.getTokenLength(); - for (int32_t i = 0; i < cl; ++i) { - appender.append((UChar) 0xA4); - } - } - break; - case kNegative: - appender.append((UChar) 0x2D); - break; - case kPositive: - appender.append((UChar) 0x2B); - break; - default: - U_ASSERT(FALSE); - break; - } - } - return appendTo; -} - -class AffixPatternAppender : public UMemory { -public: - AffixPatternAppender(AffixPattern &dest) : fDest(&dest), fIdx(0) { } - - inline void append(UChar x) { - if (fIdx == UPRV_LENGTHOF(fBuffer)) { - fDest->addLiteral(fBuffer, 0, fIdx); - fIdx = 0; - } - fBuffer[fIdx++] = x; - } - - inline void append(UChar32 x) { - if (fIdx >= UPRV_LENGTHOF(fBuffer) - 1) { - fDest->addLiteral(fBuffer, 0, fIdx); - fIdx = 0; - } - U16_APPEND_UNSAFE(fBuffer, fIdx, x); - } - - inline void flush() { - if (fIdx) { - fDest->addLiteral(fBuffer, 0, fIdx); - } - fIdx = 0; - } - - /** - * flush the buffer when we go out of scope. - */ - ~AffixPatternAppender() { - flush(); - } -private: - AffixPattern *fDest; - int32_t fIdx; - UChar fBuffer[32]; - AffixPatternAppender(const AffixPatternAppender &other); - AffixPatternAppender &operator=(const AffixPatternAppender &other); -}; - - -AffixPattern & -AffixPattern::parseUserAffixString( - const UnicodeString &affixStr, - AffixPattern &appendTo, - UErrorCode &status) { - if (U_FAILURE(status)) { - return appendTo; - } - int32_t len = affixStr.length(); - const UChar *buffer = affixStr.getBuffer(); - // 0 = not quoted; 1 = quoted. - int32_t state = 0; - AffixPatternAppender appender(appendTo); - for (int32_t i = 0; i < len; ) { - UChar token; - int32_t tokenSize = nextUserToken(buffer, i, len, &token); - i += tokenSize; - if (token == 0x27 && tokenSize == 1) { // quote - state = 1 - state; - continue; - } - if (state == 0) { - switch (token) { - case 0x25: - appender.flush(); - appendTo.add(kPercent, 1); - break; - case 0x27: // double quote - appender.append((UChar) 0x27); - break; - case 0x2030: - appender.flush(); - appendTo.add(kPerMill, 1); - break; - case 0x2D: - appender.flush(); - appendTo.add(kNegative, 1); - break; - case 0x2B: - appender.flush(); - appendTo.add(kPositive, 1); - break; - case 0xA4: - appender.flush(); - appendTo.add(kCurrency, static_cast(tokenSize)); - break; - default: - appender.append(token); - break; - } - } else { - switch (token) { - case 0x27: // double quote - appender.append((UChar) 0x27); - break; - case 0xA4: // included b/c tokenSize can be > 1 - for (int32_t j = 0; j < tokenSize; ++j) { - appender.append((UChar) 0xA4); - } - break; - default: - appender.append(token); - break; - } - } - } - return appendTo; -} - -AffixPattern & -AffixPattern::parseAffixString( - const UnicodeString &affixStr, - AffixPattern &appendTo, - UErrorCode &status) { - if (U_FAILURE(status)) { - return appendTo; - } - int32_t len = affixStr.length(); - const UChar *buffer = affixStr.getBuffer(); - for (int32_t i = 0; i < len; ) { - UChar token; - int32_t tokenSize = nextToken(buffer, i, len, &token); - if (tokenSize == 1) { - int32_t literalStart = i; - ++i; - while (i < len && (tokenSize = nextToken(buffer, i, len, &token)) == 1) { - ++i; - } - appendTo.addLiteral(buffer, literalStart, i - literalStart); - - // If we reached end of string, we are done - if (i == len) { - return appendTo; - } - } - i += tokenSize; - switch (token) { - case 0x25: - appendTo.add(kPercent, 1); - break; - case 0x2030: - appendTo.add(kPerMill, 1); - break; - case 0x2D: - appendTo.add(kNegative, 1); - break; - case 0x2B: - appendTo.add(kPositive, 1); - break; - case 0xA4: - { - if (tokenSize - 1 > 3) { - status = U_PARSE_ERROR; - return appendTo; - } - appendTo.add(kCurrency, tokenSize - 1); - } - break; - default: - appendTo.addLiteral(&token, 0, 1); - break; - } - } - return appendTo; -} - -AffixPatternIterator & -AffixPattern::iterator(AffixPatternIterator &result) const { - result.nextLiteralIndex = 0; - result.lastLiteralLength = 0; - result.nextTokenIndex = 0; - result.tokens = &tokens; - result.literals = &literals; - return result; -} - -UBool -AffixPatternIterator::nextToken() { - int32_t tlen = tokens->length(); - if (nextTokenIndex == tlen) { - return FALSE; - } - ++nextTokenIndex; - const UChar *tokenBuffer = tokens->getBuffer(); - if (UNPACK_TOKEN(tokenBuffer[nextTokenIndex - 1]) == - AffixPattern::kLiteral) { - while (nextTokenIndex < tlen && - UNPACK_LONG(tokenBuffer[nextTokenIndex])) { - ++nextTokenIndex; - } - lastLiteralLength = 0; - int32_t i = nextTokenIndex - 1; - for (; UNPACK_LONG(tokenBuffer[i]); --i) { - lastLiteralLength <<= 8; - lastLiteralLength |= UNPACK_LENGTH(tokenBuffer[i]); - } - lastLiteralLength <<= 8; - lastLiteralLength |= UNPACK_LENGTH(tokenBuffer[i]); - nextLiteralIndex += lastLiteralLength; - } - return TRUE; -} - -AffixPattern::ETokenType -AffixPatternIterator::getTokenType() const { - return UNPACK_TOKEN(tokens->charAt(nextTokenIndex - 1)); -} - -UnicodeString & -AffixPatternIterator::getLiteral(UnicodeString &result) const { - const UChar *buffer = literals->getBuffer(); - result.setTo(buffer + (nextLiteralIndex - lastLiteralLength), lastLiteralLength); - return result; -} - -int32_t -AffixPatternIterator::getTokenLength() const { - const UChar *tokenBuffer = tokens->getBuffer(); - AffixPattern::ETokenType type = UNPACK_TOKEN(tokenBuffer[nextTokenIndex - 1]); - return type == AffixPattern::kLiteral ? lastLiteralLength : UNPACK_LENGTH(tokenBuffer[nextTokenIndex - 1]); -} - -AffixPatternParser::AffixPatternParser() - : fPercent(gPercent), fPermill(gPerMill), fNegative(gNegative), fPositive(gPositive) { -} - -AffixPatternParser::AffixPatternParser( - const DecimalFormatSymbols &symbols) { - setDecimalFormatSymbols(symbols); -} - -void -AffixPatternParser::setDecimalFormatSymbols( - const DecimalFormatSymbols &symbols) { - fPercent = symbols.getConstSymbol(DecimalFormatSymbols::kPercentSymbol); - fPermill = symbols.getConstSymbol(DecimalFormatSymbols::kPerMillSymbol); - fNegative = symbols.getConstSymbol(DecimalFormatSymbols::kMinusSignSymbol); - fPositive = symbols.getConstSymbol(DecimalFormatSymbols::kPlusSignSymbol); -} - -PluralAffix & -AffixPatternParser::parse( - const AffixPattern &affixPattern, - const CurrencyAffixInfo ¤cyAffixInfo, - PluralAffix &appendTo, - UErrorCode &status) const { - if (U_FAILURE(status)) { - return appendTo; - } - AffixPatternIterator iter; - affixPattern.iterator(iter); - UnicodeString literal; - while (iter.nextToken()) { - switch (iter.getTokenType()) { - case AffixPattern::kPercent: - appendTo.append(fPercent, UNUM_PERCENT_FIELD); - break; - case AffixPattern::kPerMill: - appendTo.append(fPermill, UNUM_PERMILL_FIELD); - break; - case AffixPattern::kNegative: - appendTo.append(fNegative, UNUM_SIGN_FIELD); - break; - case AffixPattern::kPositive: - appendTo.append(fPositive, UNUM_SIGN_FIELD); - break; - case AffixPattern::kCurrency: - switch (iter.getTokenLength()) { - case 1: - appendTo.append( - currencyAffixInfo.getSymbol(), UNUM_CURRENCY_FIELD); - break; - case 2: - appendTo.append( - currencyAffixInfo.getISO(), UNUM_CURRENCY_FIELD); - break; - case 3: - appendTo.append( - currencyAffixInfo.getLong(), UNUM_CURRENCY_FIELD, status); - break; - default: - U_ASSERT(FALSE); - break; - } - break; - case AffixPattern::kLiteral: - appendTo.append(iter.getLiteral(literal)); - break; - default: - U_ASSERT(FALSE); - break; - } - } - return appendTo; -} - - -U_NAMESPACE_END -#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/affixpatternparser.h b/icu4c/source/i18n/affixpatternparser.h deleted file mode 100644 index 66cbae31fe..0000000000 --- a/icu4c/source/i18n/affixpatternparser.h +++ /dev/null @@ -1,402 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* Copyright (C) 2015, International Business Machines -* Corporation and others. All Rights Reserved. -******************************************************************************* -* affixpatternparser.h -* -* created on: 2015jan06 -* created by: Travis Keep -*/ - -#ifndef __AFFIX_PATTERN_PARSER_H__ -#define __AFFIX_PATTERN_PARSER_H__ - -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING - -#include "unicode/unistr.h" -#include "unicode/uobject.h" -#include "pluralaffix.h" - -U_NAMESPACE_BEGIN - -class PluralRules; -class FixedPrecision; -class DecimalFormatSymbols; - -/** - * A representation of the various forms of a particular currency according - * to some locale and usage context. - * - * Includes the symbol, ISO code form, and long form(s) of the currency name - * for each plural variation. - */ -class U_I18N_API CurrencyAffixInfo : public UMemory { -public: - /** - * Symbol is \u00a4; ISO form is \u00a4\u00a4; - * long form is \u00a4\u00a4\u00a4. - */ - CurrencyAffixInfo(); - - const UnicodeString &getSymbol() const { return fSymbol; } - const UnicodeString &getISO() const { return fISO; } - const PluralAffix &getLong() const { return fLong; } - void setSymbol(const UnicodeString &symbol) { - fSymbol = symbol; - fIsDefault = FALSE; - } - void setISO(const UnicodeString &iso) { - fISO = iso; - fIsDefault = FALSE; - } - UBool - equals(const CurrencyAffixInfo &other) const { - return (fSymbol == other.fSymbol) - && (fISO == other.fISO) - && (fLong.equals(other.fLong)) - && (fIsDefault == other.fIsDefault); - } - - /** - * Intializes this instance. - * - * @param locale the locale for the currency forms. - * @param rules The plural rules for the locale. - * @param currency the null terminated, 3 character ISO code of the - * currency. If NULL, resets this instance as if it were just created. - * In this case, the first 2 parameters may be NULL as well. - * @param status any error returned here. - */ - void set( - const char *locale, const PluralRules *rules, - const UChar *currency, UErrorCode &status); - - /** - * Returns true if this instance is the default. That is has no real - * currency. For instance never initialized with set() - * or reset with set(NULL, NULL, NULL, status). - */ - UBool isDefault() const { return fIsDefault; } - - /** - * Adjusts the precision used for a particular currency. - * @param currency the null terminated, 3 character ISO code of the - * currency. - * @param usage the usage of the currency - * @param precision min/max fraction digits and rounding increment - * adjusted. - * @params status any error reported here. - */ - static void adjustPrecision( - const UChar *currency, const UCurrencyUsage usage, - FixedPrecision &precision, UErrorCode &status); - -private: - /** - * The symbol form of the currency. - */ - UnicodeString fSymbol; - - /** - * The ISO form of the currency, usually three letter abbreviation. - */ - UnicodeString fISO; - - /** - * The long forms of the currency keyed by plural variation. - */ - PluralAffix fLong; - - UBool fIsDefault; - -}; - -class AffixPatternIterator; - -/** - * A locale agnostic representation of an affix pattern. - */ -class U_I18N_API AffixPattern : public UMemory { -public: - - /** - * The token types that can appear in an affix pattern. - */ - enum ETokenType { - kLiteral, - kPercent, - kPerMill, - kCurrency, - kNegative, - kPositive - }; - - /** - * An empty affix pattern. - */ - AffixPattern() - : tokens(), literals(), hasCurrencyToken(FALSE), - hasPercentToken(FALSE), hasPermillToken(FALSE), char32Count(0) { - } - - /** - * Adds a string literal to this affix pattern. - */ - void addLiteral(const UChar *, int32_t start, int32_t len); - - /** - * Adds a token to this affix pattern. t must not be kLiteral as - * the addLiteral() method adds literals. - * @param t the token type to add - */ - void add(ETokenType t); - - /** - * Adds a currency token with specific count to this affix pattern. - * @param count the token count. Used to distinguish between - * one, two, or three currency symbols. Note that adding a currency - * token with count=2 (Use ISO code) is different than adding two - * currency tokens each with count=1 (two currency symbols). - */ - void addCurrency(uint8_t count); - - /** - * Makes this instance be an empty affix pattern. - */ - void remove(); - - /** - * Provides an iterator over the tokens in this instance. - * @param result this is initialized to point just before the - * first token of this instance. Caller must call nextToken() - * on the iterator once it is set up to have it actually point - * to the first token. This first call to nextToken() will return - * FALSE if the AffixPattern being iterated over is empty. - * @return result - */ - AffixPatternIterator &iterator(AffixPatternIterator &result) const; - - /** - * Returns TRUE if this instance has currency tokens in it. - */ - UBool usesCurrency() const { - return hasCurrencyToken; - } - - UBool usesPercent() const { - return hasPercentToken; - } - - UBool usesPermill() const { - return hasPermillToken; - } - - /** - * Returns the number of code points a string of this instance - * would have if none of the special tokens were escaped. - * Used to compute the padding size. - */ - int32_t countChar32() const { - return char32Count; - } - - /** - * Appends other to this instance mutating this instance in place. - * @param other The pattern appended to the end of this one. - * @return a reference to this instance for chaining. - */ - AffixPattern &append(const AffixPattern &other); - - /** - * Converts this AffixPattern back into a user string. - * It is the inverse of parseUserAffixString. - */ - UnicodeString &toUserString(UnicodeString &appendTo) const; - - /** - * Converts this AffixPattern back into a string. - * It is the inverse of parseAffixString. - */ - UnicodeString &toString(UnicodeString &appendTo) const; - - /** - * Parses an affix pattern string appending it to an AffixPattern. - * Parses affix pattern strings produced from using - * DecimalFormatPatternParser to parse a format pattern. Affix patterns - * include the positive prefix and suffix and the negative prefix - * and suffix. This method expects affix patterns strings to be in the - * same format that DecimalFormatPatternParser produces. Namely special - * characters in the affix that correspond to a field type must be - * prefixed with an apostrophe ('). These special character sequences - * inluce minus (-), percent (%), permile (U+2030), plus (+), - * short currency (U+00a4), medium currency (u+00a4 * 2), - * long currency (u+a4 * 3), and apostrophe (') - * (apostrophe does not correspond to a field type but has to be escaped - * because it itself is the escape character). - * Since the expansion of these special character - * sequences is locale dependent, these sequences are not expanded in - * an AffixPattern instance. - * If these special characters are not prefixed with an apostrophe in - * the affix pattern string, then they are treated verbatim just as - * any other character. If an apostrophe prefixes a non special - * character in the affix pattern, the apostrophe is simply ignored. - * - * @param affixStr the string from DecimalFormatPatternParser - * @param appendTo parsed result appended here. - * @param status any error parsing returned here. - */ - static AffixPattern &parseAffixString( - const UnicodeString &affixStr, - AffixPattern &appendTo, - UErrorCode &status); - - /** - * Parses an affix pattern string appending it to an AffixPattern. - * Parses affix pattern strings as the user would supply them. - * In this function, quoting makes special characters like normal - * characters whereas in parseAffixString, quoting makes special - * characters special. - * - * @param affixStr the string from the user - * @param appendTo parsed result appended here. - * @param status any error parsing returned here. - */ - static AffixPattern &parseUserAffixString( - const UnicodeString &affixStr, - AffixPattern &appendTo, - UErrorCode &status); - - UBool equals(const AffixPattern &other) const { - return (tokens == other.tokens) - && (literals == other.literals) - && (hasCurrencyToken == other.hasCurrencyToken) - && (hasPercentToken == other.hasPercentToken) - && (hasPermillToken == other.hasPermillToken) - && (char32Count == other.char32Count); - } - -private: - /* - * Tokens stored here. Each UChar generally stands for one token. A - * Each token is of form 'etttttttllllllll' llllllll is the length of - * the token and ranges from 0-255. ttttttt is the token type and ranges - * from 0-127. If e is set it means this is an extendo token (to be - * described later). To accomodate token lengths above 255, each normal - * token (e=0) can be followed by 0 or more extendo tokens (e=1) with - * the same type. Right now only kLiteral Tokens have extendo tokens. - * Each extendo token provides the next 8 higher bits for the length. - * If a kLiteral token is followed by 2 extendo tokens then, then the - * llllllll of the next extendo token contains bits 8-15 of the length - * and the last extendo token contains bits 16-23 of the length. - */ - UnicodeString tokens; - - /* - * The characters of the kLiteral tokens are concatenated together here. - * The first characters go with the first kLiteral token, the next - * characters go with the next kLiteral token etc. - */ - UnicodeString literals; - UBool hasCurrencyToken; - UBool hasPercentToken; - UBool hasPermillToken; - int32_t char32Count; - void add(ETokenType t, uint8_t count); - -}; - -/** - * An iterator over the tokens in an AffixPattern instance. - */ -class U_I18N_API AffixPatternIterator : public UMemory { -public: - - /** - * Using an iterator without first calling iterator on an AffixPattern - * instance to initialize the iterator results in - * undefined behavior. - */ - AffixPatternIterator() : nextLiteralIndex(0), lastLiteralLength(0), nextTokenIndex(0), tokens(NULL), literals(NULL) { } - /** - * Advances this iterator to the next token. Returns FALSE when there - * are no more tokens. Calling the other methods after nextToken() - * returns FALSE results in undefined behavior. - */ - UBool nextToken(); - - /** - * Returns the type of token. - */ - AffixPattern::ETokenType getTokenType() const; - - /** - * For literal tokens, returns the literal string. Calling this for - * other token types results in undefined behavior. - * @param result replaced with a read-only alias to the literal string. - * @return result - */ - UnicodeString &getLiteral(UnicodeString &result) const; - - /** - * Returns the token length. Usually 1, but for currency tokens may - * be 2 for ISO code and 3 for long form. - */ - int32_t getTokenLength() const; -private: - int32_t nextLiteralIndex; - int32_t lastLiteralLength; - int32_t nextTokenIndex; - const UnicodeString *tokens; - const UnicodeString *literals; - friend class AffixPattern; - AffixPatternIterator(const AffixPatternIterator &); - AffixPatternIterator &operator=(const AffixPatternIterator &); -}; - -/** - * A locale aware class that converts locale independent AffixPattern - * instances into locale dependent PluralAffix instances. - */ -class U_I18N_API AffixPatternParser : public UMemory { -public: -AffixPatternParser(); -AffixPatternParser(const DecimalFormatSymbols &symbols); -void setDecimalFormatSymbols(const DecimalFormatSymbols &symbols); - -/** - * Parses affixPattern appending the result to appendTo. - * @param affixPattern The affix pattern. - * @param currencyAffixInfo contains the currency forms. - * @param appendTo The result of parsing affixPattern is appended here. - * @param status any error returned here. - * @return appendTo. - */ -PluralAffix &parse( - const AffixPattern &affixPattern, - const CurrencyAffixInfo ¤cyAffixInfo, - PluralAffix &appendTo, - UErrorCode &status) const; - -UBool equals(const AffixPatternParser &other) const { - return (fPercent == other.fPercent) - && (fPermill == other.fPermill) - && (fNegative == other.fNegative) - && (fPositive == other.fPositive); -} - -private: -UnicodeString fPercent; -UnicodeString fPermill; -UnicodeString fNegative; -UnicodeString fPositive; -}; - - -U_NAMESPACE_END -#endif /* #if !UCONFIG_NO_FORMATTING */ -#endif // __AFFIX_PATTERN_PARSER_H__ diff --git a/icu4c/source/i18n/compactdecimalformat.cpp b/icu4c/source/i18n/compactdecimalformat.cpp index 99fbfb54bd..b2e9fc5fe2 100644 --- a/icu4c/source/i18n/compactdecimalformat.cpp +++ b/icu4c/source/i18n/compactdecimalformat.cpp @@ -9,11 +9,24 @@ // Helpful in toString methods and elsewhere. #define UNISTR_FROM_STRING_EXPLICIT +#include "unicode/compactdecimalformat.h" + using namespace icu; +CompactDecimalFormat* +CompactDecimalFormat::createInstance(const Locale& inLocale, UNumberCompactStyle style, + UErrorCode& status) {} +CompactDecimalFormat::CompactDecimalFormat(const CompactDecimalFormat& source) = default; +CompactDecimalFormat::~CompactDecimalFormat() = default; + +CompactDecimalFormat& CompactDecimalFormat::operator=(const CompactDecimalFormat& rhs) {} + +UClassID CompactDecimalFormat::getStaticClassID() {} + +UClassID CompactDecimalFormat::getDynamicClassID() const {} #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/decimalformatpattern.cpp b/icu4c/source/i18n/decimalformatpattern.cpp deleted file mode 100644 index 4ee5e33441..0000000000 --- a/icu4c/source/i18n/decimalformatpattern.cpp +++ /dev/null @@ -1,656 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* Copyright (C) 1997-2015, International Business Machines Corporation and * -* others. All Rights Reserved. * -******************************************************************************* -*/ - -#include "uassert.h" -#include "decimalformatpattern.h" - -#if !UCONFIG_NO_FORMATTING - -#include "unicode/dcfmtsym.h" -#include "unicode/format.h" -#include "unicode/utf16.h" -#include "decimalformatpatternimpl.h" - - -#ifdef FMT_DEBUG -#define debug(x) printf("%s:%d: %s\n", __FILE__,__LINE__, x); -#else -#define debug(x) -#endif - -U_NAMESPACE_BEGIN - -// TODO: Travis Keep: Copied from numfmt.cpp -static int32_t kDoubleIntegerDigits = 309; -static int32_t kDoubleFractionDigits = 340; - - -// TODO: Travis Keep: Copied from numfmt.cpp -static int32_t gDefaultMaxIntegerDigits = 2000000000; - -// TODO: Travis Keep: This function was copied from format.cpp -static void syntaxError(const UnicodeString& pattern, - int32_t pos, - UParseError& parseError) { - parseError.offset = pos; - parseError.line=0; // we are not using line number - - // for pre-context - int32_t start = (pos < U_PARSE_CONTEXT_LEN)? 0 : (pos - (U_PARSE_CONTEXT_LEN-1 - /* subtract 1 so that we have room for null*/)); - int32_t stop = pos; - pattern.extract(start,stop-start,parseError.preContext,0); - //null terminate the buffer - parseError.preContext[stop-start] = 0; - - //for post-context - start = pattern.moveIndex32(pos, 1); - stop = pos + U_PARSE_CONTEXT_LEN - 1; - if (stop > pattern.length()) { - stop = pattern.length(); - } - pattern.extract(start, stop - start, parseError.postContext, 0); - //null terminate the buffer - parseError.postContext[stop-start]= 0; -} - -DecimalFormatPattern::DecimalFormatPattern() - : fMinimumIntegerDigits(1), - fMaximumIntegerDigits(gDefaultMaxIntegerDigits), - fMinimumFractionDigits(0), - fMaximumFractionDigits(3), - fUseSignificantDigits(FALSE), - fMinimumSignificantDigits(1), - fMaximumSignificantDigits(6), - fUseExponentialNotation(FALSE), - fMinExponentDigits(0), - fExponentSignAlwaysShown(FALSE), - fCurrencySignCount(fgCurrencySignCountZero), - fGroupingUsed(TRUE), - fGroupingSize(0), - fGroupingSize2(0), - fMultiplier(1), - fDecimalSeparatorAlwaysShown(FALSE), - fFormatWidth(0), - fRoundingIncrementUsed(FALSE), - fRoundingIncrement(), - fPad(kDefaultPad), - fNegPatternsBogus(TRUE), - fPosPatternsBogus(TRUE), - fNegPrefixPattern(), - fNegSuffixPattern(), - fPosPrefixPattern(), - fPosSuffixPattern(), - fPadPosition(DecimalFormatPattern::kPadBeforePrefix) { -} - - -DecimalFormatPatternParser::DecimalFormatPatternParser() : - fZeroDigit(kPatternZeroDigit), - fSigDigit(kPatternSignificantDigit), - fGroupingSeparator((UChar)kPatternGroupingSeparator), - fDecimalSeparator((UChar)kPatternDecimalSeparator), - fPercent((UChar)kPatternPercent), - fPerMill((UChar)kPatternPerMill), - fDigit((UChar)kPatternDigit), - fSeparator((UChar)kPatternSeparator), - fExponent((UChar)kPatternExponent), - fPlus((UChar)kPatternPlus), - fMinus((UChar)kPatternMinus), - fPadEscape((UChar)kPatternPadEscape) { -} - -void DecimalFormatPatternParser::useSymbols( - const DecimalFormatSymbols& symbols) { - fZeroDigit = symbols.getConstSymbol( - DecimalFormatSymbols::kZeroDigitSymbol).char32At(0); - fSigDigit = symbols.getConstSymbol( - DecimalFormatSymbols::kSignificantDigitSymbol).char32At(0); - fGroupingSeparator = symbols.getConstSymbol( - DecimalFormatSymbols::kGroupingSeparatorSymbol); - fDecimalSeparator = symbols.getConstSymbol( - DecimalFormatSymbols::kDecimalSeparatorSymbol); - fPercent = symbols.getConstSymbol( - DecimalFormatSymbols::kPercentSymbol); - fPerMill = symbols.getConstSymbol( - DecimalFormatSymbols::kPerMillSymbol); - fDigit = symbols.getConstSymbol( - DecimalFormatSymbols::kDigitSymbol); - fSeparator = symbols.getConstSymbol( - DecimalFormatSymbols::kPatternSeparatorSymbol); - fExponent = symbols.getConstSymbol( - DecimalFormatSymbols::kExponentialSymbol); - fPlus = symbols.getConstSymbol( - DecimalFormatSymbols::kPlusSignSymbol); - fMinus = symbols.getConstSymbol( - DecimalFormatSymbols::kMinusSignSymbol); - fPadEscape = symbols.getConstSymbol( - DecimalFormatSymbols::kPadEscapeSymbol); -} - -void -DecimalFormatPatternParser::applyPatternWithoutExpandAffix( - const UnicodeString& pattern, - DecimalFormatPattern& out, - UParseError& parseError, - UErrorCode& status) { - if (U_FAILURE(status)) - { - return; - } - out = DecimalFormatPattern(); - - // Clear error struct - parseError.offset = -1; - parseError.preContext[0] = parseError.postContext[0] = (UChar)0; - - // TODO: Travis Keep: This won't always work. - UChar nineDigit = (UChar)(fZeroDigit + 9); - int32_t digitLen = fDigit.length(); - int32_t groupSepLen = fGroupingSeparator.length(); - int32_t decimalSepLen = fDecimalSeparator.length(); - - int32_t pos = 0; - int32_t patLen = pattern.length(); - // Part 0 is the positive pattern. Part 1, if present, is the negative - // pattern. - for (int32_t part=0; part<2 && pos 0 || sigDigitCount > 0) { - ++digitRightCount; - } else { - ++digitLeftCount; - } - if (groupingCount >= 0 && decimalPos < 0) { - ++groupingCount; - } - pos += digitLen; - } else if ((ch >= fZeroDigit && ch <= nineDigit) || - ch == fSigDigit) { - if (digitRightCount > 0) { - // Unexpected '0' - debug("Unexpected '0'") - status = U_UNEXPECTED_TOKEN; - syntaxError(pattern,pos,parseError); - return; - } - if (ch == fSigDigit) { - ++sigDigitCount; - } else { - if (ch != fZeroDigit && roundingPos < 0) { - roundingPos = digitLeftCount + zeroDigitCount; - } - if (roundingPos >= 0) { - roundingInc.append((char)(ch - fZeroDigit + '0')); - } - ++zeroDigitCount; - } - if (groupingCount >= 0 && decimalPos < 0) { - ++groupingCount; - } - pos += U16_LENGTH(ch); - } else if (pattern.compare(pos, groupSepLen, fGroupingSeparator) == 0) { - if (decimalPos >= 0) { - // Grouping separator after decimal - debug("Grouping separator after decimal") - status = U_UNEXPECTED_TOKEN; - syntaxError(pattern,pos,parseError); - return; - } - groupingCount2 = groupingCount; - groupingCount = 0; - pos += groupSepLen; - } else if (pattern.compare(pos, decimalSepLen, fDecimalSeparator) == 0) { - if (decimalPos >= 0) { - // Multiple decimal separators - debug("Multiple decimal separators") - status = U_MULTIPLE_DECIMAL_SEPARATORS; - syntaxError(pattern,pos,parseError); - return; - } - // Intentionally incorporate the digitRightCount, - // even though it is illegal for this to be > 0 - // at this point. We check pattern syntax below. - decimalPos = digitLeftCount + zeroDigitCount + digitRightCount; - pos += decimalSepLen; - } else { - if (pattern.compare(pos, fExponent.length(), fExponent) == 0) { - if (expDigits >= 0) { - // Multiple exponential symbols - debug("Multiple exponential symbols") - status = U_MULTIPLE_EXPONENTIAL_SYMBOLS; - syntaxError(pattern,pos,parseError); - return; - } - if (groupingCount >= 0) { - // Grouping separator in exponential pattern - debug("Grouping separator in exponential pattern") - status = U_MALFORMED_EXPONENTIAL_PATTERN; - syntaxError(pattern,pos,parseError); - return; - } - pos += fExponent.length(); - // Check for positive prefix - if (pos < patLen - && pattern.compare(pos, fPlus.length(), fPlus) == 0) { - expSignAlways = TRUE; - pos += fPlus.length(); - } - // Use lookahead to parse out the exponential part of the - // pattern, then jump into suffix subpart. - expDigits = 0; - while (pos < patLen && - pattern.char32At(pos) == fZeroDigit) { - ++expDigits; - pos += U16_LENGTH(fZeroDigit); - } - - // 1. Require at least one mantissa pattern digit - // 2. Disallow "#+ @" in mantissa - // 3. Require at least one exponent pattern digit - if (((digitLeftCount + zeroDigitCount) < 1 && - (sigDigitCount + digitRightCount) < 1) || - (sigDigitCount > 0 && digitLeftCount > 0) || - expDigits < 1) { - // Malformed exponential pattern - debug("Malformed exponential pattern") - status = U_MALFORMED_EXPONENTIAL_PATTERN; - syntaxError(pattern,pos,parseError); - return; - } - } - // Transition to suffix subpart - subpart = 2; // suffix subpart - affix = &suffix; - sub0Limit = pos; - continue; - } - break; - case 1: // Prefix subpart - case 2: // Suffix subpart - // Process the prefix / suffix characters - // Process unquoted characters seen in prefix or suffix - // subpart. - - // Several syntax characters implicitly begins the - // next subpart if we are in the prefix; otherwise - // they are illegal if unquoted. - if (!pattern.compare(pos, digitLen, fDigit) || - !pattern.compare(pos, groupSepLen, fGroupingSeparator) || - !pattern.compare(pos, decimalSepLen, fDecimalSeparator) || - (ch >= fZeroDigit && ch <= nineDigit) || - ch == fSigDigit) { - if (subpart == 1) { // prefix subpart - subpart = 0; // pattern proper subpart - sub0Start = pos; // Reprocess this character - continue; - } else { - status = U_UNQUOTED_SPECIAL; - syntaxError(pattern,pos,parseError); - return; - } - } else if (ch == kCurrencySign) { - affix->append(kQuote); // Encode currency - // Use lookahead to determine if the currency sign is - // doubled or not. - U_ASSERT(U16_LENGTH(kCurrencySign) == 1); - if ((pos+1) < pattern.length() && pattern[pos+1] == kCurrencySign) { - affix->append(kCurrencySign); - ++pos; // Skip over the doubled character - if ((pos+1) < pattern.length() && - pattern[pos+1] == kCurrencySign) { - affix->append(kCurrencySign); - ++pos; // Skip over the doubled character - out.fCurrencySignCount = fgCurrencySignCountInPluralFormat; - } else { - out.fCurrencySignCount = fgCurrencySignCountInISOFormat; - } - } else { - out.fCurrencySignCount = fgCurrencySignCountInSymbolFormat; - } - // Fall through to append(ch) - } else if (ch == kQuote) { - // A quote outside quotes indicates either the opening - // quote or two quotes, which is a quote literal. That is, - // we have the first quote in 'do' or o''clock. - U_ASSERT(U16_LENGTH(kQuote) == 1); - ++pos; - if (pos < pattern.length() && pattern[pos] == kQuote) { - affix->append(kQuote); // Encode quote - // Fall through to append(ch) - } else { - subpart += 2; // open quote - continue; - } - } else if (pattern.compare(pos, fSeparator.length(), fSeparator) == 0) { - // Don't allow separators in the prefix, and don't allow - // separators in the second pattern (part == 1). - if (subpart == 1 || part == 1) { - // Unexpected separator - debug("Unexpected separator") - status = U_UNEXPECTED_TOKEN; - syntaxError(pattern,pos,parseError); - return; - } - sub2Limit = pos; - isPartDone = TRUE; // Go to next part - pos += fSeparator.length(); - break; - } else if (pattern.compare(pos, fPercent.length(), fPercent) == 0) { - // Next handle characters which are appended directly. - if (multiplier != 1) { - // Too many percent/perMill characters - debug("Too many percent characters") - status = U_MULTIPLE_PERCENT_SYMBOLS; - syntaxError(pattern,pos,parseError); - return; - } - affix->append(kQuote); // Encode percent/perMill - affix->append(kPatternPercent); // Use unlocalized pattern char - multiplier = 100; - pos += fPercent.length(); - break; - } else if (pattern.compare(pos, fPerMill.length(), fPerMill) == 0) { - // Next handle characters which are appended directly. - if (multiplier != 1) { - // Too many percent/perMill characters - debug("Too many perMill characters") - status = U_MULTIPLE_PERMILL_SYMBOLS; - syntaxError(pattern,pos,parseError); - return; - } - affix->append(kQuote); // Encode percent/perMill - affix->append(kPatternPerMill); // Use unlocalized pattern char - multiplier = 1000; - pos += fPerMill.length(); - break; - } else if (pattern.compare(pos, fPadEscape.length(), fPadEscape) == 0) { - if (padPos >= 0 || // Multiple pad specifiers - (pos+1) == pattern.length()) { // Nothing after padEscape - debug("Multiple pad specifiers") - status = U_MULTIPLE_PAD_SPECIFIERS; - syntaxError(pattern,pos,parseError); - return; - } - padPos = pos; - pos += fPadEscape.length(); - padChar = pattern.char32At(pos); - pos += U16_LENGTH(padChar); - break; - } else if (pattern.compare(pos, fMinus.length(), fMinus) == 0) { - affix->append(kQuote); // Encode minus - affix->append(kPatternMinus); - pos += fMinus.length(); - break; - } else if (pattern.compare(pos, fPlus.length(), fPlus) == 0) { - affix->append(kQuote); // Encode plus - affix->append(kPatternPlus); - pos += fPlus.length(); - break; - } - // Unquoted, non-special characters fall through to here, as - // well as other code which needs to append something to the - // affix. - affix->append(ch); - pos += U16_LENGTH(ch); - break; - case 3: // Prefix subpart, in quote - case 4: // Suffix subpart, in quote - // A quote within quotes indicates either the closing - // quote or two quotes, which is a quote literal. That is, - // we have the second quote in 'do' or 'don''t'. - if (ch == kQuote) { - ++pos; - if (pos < pattern.length() && pattern[pos] == kQuote) { - affix->append(kQuote); // Encode quote - // Fall through to append(ch) - } else { - subpart -= 2; // close quote - continue; - } - } - affix->append(ch); - pos += U16_LENGTH(ch); - break; - } - } - - if (sub0Limit == 0) { - sub0Limit = pattern.length(); - } - - if (sub2Limit == 0) { - sub2Limit = pattern.length(); - } - - /* Handle patterns with no '0' pattern character. These patterns - * are legal, but must be recodified to make sense. "##.###" -> - * "#0.###". ".###" -> ".0##". - * - * We allow patterns of the form "####" to produce a zeroDigitCount - * of zero (got that?); although this seems like it might make it - * possible for format() to produce empty strings, format() checks - * for this condition and outputs a zero digit in this situation. - * Having a zeroDigitCount of zero yields a minimum integer digits - * of zero, which allows proper round-trip patterns. We don't want - * "#" to become "#0" when toPattern() is called (even though that's - * what it really is, semantically). - */ - if (zeroDigitCount == 0 && sigDigitCount == 0 && - digitLeftCount > 0 && decimalPos >= 0) { - // Handle "###.###" and "###." and ".###" - int n = decimalPos; - if (n == 0) - ++n; // Handle ".###" - digitRightCount = digitLeftCount - n; - digitLeftCount = n - 1; - zeroDigitCount = 1; - } - - // Do syntax checking on the digits, decimal points, and quotes. - if ((decimalPos < 0 && digitRightCount > 0 && sigDigitCount == 0) || - (decimalPos >= 0 && - (sigDigitCount > 0 || - decimalPos < digitLeftCount || - decimalPos > (digitLeftCount + zeroDigitCount))) || - groupingCount == 0 || groupingCount2 == 0 || - (sigDigitCount > 0 && zeroDigitCount > 0) || - subpart > 2) - { // subpart > 2 == unmatched quote - debug("Syntax error") - status = U_PATTERN_SYNTAX_ERROR; - syntaxError(pattern,pos,parseError); - return; - } - - // Make sure pad is at legal position before or after affix. - if (padPos >= 0) { - if (padPos == start) { - padPos = DecimalFormatPattern::kPadBeforePrefix; - } else if (padPos+2 == sub0Start) { - padPos = DecimalFormatPattern::kPadAfterPrefix; - } else if (padPos == sub0Limit) { - padPos = DecimalFormatPattern::kPadBeforeSuffix; - } else if (padPos+2 == sub2Limit) { - padPos = DecimalFormatPattern::kPadAfterSuffix; - } else { - // Illegal pad position - debug("Illegal pad position") - status = U_ILLEGAL_PAD_POSITION; - syntaxError(pattern,pos,parseError); - return; - } - } - - if (part == 0) { - out.fPosPatternsBogus = FALSE; - out.fPosPrefixPattern = prefix; - out.fPosSuffixPattern = suffix; - out.fNegPatternsBogus = TRUE; - out.fNegPrefixPattern.remove(); - out.fNegSuffixPattern.remove(); - - out.fUseExponentialNotation = (expDigits >= 0); - if (out.fUseExponentialNotation) { - out.fMinExponentDigits = expDigits; - } - out.fExponentSignAlwaysShown = expSignAlways; - int32_t digitTotalCount = digitLeftCount + zeroDigitCount + digitRightCount; - // The effectiveDecimalPos is the position the decimal is at or - // would be at if there is no decimal. Note that if - // decimalPos<0, then digitTotalCount == digitLeftCount + - // zeroDigitCount. - int32_t effectiveDecimalPos = decimalPos >= 0 ? decimalPos : digitTotalCount; - UBool isSigDig = (sigDigitCount > 0); - out.fUseSignificantDigits = isSigDig; - if (isSigDig) { - out.fMinimumSignificantDigits = sigDigitCount; - out.fMaximumSignificantDigits = sigDigitCount + digitRightCount; - } else { - int32_t minInt = effectiveDecimalPos - digitLeftCount; - out.fMinimumIntegerDigits = minInt; - out.fMaximumIntegerDigits = out.fUseExponentialNotation - ? digitLeftCount + out.fMinimumIntegerDigits - : gDefaultMaxIntegerDigits; - out.fMaximumFractionDigits = decimalPos >= 0 - ? (digitTotalCount - decimalPos) : 0; - out.fMinimumFractionDigits = decimalPos >= 0 - ? (digitLeftCount + zeroDigitCount - decimalPos) : 0; - } - out.fGroupingUsed = groupingCount > 0; - out.fGroupingSize = (groupingCount > 0) ? groupingCount : 0; - out.fGroupingSize2 = (groupingCount2 > 0 && groupingCount2 != groupingCount) - ? groupingCount2 : 0; - out.fMultiplier = multiplier; - out.fDecimalSeparatorAlwaysShown = decimalPos == 0 - || decimalPos == digitTotalCount; - if (padPos >= 0) { - out.fPadPosition = (DecimalFormatPattern::EPadPosition) padPos; - // To compute the format width, first set up sub0Limit - - // sub0Start. Add in prefix/suffix length later. - - // fFormatWidth = prefix.length() + suffix.length() + - // sub0Limit - sub0Start; - out.fFormatWidth = sub0Limit - sub0Start; - out.fPad = padChar; - } else { - out.fFormatWidth = 0; - } - if (roundingPos >= 0) { - out.fRoundingIncrementUsed = TRUE; - roundingInc.setDecimalAt(effectiveDecimalPos - roundingPos); - out.fRoundingIncrement = roundingInc; - } else { - out.fRoundingIncrementUsed = FALSE; - } - } else { - out.fNegPatternsBogus = FALSE; - out.fNegPrefixPattern = prefix; - out.fNegSuffixPattern = suffix; - } - } - - if (pattern.length() == 0) { - out.fNegPatternsBogus = TRUE; - out.fNegPrefixPattern.remove(); - out.fNegSuffixPattern.remove(); - out.fPosPatternsBogus = FALSE; - out.fPosPrefixPattern.remove(); - out.fPosSuffixPattern.remove(); - - out.fMinimumIntegerDigits = 0; - out.fMaximumIntegerDigits = kDoubleIntegerDigits; - out.fMinimumFractionDigits = 0; - out.fMaximumFractionDigits = kDoubleFractionDigits; - - out.fUseExponentialNotation = FALSE; - out.fCurrencySignCount = fgCurrencySignCountZero; - out.fGroupingUsed = FALSE; - out.fGroupingSize = 0; - out.fGroupingSize2 = 0; - out.fMultiplier = 1; - out.fDecimalSeparatorAlwaysShown = FALSE; - out.fFormatWidth = 0; - out.fRoundingIncrementUsed = FALSE; - } - - // If there was no negative pattern, or if the negative pattern is - // identical to the positive pattern, then prepend the minus sign to the - // positive pattern to form the negative pattern. - if (out.fNegPatternsBogus || - (out.fNegPrefixPattern == out.fPosPrefixPattern - && out.fNegSuffixPattern == out.fPosSuffixPattern)) { - out.fNegPatternsBogus = FALSE; - out.fNegSuffixPattern = out.fPosSuffixPattern; - out.fNegPrefixPattern.remove(); - out.fNegPrefixPattern.append(kQuote).append(kPatternMinus) - .append(out.fPosPrefixPattern); - } - // TODO: Deprecate/Remove out.fNegSuffixPattern and 3 other fields. - AffixPattern::parseAffixString( - out.fNegSuffixPattern, out.fNegSuffixAffix, status); - AffixPattern::parseAffixString( - out.fPosSuffixPattern, out.fPosSuffixAffix, status); - AffixPattern::parseAffixString( - out.fNegPrefixPattern, out.fNegPrefixAffix, status); - AffixPattern::parseAffixString( - out.fPosPrefixPattern, out.fPosPrefixAffix, status); -} - -U_NAMESPACE_END - -#endif /* !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/decimalformatpattern.h b/icu4c/source/i18n/decimalformatpattern.h deleted file mode 100644 index fcb55161e6..0000000000 --- a/icu4c/source/i18n/decimalformatpattern.h +++ /dev/null @@ -1,106 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* Copyright (C) 1997-2015, International Business Machines Corporation and * -* others. All Rights Reserved. * -******************************************************************************* -*/ -#ifndef _DECIMAL_FORMAT_PATTERN -#define _DECIMAL_FORMAT_PATTERN - -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING - -#include "unicode/uobject.h" -#include "unicode/unistr.h" -#include "digitlst.h" -#include "affixpatternparser.h" - -U_NAMESPACE_BEGIN - -// currency sign count -enum CurrencySignCount { - fgCurrencySignCountZero, - fgCurrencySignCountInSymbolFormat, - fgCurrencySignCountInISOFormat, - fgCurrencySignCountInPluralFormat -}; - -class DecimalFormatSymbols; - -struct DecimalFormatPattern : public UMemory { - enum EPadPosition { - kPadBeforePrefix, - kPadAfterPrefix, - kPadBeforeSuffix, - kPadAfterSuffix - }; - - DecimalFormatPattern(); - - int32_t fMinimumIntegerDigits; - int32_t fMaximumIntegerDigits; - int32_t fMinimumFractionDigits; - int32_t fMaximumFractionDigits; - UBool fUseSignificantDigits; - int32_t fMinimumSignificantDigits; - int32_t fMaximumSignificantDigits; - UBool fUseExponentialNotation; - int32_t fMinExponentDigits; - UBool fExponentSignAlwaysShown; - int32_t fCurrencySignCount; - UBool fGroupingUsed; - int32_t fGroupingSize; - int32_t fGroupingSize2; - int32_t fMultiplier; - UBool fDecimalSeparatorAlwaysShown; - int32_t fFormatWidth; - UBool fRoundingIncrementUsed; - DigitList fRoundingIncrement; - UChar32 fPad; - UBool fNegPatternsBogus; - UBool fPosPatternsBogus; - UnicodeString fNegPrefixPattern; - UnicodeString fNegSuffixPattern; - UnicodeString fPosPrefixPattern; - UnicodeString fPosSuffixPattern; - AffixPattern fNegPrefixAffix; - AffixPattern fNegSuffixAffix; - AffixPattern fPosPrefixAffix; - AffixPattern fPosSuffixAffix; - EPadPosition fPadPosition; -}; - -class DecimalFormatPatternParser : public UMemory { - public: - DecimalFormatPatternParser(); - void useSymbols(const DecimalFormatSymbols& symbols); - - void applyPatternWithoutExpandAffix( - const UnicodeString& pattern, - DecimalFormatPattern& out, - UParseError& parseError, - UErrorCode& status); - private: - DecimalFormatPatternParser(const DecimalFormatPatternParser&); - DecimalFormatPatternParser& operator=(DecimalFormatPatternParser& rhs); - UChar32 fZeroDigit; - UChar32 fSigDigit; - UnicodeString fGroupingSeparator; - UnicodeString fDecimalSeparator; - UnicodeString fPercent; - UnicodeString fPerMill; - UnicodeString fDigit; - UnicodeString fSeparator; - UnicodeString fExponent; - UnicodeString fPlus; - UnicodeString fMinus; - UnicodeString fPadEscape; -}; - -U_NAMESPACE_END - -#endif /* !UCONFIG_NO_FORMATTING */ -#endif diff --git a/icu4c/source/i18n/decimalformatpatternimpl.h b/icu4c/source/i18n/decimalformatpatternimpl.h deleted file mode 100644 index 8cecc8cca0..0000000000 --- a/icu4c/source/i18n/decimalformatpatternimpl.h +++ /dev/null @@ -1,35 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************** -* Copyright (C) 2015, International Business Machines -* Corporation and others. All Rights Reserved. -******************************************************************************** -* -* File decimalformatpatternimpl.h -******************************************************************************** -*/ - -#ifndef DECIMALFORMATPATTERNIMPL_H -#define DECIMALFORMATPATTERNIMPL_H - -#include "unicode/utypes.h" - -#define kPatternZeroDigit ((UChar)0x0030) /*'0'*/ -#define kPatternSignificantDigit ((UChar)0x0040) /*'@'*/ -#define kPatternGroupingSeparator ((UChar)0x002C) /*','*/ -#define kPatternDecimalSeparator ((UChar)0x002E) /*'.'*/ -#define kPatternPerMill ((UChar)0x2030) -#define kPatternPercent ((UChar)0x0025) /*'%'*/ -#define kPatternDigit ((UChar)0x0023) /*'#'*/ -#define kPatternSeparator ((UChar)0x003B) /*';'*/ -#define kPatternExponent ((UChar)0x0045) /*'E'*/ -#define kPatternPlus ((UChar)0x002B) /*'+'*/ -#define kPatternMinus ((UChar)0x002D) /*'-'*/ -#define kPatternPadEscape ((UChar)0x002A) /*'*'*/ -#define kQuote ((UChar)0x0027) /*'\''*/ - -#define kCurrencySign ((UChar)0x00A4) -#define kDefaultPad ((UChar)0x0020) /* */ - -#endif diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp index c50c2ca083..dac10b80a7 100644 --- a/icu4c/source/i18n/decimfmt.cpp +++ b/icu4c/source/i18n/decimfmt.cpp @@ -89,12 +89,12 @@ UnicodeString& DecimalFormat::format(StringPiece number, UnicodeString& appendTo, FieldPositionIterator* posIter, UErrorCode& status) const {} -UnicodeString& -DecimalFormat::format(const DecimalQuantity& number, UnicodeString& appendTo, FieldPositionIterator* posIter, - UErrorCode& status) const {} +UnicodeString& DecimalFormat::format(const DecimalQuantity& number, UnicodeString& appendTo, + FieldPositionIterator* posIter, UErrorCode& status) const {} -UnicodeString& DecimalFormat::format(const DecimalQuantity& number, UnicodeString& appendTo, FieldPosition& pos, - UErrorCode& status) const {} +UnicodeString& +DecimalFormat::format(const DecimalQuantity& number, UnicodeString& appendTo, FieldPosition& pos, + UErrorCode& status) const {} void DecimalFormat::parse(const UnicodeString& text, Formattable& result, ParsePosition& parsePosition) const {} @@ -227,6 +227,12 @@ void DecimalFormat::setCurrencyUsage(UCurrencyUsage newUsage, UErrorCode* ec) {} UCurrencyUsage DecimalFormat::getCurrencyUsage() const {} +void +DecimalFormat::formatToDecimalQuantity(double number, DecimalQuantity& output, UErrorCode& status) const {} + +void DecimalFormat::formatToDecimalQuantity(const Formattable& number, DecimalQuantity& output, + UErrorCode& status) const {} + number::LocalizedNumberFormatter DecimalFormat::toNumberFormatter() const {} UClassID DecimalFormat::getStaticClassID() {} diff --git a/icu4c/source/i18n/decimfmtimpl.cpp b/icu4c/source/i18n/decimfmtimpl.cpp deleted file mode 100644 index 62bd956fa5..0000000000 --- a/icu4c/source/i18n/decimfmtimpl.cpp +++ /dev/null @@ -1,1597 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* - * Copyright (C) 2015, International Business Machines - * Corporation and others. All Rights Reserved. - * - * file name: decimfmtimpl.cpp - */ - -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING - -#include -#include "unicode/numfmt.h" -#include "unicode/plurrule.h" -#include "unicode/ustring.h" -#include "decimalformatpattern.h" -#include "decimalformatpatternimpl.h" -#include "decimfmtimpl.h" -#include "fphdlimp.h" -#include "plurrule_impl.h" -#include "valueformatter.h" -#include "visibledigits.h" - -U_NAMESPACE_BEGIN - -static const int32_t kMaxScientificIntegerDigits = 8; - -static const int32_t kFormattingPosPrefix = (1 << 0); -static const int32_t kFormattingNegPrefix = (1 << 1); -static const int32_t kFormattingPosSuffix = (1 << 2); -static const int32_t kFormattingNegSuffix = (1 << 3); -static const int32_t kFormattingSymbols = (1 << 4); -static const int32_t kFormattingCurrency = (1 << 5); -static const int32_t kFormattingUsesCurrency = (1 << 6); -static const int32_t kFormattingPluralRules = (1 << 7); -static const int32_t kFormattingAffixParser = (1 << 8); -static const int32_t kFormattingCurrencyAffixInfo = (1 << 9); -static const int32_t kFormattingAll = (1 << 10) - 1; -static const int32_t kFormattingAffixes = - kFormattingPosPrefix | kFormattingPosSuffix | - kFormattingNegPrefix | kFormattingNegSuffix; -static const int32_t kFormattingAffixParserWithCurrency = - kFormattingAffixParser | kFormattingCurrencyAffixInfo; - -DecimalFormatImpl::DecimalFormatImpl( - NumberFormat *super, - const Locale &locale, - const UnicodeString &pattern, - UErrorCode &status) - : fSuper(super), - fScale(0), - fRoundingMode(DecimalFormat::kRoundHalfEven), - fSymbols(NULL), - fCurrencyUsage(UCURR_USAGE_STANDARD), - fRules(NULL), - fMonetary(FALSE) { - if (U_FAILURE(status)) { - return; - } - fSymbols = new DecimalFormatSymbols( - locale, status); - if (fSymbols == NULL) { - status = U_MEMORY_ALLOCATION_ERROR; - return; - } - UParseError parseError; - applyPattern(pattern, FALSE, parseError, status); - updateAll(status); -} - -DecimalFormatImpl::DecimalFormatImpl( - NumberFormat *super, - const UnicodeString &pattern, - DecimalFormatSymbols *symbolsToAdopt, - UParseError &parseError, - UErrorCode &status) - : fSuper(super), - fScale(0), - fRoundingMode(DecimalFormat::kRoundHalfEven), - fSymbols(symbolsToAdopt), - fCurrencyUsage(UCURR_USAGE_STANDARD), - fRules(NULL), - fMonetary(FALSE) { - applyPattern(pattern, FALSE, parseError, status); - updateAll(status); -} - -DecimalFormatImpl::DecimalFormatImpl( - NumberFormat *super, const DecimalFormatImpl &other, UErrorCode &status) : - fSuper(super), - fMultiplier(other.fMultiplier), - fScale(other.fScale), - fRoundingMode(other.fRoundingMode), - fMinSigDigits(other.fMinSigDigits), - fMaxSigDigits(other.fMaxSigDigits), - fUseScientific(other.fUseScientific), - fUseSigDigits(other.fUseSigDigits), - fGrouping(other.fGrouping), - fPositivePrefixPattern(other.fPositivePrefixPattern), - fNegativePrefixPattern(other.fNegativePrefixPattern), - fPositiveSuffixPattern(other.fPositiveSuffixPattern), - fNegativeSuffixPattern(other.fNegativeSuffixPattern), - fSymbols(other.fSymbols), - fCurrencyUsage(other.fCurrencyUsage), - fRules(NULL), - fMonetary(other.fMonetary), - fAffixParser(other.fAffixParser), - fCurrencyAffixInfo(other.fCurrencyAffixInfo), - fEffPrecision(other.fEffPrecision), - fEffGrouping(other.fEffGrouping), - fOptions(other.fOptions), - fFormatter(other.fFormatter), - fAffixes(other.fAffixes) { - fSymbols = new DecimalFormatSymbols(*fSymbols); - if (fSymbols == NULL && U_SUCCESS(status)) { - status = U_MEMORY_ALLOCATION_ERROR; - } - if (other.fRules != NULL) { - fRules = new PluralRules(*other.fRules); - if (fRules == NULL && U_SUCCESS(status)) { - status = U_MEMORY_ALLOCATION_ERROR; - } - } -} - - -DecimalFormatImpl & -DecimalFormatImpl::assign(const DecimalFormatImpl &other, UErrorCode &status) { - if (U_FAILURE(status) || this == &other) { - return (*this); - } - UObject::operator=(other); - fMultiplier = other.fMultiplier; - fScale = other.fScale; - fRoundingMode = other.fRoundingMode; - fMinSigDigits = other.fMinSigDigits; - fMaxSigDigits = other.fMaxSigDigits; - fUseScientific = other.fUseScientific; - fUseSigDigits = other.fUseSigDigits; - fGrouping = other.fGrouping; - fPositivePrefixPattern = other.fPositivePrefixPattern; - fNegativePrefixPattern = other.fNegativePrefixPattern; - fPositiveSuffixPattern = other.fPositiveSuffixPattern; - fNegativeSuffixPattern = other.fNegativeSuffixPattern; - fCurrencyUsage = other.fCurrencyUsage; - fMonetary = other.fMonetary; - fAffixParser = other.fAffixParser; - fCurrencyAffixInfo = other.fCurrencyAffixInfo; - fEffPrecision = other.fEffPrecision; - fEffGrouping = other.fEffGrouping; - fOptions = other.fOptions; - fFormatter = other.fFormatter; - fAffixes = other.fAffixes; - *fSymbols = *other.fSymbols; - if (fRules != NULL && other.fRules != NULL) { - *fRules = *other.fRules; - } else { - delete fRules; - fRules = other.fRules; - if (fRules != NULL) { - fRules = new PluralRules(*fRules); - if (fRules == NULL) { - status = U_MEMORY_ALLOCATION_ERROR; - return *this; - } - } - } - return *this; -} - -UBool -DecimalFormatImpl::operator==(const DecimalFormatImpl &other) const { - if (this == &other) { - return TRUE; - } - return (fMultiplier == other.fMultiplier) - && (fScale == other.fScale) - && (fRoundingMode == other.fRoundingMode) - && (fMinSigDigits == other.fMinSigDigits) - && (fMaxSigDigits == other.fMaxSigDigits) - && (fUseScientific == other.fUseScientific) - && (fUseSigDigits == other.fUseSigDigits) - && fGrouping.equals(other.fGrouping) - && fPositivePrefixPattern.equals(other.fPositivePrefixPattern) - && fNegativePrefixPattern.equals(other.fNegativePrefixPattern) - && fPositiveSuffixPattern.equals(other.fPositiveSuffixPattern) - && fNegativeSuffixPattern.equals(other.fNegativeSuffixPattern) - && fCurrencyUsage == other.fCurrencyUsage - && fAffixParser.equals(other.fAffixParser) - && fCurrencyAffixInfo.equals(other.fCurrencyAffixInfo) - && fEffPrecision.equals(other.fEffPrecision) - && fEffGrouping.equals(other.fEffGrouping) - && fOptions.equals(other.fOptions) - && fFormatter.equals(other.fFormatter) - && fAffixes.equals(other.fAffixes) - && (*fSymbols == *other.fSymbols) - && ((fRules == other.fRules) || ( - (fRules != NULL) && (other.fRules != NULL) - && (*fRules == *other.fRules))) - && (fMonetary == other.fMonetary); -} - -DecimalFormatImpl::~DecimalFormatImpl() { - delete fSymbols; - delete fRules; -} - -ValueFormatter & -DecimalFormatImpl::prepareValueFormatter(ValueFormatter &vf) const { - if (fUseScientific) { - vf.prepareScientificFormatting( - fFormatter, fEffPrecision, fOptions); - return vf; - } - vf.prepareFixedDecimalFormatting( - fFormatter, fEffGrouping, fEffPrecision.fMantissa, fOptions.fMantissa); - return vf; -} - -int32_t -DecimalFormatImpl::getPatternScale() const { - UBool usesPercent = fPositivePrefixPattern.usesPercent() || - fPositiveSuffixPattern.usesPercent() || - fNegativePrefixPattern.usesPercent() || - fNegativeSuffixPattern.usesPercent(); - if (usesPercent) { - return 2; - } - UBool usesPermill = fPositivePrefixPattern.usesPermill() || - fPositiveSuffixPattern.usesPermill() || - fNegativePrefixPattern.usesPermill() || - fNegativeSuffixPattern.usesPermill(); - if (usesPermill) { - return 3; - } - return 0; -} - -void -DecimalFormatImpl::setMultiplierScale(int32_t scale) { - if (scale == 0) { - // Needed to preserve equality. fMultiplier == 0 means - // multiplier is 1. - fMultiplier.set((int32_t)0); - } else { - fMultiplier.set((int32_t)1); - fMultiplier.shiftDecimalRight(scale); - } -} - -UnicodeString & -DecimalFormatImpl::format( - int32_t number, - UnicodeString &appendTo, - FieldPosition &pos, - UErrorCode &status) const { - FieldPositionOnlyHandler handler(pos); - return formatInt32(number, appendTo, handler, status); -} - -UnicodeString & -DecimalFormatImpl::format( - int32_t number, - UnicodeString &appendTo, - FieldPositionIterator *posIter, - UErrorCode &status) const { - FieldPositionIteratorHandler handler(posIter, status); - return formatInt32(number, appendTo, handler, status); -} - -template -UBool DecimalFormatImpl::maybeFormatWithDigitList( - T number, - UnicodeString &appendTo, - FieldPositionHandler &handler, - UErrorCode &status) const { - if (!fMultiplier.isZero()) { - DigitList digits; - digits.set(number); - digits.mult(fMultiplier, status); - digits.shiftDecimalRight(fScale); - formatAdjustedDigitList(digits, appendTo, handler, status); - return TRUE; - } - if (fScale != 0) { - DigitList digits; - digits.set(number); - digits.shiftDecimalRight(fScale); - formatAdjustedDigitList(digits, appendTo, handler, status); - return TRUE; - } - return FALSE; -} - -template -UBool DecimalFormatImpl::maybeInitVisibleDigitsFromDigitList( - T number, - VisibleDigitsWithExponent &visibleDigits, - UErrorCode &status) const { - if (!fMultiplier.isZero()) { - DigitList digits; - digits.set(number); - digits.mult(fMultiplier, status); - digits.shiftDecimalRight(fScale); - initVisibleDigitsFromAdjusted(digits, visibleDigits, status); - return TRUE; - } - if (fScale != 0) { - DigitList digits; - digits.set(number); - digits.shiftDecimalRight(fScale); - initVisibleDigitsFromAdjusted(digits, visibleDigits, status); - return TRUE; - } - return FALSE; -} - -UnicodeString & -DecimalFormatImpl::formatInt32( - int32_t number, - UnicodeString &appendTo, - FieldPositionHandler &handler, - UErrorCode &status) const { - if (maybeFormatWithDigitList(number, appendTo, handler, status)) { - return appendTo; - } - ValueFormatter vf; - return fAffixes.formatInt32( - number, - prepareValueFormatter(vf), - handler, - fRules, - appendTo, - status); -} - -UnicodeString & -DecimalFormatImpl::formatInt64( - int64_t number, - UnicodeString &appendTo, - FieldPositionHandler &handler, - UErrorCode &status) const { - if (number >= INT32_MIN && number <= INT32_MAX) { - return formatInt32((int32_t) number, appendTo, handler, status); - } - VisibleDigitsWithExponent digits; - initVisibleDigitsWithExponent(number, digits, status); - return formatVisibleDigitsWithExponent( - digits, appendTo, handler, status); -} - -UnicodeString & -DecimalFormatImpl::formatDouble( - double number, - UnicodeString &appendTo, - FieldPositionHandler &handler, - UErrorCode &status) const { - VisibleDigitsWithExponent digits; - initVisibleDigitsWithExponent(number, digits, status); - return formatVisibleDigitsWithExponent( - digits, appendTo, handler, status); -} - -UnicodeString & -DecimalFormatImpl::format( - double number, - UnicodeString &appendTo, - FieldPosition &pos, - UErrorCode &status) const { - FieldPositionOnlyHandler handler(pos); - return formatDouble(number, appendTo, handler, status); -} - -UnicodeString & -DecimalFormatImpl::format( - const DigitList &number, - UnicodeString &appendTo, - FieldPosition &pos, - UErrorCode &status) const { - DigitList dl(number); - FieldPositionOnlyHandler handler(pos); - return formatDigitList(dl, appendTo, handler, status); -} - -UnicodeString & -DecimalFormatImpl::format( - int64_t number, - UnicodeString &appendTo, - FieldPosition &pos, - UErrorCode &status) const { - FieldPositionOnlyHandler handler(pos); - return formatInt64(number, appendTo, handler, status); -} - -UnicodeString & -DecimalFormatImpl::format( - int64_t number, - UnicodeString &appendTo, - FieldPositionIterator *posIter, - UErrorCode &status) const { - FieldPositionIteratorHandler handler(posIter, status); - return formatInt64(number, appendTo, handler, status); -} - -UnicodeString & -DecimalFormatImpl::format( - double number, - UnicodeString &appendTo, - FieldPositionIterator *posIter, - UErrorCode &status) const { - FieldPositionIteratorHandler handler(posIter, status); - return formatDouble(number, appendTo, handler, status); -} - -UnicodeString & -DecimalFormatImpl::format( - const DigitList &number, - UnicodeString &appendTo, - FieldPositionIterator *posIter, - UErrorCode &status) const { - DigitList dl(number); - FieldPositionIteratorHandler handler(posIter, status); - return formatDigitList(dl, appendTo, handler, status); -} - -UnicodeString & -DecimalFormatImpl::format( - StringPiece number, - UnicodeString &appendTo, - FieldPositionIterator *posIter, - UErrorCode &status) const { - DigitList dl; - dl.set(number, status); - FieldPositionIteratorHandler handler(posIter, status); - return formatDigitList(dl, appendTo, handler, status); -} - -UnicodeString & -DecimalFormatImpl::format( - const VisibleDigitsWithExponent &digits, - UnicodeString &appendTo, - FieldPosition &pos, - UErrorCode &status) const { - FieldPositionOnlyHandler handler(pos); - return formatVisibleDigitsWithExponent( - digits, appendTo, handler, status); -} - -UnicodeString & -DecimalFormatImpl::format( - const VisibleDigitsWithExponent &digits, - UnicodeString &appendTo, - FieldPositionIterator *posIter, - UErrorCode &status) const { - FieldPositionIteratorHandler handler(posIter, status); - return formatVisibleDigitsWithExponent( - digits, appendTo, handler, status); -} - -DigitList & -DecimalFormatImpl::adjustDigitList( - DigitList &number, UErrorCode &status) const { - number.setRoundingMode(fRoundingMode); - if (!fMultiplier.isZero()) { - number.mult(fMultiplier, status); - } - if (fScale != 0) { - number.shiftDecimalRight(fScale); - } - number.reduce(); - return number; -} - -UnicodeString & -DecimalFormatImpl::formatDigitList( - DigitList &number, - UnicodeString &appendTo, - FieldPositionHandler &handler, - UErrorCode &status) const { - VisibleDigitsWithExponent digits; - initVisibleDigitsWithExponent(number, digits, status); - return formatVisibleDigitsWithExponent( - digits, appendTo, handler, status); -} - -UnicodeString & -DecimalFormatImpl::formatAdjustedDigitList( - DigitList &number, - UnicodeString &appendTo, - FieldPositionHandler &handler, - UErrorCode &status) const { - ValueFormatter vf; - return fAffixes.format( - number, - prepareValueFormatter(vf), - handler, - fRules, - appendTo, - status); -} - -UnicodeString & -DecimalFormatImpl::formatVisibleDigitsWithExponent( - const VisibleDigitsWithExponent &digits, - UnicodeString &appendTo, - FieldPositionHandler &handler, - UErrorCode &status) const { - ValueFormatter vf; - return fAffixes.format( - digits, - prepareValueFormatter(vf), - handler, - fRules, - appendTo, - status); -} - -static FixedDecimal &initFixedDecimal( - const VisibleDigits &digits, FixedDecimal &result) { - result.source = 0.0; - result.isNegative = digits.isNegative(); - result._isNaN = digits.isNaN(); - result._isInfinite = digits.isInfinite(); - digits.getFixedDecimal( - result.source, result.intValue, result.decimalDigits, - result.decimalDigitsWithoutTrailingZeros, - result.visibleDecimalDigitCount, result.hasIntegerValue); - return result; -} - -FixedDecimal & -DecimalFormatImpl::getFixedDecimal(double number, FixedDecimal &result, UErrorCode &status) const { - if (U_FAILURE(status)) { - return result; - } - VisibleDigits digits; - fEffPrecision.fMantissa.initVisibleDigits(number, digits, status); - return initFixedDecimal(digits, result); -} - -FixedDecimal & -DecimalFormatImpl::getFixedDecimal( - DigitList &number, FixedDecimal &result, UErrorCode &status) const { - if (U_FAILURE(status)) { - return result; - } - VisibleDigits digits; - fEffPrecision.fMantissa.initVisibleDigits(number, digits, status); - return initFixedDecimal(digits, result); -} - -VisibleDigitsWithExponent & -DecimalFormatImpl::initVisibleDigitsWithExponent( - int64_t number, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const { - if (maybeInitVisibleDigitsFromDigitList( - number, digits, status)) { - return digits; - } - if (fUseScientific) { - fEffPrecision.initVisibleDigitsWithExponent( - number, digits, status); - } else { - fEffPrecision.fMantissa.initVisibleDigitsWithExponent( - number, digits, status); - } - return digits; -} - -VisibleDigitsWithExponent & -DecimalFormatImpl::initVisibleDigitsWithExponent( - double number, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const { - if (maybeInitVisibleDigitsFromDigitList( - number, digits, status)) { - return digits; - } - if (fUseScientific) { - fEffPrecision.initVisibleDigitsWithExponent( - number, digits, status); - } else { - fEffPrecision.fMantissa.initVisibleDigitsWithExponent( - number, digits, status); - } - return digits; -} - -VisibleDigitsWithExponent & -DecimalFormatImpl::initVisibleDigitsWithExponent( - DigitList &number, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const { - adjustDigitList(number, status); - return initVisibleDigitsFromAdjusted(number, digits, status); -} - -VisibleDigitsWithExponent & -DecimalFormatImpl::initVisibleDigitsFromAdjusted( - DigitList &number, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const { - if (fUseScientific) { - fEffPrecision.initVisibleDigitsWithExponent( - number, digits, status); - } else { - fEffPrecision.fMantissa.initVisibleDigitsWithExponent( - number, digits, status); - } - return digits; -} - -DigitList & -DecimalFormatImpl::round( - DigitList &number, UErrorCode &status) const { - if (number.isNaN() || number.isInfinite()) { - return number; - } - adjustDigitList(number, status); - ValueFormatter vf; - prepareValueFormatter(vf); - return vf.round(number, status); -} - -void -DecimalFormatImpl::setMinimumSignificantDigits(int32_t newValue) { - fMinSigDigits = newValue; - fUseSigDigits = TRUE; // ticket 9936 - updatePrecision(); -} - -void -DecimalFormatImpl::setMaximumSignificantDigits(int32_t newValue) { - fMaxSigDigits = newValue; - fUseSigDigits = TRUE; // ticket 9936 - updatePrecision(); -} - -void -DecimalFormatImpl::setMinMaxSignificantDigits(int32_t min, int32_t max) { - fMinSigDigits = min; - fMaxSigDigits = max; - fUseSigDigits = TRUE; // ticket 9936 - updatePrecision(); -} - -void -DecimalFormatImpl::setScientificNotation(UBool newValue) { - fUseScientific = newValue; - updatePrecision(); -} - -void -DecimalFormatImpl::setSignificantDigitsUsed(UBool newValue) { - fUseSigDigits = newValue; - updatePrecision(); -} - -void -DecimalFormatImpl::setGroupingSize(int32_t newValue) { - fGrouping.fGrouping = newValue; - updateGrouping(); -} - -void -DecimalFormatImpl::setSecondaryGroupingSize(int32_t newValue) { - fGrouping.fGrouping2 = newValue; - updateGrouping(); -} - -void -DecimalFormatImpl::setMinimumGroupingDigits(int32_t newValue) { - fGrouping.fMinGrouping = newValue; - updateGrouping(); -} - -void -DecimalFormatImpl::setCurrencyUsage( - UCurrencyUsage currencyUsage, UErrorCode &status) { - fCurrencyUsage = currencyUsage; - updateFormatting(kFormattingCurrency, status); -} - -void -DecimalFormatImpl::setRoundingIncrement(double d) { - if (d > 0.0) { - fEffPrecision.fMantissa.fRoundingIncrement.set(d); - } else { - fEffPrecision.fMantissa.fRoundingIncrement.set(0.0); - } -} - -double -DecimalFormatImpl::getRoundingIncrement() const { - return fEffPrecision.fMantissa.fRoundingIncrement.getDouble(); -} - -int32_t -DecimalFormatImpl::getMultiplier() const { - if (fMultiplier.isZero()) { - return 1; - } - return (int32_t) fMultiplier.getDouble(); -} - -void -DecimalFormatImpl::setMultiplier(int32_t m) { - if (m == 0 || m == 1) { - fMultiplier.set((int32_t)0); - } else { - fMultiplier.set(m); - } -} - -void -DecimalFormatImpl::setPositivePrefix(const UnicodeString &str) { - fPositivePrefixPattern.remove(); - fPositivePrefixPattern.addLiteral(str.getBuffer(), 0, str.length()); - UErrorCode status = U_ZERO_ERROR; - updateFormatting(kFormattingPosPrefix, status); -} - -void -DecimalFormatImpl::setPositiveSuffix(const UnicodeString &str) { - fPositiveSuffixPattern.remove(); - fPositiveSuffixPattern.addLiteral(str.getBuffer(), 0, str.length()); - UErrorCode status = U_ZERO_ERROR; - updateFormatting(kFormattingPosSuffix, status); -} - -void -DecimalFormatImpl::setNegativePrefix(const UnicodeString &str) { - fNegativePrefixPattern.remove(); - fNegativePrefixPattern.addLiteral(str.getBuffer(), 0, str.length()); - UErrorCode status = U_ZERO_ERROR; - updateFormatting(kFormattingNegPrefix, status); -} - -void -DecimalFormatImpl::setNegativeSuffix(const UnicodeString &str) { - fNegativeSuffixPattern.remove(); - fNegativeSuffixPattern.addLiteral(str.getBuffer(), 0, str.length()); - UErrorCode status = U_ZERO_ERROR; - updateFormatting(kFormattingNegSuffix, status); -} - -UnicodeString & -DecimalFormatImpl::getPositivePrefix(UnicodeString &result) const { - result = fAffixes.fPositivePrefix.getOtherVariant().toString(); - return result; -} - -UnicodeString & -DecimalFormatImpl::getPositiveSuffix(UnicodeString &result) const { - result = fAffixes.fPositiveSuffix.getOtherVariant().toString(); - return result; -} - -UnicodeString & -DecimalFormatImpl::getNegativePrefix(UnicodeString &result) const { - result = fAffixes.fNegativePrefix.getOtherVariant().toString(); - return result; -} - -UnicodeString & -DecimalFormatImpl::getNegativeSuffix(UnicodeString &result) const { - result = fAffixes.fNegativeSuffix.getOtherVariant().toString(); - return result; -} - -void -DecimalFormatImpl::adoptDecimalFormatSymbols(DecimalFormatSymbols *symbolsToAdopt) { - if (symbolsToAdopt == NULL) { - return; - } - delete fSymbols; - fSymbols = symbolsToAdopt; - UErrorCode status = U_ZERO_ERROR; - updateFormatting(kFormattingSymbols, status); -} - -void -DecimalFormatImpl::applyPatternFavorCurrencyPrecision( - const UnicodeString &pattern, UErrorCode &status) { - UParseError perror; - applyPattern(pattern, FALSE, perror, status); - updateForApplyPatternFavorCurrencyPrecision(status); -} - -void -DecimalFormatImpl::applyPattern( - const UnicodeString &pattern, UErrorCode &status) { - UParseError perror; - applyPattern(pattern, FALSE, perror, status); - updateForApplyPattern(status); -} - -void -DecimalFormatImpl::applyPattern( - const UnicodeString &pattern, - UParseError &perror, UErrorCode &status) { - applyPattern(pattern, FALSE, perror, status); - updateForApplyPattern(status); -} - -void -DecimalFormatImpl::applyLocalizedPattern( - const UnicodeString &pattern, UErrorCode &status) { - UParseError perror; - applyPattern(pattern, TRUE, perror, status); - updateForApplyPattern(status); -} - -void -DecimalFormatImpl::applyLocalizedPattern( - const UnicodeString &pattern, - UParseError &perror, UErrorCode &status) { - applyPattern(pattern, TRUE, perror, status); - updateForApplyPattern(status); -} - -void -DecimalFormatImpl::applyPattern( - const UnicodeString &pattern, - UBool localized, UParseError &perror, UErrorCode &status) { - if (U_FAILURE(status)) { - return; - } - DecimalFormatPatternParser patternParser; - if (localized) { - patternParser.useSymbols(*fSymbols); - } - DecimalFormatPattern out; - patternParser.applyPatternWithoutExpandAffix( - pattern, out, perror, status); - if (U_FAILURE(status)) { - return; - } - fUseScientific = out.fUseExponentialNotation; - fUseSigDigits = out.fUseSignificantDigits; - fSuper->NumberFormat::setMinimumIntegerDigits(out.fMinimumIntegerDigits); - fSuper->NumberFormat::setMaximumIntegerDigits(out.fMaximumIntegerDigits); - fSuper->NumberFormat::setMinimumFractionDigits(out.fMinimumFractionDigits); - fSuper->NumberFormat::setMaximumFractionDigits(out.fMaximumFractionDigits); - fMinSigDigits = out.fMinimumSignificantDigits; - fMaxSigDigits = out.fMaximumSignificantDigits; - fEffPrecision.fMinExponentDigits = out.fMinExponentDigits; - fOptions.fExponent.fAlwaysShowSign = out.fExponentSignAlwaysShown; - fSuper->NumberFormat::setGroupingUsed(out.fGroupingUsed); - fGrouping.fGrouping = out.fGroupingSize; - fGrouping.fGrouping2 = out.fGroupingSize2; - fOptions.fMantissa.fAlwaysShowDecimal = out.fDecimalSeparatorAlwaysShown; - if (out.fRoundingIncrementUsed) { - fEffPrecision.fMantissa.fRoundingIncrement = out.fRoundingIncrement; - } else { - fEffPrecision.fMantissa.fRoundingIncrement.clear(); - } - fAffixes.fPadChar = out.fPad; - fNegativePrefixPattern = out.fNegPrefixAffix; - fNegativeSuffixPattern = out.fNegSuffixAffix; - fPositivePrefixPattern = out.fPosPrefixAffix; - fPositiveSuffixPattern = out.fPosSuffixAffix; - - // Work around. Pattern parsing code and DecimalFormat code don't agree - // on the definition of field width, so we have to translate from - // pattern field width to decimal format field width here. - fAffixes.fWidth = out.fFormatWidth == 0 ? 0 : - out.fFormatWidth + fPositivePrefixPattern.countChar32() - + fPositiveSuffixPattern.countChar32(); - switch (out.fPadPosition) { - case DecimalFormatPattern::kPadBeforePrefix: - fAffixes.fPadPosition = DigitAffixesAndPadding::kPadBeforePrefix; - break; - case DecimalFormatPattern::kPadAfterPrefix: - fAffixes.fPadPosition = DigitAffixesAndPadding::kPadAfterPrefix; - break; - case DecimalFormatPattern::kPadBeforeSuffix: - fAffixes.fPadPosition = DigitAffixesAndPadding::kPadBeforeSuffix; - break; - case DecimalFormatPattern::kPadAfterSuffix: - fAffixes.fPadPosition = DigitAffixesAndPadding::kPadAfterSuffix; - break; - default: - break; - } -} - -void -DecimalFormatImpl::updatePrecision() { - if (fUseScientific) { - updatePrecisionForScientific(); - } else { - updatePrecisionForFixed(); - } -} - -static void updatePrecisionForScientificMinMax( - const DigitInterval &min, - const DigitInterval &max, - DigitInterval &resultMin, - DigitInterval &resultMax, - SignificantDigitInterval &resultSignificant) { - resultMin.setIntDigitCount(0); - resultMin.setFracDigitCount(0); - resultSignificant.clear(); - resultMax.clear(); - - int32_t maxIntDigitCount = max.getIntDigitCount(); - int32_t minIntDigitCount = min.getIntDigitCount(); - int32_t maxFracDigitCount = max.getFracDigitCount(); - int32_t minFracDigitCount = min.getFracDigitCount(); - - - // Not in spec: maxIntDigitCount > 8 assume - // maxIntDigitCount = minIntDigitCount. Current DecimalFormat API has - // no provision for unsetting maxIntDigitCount which would be useful for - // scientific notation. The best we can do is assume that if - // maxIntDigitCount is the default of 2000000000 or is "big enough" then - // user did not intend to explicitly set it. The 8 was derived emperically - // by extensive testing of legacy code. - if (maxIntDigitCount > 8) { - maxIntDigitCount = minIntDigitCount; - } - - // Per the spec, exponent grouping happens if maxIntDigitCount is more - // than 1 and more than minIntDigitCount. - UBool bExponentGrouping = maxIntDigitCount > 1 && minIntDigitCount < maxIntDigitCount; - if (bExponentGrouping) { - resultMax.setIntDigitCount(maxIntDigitCount); - - // For exponent grouping minIntDigits is always treated as 1 even - // if it wasn't set to 1! - resultMin.setIntDigitCount(1); - } else { - // Fixed digit count left of decimal. minIntDigitCount doesn't have - // to equal maxIntDigitCount i.e minIntDigitCount == 0 while - // maxIntDigitCount == 1. - int32_t fixedIntDigitCount = maxIntDigitCount; - - // If fixedIntDigitCount is 0 but - // min or max fraction count is 0 too then use 1. This way we can get - // unlimited precision for X.XXXEX - if (fixedIntDigitCount == 0 && (minFracDigitCount == 0 || maxFracDigitCount == 0)) { - fixedIntDigitCount = 1; - } - resultMax.setIntDigitCount(fixedIntDigitCount); - resultMin.setIntDigitCount(fixedIntDigitCount); - } - // Spec says this is how we compute significant digits. 0 means - // unlimited significant digits. - int32_t maxSigDigits = minIntDigitCount + maxFracDigitCount; - if (maxSigDigits > 0) { - int32_t minSigDigits = minIntDigitCount + minFracDigitCount; - resultSignificant.setMin(minSigDigits); - resultSignificant.setMax(maxSigDigits); - } -} - -void -DecimalFormatImpl::updatePrecisionForScientific() { - FixedPrecision *result = &fEffPrecision.fMantissa; - if (fUseSigDigits) { - result->fMax.setFracDigitCount(-1); - result->fMax.setIntDigitCount(1); - result->fMin.setFracDigitCount(0); - result->fMin.setIntDigitCount(1); - result->fSignificant.clear(); - extractSigDigits(result->fSignificant); - return; - } - DigitInterval max; - DigitInterval min; - extractMinMaxDigits(min, max); - updatePrecisionForScientificMinMax( - min, max, - result->fMin, result->fMax, result->fSignificant); -} - -void -DecimalFormatImpl::updatePrecisionForFixed() { - FixedPrecision *result = &fEffPrecision.fMantissa; - if (!fUseSigDigits) { - extractMinMaxDigits(result->fMin, result->fMax); - result->fSignificant.clear(); - } else { - extractSigDigits(result->fSignificant); - result->fMin.setIntDigitCount(1); - result->fMin.setFracDigitCount(0); - result->fMax.clear(); - } -} - -void - DecimalFormatImpl::extractMinMaxDigits( - DigitInterval &min, DigitInterval &max) const { - min.setIntDigitCount(fSuper->getMinimumIntegerDigits()); - max.setIntDigitCount(fSuper->getMaximumIntegerDigits()); - min.setFracDigitCount(fSuper->getMinimumFractionDigits()); - max.setFracDigitCount(fSuper->getMaximumFractionDigits()); -} - -void - DecimalFormatImpl::extractSigDigits( - SignificantDigitInterval &sig) const { - sig.setMin(fMinSigDigits < 0 ? 0 : fMinSigDigits); - sig.setMax(fMaxSigDigits < 0 ? 0 : fMaxSigDigits); -} - -void -DecimalFormatImpl::updateGrouping() { - if (fSuper->isGroupingUsed()) { - fEffGrouping = fGrouping; - } else { - fEffGrouping.clear(); - } -} - -void -DecimalFormatImpl::updateCurrency(UErrorCode &status) { - updateFormatting(kFormattingCurrency, TRUE, status); -} - -void -DecimalFormatImpl::updateFormatting( - int32_t changedFormattingFields, - UErrorCode &status) { - updateFormatting(changedFormattingFields, TRUE, status); -} - -void -DecimalFormatImpl::updateFormatting( - int32_t changedFormattingFields, - UBool updatePrecisionBasedOnCurrency, - UErrorCode &status) { - if (U_FAILURE(status)) { - return; - } - // Each function updates one field. Order matters. For instance, - // updatePluralRules comes before updateCurrencyAffixInfo because the - // fRules field is needed to update the fCurrencyAffixInfo field. - updateFormattingUsesCurrency(changedFormattingFields); - updateFormattingFixedPointFormatter(changedFormattingFields); - updateFormattingAffixParser(changedFormattingFields); - updateFormattingPluralRules(changedFormattingFields, status); - updateFormattingCurrencyAffixInfo( - changedFormattingFields, - updatePrecisionBasedOnCurrency, - status); - updateFormattingLocalizedPositivePrefix( - changedFormattingFields, status); - updateFormattingLocalizedPositiveSuffix( - changedFormattingFields, status); - updateFormattingLocalizedNegativePrefix( - changedFormattingFields, status); - updateFormattingLocalizedNegativeSuffix( - changedFormattingFields, status); -} - -void -DecimalFormatImpl::updateFormattingUsesCurrency( - int32_t &changedFormattingFields) { - if ((changedFormattingFields & kFormattingAffixes) == 0) { - // If no affixes changed, don't need to do any work - return; - } - UBool newUsesCurrency = - fPositivePrefixPattern.usesCurrency() || - fPositiveSuffixPattern.usesCurrency() || - fNegativePrefixPattern.usesCurrency() || - fNegativeSuffixPattern.usesCurrency(); - if (fMonetary != newUsesCurrency) { - fMonetary = newUsesCurrency; - changedFormattingFields |= kFormattingUsesCurrency; - } -} - -void -DecimalFormatImpl::updateFormattingPluralRules( - int32_t &changedFormattingFields, UErrorCode &status) { - if ((changedFormattingFields & (kFormattingSymbols | kFormattingUsesCurrency)) == 0) { - // No work to do if both fSymbols and fMonetary - // fields are unchanged - return; - } - if (U_FAILURE(status)) { - return; - } - PluralRules *newRules = NULL; - if (fMonetary) { - newRules = PluralRules::forLocale(fSymbols->getLocale(), status); - if (U_FAILURE(status)) { - return; - } - } - // Its ok to say a field has changed when it really hasn't but not - // the other way around. Here we assume the field changed unless it - // was NULL before and is still NULL now - if (fRules != newRules) { - delete fRules; - fRules = newRules; - changedFormattingFields |= kFormattingPluralRules; - } -} - -void -DecimalFormatImpl::updateFormattingCurrencyAffixInfo( - int32_t &changedFormattingFields, - UBool updatePrecisionBasedOnCurrency, - UErrorCode &status) { - if ((changedFormattingFields & ( - kFormattingSymbols | kFormattingCurrency | - kFormattingUsesCurrency | kFormattingPluralRules)) == 0) { - // If all these fields are unchanged, no work to do. - return; - } - if (U_FAILURE(status)) { - return; - } - if (!fMonetary) { - if (fCurrencyAffixInfo.isDefault()) { - // In this case don't have to do any work - return; - } - fCurrencyAffixInfo.set(NULL, NULL, NULL, status); - if (U_FAILURE(status)) { - return; - } - changedFormattingFields |= kFormattingCurrencyAffixInfo; - } else { - const UChar *currency = fSuper->getCurrency(); - UChar localeCurr[4]; - if (currency[0] == 0) { - ucurr_forLocale(fSymbols->getLocale().getName(), localeCurr, UPRV_LENGTHOF(localeCurr), &status); - if (U_SUCCESS(status)) { - currency = localeCurr; - fSuper->NumberFormat::setCurrency(currency, status); - } else { - currency = NULL; - status = U_ZERO_ERROR; - } - } - fCurrencyAffixInfo.set( - fSymbols->getLocale().getName(), fRules, currency, status); - if (U_FAILURE(status)) { - return; - } - UBool customCurrencySymbol = FALSE; - // If DecimalFormatSymbols has custom currency symbol, prefer - // that over what we just read from the resource bundles - if (fSymbols->isCustomCurrencySymbol()) { - fCurrencyAffixInfo.setSymbol( - fSymbols->getConstSymbol(DecimalFormatSymbols::kCurrencySymbol)); - customCurrencySymbol = TRUE; - } - if (fSymbols->isCustomIntlCurrencySymbol()) { - fCurrencyAffixInfo.setISO( - fSymbols->getConstSymbol(DecimalFormatSymbols::kIntlCurrencySymbol)); - customCurrencySymbol = TRUE; - } - changedFormattingFields |= kFormattingCurrencyAffixInfo; - if (currency && !customCurrencySymbol && updatePrecisionBasedOnCurrency) { - FixedPrecision precision; - CurrencyAffixInfo::adjustPrecision( - currency, fCurrencyUsage, precision, status); - if (U_FAILURE(status)) { - return; - } - fSuper->NumberFormat::setMinimumFractionDigits( - precision.fMin.getFracDigitCount()); - fSuper->NumberFormat::setMaximumFractionDigits( - precision.fMax.getFracDigitCount()); - updatePrecision(); - fEffPrecision.fMantissa.fRoundingIncrement = - precision.fRoundingIncrement; - } - - } -} - -void -DecimalFormatImpl::updateFormattingFixedPointFormatter( - int32_t &changedFormattingFields) { - if ((changedFormattingFields & (kFormattingSymbols | kFormattingUsesCurrency)) == 0) { - // No work to do if fSymbols is unchanged - return; - } - if (fMonetary) { - fFormatter.setDecimalFormatSymbolsForMonetary(*fSymbols); - } else { - fFormatter.setDecimalFormatSymbols(*fSymbols); - } -} - -void -DecimalFormatImpl::updateFormattingAffixParser( - int32_t &changedFormattingFields) { - if ((changedFormattingFields & kFormattingSymbols) == 0) { - // No work to do if fSymbols is unchanged - return; - } - fAffixParser.setDecimalFormatSymbols(*fSymbols); - changedFormattingFields |= kFormattingAffixParser; -} - -void -DecimalFormatImpl::updateFormattingLocalizedPositivePrefix( - int32_t &changedFormattingFields, UErrorCode &status) { - if (U_FAILURE(status)) { - return; - } - if ((changedFormattingFields & ( - kFormattingPosPrefix | kFormattingAffixParserWithCurrency)) == 0) { - // No work to do - return; - } - fAffixes.fPositivePrefix.remove(); - fAffixParser.parse( - fPositivePrefixPattern, - fCurrencyAffixInfo, - fAffixes.fPositivePrefix, - status); -} - -void -DecimalFormatImpl::updateFormattingLocalizedPositiveSuffix( - int32_t &changedFormattingFields, UErrorCode &status) { - if (U_FAILURE(status)) { - return; - } - if ((changedFormattingFields & ( - kFormattingPosSuffix | kFormattingAffixParserWithCurrency)) == 0) { - // No work to do - return; - } - fAffixes.fPositiveSuffix.remove(); - fAffixParser.parse( - fPositiveSuffixPattern, - fCurrencyAffixInfo, - fAffixes.fPositiveSuffix, - status); -} - -void -DecimalFormatImpl::updateFormattingLocalizedNegativePrefix( - int32_t &changedFormattingFields, UErrorCode &status) { - if (U_FAILURE(status)) { - return; - } - if ((changedFormattingFields & ( - kFormattingNegPrefix | kFormattingAffixParserWithCurrency)) == 0) { - // No work to do - return; - } - fAffixes.fNegativePrefix.remove(); - fAffixParser.parse( - fNegativePrefixPattern, - fCurrencyAffixInfo, - fAffixes.fNegativePrefix, - status); -} - -void -DecimalFormatImpl::updateFormattingLocalizedNegativeSuffix( - int32_t &changedFormattingFields, UErrorCode &status) { - if (U_FAILURE(status)) { - return; - } - if ((changedFormattingFields & ( - kFormattingNegSuffix | kFormattingAffixParserWithCurrency)) == 0) { - // No work to do - return; - } - fAffixes.fNegativeSuffix.remove(); - fAffixParser.parse( - fNegativeSuffixPattern, - fCurrencyAffixInfo, - fAffixes.fNegativeSuffix, - status); -} - -void -DecimalFormatImpl::updateForApplyPatternFavorCurrencyPrecision( - UErrorCode &status) { - updateAll(kFormattingAll & ~kFormattingSymbols, TRUE, status); -} - -void -DecimalFormatImpl::updateForApplyPattern(UErrorCode &status) { - updateAll(kFormattingAll & ~kFormattingSymbols, FALSE, status); -} - -void -DecimalFormatImpl::updateAll(UErrorCode &status) { - updateAll(kFormattingAll, TRUE, status); -} - -void -DecimalFormatImpl::updateAll( - int32_t formattingFlags, - UBool updatePrecisionBasedOnCurrency, - UErrorCode &status) { - if (U_FAILURE(status)) { - return; - } - updatePrecision(); - updateGrouping(); - updateFormatting( - formattingFlags, updatePrecisionBasedOnCurrency, status); - setMultiplierScale(getPatternScale()); -} - - -static int32_t -getMinimumLengthToDescribeGrouping(const DigitGrouping &grouping) { - if (grouping.fGrouping <= 0) { - return 0; - } - if (grouping.fGrouping2 <= 0) { - return grouping.fGrouping + 1; - } - return grouping.fGrouping + grouping.fGrouping2 + 1; -} - -/** - * Given a grouping policy, calculates how many digits are needed left of - * the decimal point to achieve a desired length left of the - * decimal point. - * @param grouping the grouping policy - * @param desiredLength number of characters needed left of decimal point - * @param minLeftDigits at least this many digits is returned - * @param leftDigits the number of digits needed stored here - * which is >= minLeftDigits. - * @return true if a perfect fit or false if having leftDigits would exceed - * desiredLength - */ -static UBool -getLeftDigitsForLeftLength( - const DigitGrouping &grouping, - int32_t desiredLength, - int32_t minLeftDigits, - int32_t &leftDigits) { - leftDigits = minLeftDigits; - int32_t lengthSoFar = leftDigits + grouping.getSeparatorCount(leftDigits); - while (lengthSoFar < desiredLength) { - lengthSoFar += grouping.isSeparatorAt(leftDigits + 1, leftDigits) ? 2 : 1; - ++leftDigits; - } - return (lengthSoFar == desiredLength); -} - -int32_t -DecimalFormatImpl::computeExponentPatternLength() const { - if (fUseScientific) { - return 1 + (fOptions.fExponent.fAlwaysShowSign ? 1 : 0) + fEffPrecision.fMinExponentDigits; - } - return 0; -} - -int32_t -DecimalFormatImpl::countFractionDigitAndDecimalPatternLength( - int32_t fracDigitCount) const { - if (!fOptions.fMantissa.fAlwaysShowDecimal && fracDigitCount == 0) { - return 0; - } - return fracDigitCount + 1; -} - -UnicodeString& -DecimalFormatImpl::toNumberPattern( - UBool hasPadding, int32_t minimumLength, UnicodeString& result) const { - // Get a grouping policy like the one in this object that does not - // have minimum grouping since toPattern doesn't support it. - DigitGrouping grouping(fEffGrouping); - grouping.fMinGrouping = 0; - - // Only for fixed digits, these are the digits that get 0's. - DigitInterval minInterval; - - // Only for fixed digits, these are the digits that get #'s. - DigitInterval maxInterval; - - // Only for significant digits - int32_t sigMin = 0; /* initialize to avoid compiler warning */ - int32_t sigMax = 0; /* initialize to avoid compiler warning */ - - // These are all the digits to be displayed. For significant digits, - // this interval always starts at the 1's place an extends left. - DigitInterval fullInterval; - - // Digit range of rounding increment. If rounding increment is .025. - // then roundingIncrementLowerExp = -3 and roundingIncrementUpperExp = -1 - int32_t roundingIncrementLowerExp = 0; - int32_t roundingIncrementUpperExp = 0; - - if (fUseSigDigits) { - SignificantDigitInterval sigInterval; - extractSigDigits(sigInterval); - sigMax = sigInterval.getMax(); - sigMin = sigInterval.getMin(); - fullInterval.setFracDigitCount(0); - fullInterval.setIntDigitCount(sigMax); - } else { - extractMinMaxDigits(minInterval, maxInterval); - if (fUseScientific) { - if (maxInterval.getIntDigitCount() > kMaxScientificIntegerDigits) { - maxInterval.setIntDigitCount(1); - minInterval.shrinkToFitWithin(maxInterval); - } - } else if (hasPadding) { - // Make max int digits match min int digits for now, we - // compute necessary padding later. - maxInterval.setIntDigitCount(minInterval.getIntDigitCount()); - } else { - // For some reason toPattern adds at least one leading '#' - maxInterval.setIntDigitCount(minInterval.getIntDigitCount() + 1); - } - if (!fEffPrecision.fMantissa.fRoundingIncrement.isZero()) { - roundingIncrementLowerExp = - fEffPrecision.fMantissa.fRoundingIncrement.getLowerExponent(); - roundingIncrementUpperExp = - fEffPrecision.fMantissa.fRoundingIncrement.getUpperExponent(); - // We have to include the rounding increment in what we display - maxInterval.expandToContainDigit(roundingIncrementLowerExp); - maxInterval.expandToContainDigit(roundingIncrementUpperExp - 1); - } - fullInterval = maxInterval; - } - // We have to include enough digits to show grouping strategy - int32_t minLengthToDescribeGrouping = - getMinimumLengthToDescribeGrouping(grouping); - if (minLengthToDescribeGrouping > 0) { - fullInterval.expandToContainDigit( - getMinimumLengthToDescribeGrouping(grouping) - 1); - } - - // If we have a minimum length, we have to add digits to the left to - // depict padding. - if (hasPadding) { - // For non scientific notation, - // minimumLengthForMantissa = minimumLength - int32_t minimumLengthForMantissa = - minimumLength - computeExponentPatternLength(); - int32_t mininumLengthForMantissaIntPart = - minimumLengthForMantissa - - countFractionDigitAndDecimalPatternLength( - fullInterval.getFracDigitCount()); - // Because of grouping, we may need fewer than expected digits to - // achieve the length we need. - int32_t digitsNeeded; - if (getLeftDigitsForLeftLength( - grouping, - mininumLengthForMantissaIntPart, - fullInterval.getIntDigitCount(), - digitsNeeded)) { - - // In this case, we achieved the exact length that we want. - fullInterval.setIntDigitCount(digitsNeeded); - } else if (digitsNeeded > fullInterval.getIntDigitCount()) { - - // Having digitsNeeded digits goes over desired length which - // means that to have desired length would mean starting on a - // grouping sepearator e.g ,###,### so add a '#' and use one - // less digit. This trick gives ####,### but that is the best - // we can do. - result.append(kPatternDigit); - fullInterval.setIntDigitCount(digitsNeeded - 1); - } - } - int32_t maxDigitPos = fullInterval.getMostSignificantExclusive(); - int32_t minDigitPos = fullInterval.getLeastSignificantInclusive(); - for (int32_t i = maxDigitPos - 1; i >= minDigitPos; --i) { - if (!fOptions.fMantissa.fAlwaysShowDecimal && i == -1) { - result.append(kPatternDecimalSeparator); - } - if (fUseSigDigits) { - // Use digit symbol - if (i >= sigMax || i < sigMax - sigMin) { - result.append(kPatternDigit); - } else { - result.append(kPatternSignificantDigit); - } - } else { - if (i < roundingIncrementUpperExp && i >= roundingIncrementLowerExp) { - result.append((UChar)(fEffPrecision.fMantissa.fRoundingIncrement.getDigitByExponent(i) + kPatternZeroDigit)); - } else if (minInterval.contains(i)) { - result.append(kPatternZeroDigit); - } else { - result.append(kPatternDigit); - } - } - if (grouping.isSeparatorAt(i + 1, i)) { - result.append(kPatternGroupingSeparator); - } - if (fOptions.fMantissa.fAlwaysShowDecimal && i == 0) { - result.append(kPatternDecimalSeparator); - } - } - if (fUseScientific) { - result.append(kPatternExponent); - if (fOptions.fExponent.fAlwaysShowSign) { - result.append(kPatternPlus); - } - for (int32_t i = 0; i < 1 || i < fEffPrecision.fMinExponentDigits; ++i) { - result.append(kPatternZeroDigit); - } - } - return result; -} - -UnicodeString& -DecimalFormatImpl::toPattern(UnicodeString& result) const { - result.remove(); - UnicodeString padSpec; - if (fAffixes.fWidth > 0) { - padSpec.append(kPatternPadEscape); - padSpec.append(fAffixes.fPadChar); - } - if (fAffixes.fPadPosition == DigitAffixesAndPadding::kPadBeforePrefix) { - result.append(padSpec); - } - fPositivePrefixPattern.toUserString(result); - if (fAffixes.fPadPosition == DigitAffixesAndPadding::kPadAfterPrefix) { - result.append(padSpec); - } - toNumberPattern( - fAffixes.fWidth > 0, - fAffixes.fWidth - fPositivePrefixPattern.countChar32() - fPositiveSuffixPattern.countChar32(), - result); - if (fAffixes.fPadPosition == DigitAffixesAndPadding::kPadBeforeSuffix) { - result.append(padSpec); - } - fPositiveSuffixPattern.toUserString(result); - if (fAffixes.fPadPosition == DigitAffixesAndPadding::kPadAfterSuffix) { - result.append(padSpec); - } - AffixPattern withNegative; - withNegative.add(AffixPattern::kNegative); - withNegative.append(fPositivePrefixPattern); - if (!fPositiveSuffixPattern.equals(fNegativeSuffixPattern) || - !withNegative.equals(fNegativePrefixPattern)) { - result.append(kPatternSeparator); - if (fAffixes.fPadPosition == DigitAffixesAndPadding::kPadBeforePrefix) { - result.append(padSpec); - } - fNegativePrefixPattern.toUserString(result); - if (fAffixes.fPadPosition == DigitAffixesAndPadding::kPadAfterPrefix) { - result.append(padSpec); - } - toNumberPattern( - fAffixes.fWidth > 0, - fAffixes.fWidth - fNegativePrefixPattern.countChar32() - fNegativeSuffixPattern.countChar32(), - result); - if (fAffixes.fPadPosition == DigitAffixesAndPadding::kPadBeforeSuffix) { - result.append(padSpec); - } - fNegativeSuffixPattern.toUserString(result); - if (fAffixes.fPadPosition == DigitAffixesAndPadding::kPadAfterSuffix) { - result.append(padSpec); - } - } - return result; -} - -int32_t -DecimalFormatImpl::getOldFormatWidth() const { - if (fAffixes.fWidth == 0) { - return 0; - } - return fAffixes.fWidth - fPositiveSuffixPattern.countChar32() - fPositivePrefixPattern.countChar32(); -} - -const UnicodeString & -DecimalFormatImpl::getConstSymbol( - DecimalFormatSymbols::ENumberFormatSymbol symbol) const { - return fSymbols->getConstSymbol(symbol); -} - -UBool -DecimalFormatImpl::isParseFastpath() const { - AffixPattern negative; - negative.add(AffixPattern::kNegative); - - return fAffixes.fWidth == 0 && - fPositivePrefixPattern.countChar32() == 0 && - fNegativePrefixPattern.equals(negative) && - fPositiveSuffixPattern.countChar32() == 0 && - fNegativeSuffixPattern.countChar32() == 0; -} - - -U_NAMESPACE_END - -#endif /* #if !UCONFIG_NO_FORMATTING */ - diff --git a/icu4c/source/i18n/decimfmtimpl.h b/icu4c/source/i18n/decimfmtimpl.h deleted file mode 100644 index 76e8053bdc..0000000000 --- a/icu4c/source/i18n/decimfmtimpl.h +++ /dev/null @@ -1,549 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************** -* Copyright (C) 2015, International Business Machines -* Corporation and others. All Rights Reserved. -******************************************************************************** -* -* File decimfmtimpl.h -******************************************************************************** -*/ - -#ifndef DECIMFMTIMPL_H -#define DECIMFMTIMPL_H - -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING - -#include "unicode/decimfmt.h" -#include "unicode/uobject.h" -#include "affixpatternparser.h" -#include "digitaffixesandpadding.h" -#include "digitformatter.h" -#include "digitgrouping.h" -#include "precision.h" - -U_NAMESPACE_BEGIN - -class UnicodeString; -class FieldPosition; -class ValueFormatter; -class FieldPositionHandler; -class FixedDecimal; - -/** - * DecimalFormatImpl is the glue code between the legacy DecimalFormat class - * and the new decimal formatting classes. DecimalFormat still handles - * parsing directly. However, DecimalFormat uses attributes of this class - * for parsing when possible. - * - * The public API of this class closely mirrors the legacy API of the - * legacy DecimalFormat deviating only when the legacy API does not make - * sense. For example, although DecimalFormat has a - * getPadCharacterString() method, DecimalFormatImpl has a getPadCharacter() - * method because formatting uses only a single pad character for padding. - * - * Each legacy DecimalFormat instance heap allocates its own instance of - * this class. Most DecimalFormat methods that deal with formatting simply - * delegate to the DecimalFormat's DecimalFormatImpl method. - * - * Because DecimalFormat extends NumberFormat, Each instance of this class - * "borrows" a pointer to the NumberFormat part of its enclosing DecimalFormat - * instance. This way each DecimalFormatImpl instance can read or even modify - * the NumberFormat portion of its enclosing DecimalFormat instance. - * - * Directed acyclic graph (DAG): - * - * This class can be represented as a directed acyclic graph (DAG) where each - * vertex is an attribute, and each directed edge indicates that the value - * of the destination attribute is calculated from the value of the source - * attribute. Attributes with setter methods reside at the bottom of the - * DAG. That is, no edges point to them. We call these independent attributes - * because their values can be set independently of one another. The rest of - * the attributes are derived attributes because their values depend on the - * independent attributes. DecimalFormatImpl often uses the derived - * attributes, not the independent attributes, when formatting numbers. - * - * The independent attributes at the bottom of the DAG correspond to the legacy - * attributes of DecimalFormat while the attributes at the top of the DAG - * correspond to the attributes of the new code. The edges of the DAG - * correspond to the code that handles the complex interaction among all the - * legacy attributes of the DecimalFormat API. - * - * We use a DAG for three reasons. - * - * First, the DAG preserves backward compatibility. Clients of the legacy - * DecimalFormat expect existing getters and setters of each attribute to be - * consistent. That means if a client sets a particular attribute to a new - * value, the attribute should retain that value until the client sets it to - * a new value. The DAG allows these attributes to remain consistent even - * though the new code may not use them when formatting. - * - * Second, the DAG obviates the need to recalculate derived attributes with - * each format. Instead, the DAG "remembers" the values of all derived - * attributes. Only setting an independent attribute requires a recalculation. - * Moreover, setting an independent attribute recalculates only the affected - * dependent attributes rather than all dependent attributes. - * - * Third, the DAG abstracts away the complex interaction among the legacy - * attributes of the DecimalFormat API. - * - * Only the independent attributes of the DAG have setters and getters. - * Derived attributes have no setters (and often no getters either). - * - * Copy and assign: - * - * For copy and assign, DecimalFormatImpl copies and assigns every attribute - * regardless of whether or not it is independent. We do this for simplicity. - * - * Implementation of the DAG: - * - * The DAG consists of three smaller DAGs: - * 1. Grouping attributes - * 2. Precision attributes - * 3. Formatting attributes. - * - * The first two DAGs are simple in that setting any independent attribute - * in the DAG recalculates all the dependent attributes in that DAG. - * The updateGrouping() and updatePrecision() perform the respective - * recalculations. - * - * Because some of the derived formatting attributes are expensive to - * calculate, the formatting attributes DAG is more complex. The - * updateFormatting() method is composed of many updateFormattingXXX() - * methods, each of which recalculates a single derived attribute. The - * updateFormatting() method accepts a bitfield of recently changed - * attributes and passes this bitfield by reference to each of the - * updateFormattingXXX() methods. Each updateFormattingXXX() method checks - * the bitfield to see if any of the attributes it uses to compute the XXX - * attribute changed. If none of them changed, it exists immediately. However, - * if at least one of them changed, it recalculates the XXX attribute and - * sets the corresponding bit in the bitfield. In this way, each - * updateFormattingXXX() method encodes the directed edges in the formatting - * DAG that point to the attribute its calculating. - * - * Maintenance of the updateFormatting() method. - * - * Use care when changing the updateFormatting() method. - * The updateFormatting() method must call each updateFormattingXXX() in the - * same partial order that the formatting DAG prescribes. That is, the - * attributes near the bottom of the DAG must be calculated before attributes - * further up. As we mentioned in the prvious paragraph, the directed edges of - * the formatting DAG are encoded within each updateFormattingXXX() method. - * Finally, adding new attributes may involve adding to the bitmap that the - * updateFormatting() method uses. The top most attributes in the DAG, - * those that do not point to any attributes but only have attributes - * pointing to it, need not have a slot in the bitmap. - * - * Keep in mind that most of the code that makes the legacy DecimalFormat API - * work the way it always has before can be found in these various updateXXX() - * methods. For example the updatePrecisionForScientific() method - * handles the complex interactions amoung the various precision attributes - * when formatting in scientific notation. Changing the way attributes - * interract, often means changing one of these updateXXX() methods. - * - * Conclusion: - * - * The DecimFmtImpl class is the glue code between the legacy and new - * number formatting code. It uses a direct acyclic graph (DAG) to - * maintain backward compatibility, to make the code efficient, and to - * abstract away the complex interraction among legacy attributs. - */ - - -class DecimalFormatImpl : public UObject { -public: - -DecimalFormatImpl( - NumberFormat *super, - const Locale &locale, - const UnicodeString &pattern, - UErrorCode &status); -DecimalFormatImpl( - NumberFormat *super, - const UnicodeString &pattern, - DecimalFormatSymbols *symbolsToAdopt, - UParseError &parseError, - UErrorCode &status); -DecimalFormatImpl( - NumberFormat *super, - const DecimalFormatImpl &other, - UErrorCode &status); -DecimalFormatImpl &assign( - const DecimalFormatImpl &other, UErrorCode &status); -virtual ~DecimalFormatImpl(); -void adoptDecimalFormatSymbols(DecimalFormatSymbols *symbolsToAdopt); -const DecimalFormatSymbols &getDecimalFormatSymbols() const { - return *fSymbols; -} -UnicodeString &format( - int32_t number, - UnicodeString &appendTo, - FieldPosition &pos, - UErrorCode &status) const; -UnicodeString &format( - int32_t number, - UnicodeString &appendTo, - FieldPositionIterator *posIter, - UErrorCode &status) const; -UnicodeString &format( - int64_t number, - UnicodeString &appendTo, - FieldPosition &pos, - UErrorCode &status) const; -UnicodeString &format( - double number, - UnicodeString &appendTo, - FieldPosition &pos, - UErrorCode &status) const; -UnicodeString &format( - const DigitList &number, - UnicodeString &appendTo, - FieldPosition &pos, - UErrorCode &status) const; -UnicodeString &format( - int64_t number, - UnicodeString &appendTo, - FieldPositionIterator *posIter, - UErrorCode &status) const; -UnicodeString &format( - double number, - UnicodeString &appendTo, - FieldPositionIterator *posIter, - UErrorCode &status) const; -UnicodeString &format( - const DigitList &number, - UnicodeString &appendTo, - FieldPositionIterator *posIter, - UErrorCode &status) const; -UnicodeString &format( - StringPiece number, - UnicodeString &appendTo, - FieldPositionIterator *posIter, - UErrorCode &status) const; -UnicodeString &format( - const VisibleDigitsWithExponent &digits, - UnicodeString &appendTo, - FieldPosition &pos, - UErrorCode &status) const; -UnicodeString &format( - const VisibleDigitsWithExponent &digits, - UnicodeString &appendTo, - FieldPositionIterator *posIter, - UErrorCode &status) const; - -UBool operator==(const DecimalFormatImpl &) const; - -UBool operator!=(const DecimalFormatImpl &other) const { - return !(*this == other); -} - -void setRoundingMode(DecimalFormat::ERoundingMode mode) { - fRoundingMode = mode; - fEffPrecision.fMantissa.fExactOnly = (fRoundingMode == DecimalFormat::kRoundUnnecessary); - fEffPrecision.fMantissa.fRoundingMode = mode; -} -DecimalFormat::ERoundingMode getRoundingMode() const { - return fRoundingMode; -} -void setFailIfMoreThanMaxDigits(UBool b) { - fEffPrecision.fMantissa.fFailIfOverMax = b; -} -UBool isFailIfMoreThanMaxDigits() const { return fEffPrecision.fMantissa.fFailIfOverMax; } -void setMinimumSignificantDigits(int32_t newValue); -void setMaximumSignificantDigits(int32_t newValue); -void setMinMaxSignificantDigits(int32_t min, int32_t max); -void setScientificNotation(UBool newValue); -void setSignificantDigitsUsed(UBool newValue); - -int32_t getMinimumSignificantDigits() const { - return fMinSigDigits; } -int32_t getMaximumSignificantDigits() const { - return fMaxSigDigits; } -UBool isScientificNotation() const { return fUseScientific; } -UBool areSignificantDigitsUsed() const { return fUseSigDigits; } -void setGroupingSize(int32_t newValue); -void setSecondaryGroupingSize(int32_t newValue); -void setMinimumGroupingDigits(int32_t newValue); -int32_t getGroupingSize() const { return fGrouping.fGrouping; } -int32_t getSecondaryGroupingSize() const { return fGrouping.fGrouping2; } -int32_t getMinimumGroupingDigits() const { return fGrouping.fMinGrouping; } -void applyPattern(const UnicodeString &pattern, UErrorCode &status); -void applyPatternFavorCurrencyPrecision( - const UnicodeString &pattern, UErrorCode &status); -void applyPattern( - const UnicodeString &pattern, UParseError &perror, UErrorCode &status); -void applyLocalizedPattern(const UnicodeString &pattern, UErrorCode &status); -void applyLocalizedPattern( - const UnicodeString &pattern, UParseError &perror, UErrorCode &status); -void setCurrencyUsage(UCurrencyUsage usage, UErrorCode &status); -UCurrencyUsage getCurrencyUsage() const { return fCurrencyUsage; } -void setRoundingIncrement(double d); -double getRoundingIncrement() const; -int32_t getMultiplier() const; -void setMultiplier(int32_t m); -UChar32 getPadCharacter() const { return fAffixes.fPadChar; } -void setPadCharacter(UChar32 c) { fAffixes.fPadChar = c; } -int32_t getFormatWidth() const { return fAffixes.fWidth; } -void setFormatWidth(int32_t x) { fAffixes.fWidth = x; } -DigitAffixesAndPadding::EPadPosition getPadPosition() const { - return fAffixes.fPadPosition; -} -void setPadPosition(DigitAffixesAndPadding::EPadPosition x) { - fAffixes.fPadPosition = x; -} -int32_t getMinimumExponentDigits() const { - return fEffPrecision.fMinExponentDigits; -} -void setMinimumExponentDigits(int32_t x) { - fEffPrecision.fMinExponentDigits = x; -} -UBool isExponentSignAlwaysShown() const { - return fOptions.fExponent.fAlwaysShowSign; -} -void setExponentSignAlwaysShown(UBool x) { - fOptions.fExponent.fAlwaysShowSign = x; -} -UBool isDecimalSeparatorAlwaysShown() const { - return fOptions.fMantissa.fAlwaysShowDecimal; -} -void setDecimalSeparatorAlwaysShown(UBool x) { - fOptions.fMantissa.fAlwaysShowDecimal = x; -} -UnicodeString &getPositivePrefix(UnicodeString &result) const; -UnicodeString &getPositiveSuffix(UnicodeString &result) const; -UnicodeString &getNegativePrefix(UnicodeString &result) const; -UnicodeString &getNegativeSuffix(UnicodeString &result) const; -void setPositivePrefix(const UnicodeString &str); -void setPositiveSuffix(const UnicodeString &str); -void setNegativePrefix(const UnicodeString &str); -void setNegativeSuffix(const UnicodeString &str); -UnicodeString &toPattern(UnicodeString& result) const; -FixedDecimal &getFixedDecimal(double value, FixedDecimal &result, UErrorCode &status) const; -FixedDecimal &getFixedDecimal(DigitList &number, FixedDecimal &result, UErrorCode &status) const; -DigitList &round(DigitList &number, UErrorCode &status) const; - -VisibleDigitsWithExponent & -initVisibleDigitsWithExponent( - int64_t number, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const; -VisibleDigitsWithExponent & -initVisibleDigitsWithExponent( - double number, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const; -VisibleDigitsWithExponent & -initVisibleDigitsWithExponent( - DigitList &number, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const; - -void updatePrecision(); -void updateGrouping(); -void updateCurrency(UErrorCode &status); - - -private: -// Disallow copy and assign -DecimalFormatImpl(const DecimalFormatImpl &other); -DecimalFormatImpl &operator=(const DecimalFormatImpl &other); -NumberFormat *fSuper; -DigitList fMultiplier; -int32_t fScale; - -DecimalFormat::ERoundingMode fRoundingMode; - -// These fields include what the user can see and set. -// When the user updates these fields, it triggers automatic updates of -// other fields that may be invisible to user - -// Updating any of the following fields triggers an update to -// fEffPrecision.fMantissa.fMin, -// fEffPrecision.fMantissa.fMax, -// fEffPrecision.fMantissa.fSignificant fields -// We have this two phase update because of backward compatibility. -// DecimalFormat has to remember all settings even if those settings are -// invalid or disabled. -int32_t fMinSigDigits; -int32_t fMaxSigDigits; -UBool fUseScientific; -UBool fUseSigDigits; -// In addition to these listed above, changes to min/max int digits and -// min/max frac digits from fSuper also trigger an update. - -// Updating any of the following fields triggers an update to -// fEffGrouping field Again we do it this way because original -// grouping settings have to be retained if grouping is turned off. -DigitGrouping fGrouping; -// In addition to these listed above, changes to isGroupingUsed in -// fSuper also triggers an update to fEffGrouping. - -// Updating any of the following fields triggers updates on the following: -// fMonetary, fRules, fAffixParser, fCurrencyAffixInfo, -// fFormatter, fAffixes.fPositivePrefiix, fAffixes.fPositiveSuffix, -// fAffixes.fNegativePrefiix, fAffixes.fNegativeSuffix -// We do this two phase update because localizing the affix patterns -// and formatters can be expensive. Better to do it once with the setters -// than each time within format. -AffixPattern fPositivePrefixPattern; -AffixPattern fNegativePrefixPattern; -AffixPattern fPositiveSuffixPattern; -AffixPattern fNegativeSuffixPattern; -DecimalFormatSymbols *fSymbols; -UCurrencyUsage fCurrencyUsage; -// In addition to these listed above, changes to getCurrency() in -// fSuper also triggers an update. - -// Optional may be NULL -PluralRules *fRules; - -// These fields are totally hidden from user and are used to derive the affixes -// in fAffixes below from the four affix patterns above. -UBool fMonetary; -AffixPatternParser fAffixParser; -CurrencyAffixInfo fCurrencyAffixInfo; - -// The actual precision used when formatting -ScientificPrecision fEffPrecision; - -// The actual grouping used when formatting -DigitGrouping fEffGrouping; -SciFormatterOptions fOptions; // Encapsulates fixed precision options -DigitFormatter fFormatter; -DigitAffixesAndPadding fAffixes; - -UnicodeString &formatInt32( - int32_t number, - UnicodeString &appendTo, - FieldPositionHandler &handler, - UErrorCode &status) const; - -UnicodeString &formatInt64( - int64_t number, - UnicodeString &appendTo, - FieldPositionHandler &handler, - UErrorCode &status) const; - -UnicodeString &formatDouble( - double number, - UnicodeString &appendTo, - FieldPositionHandler &handler, - UErrorCode &status) const; - -// Scales for precent or permille symbols -UnicodeString &formatDigitList( - DigitList &number, - UnicodeString &appendTo, - FieldPositionHandler &handler, - UErrorCode &status) const; - -// Does not scale for precent or permille symbols -UnicodeString &formatAdjustedDigitList( - DigitList &number, - UnicodeString &appendTo, - FieldPositionHandler &handler, - UErrorCode &status) const; - -UnicodeString &formatVisibleDigitsWithExponent( - const VisibleDigitsWithExponent &number, - UnicodeString &appendTo, - FieldPositionHandler &handler, - UErrorCode &status) const; - -VisibleDigitsWithExponent & -initVisibleDigitsFromAdjusted( - DigitList &number, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const; - -template -UBool maybeFormatWithDigitList( - T number, - UnicodeString &appendTo, - FieldPositionHandler &handler, - UErrorCode &status) const; - -template -UBool maybeInitVisibleDigitsFromDigitList( - T number, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const; - -DigitList &adjustDigitList(DigitList &number, UErrorCode &status) const; - -void applyPattern( - const UnicodeString &pattern, - UBool localized, UParseError &perror, UErrorCode &status); - -ValueFormatter &prepareValueFormatter(ValueFormatter &vf) const; -void setMultiplierScale(int32_t s); -int32_t getPatternScale() const; -void setScale(int32_t s) { fScale = s; } -int32_t getScale() const { return fScale; } - -// Updates everything -void updateAll(UErrorCode &status); -void updateAll( - int32_t formattingFlags, - UBool updatePrecisionBasedOnCurrency, - UErrorCode &status); - -// Updates from formatting pattern changes -void updateForApplyPattern(UErrorCode &status); -void updateForApplyPatternFavorCurrencyPrecision(UErrorCode &status); - -// Updates from changes to third group of attributes -void updateFormatting(int32_t changedFormattingFields, UErrorCode &status); -void updateFormatting( - int32_t changedFormattingFields, - UBool updatePrecisionBasedOnCurrency, - UErrorCode &status); - -// Helper functions for updatePrecision -void updatePrecisionForScientific(); -void updatePrecisionForFixed(); -void extractMinMaxDigits(DigitInterval &min, DigitInterval &max) const; -void extractSigDigits(SignificantDigitInterval &sig) const; - -// Helper functions for updateFormatting -void updateFormattingUsesCurrency(int32_t &changedFormattingFields); -void updateFormattingPluralRules( - int32_t &changedFormattingFields, UErrorCode &status); -void updateFormattingAffixParser(int32_t &changedFormattingFields); -void updateFormattingCurrencyAffixInfo( - int32_t &changedFormattingFields, - UBool updatePrecisionBasedOnCurrency, - UErrorCode &status); -void updateFormattingFixedPointFormatter( - int32_t &changedFormattingFields); -void updateFormattingLocalizedPositivePrefix( - int32_t &changedFormattingFields, UErrorCode &status); -void updateFormattingLocalizedPositiveSuffix( - int32_t &changedFormattingFields, UErrorCode &status); -void updateFormattingLocalizedNegativePrefix( - int32_t &changedFormattingFields, UErrorCode &status); -void updateFormattingLocalizedNegativeSuffix( - int32_t &changedFormattingFields, UErrorCode &status); - -int32_t computeExponentPatternLength() const; -int32_t countFractionDigitAndDecimalPatternLength(int32_t fracDigitCount) const; -UnicodeString &toNumberPattern( - UBool hasPadding, int32_t minimumLength, UnicodeString& result) const; - -int32_t getOldFormatWidth() const; -const UnicodeString &getConstSymbol( - DecimalFormatSymbols::ENumberFormatSymbol symbol) const; -UBool isParseFastpath() const; - -friend class DecimalFormat; - -}; - - -U_NAMESPACE_END -#endif /* #if !UCONFIG_NO_FORMATTING */ -#endif // DECIMFMTIMPL_H -//eof diff --git a/icu4c/source/i18n/digitaffix.cpp b/icu4c/source/i18n/digitaffix.cpp deleted file mode 100644 index 3a02e4632b..0000000000 --- a/icu4c/source/i18n/digitaffix.cpp +++ /dev/null @@ -1,109 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* - * Copyright (C) 2015, International Business Machines - * Corporation and others. All Rights Reserved. - * - * file name: digitaffix.cpp - */ - -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING - -#include "digitaffix.h" -#include "fphdlimp.h" -#include "uassert.h" -#include "unistrappender.h" - -U_NAMESPACE_BEGIN - -DigitAffix::DigitAffix() : fAffix(), fAnnotations() { -} - -DigitAffix::DigitAffix( - const UChar *value, int32_t charCount, int32_t fieldId) - : fAffix(value, charCount), - fAnnotations(charCount, (UChar) fieldId, charCount) { -} - -void -DigitAffix::remove() { - fAffix.remove(); - fAnnotations.remove(); -} - -void -DigitAffix::appendUChar(UChar value, int32_t fieldId) { - fAffix.append(value); - fAnnotations.append((UChar) fieldId); -} - -void -DigitAffix::append(const UnicodeString &value, int32_t fieldId) { - fAffix.append(value); - { - UnicodeStringAppender appender(fAnnotations); - int32_t len = value.length(); - for (int32_t i = 0; i < len; ++i) { - appender.append((UChar) fieldId); - } - } -} - -void -DigitAffix::setTo(const UnicodeString &value, int32_t fieldId) { - fAffix = value; - fAnnotations.remove(); - { - UnicodeStringAppender appender(fAnnotations); - int32_t len = value.length(); - for (int32_t i = 0; i < len; ++i) { - appender.append((UChar) fieldId); - } - } -} - -void -DigitAffix::append(const UChar *value, int32_t charCount, int32_t fieldId) { - fAffix.append(value, charCount); - { - UnicodeStringAppender appender(fAnnotations); - for (int32_t i = 0; i < charCount; ++i) { - appender.append((UChar) fieldId); - } - } -} - -UnicodeString & -DigitAffix::format(FieldPositionHandler &handler, UnicodeString &appendTo) const { - int32_t len = fAffix.length(); - if (len == 0) { - return appendTo; - } - if (!handler.isRecording()) { - return appendTo.append(fAffix); - } - U_ASSERT(fAffix.length() == fAnnotations.length()); - int32_t appendToStart = appendTo.length(); - int32_t lastId = (int32_t) fAnnotations.charAt(0); - int32_t lastIdStart = 0; - for (int32_t i = 1; i < len; ++i) { - int32_t id = (int32_t) fAnnotations.charAt(i); - if (id != lastId) { - if (lastId != UNUM_FIELD_COUNT) { - handler.addAttribute(lastId, appendToStart + lastIdStart, appendToStart + i); - } - lastId = id; - lastIdStart = i; - } - } - if (lastId != UNUM_FIELD_COUNT) { - handler.addAttribute(lastId, appendToStart + lastIdStart, appendToStart + len); - } - return appendTo.append(fAffix); -} - -U_NAMESPACE_END - -#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/digitaffix.h b/icu4c/source/i18n/digitaffix.h deleted file mode 100644 index 005c36f848..0000000000 --- a/icu4c/source/i18n/digitaffix.h +++ /dev/null @@ -1,104 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* Copyright (C) 2015, International Business Machines -* Corporation and others. All Rights Reserved. -******************************************************************************* -* digitaffix.h -* -* created on: 2015jan06 -* created by: Travis Keep -*/ - -#ifndef __DIGITAFFIX_H__ -#define __DIGITAFFIX_H__ - -#include "unicode/uobject.h" - -#if !UCONFIG_NO_FORMATTING - -#include "unicode/unistr.h" -#include "unicode/unum.h" -#include "unicode/utypes.h" - -U_NAMESPACE_BEGIN - -class FieldPositionHandler; - -/** - * A prefix or suffix of a formatted number. - */ -class U_I18N_API DigitAffix : public UMemory { -public: - - /** - * Creates an empty DigitAffix. - */ - DigitAffix(); - - /** - * Creates a DigitAffix containing given UChars where all of it has - * a field type of fieldId. - */ - DigitAffix( - const UChar *value, - int32_t charCount, - int32_t fieldId=UNUM_FIELD_COUNT); - - /** - * Makes this affix be the empty string. - */ - void remove(); - - /** - * Append value to this affix. If fieldId is present, the appended - * string is considered to be the type fieldId. - */ - void appendUChar(UChar value, int32_t fieldId=UNUM_FIELD_COUNT); - - /** - * Append value to this affix. If fieldId is present, the appended - * string is considered to be the type fieldId. - */ - void append(const UnicodeString &value, int32_t fieldId=UNUM_FIELD_COUNT); - - /** - * Sets this affix to given string. The entire string - * is considered to be the type fieldId. - */ - void setTo(const UnicodeString &value, int32_t fieldId=UNUM_FIELD_COUNT); - - /** - * Append value to this affix. If fieldId is present, the appended - * string is considered to be the type fieldId. - */ - void append(const UChar *value, int32_t charCount, int32_t fieldId=UNUM_FIELD_COUNT); - - /** - * Formats this affix. - */ - UnicodeString &format( - FieldPositionHandler &handler, UnicodeString &appendTo) const; - int32_t countChar32() const { return fAffix.countChar32(); } - - /** - * Returns this affix as a unicode string. - */ - const UnicodeString & toString() const { return fAffix; } - - /** - * Returns TRUE if this object equals rhs. - */ - UBool equals(const DigitAffix &rhs) const { - return ((fAffix == rhs.fAffix) && (fAnnotations == rhs.fAnnotations)); - } -private: - UnicodeString fAffix; - UnicodeString fAnnotations; -}; - - -U_NAMESPACE_END -#endif // #if !UCONFIG_NO_FORMATTING -#endif // __DIGITAFFIX_H__ diff --git a/icu4c/source/i18n/digitaffixesandpadding.cpp b/icu4c/source/i18n/digitaffixesandpadding.cpp deleted file mode 100644 index 487d9a345d..0000000000 --- a/icu4c/source/i18n/digitaffixesandpadding.cpp +++ /dev/null @@ -1,175 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* - * Copyright (C) 2015, International Business Machines - * Corporation and others. All Rights Reserved. - * - * file name: digitaffixesandpadding.cpp - */ - -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING - -#include "unicode/plurrule.h" -#include "charstr.h" -#include "digitaffix.h" -#include "digitaffixesandpadding.h" -#include "digitlst.h" -#include "uassert.h" -#include "valueformatter.h" -#include "visibledigits.h" - -U_NAMESPACE_BEGIN - -UBool -DigitAffixesAndPadding::needsPluralRules() const { - return ( - fPositivePrefix.hasMultipleVariants() || - fPositiveSuffix.hasMultipleVariants() || - fNegativePrefix.hasMultipleVariants() || - fNegativeSuffix.hasMultipleVariants()); -} - -UnicodeString & -DigitAffixesAndPadding::formatInt32( - int32_t value, - const ValueFormatter &formatter, - FieldPositionHandler &handler, - const PluralRules *optPluralRules, - UnicodeString &appendTo, - UErrorCode &status) const { - if (U_FAILURE(status)) { - return appendTo; - } - if (optPluralRules != NULL || fWidth > 0 || !formatter.isFastFormattable(value)) { - VisibleDigitsWithExponent digits; - formatter.toVisibleDigitsWithExponent( - (int64_t) value, digits, status); - return format( - digits, - formatter, - handler, - optPluralRules, - appendTo, - status); - } - UBool bPositive = value >= 0; - const DigitAffix *prefix = bPositive ? &fPositivePrefix.getOtherVariant() : &fNegativePrefix.getOtherVariant(); - const DigitAffix *suffix = bPositive ? &fPositiveSuffix.getOtherVariant() : &fNegativeSuffix.getOtherVariant(); - if (value < 0) { - value = -value; - } - prefix->format(handler, appendTo); - formatter.formatInt32(value, handler, appendTo); - return suffix->format(handler, appendTo); -} - -static UnicodeString & -formatAffix( - const DigitAffix *affix, - FieldPositionHandler &handler, - UnicodeString &appendTo) { - if (affix) { - affix->format(handler, appendTo); - } - return appendTo; -} - -static int32_t -countAffixChar32(const DigitAffix *affix) { - if (affix) { - return affix->countChar32(); - } - return 0; -} - -UnicodeString & -DigitAffixesAndPadding::format( - const VisibleDigitsWithExponent &digits, - const ValueFormatter &formatter, - FieldPositionHandler &handler, - const PluralRules *optPluralRules, - UnicodeString &appendTo, - UErrorCode &status) const { - if (U_FAILURE(status)) { - return appendTo; - } - const DigitAffix *prefix = NULL; - const DigitAffix *suffix = NULL; - if (!digits.isNaN()) { - UBool bPositive = !digits.isNegative(); - const PluralAffix *pluralPrefix = bPositive ? &fPositivePrefix : &fNegativePrefix; - const PluralAffix *pluralSuffix = bPositive ? &fPositiveSuffix : &fNegativeSuffix; - if (optPluralRules == NULL || digits.isInfinite()) { - prefix = &pluralPrefix->getOtherVariant(); - suffix = &pluralSuffix->getOtherVariant(); - } else { - UnicodeString count(optPluralRules->select(digits)); - prefix = &pluralPrefix->getByCategory(count); - suffix = &pluralSuffix->getByCategory(count); - } - } - if (fWidth <= 0) { - formatAffix(prefix, handler, appendTo); - formatter.format(digits, handler, appendTo); - return formatAffix(suffix, handler, appendTo); - } - int32_t codePointCount = countAffixChar32(prefix) + formatter.countChar32(digits) + countAffixChar32(suffix); - int32_t paddingCount = fWidth - codePointCount; - switch (fPadPosition) { - case kPadBeforePrefix: - appendPadding(paddingCount, appendTo); - formatAffix(prefix, handler, appendTo); - formatter.format(digits, handler, appendTo); - return formatAffix(suffix, handler, appendTo); - case kPadAfterPrefix: - formatAffix(prefix, handler, appendTo); - appendPadding(paddingCount, appendTo); - formatter.format(digits, handler, appendTo); - return formatAffix(suffix, handler, appendTo); - case kPadBeforeSuffix: - formatAffix(prefix, handler, appendTo); - formatter.format(digits, handler, appendTo); - appendPadding(paddingCount, appendTo); - return formatAffix(suffix, handler, appendTo); - case kPadAfterSuffix: - formatAffix(prefix, handler, appendTo); - formatter.format(digits, handler, appendTo); - formatAffix(suffix, handler, appendTo); - return appendPadding(paddingCount, appendTo); - default: - U_ASSERT(FALSE); - return appendTo; - } -} - -UnicodeString & -DigitAffixesAndPadding::format( - DigitList &value, - const ValueFormatter &formatter, - FieldPositionHandler &handler, - const PluralRules *optPluralRules, - UnicodeString &appendTo, - UErrorCode &status) const { - VisibleDigitsWithExponent digits; - formatter.toVisibleDigitsWithExponent( - value, digits, status); - if (U_FAILURE(status)) { - return appendTo; - } - return format( - digits, formatter, handler, optPluralRules, appendTo, status); -} - -UnicodeString & -DigitAffixesAndPadding::appendPadding(int32_t paddingCount, UnicodeString &appendTo) const { - for (int32_t i = 0; i < paddingCount; ++i) { - appendTo.append(fPadChar); - } - return appendTo; -} - - -U_NAMESPACE_END -#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/digitaffixesandpadding.h b/icu4c/source/i18n/digitaffixesandpadding.h deleted file mode 100644 index 7c4772b5d7..0000000000 --- a/icu4c/source/i18n/digitaffixesandpadding.h +++ /dev/null @@ -1,179 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* Copyright (C) 2015, International Business Machines -* Corporation and others. All Rights Reserved. -******************************************************************************* -* digitaffixesandpadding.h -* -* created on: 2015jan06 -* created by: Travis Keep -*/ - -#ifndef __DIGITAFFIXESANDPADDING_H__ -#define __DIGITAFFIXESANDPADDING_H__ - -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING - -#include "unicode/uobject.h" -#include "pluralaffix.h" - -U_NAMESPACE_BEGIN - -class DigitList; -class ValueFormatter; -class UnicodeString; -class FieldPositionHandler; -class PluralRules; -class VisibleDigitsWithExponent; - -/** - * A formatter of numbers. This class can format any numerical value - * except for not a number (NaN), positive infinity, and negative infinity. - * This class manages prefixes, suffixes, and padding but delegates the - * formatting of actual positive values to a ValueFormatter. - */ -class U_I18N_API DigitAffixesAndPadding : public UMemory { -public: - -/** - * Equivalent to DecimalFormat EPadPosition, but redeclared here to prevent - * depending on DecimalFormat which would cause a circular dependency. - */ -enum EPadPosition { - kPadBeforePrefix, - kPadAfterPrefix, - kPadBeforeSuffix, - kPadAfterSuffix -}; - -/** - * The positive prefix - */ -PluralAffix fPositivePrefix; - -/** - * The positive suffix - */ -PluralAffix fPositiveSuffix; - -/** - * The negative suffix - */ -PluralAffix fNegativePrefix; - -/** - * The negative suffix - */ -PluralAffix fNegativeSuffix; - -/** - * The padding position - */ -EPadPosition fPadPosition; - -/** - * The padding character. - */ -UChar32 fPadChar; - -/** - * The field width in code points. The format method inserts instances of - * the padding character as needed in the desired padding position so that - * the entire formatted string contains this many code points. If the - * formatted string already exceeds this many code points, the format method - * inserts no padding. - */ -int32_t fWidth; - -/** - * Pad position is before prefix; padding character is '*' field width is 0. - * The affixes are all the empty string with no annotated fields with just - * the 'other' plural variation. - */ -DigitAffixesAndPadding() - : fPadPosition(kPadBeforePrefix), fPadChar(0x2a), fWidth(0) { } - -/** - * Returns TRUE if this object is equal to rhs. - */ -UBool equals(const DigitAffixesAndPadding &rhs) const { - return (fPositivePrefix.equals(rhs.fPositivePrefix) && - fPositiveSuffix.equals(rhs.fPositiveSuffix) && - fNegativePrefix.equals(rhs.fNegativePrefix) && - fNegativeSuffix.equals(rhs.fNegativeSuffix) && - fPadPosition == rhs.fPadPosition && - fWidth == rhs.fWidth && - fPadChar == rhs.fPadChar); -} - -/** - * Returns TRUE if a plural rules instance is needed to complete the - * formatting by detecting if any of the affixes have multiple plural - * variations. - */ -UBool needsPluralRules() const; - -/** - * Formats value and appends to appendTo. - * - * @param value the value to format. May be NaN or ininite. - * @param formatter handles the details of formatting the actual value. - * @param handler records field positions - * @param optPluralRules the plural rules, but may be NULL if - * needsPluralRules returns FALSE. - * @appendTo formatted string appended here. - * @status any error returned here. - */ -UnicodeString &format( - const VisibleDigitsWithExponent &value, - const ValueFormatter &formatter, - FieldPositionHandler &handler, - const PluralRules *optPluralRules, - UnicodeString &appendTo, - UErrorCode &status) const; - -/** - * For testing only. - */ -UnicodeString &format( - DigitList &value, - const ValueFormatter &formatter, - FieldPositionHandler &handler, - const PluralRules *optPluralRules, - UnicodeString &appendTo, - UErrorCode &status) const; - -/** - * Formats a 32-bit integer and appends to appendTo. When formatting an - * integer, this method is preferred to plain format as it can run - * several times faster under certain conditions. - * - * @param value the value to format. - * @param formatter handles the details of formatting the actual value. - * @param handler records field positions - * @param optPluralRules the plural rules, but may be NULL if - * needsPluralRules returns FALSE. - * @appendTo formatted string appended here. - * @status any error returned here. - */ -UnicodeString &formatInt32( - int32_t value, - const ValueFormatter &formatter, - FieldPositionHandler &handler, - const PluralRules *optPluralRules, - UnicodeString &appendTo, - UErrorCode &status) const; - -private: -UnicodeString &appendPadding(int32_t paddingCount, UnicodeString &appendTo) const; - -}; - - -U_NAMESPACE_END -#endif /* #if !UCONFIG_NO_FORMATTING */ -#endif // __DIGITAFFIXANDPADDING_H__ diff --git a/icu4c/source/i18n/digitformatter.cpp b/icu4c/source/i18n/digitformatter.cpp deleted file mode 100644 index 48338850f9..0000000000 --- a/icu4c/source/i18n/digitformatter.cpp +++ /dev/null @@ -1,417 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* - * Copyright (C) 2015, International Business Machines - * Corporation and others. All Rights Reserved. - * - * file name: digitformatter.cpp - */ - -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING - -#include "unicode/dcfmtsym.h" -#include "unicode/unum.h" - -#include "digitformatter.h" -#include "digitgrouping.h" -#include "digitinterval.h" -#include "digitlst.h" -#include "fphdlimp.h" -#include "smallintformatter.h" -#include "unistrappender.h" -#include "visibledigits.h" - -U_NAMESPACE_BEGIN - -DigitFormatter::DigitFormatter() - : fGroupingSeparator(",", -1, US_INV), fDecimal(".", -1, US_INV), - fNegativeSign("-", -1, US_INV), fPositiveSign("+", -1, US_INV), - fIsStandardDigits(TRUE), fExponent("E", -1, US_INV) { - for (int32_t i = 0; i < 10; ++i) { - fLocalizedDigits[i] = (UChar32) (0x30 + i); - } - fInfinity.setTo(UnicodeString("Inf", -1, US_INV), UNUM_INTEGER_FIELD); - fNan.setTo(UnicodeString("Nan", -1, US_INV), UNUM_INTEGER_FIELD); -} - -DigitFormatter::DigitFormatter(const DecimalFormatSymbols &symbols) { - setDecimalFormatSymbols(symbols); -} - -void -DigitFormatter::setOtherDecimalFormatSymbols( - const DecimalFormatSymbols &symbols) { - fLocalizedDigits[0] = symbols.getConstSymbol(DecimalFormatSymbols::kZeroDigitSymbol).char32At(0); - fLocalizedDigits[1] = symbols.getConstSymbol(DecimalFormatSymbols::kOneDigitSymbol).char32At(0); - fLocalizedDigits[2] = symbols.getConstSymbol(DecimalFormatSymbols::kTwoDigitSymbol).char32At(0); - fLocalizedDigits[3] = symbols.getConstSymbol(DecimalFormatSymbols::kThreeDigitSymbol).char32At(0); - fLocalizedDigits[4] = symbols.getConstSymbol(DecimalFormatSymbols::kFourDigitSymbol).char32At(0); - fLocalizedDigits[5] = symbols.getConstSymbol(DecimalFormatSymbols::kFiveDigitSymbol).char32At(0); - fLocalizedDigits[6] = symbols.getConstSymbol(DecimalFormatSymbols::kSixDigitSymbol).char32At(0); - fLocalizedDigits[7] = symbols.getConstSymbol(DecimalFormatSymbols::kSevenDigitSymbol).char32At(0); - fLocalizedDigits[8] = symbols.getConstSymbol(DecimalFormatSymbols::kEightDigitSymbol).char32At(0); - fLocalizedDigits[9] = symbols.getConstSymbol(DecimalFormatSymbols::kNineDigitSymbol).char32At(0); - fIsStandardDigits = isStandardDigits(); - fNegativeSign = symbols.getConstSymbol(DecimalFormatSymbols::kMinusSignSymbol); - fPositiveSign = symbols.getConstSymbol(DecimalFormatSymbols::kPlusSignSymbol); - fInfinity.setTo(symbols.getConstSymbol(DecimalFormatSymbols::kInfinitySymbol), UNUM_INTEGER_FIELD); - fNan.setTo(symbols.getConstSymbol(DecimalFormatSymbols::kNaNSymbol), UNUM_INTEGER_FIELD); - fExponent = symbols.getConstSymbol(DecimalFormatSymbols::kExponentialSymbol); -} - -void -DigitFormatter::setDecimalFormatSymbolsForMonetary( - const DecimalFormatSymbols &symbols) { - setOtherDecimalFormatSymbols(symbols); - fGroupingSeparator = symbols.getConstSymbol(DecimalFormatSymbols::kMonetaryGroupingSeparatorSymbol); - fDecimal = symbols.getConstSymbol(DecimalFormatSymbols::kMonetarySeparatorSymbol); -} - -void -DigitFormatter::setDecimalFormatSymbols( - const DecimalFormatSymbols &symbols) { - setOtherDecimalFormatSymbols(symbols); - fGroupingSeparator = symbols.getConstSymbol(DecimalFormatSymbols::kGroupingSeparatorSymbol); - fDecimal = symbols.getConstSymbol(DecimalFormatSymbols::kDecimalSeparatorSymbol); -} - -static void appendField( - int32_t fieldId, - const UnicodeString &value, - FieldPositionHandler &handler, - UnicodeString &appendTo) { - int32_t currentLength = appendTo.length(); - appendTo.append(value); - handler.addAttribute( - fieldId, - currentLength, - appendTo.length()); -} - -int32_t DigitFormatter::countChar32( - const DigitGrouping &grouping, - const DigitInterval &interval, - const DigitFormatterOptions &options) const { - int32_t result = interval.length(); - - // We always emit '0' in lieu of no digits. - if (result == 0) { - result = 1; - } - if (options.fAlwaysShowDecimal || interval.getLeastSignificantInclusive() < 0) { - result += fDecimal.countChar32(); - } - result += grouping.getSeparatorCount(interval.getIntDigitCount()) * fGroupingSeparator.countChar32(); - return result; -} - -int32_t -DigitFormatter::countChar32( - const VisibleDigits &digits, - const DigitGrouping &grouping, - const DigitFormatterOptions &options) const { - if (digits.isNaN()) { - return countChar32ForNaN(); - } - if (digits.isInfinite()) { - return countChar32ForInfinity(); - } - return countChar32( - grouping, - digits.getInterval(), - options); -} - -int32_t -DigitFormatter::countChar32( - const VisibleDigitsWithExponent &digits, - const SciFormatterOptions &options) const { - if (digits.isNaN()) { - return countChar32ForNaN(); - } - if (digits.isInfinite()) { - return countChar32ForInfinity(); - } - const VisibleDigits *exponent = digits.getExponent(); - if (exponent == NULL) { - DigitGrouping grouping; - return countChar32( - grouping, - digits.getMantissa().getInterval(), - options.fMantissa); - } - return countChar32( - *exponent, digits.getMantissa().getInterval(), options); -} - -int32_t -DigitFormatter::countChar32( - const VisibleDigits &exponent, - const DigitInterval &mantissaInterval, - const SciFormatterOptions &options) const { - DigitGrouping grouping; - int32_t count = countChar32( - grouping, mantissaInterval, options.fMantissa); - count += fExponent.countChar32(); - count += countChar32ForExponent( - exponent, options.fExponent); - return count; -} - -UnicodeString &DigitFormatter::format( - const VisibleDigits &digits, - const DigitGrouping &grouping, - const DigitFormatterOptions &options, - FieldPositionHandler &handler, - UnicodeString &appendTo) const { - if (digits.isNaN()) { - return formatNaN(handler, appendTo); - } - if (digits.isInfinite()) { - return formatInfinity(handler, appendTo); - } - - const DigitInterval &interval = digits.getInterval(); - int32_t digitsLeftOfDecimal = interval.getMostSignificantExclusive(); - int32_t lastDigitPos = interval.getLeastSignificantInclusive(); - int32_t intBegin = appendTo.length(); - int32_t fracBegin = 0; /* initialize to avoid compiler warning */ - - // Emit "0" instead of empty string. - if (digitsLeftOfDecimal == 0 && lastDigitPos == 0) { - appendTo.append(fLocalizedDigits[0]); - handler.addAttribute(UNUM_INTEGER_FIELD, intBegin, appendTo.length()); - if (options.fAlwaysShowDecimal) { - appendField( - UNUM_DECIMAL_SEPARATOR_FIELD, - fDecimal, - handler, - appendTo); - } - return appendTo; - } - { - UnicodeStringAppender appender(appendTo); - for (int32_t i = interval.getMostSignificantExclusive() - 1; - i >= interval.getLeastSignificantInclusive(); --i) { - if (i == -1) { - appender.flush(); - appendField( - UNUM_DECIMAL_SEPARATOR_FIELD, - fDecimal, - handler, - appendTo); - fracBegin = appendTo.length(); - } - appender.append(fLocalizedDigits[digits.getDigitByExponent(i)]); - if (grouping.isSeparatorAt(digitsLeftOfDecimal, i)) { - appender.flush(); - appendField( - UNUM_GROUPING_SEPARATOR_FIELD, - fGroupingSeparator, - handler, - appendTo); - } - if (i == 0) { - appender.flush(); - if (digitsLeftOfDecimal > 0) { - handler.addAttribute(UNUM_INTEGER_FIELD, intBegin, appendTo.length()); - } - } - } - if (options.fAlwaysShowDecimal && lastDigitPos == 0) { - appender.flush(); - appendField( - UNUM_DECIMAL_SEPARATOR_FIELD, - fDecimal, - handler, - appendTo); - } - } - // lastDigitPos is never > 0 so we are guaranteed that kIntegerField - // is already added. - if (lastDigitPos < 0) { - handler.addAttribute(UNUM_FRACTION_FIELD, fracBegin, appendTo.length()); - } - return appendTo; -} - -UnicodeString & -DigitFormatter::format( - const VisibleDigitsWithExponent &digits, - const SciFormatterOptions &options, - FieldPositionHandler &handler, - UnicodeString &appendTo) const { - DigitGrouping grouping; - format( - digits.getMantissa(), - grouping, - options.fMantissa, - handler, - appendTo); - const VisibleDigits *exponent = digits.getExponent(); - if (exponent == NULL) { - return appendTo; - } - int32_t expBegin = appendTo.length(); - appendTo.append(fExponent); - handler.addAttribute( - UNUM_EXPONENT_SYMBOL_FIELD, expBegin, appendTo.length()); - return formatExponent( - *exponent, - options.fExponent, - UNUM_EXPONENT_SIGN_FIELD, - UNUM_EXPONENT_FIELD, - handler, - appendTo); -} - -static int32_t formatInt( - int32_t value, uint8_t *digits) { - int32_t idx = 0; - while (value > 0) { - digits[idx++] = (uint8_t) (value % 10); - value /= 10; - } - return idx; -} - -UnicodeString & -DigitFormatter::formatDigits( - const uint8_t *digits, - int32_t count, - const IntDigitCountRange &range, - int32_t intField, - FieldPositionHandler &handler, - UnicodeString &appendTo) const { - int32_t i = range.pin(count) - 1; - int32_t begin = appendTo.length(); - - // Always emit '0' as placeholder for empty string. - if (i == -1) { - appendTo.append(fLocalizedDigits[0]); - handler.addAttribute(intField, begin, appendTo.length()); - return appendTo; - } - { - UnicodeStringAppender appender(appendTo); - for (; i >= count; --i) { - appender.append(fLocalizedDigits[0]); - } - for (; i >= 0; --i) { - appender.append(fLocalizedDigits[digits[i]]); - } - } - handler.addAttribute(intField, begin, appendTo.length()); - return appendTo; -} - -UnicodeString & -DigitFormatter::formatExponent( - const VisibleDigits &digits, - const DigitFormatterIntOptions &options, - int32_t signField, - int32_t intField, - FieldPositionHandler &handler, - UnicodeString &appendTo) const { - UBool neg = digits.isNegative(); - if (neg || options.fAlwaysShowSign) { - appendField( - signField, - neg ? fNegativeSign : fPositiveSign, - handler, - appendTo); - } - int32_t begin = appendTo.length(); - DigitGrouping grouping; - DigitFormatterOptions expOptions; - FieldPosition fpos(FieldPosition::DONT_CARE); - FieldPositionOnlyHandler noHandler(fpos); - format( - digits, - grouping, - expOptions, - noHandler, - appendTo); - handler.addAttribute(intField, begin, appendTo.length()); - return appendTo; -} - -int32_t -DigitFormatter::countChar32ForExponent( - const VisibleDigits &exponent, - const DigitFormatterIntOptions &options) const { - int32_t result = 0; - UBool neg = exponent.isNegative(); - if (neg || options.fAlwaysShowSign) { - result += neg ? fNegativeSign.countChar32() : fPositiveSign.countChar32(); - } - DigitGrouping grouping; - DigitFormatterOptions expOptions; - result += countChar32(grouping, exponent.getInterval(), expOptions); - return result; -} - -UnicodeString & -DigitFormatter::formatPositiveInt32( - int32_t positiveValue, - const IntDigitCountRange &range, - FieldPositionHandler &handler, - UnicodeString &appendTo) const { - // super fast path - if (fIsStandardDigits && SmallIntFormatter::canFormat(positiveValue, range)) { - int32_t begin = appendTo.length(); - SmallIntFormatter::format(positiveValue, range, appendTo); - handler.addAttribute(UNUM_INTEGER_FIELD, begin, appendTo.length()); - return appendTo; - } - uint8_t digits[10]; - int32_t count = formatInt(positiveValue, digits); - return formatDigits( - digits, - count, - range, - UNUM_INTEGER_FIELD, - handler, - appendTo); -} - -UBool DigitFormatter::isStandardDigits() const { - UChar32 cdigit = 0x30; - for (int32_t i = 0; i < UPRV_LENGTHOF(fLocalizedDigits); ++i) { - if (fLocalizedDigits[i] != cdigit) { - return FALSE; - } - ++cdigit; - } - return TRUE; -} - -UBool -DigitFormatter::equals(const DigitFormatter &rhs) const { - UBool result = (fGroupingSeparator == rhs.fGroupingSeparator) && - (fDecimal == rhs.fDecimal) && - (fNegativeSign == rhs.fNegativeSign) && - (fPositiveSign == rhs.fPositiveSign) && - (fInfinity.equals(rhs.fInfinity)) && - (fNan.equals(rhs.fNan)) && - (fIsStandardDigits == rhs.fIsStandardDigits) && - (fExponent == rhs.fExponent); - - if (!result) { - return FALSE; - } - for (int32_t i = 0; i < UPRV_LENGTHOF(fLocalizedDigits); ++i) { - if (fLocalizedDigits[i] != rhs.fLocalizedDigits[i]) { - return FALSE; - } - } - return TRUE; -} - - -U_NAMESPACE_END - -#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/digitformatter.h b/icu4c/source/i18n/digitformatter.h deleted file mode 100644 index 54a54c3639..0000000000 --- a/icu4c/source/i18n/digitformatter.h +++ /dev/null @@ -1,288 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* Copyright (C) 2015, International Business Machines -* Corporation and others. All Rights Reserved. -******************************************************************************* -* digitformatter.h -* -* created on: 2015jan06 -* created by: Travis Keep -*/ - -#ifndef __DIGITFORMATTER_H__ -#define __DIGITFORMATTER_H__ - -#include "unicode/uobject.h" - -#if !UCONFIG_NO_FORMATTING - -#include "unicode/utypes.h" -#include "unicode/unistr.h" -#include "digitaffix.h" - -U_NAMESPACE_BEGIN - -class DecimalFormatSymbols; -class DigitList; -class DigitGrouping; -class DigitInterval; -class UnicodeString; -class FieldPositionHandler; -class IntDigitCountRange; -class VisibleDigits; -class VisibleDigitsWithExponent; - -/** - * Various options for formatting in fixed point. - */ -class U_I18N_API DigitFormatterOptions : public UMemory { - public: - DigitFormatterOptions() : fAlwaysShowDecimal(FALSE) { } - - /** - * Returns TRUE if this object equals rhs. - */ - UBool equals(const DigitFormatterOptions &rhs) const { - return ( - fAlwaysShowDecimal == rhs.fAlwaysShowDecimal); - } - - /** - * Returns TRUE if these options allow for fast formatting of - * integers. - */ - UBool isFastFormattable() const { - return (fAlwaysShowDecimal == FALSE); - } - - /** - * If TRUE, show the decimal separator even when there are no fraction - * digits. default is FALSE. - */ - UBool fAlwaysShowDecimal; -}; - -/** - * Various options for formatting an integer. - */ -class U_I18N_API DigitFormatterIntOptions : public UMemory { - public: - DigitFormatterIntOptions() : fAlwaysShowSign(FALSE) { } - - /** - * Returns TRUE if this object equals rhs. - */ - UBool equals(const DigitFormatterIntOptions &rhs) const { - return (fAlwaysShowSign == rhs.fAlwaysShowSign); - } - - /** - * If TRUE, always prefix the integer with its sign even if the number is - * positive. Default is FALSE. - */ - UBool fAlwaysShowSign; -}; - -/** - * Options for formatting in scientific notation. - */ -class U_I18N_API SciFormatterOptions : public UMemory { - public: - - /** - * Returns TRUE if this object equals rhs. - */ - UBool equals(const SciFormatterOptions &rhs) const { - return (fMantissa.equals(rhs.fMantissa) && - fExponent.equals(rhs.fExponent)); - } - - /** - * Options for formatting the mantissa. - */ - DigitFormatterOptions fMantissa; - - /** - * Options for formatting the exponent. - */ - DigitFormatterIntOptions fExponent; -}; - - -/** - * Does fixed point formatting. - * - * This class only does fixed point formatting. It does no rounding before - * formatting. - */ -class U_I18N_API DigitFormatter : public UMemory { -public: - -/** - * Decimal separator is period (.), Plus sign is plus (+), - * minus sign is minus (-), grouping separator is comma (,), digits are 0-9. - */ -DigitFormatter(); - -/** - * Let symbols determine the digits, decimal separator, - * plus and mius sign, grouping separator, and possibly other settings. - */ -DigitFormatter(const DecimalFormatSymbols &symbols); - -/** - * Change what this instance uses for digits, decimal separator, - * plus and mius sign, grouping separator, and possibly other settings - * according to symbols. - */ -void setDecimalFormatSymbols(const DecimalFormatSymbols &symbols); - -/** - * Change what this instance uses for digits, decimal separator, - * plus and mius sign, grouping separator, and possibly other settings - * according to symbols in the context of monetary amounts. - */ -void setDecimalFormatSymbolsForMonetary(const DecimalFormatSymbols &symbols); - -/** - * Fixed point formatting. - * - * @param positiveDigits the value to format - * Negative sign can be present, but it won't show. - * @param grouping controls how digit grouping is done - * @param options formatting options - * @param handler records field positions - * @param appendTo formatted value appended here. - * @return appendTo - */ -UnicodeString &format( - const VisibleDigits &positiveDigits, - const DigitGrouping &grouping, - const DigitFormatterOptions &options, - FieldPositionHandler &handler, - UnicodeString &appendTo) const; - -/** - * formats in scientifc notation. - * @param positiveDigits the value to format. - * Negative sign can be present, but it won't show. - * @param options formatting options - * @param handler records field positions. - * @param appendTo formatted value appended here. - */ -UnicodeString &format( - const VisibleDigitsWithExponent &positiveDigits, - const SciFormatterOptions &options, - FieldPositionHandler &handler, - UnicodeString &appendTo) const; - -/** - * Fixed point formatting of integers. - * Always performed with no grouping and no decimal point. - * - * @param positiveValue the value to format must be positive. - * @param range specifies minimum and maximum number of digits. - * @param handler records field positions - * @param appendTo formatted value appended here. - * @return appendTo - */ -UnicodeString &formatPositiveInt32( - int32_t positiveValue, - const IntDigitCountRange &range, - FieldPositionHandler &handler, - UnicodeString &appendTo) const; - -/** - * Counts how many code points are needed for fixed formatting. - * If digits is negative, the negative sign is not included in the count. - */ -int32_t countChar32( - const VisibleDigits &digits, - const DigitGrouping &grouping, - const DigitFormatterOptions &options) const; - -/** - * Counts how many code points are needed for scientific formatting. - * If digits is negative, the negative sign is not included in the count. - */ -int32_t countChar32( - const VisibleDigitsWithExponent &digits, - const SciFormatterOptions &options) const; - -/** - * Returns TRUE if this object equals rhs. - */ -UBool equals(const DigitFormatter &rhs) const; - -private: -UChar32 fLocalizedDigits[10]; -UnicodeString fGroupingSeparator; -UnicodeString fDecimal; -UnicodeString fNegativeSign; -UnicodeString fPositiveSign; -DigitAffix fInfinity; -DigitAffix fNan; -UBool fIsStandardDigits; -UnicodeString fExponent; -UBool isStandardDigits() const; - -UnicodeString &formatDigits( - const uint8_t *digits, - int32_t count, - const IntDigitCountRange &range, - int32_t intField, - FieldPositionHandler &handler, - UnicodeString &appendTo) const; - -void setOtherDecimalFormatSymbols(const DecimalFormatSymbols &symbols); - -int32_t countChar32( - const VisibleDigits &exponent, - const DigitInterval &mantissaInterval, - const SciFormatterOptions &options) const; - -UnicodeString &formatNaN( - FieldPositionHandler &handler, - UnicodeString &appendTo) const { - return fNan.format(handler, appendTo); -} - -int32_t countChar32ForNaN() const { - return fNan.toString().countChar32(); -} - -UnicodeString &formatInfinity( - FieldPositionHandler &handler, - UnicodeString &appendTo) const { - return fInfinity.format(handler, appendTo); -} - -int32_t countChar32ForInfinity() const { - return fInfinity.toString().countChar32(); -} - -UnicodeString &formatExponent( - const VisibleDigits &digits, - const DigitFormatterIntOptions &options, - int32_t signField, - int32_t intField, - FieldPositionHandler &handler, - UnicodeString &appendTo) const; - -int32_t countChar32( - const DigitGrouping &grouping, - const DigitInterval &interval, - const DigitFormatterOptions &options) const; - -int32_t countChar32ForExponent( - const VisibleDigits &exponent, - const DigitFormatterIntOptions &options) const; - -}; - - -U_NAMESPACE_END -#endif /* #if !UCONFIG_NO_FORMATTING */ -#endif // __DIGITFORMATTER_H__ diff --git a/icu4c/source/i18n/digitgrouping.cpp b/icu4c/source/i18n/digitgrouping.cpp deleted file mode 100644 index 67f8f2b061..0000000000 --- a/icu4c/source/i18n/digitgrouping.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* - * Copyright (C) 2015, International Business Machines - * Corporation and others. All Rights Reserved. - * - * file name: digitgrouping.cpp - */ - -#include "unicode/utypes.h" - -#include "digitgrouping.h" -#include "smallintformatter.h" - -U_NAMESPACE_BEGIN - -UBool DigitGrouping::isSeparatorAt( - int32_t digitsLeftOfDecimal, int32_t digitPos) const { - if (!isGroupingEnabled(digitsLeftOfDecimal) || digitPos < fGrouping) { - return FALSE; - } - return ((digitPos - fGrouping) % getGrouping2() == 0); -} - -int32_t DigitGrouping::getSeparatorCount(int32_t digitsLeftOfDecimal) const { - if (!isGroupingEnabled(digitsLeftOfDecimal)) { - return 0; - } - return (digitsLeftOfDecimal - 1 - fGrouping) / getGrouping2() + 1; -} - -UBool DigitGrouping::isGroupingEnabled(int32_t digitsLeftOfDecimal) const { - return (isGroupingUsed() - && digitsLeftOfDecimal >= fGrouping + getMinGrouping()); -} - -UBool DigitGrouping::isNoGrouping( - int32_t positiveValue, const IntDigitCountRange &range) const { - return getSeparatorCount( - SmallIntFormatter::estimateDigitCount(positiveValue, range)) == 0; -} - -int32_t DigitGrouping::getGrouping2() const { - return (fGrouping2 > 0 ? fGrouping2 : fGrouping); -} - -int32_t DigitGrouping::getMinGrouping() const { - return (fMinGrouping > 0 ? fMinGrouping : 1); -} - -void -DigitGrouping::clear() { - fMinGrouping = 0; - fGrouping = 0; - fGrouping2 = 0; -} - -U_NAMESPACE_END - diff --git a/icu4c/source/i18n/digitgrouping.h b/icu4c/source/i18n/digitgrouping.h deleted file mode 100644 index 131d76b508..0000000000 --- a/icu4c/source/i18n/digitgrouping.h +++ /dev/null @@ -1,112 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* Copyright (C) 2015, International Business Machines -* Corporation and others. All Rights Reserved. -******************************************************************************* -* digitgrouping.h -* -* created on: 2015jan6 -* created by: Travis Keep -*/ - -#ifndef __DIGITGROUPING_H__ -#define __DIGITGROUPING_H__ - -#include "unicode/uobject.h" -#include "unicode/utypes.h" - -U_NAMESPACE_BEGIN - -class IntDigitCountRange; - -/** - * The digit grouping policy. - */ -class U_I18N_API DigitGrouping : public UMemory { -public: - /** - * Default is no digit grouping. - */ - DigitGrouping() : fGrouping(0), fGrouping2(0), fMinGrouping(0) { } - - /** - * Returns TRUE if this object is equal to rhs. - */ - UBool equals(const DigitGrouping &rhs) const { - return ((fGrouping == rhs.fGrouping) && - (fGrouping2 == rhs.fGrouping2) && - (fMinGrouping == rhs.fMinGrouping)); - } - - /** - * Returns true if a separator is needed after a particular digit. - * @param digitsLeftOfDecimal the total count of digits left of the - * decimal. - * @param digitPos 0 is the one's place; 1 is the 10's place; -1 is the - * 1/10's place etc. - */ - UBool isSeparatorAt(int32_t digitsLeftOfDecimal, int32_t digitPos) const; - - /** - * Returns the total number of separators to be used to format a particular - * number. - * @param digitsLeftOfDecimal the total number of digits to the left of - * the decimal. - */ - int32_t getSeparatorCount(int32_t digitsLeftOfDecimal) const; - - /** - * Returns true if grouping is used FALSE otherwise. When - * isGroupingUsed() returns FALSE; isSeparatorAt always returns FALSE - * and getSeparatorCount always returns 0. - */ - UBool isGroupingUsed() const { return fGrouping > 0; } - - /** - * Returns TRUE if this instance would not add grouping separators - * when formatting value using the given constraint on digit count. - * - * @param value the value to format. - * @param range the minimum and maximum digits for formatting value. - */ - UBool isNoGrouping( - int32_t positiveValue, const IntDigitCountRange &range) const; - - /** - * Clears this instance so that digit grouping is not in effect. - */ - void clear(); - -public: - - /** - * Primary grouping size. A value of 0, the default, or a negative - * number causes isGroupingUsed() to return FALSE. - */ - int32_t fGrouping; - - /** - * Secondary grouping size. If > 0, this size is used instead of - * 'fGrouping' for all but the group just to the left of the decimal - * point. The default value of 0, or a negative value indicates that - * there is no secondary grouping size. - */ - int32_t fGrouping2; - - /** - * If set (that is > 0), uses no grouping separators if fewer than - * (fGrouping + fMinGrouping) digits appear left of the decimal place. - * The default value for this field is 0. - */ - int32_t fMinGrouping; -private: - UBool isGroupingEnabled(int32_t digitsLeftOfDecimal) const; - int32_t getGrouping2() const; - int32_t getMinGrouping() const; -}; - -U_NAMESPACE_END - -#endif // __DIGITGROUPING_H__ diff --git a/icu4c/source/i18n/digitinterval.cpp b/icu4c/source/i18n/digitinterval.cpp deleted file mode 100644 index fd0e3543c8..0000000000 --- a/icu4c/source/i18n/digitinterval.cpp +++ /dev/null @@ -1,56 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* - * Copyright (C) 2015, International Business Machines - * Corporation and others. All Rights Reserved. - * - * file name: digitinterval.cpp - */ - -#include "unicode/utypes.h" - -#include "digitinterval.h" - -U_NAMESPACE_BEGIN - -void DigitInterval::expandToContain(const DigitInterval &rhs) { - if (fSmallestInclusive > rhs.fSmallestInclusive) { - fSmallestInclusive = rhs.fSmallestInclusive; - } - if (fLargestExclusive < rhs.fLargestExclusive) { - fLargestExclusive = rhs.fLargestExclusive; - } -} - -void DigitInterval::shrinkToFitWithin(const DigitInterval &rhs) { - if (fSmallestInclusive < rhs.fSmallestInclusive) { - fSmallestInclusive = rhs.fSmallestInclusive; - } - if (fLargestExclusive > rhs.fLargestExclusive) { - fLargestExclusive = rhs.fLargestExclusive; - } -} - -void DigitInterval::setIntDigitCount(int32_t count) { - fLargestExclusive = count < 0 ? INT32_MAX : count; -} - -void DigitInterval::setFracDigitCount(int32_t count) { - fSmallestInclusive = count < 0 ? INT32_MIN : -count; -} - -void DigitInterval::expandToContainDigit(int32_t digitExponent) { - if (fLargestExclusive <= digitExponent) { - fLargestExclusive = digitExponent + 1; - } else if (fSmallestInclusive > digitExponent) { - fSmallestInclusive = digitExponent; - } -} - -UBool DigitInterval::contains(int32_t x) const { - return (x < fLargestExclusive && x >= fSmallestInclusive); -} - - -U_NAMESPACE_END - diff --git a/icu4c/source/i18n/digitinterval.h b/icu4c/source/i18n/digitinterval.h deleted file mode 100644 index 55ced9445a..0000000000 --- a/icu4c/source/i18n/digitinterval.h +++ /dev/null @@ -1,159 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* Copyright (C) 2015, International Business Machines -* Corporation and others. All Rights Reserved. -******************************************************************************* -* digitinterval.h -* -* created on: 2015jan6 -* created by: Travis Keep -*/ - -#ifndef __DIGITINTERVAL_H__ -#define __DIGITINTERVAL_H__ - -#include "unicode/uobject.h" -#include "unicode/utypes.h" - -U_NAMESPACE_BEGIN - -/** - * An interval of digits. - * DigitIntervals are for fixed point formatting. A DigitInterval specifies - * zero or more integer digits and zero or more fractional digits. This class - * specifies particular digits in a number by their power of 10. For example, - * the digit position just to the left of the decimal is 0, and the digit - * position just left of that is 1. The digit position just to the right of - * the decimal is -1. The digit position just to the right of that is -2. - */ -class U_I18N_API DigitInterval : public UMemory { -public: - - /** - * Spans all integer and fraction digits - */ - DigitInterval() - : fLargestExclusive(INT32_MAX), fSmallestInclusive(INT32_MIN) { } - - - /** - * Makes this instance span all digits. - */ - void clear() { - fLargestExclusive = INT32_MAX; - fSmallestInclusive = INT32_MIN; - } - - /** - * Returns TRUE if this interval contains this digit position. - */ - UBool contains(int32_t digitPosition) const; - - /** - * Returns true if this object is the same as rhs. - */ - UBool equals(const DigitInterval &rhs) const { - return ((fLargestExclusive == rhs.fLargestExclusive) && - (fSmallestInclusive == rhs.fSmallestInclusive)); - } - - /** - * Expand this interval so that it contains all of rhs. - */ - void expandToContain(const DigitInterval &rhs); - - /** - * Shrink this interval so that it contains no more than rhs. - */ - void shrinkToFitWithin(const DigitInterval &rhs); - - /** - * Expand this interval as necessary to contain digit with given exponent - * After this method returns, this interval is guaranteed to contain - * digitExponent. - */ - void expandToContainDigit(int32_t digitExponent); - - /** - * Changes the number of digits to the left of the decimal point that - * this interval spans. If count is negative, it means span all digits - * to the left of the decimal point. - */ - void setIntDigitCount(int32_t count); - - /** - * Changes the number of digits to the right of the decimal point that - * this interval spans. If count is negative, it means span all digits - * to the right of the decimal point. - */ - void setFracDigitCount(int32_t count); - - /** - * Sets the least significant inclusive value to smallest. If smallest >= 0 - * then least significant inclusive value becomes 0. - */ - void setLeastSignificantInclusive(int32_t smallest) { - fSmallestInclusive = smallest < 0 ? smallest : 0; - } - - /** - * Sets the most significant exclusive value to largest. - * If largest <= 0 then most significant exclusive value becomes 0. - */ - void setMostSignificantExclusive(int32_t largest) { - fLargestExclusive = largest > 0 ? largest : 0; - } - - /** - * If returns 8, the most significant digit in interval is the 10^7 digit. - * Returns INT32_MAX if this interval spans all digits to left of - * decimal point. - */ - int32_t getMostSignificantExclusive() const { - return fLargestExclusive; - } - - /** - * Returns number of digits to the left of the decimal that this - * interval includes. This is a synonym for getMostSignificantExclusive(). - */ - int32_t getIntDigitCount() const { - return fLargestExclusive; - } - - /** - * Returns number of digits to the right of the decimal that this - * interval includes. - */ - int32_t getFracDigitCount() const { - return fSmallestInclusive == INT32_MIN ? INT32_MAX : -fSmallestInclusive; - } - - /** - * Returns the total number of digits that this interval spans. - * Caution: If this interval spans all digits to the left or right of - * decimal point instead of some fixed number, then what length() - * returns is undefined. - */ - int32_t length() const { - return fLargestExclusive - fSmallestInclusive; - } - - /** - * If returns -3, the least significant digit in interval is the 10^-3 - * digit. Returns INT32_MIN if this interval spans all digits to right of - * decimal point. - */ - int32_t getLeastSignificantInclusive() const { - return fSmallestInclusive; - } -private: - int32_t fLargestExclusive; - int32_t fSmallestInclusive; -}; - -U_NAMESPACE_END - -#endif // __DIGITINTERVAL_H__ diff --git a/icu4c/source/i18n/digitlst.cpp b/icu4c/source/i18n/digitlst.cpp deleted file mode 100644 index 5ceccd38b6..0000000000 --- a/icu4c/source/i18n/digitlst.cpp +++ /dev/null @@ -1,1143 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -********************************************************************** -* Copyright (C) 1997-2015, International Business Machines -* Corporation and others. All Rights Reserved. -********************************************************************** -* -* File DIGITLST.CPP -* -* Modification History: -* -* Date Name Description -* 03/21/97 clhuang Converted from java. -* 03/21/97 clhuang Implemented with new APIs. -* 03/27/97 helena Updated to pass the simple test after code review. -* 03/31/97 aliu Moved isLONG_MIN to here, and fixed it. -* 04/15/97 aliu Changed MAX_COUNT to DBL_DIG. Changed Digit to char. -* Reworked representation by replacing fDecimalAt -* with fExponent. -* 04/16/97 aliu Rewrote set() and getDouble() to use sprintf/atof -* to do digit conversion. -* 09/09/97 aliu Modified for exponential notation support. -* 08/02/98 stephen Added nearest/even rounding -* Fixed bug in fitsIntoLong -****************************************************************************** -*/ - -#if defined(__CYGWIN__) && !defined(_GNU_SOURCE) -#define _GNU_SOURCE -#endif - -#include "digitlst.h" - -#if !UCONFIG_NO_FORMATTING - -#include "unicode/putil.h" -#include "charstr.h" -#include "cmemory.h" -#include "cstring.h" -#include "mutex.h" -#include "putilimp.h" -#include "uassert.h" -#include "digitinterval.h" -#include "ucln_in.h" -#include "umutex.h" -#include "double-conversion.h" -#include -#include -#include -#include -#include - -using double_conversion::DoubleToStringConverter; - -#if !defined(U_USE_STRTOD_L) -# if U_PLATFORM_USES_ONLY_WIN32_API -# define U_USE_STRTOD_L 1 -# define U_HAVE_XLOCALE_H 0 -# elif defined(U_HAVE_STRTOD_L) -# define U_USE_STRTOD_L U_HAVE_STRTOD_L -# else -# define U_USE_STRTOD_L 0 -# endif -#endif - -#if U_USE_STRTOD_L -# if U_HAVE_XLOCALE_H -# include -# else -# include -# endif -#endif - -// *************************************************************************** -// class DigitList -// A wrapper onto decNumber. -// Used to be standalone. -// *************************************************************************** - -/** - * This is the zero digit. The base for the digits returned by getDigit() - * Note that it is the platform invariant digit, and is not Unicode. - */ -#define kZero '0' - - -/* Only for 32 bit numbers. Ignore the negative sign. */ -//static const char LONG_MIN_REP[] = "2147483648"; -//static const char I64_MIN_REP[] = "9223372036854775808"; - - -U_NAMESPACE_BEGIN - -// ------------------------------------- -// default constructor - -DigitList::DigitList() -{ - uprv_decContextDefault(&fContext, DEC_INIT_BASE); - fContext.traps = 0; - uprv_decContextSetRounding(&fContext, DEC_ROUND_HALF_EVEN); - fContext.digits = fStorage.getCapacity(); - - fDecNumber = fStorage.getAlias(); - uprv_decNumberZero(fDecNumber); - - internalSetDouble(0.0); -} - -// ------------------------------------- - -DigitList::~DigitList() -{ -} - -// ------------------------------------- -// copy constructor - -DigitList::DigitList(const DigitList &other) -{ - fDecNumber = fStorage.getAlias(); - *this = other; -} - - -// ------------------------------------- -// assignment operator - -DigitList& -DigitList::operator=(const DigitList& other) -{ - if (this != &other) - { - uprv_memcpy(&fContext, &other.fContext, sizeof(decContext)); - - if (other.fStorage.getCapacity() > fStorage.getCapacity()) { - fDecNumber = fStorage.resize(other.fStorage.getCapacity()); - } - // Always reset the fContext.digits, even if fDecNumber was not reallocated, - // because above we copied fContext from other.fContext. - fContext.digits = fStorage.getCapacity(); - uprv_decNumberCopy(fDecNumber, other.fDecNumber); - - { - // fDouble is lazily created and cached. - // Avoid potential races with that happening with other.fDouble - // while we are doing the assignment. - Mutex mutex; - - if(other.fHave==kDouble) { - fUnion.fDouble = other.fUnion.fDouble; - } - fHave = other.fHave; - } - } - return *this; -} - -// ------------------------------------- -// operator == (does not exactly match the old DigitList function) - -UBool -DigitList::operator==(const DigitList& that) const -{ - if (this == &that) { - return TRUE; - } - decNumber n; // Has space for only a none digit value. - decContext c; - uprv_decContextDefault(&c, DEC_INIT_BASE); - c.digits = 1; - c.traps = 0; - - uprv_decNumberCompare(&n, this->fDecNumber, that.fDecNumber, &c); - UBool result = decNumberIsZero(&n); - return result; -} - -// ------------------------------------- -// comparison function. Returns -// Not Comparable : -2 -// < : -1 -// == : 0 -// > : +1 -int32_t DigitList::compare(const DigitList &other) { - decNumber result; - int32_t savedDigits = fContext.digits; - fContext.digits = 1; - uprv_decNumberCompare(&result, this->fDecNumber, other.fDecNumber, &fContext); - fContext.digits = savedDigits; - if (decNumberIsZero(&result)) { - return 0; - } else if (decNumberIsSpecial(&result)) { - return -2; - } else if (result.bits & DECNEG) { - return -1; - } else { - return 1; - } -} - - -// ------------------------------------- -// Reduce - remove trailing zero digits. -void -DigitList::reduce() { - uprv_decNumberReduce(fDecNumber, fDecNumber, &fContext); -} - - -// ------------------------------------- -// trim - remove trailing fraction zero digits. -void -DigitList::trim() { - uprv_decNumberTrim(fDecNumber); -} - -// ------------------------------------- -// Resets the digit list; sets all the digits to zero. - -void -DigitList::clear() -{ - uprv_decNumberZero(fDecNumber); - uprv_decContextSetRounding(&fContext, DEC_ROUND_HALF_EVEN); - internalSetDouble(0.0); -} - - -/** - * Formats a int64_t number into a base 10 string representation, and NULL terminates it. - * @param number The number to format - * @param outputStr The string to output to. Must be at least MAX_DIGITS+2 in length (21), - * to hold the longest int64_t value. - * @return the number of digits written, not including the sign. - */ -static int32_t -formatBase10(int64_t number, char *outputStr) { - // The number is output backwards, starting with the LSD. - // Fill the buffer from the far end. After the number is complete, - // slide the string contents to the front. - - const int32_t MAX_IDX = MAX_DIGITS+2; - int32_t destIdx = MAX_IDX; - outputStr[--destIdx] = 0; - - int64_t n = number; - if (number < 0) { // Negative numbers are slightly larger than a postive - outputStr[--destIdx] = (char)(-(n % 10) + kZero); - n /= -10; - } - do { - outputStr[--destIdx] = (char)(n % 10 + kZero); - n /= 10; - } while (n > 0); - - if (number < 0) { - outputStr[--destIdx] = '-'; - } - - // Slide the number to the start of the output str - U_ASSERT(destIdx >= 0); - int32_t length = MAX_IDX - destIdx; - uprv_memmove(outputStr, outputStr+MAX_IDX-length, length); - - return length; -} - - -// ------------------------------------- -// -// setRoundingMode() -// For most modes, the meaning and names are the same between the decNumber library -// (which DigitList follows) and the ICU Formatting Rounding Mode values. -// The flag constants are different, however. -// -// Note that ICU's kRoundingUnnecessary is not implemented directly by DigitList. -// This mode, inherited from Java, means that numbers that would not format exactly -// will return an error when formatting is attempted. - -void -DigitList::setRoundingMode(DecimalFormat::ERoundingMode m) { - enum rounding r; - - switch (m) { - case DecimalFormat::kRoundCeiling: r = DEC_ROUND_CEILING; break; - case DecimalFormat::kRoundFloor: r = DEC_ROUND_FLOOR; break; - case DecimalFormat::kRoundDown: r = DEC_ROUND_DOWN; break; - case DecimalFormat::kRoundUp: r = DEC_ROUND_UP; break; - case DecimalFormat::kRoundHalfEven: r = DEC_ROUND_HALF_EVEN; break; - case DecimalFormat::kRoundHalfDown: r = DEC_ROUND_HALF_DOWN; break; - case DecimalFormat::kRoundHalfUp: r = DEC_ROUND_HALF_UP; break; - case DecimalFormat::kRoundUnnecessary: r = DEC_ROUND_HALF_EVEN; break; - default: - // TODO: how to report the problem? - // Leave existing mode unchanged. - r = uprv_decContextGetRounding(&fContext); - } - uprv_decContextSetRounding(&fContext, r); - -} - - -// ------------------------------------- - -void -DigitList::setPositive(UBool s) { - if (s) { - fDecNumber->bits &= ~DECNEG; - } else { - fDecNumber->bits |= DECNEG; - } - internalClear(); -} -// ------------------------------------- - -void -DigitList::setDecimalAt(int32_t d) { - U_ASSERT((fDecNumber->bits & DECSPECIAL) == 0); // Not Infinity or NaN - U_ASSERT(d-1>-999999999); - U_ASSERT(d-1< 999999999); - int32_t adjustedDigits = fDecNumber->digits; - if (decNumberIsZero(fDecNumber)) { - // Account for difference in how zero is represented between DigitList & decNumber. - adjustedDigits = 0; - } - fDecNumber->exponent = d - adjustedDigits; - internalClear(); -} - -int32_t -DigitList::getDecimalAt() { - U_ASSERT((fDecNumber->bits & DECSPECIAL) == 0); // Not Infinity or NaN - if (decNumberIsZero(fDecNumber) || ((fDecNumber->bits & DECSPECIAL) != 0)) { - return fDecNumber->exponent; // Exponent should be zero for these cases. - } - return fDecNumber->exponent + fDecNumber->digits; -} - -void -DigitList::setCount(int32_t c) { - U_ASSERT(c <= fContext.digits); - if (c == 0) { - // For a value of zero, DigitList sets all fields to zero, while - // decNumber keeps one digit (with that digit being a zero) - c = 1; - fDecNumber->lsu[0] = 0; - } - fDecNumber->digits = c; - internalClear(); -} - -int32_t -DigitList::getCount() const { - if (decNumberIsZero(fDecNumber) && fDecNumber->exponent==0) { - // The extra test for exponent==0 is needed because parsing sometimes appends - // zero digits. It's bogus, decimalFormatter parsing needs to be cleaned up. - return 0; - } else { - return fDecNumber->digits; - } -} - -void -DigitList::setDigit(int32_t i, char v) { - int32_t count = fDecNumber->digits; - U_ASSERT(i='0' && v<='9'); - v &= 0x0f; - fDecNumber->lsu[count-i-1] = v; - internalClear(); -} - -char -DigitList::getDigit(int32_t i) { - int32_t count = fDecNumber->digits; - U_ASSERT(ilsu[count-i-1] + '0'; -} - -// copied from DigitList::getDigit() -uint8_t -DigitList::getDigitValue(int32_t i) { - int32_t count = fDecNumber->digits; - U_ASSERT(ilsu[count-i-1]; -} - -// ------------------------------------- -// Appends the digit to the digit list if it's not out of scope. -// Ignores the digit, otherwise. -// -// This function is horribly inefficient to implement with decNumber because -// the digits are stored least significant first, which requires moving all -// existing digits down one to make space for the new one to be appended. -// -void -DigitList::append(char digit) -{ - U_ASSERT(digit>='0' && digit<='9'); - // Ignore digits which exceed the precision we can represent - // And don't fix for larger precision. Fix callers instead. - if (decNumberIsZero(fDecNumber)) { - // Zero needs to be special cased because of the difference in the way - // that the old DigitList and decNumber represent it. - // digit cout was zero for digitList, is one for decNumber - fDecNumber->lsu[0] = digit & 0x0f; - fDecNumber->digits = 1; - fDecNumber->exponent--; // To match the old digit list implementation. - } else { - int32_t nDigits = fDecNumber->digits; - if (nDigits < fContext.digits) { - int i; - for (i=nDigits; i>0; i--) { - fDecNumber->lsu[i] = fDecNumber->lsu[i-1]; - } - fDecNumber->lsu[0] = digit & 0x0f; - fDecNumber->digits++; - // DigitList emulation - appending doesn't change the magnitude of existing - // digits. With decNumber's decimal being after the - // least signficant digit, we need to adjust the exponent. - fDecNumber->exponent--; - } - } - internalClear(); -} - -// ------------------------------------- - -/** - * Currently, getDouble() depends on strtod() to do its conversion. - * - * WARNING!! - * This is an extremely costly function. ~1/2 of the conversion time - * can be linked to this function. - */ -double -DigitList::getDouble() const -{ - { - Mutex mutex; - if (fHave == kDouble) { - return fUnion.fDouble; - } - } - - double tDouble = 0.0; - if (isZero()) { - tDouble = 0.0; - if (decNumberIsNegative(fDecNumber)) { - tDouble /= -1; - } - } else if (isInfinite()) { - if (std::numeric_limits::has_infinity) { - tDouble = std::numeric_limits::infinity(); - } else { - tDouble = std::numeric_limits::max(); - } - if (!isPositive()) { - tDouble = -tDouble; //this was incorrectly "-fDouble" originally. - } - } else { - MaybeStackArray s; - // Note: 14 is a magic constant from the decNumber library documentation, - // the max number of extra characters beyond the number of digits - // needed to represent the number in string form. Add a few more - // for the additional digits we retain. - - // Round down to appx. double precision, if the number is longer than that. - // Copy the number first, so that we don't modify the original. - if (getCount() > MAX_DBL_DIGITS + 3) { - DigitList numToConvert(*this); - numToConvert.reduce(); // Removes any trailing zeros, so that digit count is good. - numToConvert.round(MAX_DBL_DIGITS+3); - uprv_decNumberToString(numToConvert.fDecNumber, s.getAlias()); - // TODO: how many extra digits should be included for an accurate conversion? - } else { - uprv_decNumberToString(this->fDecNumber, s.getAlias()); - } - U_ASSERT(uprv_strlen(&s[0]) < MAX_DBL_DIGITS+18); - - char *end = NULL; - tDouble = decimalStrToDouble(s.getAlias(), &end); - } - { - Mutex mutex; - DigitList *nonConstThis = const_cast(this); - nonConstThis->internalSetDouble(tDouble); - } - return tDouble; -} - -#if U_USE_STRTOD_L && U_PLATFORM_USES_ONLY_WIN32_API -# define locale_t _locale_t -# define freelocale _free_locale -# define strtod_l _strtod_l -#endif - -#if U_USE_STRTOD_L -static locale_t gCLocale = (locale_t)0; -#endif -static icu::UInitOnce gCLocaleInitOnce = U_INITONCE_INITIALIZER; - -U_CDECL_BEGIN -// Cleanup callback func -static UBool U_CALLCONV digitList_cleanup(void) -{ -#if U_USE_STRTOD_L - if (gCLocale != (locale_t)0) { - freelocale(gCLocale); - } -#endif - return TRUE; -} -// C Locale initialization func -static void U_CALLCONV initCLocale(void) { - ucln_i18n_registerCleanup(UCLN_I18N_DIGITLIST, digitList_cleanup); -#if U_USE_STRTOD_L -# if U_PLATFORM_USES_ONLY_WIN32_API - gCLocale = _create_locale(LC_ALL, "C"); -# else - gCLocale = newlocale(LC_ALL_MASK, "C", (locale_t)0); -# endif -#endif -} -U_CDECL_END - -double -DigitList::decimalStrToDouble(char *decstr, char **end) { - umtx_initOnce(gCLocaleInitOnce, &initCLocale); -#if U_USE_STRTOD_L - return strtod_l(decstr, end, gCLocale); -#else - char *decimalPt = strchr(decstr, '.'); - if (decimalPt) { - // We need to know the decimal separator character that will be used with strtod(). - // Depends on the C runtime global locale. - // Most commonly is '.' - char rep[MAX_DIGITS]; - sprintf(rep, "%+1.1f", 1.0); - *decimalPt = rep[2]; - } - return uprv_strtod(decstr, end); -#endif -} - -// ------------------------------------- - -/** - * convert this number to an int32_t. Round if there is a fractional part. - * Return zero if the number cannot be represented. - */ -int32_t DigitList::getLong() /*const*/ -{ - int32_t result = 0; - if (getUpperExponent() > 10) { - // Overflow, absolute value too big. - return result; - } - if (fDecNumber->exponent != 0) { - // Force to an integer, with zero exponent, rounding if necessary. - // (decNumberToInt32 will only work if the exponent is exactly zero.) - DigitList copy(*this); - DigitList zero; - uprv_decNumberQuantize(copy.fDecNumber, copy.fDecNumber, zero.fDecNumber, &fContext); - result = uprv_decNumberToInt32(copy.fDecNumber, &fContext); - } else { - result = uprv_decNumberToInt32(fDecNumber, &fContext); - } - return result; -} - - -/** - * convert this number to an int64_t. Truncate if there is a fractional part. - * Return zero if the number cannot be represented. - */ -int64_t DigitList::getInt64() /*const*/ { - // TODO: fast conversion if fHave == fDouble - - // Truncate if non-integer. - // Return 0 if out of range. - // Range of in64_t is -9223372036854775808 to 9223372036854775807 (19 digits) - // - if (getUpperExponent() > 19) { - // Overflow, absolute value too big. - return 0; - } - - // The number of integer digits may differ from the number of digits stored - // in the decimal number. - // for 12.345 numIntDigits = 2, number->digits = 5 - // for 12E4 numIntDigits = 6, number->digits = 2 - // The conversion ignores the fraction digits in the first case, - // and fakes up extra zero digits in the second. - // TODO: It would be faster to store a table of powers of ten to multiply by - // instead of looping over zero digits, multiplying each time. - - int32_t numIntDigits = getUpperExponent(); - uint64_t value = 0; - for (int32_t i = 0; i < numIntDigits; i++) { - // Loop is iterating over digits starting with the most significant. - // Numbers are stored with the least significant digit at index zero. - int32_t digitIndex = fDecNumber->digits - i - 1; - int32_t v = (digitIndex >= 0) ? fDecNumber->lsu[digitIndex] : 0; - value = value * (uint64_t)10 + (uint64_t)v; - } - - if (decNumberIsNegative(fDecNumber)) { - value = ~value; - value += 1; - } - int64_t svalue = (int64_t)value; - - // Check overflow. It's convenient that the MSD is 9 only on overflow, the amount of - // overflow can't wrap too far. The test will also fail -0, but - // that does no harm; the right answer is 0. - if (numIntDigits == 19) { - if (( decNumberIsNegative(fDecNumber) && svalue>0) || - (!decNumberIsNegative(fDecNumber) && svalue<0)) { - svalue = 0; - } - } - - return svalue; -} - - -/** - * Return a string form of this number. - * Format is as defined by the decNumber library, for interchange of - * decimal numbers. - */ -void DigitList::getDecimal(CharString &str, UErrorCode &status) { - if (U_FAILURE(status)) { - return; - } - - // A decimal number in string form can, worst case, be 14 characters longer - // than the number of digits. So says the decNumber library doc. - int32_t maxLength = fDecNumber->digits + 14; - int32_t capacity = 0; - char *buffer = str.clear().getAppendBuffer(maxLength, 0, capacity, status); - if (U_FAILURE(status)) { - return; // Memory allocation error on growing the string. - } - U_ASSERT(capacity >= maxLength); - uprv_decNumberToString(this->fDecNumber, buffer); - U_ASSERT((int32_t)uprv_strlen(buffer) <= maxLength); - str.append(buffer, -1, status); -} - -/** - * Return true if this is an integer value that can be held - * by an int32_t type. - */ -UBool -DigitList::fitsIntoLong(UBool ignoreNegativeZero) /*const*/ -{ - if (decNumberIsSpecial(this->fDecNumber)) { - // NaN or Infinity. Does not fit in int32. - return FALSE; - } - uprv_decNumberTrim(this->fDecNumber); - if (fDecNumber->exponent < 0) { - // Number contains fraction digits. - return FALSE; - } - if (decNumberIsZero(this->fDecNumber) && !ignoreNegativeZero && - (fDecNumber->bits & DECNEG) != 0) { - // Negative Zero, not ingored. Cannot represent as a long. - return FALSE; - } - if (getUpperExponent() < 10) { - // The number is 9 or fewer digits. - // The max and min int32 are 10 digts, so this number fits. - // This is the common case. - return TRUE; - } - - // TODO: Should cache these constants; construction is relatively costly. - // But not of huge consequence; they're only needed for 10 digit ints. - UErrorCode status = U_ZERO_ERROR; - DigitList min32; min32.set("-2147483648", status); - if (this->compare(min32) < 0) { - return FALSE; - } - DigitList max32; max32.set("2147483647", status); - if (this->compare(max32) > 0) { - return FALSE; - } - if (U_FAILURE(status)) { - return FALSE; - } - return true; -} - - - -/** - * Return true if the number represented by this object can fit into - * a long. - */ -UBool -DigitList::fitsIntoInt64(UBool ignoreNegativeZero) /*const*/ -{ - if (decNumberIsSpecial(this->fDecNumber)) { - // NaN or Infinity. Does not fit in int32. - return FALSE; - } - uprv_decNumberTrim(this->fDecNumber); - if (fDecNumber->exponent < 0) { - // Number contains fraction digits. - return FALSE; - } - if (decNumberIsZero(this->fDecNumber) && !ignoreNegativeZero && - (fDecNumber->bits & DECNEG) != 0) { - // Negative Zero, not ingored. Cannot represent as a long. - return FALSE; - } - if (getUpperExponent() < 19) { - // The number is 18 or fewer digits. - // The max and min int64 are 19 digts, so this number fits. - // This is the common case. - return TRUE; - } - - // TODO: Should cache these constants; construction is relatively costly. - // But not of huge consequence; they're only needed for 19 digit ints. - UErrorCode status = U_ZERO_ERROR; - DigitList min64; min64.set("-9223372036854775808", status); - if (this->compare(min64) < 0) { - return FALSE; - } - DigitList max64; max64.set("9223372036854775807", status); - if (this->compare(max64) > 0) { - return FALSE; - } - if (U_FAILURE(status)) { - return FALSE; - } - return true; -} - - -// ------------------------------------- - -void -DigitList::set(int32_t source) -{ - set((int64_t)source); - internalSetDouble(source); -} - -// ------------------------------------- -/** - * Set an int64, via decnumber - */ -void -DigitList::set(int64_t source) -{ - char str[MAX_DIGITS+2]; // Leave room for sign and trailing nul. - formatBase10(source, str); - U_ASSERT(uprv_strlen(str) < sizeof(str)); - - uprv_decNumberFromString(fDecNumber, str, &fContext); - internalSetDouble(static_cast(source)); -} - -// ------------------------------------- -/** - * Set the DigitList from a decimal number string. - * - * The incoming string _must_ be nul terminated, even though it is arriving - * as a StringPiece because that is what the decNumber library wants. - * We can get away with this for an internal function; it would not - * be acceptable for a public API. - */ -void -DigitList::set(StringPiece source, UErrorCode &status, uint32_t /*fastpathBits*/) { - if (U_FAILURE(status)) { - return; - } - -#if 0 - if(fastpathBits==(kFastpathOk|kNoDecimal)) { - int32_t size = source.size(); - const char *data = source.data(); - int64_t r = 0; - int64_t m = 1; - // fast parse - while(size>0) { - char ch = data[--size]; - if(ch=='+') { - break; - } else if(ch=='-') { - r = -r; - break; - } else { - int64_t d = ch-'0'; - //printf("CH[%d]=%c, %d, *=%d\n", size,ch, (int)d, (int)m); - r+=(d)*m; - m *= 10; - } - } - //printf("R=%d\n", r); - set(r); - } else -#endif - { - // Figure out a max number of digits to use during the conversion, and - // resize the number up if necessary. - int32_t numDigits = source.length(); - if (numDigits > fContext.digits) { - // fContext.digits == fStorage.getCapacity() - decNumber *t = fStorage.resize(numDigits, fStorage.getCapacity()); - if (t == NULL) { - status = U_MEMORY_ALLOCATION_ERROR; - return; - } - fDecNumber = t; - fContext.digits = numDigits; - } - - fContext.status = 0; - uprv_decNumberFromString(fDecNumber, source.data(), &fContext); - if ((fContext.status & DEC_Conversion_syntax) != 0) { - status = U_DECIMAL_NUMBER_SYNTAX_ERROR; - } - } - internalClear(); -} - -/** - * Set the digit list to a representation of the given double value. - * This method supports both fixed-point and exponential notation. - * @param source Value to be converted. - */ -void -DigitList::set(double source) -{ - // for now, simple implementation; later, do proper IEEE stuff - char rep[MAX_DIGITS + 8]; // Extra space for '+', '.', e+NNN, and '\0' (actually +8 is enough) - - // Generate a representation of the form /[+-][0-9].[0-9]+e[+-][0-9]+/ - // Can also generate /[+-]nan/ or /[+-]inf/ - // TODO: Use something other than sprintf() here, since it's behavior is somewhat platform specific. - // That is why infinity is special cased here. - if (uprv_isInfinite(source)) { - if (uprv_isNegativeInfinity(source)) { - uprv_strcpy(rep,"-inf"); // Handle negative infinity - } else { - uprv_strcpy(rep,"inf"); - } - } else if (uprv_isNaN(source)) { - uprv_strcpy(rep, "NaN"); - } else { - bool sign; - int32_t length; - int32_t point; - DoubleToStringConverter::DoubleToAscii( - source, - DoubleToStringConverter::DtoaMode::SHORTEST, - 0, - rep + 1, - sizeof(rep), - &sign, - &length, - &point - ); - - // Convert the raw buffer into a string for decNumber - int32_t power = point - length; - if (sign) { - rep[0] = '-'; - } else { - rep[0] = '0'; - } - length++; - rep[length++] = 'E'; - if (power < 0) { - rep[length++] = '-'; - power = -power; - } else { - rep[length++] = '+'; - } - if (power < 10) { - rep[length++] = power + '0'; - } else if (power < 100) { - rep[length++] = (power / 10) + '0'; - rep[length++] = (power % 10) + '0'; - } else { - U_ASSERT(power < 1000); - rep[length + 2] = (power % 10) + '0'; - power /= 10; - rep[length + 1] = (power % 10) + '0'; - power /= 10; - rep[length] = power + '0'; - length += 3; - } - rep[length++] = 0; - } - U_ASSERT(uprv_strlen(rep) < sizeof(rep)); - - // uprv_decNumberFromString() will parse the string expecting '.' as a - // decimal separator, however sprintf() can use ',' in certain locales. - // Overwrite a ',' with '.' here before proceeding. - char *decimalSeparator = strchr(rep, ','); - if (decimalSeparator != NULL) { - *decimalSeparator = '.'; - } - - // Create a decNumber from the string. - uprv_decNumberFromString(fDecNumber, rep, &fContext); - uprv_decNumberTrim(fDecNumber); - internalSetDouble(source); -} - -// ------------------------------------- - -/* - * Multiply - * The number will be expanded if need be to retain full precision. - * In practice, for formatting, multiply is by 10, 100 or 1000, so more digits - * will not be required for this use. - */ -void -DigitList::mult(const DigitList &other, UErrorCode &status) { - if (U_FAILURE(status)) { - return; - } - fContext.status = 0; - int32_t requiredDigits = this->digits() + other.digits(); - if (requiredDigits > fContext.digits) { - reduce(); // Remove any trailing zeros - int32_t requiredDigits = this->digits() + other.digits(); - ensureCapacity(requiredDigits, status); - } - uprv_decNumberMultiply(fDecNumber, fDecNumber, other.fDecNumber, &fContext); - internalClear(); -} - -// ------------------------------------- - -/* - * Divide - * The number will _not_ be expanded for inexact results. - * TODO: probably should expand some, for rounding increments that - * could add a few digits, e.g. .25, but not expand arbitrarily. - */ -void -DigitList::div(const DigitList &other, UErrorCode &status) { - if (U_FAILURE(status)) { - return; - } - uprv_decNumberDivide(fDecNumber, fDecNumber, other.fDecNumber, &fContext); - internalClear(); -} - -// ------------------------------------- - -/* - * ensureCapacity. Grow the digit storage for the number if it's less than the requested - * amount. Never reduce it. Available size is kept in fContext.digits. - */ -void -DigitList::ensureCapacity(int32_t requestedCapacity, UErrorCode &status) { - if (U_FAILURE(status)) { - return; - } - if (requestedCapacity <= 0) { - status = U_ILLEGAL_ARGUMENT_ERROR; - return; - } - if (requestedCapacity > DEC_MAX_DIGITS) { - // Don't report an error for requesting too much. - // Arithemetic Results will be rounded to what can be supported. - // At 999,999,999 max digits, exceeding the limit is not too likely! - requestedCapacity = DEC_MAX_DIGITS; - } - if (requestedCapacity > fContext.digits) { - decNumber *newBuffer = fStorage.resize(requestedCapacity, fStorage.getCapacity()); - if (newBuffer == NULL) { - status = U_MEMORY_ALLOCATION_ERROR; - return; - } - fContext.digits = requestedCapacity; - fDecNumber = newBuffer; - } -} - -// ------------------------------------- - -/** - * Round the representation to the given number of digits. - * @param maximumDigits The maximum number of digits to be shown. - * Upon return, count will be less than or equal to maximumDigits. - */ -void -DigitList::round(int32_t maximumDigits) -{ - reduce(); - if (maximumDigits >= fDecNumber->digits) { - return; - } - int32_t savedDigits = fContext.digits; - fContext.digits = maximumDigits; - uprv_decNumberPlus(fDecNumber, fDecNumber, &fContext); - fContext.digits = savedDigits; - uprv_decNumberTrim(fDecNumber); - reduce(); - internalClear(); -} - - -void -DigitList::roundFixedPoint(int32_t maximumFractionDigits) { - reduce(); // Remove trailing zeros. - if (fDecNumber->exponent >= -maximumFractionDigits) { - return; - } - decNumber scale; // Dummy decimal number, but with the desired number of - uprv_decNumberZero(&scale); // fraction digits. - scale.exponent = -maximumFractionDigits; - scale.lsu[0] = 1; - - uprv_decNumberQuantize(fDecNumber, fDecNumber, &scale, &fContext); - reduce(); - internalClear(); -} - -// ------------------------------------- - -void -DigitList::toIntegralValue() { - uprv_decNumberToIntegralValue(fDecNumber, fDecNumber, &fContext); -} - - -// ------------------------------------- -UBool -DigitList::isZero() const -{ - return decNumberIsZero(fDecNumber); -} - -// ------------------------------------- -int32_t -DigitList::getUpperExponent() const { - return fDecNumber->digits + fDecNumber->exponent; -} - -DigitInterval & -DigitList::getSmallestInterval(DigitInterval &result) const { - result.setLeastSignificantInclusive(fDecNumber->exponent); - result.setMostSignificantExclusive(getUpperExponent()); - return result; -} - -uint8_t -DigitList::getDigitByExponent(int32_t exponent) const { - int32_t idx = exponent - fDecNumber->exponent; - if (idx < 0 || idx >= fDecNumber->digits) { - return 0; - } - return fDecNumber->lsu[idx]; -} - -void -DigitList::appendDigitsTo(CharString &str, UErrorCode &status) const { - str.append((const char *) fDecNumber->lsu, fDecNumber->digits, status); -} - -void -DigitList::roundAtExponent(int32_t exponent, int32_t maxSigDigits) { - reduce(); - if (maxSigDigits < fDecNumber->digits) { - int32_t minExponent = getUpperExponent() - maxSigDigits; - if (exponent < minExponent) { - exponent = minExponent; - } - } - if (exponent <= fDecNumber->exponent) { - return; - } - int32_t digits = getUpperExponent() - exponent; - if (digits > 0) { - round(digits); - } else { - roundFixedPoint(-exponent); - } -} - -void -DigitList::quantize(const DigitList &quantity, UErrorCode &status) { - if (U_FAILURE(status)) { - return; - } - div(quantity, status); - roundAtExponent(0); - mult(quantity, status); - reduce(); -} - -int32_t -DigitList::getScientificExponent( - int32_t minIntDigitCount, int32_t exponentMultiplier) const { - // The exponent for zero is always zero. - if (isZero()) { - return 0; - } - int32_t intDigitCount = getUpperExponent(); - int32_t exponent; - if (intDigitCount >= minIntDigitCount) { - int32_t maxAdjustment = intDigitCount - minIntDigitCount; - exponent = (maxAdjustment / exponentMultiplier) * exponentMultiplier; - } else { - int32_t minAdjustment = minIntDigitCount - intDigitCount; - exponent = ((minAdjustment + exponentMultiplier - 1) / exponentMultiplier) * -exponentMultiplier; - } - return exponent; -} - -int32_t -DigitList::toScientific( - int32_t minIntDigitCount, int32_t exponentMultiplier) { - int32_t exponent = getScientificExponent( - minIntDigitCount, exponentMultiplier); - shiftDecimalRight(-exponent); - return exponent; -} - -void -DigitList::shiftDecimalRight(int32_t n) { - fDecNumber->exponent += n; - internalClear(); -} - -U_NAMESPACE_END -#endif // #if !UCONFIG_NO_FORMATTING - -//eof diff --git a/icu4c/source/i18n/digitlst.h b/icu4c/source/i18n/digitlst.h deleted file mode 100644 index a51ebc45c7..0000000000 --- a/icu4c/source/i18n/digitlst.h +++ /dev/null @@ -1,529 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -****************************************************************************** -* -* Copyright (C) 1997-2015, International Business Machines -* Corporation and others. All Rights Reserved. -* -****************************************************************************** -* -* File DIGITLST.H -* -* Modification History: -* -* Date Name Description -* 02/25/97 aliu Converted from java. -* 03/21/97 clhuang Updated per C++ implementation. -* 04/15/97 aliu Changed MAX_COUNT to DBL_DIG. Changed Digit to char. -* 09/09/97 aliu Adapted for exponential notation support. -* 08/02/98 stephen Added nearest/even rounding -* 06/29/99 stephen Made LONG_DIGITS a macro to satisfy SUN compiler -* 07/09/99 stephen Removed kMaxCount (unused, for HP compiler) -****************************************************************************** -*/ - -#ifndef DIGITLST_H -#define DIGITLST_H - -#include "unicode/uobject.h" - -#if !UCONFIG_NO_FORMATTING -#include "unicode/decimfmt.h" -#include -#include "decContext.h" -#include "decNumber.h" -#include "cmemory.h" - -// Decimal digits in a 64-bit int -#define INT64_DIGITS 19 - -typedef enum EDigitListValues { - MAX_DBL_DIGITS = DBL_DIG, - MAX_I64_DIGITS = INT64_DIGITS, - MAX_DIGITS = MAX_I64_DIGITS, - MAX_EXPONENT = DBL_DIG, - DIGIT_PADDING = 3, - DEFAULT_DIGITS = 40, // Initial storage size, will grow as needed. - - // "+." + fDigits + "e" + fDecimalAt - MAX_DEC_DIGITS = MAX_DIGITS + DIGIT_PADDING + MAX_EXPONENT -} EDigitListValues; - -U_NAMESPACE_BEGIN - -class CharString; -class DigitInterval; - -// Export an explicit template instantiation of the MaybeStackHeaderAndArray that -// is used as a data member of DigitList. -// -// MSVC requires this, even though it should not be necessary. -// No direct access to the MaybeStackHeaderAndArray leaks out of the i18n library. -// -// Macintosh produces duplicate definition linker errors with the explicit template -// instantiation. -// -#if !U_PLATFORM_IS_DARWIN_BASED -template class U_I18N_API MaybeStackHeaderAndArray; -#endif - - -enum EStackMode { kOnStack }; - -enum EFastpathBits { kFastpathOk = 1, kNoDecimal = 2 }; - -/** - * Digit List is actually a Decimal Floating Point number. - * The original implementation has been replaced by a thin wrapper onto a - * decimal number from the decNumber library. - * - * The original DigitList API has been retained, to minimize the impact of - * the change on the rest of the ICU formatting code. - * - * The change to decNumber enables support for big decimal numbers, and - * allows rounding computations to be done directly in decimal, avoiding - * extra, and inaccurate, conversions to and from doubles. - * - * Original DigitList comments: - * - * Digit List utility class. Private to DecimalFormat. Handles the transcoding - * between numeric values and strings of characters. Only handles - * non-negative numbers. The division of labor between DigitList and - * DecimalFormat is that DigitList handles the radix 10 representation - * issues; DecimalFormat handles the locale-specific issues such as - * positive/negative, grouping, decimal point, currency, and so on. - *

- * A DigitList is really a representation of a floating point value. - * It may be an integer value; we assume that a double has sufficient - * precision to represent all digits of a long. - *

- * The DigitList representation consists of a string of characters, - * which are the digits radix 10, from '0' to '9'. It also has a radix - * 10 exponent associated with it. The value represented by a DigitList - * object can be computed by mulitplying the fraction f, where 0 <= f < 1, - * derived by placing all the digits of the list to the right of the - * decimal point, by 10^exponent. - * - * -------- - * - * DigitList vs. decimalNumber: - * - * DigitList stores digits with the most significant first. - * decNumber stores digits with the least significant first. - * - * DigitList, decimal point is before the most significant. - * decNumber, decimal point is after the least signficant digit. - * - * digitList: 0.ddddd * 10 ^ exp - * decNumber: ddddd. * 10 ^ exp - * - * digitList exponent = decNumber exponent + digit count - * - * digitList, digits are platform invariant chars, '0' - '9' - * decNumber, digits are binary, one per byte, 0 - 9. - * - * (decNumber library is configurable in how digits are stored, ICU has configured - * it this way for convenience in replacing the old DigitList implementation.) - */ -class U_I18N_API DigitList : public UMemory { // Declare external to make compiler happy -public: - - DigitList(); - ~DigitList(); - - /* copy constructor - * @param DigitList The object to be copied. - * @return the newly created object. - */ - DigitList(const DigitList&); // copy constructor - - /* assignment operator - * @param DigitList The object to be copied. - * @return the newly created object. - */ - DigitList& operator=(const DigitList&); // assignment operator - - /** - * Return true if another object is semantically equal to this one. - * @param other The DigitList to be compared for equality - * @return true if another object is semantically equal to this one. - * return false otherwise. - */ - UBool operator==(const DigitList& other) const; - - int32_t compare(const DigitList& other); - - - inline UBool operator!=(const DigitList& other) const { return !operator==(other); } - - /** - * Clears out the digits. - * Use before appending them. - * Typically, you set a series of digits with append, then at the point - * you hit the decimal point, you set myDigitList.fDecimalAt = myDigitList.fCount; - * then go on appending digits. - */ - void clear(void); - - /** - * Remove, by rounding, any fractional part of the decimal number, - * leaving an integer value. - */ - void toIntegralValue(); - - /** - * Appends digits to the list. - * CAUTION: this function is not recommended for new code. - * In the original DigitList implementation, decimal numbers were - * parsed by appending them to a digit list as they were encountered. - * With the revamped DigitList based on decNumber, append is very - * inefficient, and the interaction with the exponent value is confusing. - * Best avoided. - * TODO: remove this function once all use has been replaced. - * TODO: describe alternative to append() - * @param digit The digit to be appended. - */ - void append(char digit); - - /** - * Utility routine to get the value of the digit list - * Returns 0.0 if zero length. - * @return the value of the digit list. - */ - double getDouble(void) const; - - /** - * Utility routine to get the value of the digit list - * Make sure that fitsIntoLong() is called before calling this function. - * Returns 0 if zero length. - * @return the value of the digit list, return 0 if it is zero length - */ - int32_t getLong(void) /*const*/; - - /** - * Utility routine to get the value of the digit list - * Make sure that fitsIntoInt64() is called before calling this function. - * Returns 0 if zero length. - * @return the value of the digit list, return 0 if it is zero length - */ - int64_t getInt64(void) /*const*/; - - /** - * Utility routine to get the value of the digit list as a decimal string. - */ - void getDecimal(CharString &str, UErrorCode &status); - - /** - * Return true if the number represented by this object can fit into - * a long. - * @param ignoreNegativeZero True if negative zero is ignored. - * @return true if the number represented by this object can fit into - * a long, return false otherwise. - */ - UBool fitsIntoLong(UBool ignoreNegativeZero) /*const*/; - - /** - * Return true if the number represented by this object can fit into - * an int64_t. - * @param ignoreNegativeZero True if negative zero is ignored. - * @return true if the number represented by this object can fit into - * a long, return false otherwise. - */ - UBool fitsIntoInt64(UBool ignoreNegativeZero) /*const*/; - - /** - * Utility routine to set the value of the digit list from a double. - * @param source The value to be set - */ - void set(double source); - - /** - * Utility routine to set the value of the digit list from a long. - * If a non-zero maximumDigits is specified, no more than that number of - * significant digits will be produced. - * @param source The value to be set - */ - void set(int32_t source); - - /** - * Utility routine to set the value of the digit list from an int64. - * If a non-zero maximumDigits is specified, no more than that number of - * significant digits will be produced. - * @param source The value to be set - */ - void set(int64_t source); - - /** - * Utility routine to set the value of the digit list from an int64. - * Does not set the decnumber unless requested later - * If a non-zero maximumDigits is specified, no more than that number of - * significant digits will be produced. - * @param source The value to be set - */ - void setInteger(int64_t source); - - /** - * Utility routine to set the value of the digit list from a decimal number - * string. - * @param source The value to be set. The string must be nul-terminated. - * @param fastpathBits special flags for fast parsing - */ - void set(StringPiece source, UErrorCode &status, uint32_t fastpathBits = 0); - - /** - * Multiply this = this * arg - * This digitlist will be expanded if necessary to accomodate the result. - * @param arg the number to multiply by. - */ - void mult(const DigitList &arg, UErrorCode &status); - - /** - * Divide this = this / arg - */ - void div(const DigitList &arg, UErrorCode &status); - - // The following functions replace direct access to the original DigitList implmentation - // data structures. - - void setRoundingMode(DecimalFormat::ERoundingMode m); - - /** Test a number for zero. - * @return TRUE if the number is zero - */ - UBool isZero(void) const; - - /** Test for a Nan - * @return TRUE if the number is a NaN - */ - UBool isNaN(void) const {return decNumberIsNaN(fDecNumber);} - - UBool isInfinite() const {return decNumberIsInfinite(fDecNumber);} - - /** Reduce, or normalize. Removes trailing zeroes, adjusts exponent appropriately. */ - void reduce(); - - /** Remove trailing fraction zeros, adjust exponent accordingly. */ - void trim(); - - /** Set to zero */ - void setToZero() {uprv_decNumberZero(fDecNumber);} - - /** get the number of digits in the decimal number */ - int32_t digits() const {return fDecNumber->digits;} - - /** - * Round the number to the given number of digits. - * @param maximumDigits The maximum number of digits to be shown. - * Upon return, count will be less than or equal to maximumDigits. - * result is guaranteed to be trimmed. - */ - void round(int32_t maximumDigits); - - void roundFixedPoint(int32_t maximumFractionDigits); - - /** Ensure capacity for digits. Grow the storage if it is currently less than - * the requested size. Capacity is not reduced if it is already greater - * than requested. - */ - void ensureCapacity(int32_t requestedSize, UErrorCode &status); - - UBool isPositive(void) const { return decNumberIsNegative(fDecNumber) == 0;} - void setPositive(UBool s); - - void setDecimalAt(int32_t d); - int32_t getDecimalAt(); - - void setCount(int32_t c); - int32_t getCount() const; - - /** - * Set the digit in platform (invariant) format, from '0'..'9' - * @param i index of digit - * @param v digit value, from '0' to '9' in platform invariant format - */ - void setDigit(int32_t i, char v); - - /** - * Get the digit in platform (invariant) format, from '0'..'9' inclusive - * @param i index of digit - * @return invariant format of the digit - */ - char getDigit(int32_t i); - - - /** - * Get the digit's value, as an integer from 0..9 inclusive. - * Note that internally this value is a decNumberUnit, but ICU configures it to be a uint8_t. - * @param i index of digit - * @return value of that digit - */ - uint8_t getDigitValue(int32_t i); - - /** - * Gets the upper bound exponent for this value. For 987, returns 3 - * because 10^3 is the smallest power of 10 that is just greater than - * 987. - */ - int32_t getUpperExponent() const; - - /** - * Gets the lower bound exponent for this value. For 98.7, returns -1 - * because the right most digit, is the 10^-1 place. - */ - int32_t getLowerExponent() const { return fDecNumber->exponent; } - - /** - * Sets result to the smallest DigitInterval needed to display this - * DigitList in fixed point form and returns result. - */ - DigitInterval& getSmallestInterval(DigitInterval &result) const; - - /** - * Like getDigitValue, but the digit is identified by exponent. - * For example, getDigitByExponent(7) returns the 10^7 place of this - * DigitList. Unlike getDigitValue, there are no upper or lower bounds - * for passed parameter. Instead, getDigitByExponent returns 0 if - * the exponent falls outside the interval for this DigitList. - */ - uint8_t getDigitByExponent(int32_t exponent) const; - - /** - * Appends the digits in this object to a CharString. - * 3 is appended as (char) 3, not '3' - */ - void appendDigitsTo(CharString &str, UErrorCode &status) const; - - /** - * Equivalent to roundFixedPoint(-digitExponent) except unlike - * roundFixedPoint, this works for any digitExponent value. - * If maxSigDigits is set then this instance is rounded to have no more - * than maxSigDigits. The end result is guaranteed to be trimmed. - */ - void roundAtExponent(int32_t digitExponent, int32_t maxSigDigits=INT32_MAX); - - /** - * Quantizes according to some amount and rounds according to the - * context of this instance. Quantizing 3.233 with 0.05 gives 3.25. - */ - void quantize(const DigitList &amount, UErrorCode &status); - - /** - * Like toScientific but only returns the exponent - * leaving this instance unchanged. - */ - int32_t getScientificExponent( - int32_t minIntDigitCount, int32_t exponentMultiplier) const; - - /** - * Converts this instance to scientific notation. This instance - * becomes the mantissa and the exponent is returned. - * @param minIntDigitCount minimum integer digits in mantissa - * Exponent is set so that the actual number of integer digits - * in mantissa is as close to the minimum as possible. - * @param exponentMultiplier The exponent is always a multiple of - * This number. Usually 1, but set to 3 for engineering notation. - * @return exponent - */ - int32_t toScientific( - int32_t minIntDigitCount, int32_t exponentMultiplier); - - /** - * Shifts decimal to the right. - */ - void shiftDecimalRight(int32_t numPlaces); - -private: - /* - * These data members are intentionally public and can be set directly. - *

- * The value represented is given by placing the decimal point before - * fDigits[fDecimalAt]. If fDecimalAt is < 0, then leading zeros between - * the decimal point and the first nonzero digit are implied. If fDecimalAt - * is > fCount, then trailing zeros between the fDigits[fCount-1] and the - * decimal point are implied. - *

- * Equivalently, the represented value is given by f * 10^fDecimalAt. Here - * f is a value 0.1 <= f < 1 arrived at by placing the digits in fDigits to - * the right of the decimal. - *

- * DigitList is normalized, so if it is non-zero, fDigits[0] is non-zero. We - * don't allow denormalized numbers because our exponent is effectively of - * unlimited magnitude. The fCount value contains the number of significant - * digits present in fDigits[]. - *

- * Zero is represented by any DigitList with fCount == 0 or with each fDigits[i] - * for all i <= fCount == '0'. - * - * int32_t fDecimalAt; - * int32_t fCount; - * UBool fIsPositive; - * char *fDigits; - * DecimalFormat::ERoundingMode fRoundingMode; - */ - -public: - decContext fContext; // public access to status flags. - -private: - decNumber *fDecNumber; - MaybeStackHeaderAndArray fStorage; - - /* Cached double value corresponding to this decimal number. - * This is an optimization for the formatting implementation, which may - * ask for the double value multiple times. - */ - union DoubleOrInt64 { - double fDouble; - int64_t fInt64; - } fUnion; - enum EHave { - kNone=0, - kDouble - } fHave; - - - - UBool shouldRoundUp(int32_t maximumDigits) const; - - public: - -#if U_OVERRIDE_CXX_ALLOCATION - using UMemory::operator new; - using UMemory::operator delete; -#else - static inline void * U_EXPORT2 operator new(size_t size) U_NO_THROW { return ::operator new(size); }; - static inline void U_EXPORT2 operator delete(void *ptr ) U_NO_THROW { ::operator delete(ptr); }; -#endif - - static double U_EXPORT2 decimalStrToDouble(char *decstr, char **end); - - /** - * Placement new for stack usage - * @internal - */ - static inline void * U_EXPORT2 operator new(size_t /*size*/, void * onStack, EStackMode /*mode*/) U_NO_THROW { return onStack; } - - /** - * Placement delete for stack usage - * @internal - */ - static inline void U_EXPORT2 operator delete(void * /*ptr*/, void * /*onStack*/, EStackMode /*mode*/) U_NO_THROW {} - - private: - inline void internalSetDouble(double d) { - fHave = kDouble; - fUnion.fDouble=d; - } - inline void internalClear() { - fHave = kNone; - } -}; - - -U_NAMESPACE_END - -#endif // #if !UCONFIG_NO_FORMATTING -#endif // _DIGITLST - -//eof diff --git a/icu4c/source/i18n/msgfmt.cpp b/icu4c/source/i18n/msgfmt.cpp index 57faaa9821..a758f0d055 100644 --- a/icu4c/source/i18n/msgfmt.cpp +++ b/icu4c/source/i18n/msgfmt.cpp @@ -48,7 +48,6 @@ #include "ustrfmt.h" #include "util.h" #include "uvector.h" -#include "visibledigits.h" #include "number_decimalquantity.h" // ***************************************************************************** diff --git a/icu4c/source/i18n/number_decimalquantity.cpp b/icu4c/source/i18n/number_decimalquantity.cpp index 436b222bfa..9798f932d8 100644 --- a/icu4c/source/i18n/number_decimalquantity.cpp +++ b/icu4c/source/i18n/number_decimalquantity.cpp @@ -1031,6 +1031,11 @@ const char16_t* DecimalQuantity::checkHealth() const { return nullptr; } +bool DecimalQuantity::operator==(const DecimalQuantity& other) const { + // FIXME: Make a faster implementation. + return toString() == other.toString(); +} + UnicodeString DecimalQuantity::toString() const { MaybeStackArray digits(precision + 1); for (int32_t i = 0; i < precision; i++) { diff --git a/icu4c/source/i18n/number_decimalquantity.h b/icu4c/source/i18n/number_decimalquantity.h index 155fdfef59..a3c329e265 100644 --- a/icu4c/source/i18n/number_decimalquantity.h +++ b/icu4c/source/i18n/number_decimalquantity.h @@ -243,7 +243,9 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { bool operator==(const DecimalQuantity& other) const; - bool operator!=(const DecimalQuantity& other) const; + inline bool operator!=(const DecimalQuantity& other) const { + return !(*this == other); + } /** * Bogus flag for when a DecimalQuantity is stored on the stack. diff --git a/icu4c/source/i18n/pluralaffix.cpp b/icu4c/source/i18n/pluralaffix.cpp deleted file mode 100644 index ea400206b3..0000000000 --- a/icu4c/source/i18n/pluralaffix.cpp +++ /dev/null @@ -1,104 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* - * Copyright (C) 2015, International Business Machines - * Corporation and others. All Rights Reserved. - * - * file name: pluralaffix.cpp - */ - -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING - -#include "cstring.h" -#include "digitaffix.h" -#include "pluralaffix.h" - -U_NAMESPACE_BEGIN - -UBool -PluralAffix::setVariant( - const char *variant, const UnicodeString &value, UErrorCode &status) { - DigitAffix *current = affixes.getMutable(variant, status); - if (U_FAILURE(status)) { - return FALSE; - } - current->remove(); - current->append(value); - return TRUE; -} - -void -PluralAffix::remove() { - affixes.clear(); -} - -void -PluralAffix::appendUChar( - const UChar value, int32_t fieldId) { - PluralMapBase::Category index = PluralMapBase::NONE; - for (DigitAffix *current = affixes.nextMutable(index); - current != NULL; current = affixes.nextMutable(index)) { - current->appendUChar(value, fieldId); - } -} - -void -PluralAffix::append( - const UnicodeString &value, int32_t fieldId) { - PluralMapBase::Category index = PluralMapBase::NONE; - for (DigitAffix *current = affixes.nextMutable(index); - current != NULL; current = affixes.nextMutable(index)) { - current->append(value, fieldId); - } -} - -void -PluralAffix::append( - const UChar *value, int32_t charCount, int32_t fieldId) { - PluralMapBase::Category index = PluralMapBase::NONE; - for (DigitAffix *current = affixes.nextMutable(index); - current != NULL; current = affixes.nextMutable(index)) { - current->append(value, charCount, fieldId); - } -} - -UBool -PluralAffix::append( - const PluralAffix &rhs, int32_t fieldId, UErrorCode &status) { - if (U_FAILURE(status)) { - return FALSE; - } - PluralMapBase::Category index = PluralMapBase::NONE; - while(rhs.affixes.next(index) != NULL) { - affixes.getMutableWithDefault(index, affixes.getOther(), status); - } - index = PluralMapBase::NONE; - for (DigitAffix *current = affixes.nextMutable(index); - current != NULL; current = affixes.nextMutable(index)) { - current->append(rhs.affixes.get(index).toString(), fieldId); - } - return TRUE; -} - -const DigitAffix & -PluralAffix::getByCategory(const char *category) const { - return affixes.get(category); -} - -const DigitAffix & -PluralAffix::getByCategory(const UnicodeString &category) const { - return affixes.get(category); -} - -UBool -PluralAffix::hasMultipleVariants() const { - // This works because OTHER is guaranteed to be the first enum value - PluralMapBase::Category index = PluralMapBase::OTHER; - return (affixes.next(index) != NULL); -} - -U_NAMESPACE_END - -#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/pluralaffix.h b/icu4c/source/i18n/pluralaffix.h deleted file mode 100644 index df86d8de5d..0000000000 --- a/icu4c/source/i18n/pluralaffix.h +++ /dev/null @@ -1,177 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* Copyright (C) 2015, International Business Machines -* Corporation and others. All Rights Reserved. -******************************************************************************* -* pluralaffix.h -* -* created on: 2015jan06 -* created by: Travis Keep -*/ - -#ifndef __PLURALAFFIX_H__ -#define __PLURALAFFIX_H__ - -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING - -#include "unicode/unum.h" -#include "unicode/uobject.h" - -#include "digitaffix.h" -#include "pluralmap.h" - -U_NAMESPACE_BEGIN - -class FieldPositionHandler; - -// Export an explicit template instantiation. -// -// MSVC requires this, even though it should not be necessary. -// No direct access leaks out of the i18n library. -// -// Macintosh produces duplicate definition linker errors with the explicit template -// instantiation. -// -#if !U_PLATFORM_IS_DARWIN_BASED -template class U_I18N_API PluralMap; -#endif - - -/** - * A plural aware prefix or suffix of a formatted number. - * - * PluralAffix is essentially a map of DigitAffix objects keyed by plural - * category. The 'other' category is the default and always has some - * value. The rest of the categories are optional. Querying for a category that - * is not set always returns the DigitAffix stored in the 'other' category. - * - * To use one of these objects, build it up first using append() and - * setVariant() methods. Once built, leave unchanged and let multiple threads - * safely access. - * - * The following code is sample code for building up: - * one: US Dollar - - * other: US Dollars - - * - * and storing it in "negativeCurrencyPrefix" - * - * UErrorCode status = U_ZERO_ERROR; - * - * PluralAffix negativeCurrencyPrefix; - * - * PluralAffix currencyName; - * currencyName.setVariant("one", "US Dollar", status); - * currencyName.setVariant("other", "US Dollars", status); - * - * negativeCurrencyPrefix.append(currencyName, UNUM_CURRENCY_FIELD, status); - * negativeCurrencyPrefix.append(" "); - * negativeCurrencyPrefix.append("-", UNUM_SIGN_FIELD, status); - */ -class U_I18N_API PluralAffix : public UMemory { -public: - - /** - * Create empty PluralAffix. - */ - PluralAffix() : affixes() { } - - /** - * Create a PluralAffix where the 'other' variant is otherVariant. - */ - PluralAffix(const DigitAffix &otherVariant) : affixes(otherVariant) { } - - /** - * Sets a particular variant for a plural category while overwriting - * anything that may have been previously stored for that plural - * category. The set value has no field annotations. - * @param category "one", "two", "few", ... - * @param variant the variant to store under the particular category - * @param status Any error returned here. - */ - UBool setVariant( - const char *category, - const UnicodeString &variant, - UErrorCode &status); - /** - * Make the 'other' variant be the empty string with no field annotations - * and remove the variants for the rest of the plural categories. - */ - void remove(); - - /** - * Append value to all set plural categories. If fieldId present, value - * is that field type. - */ - void appendUChar(UChar value, int32_t fieldId=UNUM_FIELD_COUNT); - - /** - * Append value to all set plural categories. If fieldId present, value - * is that field type. - */ - void append(const UnicodeString &value, int32_t fieldId=UNUM_FIELD_COUNT); - - /** - * Append value to all set plural categories. If fieldId present, value - * is that field type. - */ - void append(const UChar *value, int32_t charCount, int32_t fieldId=UNUM_FIELD_COUNT); - - /** - * Append the value for each plural category in rhs to the corresponding - * plural category in this instance. Each value appended from rhs is - * of type fieldId. - */ - UBool append( - const PluralAffix &rhs, - int32_t fieldId, - UErrorCode &status); - /** - * Get the DigitAffix for a paricular category such as "zero", "one", ... - * If the particular category is not set, returns the 'other' category - * which is always set. - */ - const DigitAffix &getByCategory(const char *category) const; - - /** - * Get the DigitAffix for a paricular category such as "zero", "one", ... - * If the particular category is not set, returns the 'other' category - * which is always set. - */ - const DigitAffix &getByCategory(const UnicodeString &category) const; - - /** - * Get the DigitAffix for the other category which is always set. - */ - const DigitAffix &getOtherVariant() const { - return affixes.getOther(); - } - - /** - * Returns TRUE if this instance has variants stored besides the "other" - * variant. - */ - UBool hasMultipleVariants() const; - - /** - * Returns TRUE if this instance equals rhs. - */ - UBool equals(const PluralAffix &rhs) const { - return affixes.equals(rhs.affixes, &eq); - } - -private: - PluralMap affixes; - - static UBool eq(const DigitAffix &x, const DigitAffix &y) { - return x.equals(y); - } -}; - - -U_NAMESPACE_END -#endif /* #if !UCONFIG_NO_FORMATTING */ -#endif // __PLURALAFFIX_H__ diff --git a/icu4c/source/i18n/plurfmt.cpp b/icu4c/source/i18n/plurfmt.cpp index f0e18f9cc0..a8fd24d176 100644 --- a/icu4c/source/i18n/plurfmt.cpp +++ b/icu4c/source/i18n/plurfmt.cpp @@ -21,13 +21,14 @@ #include "plurrule_impl.h" #include "uassert.h" #include "uhash.h" -#include "precision.h" -#include "visibledigits.h" +#include "number_decimalquantity.h" #if !UCONFIG_NO_FORMATTING U_NAMESPACE_BEGIN +using number::impl::DecimalQuantity; + static const UChar OTHER_STRING[] = { 0x6F, 0x74, 0x68, 0x65, 0x72, 0 // "other" }; @@ -262,13 +263,9 @@ PluralFormat::format(const Formattable& numberObject, double number, // Select it based on the formatted number-offset. double numberMinusOffset = number - offset; UnicodeString numberString; + DecimalQuantity quantity; + quantity.setToDouble(numberMinusOffset); FieldPosition ignorePos; - FixedPrecision fp; - VisibleDigitsWithExponent dec; - fp.initVisibleDigitsWithExponent(numberMinusOffset, dec, status); - if (U_FAILURE(status)) { - return appendTo; - } if (offset == 0) { DecimalFormat *decFmt = dynamic_cast(numberFormat); if(decFmt != NULL) { @@ -278,7 +275,7 @@ PluralFormat::format(const Formattable& numberObject, double number, // return appendTo; // } // decFmt->format(dec, numberString, ignorePos, status); - decFmt->format(numberObject, numberString, ignorePos, status); + decFmt->format(quantity, numberString, ignorePos, status); } else { numberFormat->format( numberObject, numberString, ignorePos, status); // could be BigDecimal etc. @@ -292,13 +289,13 @@ PluralFormat::format(const Formattable& numberObject, double number, // return appendTo; // } // decFmt->format(dec, numberString, ignorePos, status); - decFmt->format(numberObject, numberString, ignorePos, status); + decFmt->format(quantity, numberString, ignorePos, status); } else { numberFormat->format( numberMinusOffset, numberString, ignorePos, status); } } - int32_t partIndex = findSubMessage(msgPattern, 0, pluralRulesWrapper, &dec, number, status); + int32_t partIndex = findSubMessage(msgPattern, 0, pluralRulesWrapper, &quantity, number, status); if (U_FAILURE(status)) { return appendTo; } // Replace syntactic # signs in the top level of this sub-message // (not in nested arguments) with the formatted number-offset. @@ -587,7 +584,7 @@ PluralFormat::PluralSelectorAdapter::~PluralSelectorAdapter() { UnicodeString PluralFormat::PluralSelectorAdapter::select(void *context, double number, UErrorCode& /*ec*/) const { (void)number; // unused except in the assertion - VisibleDigitsWithExponent *dec=static_cast(context); + IFixedDecimal *dec=static_cast(context); return pluralRules->select(*dec); } diff --git a/icu4c/source/i18n/plurrule.cpp b/icu4c/source/i18n/plurrule.cpp index 36d7bc7f98..eb77905b0c 100644 --- a/icu4c/source/i18n/plurrule.cpp +++ b/icu4c/source/i18n/plurrule.cpp @@ -34,8 +34,6 @@ #include "uvectr32.h" #include "sharedpluralrules.h" #include "unifiedcache.h" -#include "digitinterval.h" -#include "visibledigits.h" #include "number_decimalquantity.h" #if !UCONFIG_NO_FORMATTING @@ -280,14 +278,6 @@ PluralRules::select(const IFixedDecimal &number) const { } } -UnicodeString -PluralRules::select(const VisibleDigitsWithExponent &number) const { - if (number.getExponent() != NULL) { - return UnicodeString(TRUE, PLURAL_DEFAULT_RULE, -1); - } - return select(FixedDecimal(number.getMantissa())); -} - StringEnumeration* @@ -315,7 +305,7 @@ PluralRules::getAllKeywordValues(const UnicodeString & /* keyword */, double * / return 0; } - + static double scaleForInt(double d) { double scale = 1.0; while (d != floor(d)) { @@ -350,7 +340,7 @@ getSamplesFromString(const UnicodeString &samples, double *dest, dest[sampleCount++] = sampleValue; } } else { - + FixedDecimal fixedLo(sampleRange.tempSubStringBetween(0, tildeIndex), status); FixedDecimal fixedHi(sampleRange.tempSubStringBetween(tildeIndex+1), status); double rangeLo = fixedLo.source; @@ -366,7 +356,7 @@ getSamplesFromString(const UnicodeString &samples, double *dest, // For ranges of samples with fraction decimal digits, scale the number up so that we // are adding one in the units place. Avoids roundoffs from repetitive adds of tenths. - double scale = scaleForInt(rangeLo); + double scale = scaleForInt(rangeLo); double t = scaleForInt(rangeHi); if (t > scale) { scale = t; @@ -402,12 +392,12 @@ PluralRules::getSamples(const UnicodeString &keyword, double *dest, return 0; } int32_t numSamples = getSamplesFromString(rc->fIntegerSamples, dest, destCapacity, status); - if (numSamples == 0) { + if (numSamples == 0) { numSamples = getSamplesFromString(rc->fDecimalSamples, dest, destCapacity, status); } return numSamples; } - + RuleChain *PluralRules::rulesForKeyword(const UnicodeString &keyword) const { RuleChain *rc; @@ -549,7 +539,7 @@ PluralRuleParser::parse(const UnicodeString& ruleData, PluralRules *prules, UErr } else { curAndConstraint->rangeList->setElementAt(getNumberValue(token), rangeHiIdx); - if (curAndConstraint->rangeList->elementAti(rangeLowIdx) > + if (curAndConstraint->rangeList->elementAti(rangeLowIdx) > curAndConstraint->rangeList->elementAti(rangeHiIdx)) { // Range Lower bound > Range Upper bound. // U_UNEXPECTED_TOKEN seems a little funny, but it is consistently @@ -600,7 +590,7 @@ PluralRuleParser::parse(const UnicodeString& ruleData, PluralRules *prules, UErr // The new rule chain goes at the end of the linked list of rule chains, // unless there is an "other" keyword & chain. "other" must remain last. RuleChain *insertAfter = prules->mRules; - while (insertAfter->fNext!=NULL && + while (insertAfter->fNext!=NULL && insertAfter->fNext->fKeyword.compare(PLURAL_KEYWORD_OTHER, 5) != 0 ){ insertAfter=insertAfter->fNext; } @@ -641,7 +631,7 @@ PluralRuleParser::parse(const UnicodeString& ruleData, PluralRules *prules, UErr currentChain->fDecimalSamples.append(token); } break; - + default: break; } @@ -896,13 +886,13 @@ OrConstraint::isFulfilled(const IFixedDecimal &number) { } -RuleChain::RuleChain(): fKeyword(), fNext(NULL), ruleHeader(NULL), fDecimalSamples(), fIntegerSamples(), +RuleChain::RuleChain(): fKeyword(), fNext(NULL), ruleHeader(NULL), fDecimalSamples(), fIntegerSamples(), fDecimalSamplesUnbounded(FALSE), fIntegerSamplesUnbounded(FALSE) { } -RuleChain::RuleChain(const RuleChain& other) : +RuleChain::RuleChain(const RuleChain& other) : fKeyword(other.fKeyword), fNext(NULL), ruleHeader(NULL), fDecimalSamples(other.fDecimalSamples), - fIntegerSamples(other.fIntegerSamples), fDecimalSamplesUnbounded(other.fDecimalSamplesUnbounded), + fIntegerSamples(other.fIntegerSamples), fDecimalSamplesUnbounded(other.fDecimalSamplesUnbounded), fIntegerSamplesUnbounded(other.fIntegerSamplesUnbounded) { if (other.ruleHeader != NULL) { this->ruleHeader = new OrConstraint(*(other.ruleHeader)); @@ -1071,9 +1061,9 @@ RuleChain::isKeyword(const UnicodeString& keywordParam) const { } -PluralRuleParser::PluralRuleParser() : - ruleIndex(0), token(), type(none), prevType(none), - curAndConstraint(NULL), currentChain(NULL), rangeLowIdx(-1), rangeHiIdx(-1) +PluralRuleParser::PluralRuleParser() : + ruleIndex(0), token(), type(none), prevType(none), + curAndConstraint(NULL), currentChain(NULL), rangeLowIdx(-1), rangeHiIdx(-1) { } @@ -1174,8 +1164,8 @@ PluralRuleParser::checkSyntax(UErrorCode &status) break; case tNumber: if (type != tDot2 && type != tSemiColon && type != tIs && type != tNot && - type != tIn && type != tEqual && type != tNotEqual && type != tWithin && - type != tAnd && type != tOr && type != tComma && type != tAt && + type != tIn && type != tEqual && type != tNotEqual && type != tWithin && + type != tAnd && type != tOr && type != tComma && type != tAt && type != tEOF) { status = U_UNEXPECTED_TOKEN; @@ -1220,7 +1210,7 @@ PluralRuleParser::getNextToken(UErrorCode &status) return; } int32_t curIndex= ruleIndex; - + switch (type) { case tColon: case tSemiColon: @@ -1325,7 +1315,7 @@ PluralRuleParser::charType(UChar ch) { // Set token type for reserved words in the Plural Rule syntax. -tokenType +tokenType PluralRuleParser::getKeyType(const UnicodeString &token, tokenType keyType) { if (keyType != tKeyword) { @@ -1430,16 +1420,6 @@ PluralOperand tokenTypeToPluralOperand(tokenType tt) { IFixedDecimal::~IFixedDecimal() = default; -FixedDecimal::FixedDecimal(const VisibleDigits &digits) { - digits.getFixedDecimal( - source, intValue, decimalDigits, - decimalDigitsWithoutTrailingZeros, - visibleDecimalDigitCount, hasIntegerValue); - isNegative = digits.isNegative(); - _isNaN = digits.isNaN(); - _isInfinite = digits.isInfinite(); -} - FixedDecimal::FixedDecimal(double n, int32_t v, int64_t f) { init(n, v, f); // check values. TODO make into unit test. @@ -1589,7 +1569,7 @@ int32_t FixedDecimal::decimals(double n) { if (buf[i] != '0') { break; } - --numFractionDigits; + --numFractionDigits; } numFractionDigits -= exponent; // Fraction part of fixed point representation. return numFractionDigits; @@ -1638,7 +1618,7 @@ void FixedDecimal::adjustForMinFractionDigits(int32_t minFractionDigits) { visibleDecimalDigitCount += numTrailingFractionZeros; } } - + double FixedDecimal::getPluralOperand(PluralOperand operand) const { switch(operand) { diff --git a/icu4c/source/i18n/plurrule_impl.h b/icu4c/source/i18n/plurrule_impl.h index a07fc23e03..c85c922f15 100644 --- a/icu4c/source/i18n/plurrule_impl.h +++ b/icu4c/source/i18n/plurrule_impl.h @@ -271,7 +271,6 @@ class U_I18N_API FixedDecimal: public IFixedDecimal, public UObject { FixedDecimal(double n, int32_t v, int64_t f); FixedDecimal(double n, int32_t); explicit FixedDecimal(double n); - explicit FixedDecimal(const VisibleDigits &n); FixedDecimal(); ~FixedDecimal() U_OVERRIDE; FixedDecimal(const UnicodeString &s, UErrorCode &ec); diff --git a/icu4c/source/i18n/precision.cpp b/icu4c/source/i18n/precision.cpp deleted file mode 100644 index bb4970c05f..0000000000 --- a/icu4c/source/i18n/precision.cpp +++ /dev/null @@ -1,444 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* - * Copyright (C) 2015, International Business Machines - * Corporation and others. All Rights Reserved. - * - * file name: precisison.cpp - */ - -#include - -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING - -#include "digitlst.h" -#include "fmtableimp.h" -#include "precision.h" -#include "putilimp.h" -#include "visibledigits.h" - -U_NAMESPACE_BEGIN - -static const int32_t gPower10[] = {1, 10, 100, 1000}; - -FixedPrecision::FixedPrecision() - : fExactOnly(FALSE), fFailIfOverMax(FALSE), fRoundingMode(DecimalFormat::kRoundHalfEven) { - fMin.setIntDigitCount(1); - fMin.setFracDigitCount(0); -} - -UBool -FixedPrecision::isRoundingRequired( - int32_t upperExponent, int32_t lowerExponent) const { - int32_t leastSigAllowed = fMax.getLeastSignificantInclusive(); - int32_t maxSignificantDigits = fSignificant.getMax(); - int32_t roundDigit; - if (maxSignificantDigits == INT32_MAX) { - roundDigit = leastSigAllowed; - } else { - int32_t limitDigit = upperExponent - maxSignificantDigits; - roundDigit = - limitDigit > leastSigAllowed ? limitDigit : leastSigAllowed; - } - return (roundDigit > lowerExponent); -} - -DigitList & -FixedPrecision::round( - DigitList &value, int32_t exponent, UErrorCode &status) const { - if (U_FAILURE(status)) { - return value; - } - value .fContext.status &= ~DEC_Inexact; - if (!fRoundingIncrement.isZero()) { - if (exponent == 0) { - value.quantize(fRoundingIncrement, status); - } else { - DigitList adjustedIncrement(fRoundingIncrement); - adjustedIncrement.shiftDecimalRight(exponent); - value.quantize(adjustedIncrement, status); - } - if (U_FAILURE(status)) { - return value; - } - } - int32_t leastSig = fMax.getLeastSignificantInclusive(); - if (leastSig == INT32_MIN) { - value.round(fSignificant.getMax()); - } else { - value.roundAtExponent( - exponent + leastSig, - fSignificant.getMax()); - } - if (fExactOnly && (value.fContext.status & DEC_Inexact)) { - status = U_FORMAT_INEXACT_ERROR; - } else if (fFailIfOverMax) { - // Smallest interval for value stored in interval - DigitInterval interval; - value.getSmallestInterval(interval); - if (fMax.getIntDigitCount() < interval.getIntDigitCount()) { - status = U_ILLEGAL_ARGUMENT_ERROR; - } - } - return value; -} - -DigitInterval & -FixedPrecision::getIntervalForZero(DigitInterval &interval) const { - interval = fMin; - if (fSignificant.getMin() > 0) { - interval.expandToContainDigit(interval.getIntDigitCount() - fSignificant.getMin()); - } - interval.shrinkToFitWithin(fMax); - return interval; -} - -DigitInterval & -FixedPrecision::getInterval( - int32_t upperExponent, DigitInterval &interval) const { - if (fSignificant.getMin() > 0) { - interval.expandToContainDigit( - upperExponent - fSignificant.getMin()); - } - interval.expandToContain(fMin); - interval.shrinkToFitWithin(fMax); - return interval; -} - -DigitInterval & -FixedPrecision::getInterval( - const DigitList &value, DigitInterval &interval) const { - if (value.isZero()) { - interval = fMin; - if (fSignificant.getMin() > 0) { - interval.expandToContainDigit(interval.getIntDigitCount() - fSignificant.getMin()); - } - } else { - value.getSmallestInterval(interval); - if (fSignificant.getMin() > 0) { - interval.expandToContainDigit( - value.getUpperExponent() - fSignificant.getMin()); - } - interval.expandToContain(fMin); - } - interval.shrinkToFitWithin(fMax); - return interval; -} - -UBool -FixedPrecision::isFastFormattable() const { - return (fMin.getFracDigitCount() == 0 && fSignificant.isNoConstraints() && fRoundingIncrement.isZero() && !fFailIfOverMax); -} - -UBool -FixedPrecision::handleNonNumeric(DigitList &value, VisibleDigits &digits) { - if (value.isNaN()) { - digits.setNaN(); - return TRUE; - } - if (value.isInfinite()) { - digits.setInfinite(); - if (!value.isPositive()) { - digits.setNegative(); - } - return TRUE; - } - return FALSE; -} - -VisibleDigits & -FixedPrecision::initVisibleDigits( - DigitList &value, - VisibleDigits &digits, - UErrorCode &status) const { - if (U_FAILURE(status)) { - return digits; - } - digits.clear(); - if (handleNonNumeric(value, digits)) { - return digits; - } - if (!value.isPositive()) { - digits.setNegative(); - } - value.setRoundingMode(fRoundingMode); - round(value, 0, status); - getInterval(value, digits.fInterval); - digits.fExponent = value.getLowerExponent(); - value.appendDigitsTo(digits.fDigits, status); - return digits; -} - -VisibleDigits & -FixedPrecision::initVisibleDigits( - int64_t value, - VisibleDigits &digits, - UErrorCode &status) const { - if (U_FAILURE(status)) { - return digits; - } - if (!fRoundingIncrement.isZero()) { - // If we have round increment, use digit list. - DigitList digitList; - digitList.set(value); - return initVisibleDigits(digitList, digits, status); - } - // Try fast path - if (initVisibleDigits(value, 0, digits, status)) { - digits.fAbsDoubleValue = fabs((double) value); - digits.fAbsDoubleValueSet = U_SUCCESS(status) && !digits.isOverMaxDigits(); - return digits; - } - // Oops have to use digit list - DigitList digitList; - digitList.set(value); - return initVisibleDigits(digitList, digits, status); -} - -VisibleDigits & -FixedPrecision::initVisibleDigits( - double value, - VisibleDigits &digits, - UErrorCode &status) const { - if (U_FAILURE(status)) { - return digits; - } - digits.clear(); - if (uprv_isNaN(value)) { - digits.setNaN(); - return digits; - } - if (uprv_isPositiveInfinity(value)) { - digits.setInfinite(); - return digits; - } - if (uprv_isNegativeInfinity(value)) { - digits.setInfinite(); - digits.setNegative(); - return digits; - } - if (!fRoundingIncrement.isZero()) { - // If we have round increment, use digit list. - DigitList digitList; - digitList.set(value); - return initVisibleDigits(digitList, digits, status); - } - // Try to find n such that value * 10^n is an integer - int32_t n = -1; - double scaled; - for (int32_t i = 0; i < UPRV_LENGTHOF(gPower10); ++i) { - scaled = value * gPower10[i]; - if (scaled > MAX_INT64_IN_DOUBLE || scaled < -MAX_INT64_IN_DOUBLE) { - break; - } - if (scaled == floor(scaled)) { - n = i; - break; - } - } - // Try fast path - if (n >= 0 && initVisibleDigits(static_cast(scaled), -n, digits, status)) { - digits.fAbsDoubleValue = fabs(value); - digits.fAbsDoubleValueSet = U_SUCCESS(status) && !digits.isOverMaxDigits(); - // Adjust for negative 0 because when we cast to an int64, - // negative 0 becomes positive 0. - if (scaled == 0.0 && uprv_isNegative(scaled)) { - digits.setNegative(); - } - return digits; - } - - // Oops have to use digit list - DigitList digitList; - digitList.set(value); - return initVisibleDigits(digitList, digits, status); -} - -UBool -FixedPrecision::initVisibleDigits( - int64_t mantissa, - int32_t exponent, - VisibleDigits &digits, - UErrorCode &status) const { - if (U_FAILURE(status)) { - return TRUE; - } - digits.clear(); - - // Precompute fAbsIntValue if it is small enough, but we don't know yet - // if it will be valid. - UBool absIntValueComputed = FALSE; - if (mantissa > -1000000000000000000LL /* -1e18 */ - && mantissa < 1000000000000000000LL /* 1e18 */) { - digits.fAbsIntValue = mantissa; - if (digits.fAbsIntValue < 0) { - digits.fAbsIntValue = -digits.fAbsIntValue; - } - int32_t i = 0; - int32_t maxPower10Exp = UPRV_LENGTHOF(gPower10) - 1; - for (; i > exponent + maxPower10Exp; i -= maxPower10Exp) { - digits.fAbsIntValue /= gPower10[maxPower10Exp]; - } - digits.fAbsIntValue /= gPower10[i - exponent]; - absIntValueComputed = TRUE; - } - if (mantissa == 0) { - getIntervalForZero(digits.fInterval); - digits.fAbsIntValueSet = absIntValueComputed; - return TRUE; - } - // be sure least significant digit is non zero - while (mantissa % 10 == 0) { - mantissa /= 10; - ++exponent; - } - if (mantissa < 0) { - digits.fDigits.append((char) -(mantissa % -10), status); - mantissa /= -10; - digits.setNegative(); - } - while (mantissa) { - digits.fDigits.append((char) (mantissa % 10), status); - mantissa /= 10; - } - if (U_FAILURE(status)) { - return TRUE; - } - digits.fExponent = exponent; - int32_t upperExponent = exponent + digits.fDigits.length(); - if (fFailIfOverMax && upperExponent > fMax.getIntDigitCount()) { - status = U_ILLEGAL_ARGUMENT_ERROR; - return TRUE; - } - UBool roundingRequired = - isRoundingRequired(upperExponent, exponent); - if (roundingRequired) { - if (fExactOnly) { - status = U_FORMAT_INEXACT_ERROR; - return TRUE; - } - return FALSE; - } - digits.fInterval.setLeastSignificantInclusive(exponent); - digits.fInterval.setMostSignificantExclusive(upperExponent); - getInterval(upperExponent, digits.fInterval); - - // The intValue we computed above is only valid if our visible digits - // doesn't exceed the maximum integer digits allowed. - digits.fAbsIntValueSet = absIntValueComputed && !digits.isOverMaxDigits(); - return TRUE; -} - -VisibleDigitsWithExponent & -FixedPrecision::initVisibleDigitsWithExponent( - DigitList &value, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const { - digits.clear(); - initVisibleDigits(value, digits.fMantissa, status); - return digits; -} - -VisibleDigitsWithExponent & -FixedPrecision::initVisibleDigitsWithExponent( - double value, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const { - digits.clear(); - initVisibleDigits(value, digits.fMantissa, status); - return digits; -} - -VisibleDigitsWithExponent & -FixedPrecision::initVisibleDigitsWithExponent( - int64_t value, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const { - digits.clear(); - initVisibleDigits(value, digits.fMantissa, status); - return digits; -} - -ScientificPrecision::ScientificPrecision() : fMinExponentDigits(1) { -} - -DigitList & -ScientificPrecision::round(DigitList &value, UErrorCode &status) const { - if (U_FAILURE(status)) { - return value; - } - int32_t exponent = value.getScientificExponent( - fMantissa.fMin.getIntDigitCount(), getMultiplier()); - return fMantissa.round(value, exponent, status); -} - -int32_t -ScientificPrecision::toScientific(DigitList &value) const { - return value.toScientific( - fMantissa.fMin.getIntDigitCount(), getMultiplier()); -} - -int32_t -ScientificPrecision::getMultiplier() const { - int32_t maxIntDigitCount = fMantissa.fMax.getIntDigitCount(); - if (maxIntDigitCount == INT32_MAX) { - return 1; - } - int32_t multiplier = - maxIntDigitCount - fMantissa.fMin.getIntDigitCount() + 1; - return (multiplier < 1 ? 1 : multiplier); -} - -VisibleDigitsWithExponent & -ScientificPrecision::initVisibleDigitsWithExponent( - DigitList &value, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const { - if (U_FAILURE(status)) { - return digits; - } - digits.clear(); - if (FixedPrecision::handleNonNumeric(value, digits.fMantissa)) { - return digits; - } - value.setRoundingMode(fMantissa.fRoundingMode); - int64_t exponent = toScientific(round(value, status)); - fMantissa.initVisibleDigits(value, digits.fMantissa, status); - FixedPrecision exponentPrecision; - exponentPrecision.fMin.setIntDigitCount(fMinExponentDigits); - exponentPrecision.initVisibleDigits(exponent, digits.fExponent, status); - digits.fHasExponent = TRUE; - return digits; -} - -VisibleDigitsWithExponent & -ScientificPrecision::initVisibleDigitsWithExponent( - double value, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const { - if (U_FAILURE(status)) { - return digits; - } - DigitList digitList; - digitList.set(value); - return initVisibleDigitsWithExponent(digitList, digits, status); -} - -VisibleDigitsWithExponent & -ScientificPrecision::initVisibleDigitsWithExponent( - int64_t value, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const { - if (U_FAILURE(status)) { - return digits; - } - DigitList digitList; - digitList.set(value); - return initVisibleDigitsWithExponent(digitList, digits, status); -} - - -U_NAMESPACE_END -#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/precision.h b/icu4c/source/i18n/precision.h deleted file mode 100644 index 99f6d2d750..0000000000 --- a/icu4c/source/i18n/precision.h +++ /dev/null @@ -1,323 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* Copyright (C) 2015, International Business Machines -* Corporation and others. All Rights Reserved. -******************************************************************************* -* precision.h -* -* created on: 2015jan06 -* created by: Travis Keep -*/ - -#ifndef __PRECISION_H__ -#define __PRECISION_H__ - -#include "unicode/uobject.h" - -#if !UCONFIG_NO_FORMATTING -#include "unicode/utypes.h" - -#include "digitinterval.h" -#include "digitlst.h" -#include "significantdigitinterval.h" - -U_NAMESPACE_BEGIN - -class VisibleDigits; -class VisibleDigitsWithExponent; - - -/** - * A precision manager for values to be formatted as fixed point. - * Handles rounding of number to prepare it for formatting. - */ -class U_I18N_API FixedPrecision : public UMemory { -public: - - /** - * The smallest format interval allowed. Default is 1 integer digit and no - * fraction digits. - */ - DigitInterval fMin; - - /** - * The largest format interval allowed. Must contain fMin. - * Default is all digits. - */ - DigitInterval fMax; - - /** - * Min and max significant digits allowed. The default is no constraints. - */ - SignificantDigitInterval fSignificant; - - /** - * The rounding increment or zero if there is no rounding increment. - * Default is zero. - */ - DigitList fRoundingIncrement; - - /** - * If set, causes round() to set status to U_FORMAT_INEXACT_ERROR if - * any rounding is done. Default is FALSE. - */ - UBool fExactOnly; - - /** - * If set, causes round() to set status to U_ILLEGAL_ARGUMENT_ERROR if - * rounded number has more than maximum integer digits. Default is FALSE. - */ - UBool fFailIfOverMax; - - /** - * Controls the rounding mode that initVisibleDigits uses. - * Default is DecimalFormat::kRoundHalfEven - */ - DecimalFormat::ERoundingMode fRoundingMode; - - FixedPrecision(); - - /** - * Returns TRUE if this object equals rhs. - */ - UBool equals(const FixedPrecision &rhs) const { - return (fMin.equals(rhs.fMin) && - fMax.equals(rhs.fMax) && - fSignificant.equals(rhs.fSignificant) && - (fRoundingIncrement == rhs.fRoundingIncrement) && - fExactOnly == rhs.fExactOnly && - fFailIfOverMax == rhs.fFailIfOverMax && - fRoundingMode == rhs.fRoundingMode); - } - - /** - * Rounds value in place to prepare it for formatting. - * @param value The value to be rounded. It is rounded in place. - * @param exponent Always pass 0 for fixed decimal formatting. scientific - * precision passes the exponent value. Essentially, it divides value by - * 10^exponent, rounds and then multiplies by 10^exponent. - * @param status error returned here. - * @return reference to value. - */ - DigitList &round(DigitList &value, int32_t exponent, UErrorCode &status) const; - - /** - * Returns the interval to use to format the rounded value. - * @param roundedValue the already rounded value to format. - * @param interval modified in place to be the interval to use to format - * the rounded value. - * @return a reference to interval. - */ - DigitInterval &getInterval( - const DigitList &roundedValue, DigitInterval &interval) const; - - /** - * Returns TRUE if this instance allows for fast formatting of integers. - */ - UBool isFastFormattable() const; - - /** - * Initializes a VisibleDigits. - * @param value value for VisibleDigits - * Caller must not assume that the value of this parameter will remain - * unchanged. - * @param digits This is the value that is initialized. - * @param status any error returned here. - * @return digits - */ - VisibleDigits &initVisibleDigits( - DigitList &value, - VisibleDigits &digits, - UErrorCode &status) const; - - /** - * Initializes a VisibleDigits. - * @param value value for VisibleDigits - * @param digits This is the value that is initialized. - * @param status any error returned here. - * @return digits - */ - VisibleDigits &initVisibleDigits( - double value, - VisibleDigits &digits, - UErrorCode &status) const; - - /** - * Initializes a VisibleDigits. - * @param value value for VisibleDigits - * @param digits This is the value that is initialized. - * @param status any error returned here. - * @return digits - */ - VisibleDigits &initVisibleDigits( - int64_t value, - VisibleDigits &digits, - UErrorCode &status) const; - - /** - * Initializes a VisibleDigitsWithExponent. - * @param value value for VisibleDigits - * Caller must not assume that the value of this parameter will remain - * unchanged. - * @param digits This is the value that is initialized. - * @param status any error returned here. - * @return digits - */ - VisibleDigitsWithExponent &initVisibleDigitsWithExponent( - DigitList &value, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const; - - /** - * Initializes a VisibleDigitsWithExponent. - * @param value value for VisibleDigits - * @param digits This is the value that is initialized. - * @param status any error returned here. - * @return digits - */ - VisibleDigitsWithExponent &initVisibleDigitsWithExponent( - double value, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const; - - /** - * Initializes a VisibleDigitsWithExponent. - * @param value value for VisibleDigits - * @param digits This is the value that is initialized. - * @param status any error returned here. - * @return digits - */ - VisibleDigitsWithExponent &initVisibleDigitsWithExponent( - int64_t value, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const; - -private: - /** - * Attempts to initialize 'digits' using simple mod 10 arithmetic. - * Returns FALSE if this is not possible such as when rounding - * would change the value. Otherwise returns TRUE. - * - * If the method returns FALSE, caller should create a DigitList - * and use it to initialize 'digits'. If this method returns TRUE, - * caller should accept the value stored in 'digits'. If this - * method returns TRUE along with a non zero error, caller must accept - * the error and not try again with a DigitList. - * - * Before calling this method, caller must verify that this object - * has no rounding increment set. - * - * The value that 'digits' is initialized to is mantissa * 10^exponent. - * For example mantissa = 54700 and exponent = -3 means 54.7. The - * properties of this object (such as min and max fraction digits), - * not the number of trailing zeros in the mantissa, determine whether or - * not the result contains any trailing 0's after the decimal point. - * - * @param mantissa the digits. May be positive or negative. May contain - * trailing zeros. - * @param exponent must always be zero or negative. An exponent > 0 - * yields undefined results! - * @param digits result stored here. - * @param status any error returned here. - */ - UBool - initVisibleDigits( - int64_t mantissa, - int32_t exponent, - VisibleDigits &digits, - UErrorCode &status) const; - UBool isRoundingRequired( - int32_t upperExponent, int32_t lowerExponent) const; - DigitInterval &getIntervalForZero(DigitInterval &interval) const; - DigitInterval &getInterval( - int32_t upperExponent, DigitInterval &interval) const; - static UBool handleNonNumeric(DigitList &value, VisibleDigits &digits); - - friend class ScientificPrecision; -}; - -/** - * A precision manager for values to be expressed as scientific notation. - */ -class U_I18N_API ScientificPrecision : public UMemory { -public: - FixedPrecision fMantissa; - int32_t fMinExponentDigits; - - ScientificPrecision(); - - /** - * rounds value in place to prepare it for formatting. - * @param value The value to be rounded. It is rounded in place. - * @param status error returned here. - * @return reference to value. - */ - DigitList &round(DigitList &value, UErrorCode &status) const; - - /** - * Converts value to a mantissa and exponent. - * - * @param value modified in place to be the mantissa. Depending on - * the precision settings, the resulting mantissa may not fall - * between 1.0 and 10.0. - * @return the exponent of value. - */ - int32_t toScientific(DigitList &value) const; - - /** - * Returns TRUE if this object equals rhs. - */ - UBool equals(const ScientificPrecision &rhs) const { - return fMantissa.equals(rhs.fMantissa) && fMinExponentDigits == rhs.fMinExponentDigits; - } - - /** - * Initializes a VisibleDigitsWithExponent. - * @param value the value - * Caller must not assume that the value of this parameter will remain - * unchanged. - * @param digits This is the value that is initialized. - * @param status any error returned here. - * @return digits - */ - VisibleDigitsWithExponent &initVisibleDigitsWithExponent( - DigitList &value, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const; - - /** - * Initializes a VisibleDigitsWithExponent. - * @param value the value - * @param digits This is the value that is initialized. - * @param status any error returned here. - * @return digits - */ - VisibleDigitsWithExponent &initVisibleDigitsWithExponent( - double value, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const; - - /** - * Initializes a VisibleDigitsWithExponent. - * @param value the value - * @param digits This is the value that is initialized. - * @param status any error returned here. - * @return digits - */ - VisibleDigitsWithExponent &initVisibleDigitsWithExponent( - int64_t value, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const; - -private: - int32_t getMultiplier() const; - -}; - - - -U_NAMESPACE_END -#endif // #if !UCONFIG_NO_FORMATTING -#endif // __PRECISION_H__ diff --git a/icu4c/source/i18n/quantityformatter.cpp b/icu4c/source/i18n/quantityformatter.cpp index 9ef607e50a..ba06ba06b9 100644 --- a/icu4c/source/i18n/quantityformatter.cpp +++ b/icu4c/source/i18n/quantityformatter.cpp @@ -23,7 +23,6 @@ #include "unicode/fmtable.h" #include "unicode/fieldpos.h" #include "standardplural.h" -#include "visibledigits.h" #include "uassert.h" #include "number_decimalquantity.h" diff --git a/icu4c/source/i18n/smallintformatter.cpp b/icu4c/source/i18n/smallintformatter.cpp deleted file mode 100644 index 72ffffc754..0000000000 --- a/icu4c/source/i18n/smallintformatter.cpp +++ /dev/null @@ -1,2623 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* - * Copyright (C) 2015, International Business Machines - * Corporation and others. All Rights Reserved. - * - * file name: smallintformatter.cpp - */ - -#include "unicode/unistr.h" - -#include "smallintformatter.h" - -static const int32_t gMaxFastInt = 4096; - -static const UChar gDigits[] = { - 0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x31, - 0x30,0x30,0x30,0x32,0x30,0x30,0x30,0x33, - 0x30,0x30,0x30,0x34,0x30,0x30,0x30,0x35, - 0x30,0x30,0x30,0x36,0x30,0x30,0x30,0x37, - 0x30,0x30,0x30,0x38,0x30,0x30,0x30,0x39, - 0x30,0x30,0x31,0x30,0x30,0x30,0x31,0x31, - 0x30,0x30,0x31,0x32,0x30,0x30,0x31,0x33, - 0x30,0x30,0x31,0x34,0x30,0x30,0x31,0x35, - 0x30,0x30,0x31,0x36,0x30,0x30,0x31,0x37, - 0x30,0x30,0x31,0x38,0x30,0x30,0x31,0x39, - 0x30,0x30,0x32,0x30,0x30,0x30,0x32,0x31, - 0x30,0x30,0x32,0x32,0x30,0x30,0x32,0x33, - 0x30,0x30,0x32,0x34,0x30,0x30,0x32,0x35, - 0x30,0x30,0x32,0x36,0x30,0x30,0x32,0x37, - 0x30,0x30,0x32,0x38,0x30,0x30,0x32,0x39, - 0x30,0x30,0x33,0x30,0x30,0x30,0x33,0x31, - 0x30,0x30,0x33,0x32,0x30,0x30,0x33,0x33, - 0x30,0x30,0x33,0x34,0x30,0x30,0x33,0x35, - 0x30,0x30,0x33,0x36,0x30,0x30,0x33,0x37, - 0x30,0x30,0x33,0x38,0x30,0x30,0x33,0x39, - 0x30,0x30,0x34,0x30,0x30,0x30,0x34,0x31, - 0x30,0x30,0x34,0x32,0x30,0x30,0x34,0x33, - 0x30,0x30,0x34,0x34,0x30,0x30,0x34,0x35, - 0x30,0x30,0x34,0x36,0x30,0x30,0x34,0x37, - 0x30,0x30,0x34,0x38,0x30,0x30,0x34,0x39, - 0x30,0x30,0x35,0x30,0x30,0x30,0x35,0x31, - 0x30,0x30,0x35,0x32,0x30,0x30,0x35,0x33, - 0x30,0x30,0x35,0x34,0x30,0x30,0x35,0x35, - 0x30,0x30,0x35,0x36,0x30,0x30,0x35,0x37, - 0x30,0x30,0x35,0x38,0x30,0x30,0x35,0x39, - 0x30,0x30,0x36,0x30,0x30,0x30,0x36,0x31, - 0x30,0x30,0x36,0x32,0x30,0x30,0x36,0x33, - 0x30,0x30,0x36,0x34,0x30,0x30,0x36,0x35, - 0x30,0x30,0x36,0x36,0x30,0x30,0x36,0x37, - 0x30,0x30,0x36,0x38,0x30,0x30,0x36,0x39, - 0x30,0x30,0x37,0x30,0x30,0x30,0x37,0x31, - 0x30,0x30,0x37,0x32,0x30,0x30,0x37,0x33, - 0x30,0x30,0x37,0x34,0x30,0x30,0x37,0x35, - 0x30,0x30,0x37,0x36,0x30,0x30,0x37,0x37, - 0x30,0x30,0x37,0x38,0x30,0x30,0x37,0x39, - 0x30,0x30,0x38,0x30,0x30,0x30,0x38,0x31, - 0x30,0x30,0x38,0x32,0x30,0x30,0x38,0x33, - 0x30,0x30,0x38,0x34,0x30,0x30,0x38,0x35, - 0x30,0x30,0x38,0x36,0x30,0x30,0x38,0x37, - 0x30,0x30,0x38,0x38,0x30,0x30,0x38,0x39, - 0x30,0x30,0x39,0x30,0x30,0x30,0x39,0x31, - 0x30,0x30,0x39,0x32,0x30,0x30,0x39,0x33, - 0x30,0x30,0x39,0x34,0x30,0x30,0x39,0x35, - 0x30,0x30,0x39,0x36,0x30,0x30,0x39,0x37, - 0x30,0x30,0x39,0x38,0x30,0x30,0x39,0x39, - 0x30,0x31,0x30,0x30,0x30,0x31,0x30,0x31, - 0x30,0x31,0x30,0x32,0x30,0x31,0x30,0x33, - 0x30,0x31,0x30,0x34,0x30,0x31,0x30,0x35, - 0x30,0x31,0x30,0x36,0x30,0x31,0x30,0x37, - 0x30,0x31,0x30,0x38,0x30,0x31,0x30,0x39, - 0x30,0x31,0x31,0x30,0x30,0x31,0x31,0x31, - 0x30,0x31,0x31,0x32,0x30,0x31,0x31,0x33, - 0x30,0x31,0x31,0x34,0x30,0x31,0x31,0x35, - 0x30,0x31,0x31,0x36,0x30,0x31,0x31,0x37, - 0x30,0x31,0x31,0x38,0x30,0x31,0x31,0x39, - 0x30,0x31,0x32,0x30,0x30,0x31,0x32,0x31, - 0x30,0x31,0x32,0x32,0x30,0x31,0x32,0x33, - 0x30,0x31,0x32,0x34,0x30,0x31,0x32,0x35, - 0x30,0x31,0x32,0x36,0x30,0x31,0x32,0x37, - 0x30,0x31,0x32,0x38,0x30,0x31,0x32,0x39, - 0x30,0x31,0x33,0x30,0x30,0x31,0x33,0x31, - 0x30,0x31,0x33,0x32,0x30,0x31,0x33,0x33, - 0x30,0x31,0x33,0x34,0x30,0x31,0x33,0x35, - 0x30,0x31,0x33,0x36,0x30,0x31,0x33,0x37, - 0x30,0x31,0x33,0x38,0x30,0x31,0x33,0x39, - 0x30,0x31,0x34,0x30,0x30,0x31,0x34,0x31, - 0x30,0x31,0x34,0x32,0x30,0x31,0x34,0x33, - 0x30,0x31,0x34,0x34,0x30,0x31,0x34,0x35, - 0x30,0x31,0x34,0x36,0x30,0x31,0x34,0x37, - 0x30,0x31,0x34,0x38,0x30,0x31,0x34,0x39, - 0x30,0x31,0x35,0x30,0x30,0x31,0x35,0x31, - 0x30,0x31,0x35,0x32,0x30,0x31,0x35,0x33, - 0x30,0x31,0x35,0x34,0x30,0x31,0x35,0x35, - 0x30,0x31,0x35,0x36,0x30,0x31,0x35,0x37, - 0x30,0x31,0x35,0x38,0x30,0x31,0x35,0x39, - 0x30,0x31,0x36,0x30,0x30,0x31,0x36,0x31, - 0x30,0x31,0x36,0x32,0x30,0x31,0x36,0x33, - 0x30,0x31,0x36,0x34,0x30,0x31,0x36,0x35, - 0x30,0x31,0x36,0x36,0x30,0x31,0x36,0x37, - 0x30,0x31,0x36,0x38,0x30,0x31,0x36,0x39, - 0x30,0x31,0x37,0x30,0x30,0x31,0x37,0x31, - 0x30,0x31,0x37,0x32,0x30,0x31,0x37,0x33, - 0x30,0x31,0x37,0x34,0x30,0x31,0x37,0x35, - 0x30,0x31,0x37,0x36,0x30,0x31,0x37,0x37, - 0x30,0x31,0x37,0x38,0x30,0x31,0x37,0x39, - 0x30,0x31,0x38,0x30,0x30,0x31,0x38,0x31, - 0x30,0x31,0x38,0x32,0x30,0x31,0x38,0x33, - 0x30,0x31,0x38,0x34,0x30,0x31,0x38,0x35, - 0x30,0x31,0x38,0x36,0x30,0x31,0x38,0x37, - 0x30,0x31,0x38,0x38,0x30,0x31,0x38,0x39, - 0x30,0x31,0x39,0x30,0x30,0x31,0x39,0x31, - 0x30,0x31,0x39,0x32,0x30,0x31,0x39,0x33, - 0x30,0x31,0x39,0x34,0x30,0x31,0x39,0x35, - 0x30,0x31,0x39,0x36,0x30,0x31,0x39,0x37, - 0x30,0x31,0x39,0x38,0x30,0x31,0x39,0x39, - 0x30,0x32,0x30,0x30,0x30,0x32,0x30,0x31, - 0x30,0x32,0x30,0x32,0x30,0x32,0x30,0x33, - 0x30,0x32,0x30,0x34,0x30,0x32,0x30,0x35, - 0x30,0x32,0x30,0x36,0x30,0x32,0x30,0x37, - 0x30,0x32,0x30,0x38,0x30,0x32,0x30,0x39, - 0x30,0x32,0x31,0x30,0x30,0x32,0x31,0x31, - 0x30,0x32,0x31,0x32,0x30,0x32,0x31,0x33, - 0x30,0x32,0x31,0x34,0x30,0x32,0x31,0x35, - 0x30,0x32,0x31,0x36,0x30,0x32,0x31,0x37, - 0x30,0x32,0x31,0x38,0x30,0x32,0x31,0x39, - 0x30,0x32,0x32,0x30,0x30,0x32,0x32,0x31, - 0x30,0x32,0x32,0x32,0x30,0x32,0x32,0x33, - 0x30,0x32,0x32,0x34,0x30,0x32,0x32,0x35, - 0x30,0x32,0x32,0x36,0x30,0x32,0x32,0x37, - 0x30,0x32,0x32,0x38,0x30,0x32,0x32,0x39, - 0x30,0x32,0x33,0x30,0x30,0x32,0x33,0x31, - 0x30,0x32,0x33,0x32,0x30,0x32,0x33,0x33, - 0x30,0x32,0x33,0x34,0x30,0x32,0x33,0x35, - 0x30,0x32,0x33,0x36,0x30,0x32,0x33,0x37, - 0x30,0x32,0x33,0x38,0x30,0x32,0x33,0x39, - 0x30,0x32,0x34,0x30,0x30,0x32,0x34,0x31, - 0x30,0x32,0x34,0x32,0x30,0x32,0x34,0x33, - 0x30,0x32,0x34,0x34,0x30,0x32,0x34,0x35, - 0x30,0x32,0x34,0x36,0x30,0x32,0x34,0x37, - 0x30,0x32,0x34,0x38,0x30,0x32,0x34,0x39, - 0x30,0x32,0x35,0x30,0x30,0x32,0x35,0x31, - 0x30,0x32,0x35,0x32,0x30,0x32,0x35,0x33, - 0x30,0x32,0x35,0x34,0x30,0x32,0x35,0x35, - 0x30,0x32,0x35,0x36,0x30,0x32,0x35,0x37, - 0x30,0x32,0x35,0x38,0x30,0x32,0x35,0x39, - 0x30,0x32,0x36,0x30,0x30,0x32,0x36,0x31, - 0x30,0x32,0x36,0x32,0x30,0x32,0x36,0x33, - 0x30,0x32,0x36,0x34,0x30,0x32,0x36,0x35, - 0x30,0x32,0x36,0x36,0x30,0x32,0x36,0x37, - 0x30,0x32,0x36,0x38,0x30,0x32,0x36,0x39, - 0x30,0x32,0x37,0x30,0x30,0x32,0x37,0x31, - 0x30,0x32,0x37,0x32,0x30,0x32,0x37,0x33, - 0x30,0x32,0x37,0x34,0x30,0x32,0x37,0x35, - 0x30,0x32,0x37,0x36,0x30,0x32,0x37,0x37, - 0x30,0x32,0x37,0x38,0x30,0x32,0x37,0x39, - 0x30,0x32,0x38,0x30,0x30,0x32,0x38,0x31, - 0x30,0x32,0x38,0x32,0x30,0x32,0x38,0x33, - 0x30,0x32,0x38,0x34,0x30,0x32,0x38,0x35, - 0x30,0x32,0x38,0x36,0x30,0x32,0x38,0x37, - 0x30,0x32,0x38,0x38,0x30,0x32,0x38,0x39, - 0x30,0x32,0x39,0x30,0x30,0x32,0x39,0x31, - 0x30,0x32,0x39,0x32,0x30,0x32,0x39,0x33, - 0x30,0x32,0x39,0x34,0x30,0x32,0x39,0x35, - 0x30,0x32,0x39,0x36,0x30,0x32,0x39,0x37, - 0x30,0x32,0x39,0x38,0x30,0x32,0x39,0x39, - 0x30,0x33,0x30,0x30,0x30,0x33,0x30,0x31, - 0x30,0x33,0x30,0x32,0x30,0x33,0x30,0x33, - 0x30,0x33,0x30,0x34,0x30,0x33,0x30,0x35, - 0x30,0x33,0x30,0x36,0x30,0x33,0x30,0x37, - 0x30,0x33,0x30,0x38,0x30,0x33,0x30,0x39, - 0x30,0x33,0x31,0x30,0x30,0x33,0x31,0x31, - 0x30,0x33,0x31,0x32,0x30,0x33,0x31,0x33, - 0x30,0x33,0x31,0x34,0x30,0x33,0x31,0x35, - 0x30,0x33,0x31,0x36,0x30,0x33,0x31,0x37, - 0x30,0x33,0x31,0x38,0x30,0x33,0x31,0x39, - 0x30,0x33,0x32,0x30,0x30,0x33,0x32,0x31, - 0x30,0x33,0x32,0x32,0x30,0x33,0x32,0x33, - 0x30,0x33,0x32,0x34,0x30,0x33,0x32,0x35, - 0x30,0x33,0x32,0x36,0x30,0x33,0x32,0x37, - 0x30,0x33,0x32,0x38,0x30,0x33,0x32,0x39, - 0x30,0x33,0x33,0x30,0x30,0x33,0x33,0x31, - 0x30,0x33,0x33,0x32,0x30,0x33,0x33,0x33, - 0x30,0x33,0x33,0x34,0x30,0x33,0x33,0x35, - 0x30,0x33,0x33,0x36,0x30,0x33,0x33,0x37, - 0x30,0x33,0x33,0x38,0x30,0x33,0x33,0x39, - 0x30,0x33,0x34,0x30,0x30,0x33,0x34,0x31, - 0x30,0x33,0x34,0x32,0x30,0x33,0x34,0x33, - 0x30,0x33,0x34,0x34,0x30,0x33,0x34,0x35, - 0x30,0x33,0x34,0x36,0x30,0x33,0x34,0x37, - 0x30,0x33,0x34,0x38,0x30,0x33,0x34,0x39, - 0x30,0x33,0x35,0x30,0x30,0x33,0x35,0x31, - 0x30,0x33,0x35,0x32,0x30,0x33,0x35,0x33, - 0x30,0x33,0x35,0x34,0x30,0x33,0x35,0x35, - 0x30,0x33,0x35,0x36,0x30,0x33,0x35,0x37, - 0x30,0x33,0x35,0x38,0x30,0x33,0x35,0x39, - 0x30,0x33,0x36,0x30,0x30,0x33,0x36,0x31, - 0x30,0x33,0x36,0x32,0x30,0x33,0x36,0x33, - 0x30,0x33,0x36,0x34,0x30,0x33,0x36,0x35, - 0x30,0x33,0x36,0x36,0x30,0x33,0x36,0x37, - 0x30,0x33,0x36,0x38,0x30,0x33,0x36,0x39, - 0x30,0x33,0x37,0x30,0x30,0x33,0x37,0x31, - 0x30,0x33,0x37,0x32,0x30,0x33,0x37,0x33, - 0x30,0x33,0x37,0x34,0x30,0x33,0x37,0x35, - 0x30,0x33,0x37,0x36,0x30,0x33,0x37,0x37, - 0x30,0x33,0x37,0x38,0x30,0x33,0x37,0x39, - 0x30,0x33,0x38,0x30,0x30,0x33,0x38,0x31, - 0x30,0x33,0x38,0x32,0x30,0x33,0x38,0x33, - 0x30,0x33,0x38,0x34,0x30,0x33,0x38,0x35, - 0x30,0x33,0x38,0x36,0x30,0x33,0x38,0x37, - 0x30,0x33,0x38,0x38,0x30,0x33,0x38,0x39, - 0x30,0x33,0x39,0x30,0x30,0x33,0x39,0x31, - 0x30,0x33,0x39,0x32,0x30,0x33,0x39,0x33, - 0x30,0x33,0x39,0x34,0x30,0x33,0x39,0x35, - 0x30,0x33,0x39,0x36,0x30,0x33,0x39,0x37, - 0x30,0x33,0x39,0x38,0x30,0x33,0x39,0x39, - 0x30,0x34,0x30,0x30,0x30,0x34,0x30,0x31, - 0x30,0x34,0x30,0x32,0x30,0x34,0x30,0x33, - 0x30,0x34,0x30,0x34,0x30,0x34,0x30,0x35, - 0x30,0x34,0x30,0x36,0x30,0x34,0x30,0x37, - 0x30,0x34,0x30,0x38,0x30,0x34,0x30,0x39, - 0x30,0x34,0x31,0x30,0x30,0x34,0x31,0x31, - 0x30,0x34,0x31,0x32,0x30,0x34,0x31,0x33, - 0x30,0x34,0x31,0x34,0x30,0x34,0x31,0x35, - 0x30,0x34,0x31,0x36,0x30,0x34,0x31,0x37, - 0x30,0x34,0x31,0x38,0x30,0x34,0x31,0x39, - 0x30,0x34,0x32,0x30,0x30,0x34,0x32,0x31, - 0x30,0x34,0x32,0x32,0x30,0x34,0x32,0x33, - 0x30,0x34,0x32,0x34,0x30,0x34,0x32,0x35, - 0x30,0x34,0x32,0x36,0x30,0x34,0x32,0x37, - 0x30,0x34,0x32,0x38,0x30,0x34,0x32,0x39, - 0x30,0x34,0x33,0x30,0x30,0x34,0x33,0x31, - 0x30,0x34,0x33,0x32,0x30,0x34,0x33,0x33, - 0x30,0x34,0x33,0x34,0x30,0x34,0x33,0x35, - 0x30,0x34,0x33,0x36,0x30,0x34,0x33,0x37, - 0x30,0x34,0x33,0x38,0x30,0x34,0x33,0x39, - 0x30,0x34,0x34,0x30,0x30,0x34,0x34,0x31, - 0x30,0x34,0x34,0x32,0x30,0x34,0x34,0x33, - 0x30,0x34,0x34,0x34,0x30,0x34,0x34,0x35, - 0x30,0x34,0x34,0x36,0x30,0x34,0x34,0x37, - 0x30,0x34,0x34,0x38,0x30,0x34,0x34,0x39, - 0x30,0x34,0x35,0x30,0x30,0x34,0x35,0x31, - 0x30,0x34,0x35,0x32,0x30,0x34,0x35,0x33, - 0x30,0x34,0x35,0x34,0x30,0x34,0x35,0x35, - 0x30,0x34,0x35,0x36,0x30,0x34,0x35,0x37, - 0x30,0x34,0x35,0x38,0x30,0x34,0x35,0x39, - 0x30,0x34,0x36,0x30,0x30,0x34,0x36,0x31, - 0x30,0x34,0x36,0x32,0x30,0x34,0x36,0x33, - 0x30,0x34,0x36,0x34,0x30,0x34,0x36,0x35, - 0x30,0x34,0x36,0x36,0x30,0x34,0x36,0x37, - 0x30,0x34,0x36,0x38,0x30,0x34,0x36,0x39, - 0x30,0x34,0x37,0x30,0x30,0x34,0x37,0x31, - 0x30,0x34,0x37,0x32,0x30,0x34,0x37,0x33, - 0x30,0x34,0x37,0x34,0x30,0x34,0x37,0x35, - 0x30,0x34,0x37,0x36,0x30,0x34,0x37,0x37, - 0x30,0x34,0x37,0x38,0x30,0x34,0x37,0x39, - 0x30,0x34,0x38,0x30,0x30,0x34,0x38,0x31, - 0x30,0x34,0x38,0x32,0x30,0x34,0x38,0x33, - 0x30,0x34,0x38,0x34,0x30,0x34,0x38,0x35, - 0x30,0x34,0x38,0x36,0x30,0x34,0x38,0x37, - 0x30,0x34,0x38,0x38,0x30,0x34,0x38,0x39, - 0x30,0x34,0x39,0x30,0x30,0x34,0x39,0x31, - 0x30,0x34,0x39,0x32,0x30,0x34,0x39,0x33, - 0x30,0x34,0x39,0x34,0x30,0x34,0x39,0x35, - 0x30,0x34,0x39,0x36,0x30,0x34,0x39,0x37, - 0x30,0x34,0x39,0x38,0x30,0x34,0x39,0x39, - 0x30,0x35,0x30,0x30,0x30,0x35,0x30,0x31, - 0x30,0x35,0x30,0x32,0x30,0x35,0x30,0x33, - 0x30,0x35,0x30,0x34,0x30,0x35,0x30,0x35, - 0x30,0x35,0x30,0x36,0x30,0x35,0x30,0x37, - 0x30,0x35,0x30,0x38,0x30,0x35,0x30,0x39, - 0x30,0x35,0x31,0x30,0x30,0x35,0x31,0x31, - 0x30,0x35,0x31,0x32,0x30,0x35,0x31,0x33, - 0x30,0x35,0x31,0x34,0x30,0x35,0x31,0x35, - 0x30,0x35,0x31,0x36,0x30,0x35,0x31,0x37, - 0x30,0x35,0x31,0x38,0x30,0x35,0x31,0x39, - 0x30,0x35,0x32,0x30,0x30,0x35,0x32,0x31, - 0x30,0x35,0x32,0x32,0x30,0x35,0x32,0x33, - 0x30,0x35,0x32,0x34,0x30,0x35,0x32,0x35, - 0x30,0x35,0x32,0x36,0x30,0x35,0x32,0x37, - 0x30,0x35,0x32,0x38,0x30,0x35,0x32,0x39, - 0x30,0x35,0x33,0x30,0x30,0x35,0x33,0x31, - 0x30,0x35,0x33,0x32,0x30,0x35,0x33,0x33, - 0x30,0x35,0x33,0x34,0x30,0x35,0x33,0x35, - 0x30,0x35,0x33,0x36,0x30,0x35,0x33,0x37, - 0x30,0x35,0x33,0x38,0x30,0x35,0x33,0x39, - 0x30,0x35,0x34,0x30,0x30,0x35,0x34,0x31, - 0x30,0x35,0x34,0x32,0x30,0x35,0x34,0x33, - 0x30,0x35,0x34,0x34,0x30,0x35,0x34,0x35, - 0x30,0x35,0x34,0x36,0x30,0x35,0x34,0x37, - 0x30,0x35,0x34,0x38,0x30,0x35,0x34,0x39, - 0x30,0x35,0x35,0x30,0x30,0x35,0x35,0x31, - 0x30,0x35,0x35,0x32,0x30,0x35,0x35,0x33, - 0x30,0x35,0x35,0x34,0x30,0x35,0x35,0x35, - 0x30,0x35,0x35,0x36,0x30,0x35,0x35,0x37, - 0x30,0x35,0x35,0x38,0x30,0x35,0x35,0x39, - 0x30,0x35,0x36,0x30,0x30,0x35,0x36,0x31, - 0x30,0x35,0x36,0x32,0x30,0x35,0x36,0x33, - 0x30,0x35,0x36,0x34,0x30,0x35,0x36,0x35, - 0x30,0x35,0x36,0x36,0x30,0x35,0x36,0x37, - 0x30,0x35,0x36,0x38,0x30,0x35,0x36,0x39, - 0x30,0x35,0x37,0x30,0x30,0x35,0x37,0x31, - 0x30,0x35,0x37,0x32,0x30,0x35,0x37,0x33, - 0x30,0x35,0x37,0x34,0x30,0x35,0x37,0x35, - 0x30,0x35,0x37,0x36,0x30,0x35,0x37,0x37, - 0x30,0x35,0x37,0x38,0x30,0x35,0x37,0x39, - 0x30,0x35,0x38,0x30,0x30,0x35,0x38,0x31, - 0x30,0x35,0x38,0x32,0x30,0x35,0x38,0x33, - 0x30,0x35,0x38,0x34,0x30,0x35,0x38,0x35, - 0x30,0x35,0x38,0x36,0x30,0x35,0x38,0x37, - 0x30,0x35,0x38,0x38,0x30,0x35,0x38,0x39, - 0x30,0x35,0x39,0x30,0x30,0x35,0x39,0x31, - 0x30,0x35,0x39,0x32,0x30,0x35,0x39,0x33, - 0x30,0x35,0x39,0x34,0x30,0x35,0x39,0x35, - 0x30,0x35,0x39,0x36,0x30,0x35,0x39,0x37, - 0x30,0x35,0x39,0x38,0x30,0x35,0x39,0x39, - 0x30,0x36,0x30,0x30,0x30,0x36,0x30,0x31, - 0x30,0x36,0x30,0x32,0x30,0x36,0x30,0x33, - 0x30,0x36,0x30,0x34,0x30,0x36,0x30,0x35, - 0x30,0x36,0x30,0x36,0x30,0x36,0x30,0x37, - 0x30,0x36,0x30,0x38,0x30,0x36,0x30,0x39, - 0x30,0x36,0x31,0x30,0x30,0x36,0x31,0x31, - 0x30,0x36,0x31,0x32,0x30,0x36,0x31,0x33, - 0x30,0x36,0x31,0x34,0x30,0x36,0x31,0x35, - 0x30,0x36,0x31,0x36,0x30,0x36,0x31,0x37, - 0x30,0x36,0x31,0x38,0x30,0x36,0x31,0x39, - 0x30,0x36,0x32,0x30,0x30,0x36,0x32,0x31, - 0x30,0x36,0x32,0x32,0x30,0x36,0x32,0x33, - 0x30,0x36,0x32,0x34,0x30,0x36,0x32,0x35, - 0x30,0x36,0x32,0x36,0x30,0x36,0x32,0x37, - 0x30,0x36,0x32,0x38,0x30,0x36,0x32,0x39, - 0x30,0x36,0x33,0x30,0x30,0x36,0x33,0x31, - 0x30,0x36,0x33,0x32,0x30,0x36,0x33,0x33, - 0x30,0x36,0x33,0x34,0x30,0x36,0x33,0x35, - 0x30,0x36,0x33,0x36,0x30,0x36,0x33,0x37, - 0x30,0x36,0x33,0x38,0x30,0x36,0x33,0x39, - 0x30,0x36,0x34,0x30,0x30,0x36,0x34,0x31, - 0x30,0x36,0x34,0x32,0x30,0x36,0x34,0x33, - 0x30,0x36,0x34,0x34,0x30,0x36,0x34,0x35, - 0x30,0x36,0x34,0x36,0x30,0x36,0x34,0x37, - 0x30,0x36,0x34,0x38,0x30,0x36,0x34,0x39, - 0x30,0x36,0x35,0x30,0x30,0x36,0x35,0x31, - 0x30,0x36,0x35,0x32,0x30,0x36,0x35,0x33, - 0x30,0x36,0x35,0x34,0x30,0x36,0x35,0x35, - 0x30,0x36,0x35,0x36,0x30,0x36,0x35,0x37, - 0x30,0x36,0x35,0x38,0x30,0x36,0x35,0x39, - 0x30,0x36,0x36,0x30,0x30,0x36,0x36,0x31, - 0x30,0x36,0x36,0x32,0x30,0x36,0x36,0x33, - 0x30,0x36,0x36,0x34,0x30,0x36,0x36,0x35, - 0x30,0x36,0x36,0x36,0x30,0x36,0x36,0x37, - 0x30,0x36,0x36,0x38,0x30,0x36,0x36,0x39, - 0x30,0x36,0x37,0x30,0x30,0x36,0x37,0x31, - 0x30,0x36,0x37,0x32,0x30,0x36,0x37,0x33, - 0x30,0x36,0x37,0x34,0x30,0x36,0x37,0x35, - 0x30,0x36,0x37,0x36,0x30,0x36,0x37,0x37, - 0x30,0x36,0x37,0x38,0x30,0x36,0x37,0x39, - 0x30,0x36,0x38,0x30,0x30,0x36,0x38,0x31, - 0x30,0x36,0x38,0x32,0x30,0x36,0x38,0x33, - 0x30,0x36,0x38,0x34,0x30,0x36,0x38,0x35, - 0x30,0x36,0x38,0x36,0x30,0x36,0x38,0x37, - 0x30,0x36,0x38,0x38,0x30,0x36,0x38,0x39, - 0x30,0x36,0x39,0x30,0x30,0x36,0x39,0x31, - 0x30,0x36,0x39,0x32,0x30,0x36,0x39,0x33, - 0x30,0x36,0x39,0x34,0x30,0x36,0x39,0x35, - 0x30,0x36,0x39,0x36,0x30,0x36,0x39,0x37, - 0x30,0x36,0x39,0x38,0x30,0x36,0x39,0x39, - 0x30,0x37,0x30,0x30,0x30,0x37,0x30,0x31, - 0x30,0x37,0x30,0x32,0x30,0x37,0x30,0x33, - 0x30,0x37,0x30,0x34,0x30,0x37,0x30,0x35, - 0x30,0x37,0x30,0x36,0x30,0x37,0x30,0x37, - 0x30,0x37,0x30,0x38,0x30,0x37,0x30,0x39, - 0x30,0x37,0x31,0x30,0x30,0x37,0x31,0x31, - 0x30,0x37,0x31,0x32,0x30,0x37,0x31,0x33, - 0x30,0x37,0x31,0x34,0x30,0x37,0x31,0x35, - 0x30,0x37,0x31,0x36,0x30,0x37,0x31,0x37, - 0x30,0x37,0x31,0x38,0x30,0x37,0x31,0x39, - 0x30,0x37,0x32,0x30,0x30,0x37,0x32,0x31, - 0x30,0x37,0x32,0x32,0x30,0x37,0x32,0x33, - 0x30,0x37,0x32,0x34,0x30,0x37,0x32,0x35, - 0x30,0x37,0x32,0x36,0x30,0x37,0x32,0x37, - 0x30,0x37,0x32,0x38,0x30,0x37,0x32,0x39, - 0x30,0x37,0x33,0x30,0x30,0x37,0x33,0x31, - 0x30,0x37,0x33,0x32,0x30,0x37,0x33,0x33, - 0x30,0x37,0x33,0x34,0x30,0x37,0x33,0x35, - 0x30,0x37,0x33,0x36,0x30,0x37,0x33,0x37, - 0x30,0x37,0x33,0x38,0x30,0x37,0x33,0x39, - 0x30,0x37,0x34,0x30,0x30,0x37,0x34,0x31, - 0x30,0x37,0x34,0x32,0x30,0x37,0x34,0x33, - 0x30,0x37,0x34,0x34,0x30,0x37,0x34,0x35, - 0x30,0x37,0x34,0x36,0x30,0x37,0x34,0x37, - 0x30,0x37,0x34,0x38,0x30,0x37,0x34,0x39, - 0x30,0x37,0x35,0x30,0x30,0x37,0x35,0x31, - 0x30,0x37,0x35,0x32,0x30,0x37,0x35,0x33, - 0x30,0x37,0x35,0x34,0x30,0x37,0x35,0x35, - 0x30,0x37,0x35,0x36,0x30,0x37,0x35,0x37, - 0x30,0x37,0x35,0x38,0x30,0x37,0x35,0x39, - 0x30,0x37,0x36,0x30,0x30,0x37,0x36,0x31, - 0x30,0x37,0x36,0x32,0x30,0x37,0x36,0x33, - 0x30,0x37,0x36,0x34,0x30,0x37,0x36,0x35, - 0x30,0x37,0x36,0x36,0x30,0x37,0x36,0x37, - 0x30,0x37,0x36,0x38,0x30,0x37,0x36,0x39, - 0x30,0x37,0x37,0x30,0x30,0x37,0x37,0x31, - 0x30,0x37,0x37,0x32,0x30,0x37,0x37,0x33, - 0x30,0x37,0x37,0x34,0x30,0x37,0x37,0x35, - 0x30,0x37,0x37,0x36,0x30,0x37,0x37,0x37, - 0x30,0x37,0x37,0x38,0x30,0x37,0x37,0x39, - 0x30,0x37,0x38,0x30,0x30,0x37,0x38,0x31, - 0x30,0x37,0x38,0x32,0x30,0x37,0x38,0x33, - 0x30,0x37,0x38,0x34,0x30,0x37,0x38,0x35, - 0x30,0x37,0x38,0x36,0x30,0x37,0x38,0x37, - 0x30,0x37,0x38,0x38,0x30,0x37,0x38,0x39, - 0x30,0x37,0x39,0x30,0x30,0x37,0x39,0x31, - 0x30,0x37,0x39,0x32,0x30,0x37,0x39,0x33, - 0x30,0x37,0x39,0x34,0x30,0x37,0x39,0x35, - 0x30,0x37,0x39,0x36,0x30,0x37,0x39,0x37, - 0x30,0x37,0x39,0x38,0x30,0x37,0x39,0x39, - 0x30,0x38,0x30,0x30,0x30,0x38,0x30,0x31, - 0x30,0x38,0x30,0x32,0x30,0x38,0x30,0x33, - 0x30,0x38,0x30,0x34,0x30,0x38,0x30,0x35, - 0x30,0x38,0x30,0x36,0x30,0x38,0x30,0x37, - 0x30,0x38,0x30,0x38,0x30,0x38,0x30,0x39, - 0x30,0x38,0x31,0x30,0x30,0x38,0x31,0x31, - 0x30,0x38,0x31,0x32,0x30,0x38,0x31,0x33, - 0x30,0x38,0x31,0x34,0x30,0x38,0x31,0x35, - 0x30,0x38,0x31,0x36,0x30,0x38,0x31,0x37, - 0x30,0x38,0x31,0x38,0x30,0x38,0x31,0x39, - 0x30,0x38,0x32,0x30,0x30,0x38,0x32,0x31, - 0x30,0x38,0x32,0x32,0x30,0x38,0x32,0x33, - 0x30,0x38,0x32,0x34,0x30,0x38,0x32,0x35, - 0x30,0x38,0x32,0x36,0x30,0x38,0x32,0x37, - 0x30,0x38,0x32,0x38,0x30,0x38,0x32,0x39, - 0x30,0x38,0x33,0x30,0x30,0x38,0x33,0x31, - 0x30,0x38,0x33,0x32,0x30,0x38,0x33,0x33, - 0x30,0x38,0x33,0x34,0x30,0x38,0x33,0x35, - 0x30,0x38,0x33,0x36,0x30,0x38,0x33,0x37, - 0x30,0x38,0x33,0x38,0x30,0x38,0x33,0x39, - 0x30,0x38,0x34,0x30,0x30,0x38,0x34,0x31, - 0x30,0x38,0x34,0x32,0x30,0x38,0x34,0x33, - 0x30,0x38,0x34,0x34,0x30,0x38,0x34,0x35, - 0x30,0x38,0x34,0x36,0x30,0x38,0x34,0x37, - 0x30,0x38,0x34,0x38,0x30,0x38,0x34,0x39, - 0x30,0x38,0x35,0x30,0x30,0x38,0x35,0x31, - 0x30,0x38,0x35,0x32,0x30,0x38,0x35,0x33, - 0x30,0x38,0x35,0x34,0x30,0x38,0x35,0x35, - 0x30,0x38,0x35,0x36,0x30,0x38,0x35,0x37, - 0x30,0x38,0x35,0x38,0x30,0x38,0x35,0x39, - 0x30,0x38,0x36,0x30,0x30,0x38,0x36,0x31, - 0x30,0x38,0x36,0x32,0x30,0x38,0x36,0x33, - 0x30,0x38,0x36,0x34,0x30,0x38,0x36,0x35, - 0x30,0x38,0x36,0x36,0x30,0x38,0x36,0x37, - 0x30,0x38,0x36,0x38,0x30,0x38,0x36,0x39, - 0x30,0x38,0x37,0x30,0x30,0x38,0x37,0x31, - 0x30,0x38,0x37,0x32,0x30,0x38,0x37,0x33, - 0x30,0x38,0x37,0x34,0x30,0x38,0x37,0x35, - 0x30,0x38,0x37,0x36,0x30,0x38,0x37,0x37, - 0x30,0x38,0x37,0x38,0x30,0x38,0x37,0x39, - 0x30,0x38,0x38,0x30,0x30,0x38,0x38,0x31, - 0x30,0x38,0x38,0x32,0x30,0x38,0x38,0x33, - 0x30,0x38,0x38,0x34,0x30,0x38,0x38,0x35, - 0x30,0x38,0x38,0x36,0x30,0x38,0x38,0x37, - 0x30,0x38,0x38,0x38,0x30,0x38,0x38,0x39, - 0x30,0x38,0x39,0x30,0x30,0x38,0x39,0x31, - 0x30,0x38,0x39,0x32,0x30,0x38,0x39,0x33, - 0x30,0x38,0x39,0x34,0x30,0x38,0x39,0x35, - 0x30,0x38,0x39,0x36,0x30,0x38,0x39,0x37, - 0x30,0x38,0x39,0x38,0x30,0x38,0x39,0x39, - 0x30,0x39,0x30,0x30,0x30,0x39,0x30,0x31, - 0x30,0x39,0x30,0x32,0x30,0x39,0x30,0x33, - 0x30,0x39,0x30,0x34,0x30,0x39,0x30,0x35, - 0x30,0x39,0x30,0x36,0x30,0x39,0x30,0x37, - 0x30,0x39,0x30,0x38,0x30,0x39,0x30,0x39, - 0x30,0x39,0x31,0x30,0x30,0x39,0x31,0x31, - 0x30,0x39,0x31,0x32,0x30,0x39,0x31,0x33, - 0x30,0x39,0x31,0x34,0x30,0x39,0x31,0x35, - 0x30,0x39,0x31,0x36,0x30,0x39,0x31,0x37, - 0x30,0x39,0x31,0x38,0x30,0x39,0x31,0x39, - 0x30,0x39,0x32,0x30,0x30,0x39,0x32,0x31, - 0x30,0x39,0x32,0x32,0x30,0x39,0x32,0x33, - 0x30,0x39,0x32,0x34,0x30,0x39,0x32,0x35, - 0x30,0x39,0x32,0x36,0x30,0x39,0x32,0x37, - 0x30,0x39,0x32,0x38,0x30,0x39,0x32,0x39, - 0x30,0x39,0x33,0x30,0x30,0x39,0x33,0x31, - 0x30,0x39,0x33,0x32,0x30,0x39,0x33,0x33, - 0x30,0x39,0x33,0x34,0x30,0x39,0x33,0x35, - 0x30,0x39,0x33,0x36,0x30,0x39,0x33,0x37, - 0x30,0x39,0x33,0x38,0x30,0x39,0x33,0x39, - 0x30,0x39,0x34,0x30,0x30,0x39,0x34,0x31, - 0x30,0x39,0x34,0x32,0x30,0x39,0x34,0x33, - 0x30,0x39,0x34,0x34,0x30,0x39,0x34,0x35, - 0x30,0x39,0x34,0x36,0x30,0x39,0x34,0x37, - 0x30,0x39,0x34,0x38,0x30,0x39,0x34,0x39, - 0x30,0x39,0x35,0x30,0x30,0x39,0x35,0x31, - 0x30,0x39,0x35,0x32,0x30,0x39,0x35,0x33, - 0x30,0x39,0x35,0x34,0x30,0x39,0x35,0x35, - 0x30,0x39,0x35,0x36,0x30,0x39,0x35,0x37, - 0x30,0x39,0x35,0x38,0x30,0x39,0x35,0x39, - 0x30,0x39,0x36,0x30,0x30,0x39,0x36,0x31, - 0x30,0x39,0x36,0x32,0x30,0x39,0x36,0x33, - 0x30,0x39,0x36,0x34,0x30,0x39,0x36,0x35, - 0x30,0x39,0x36,0x36,0x30,0x39,0x36,0x37, - 0x30,0x39,0x36,0x38,0x30,0x39,0x36,0x39, - 0x30,0x39,0x37,0x30,0x30,0x39,0x37,0x31, - 0x30,0x39,0x37,0x32,0x30,0x39,0x37,0x33, - 0x30,0x39,0x37,0x34,0x30,0x39,0x37,0x35, - 0x30,0x39,0x37,0x36,0x30,0x39,0x37,0x37, - 0x30,0x39,0x37,0x38,0x30,0x39,0x37,0x39, - 0x30,0x39,0x38,0x30,0x30,0x39,0x38,0x31, - 0x30,0x39,0x38,0x32,0x30,0x39,0x38,0x33, - 0x30,0x39,0x38,0x34,0x30,0x39,0x38,0x35, - 0x30,0x39,0x38,0x36,0x30,0x39,0x38,0x37, - 0x30,0x39,0x38,0x38,0x30,0x39,0x38,0x39, - 0x30,0x39,0x39,0x30,0x30,0x39,0x39,0x31, - 0x30,0x39,0x39,0x32,0x30,0x39,0x39,0x33, - 0x30,0x39,0x39,0x34,0x30,0x39,0x39,0x35, - 0x30,0x39,0x39,0x36,0x30,0x39,0x39,0x37, - 0x30,0x39,0x39,0x38,0x30,0x39,0x39,0x39, - 0x31,0x30,0x30,0x30,0x31,0x30,0x30,0x31, - 0x31,0x30,0x30,0x32,0x31,0x30,0x30,0x33, - 0x31,0x30,0x30,0x34,0x31,0x30,0x30,0x35, - 0x31,0x30,0x30,0x36,0x31,0x30,0x30,0x37, - 0x31,0x30,0x30,0x38,0x31,0x30,0x30,0x39, - 0x31,0x30,0x31,0x30,0x31,0x30,0x31,0x31, - 0x31,0x30,0x31,0x32,0x31,0x30,0x31,0x33, - 0x31,0x30,0x31,0x34,0x31,0x30,0x31,0x35, - 0x31,0x30,0x31,0x36,0x31,0x30,0x31,0x37, - 0x31,0x30,0x31,0x38,0x31,0x30,0x31,0x39, - 0x31,0x30,0x32,0x30,0x31,0x30,0x32,0x31, - 0x31,0x30,0x32,0x32,0x31,0x30,0x32,0x33, - 0x31,0x30,0x32,0x34,0x31,0x30,0x32,0x35, - 0x31,0x30,0x32,0x36,0x31,0x30,0x32,0x37, - 0x31,0x30,0x32,0x38,0x31,0x30,0x32,0x39, - 0x31,0x30,0x33,0x30,0x31,0x30,0x33,0x31, - 0x31,0x30,0x33,0x32,0x31,0x30,0x33,0x33, - 0x31,0x30,0x33,0x34,0x31,0x30,0x33,0x35, - 0x31,0x30,0x33,0x36,0x31,0x30,0x33,0x37, - 0x31,0x30,0x33,0x38,0x31,0x30,0x33,0x39, - 0x31,0x30,0x34,0x30,0x31,0x30,0x34,0x31, - 0x31,0x30,0x34,0x32,0x31,0x30,0x34,0x33, - 0x31,0x30,0x34,0x34,0x31,0x30,0x34,0x35, - 0x31,0x30,0x34,0x36,0x31,0x30,0x34,0x37, - 0x31,0x30,0x34,0x38,0x31,0x30,0x34,0x39, - 0x31,0x30,0x35,0x30,0x31,0x30,0x35,0x31, - 0x31,0x30,0x35,0x32,0x31,0x30,0x35,0x33, - 0x31,0x30,0x35,0x34,0x31,0x30,0x35,0x35, - 0x31,0x30,0x35,0x36,0x31,0x30,0x35,0x37, - 0x31,0x30,0x35,0x38,0x31,0x30,0x35,0x39, - 0x31,0x30,0x36,0x30,0x31,0x30,0x36,0x31, - 0x31,0x30,0x36,0x32,0x31,0x30,0x36,0x33, - 0x31,0x30,0x36,0x34,0x31,0x30,0x36,0x35, - 0x31,0x30,0x36,0x36,0x31,0x30,0x36,0x37, - 0x31,0x30,0x36,0x38,0x31,0x30,0x36,0x39, - 0x31,0x30,0x37,0x30,0x31,0x30,0x37,0x31, - 0x31,0x30,0x37,0x32,0x31,0x30,0x37,0x33, - 0x31,0x30,0x37,0x34,0x31,0x30,0x37,0x35, - 0x31,0x30,0x37,0x36,0x31,0x30,0x37,0x37, - 0x31,0x30,0x37,0x38,0x31,0x30,0x37,0x39, - 0x31,0x30,0x38,0x30,0x31,0x30,0x38,0x31, - 0x31,0x30,0x38,0x32,0x31,0x30,0x38,0x33, - 0x31,0x30,0x38,0x34,0x31,0x30,0x38,0x35, - 0x31,0x30,0x38,0x36,0x31,0x30,0x38,0x37, - 0x31,0x30,0x38,0x38,0x31,0x30,0x38,0x39, - 0x31,0x30,0x39,0x30,0x31,0x30,0x39,0x31, - 0x31,0x30,0x39,0x32,0x31,0x30,0x39,0x33, - 0x31,0x30,0x39,0x34,0x31,0x30,0x39,0x35, - 0x31,0x30,0x39,0x36,0x31,0x30,0x39,0x37, - 0x31,0x30,0x39,0x38,0x31,0x30,0x39,0x39, - 0x31,0x31,0x30,0x30,0x31,0x31,0x30,0x31, - 0x31,0x31,0x30,0x32,0x31,0x31,0x30,0x33, - 0x31,0x31,0x30,0x34,0x31,0x31,0x30,0x35, - 0x31,0x31,0x30,0x36,0x31,0x31,0x30,0x37, - 0x31,0x31,0x30,0x38,0x31,0x31,0x30,0x39, - 0x31,0x31,0x31,0x30,0x31,0x31,0x31,0x31, - 0x31,0x31,0x31,0x32,0x31,0x31,0x31,0x33, - 0x31,0x31,0x31,0x34,0x31,0x31,0x31,0x35, - 0x31,0x31,0x31,0x36,0x31,0x31,0x31,0x37, - 0x31,0x31,0x31,0x38,0x31,0x31,0x31,0x39, - 0x31,0x31,0x32,0x30,0x31,0x31,0x32,0x31, - 0x31,0x31,0x32,0x32,0x31,0x31,0x32,0x33, - 0x31,0x31,0x32,0x34,0x31,0x31,0x32,0x35, - 0x31,0x31,0x32,0x36,0x31,0x31,0x32,0x37, - 0x31,0x31,0x32,0x38,0x31,0x31,0x32,0x39, - 0x31,0x31,0x33,0x30,0x31,0x31,0x33,0x31, - 0x31,0x31,0x33,0x32,0x31,0x31,0x33,0x33, - 0x31,0x31,0x33,0x34,0x31,0x31,0x33,0x35, - 0x31,0x31,0x33,0x36,0x31,0x31,0x33,0x37, - 0x31,0x31,0x33,0x38,0x31,0x31,0x33,0x39, - 0x31,0x31,0x34,0x30,0x31,0x31,0x34,0x31, - 0x31,0x31,0x34,0x32,0x31,0x31,0x34,0x33, - 0x31,0x31,0x34,0x34,0x31,0x31,0x34,0x35, - 0x31,0x31,0x34,0x36,0x31,0x31,0x34,0x37, - 0x31,0x31,0x34,0x38,0x31,0x31,0x34,0x39, - 0x31,0x31,0x35,0x30,0x31,0x31,0x35,0x31, - 0x31,0x31,0x35,0x32,0x31,0x31,0x35,0x33, - 0x31,0x31,0x35,0x34,0x31,0x31,0x35,0x35, - 0x31,0x31,0x35,0x36,0x31,0x31,0x35,0x37, - 0x31,0x31,0x35,0x38,0x31,0x31,0x35,0x39, - 0x31,0x31,0x36,0x30,0x31,0x31,0x36,0x31, - 0x31,0x31,0x36,0x32,0x31,0x31,0x36,0x33, - 0x31,0x31,0x36,0x34,0x31,0x31,0x36,0x35, - 0x31,0x31,0x36,0x36,0x31,0x31,0x36,0x37, - 0x31,0x31,0x36,0x38,0x31,0x31,0x36,0x39, - 0x31,0x31,0x37,0x30,0x31,0x31,0x37,0x31, - 0x31,0x31,0x37,0x32,0x31,0x31,0x37,0x33, - 0x31,0x31,0x37,0x34,0x31,0x31,0x37,0x35, - 0x31,0x31,0x37,0x36,0x31,0x31,0x37,0x37, - 0x31,0x31,0x37,0x38,0x31,0x31,0x37,0x39, - 0x31,0x31,0x38,0x30,0x31,0x31,0x38,0x31, - 0x31,0x31,0x38,0x32,0x31,0x31,0x38,0x33, - 0x31,0x31,0x38,0x34,0x31,0x31,0x38,0x35, - 0x31,0x31,0x38,0x36,0x31,0x31,0x38,0x37, - 0x31,0x31,0x38,0x38,0x31,0x31,0x38,0x39, - 0x31,0x31,0x39,0x30,0x31,0x31,0x39,0x31, - 0x31,0x31,0x39,0x32,0x31,0x31,0x39,0x33, - 0x31,0x31,0x39,0x34,0x31,0x31,0x39,0x35, - 0x31,0x31,0x39,0x36,0x31,0x31,0x39,0x37, - 0x31,0x31,0x39,0x38,0x31,0x31,0x39,0x39, - 0x31,0x32,0x30,0x30,0x31,0x32,0x30,0x31, - 0x31,0x32,0x30,0x32,0x31,0x32,0x30,0x33, - 0x31,0x32,0x30,0x34,0x31,0x32,0x30,0x35, - 0x31,0x32,0x30,0x36,0x31,0x32,0x30,0x37, - 0x31,0x32,0x30,0x38,0x31,0x32,0x30,0x39, - 0x31,0x32,0x31,0x30,0x31,0x32,0x31,0x31, - 0x31,0x32,0x31,0x32,0x31,0x32,0x31,0x33, - 0x31,0x32,0x31,0x34,0x31,0x32,0x31,0x35, - 0x31,0x32,0x31,0x36,0x31,0x32,0x31,0x37, - 0x31,0x32,0x31,0x38,0x31,0x32,0x31,0x39, - 0x31,0x32,0x32,0x30,0x31,0x32,0x32,0x31, - 0x31,0x32,0x32,0x32,0x31,0x32,0x32,0x33, - 0x31,0x32,0x32,0x34,0x31,0x32,0x32,0x35, - 0x31,0x32,0x32,0x36,0x31,0x32,0x32,0x37, - 0x31,0x32,0x32,0x38,0x31,0x32,0x32,0x39, - 0x31,0x32,0x33,0x30,0x31,0x32,0x33,0x31, - 0x31,0x32,0x33,0x32,0x31,0x32,0x33,0x33, - 0x31,0x32,0x33,0x34,0x31,0x32,0x33,0x35, - 0x31,0x32,0x33,0x36,0x31,0x32,0x33,0x37, - 0x31,0x32,0x33,0x38,0x31,0x32,0x33,0x39, - 0x31,0x32,0x34,0x30,0x31,0x32,0x34,0x31, - 0x31,0x32,0x34,0x32,0x31,0x32,0x34,0x33, - 0x31,0x32,0x34,0x34,0x31,0x32,0x34,0x35, - 0x31,0x32,0x34,0x36,0x31,0x32,0x34,0x37, - 0x31,0x32,0x34,0x38,0x31,0x32,0x34,0x39, - 0x31,0x32,0x35,0x30,0x31,0x32,0x35,0x31, - 0x31,0x32,0x35,0x32,0x31,0x32,0x35,0x33, - 0x31,0x32,0x35,0x34,0x31,0x32,0x35,0x35, - 0x31,0x32,0x35,0x36,0x31,0x32,0x35,0x37, - 0x31,0x32,0x35,0x38,0x31,0x32,0x35,0x39, - 0x31,0x32,0x36,0x30,0x31,0x32,0x36,0x31, - 0x31,0x32,0x36,0x32,0x31,0x32,0x36,0x33, - 0x31,0x32,0x36,0x34,0x31,0x32,0x36,0x35, - 0x31,0x32,0x36,0x36,0x31,0x32,0x36,0x37, - 0x31,0x32,0x36,0x38,0x31,0x32,0x36,0x39, - 0x31,0x32,0x37,0x30,0x31,0x32,0x37,0x31, - 0x31,0x32,0x37,0x32,0x31,0x32,0x37,0x33, - 0x31,0x32,0x37,0x34,0x31,0x32,0x37,0x35, - 0x31,0x32,0x37,0x36,0x31,0x32,0x37,0x37, - 0x31,0x32,0x37,0x38,0x31,0x32,0x37,0x39, - 0x31,0x32,0x38,0x30,0x31,0x32,0x38,0x31, - 0x31,0x32,0x38,0x32,0x31,0x32,0x38,0x33, - 0x31,0x32,0x38,0x34,0x31,0x32,0x38,0x35, - 0x31,0x32,0x38,0x36,0x31,0x32,0x38,0x37, - 0x31,0x32,0x38,0x38,0x31,0x32,0x38,0x39, - 0x31,0x32,0x39,0x30,0x31,0x32,0x39,0x31, - 0x31,0x32,0x39,0x32,0x31,0x32,0x39,0x33, - 0x31,0x32,0x39,0x34,0x31,0x32,0x39,0x35, - 0x31,0x32,0x39,0x36,0x31,0x32,0x39,0x37, - 0x31,0x32,0x39,0x38,0x31,0x32,0x39,0x39, - 0x31,0x33,0x30,0x30,0x31,0x33,0x30,0x31, - 0x31,0x33,0x30,0x32,0x31,0x33,0x30,0x33, - 0x31,0x33,0x30,0x34,0x31,0x33,0x30,0x35, - 0x31,0x33,0x30,0x36,0x31,0x33,0x30,0x37, - 0x31,0x33,0x30,0x38,0x31,0x33,0x30,0x39, - 0x31,0x33,0x31,0x30,0x31,0x33,0x31,0x31, - 0x31,0x33,0x31,0x32,0x31,0x33,0x31,0x33, - 0x31,0x33,0x31,0x34,0x31,0x33,0x31,0x35, - 0x31,0x33,0x31,0x36,0x31,0x33,0x31,0x37, - 0x31,0x33,0x31,0x38,0x31,0x33,0x31,0x39, - 0x31,0x33,0x32,0x30,0x31,0x33,0x32,0x31, - 0x31,0x33,0x32,0x32,0x31,0x33,0x32,0x33, - 0x31,0x33,0x32,0x34,0x31,0x33,0x32,0x35, - 0x31,0x33,0x32,0x36,0x31,0x33,0x32,0x37, - 0x31,0x33,0x32,0x38,0x31,0x33,0x32,0x39, - 0x31,0x33,0x33,0x30,0x31,0x33,0x33,0x31, - 0x31,0x33,0x33,0x32,0x31,0x33,0x33,0x33, - 0x31,0x33,0x33,0x34,0x31,0x33,0x33,0x35, - 0x31,0x33,0x33,0x36,0x31,0x33,0x33,0x37, - 0x31,0x33,0x33,0x38,0x31,0x33,0x33,0x39, - 0x31,0x33,0x34,0x30,0x31,0x33,0x34,0x31, - 0x31,0x33,0x34,0x32,0x31,0x33,0x34,0x33, - 0x31,0x33,0x34,0x34,0x31,0x33,0x34,0x35, - 0x31,0x33,0x34,0x36,0x31,0x33,0x34,0x37, - 0x31,0x33,0x34,0x38,0x31,0x33,0x34,0x39, - 0x31,0x33,0x35,0x30,0x31,0x33,0x35,0x31, - 0x31,0x33,0x35,0x32,0x31,0x33,0x35,0x33, - 0x31,0x33,0x35,0x34,0x31,0x33,0x35,0x35, - 0x31,0x33,0x35,0x36,0x31,0x33,0x35,0x37, - 0x31,0x33,0x35,0x38,0x31,0x33,0x35,0x39, - 0x31,0x33,0x36,0x30,0x31,0x33,0x36,0x31, - 0x31,0x33,0x36,0x32,0x31,0x33,0x36,0x33, - 0x31,0x33,0x36,0x34,0x31,0x33,0x36,0x35, - 0x31,0x33,0x36,0x36,0x31,0x33,0x36,0x37, - 0x31,0x33,0x36,0x38,0x31,0x33,0x36,0x39, - 0x31,0x33,0x37,0x30,0x31,0x33,0x37,0x31, - 0x31,0x33,0x37,0x32,0x31,0x33,0x37,0x33, - 0x31,0x33,0x37,0x34,0x31,0x33,0x37,0x35, - 0x31,0x33,0x37,0x36,0x31,0x33,0x37,0x37, - 0x31,0x33,0x37,0x38,0x31,0x33,0x37,0x39, - 0x31,0x33,0x38,0x30,0x31,0x33,0x38,0x31, - 0x31,0x33,0x38,0x32,0x31,0x33,0x38,0x33, - 0x31,0x33,0x38,0x34,0x31,0x33,0x38,0x35, - 0x31,0x33,0x38,0x36,0x31,0x33,0x38,0x37, - 0x31,0x33,0x38,0x38,0x31,0x33,0x38,0x39, - 0x31,0x33,0x39,0x30,0x31,0x33,0x39,0x31, - 0x31,0x33,0x39,0x32,0x31,0x33,0x39,0x33, - 0x31,0x33,0x39,0x34,0x31,0x33,0x39,0x35, - 0x31,0x33,0x39,0x36,0x31,0x33,0x39,0x37, - 0x31,0x33,0x39,0x38,0x31,0x33,0x39,0x39, - 0x31,0x34,0x30,0x30,0x31,0x34,0x30,0x31, - 0x31,0x34,0x30,0x32,0x31,0x34,0x30,0x33, - 0x31,0x34,0x30,0x34,0x31,0x34,0x30,0x35, - 0x31,0x34,0x30,0x36,0x31,0x34,0x30,0x37, - 0x31,0x34,0x30,0x38,0x31,0x34,0x30,0x39, - 0x31,0x34,0x31,0x30,0x31,0x34,0x31,0x31, - 0x31,0x34,0x31,0x32,0x31,0x34,0x31,0x33, - 0x31,0x34,0x31,0x34,0x31,0x34,0x31,0x35, - 0x31,0x34,0x31,0x36,0x31,0x34,0x31,0x37, - 0x31,0x34,0x31,0x38,0x31,0x34,0x31,0x39, - 0x31,0x34,0x32,0x30,0x31,0x34,0x32,0x31, - 0x31,0x34,0x32,0x32,0x31,0x34,0x32,0x33, - 0x31,0x34,0x32,0x34,0x31,0x34,0x32,0x35, - 0x31,0x34,0x32,0x36,0x31,0x34,0x32,0x37, - 0x31,0x34,0x32,0x38,0x31,0x34,0x32,0x39, - 0x31,0x34,0x33,0x30,0x31,0x34,0x33,0x31, - 0x31,0x34,0x33,0x32,0x31,0x34,0x33,0x33, - 0x31,0x34,0x33,0x34,0x31,0x34,0x33,0x35, - 0x31,0x34,0x33,0x36,0x31,0x34,0x33,0x37, - 0x31,0x34,0x33,0x38,0x31,0x34,0x33,0x39, - 0x31,0x34,0x34,0x30,0x31,0x34,0x34,0x31, - 0x31,0x34,0x34,0x32,0x31,0x34,0x34,0x33, - 0x31,0x34,0x34,0x34,0x31,0x34,0x34,0x35, - 0x31,0x34,0x34,0x36,0x31,0x34,0x34,0x37, - 0x31,0x34,0x34,0x38,0x31,0x34,0x34,0x39, - 0x31,0x34,0x35,0x30,0x31,0x34,0x35,0x31, - 0x31,0x34,0x35,0x32,0x31,0x34,0x35,0x33, - 0x31,0x34,0x35,0x34,0x31,0x34,0x35,0x35, - 0x31,0x34,0x35,0x36,0x31,0x34,0x35,0x37, - 0x31,0x34,0x35,0x38,0x31,0x34,0x35,0x39, - 0x31,0x34,0x36,0x30,0x31,0x34,0x36,0x31, - 0x31,0x34,0x36,0x32,0x31,0x34,0x36,0x33, - 0x31,0x34,0x36,0x34,0x31,0x34,0x36,0x35, - 0x31,0x34,0x36,0x36,0x31,0x34,0x36,0x37, - 0x31,0x34,0x36,0x38,0x31,0x34,0x36,0x39, - 0x31,0x34,0x37,0x30,0x31,0x34,0x37,0x31, - 0x31,0x34,0x37,0x32,0x31,0x34,0x37,0x33, - 0x31,0x34,0x37,0x34,0x31,0x34,0x37,0x35, - 0x31,0x34,0x37,0x36,0x31,0x34,0x37,0x37, - 0x31,0x34,0x37,0x38,0x31,0x34,0x37,0x39, - 0x31,0x34,0x38,0x30,0x31,0x34,0x38,0x31, - 0x31,0x34,0x38,0x32,0x31,0x34,0x38,0x33, - 0x31,0x34,0x38,0x34,0x31,0x34,0x38,0x35, - 0x31,0x34,0x38,0x36,0x31,0x34,0x38,0x37, - 0x31,0x34,0x38,0x38,0x31,0x34,0x38,0x39, - 0x31,0x34,0x39,0x30,0x31,0x34,0x39,0x31, - 0x31,0x34,0x39,0x32,0x31,0x34,0x39,0x33, - 0x31,0x34,0x39,0x34,0x31,0x34,0x39,0x35, - 0x31,0x34,0x39,0x36,0x31,0x34,0x39,0x37, - 0x31,0x34,0x39,0x38,0x31,0x34,0x39,0x39, - 0x31,0x35,0x30,0x30,0x31,0x35,0x30,0x31, - 0x31,0x35,0x30,0x32,0x31,0x35,0x30,0x33, - 0x31,0x35,0x30,0x34,0x31,0x35,0x30,0x35, - 0x31,0x35,0x30,0x36,0x31,0x35,0x30,0x37, - 0x31,0x35,0x30,0x38,0x31,0x35,0x30,0x39, - 0x31,0x35,0x31,0x30,0x31,0x35,0x31,0x31, - 0x31,0x35,0x31,0x32,0x31,0x35,0x31,0x33, - 0x31,0x35,0x31,0x34,0x31,0x35,0x31,0x35, - 0x31,0x35,0x31,0x36,0x31,0x35,0x31,0x37, - 0x31,0x35,0x31,0x38,0x31,0x35,0x31,0x39, - 0x31,0x35,0x32,0x30,0x31,0x35,0x32,0x31, - 0x31,0x35,0x32,0x32,0x31,0x35,0x32,0x33, - 0x31,0x35,0x32,0x34,0x31,0x35,0x32,0x35, - 0x31,0x35,0x32,0x36,0x31,0x35,0x32,0x37, - 0x31,0x35,0x32,0x38,0x31,0x35,0x32,0x39, - 0x31,0x35,0x33,0x30,0x31,0x35,0x33,0x31, - 0x31,0x35,0x33,0x32,0x31,0x35,0x33,0x33, - 0x31,0x35,0x33,0x34,0x31,0x35,0x33,0x35, - 0x31,0x35,0x33,0x36,0x31,0x35,0x33,0x37, - 0x31,0x35,0x33,0x38,0x31,0x35,0x33,0x39, - 0x31,0x35,0x34,0x30,0x31,0x35,0x34,0x31, - 0x31,0x35,0x34,0x32,0x31,0x35,0x34,0x33, - 0x31,0x35,0x34,0x34,0x31,0x35,0x34,0x35, - 0x31,0x35,0x34,0x36,0x31,0x35,0x34,0x37, - 0x31,0x35,0x34,0x38,0x31,0x35,0x34,0x39, - 0x31,0x35,0x35,0x30,0x31,0x35,0x35,0x31, - 0x31,0x35,0x35,0x32,0x31,0x35,0x35,0x33, - 0x31,0x35,0x35,0x34,0x31,0x35,0x35,0x35, - 0x31,0x35,0x35,0x36,0x31,0x35,0x35,0x37, - 0x31,0x35,0x35,0x38,0x31,0x35,0x35,0x39, - 0x31,0x35,0x36,0x30,0x31,0x35,0x36,0x31, - 0x31,0x35,0x36,0x32,0x31,0x35,0x36,0x33, - 0x31,0x35,0x36,0x34,0x31,0x35,0x36,0x35, - 0x31,0x35,0x36,0x36,0x31,0x35,0x36,0x37, - 0x31,0x35,0x36,0x38,0x31,0x35,0x36,0x39, - 0x31,0x35,0x37,0x30,0x31,0x35,0x37,0x31, - 0x31,0x35,0x37,0x32,0x31,0x35,0x37,0x33, - 0x31,0x35,0x37,0x34,0x31,0x35,0x37,0x35, - 0x31,0x35,0x37,0x36,0x31,0x35,0x37,0x37, - 0x31,0x35,0x37,0x38,0x31,0x35,0x37,0x39, - 0x31,0x35,0x38,0x30,0x31,0x35,0x38,0x31, - 0x31,0x35,0x38,0x32,0x31,0x35,0x38,0x33, - 0x31,0x35,0x38,0x34,0x31,0x35,0x38,0x35, - 0x31,0x35,0x38,0x36,0x31,0x35,0x38,0x37, - 0x31,0x35,0x38,0x38,0x31,0x35,0x38,0x39, - 0x31,0x35,0x39,0x30,0x31,0x35,0x39,0x31, - 0x31,0x35,0x39,0x32,0x31,0x35,0x39,0x33, - 0x31,0x35,0x39,0x34,0x31,0x35,0x39,0x35, - 0x31,0x35,0x39,0x36,0x31,0x35,0x39,0x37, - 0x31,0x35,0x39,0x38,0x31,0x35,0x39,0x39, - 0x31,0x36,0x30,0x30,0x31,0x36,0x30,0x31, - 0x31,0x36,0x30,0x32,0x31,0x36,0x30,0x33, - 0x31,0x36,0x30,0x34,0x31,0x36,0x30,0x35, - 0x31,0x36,0x30,0x36,0x31,0x36,0x30,0x37, - 0x31,0x36,0x30,0x38,0x31,0x36,0x30,0x39, - 0x31,0x36,0x31,0x30,0x31,0x36,0x31,0x31, - 0x31,0x36,0x31,0x32,0x31,0x36,0x31,0x33, - 0x31,0x36,0x31,0x34,0x31,0x36,0x31,0x35, - 0x31,0x36,0x31,0x36,0x31,0x36,0x31,0x37, - 0x31,0x36,0x31,0x38,0x31,0x36,0x31,0x39, - 0x31,0x36,0x32,0x30,0x31,0x36,0x32,0x31, - 0x31,0x36,0x32,0x32,0x31,0x36,0x32,0x33, - 0x31,0x36,0x32,0x34,0x31,0x36,0x32,0x35, - 0x31,0x36,0x32,0x36,0x31,0x36,0x32,0x37, - 0x31,0x36,0x32,0x38,0x31,0x36,0x32,0x39, - 0x31,0x36,0x33,0x30,0x31,0x36,0x33,0x31, - 0x31,0x36,0x33,0x32,0x31,0x36,0x33,0x33, - 0x31,0x36,0x33,0x34,0x31,0x36,0x33,0x35, - 0x31,0x36,0x33,0x36,0x31,0x36,0x33,0x37, - 0x31,0x36,0x33,0x38,0x31,0x36,0x33,0x39, - 0x31,0x36,0x34,0x30,0x31,0x36,0x34,0x31, - 0x31,0x36,0x34,0x32,0x31,0x36,0x34,0x33, - 0x31,0x36,0x34,0x34,0x31,0x36,0x34,0x35, - 0x31,0x36,0x34,0x36,0x31,0x36,0x34,0x37, - 0x31,0x36,0x34,0x38,0x31,0x36,0x34,0x39, - 0x31,0x36,0x35,0x30,0x31,0x36,0x35,0x31, - 0x31,0x36,0x35,0x32,0x31,0x36,0x35,0x33, - 0x31,0x36,0x35,0x34,0x31,0x36,0x35,0x35, - 0x31,0x36,0x35,0x36,0x31,0x36,0x35,0x37, - 0x31,0x36,0x35,0x38,0x31,0x36,0x35,0x39, - 0x31,0x36,0x36,0x30,0x31,0x36,0x36,0x31, - 0x31,0x36,0x36,0x32,0x31,0x36,0x36,0x33, - 0x31,0x36,0x36,0x34,0x31,0x36,0x36,0x35, - 0x31,0x36,0x36,0x36,0x31,0x36,0x36,0x37, - 0x31,0x36,0x36,0x38,0x31,0x36,0x36,0x39, - 0x31,0x36,0x37,0x30,0x31,0x36,0x37,0x31, - 0x31,0x36,0x37,0x32,0x31,0x36,0x37,0x33, - 0x31,0x36,0x37,0x34,0x31,0x36,0x37,0x35, - 0x31,0x36,0x37,0x36,0x31,0x36,0x37,0x37, - 0x31,0x36,0x37,0x38,0x31,0x36,0x37,0x39, - 0x31,0x36,0x38,0x30,0x31,0x36,0x38,0x31, - 0x31,0x36,0x38,0x32,0x31,0x36,0x38,0x33, - 0x31,0x36,0x38,0x34,0x31,0x36,0x38,0x35, - 0x31,0x36,0x38,0x36,0x31,0x36,0x38,0x37, - 0x31,0x36,0x38,0x38,0x31,0x36,0x38,0x39, - 0x31,0x36,0x39,0x30,0x31,0x36,0x39,0x31, - 0x31,0x36,0x39,0x32,0x31,0x36,0x39,0x33, - 0x31,0x36,0x39,0x34,0x31,0x36,0x39,0x35, - 0x31,0x36,0x39,0x36,0x31,0x36,0x39,0x37, - 0x31,0x36,0x39,0x38,0x31,0x36,0x39,0x39, - 0x31,0x37,0x30,0x30,0x31,0x37,0x30,0x31, - 0x31,0x37,0x30,0x32,0x31,0x37,0x30,0x33, - 0x31,0x37,0x30,0x34,0x31,0x37,0x30,0x35, - 0x31,0x37,0x30,0x36,0x31,0x37,0x30,0x37, - 0x31,0x37,0x30,0x38,0x31,0x37,0x30,0x39, - 0x31,0x37,0x31,0x30,0x31,0x37,0x31,0x31, - 0x31,0x37,0x31,0x32,0x31,0x37,0x31,0x33, - 0x31,0x37,0x31,0x34,0x31,0x37,0x31,0x35, - 0x31,0x37,0x31,0x36,0x31,0x37,0x31,0x37, - 0x31,0x37,0x31,0x38,0x31,0x37,0x31,0x39, - 0x31,0x37,0x32,0x30,0x31,0x37,0x32,0x31, - 0x31,0x37,0x32,0x32,0x31,0x37,0x32,0x33, - 0x31,0x37,0x32,0x34,0x31,0x37,0x32,0x35, - 0x31,0x37,0x32,0x36,0x31,0x37,0x32,0x37, - 0x31,0x37,0x32,0x38,0x31,0x37,0x32,0x39, - 0x31,0x37,0x33,0x30,0x31,0x37,0x33,0x31, - 0x31,0x37,0x33,0x32,0x31,0x37,0x33,0x33, - 0x31,0x37,0x33,0x34,0x31,0x37,0x33,0x35, - 0x31,0x37,0x33,0x36,0x31,0x37,0x33,0x37, - 0x31,0x37,0x33,0x38,0x31,0x37,0x33,0x39, - 0x31,0x37,0x34,0x30,0x31,0x37,0x34,0x31, - 0x31,0x37,0x34,0x32,0x31,0x37,0x34,0x33, - 0x31,0x37,0x34,0x34,0x31,0x37,0x34,0x35, - 0x31,0x37,0x34,0x36,0x31,0x37,0x34,0x37, - 0x31,0x37,0x34,0x38,0x31,0x37,0x34,0x39, - 0x31,0x37,0x35,0x30,0x31,0x37,0x35,0x31, - 0x31,0x37,0x35,0x32,0x31,0x37,0x35,0x33, - 0x31,0x37,0x35,0x34,0x31,0x37,0x35,0x35, - 0x31,0x37,0x35,0x36,0x31,0x37,0x35,0x37, - 0x31,0x37,0x35,0x38,0x31,0x37,0x35,0x39, - 0x31,0x37,0x36,0x30,0x31,0x37,0x36,0x31, - 0x31,0x37,0x36,0x32,0x31,0x37,0x36,0x33, - 0x31,0x37,0x36,0x34,0x31,0x37,0x36,0x35, - 0x31,0x37,0x36,0x36,0x31,0x37,0x36,0x37, - 0x31,0x37,0x36,0x38,0x31,0x37,0x36,0x39, - 0x31,0x37,0x37,0x30,0x31,0x37,0x37,0x31, - 0x31,0x37,0x37,0x32,0x31,0x37,0x37,0x33, - 0x31,0x37,0x37,0x34,0x31,0x37,0x37,0x35, - 0x31,0x37,0x37,0x36,0x31,0x37,0x37,0x37, - 0x31,0x37,0x37,0x38,0x31,0x37,0x37,0x39, - 0x31,0x37,0x38,0x30,0x31,0x37,0x38,0x31, - 0x31,0x37,0x38,0x32,0x31,0x37,0x38,0x33, - 0x31,0x37,0x38,0x34,0x31,0x37,0x38,0x35, - 0x31,0x37,0x38,0x36,0x31,0x37,0x38,0x37, - 0x31,0x37,0x38,0x38,0x31,0x37,0x38,0x39, - 0x31,0x37,0x39,0x30,0x31,0x37,0x39,0x31, - 0x31,0x37,0x39,0x32,0x31,0x37,0x39,0x33, - 0x31,0x37,0x39,0x34,0x31,0x37,0x39,0x35, - 0x31,0x37,0x39,0x36,0x31,0x37,0x39,0x37, - 0x31,0x37,0x39,0x38,0x31,0x37,0x39,0x39, - 0x31,0x38,0x30,0x30,0x31,0x38,0x30,0x31, - 0x31,0x38,0x30,0x32,0x31,0x38,0x30,0x33, - 0x31,0x38,0x30,0x34,0x31,0x38,0x30,0x35, - 0x31,0x38,0x30,0x36,0x31,0x38,0x30,0x37, - 0x31,0x38,0x30,0x38,0x31,0x38,0x30,0x39, - 0x31,0x38,0x31,0x30,0x31,0x38,0x31,0x31, - 0x31,0x38,0x31,0x32,0x31,0x38,0x31,0x33, - 0x31,0x38,0x31,0x34,0x31,0x38,0x31,0x35, - 0x31,0x38,0x31,0x36,0x31,0x38,0x31,0x37, - 0x31,0x38,0x31,0x38,0x31,0x38,0x31,0x39, - 0x31,0x38,0x32,0x30,0x31,0x38,0x32,0x31, - 0x31,0x38,0x32,0x32,0x31,0x38,0x32,0x33, - 0x31,0x38,0x32,0x34,0x31,0x38,0x32,0x35, - 0x31,0x38,0x32,0x36,0x31,0x38,0x32,0x37, - 0x31,0x38,0x32,0x38,0x31,0x38,0x32,0x39, - 0x31,0x38,0x33,0x30,0x31,0x38,0x33,0x31, - 0x31,0x38,0x33,0x32,0x31,0x38,0x33,0x33, - 0x31,0x38,0x33,0x34,0x31,0x38,0x33,0x35, - 0x31,0x38,0x33,0x36,0x31,0x38,0x33,0x37, - 0x31,0x38,0x33,0x38,0x31,0x38,0x33,0x39, - 0x31,0x38,0x34,0x30,0x31,0x38,0x34,0x31, - 0x31,0x38,0x34,0x32,0x31,0x38,0x34,0x33, - 0x31,0x38,0x34,0x34,0x31,0x38,0x34,0x35, - 0x31,0x38,0x34,0x36,0x31,0x38,0x34,0x37, - 0x31,0x38,0x34,0x38,0x31,0x38,0x34,0x39, - 0x31,0x38,0x35,0x30,0x31,0x38,0x35,0x31, - 0x31,0x38,0x35,0x32,0x31,0x38,0x35,0x33, - 0x31,0x38,0x35,0x34,0x31,0x38,0x35,0x35, - 0x31,0x38,0x35,0x36,0x31,0x38,0x35,0x37, - 0x31,0x38,0x35,0x38,0x31,0x38,0x35,0x39, - 0x31,0x38,0x36,0x30,0x31,0x38,0x36,0x31, - 0x31,0x38,0x36,0x32,0x31,0x38,0x36,0x33, - 0x31,0x38,0x36,0x34,0x31,0x38,0x36,0x35, - 0x31,0x38,0x36,0x36,0x31,0x38,0x36,0x37, - 0x31,0x38,0x36,0x38,0x31,0x38,0x36,0x39, - 0x31,0x38,0x37,0x30,0x31,0x38,0x37,0x31, - 0x31,0x38,0x37,0x32,0x31,0x38,0x37,0x33, - 0x31,0x38,0x37,0x34,0x31,0x38,0x37,0x35, - 0x31,0x38,0x37,0x36,0x31,0x38,0x37,0x37, - 0x31,0x38,0x37,0x38,0x31,0x38,0x37,0x39, - 0x31,0x38,0x38,0x30,0x31,0x38,0x38,0x31, - 0x31,0x38,0x38,0x32,0x31,0x38,0x38,0x33, - 0x31,0x38,0x38,0x34,0x31,0x38,0x38,0x35, - 0x31,0x38,0x38,0x36,0x31,0x38,0x38,0x37, - 0x31,0x38,0x38,0x38,0x31,0x38,0x38,0x39, - 0x31,0x38,0x39,0x30,0x31,0x38,0x39,0x31, - 0x31,0x38,0x39,0x32,0x31,0x38,0x39,0x33, - 0x31,0x38,0x39,0x34,0x31,0x38,0x39,0x35, - 0x31,0x38,0x39,0x36,0x31,0x38,0x39,0x37, - 0x31,0x38,0x39,0x38,0x31,0x38,0x39,0x39, - 0x31,0x39,0x30,0x30,0x31,0x39,0x30,0x31, - 0x31,0x39,0x30,0x32,0x31,0x39,0x30,0x33, - 0x31,0x39,0x30,0x34,0x31,0x39,0x30,0x35, - 0x31,0x39,0x30,0x36,0x31,0x39,0x30,0x37, - 0x31,0x39,0x30,0x38,0x31,0x39,0x30,0x39, - 0x31,0x39,0x31,0x30,0x31,0x39,0x31,0x31, - 0x31,0x39,0x31,0x32,0x31,0x39,0x31,0x33, - 0x31,0x39,0x31,0x34,0x31,0x39,0x31,0x35, - 0x31,0x39,0x31,0x36,0x31,0x39,0x31,0x37, - 0x31,0x39,0x31,0x38,0x31,0x39,0x31,0x39, - 0x31,0x39,0x32,0x30,0x31,0x39,0x32,0x31, - 0x31,0x39,0x32,0x32,0x31,0x39,0x32,0x33, - 0x31,0x39,0x32,0x34,0x31,0x39,0x32,0x35, - 0x31,0x39,0x32,0x36,0x31,0x39,0x32,0x37, - 0x31,0x39,0x32,0x38,0x31,0x39,0x32,0x39, - 0x31,0x39,0x33,0x30,0x31,0x39,0x33,0x31, - 0x31,0x39,0x33,0x32,0x31,0x39,0x33,0x33, - 0x31,0x39,0x33,0x34,0x31,0x39,0x33,0x35, - 0x31,0x39,0x33,0x36,0x31,0x39,0x33,0x37, - 0x31,0x39,0x33,0x38,0x31,0x39,0x33,0x39, - 0x31,0x39,0x34,0x30,0x31,0x39,0x34,0x31, - 0x31,0x39,0x34,0x32,0x31,0x39,0x34,0x33, - 0x31,0x39,0x34,0x34,0x31,0x39,0x34,0x35, - 0x31,0x39,0x34,0x36,0x31,0x39,0x34,0x37, - 0x31,0x39,0x34,0x38,0x31,0x39,0x34,0x39, - 0x31,0x39,0x35,0x30,0x31,0x39,0x35,0x31, - 0x31,0x39,0x35,0x32,0x31,0x39,0x35,0x33, - 0x31,0x39,0x35,0x34,0x31,0x39,0x35,0x35, - 0x31,0x39,0x35,0x36,0x31,0x39,0x35,0x37, - 0x31,0x39,0x35,0x38,0x31,0x39,0x35,0x39, - 0x31,0x39,0x36,0x30,0x31,0x39,0x36,0x31, - 0x31,0x39,0x36,0x32,0x31,0x39,0x36,0x33, - 0x31,0x39,0x36,0x34,0x31,0x39,0x36,0x35, - 0x31,0x39,0x36,0x36,0x31,0x39,0x36,0x37, - 0x31,0x39,0x36,0x38,0x31,0x39,0x36,0x39, - 0x31,0x39,0x37,0x30,0x31,0x39,0x37,0x31, - 0x31,0x39,0x37,0x32,0x31,0x39,0x37,0x33, - 0x31,0x39,0x37,0x34,0x31,0x39,0x37,0x35, - 0x31,0x39,0x37,0x36,0x31,0x39,0x37,0x37, - 0x31,0x39,0x37,0x38,0x31,0x39,0x37,0x39, - 0x31,0x39,0x38,0x30,0x31,0x39,0x38,0x31, - 0x31,0x39,0x38,0x32,0x31,0x39,0x38,0x33, - 0x31,0x39,0x38,0x34,0x31,0x39,0x38,0x35, - 0x31,0x39,0x38,0x36,0x31,0x39,0x38,0x37, - 0x31,0x39,0x38,0x38,0x31,0x39,0x38,0x39, - 0x31,0x39,0x39,0x30,0x31,0x39,0x39,0x31, - 0x31,0x39,0x39,0x32,0x31,0x39,0x39,0x33, - 0x31,0x39,0x39,0x34,0x31,0x39,0x39,0x35, - 0x31,0x39,0x39,0x36,0x31,0x39,0x39,0x37, - 0x31,0x39,0x39,0x38,0x31,0x39,0x39,0x39, - 0x32,0x30,0x30,0x30,0x32,0x30,0x30,0x31, - 0x32,0x30,0x30,0x32,0x32,0x30,0x30,0x33, - 0x32,0x30,0x30,0x34,0x32,0x30,0x30,0x35, - 0x32,0x30,0x30,0x36,0x32,0x30,0x30,0x37, - 0x32,0x30,0x30,0x38,0x32,0x30,0x30,0x39, - 0x32,0x30,0x31,0x30,0x32,0x30,0x31,0x31, - 0x32,0x30,0x31,0x32,0x32,0x30,0x31,0x33, - 0x32,0x30,0x31,0x34,0x32,0x30,0x31,0x35, - 0x32,0x30,0x31,0x36,0x32,0x30,0x31,0x37, - 0x32,0x30,0x31,0x38,0x32,0x30,0x31,0x39, - 0x32,0x30,0x32,0x30,0x32,0x30,0x32,0x31, - 0x32,0x30,0x32,0x32,0x32,0x30,0x32,0x33, - 0x32,0x30,0x32,0x34,0x32,0x30,0x32,0x35, - 0x32,0x30,0x32,0x36,0x32,0x30,0x32,0x37, - 0x32,0x30,0x32,0x38,0x32,0x30,0x32,0x39, - 0x32,0x30,0x33,0x30,0x32,0x30,0x33,0x31, - 0x32,0x30,0x33,0x32,0x32,0x30,0x33,0x33, - 0x32,0x30,0x33,0x34,0x32,0x30,0x33,0x35, - 0x32,0x30,0x33,0x36,0x32,0x30,0x33,0x37, - 0x32,0x30,0x33,0x38,0x32,0x30,0x33,0x39, - 0x32,0x30,0x34,0x30,0x32,0x30,0x34,0x31, - 0x32,0x30,0x34,0x32,0x32,0x30,0x34,0x33, - 0x32,0x30,0x34,0x34,0x32,0x30,0x34,0x35, - 0x32,0x30,0x34,0x36,0x32,0x30,0x34,0x37, - 0x32,0x30,0x34,0x38,0x32,0x30,0x34,0x39, - 0x32,0x30,0x35,0x30,0x32,0x30,0x35,0x31, - 0x32,0x30,0x35,0x32,0x32,0x30,0x35,0x33, - 0x32,0x30,0x35,0x34,0x32,0x30,0x35,0x35, - 0x32,0x30,0x35,0x36,0x32,0x30,0x35,0x37, - 0x32,0x30,0x35,0x38,0x32,0x30,0x35,0x39, - 0x32,0x30,0x36,0x30,0x32,0x30,0x36,0x31, - 0x32,0x30,0x36,0x32,0x32,0x30,0x36,0x33, - 0x32,0x30,0x36,0x34,0x32,0x30,0x36,0x35, - 0x32,0x30,0x36,0x36,0x32,0x30,0x36,0x37, - 0x32,0x30,0x36,0x38,0x32,0x30,0x36,0x39, - 0x32,0x30,0x37,0x30,0x32,0x30,0x37,0x31, - 0x32,0x30,0x37,0x32,0x32,0x30,0x37,0x33, - 0x32,0x30,0x37,0x34,0x32,0x30,0x37,0x35, - 0x32,0x30,0x37,0x36,0x32,0x30,0x37,0x37, - 0x32,0x30,0x37,0x38,0x32,0x30,0x37,0x39, - 0x32,0x30,0x38,0x30,0x32,0x30,0x38,0x31, - 0x32,0x30,0x38,0x32,0x32,0x30,0x38,0x33, - 0x32,0x30,0x38,0x34,0x32,0x30,0x38,0x35, - 0x32,0x30,0x38,0x36,0x32,0x30,0x38,0x37, - 0x32,0x30,0x38,0x38,0x32,0x30,0x38,0x39, - 0x32,0x30,0x39,0x30,0x32,0x30,0x39,0x31, - 0x32,0x30,0x39,0x32,0x32,0x30,0x39,0x33, - 0x32,0x30,0x39,0x34,0x32,0x30,0x39,0x35, - 0x32,0x30,0x39,0x36,0x32,0x30,0x39,0x37, - 0x32,0x30,0x39,0x38,0x32,0x30,0x39,0x39, - 0x32,0x31,0x30,0x30,0x32,0x31,0x30,0x31, - 0x32,0x31,0x30,0x32,0x32,0x31,0x30,0x33, - 0x32,0x31,0x30,0x34,0x32,0x31,0x30,0x35, - 0x32,0x31,0x30,0x36,0x32,0x31,0x30,0x37, - 0x32,0x31,0x30,0x38,0x32,0x31,0x30,0x39, - 0x32,0x31,0x31,0x30,0x32,0x31,0x31,0x31, - 0x32,0x31,0x31,0x32,0x32,0x31,0x31,0x33, - 0x32,0x31,0x31,0x34,0x32,0x31,0x31,0x35, - 0x32,0x31,0x31,0x36,0x32,0x31,0x31,0x37, - 0x32,0x31,0x31,0x38,0x32,0x31,0x31,0x39, - 0x32,0x31,0x32,0x30,0x32,0x31,0x32,0x31, - 0x32,0x31,0x32,0x32,0x32,0x31,0x32,0x33, - 0x32,0x31,0x32,0x34,0x32,0x31,0x32,0x35, - 0x32,0x31,0x32,0x36,0x32,0x31,0x32,0x37, - 0x32,0x31,0x32,0x38,0x32,0x31,0x32,0x39, - 0x32,0x31,0x33,0x30,0x32,0x31,0x33,0x31, - 0x32,0x31,0x33,0x32,0x32,0x31,0x33,0x33, - 0x32,0x31,0x33,0x34,0x32,0x31,0x33,0x35, - 0x32,0x31,0x33,0x36,0x32,0x31,0x33,0x37, - 0x32,0x31,0x33,0x38,0x32,0x31,0x33,0x39, - 0x32,0x31,0x34,0x30,0x32,0x31,0x34,0x31, - 0x32,0x31,0x34,0x32,0x32,0x31,0x34,0x33, - 0x32,0x31,0x34,0x34,0x32,0x31,0x34,0x35, - 0x32,0x31,0x34,0x36,0x32,0x31,0x34,0x37, - 0x32,0x31,0x34,0x38,0x32,0x31,0x34,0x39, - 0x32,0x31,0x35,0x30,0x32,0x31,0x35,0x31, - 0x32,0x31,0x35,0x32,0x32,0x31,0x35,0x33, - 0x32,0x31,0x35,0x34,0x32,0x31,0x35,0x35, - 0x32,0x31,0x35,0x36,0x32,0x31,0x35,0x37, - 0x32,0x31,0x35,0x38,0x32,0x31,0x35,0x39, - 0x32,0x31,0x36,0x30,0x32,0x31,0x36,0x31, - 0x32,0x31,0x36,0x32,0x32,0x31,0x36,0x33, - 0x32,0x31,0x36,0x34,0x32,0x31,0x36,0x35, - 0x32,0x31,0x36,0x36,0x32,0x31,0x36,0x37, - 0x32,0x31,0x36,0x38,0x32,0x31,0x36,0x39, - 0x32,0x31,0x37,0x30,0x32,0x31,0x37,0x31, - 0x32,0x31,0x37,0x32,0x32,0x31,0x37,0x33, - 0x32,0x31,0x37,0x34,0x32,0x31,0x37,0x35, - 0x32,0x31,0x37,0x36,0x32,0x31,0x37,0x37, - 0x32,0x31,0x37,0x38,0x32,0x31,0x37,0x39, - 0x32,0x31,0x38,0x30,0x32,0x31,0x38,0x31, - 0x32,0x31,0x38,0x32,0x32,0x31,0x38,0x33, - 0x32,0x31,0x38,0x34,0x32,0x31,0x38,0x35, - 0x32,0x31,0x38,0x36,0x32,0x31,0x38,0x37, - 0x32,0x31,0x38,0x38,0x32,0x31,0x38,0x39, - 0x32,0x31,0x39,0x30,0x32,0x31,0x39,0x31, - 0x32,0x31,0x39,0x32,0x32,0x31,0x39,0x33, - 0x32,0x31,0x39,0x34,0x32,0x31,0x39,0x35, - 0x32,0x31,0x39,0x36,0x32,0x31,0x39,0x37, - 0x32,0x31,0x39,0x38,0x32,0x31,0x39,0x39, - 0x32,0x32,0x30,0x30,0x32,0x32,0x30,0x31, - 0x32,0x32,0x30,0x32,0x32,0x32,0x30,0x33, - 0x32,0x32,0x30,0x34,0x32,0x32,0x30,0x35, - 0x32,0x32,0x30,0x36,0x32,0x32,0x30,0x37, - 0x32,0x32,0x30,0x38,0x32,0x32,0x30,0x39, - 0x32,0x32,0x31,0x30,0x32,0x32,0x31,0x31, - 0x32,0x32,0x31,0x32,0x32,0x32,0x31,0x33, - 0x32,0x32,0x31,0x34,0x32,0x32,0x31,0x35, - 0x32,0x32,0x31,0x36,0x32,0x32,0x31,0x37, - 0x32,0x32,0x31,0x38,0x32,0x32,0x31,0x39, - 0x32,0x32,0x32,0x30,0x32,0x32,0x32,0x31, - 0x32,0x32,0x32,0x32,0x32,0x32,0x32,0x33, - 0x32,0x32,0x32,0x34,0x32,0x32,0x32,0x35, - 0x32,0x32,0x32,0x36,0x32,0x32,0x32,0x37, - 0x32,0x32,0x32,0x38,0x32,0x32,0x32,0x39, - 0x32,0x32,0x33,0x30,0x32,0x32,0x33,0x31, - 0x32,0x32,0x33,0x32,0x32,0x32,0x33,0x33, - 0x32,0x32,0x33,0x34,0x32,0x32,0x33,0x35, - 0x32,0x32,0x33,0x36,0x32,0x32,0x33,0x37, - 0x32,0x32,0x33,0x38,0x32,0x32,0x33,0x39, - 0x32,0x32,0x34,0x30,0x32,0x32,0x34,0x31, - 0x32,0x32,0x34,0x32,0x32,0x32,0x34,0x33, - 0x32,0x32,0x34,0x34,0x32,0x32,0x34,0x35, - 0x32,0x32,0x34,0x36,0x32,0x32,0x34,0x37, - 0x32,0x32,0x34,0x38,0x32,0x32,0x34,0x39, - 0x32,0x32,0x35,0x30,0x32,0x32,0x35,0x31, - 0x32,0x32,0x35,0x32,0x32,0x32,0x35,0x33, - 0x32,0x32,0x35,0x34,0x32,0x32,0x35,0x35, - 0x32,0x32,0x35,0x36,0x32,0x32,0x35,0x37, - 0x32,0x32,0x35,0x38,0x32,0x32,0x35,0x39, - 0x32,0x32,0x36,0x30,0x32,0x32,0x36,0x31, - 0x32,0x32,0x36,0x32,0x32,0x32,0x36,0x33, - 0x32,0x32,0x36,0x34,0x32,0x32,0x36,0x35, - 0x32,0x32,0x36,0x36,0x32,0x32,0x36,0x37, - 0x32,0x32,0x36,0x38,0x32,0x32,0x36,0x39, - 0x32,0x32,0x37,0x30,0x32,0x32,0x37,0x31, - 0x32,0x32,0x37,0x32,0x32,0x32,0x37,0x33, - 0x32,0x32,0x37,0x34,0x32,0x32,0x37,0x35, - 0x32,0x32,0x37,0x36,0x32,0x32,0x37,0x37, - 0x32,0x32,0x37,0x38,0x32,0x32,0x37,0x39, - 0x32,0x32,0x38,0x30,0x32,0x32,0x38,0x31, - 0x32,0x32,0x38,0x32,0x32,0x32,0x38,0x33, - 0x32,0x32,0x38,0x34,0x32,0x32,0x38,0x35, - 0x32,0x32,0x38,0x36,0x32,0x32,0x38,0x37, - 0x32,0x32,0x38,0x38,0x32,0x32,0x38,0x39, - 0x32,0x32,0x39,0x30,0x32,0x32,0x39,0x31, - 0x32,0x32,0x39,0x32,0x32,0x32,0x39,0x33, - 0x32,0x32,0x39,0x34,0x32,0x32,0x39,0x35, - 0x32,0x32,0x39,0x36,0x32,0x32,0x39,0x37, - 0x32,0x32,0x39,0x38,0x32,0x32,0x39,0x39, - 0x32,0x33,0x30,0x30,0x32,0x33,0x30,0x31, - 0x32,0x33,0x30,0x32,0x32,0x33,0x30,0x33, - 0x32,0x33,0x30,0x34,0x32,0x33,0x30,0x35, - 0x32,0x33,0x30,0x36,0x32,0x33,0x30,0x37, - 0x32,0x33,0x30,0x38,0x32,0x33,0x30,0x39, - 0x32,0x33,0x31,0x30,0x32,0x33,0x31,0x31, - 0x32,0x33,0x31,0x32,0x32,0x33,0x31,0x33, - 0x32,0x33,0x31,0x34,0x32,0x33,0x31,0x35, - 0x32,0x33,0x31,0x36,0x32,0x33,0x31,0x37, - 0x32,0x33,0x31,0x38,0x32,0x33,0x31,0x39, - 0x32,0x33,0x32,0x30,0x32,0x33,0x32,0x31, - 0x32,0x33,0x32,0x32,0x32,0x33,0x32,0x33, - 0x32,0x33,0x32,0x34,0x32,0x33,0x32,0x35, - 0x32,0x33,0x32,0x36,0x32,0x33,0x32,0x37, - 0x32,0x33,0x32,0x38,0x32,0x33,0x32,0x39, - 0x32,0x33,0x33,0x30,0x32,0x33,0x33,0x31, - 0x32,0x33,0x33,0x32,0x32,0x33,0x33,0x33, - 0x32,0x33,0x33,0x34,0x32,0x33,0x33,0x35, - 0x32,0x33,0x33,0x36,0x32,0x33,0x33,0x37, - 0x32,0x33,0x33,0x38,0x32,0x33,0x33,0x39, - 0x32,0x33,0x34,0x30,0x32,0x33,0x34,0x31, - 0x32,0x33,0x34,0x32,0x32,0x33,0x34,0x33, - 0x32,0x33,0x34,0x34,0x32,0x33,0x34,0x35, - 0x32,0x33,0x34,0x36,0x32,0x33,0x34,0x37, - 0x32,0x33,0x34,0x38,0x32,0x33,0x34,0x39, - 0x32,0x33,0x35,0x30,0x32,0x33,0x35,0x31, - 0x32,0x33,0x35,0x32,0x32,0x33,0x35,0x33, - 0x32,0x33,0x35,0x34,0x32,0x33,0x35,0x35, - 0x32,0x33,0x35,0x36,0x32,0x33,0x35,0x37, - 0x32,0x33,0x35,0x38,0x32,0x33,0x35,0x39, - 0x32,0x33,0x36,0x30,0x32,0x33,0x36,0x31, - 0x32,0x33,0x36,0x32,0x32,0x33,0x36,0x33, - 0x32,0x33,0x36,0x34,0x32,0x33,0x36,0x35, - 0x32,0x33,0x36,0x36,0x32,0x33,0x36,0x37, - 0x32,0x33,0x36,0x38,0x32,0x33,0x36,0x39, - 0x32,0x33,0x37,0x30,0x32,0x33,0x37,0x31, - 0x32,0x33,0x37,0x32,0x32,0x33,0x37,0x33, - 0x32,0x33,0x37,0x34,0x32,0x33,0x37,0x35, - 0x32,0x33,0x37,0x36,0x32,0x33,0x37,0x37, - 0x32,0x33,0x37,0x38,0x32,0x33,0x37,0x39, - 0x32,0x33,0x38,0x30,0x32,0x33,0x38,0x31, - 0x32,0x33,0x38,0x32,0x32,0x33,0x38,0x33, - 0x32,0x33,0x38,0x34,0x32,0x33,0x38,0x35, - 0x32,0x33,0x38,0x36,0x32,0x33,0x38,0x37, - 0x32,0x33,0x38,0x38,0x32,0x33,0x38,0x39, - 0x32,0x33,0x39,0x30,0x32,0x33,0x39,0x31, - 0x32,0x33,0x39,0x32,0x32,0x33,0x39,0x33, - 0x32,0x33,0x39,0x34,0x32,0x33,0x39,0x35, - 0x32,0x33,0x39,0x36,0x32,0x33,0x39,0x37, - 0x32,0x33,0x39,0x38,0x32,0x33,0x39,0x39, - 0x32,0x34,0x30,0x30,0x32,0x34,0x30,0x31, - 0x32,0x34,0x30,0x32,0x32,0x34,0x30,0x33, - 0x32,0x34,0x30,0x34,0x32,0x34,0x30,0x35, - 0x32,0x34,0x30,0x36,0x32,0x34,0x30,0x37, - 0x32,0x34,0x30,0x38,0x32,0x34,0x30,0x39, - 0x32,0x34,0x31,0x30,0x32,0x34,0x31,0x31, - 0x32,0x34,0x31,0x32,0x32,0x34,0x31,0x33, - 0x32,0x34,0x31,0x34,0x32,0x34,0x31,0x35, - 0x32,0x34,0x31,0x36,0x32,0x34,0x31,0x37, - 0x32,0x34,0x31,0x38,0x32,0x34,0x31,0x39, - 0x32,0x34,0x32,0x30,0x32,0x34,0x32,0x31, - 0x32,0x34,0x32,0x32,0x32,0x34,0x32,0x33, - 0x32,0x34,0x32,0x34,0x32,0x34,0x32,0x35, - 0x32,0x34,0x32,0x36,0x32,0x34,0x32,0x37, - 0x32,0x34,0x32,0x38,0x32,0x34,0x32,0x39, - 0x32,0x34,0x33,0x30,0x32,0x34,0x33,0x31, - 0x32,0x34,0x33,0x32,0x32,0x34,0x33,0x33, - 0x32,0x34,0x33,0x34,0x32,0x34,0x33,0x35, - 0x32,0x34,0x33,0x36,0x32,0x34,0x33,0x37, - 0x32,0x34,0x33,0x38,0x32,0x34,0x33,0x39, - 0x32,0x34,0x34,0x30,0x32,0x34,0x34,0x31, - 0x32,0x34,0x34,0x32,0x32,0x34,0x34,0x33, - 0x32,0x34,0x34,0x34,0x32,0x34,0x34,0x35, - 0x32,0x34,0x34,0x36,0x32,0x34,0x34,0x37, - 0x32,0x34,0x34,0x38,0x32,0x34,0x34,0x39, - 0x32,0x34,0x35,0x30,0x32,0x34,0x35,0x31, - 0x32,0x34,0x35,0x32,0x32,0x34,0x35,0x33, - 0x32,0x34,0x35,0x34,0x32,0x34,0x35,0x35, - 0x32,0x34,0x35,0x36,0x32,0x34,0x35,0x37, - 0x32,0x34,0x35,0x38,0x32,0x34,0x35,0x39, - 0x32,0x34,0x36,0x30,0x32,0x34,0x36,0x31, - 0x32,0x34,0x36,0x32,0x32,0x34,0x36,0x33, - 0x32,0x34,0x36,0x34,0x32,0x34,0x36,0x35, - 0x32,0x34,0x36,0x36,0x32,0x34,0x36,0x37, - 0x32,0x34,0x36,0x38,0x32,0x34,0x36,0x39, - 0x32,0x34,0x37,0x30,0x32,0x34,0x37,0x31, - 0x32,0x34,0x37,0x32,0x32,0x34,0x37,0x33, - 0x32,0x34,0x37,0x34,0x32,0x34,0x37,0x35, - 0x32,0x34,0x37,0x36,0x32,0x34,0x37,0x37, - 0x32,0x34,0x37,0x38,0x32,0x34,0x37,0x39, - 0x32,0x34,0x38,0x30,0x32,0x34,0x38,0x31, - 0x32,0x34,0x38,0x32,0x32,0x34,0x38,0x33, - 0x32,0x34,0x38,0x34,0x32,0x34,0x38,0x35, - 0x32,0x34,0x38,0x36,0x32,0x34,0x38,0x37, - 0x32,0x34,0x38,0x38,0x32,0x34,0x38,0x39, - 0x32,0x34,0x39,0x30,0x32,0x34,0x39,0x31, - 0x32,0x34,0x39,0x32,0x32,0x34,0x39,0x33, - 0x32,0x34,0x39,0x34,0x32,0x34,0x39,0x35, - 0x32,0x34,0x39,0x36,0x32,0x34,0x39,0x37, - 0x32,0x34,0x39,0x38,0x32,0x34,0x39,0x39, - 0x32,0x35,0x30,0x30,0x32,0x35,0x30,0x31, - 0x32,0x35,0x30,0x32,0x32,0x35,0x30,0x33, - 0x32,0x35,0x30,0x34,0x32,0x35,0x30,0x35, - 0x32,0x35,0x30,0x36,0x32,0x35,0x30,0x37, - 0x32,0x35,0x30,0x38,0x32,0x35,0x30,0x39, - 0x32,0x35,0x31,0x30,0x32,0x35,0x31,0x31, - 0x32,0x35,0x31,0x32,0x32,0x35,0x31,0x33, - 0x32,0x35,0x31,0x34,0x32,0x35,0x31,0x35, - 0x32,0x35,0x31,0x36,0x32,0x35,0x31,0x37, - 0x32,0x35,0x31,0x38,0x32,0x35,0x31,0x39, - 0x32,0x35,0x32,0x30,0x32,0x35,0x32,0x31, - 0x32,0x35,0x32,0x32,0x32,0x35,0x32,0x33, - 0x32,0x35,0x32,0x34,0x32,0x35,0x32,0x35, - 0x32,0x35,0x32,0x36,0x32,0x35,0x32,0x37, - 0x32,0x35,0x32,0x38,0x32,0x35,0x32,0x39, - 0x32,0x35,0x33,0x30,0x32,0x35,0x33,0x31, - 0x32,0x35,0x33,0x32,0x32,0x35,0x33,0x33, - 0x32,0x35,0x33,0x34,0x32,0x35,0x33,0x35, - 0x32,0x35,0x33,0x36,0x32,0x35,0x33,0x37, - 0x32,0x35,0x33,0x38,0x32,0x35,0x33,0x39, - 0x32,0x35,0x34,0x30,0x32,0x35,0x34,0x31, - 0x32,0x35,0x34,0x32,0x32,0x35,0x34,0x33, - 0x32,0x35,0x34,0x34,0x32,0x35,0x34,0x35, - 0x32,0x35,0x34,0x36,0x32,0x35,0x34,0x37, - 0x32,0x35,0x34,0x38,0x32,0x35,0x34,0x39, - 0x32,0x35,0x35,0x30,0x32,0x35,0x35,0x31, - 0x32,0x35,0x35,0x32,0x32,0x35,0x35,0x33, - 0x32,0x35,0x35,0x34,0x32,0x35,0x35,0x35, - 0x32,0x35,0x35,0x36,0x32,0x35,0x35,0x37, - 0x32,0x35,0x35,0x38,0x32,0x35,0x35,0x39, - 0x32,0x35,0x36,0x30,0x32,0x35,0x36,0x31, - 0x32,0x35,0x36,0x32,0x32,0x35,0x36,0x33, - 0x32,0x35,0x36,0x34,0x32,0x35,0x36,0x35, - 0x32,0x35,0x36,0x36,0x32,0x35,0x36,0x37, - 0x32,0x35,0x36,0x38,0x32,0x35,0x36,0x39, - 0x32,0x35,0x37,0x30,0x32,0x35,0x37,0x31, - 0x32,0x35,0x37,0x32,0x32,0x35,0x37,0x33, - 0x32,0x35,0x37,0x34,0x32,0x35,0x37,0x35, - 0x32,0x35,0x37,0x36,0x32,0x35,0x37,0x37, - 0x32,0x35,0x37,0x38,0x32,0x35,0x37,0x39, - 0x32,0x35,0x38,0x30,0x32,0x35,0x38,0x31, - 0x32,0x35,0x38,0x32,0x32,0x35,0x38,0x33, - 0x32,0x35,0x38,0x34,0x32,0x35,0x38,0x35, - 0x32,0x35,0x38,0x36,0x32,0x35,0x38,0x37, - 0x32,0x35,0x38,0x38,0x32,0x35,0x38,0x39, - 0x32,0x35,0x39,0x30,0x32,0x35,0x39,0x31, - 0x32,0x35,0x39,0x32,0x32,0x35,0x39,0x33, - 0x32,0x35,0x39,0x34,0x32,0x35,0x39,0x35, - 0x32,0x35,0x39,0x36,0x32,0x35,0x39,0x37, - 0x32,0x35,0x39,0x38,0x32,0x35,0x39,0x39, - 0x32,0x36,0x30,0x30,0x32,0x36,0x30,0x31, - 0x32,0x36,0x30,0x32,0x32,0x36,0x30,0x33, - 0x32,0x36,0x30,0x34,0x32,0x36,0x30,0x35, - 0x32,0x36,0x30,0x36,0x32,0x36,0x30,0x37, - 0x32,0x36,0x30,0x38,0x32,0x36,0x30,0x39, - 0x32,0x36,0x31,0x30,0x32,0x36,0x31,0x31, - 0x32,0x36,0x31,0x32,0x32,0x36,0x31,0x33, - 0x32,0x36,0x31,0x34,0x32,0x36,0x31,0x35, - 0x32,0x36,0x31,0x36,0x32,0x36,0x31,0x37, - 0x32,0x36,0x31,0x38,0x32,0x36,0x31,0x39, - 0x32,0x36,0x32,0x30,0x32,0x36,0x32,0x31, - 0x32,0x36,0x32,0x32,0x32,0x36,0x32,0x33, - 0x32,0x36,0x32,0x34,0x32,0x36,0x32,0x35, - 0x32,0x36,0x32,0x36,0x32,0x36,0x32,0x37, - 0x32,0x36,0x32,0x38,0x32,0x36,0x32,0x39, - 0x32,0x36,0x33,0x30,0x32,0x36,0x33,0x31, - 0x32,0x36,0x33,0x32,0x32,0x36,0x33,0x33, - 0x32,0x36,0x33,0x34,0x32,0x36,0x33,0x35, - 0x32,0x36,0x33,0x36,0x32,0x36,0x33,0x37, - 0x32,0x36,0x33,0x38,0x32,0x36,0x33,0x39, - 0x32,0x36,0x34,0x30,0x32,0x36,0x34,0x31, - 0x32,0x36,0x34,0x32,0x32,0x36,0x34,0x33, - 0x32,0x36,0x34,0x34,0x32,0x36,0x34,0x35, - 0x32,0x36,0x34,0x36,0x32,0x36,0x34,0x37, - 0x32,0x36,0x34,0x38,0x32,0x36,0x34,0x39, - 0x32,0x36,0x35,0x30,0x32,0x36,0x35,0x31, - 0x32,0x36,0x35,0x32,0x32,0x36,0x35,0x33, - 0x32,0x36,0x35,0x34,0x32,0x36,0x35,0x35, - 0x32,0x36,0x35,0x36,0x32,0x36,0x35,0x37, - 0x32,0x36,0x35,0x38,0x32,0x36,0x35,0x39, - 0x32,0x36,0x36,0x30,0x32,0x36,0x36,0x31, - 0x32,0x36,0x36,0x32,0x32,0x36,0x36,0x33, - 0x32,0x36,0x36,0x34,0x32,0x36,0x36,0x35, - 0x32,0x36,0x36,0x36,0x32,0x36,0x36,0x37, - 0x32,0x36,0x36,0x38,0x32,0x36,0x36,0x39, - 0x32,0x36,0x37,0x30,0x32,0x36,0x37,0x31, - 0x32,0x36,0x37,0x32,0x32,0x36,0x37,0x33, - 0x32,0x36,0x37,0x34,0x32,0x36,0x37,0x35, - 0x32,0x36,0x37,0x36,0x32,0x36,0x37,0x37, - 0x32,0x36,0x37,0x38,0x32,0x36,0x37,0x39, - 0x32,0x36,0x38,0x30,0x32,0x36,0x38,0x31, - 0x32,0x36,0x38,0x32,0x32,0x36,0x38,0x33, - 0x32,0x36,0x38,0x34,0x32,0x36,0x38,0x35, - 0x32,0x36,0x38,0x36,0x32,0x36,0x38,0x37, - 0x32,0x36,0x38,0x38,0x32,0x36,0x38,0x39, - 0x32,0x36,0x39,0x30,0x32,0x36,0x39,0x31, - 0x32,0x36,0x39,0x32,0x32,0x36,0x39,0x33, - 0x32,0x36,0x39,0x34,0x32,0x36,0x39,0x35, - 0x32,0x36,0x39,0x36,0x32,0x36,0x39,0x37, - 0x32,0x36,0x39,0x38,0x32,0x36,0x39,0x39, - 0x32,0x37,0x30,0x30,0x32,0x37,0x30,0x31, - 0x32,0x37,0x30,0x32,0x32,0x37,0x30,0x33, - 0x32,0x37,0x30,0x34,0x32,0x37,0x30,0x35, - 0x32,0x37,0x30,0x36,0x32,0x37,0x30,0x37, - 0x32,0x37,0x30,0x38,0x32,0x37,0x30,0x39, - 0x32,0x37,0x31,0x30,0x32,0x37,0x31,0x31, - 0x32,0x37,0x31,0x32,0x32,0x37,0x31,0x33, - 0x32,0x37,0x31,0x34,0x32,0x37,0x31,0x35, - 0x32,0x37,0x31,0x36,0x32,0x37,0x31,0x37, - 0x32,0x37,0x31,0x38,0x32,0x37,0x31,0x39, - 0x32,0x37,0x32,0x30,0x32,0x37,0x32,0x31, - 0x32,0x37,0x32,0x32,0x32,0x37,0x32,0x33, - 0x32,0x37,0x32,0x34,0x32,0x37,0x32,0x35, - 0x32,0x37,0x32,0x36,0x32,0x37,0x32,0x37, - 0x32,0x37,0x32,0x38,0x32,0x37,0x32,0x39, - 0x32,0x37,0x33,0x30,0x32,0x37,0x33,0x31, - 0x32,0x37,0x33,0x32,0x32,0x37,0x33,0x33, - 0x32,0x37,0x33,0x34,0x32,0x37,0x33,0x35, - 0x32,0x37,0x33,0x36,0x32,0x37,0x33,0x37, - 0x32,0x37,0x33,0x38,0x32,0x37,0x33,0x39, - 0x32,0x37,0x34,0x30,0x32,0x37,0x34,0x31, - 0x32,0x37,0x34,0x32,0x32,0x37,0x34,0x33, - 0x32,0x37,0x34,0x34,0x32,0x37,0x34,0x35, - 0x32,0x37,0x34,0x36,0x32,0x37,0x34,0x37, - 0x32,0x37,0x34,0x38,0x32,0x37,0x34,0x39, - 0x32,0x37,0x35,0x30,0x32,0x37,0x35,0x31, - 0x32,0x37,0x35,0x32,0x32,0x37,0x35,0x33, - 0x32,0x37,0x35,0x34,0x32,0x37,0x35,0x35, - 0x32,0x37,0x35,0x36,0x32,0x37,0x35,0x37, - 0x32,0x37,0x35,0x38,0x32,0x37,0x35,0x39, - 0x32,0x37,0x36,0x30,0x32,0x37,0x36,0x31, - 0x32,0x37,0x36,0x32,0x32,0x37,0x36,0x33, - 0x32,0x37,0x36,0x34,0x32,0x37,0x36,0x35, - 0x32,0x37,0x36,0x36,0x32,0x37,0x36,0x37, - 0x32,0x37,0x36,0x38,0x32,0x37,0x36,0x39, - 0x32,0x37,0x37,0x30,0x32,0x37,0x37,0x31, - 0x32,0x37,0x37,0x32,0x32,0x37,0x37,0x33, - 0x32,0x37,0x37,0x34,0x32,0x37,0x37,0x35, - 0x32,0x37,0x37,0x36,0x32,0x37,0x37,0x37, - 0x32,0x37,0x37,0x38,0x32,0x37,0x37,0x39, - 0x32,0x37,0x38,0x30,0x32,0x37,0x38,0x31, - 0x32,0x37,0x38,0x32,0x32,0x37,0x38,0x33, - 0x32,0x37,0x38,0x34,0x32,0x37,0x38,0x35, - 0x32,0x37,0x38,0x36,0x32,0x37,0x38,0x37, - 0x32,0x37,0x38,0x38,0x32,0x37,0x38,0x39, - 0x32,0x37,0x39,0x30,0x32,0x37,0x39,0x31, - 0x32,0x37,0x39,0x32,0x32,0x37,0x39,0x33, - 0x32,0x37,0x39,0x34,0x32,0x37,0x39,0x35, - 0x32,0x37,0x39,0x36,0x32,0x37,0x39,0x37, - 0x32,0x37,0x39,0x38,0x32,0x37,0x39,0x39, - 0x32,0x38,0x30,0x30,0x32,0x38,0x30,0x31, - 0x32,0x38,0x30,0x32,0x32,0x38,0x30,0x33, - 0x32,0x38,0x30,0x34,0x32,0x38,0x30,0x35, - 0x32,0x38,0x30,0x36,0x32,0x38,0x30,0x37, - 0x32,0x38,0x30,0x38,0x32,0x38,0x30,0x39, - 0x32,0x38,0x31,0x30,0x32,0x38,0x31,0x31, - 0x32,0x38,0x31,0x32,0x32,0x38,0x31,0x33, - 0x32,0x38,0x31,0x34,0x32,0x38,0x31,0x35, - 0x32,0x38,0x31,0x36,0x32,0x38,0x31,0x37, - 0x32,0x38,0x31,0x38,0x32,0x38,0x31,0x39, - 0x32,0x38,0x32,0x30,0x32,0x38,0x32,0x31, - 0x32,0x38,0x32,0x32,0x32,0x38,0x32,0x33, - 0x32,0x38,0x32,0x34,0x32,0x38,0x32,0x35, - 0x32,0x38,0x32,0x36,0x32,0x38,0x32,0x37, - 0x32,0x38,0x32,0x38,0x32,0x38,0x32,0x39, - 0x32,0x38,0x33,0x30,0x32,0x38,0x33,0x31, - 0x32,0x38,0x33,0x32,0x32,0x38,0x33,0x33, - 0x32,0x38,0x33,0x34,0x32,0x38,0x33,0x35, - 0x32,0x38,0x33,0x36,0x32,0x38,0x33,0x37, - 0x32,0x38,0x33,0x38,0x32,0x38,0x33,0x39, - 0x32,0x38,0x34,0x30,0x32,0x38,0x34,0x31, - 0x32,0x38,0x34,0x32,0x32,0x38,0x34,0x33, - 0x32,0x38,0x34,0x34,0x32,0x38,0x34,0x35, - 0x32,0x38,0x34,0x36,0x32,0x38,0x34,0x37, - 0x32,0x38,0x34,0x38,0x32,0x38,0x34,0x39, - 0x32,0x38,0x35,0x30,0x32,0x38,0x35,0x31, - 0x32,0x38,0x35,0x32,0x32,0x38,0x35,0x33, - 0x32,0x38,0x35,0x34,0x32,0x38,0x35,0x35, - 0x32,0x38,0x35,0x36,0x32,0x38,0x35,0x37, - 0x32,0x38,0x35,0x38,0x32,0x38,0x35,0x39, - 0x32,0x38,0x36,0x30,0x32,0x38,0x36,0x31, - 0x32,0x38,0x36,0x32,0x32,0x38,0x36,0x33, - 0x32,0x38,0x36,0x34,0x32,0x38,0x36,0x35, - 0x32,0x38,0x36,0x36,0x32,0x38,0x36,0x37, - 0x32,0x38,0x36,0x38,0x32,0x38,0x36,0x39, - 0x32,0x38,0x37,0x30,0x32,0x38,0x37,0x31, - 0x32,0x38,0x37,0x32,0x32,0x38,0x37,0x33, - 0x32,0x38,0x37,0x34,0x32,0x38,0x37,0x35, - 0x32,0x38,0x37,0x36,0x32,0x38,0x37,0x37, - 0x32,0x38,0x37,0x38,0x32,0x38,0x37,0x39, - 0x32,0x38,0x38,0x30,0x32,0x38,0x38,0x31, - 0x32,0x38,0x38,0x32,0x32,0x38,0x38,0x33, - 0x32,0x38,0x38,0x34,0x32,0x38,0x38,0x35, - 0x32,0x38,0x38,0x36,0x32,0x38,0x38,0x37, - 0x32,0x38,0x38,0x38,0x32,0x38,0x38,0x39, - 0x32,0x38,0x39,0x30,0x32,0x38,0x39,0x31, - 0x32,0x38,0x39,0x32,0x32,0x38,0x39,0x33, - 0x32,0x38,0x39,0x34,0x32,0x38,0x39,0x35, - 0x32,0x38,0x39,0x36,0x32,0x38,0x39,0x37, - 0x32,0x38,0x39,0x38,0x32,0x38,0x39,0x39, - 0x32,0x39,0x30,0x30,0x32,0x39,0x30,0x31, - 0x32,0x39,0x30,0x32,0x32,0x39,0x30,0x33, - 0x32,0x39,0x30,0x34,0x32,0x39,0x30,0x35, - 0x32,0x39,0x30,0x36,0x32,0x39,0x30,0x37, - 0x32,0x39,0x30,0x38,0x32,0x39,0x30,0x39, - 0x32,0x39,0x31,0x30,0x32,0x39,0x31,0x31, - 0x32,0x39,0x31,0x32,0x32,0x39,0x31,0x33, - 0x32,0x39,0x31,0x34,0x32,0x39,0x31,0x35, - 0x32,0x39,0x31,0x36,0x32,0x39,0x31,0x37, - 0x32,0x39,0x31,0x38,0x32,0x39,0x31,0x39, - 0x32,0x39,0x32,0x30,0x32,0x39,0x32,0x31, - 0x32,0x39,0x32,0x32,0x32,0x39,0x32,0x33, - 0x32,0x39,0x32,0x34,0x32,0x39,0x32,0x35, - 0x32,0x39,0x32,0x36,0x32,0x39,0x32,0x37, - 0x32,0x39,0x32,0x38,0x32,0x39,0x32,0x39, - 0x32,0x39,0x33,0x30,0x32,0x39,0x33,0x31, - 0x32,0x39,0x33,0x32,0x32,0x39,0x33,0x33, - 0x32,0x39,0x33,0x34,0x32,0x39,0x33,0x35, - 0x32,0x39,0x33,0x36,0x32,0x39,0x33,0x37, - 0x32,0x39,0x33,0x38,0x32,0x39,0x33,0x39, - 0x32,0x39,0x34,0x30,0x32,0x39,0x34,0x31, - 0x32,0x39,0x34,0x32,0x32,0x39,0x34,0x33, - 0x32,0x39,0x34,0x34,0x32,0x39,0x34,0x35, - 0x32,0x39,0x34,0x36,0x32,0x39,0x34,0x37, - 0x32,0x39,0x34,0x38,0x32,0x39,0x34,0x39, - 0x32,0x39,0x35,0x30,0x32,0x39,0x35,0x31, - 0x32,0x39,0x35,0x32,0x32,0x39,0x35,0x33, - 0x32,0x39,0x35,0x34,0x32,0x39,0x35,0x35, - 0x32,0x39,0x35,0x36,0x32,0x39,0x35,0x37, - 0x32,0x39,0x35,0x38,0x32,0x39,0x35,0x39, - 0x32,0x39,0x36,0x30,0x32,0x39,0x36,0x31, - 0x32,0x39,0x36,0x32,0x32,0x39,0x36,0x33, - 0x32,0x39,0x36,0x34,0x32,0x39,0x36,0x35, - 0x32,0x39,0x36,0x36,0x32,0x39,0x36,0x37, - 0x32,0x39,0x36,0x38,0x32,0x39,0x36,0x39, - 0x32,0x39,0x37,0x30,0x32,0x39,0x37,0x31, - 0x32,0x39,0x37,0x32,0x32,0x39,0x37,0x33, - 0x32,0x39,0x37,0x34,0x32,0x39,0x37,0x35, - 0x32,0x39,0x37,0x36,0x32,0x39,0x37,0x37, - 0x32,0x39,0x37,0x38,0x32,0x39,0x37,0x39, - 0x32,0x39,0x38,0x30,0x32,0x39,0x38,0x31, - 0x32,0x39,0x38,0x32,0x32,0x39,0x38,0x33, - 0x32,0x39,0x38,0x34,0x32,0x39,0x38,0x35, - 0x32,0x39,0x38,0x36,0x32,0x39,0x38,0x37, - 0x32,0x39,0x38,0x38,0x32,0x39,0x38,0x39, - 0x32,0x39,0x39,0x30,0x32,0x39,0x39,0x31, - 0x32,0x39,0x39,0x32,0x32,0x39,0x39,0x33, - 0x32,0x39,0x39,0x34,0x32,0x39,0x39,0x35, - 0x32,0x39,0x39,0x36,0x32,0x39,0x39,0x37, - 0x32,0x39,0x39,0x38,0x32,0x39,0x39,0x39, - 0x33,0x30,0x30,0x30,0x33,0x30,0x30,0x31, - 0x33,0x30,0x30,0x32,0x33,0x30,0x30,0x33, - 0x33,0x30,0x30,0x34,0x33,0x30,0x30,0x35, - 0x33,0x30,0x30,0x36,0x33,0x30,0x30,0x37, - 0x33,0x30,0x30,0x38,0x33,0x30,0x30,0x39, - 0x33,0x30,0x31,0x30,0x33,0x30,0x31,0x31, - 0x33,0x30,0x31,0x32,0x33,0x30,0x31,0x33, - 0x33,0x30,0x31,0x34,0x33,0x30,0x31,0x35, - 0x33,0x30,0x31,0x36,0x33,0x30,0x31,0x37, - 0x33,0x30,0x31,0x38,0x33,0x30,0x31,0x39, - 0x33,0x30,0x32,0x30,0x33,0x30,0x32,0x31, - 0x33,0x30,0x32,0x32,0x33,0x30,0x32,0x33, - 0x33,0x30,0x32,0x34,0x33,0x30,0x32,0x35, - 0x33,0x30,0x32,0x36,0x33,0x30,0x32,0x37, - 0x33,0x30,0x32,0x38,0x33,0x30,0x32,0x39, - 0x33,0x30,0x33,0x30,0x33,0x30,0x33,0x31, - 0x33,0x30,0x33,0x32,0x33,0x30,0x33,0x33, - 0x33,0x30,0x33,0x34,0x33,0x30,0x33,0x35, - 0x33,0x30,0x33,0x36,0x33,0x30,0x33,0x37, - 0x33,0x30,0x33,0x38,0x33,0x30,0x33,0x39, - 0x33,0x30,0x34,0x30,0x33,0x30,0x34,0x31, - 0x33,0x30,0x34,0x32,0x33,0x30,0x34,0x33, - 0x33,0x30,0x34,0x34,0x33,0x30,0x34,0x35, - 0x33,0x30,0x34,0x36,0x33,0x30,0x34,0x37, - 0x33,0x30,0x34,0x38,0x33,0x30,0x34,0x39, - 0x33,0x30,0x35,0x30,0x33,0x30,0x35,0x31, - 0x33,0x30,0x35,0x32,0x33,0x30,0x35,0x33, - 0x33,0x30,0x35,0x34,0x33,0x30,0x35,0x35, - 0x33,0x30,0x35,0x36,0x33,0x30,0x35,0x37, - 0x33,0x30,0x35,0x38,0x33,0x30,0x35,0x39, - 0x33,0x30,0x36,0x30,0x33,0x30,0x36,0x31, - 0x33,0x30,0x36,0x32,0x33,0x30,0x36,0x33, - 0x33,0x30,0x36,0x34,0x33,0x30,0x36,0x35, - 0x33,0x30,0x36,0x36,0x33,0x30,0x36,0x37, - 0x33,0x30,0x36,0x38,0x33,0x30,0x36,0x39, - 0x33,0x30,0x37,0x30,0x33,0x30,0x37,0x31, - 0x33,0x30,0x37,0x32,0x33,0x30,0x37,0x33, - 0x33,0x30,0x37,0x34,0x33,0x30,0x37,0x35, - 0x33,0x30,0x37,0x36,0x33,0x30,0x37,0x37, - 0x33,0x30,0x37,0x38,0x33,0x30,0x37,0x39, - 0x33,0x30,0x38,0x30,0x33,0x30,0x38,0x31, - 0x33,0x30,0x38,0x32,0x33,0x30,0x38,0x33, - 0x33,0x30,0x38,0x34,0x33,0x30,0x38,0x35, - 0x33,0x30,0x38,0x36,0x33,0x30,0x38,0x37, - 0x33,0x30,0x38,0x38,0x33,0x30,0x38,0x39, - 0x33,0x30,0x39,0x30,0x33,0x30,0x39,0x31, - 0x33,0x30,0x39,0x32,0x33,0x30,0x39,0x33, - 0x33,0x30,0x39,0x34,0x33,0x30,0x39,0x35, - 0x33,0x30,0x39,0x36,0x33,0x30,0x39,0x37, - 0x33,0x30,0x39,0x38,0x33,0x30,0x39,0x39, - 0x33,0x31,0x30,0x30,0x33,0x31,0x30,0x31, - 0x33,0x31,0x30,0x32,0x33,0x31,0x30,0x33, - 0x33,0x31,0x30,0x34,0x33,0x31,0x30,0x35, - 0x33,0x31,0x30,0x36,0x33,0x31,0x30,0x37, - 0x33,0x31,0x30,0x38,0x33,0x31,0x30,0x39, - 0x33,0x31,0x31,0x30,0x33,0x31,0x31,0x31, - 0x33,0x31,0x31,0x32,0x33,0x31,0x31,0x33, - 0x33,0x31,0x31,0x34,0x33,0x31,0x31,0x35, - 0x33,0x31,0x31,0x36,0x33,0x31,0x31,0x37, - 0x33,0x31,0x31,0x38,0x33,0x31,0x31,0x39, - 0x33,0x31,0x32,0x30,0x33,0x31,0x32,0x31, - 0x33,0x31,0x32,0x32,0x33,0x31,0x32,0x33, - 0x33,0x31,0x32,0x34,0x33,0x31,0x32,0x35, - 0x33,0x31,0x32,0x36,0x33,0x31,0x32,0x37, - 0x33,0x31,0x32,0x38,0x33,0x31,0x32,0x39, - 0x33,0x31,0x33,0x30,0x33,0x31,0x33,0x31, - 0x33,0x31,0x33,0x32,0x33,0x31,0x33,0x33, - 0x33,0x31,0x33,0x34,0x33,0x31,0x33,0x35, - 0x33,0x31,0x33,0x36,0x33,0x31,0x33,0x37, - 0x33,0x31,0x33,0x38,0x33,0x31,0x33,0x39, - 0x33,0x31,0x34,0x30,0x33,0x31,0x34,0x31, - 0x33,0x31,0x34,0x32,0x33,0x31,0x34,0x33, - 0x33,0x31,0x34,0x34,0x33,0x31,0x34,0x35, - 0x33,0x31,0x34,0x36,0x33,0x31,0x34,0x37, - 0x33,0x31,0x34,0x38,0x33,0x31,0x34,0x39, - 0x33,0x31,0x35,0x30,0x33,0x31,0x35,0x31, - 0x33,0x31,0x35,0x32,0x33,0x31,0x35,0x33, - 0x33,0x31,0x35,0x34,0x33,0x31,0x35,0x35, - 0x33,0x31,0x35,0x36,0x33,0x31,0x35,0x37, - 0x33,0x31,0x35,0x38,0x33,0x31,0x35,0x39, - 0x33,0x31,0x36,0x30,0x33,0x31,0x36,0x31, - 0x33,0x31,0x36,0x32,0x33,0x31,0x36,0x33, - 0x33,0x31,0x36,0x34,0x33,0x31,0x36,0x35, - 0x33,0x31,0x36,0x36,0x33,0x31,0x36,0x37, - 0x33,0x31,0x36,0x38,0x33,0x31,0x36,0x39, - 0x33,0x31,0x37,0x30,0x33,0x31,0x37,0x31, - 0x33,0x31,0x37,0x32,0x33,0x31,0x37,0x33, - 0x33,0x31,0x37,0x34,0x33,0x31,0x37,0x35, - 0x33,0x31,0x37,0x36,0x33,0x31,0x37,0x37, - 0x33,0x31,0x37,0x38,0x33,0x31,0x37,0x39, - 0x33,0x31,0x38,0x30,0x33,0x31,0x38,0x31, - 0x33,0x31,0x38,0x32,0x33,0x31,0x38,0x33, - 0x33,0x31,0x38,0x34,0x33,0x31,0x38,0x35, - 0x33,0x31,0x38,0x36,0x33,0x31,0x38,0x37, - 0x33,0x31,0x38,0x38,0x33,0x31,0x38,0x39, - 0x33,0x31,0x39,0x30,0x33,0x31,0x39,0x31, - 0x33,0x31,0x39,0x32,0x33,0x31,0x39,0x33, - 0x33,0x31,0x39,0x34,0x33,0x31,0x39,0x35, - 0x33,0x31,0x39,0x36,0x33,0x31,0x39,0x37, - 0x33,0x31,0x39,0x38,0x33,0x31,0x39,0x39, - 0x33,0x32,0x30,0x30,0x33,0x32,0x30,0x31, - 0x33,0x32,0x30,0x32,0x33,0x32,0x30,0x33, - 0x33,0x32,0x30,0x34,0x33,0x32,0x30,0x35, - 0x33,0x32,0x30,0x36,0x33,0x32,0x30,0x37, - 0x33,0x32,0x30,0x38,0x33,0x32,0x30,0x39, - 0x33,0x32,0x31,0x30,0x33,0x32,0x31,0x31, - 0x33,0x32,0x31,0x32,0x33,0x32,0x31,0x33, - 0x33,0x32,0x31,0x34,0x33,0x32,0x31,0x35, - 0x33,0x32,0x31,0x36,0x33,0x32,0x31,0x37, - 0x33,0x32,0x31,0x38,0x33,0x32,0x31,0x39, - 0x33,0x32,0x32,0x30,0x33,0x32,0x32,0x31, - 0x33,0x32,0x32,0x32,0x33,0x32,0x32,0x33, - 0x33,0x32,0x32,0x34,0x33,0x32,0x32,0x35, - 0x33,0x32,0x32,0x36,0x33,0x32,0x32,0x37, - 0x33,0x32,0x32,0x38,0x33,0x32,0x32,0x39, - 0x33,0x32,0x33,0x30,0x33,0x32,0x33,0x31, - 0x33,0x32,0x33,0x32,0x33,0x32,0x33,0x33, - 0x33,0x32,0x33,0x34,0x33,0x32,0x33,0x35, - 0x33,0x32,0x33,0x36,0x33,0x32,0x33,0x37, - 0x33,0x32,0x33,0x38,0x33,0x32,0x33,0x39, - 0x33,0x32,0x34,0x30,0x33,0x32,0x34,0x31, - 0x33,0x32,0x34,0x32,0x33,0x32,0x34,0x33, - 0x33,0x32,0x34,0x34,0x33,0x32,0x34,0x35, - 0x33,0x32,0x34,0x36,0x33,0x32,0x34,0x37, - 0x33,0x32,0x34,0x38,0x33,0x32,0x34,0x39, - 0x33,0x32,0x35,0x30,0x33,0x32,0x35,0x31, - 0x33,0x32,0x35,0x32,0x33,0x32,0x35,0x33, - 0x33,0x32,0x35,0x34,0x33,0x32,0x35,0x35, - 0x33,0x32,0x35,0x36,0x33,0x32,0x35,0x37, - 0x33,0x32,0x35,0x38,0x33,0x32,0x35,0x39, - 0x33,0x32,0x36,0x30,0x33,0x32,0x36,0x31, - 0x33,0x32,0x36,0x32,0x33,0x32,0x36,0x33, - 0x33,0x32,0x36,0x34,0x33,0x32,0x36,0x35, - 0x33,0x32,0x36,0x36,0x33,0x32,0x36,0x37, - 0x33,0x32,0x36,0x38,0x33,0x32,0x36,0x39, - 0x33,0x32,0x37,0x30,0x33,0x32,0x37,0x31, - 0x33,0x32,0x37,0x32,0x33,0x32,0x37,0x33, - 0x33,0x32,0x37,0x34,0x33,0x32,0x37,0x35, - 0x33,0x32,0x37,0x36,0x33,0x32,0x37,0x37, - 0x33,0x32,0x37,0x38,0x33,0x32,0x37,0x39, - 0x33,0x32,0x38,0x30,0x33,0x32,0x38,0x31, - 0x33,0x32,0x38,0x32,0x33,0x32,0x38,0x33, - 0x33,0x32,0x38,0x34,0x33,0x32,0x38,0x35, - 0x33,0x32,0x38,0x36,0x33,0x32,0x38,0x37, - 0x33,0x32,0x38,0x38,0x33,0x32,0x38,0x39, - 0x33,0x32,0x39,0x30,0x33,0x32,0x39,0x31, - 0x33,0x32,0x39,0x32,0x33,0x32,0x39,0x33, - 0x33,0x32,0x39,0x34,0x33,0x32,0x39,0x35, - 0x33,0x32,0x39,0x36,0x33,0x32,0x39,0x37, - 0x33,0x32,0x39,0x38,0x33,0x32,0x39,0x39, - 0x33,0x33,0x30,0x30,0x33,0x33,0x30,0x31, - 0x33,0x33,0x30,0x32,0x33,0x33,0x30,0x33, - 0x33,0x33,0x30,0x34,0x33,0x33,0x30,0x35, - 0x33,0x33,0x30,0x36,0x33,0x33,0x30,0x37, - 0x33,0x33,0x30,0x38,0x33,0x33,0x30,0x39, - 0x33,0x33,0x31,0x30,0x33,0x33,0x31,0x31, - 0x33,0x33,0x31,0x32,0x33,0x33,0x31,0x33, - 0x33,0x33,0x31,0x34,0x33,0x33,0x31,0x35, - 0x33,0x33,0x31,0x36,0x33,0x33,0x31,0x37, - 0x33,0x33,0x31,0x38,0x33,0x33,0x31,0x39, - 0x33,0x33,0x32,0x30,0x33,0x33,0x32,0x31, - 0x33,0x33,0x32,0x32,0x33,0x33,0x32,0x33, - 0x33,0x33,0x32,0x34,0x33,0x33,0x32,0x35, - 0x33,0x33,0x32,0x36,0x33,0x33,0x32,0x37, - 0x33,0x33,0x32,0x38,0x33,0x33,0x32,0x39, - 0x33,0x33,0x33,0x30,0x33,0x33,0x33,0x31, - 0x33,0x33,0x33,0x32,0x33,0x33,0x33,0x33, - 0x33,0x33,0x33,0x34,0x33,0x33,0x33,0x35, - 0x33,0x33,0x33,0x36,0x33,0x33,0x33,0x37, - 0x33,0x33,0x33,0x38,0x33,0x33,0x33,0x39, - 0x33,0x33,0x34,0x30,0x33,0x33,0x34,0x31, - 0x33,0x33,0x34,0x32,0x33,0x33,0x34,0x33, - 0x33,0x33,0x34,0x34,0x33,0x33,0x34,0x35, - 0x33,0x33,0x34,0x36,0x33,0x33,0x34,0x37, - 0x33,0x33,0x34,0x38,0x33,0x33,0x34,0x39, - 0x33,0x33,0x35,0x30,0x33,0x33,0x35,0x31, - 0x33,0x33,0x35,0x32,0x33,0x33,0x35,0x33, - 0x33,0x33,0x35,0x34,0x33,0x33,0x35,0x35, - 0x33,0x33,0x35,0x36,0x33,0x33,0x35,0x37, - 0x33,0x33,0x35,0x38,0x33,0x33,0x35,0x39, - 0x33,0x33,0x36,0x30,0x33,0x33,0x36,0x31, - 0x33,0x33,0x36,0x32,0x33,0x33,0x36,0x33, - 0x33,0x33,0x36,0x34,0x33,0x33,0x36,0x35, - 0x33,0x33,0x36,0x36,0x33,0x33,0x36,0x37, - 0x33,0x33,0x36,0x38,0x33,0x33,0x36,0x39, - 0x33,0x33,0x37,0x30,0x33,0x33,0x37,0x31, - 0x33,0x33,0x37,0x32,0x33,0x33,0x37,0x33, - 0x33,0x33,0x37,0x34,0x33,0x33,0x37,0x35, - 0x33,0x33,0x37,0x36,0x33,0x33,0x37,0x37, - 0x33,0x33,0x37,0x38,0x33,0x33,0x37,0x39, - 0x33,0x33,0x38,0x30,0x33,0x33,0x38,0x31, - 0x33,0x33,0x38,0x32,0x33,0x33,0x38,0x33, - 0x33,0x33,0x38,0x34,0x33,0x33,0x38,0x35, - 0x33,0x33,0x38,0x36,0x33,0x33,0x38,0x37, - 0x33,0x33,0x38,0x38,0x33,0x33,0x38,0x39, - 0x33,0x33,0x39,0x30,0x33,0x33,0x39,0x31, - 0x33,0x33,0x39,0x32,0x33,0x33,0x39,0x33, - 0x33,0x33,0x39,0x34,0x33,0x33,0x39,0x35, - 0x33,0x33,0x39,0x36,0x33,0x33,0x39,0x37, - 0x33,0x33,0x39,0x38,0x33,0x33,0x39,0x39, - 0x33,0x34,0x30,0x30,0x33,0x34,0x30,0x31, - 0x33,0x34,0x30,0x32,0x33,0x34,0x30,0x33, - 0x33,0x34,0x30,0x34,0x33,0x34,0x30,0x35, - 0x33,0x34,0x30,0x36,0x33,0x34,0x30,0x37, - 0x33,0x34,0x30,0x38,0x33,0x34,0x30,0x39, - 0x33,0x34,0x31,0x30,0x33,0x34,0x31,0x31, - 0x33,0x34,0x31,0x32,0x33,0x34,0x31,0x33, - 0x33,0x34,0x31,0x34,0x33,0x34,0x31,0x35, - 0x33,0x34,0x31,0x36,0x33,0x34,0x31,0x37, - 0x33,0x34,0x31,0x38,0x33,0x34,0x31,0x39, - 0x33,0x34,0x32,0x30,0x33,0x34,0x32,0x31, - 0x33,0x34,0x32,0x32,0x33,0x34,0x32,0x33, - 0x33,0x34,0x32,0x34,0x33,0x34,0x32,0x35, - 0x33,0x34,0x32,0x36,0x33,0x34,0x32,0x37, - 0x33,0x34,0x32,0x38,0x33,0x34,0x32,0x39, - 0x33,0x34,0x33,0x30,0x33,0x34,0x33,0x31, - 0x33,0x34,0x33,0x32,0x33,0x34,0x33,0x33, - 0x33,0x34,0x33,0x34,0x33,0x34,0x33,0x35, - 0x33,0x34,0x33,0x36,0x33,0x34,0x33,0x37, - 0x33,0x34,0x33,0x38,0x33,0x34,0x33,0x39, - 0x33,0x34,0x34,0x30,0x33,0x34,0x34,0x31, - 0x33,0x34,0x34,0x32,0x33,0x34,0x34,0x33, - 0x33,0x34,0x34,0x34,0x33,0x34,0x34,0x35, - 0x33,0x34,0x34,0x36,0x33,0x34,0x34,0x37, - 0x33,0x34,0x34,0x38,0x33,0x34,0x34,0x39, - 0x33,0x34,0x35,0x30,0x33,0x34,0x35,0x31, - 0x33,0x34,0x35,0x32,0x33,0x34,0x35,0x33, - 0x33,0x34,0x35,0x34,0x33,0x34,0x35,0x35, - 0x33,0x34,0x35,0x36,0x33,0x34,0x35,0x37, - 0x33,0x34,0x35,0x38,0x33,0x34,0x35,0x39, - 0x33,0x34,0x36,0x30,0x33,0x34,0x36,0x31, - 0x33,0x34,0x36,0x32,0x33,0x34,0x36,0x33, - 0x33,0x34,0x36,0x34,0x33,0x34,0x36,0x35, - 0x33,0x34,0x36,0x36,0x33,0x34,0x36,0x37, - 0x33,0x34,0x36,0x38,0x33,0x34,0x36,0x39, - 0x33,0x34,0x37,0x30,0x33,0x34,0x37,0x31, - 0x33,0x34,0x37,0x32,0x33,0x34,0x37,0x33, - 0x33,0x34,0x37,0x34,0x33,0x34,0x37,0x35, - 0x33,0x34,0x37,0x36,0x33,0x34,0x37,0x37, - 0x33,0x34,0x37,0x38,0x33,0x34,0x37,0x39, - 0x33,0x34,0x38,0x30,0x33,0x34,0x38,0x31, - 0x33,0x34,0x38,0x32,0x33,0x34,0x38,0x33, - 0x33,0x34,0x38,0x34,0x33,0x34,0x38,0x35, - 0x33,0x34,0x38,0x36,0x33,0x34,0x38,0x37, - 0x33,0x34,0x38,0x38,0x33,0x34,0x38,0x39, - 0x33,0x34,0x39,0x30,0x33,0x34,0x39,0x31, - 0x33,0x34,0x39,0x32,0x33,0x34,0x39,0x33, - 0x33,0x34,0x39,0x34,0x33,0x34,0x39,0x35, - 0x33,0x34,0x39,0x36,0x33,0x34,0x39,0x37, - 0x33,0x34,0x39,0x38,0x33,0x34,0x39,0x39, - 0x33,0x35,0x30,0x30,0x33,0x35,0x30,0x31, - 0x33,0x35,0x30,0x32,0x33,0x35,0x30,0x33, - 0x33,0x35,0x30,0x34,0x33,0x35,0x30,0x35, - 0x33,0x35,0x30,0x36,0x33,0x35,0x30,0x37, - 0x33,0x35,0x30,0x38,0x33,0x35,0x30,0x39, - 0x33,0x35,0x31,0x30,0x33,0x35,0x31,0x31, - 0x33,0x35,0x31,0x32,0x33,0x35,0x31,0x33, - 0x33,0x35,0x31,0x34,0x33,0x35,0x31,0x35, - 0x33,0x35,0x31,0x36,0x33,0x35,0x31,0x37, - 0x33,0x35,0x31,0x38,0x33,0x35,0x31,0x39, - 0x33,0x35,0x32,0x30,0x33,0x35,0x32,0x31, - 0x33,0x35,0x32,0x32,0x33,0x35,0x32,0x33, - 0x33,0x35,0x32,0x34,0x33,0x35,0x32,0x35, - 0x33,0x35,0x32,0x36,0x33,0x35,0x32,0x37, - 0x33,0x35,0x32,0x38,0x33,0x35,0x32,0x39, - 0x33,0x35,0x33,0x30,0x33,0x35,0x33,0x31, - 0x33,0x35,0x33,0x32,0x33,0x35,0x33,0x33, - 0x33,0x35,0x33,0x34,0x33,0x35,0x33,0x35, - 0x33,0x35,0x33,0x36,0x33,0x35,0x33,0x37, - 0x33,0x35,0x33,0x38,0x33,0x35,0x33,0x39, - 0x33,0x35,0x34,0x30,0x33,0x35,0x34,0x31, - 0x33,0x35,0x34,0x32,0x33,0x35,0x34,0x33, - 0x33,0x35,0x34,0x34,0x33,0x35,0x34,0x35, - 0x33,0x35,0x34,0x36,0x33,0x35,0x34,0x37, - 0x33,0x35,0x34,0x38,0x33,0x35,0x34,0x39, - 0x33,0x35,0x35,0x30,0x33,0x35,0x35,0x31, - 0x33,0x35,0x35,0x32,0x33,0x35,0x35,0x33, - 0x33,0x35,0x35,0x34,0x33,0x35,0x35,0x35, - 0x33,0x35,0x35,0x36,0x33,0x35,0x35,0x37, - 0x33,0x35,0x35,0x38,0x33,0x35,0x35,0x39, - 0x33,0x35,0x36,0x30,0x33,0x35,0x36,0x31, - 0x33,0x35,0x36,0x32,0x33,0x35,0x36,0x33, - 0x33,0x35,0x36,0x34,0x33,0x35,0x36,0x35, - 0x33,0x35,0x36,0x36,0x33,0x35,0x36,0x37, - 0x33,0x35,0x36,0x38,0x33,0x35,0x36,0x39, - 0x33,0x35,0x37,0x30,0x33,0x35,0x37,0x31, - 0x33,0x35,0x37,0x32,0x33,0x35,0x37,0x33, - 0x33,0x35,0x37,0x34,0x33,0x35,0x37,0x35, - 0x33,0x35,0x37,0x36,0x33,0x35,0x37,0x37, - 0x33,0x35,0x37,0x38,0x33,0x35,0x37,0x39, - 0x33,0x35,0x38,0x30,0x33,0x35,0x38,0x31, - 0x33,0x35,0x38,0x32,0x33,0x35,0x38,0x33, - 0x33,0x35,0x38,0x34,0x33,0x35,0x38,0x35, - 0x33,0x35,0x38,0x36,0x33,0x35,0x38,0x37, - 0x33,0x35,0x38,0x38,0x33,0x35,0x38,0x39, - 0x33,0x35,0x39,0x30,0x33,0x35,0x39,0x31, - 0x33,0x35,0x39,0x32,0x33,0x35,0x39,0x33, - 0x33,0x35,0x39,0x34,0x33,0x35,0x39,0x35, - 0x33,0x35,0x39,0x36,0x33,0x35,0x39,0x37, - 0x33,0x35,0x39,0x38,0x33,0x35,0x39,0x39, - 0x33,0x36,0x30,0x30,0x33,0x36,0x30,0x31, - 0x33,0x36,0x30,0x32,0x33,0x36,0x30,0x33, - 0x33,0x36,0x30,0x34,0x33,0x36,0x30,0x35, - 0x33,0x36,0x30,0x36,0x33,0x36,0x30,0x37, - 0x33,0x36,0x30,0x38,0x33,0x36,0x30,0x39, - 0x33,0x36,0x31,0x30,0x33,0x36,0x31,0x31, - 0x33,0x36,0x31,0x32,0x33,0x36,0x31,0x33, - 0x33,0x36,0x31,0x34,0x33,0x36,0x31,0x35, - 0x33,0x36,0x31,0x36,0x33,0x36,0x31,0x37, - 0x33,0x36,0x31,0x38,0x33,0x36,0x31,0x39, - 0x33,0x36,0x32,0x30,0x33,0x36,0x32,0x31, - 0x33,0x36,0x32,0x32,0x33,0x36,0x32,0x33, - 0x33,0x36,0x32,0x34,0x33,0x36,0x32,0x35, - 0x33,0x36,0x32,0x36,0x33,0x36,0x32,0x37, - 0x33,0x36,0x32,0x38,0x33,0x36,0x32,0x39, - 0x33,0x36,0x33,0x30,0x33,0x36,0x33,0x31, - 0x33,0x36,0x33,0x32,0x33,0x36,0x33,0x33, - 0x33,0x36,0x33,0x34,0x33,0x36,0x33,0x35, - 0x33,0x36,0x33,0x36,0x33,0x36,0x33,0x37, - 0x33,0x36,0x33,0x38,0x33,0x36,0x33,0x39, - 0x33,0x36,0x34,0x30,0x33,0x36,0x34,0x31, - 0x33,0x36,0x34,0x32,0x33,0x36,0x34,0x33, - 0x33,0x36,0x34,0x34,0x33,0x36,0x34,0x35, - 0x33,0x36,0x34,0x36,0x33,0x36,0x34,0x37, - 0x33,0x36,0x34,0x38,0x33,0x36,0x34,0x39, - 0x33,0x36,0x35,0x30,0x33,0x36,0x35,0x31, - 0x33,0x36,0x35,0x32,0x33,0x36,0x35,0x33, - 0x33,0x36,0x35,0x34,0x33,0x36,0x35,0x35, - 0x33,0x36,0x35,0x36,0x33,0x36,0x35,0x37, - 0x33,0x36,0x35,0x38,0x33,0x36,0x35,0x39, - 0x33,0x36,0x36,0x30,0x33,0x36,0x36,0x31, - 0x33,0x36,0x36,0x32,0x33,0x36,0x36,0x33, - 0x33,0x36,0x36,0x34,0x33,0x36,0x36,0x35, - 0x33,0x36,0x36,0x36,0x33,0x36,0x36,0x37, - 0x33,0x36,0x36,0x38,0x33,0x36,0x36,0x39, - 0x33,0x36,0x37,0x30,0x33,0x36,0x37,0x31, - 0x33,0x36,0x37,0x32,0x33,0x36,0x37,0x33, - 0x33,0x36,0x37,0x34,0x33,0x36,0x37,0x35, - 0x33,0x36,0x37,0x36,0x33,0x36,0x37,0x37, - 0x33,0x36,0x37,0x38,0x33,0x36,0x37,0x39, - 0x33,0x36,0x38,0x30,0x33,0x36,0x38,0x31, - 0x33,0x36,0x38,0x32,0x33,0x36,0x38,0x33, - 0x33,0x36,0x38,0x34,0x33,0x36,0x38,0x35, - 0x33,0x36,0x38,0x36,0x33,0x36,0x38,0x37, - 0x33,0x36,0x38,0x38,0x33,0x36,0x38,0x39, - 0x33,0x36,0x39,0x30,0x33,0x36,0x39,0x31, - 0x33,0x36,0x39,0x32,0x33,0x36,0x39,0x33, - 0x33,0x36,0x39,0x34,0x33,0x36,0x39,0x35, - 0x33,0x36,0x39,0x36,0x33,0x36,0x39,0x37, - 0x33,0x36,0x39,0x38,0x33,0x36,0x39,0x39, - 0x33,0x37,0x30,0x30,0x33,0x37,0x30,0x31, - 0x33,0x37,0x30,0x32,0x33,0x37,0x30,0x33, - 0x33,0x37,0x30,0x34,0x33,0x37,0x30,0x35, - 0x33,0x37,0x30,0x36,0x33,0x37,0x30,0x37, - 0x33,0x37,0x30,0x38,0x33,0x37,0x30,0x39, - 0x33,0x37,0x31,0x30,0x33,0x37,0x31,0x31, - 0x33,0x37,0x31,0x32,0x33,0x37,0x31,0x33, - 0x33,0x37,0x31,0x34,0x33,0x37,0x31,0x35, - 0x33,0x37,0x31,0x36,0x33,0x37,0x31,0x37, - 0x33,0x37,0x31,0x38,0x33,0x37,0x31,0x39, - 0x33,0x37,0x32,0x30,0x33,0x37,0x32,0x31, - 0x33,0x37,0x32,0x32,0x33,0x37,0x32,0x33, - 0x33,0x37,0x32,0x34,0x33,0x37,0x32,0x35, - 0x33,0x37,0x32,0x36,0x33,0x37,0x32,0x37, - 0x33,0x37,0x32,0x38,0x33,0x37,0x32,0x39, - 0x33,0x37,0x33,0x30,0x33,0x37,0x33,0x31, - 0x33,0x37,0x33,0x32,0x33,0x37,0x33,0x33, - 0x33,0x37,0x33,0x34,0x33,0x37,0x33,0x35, - 0x33,0x37,0x33,0x36,0x33,0x37,0x33,0x37, - 0x33,0x37,0x33,0x38,0x33,0x37,0x33,0x39, - 0x33,0x37,0x34,0x30,0x33,0x37,0x34,0x31, - 0x33,0x37,0x34,0x32,0x33,0x37,0x34,0x33, - 0x33,0x37,0x34,0x34,0x33,0x37,0x34,0x35, - 0x33,0x37,0x34,0x36,0x33,0x37,0x34,0x37, - 0x33,0x37,0x34,0x38,0x33,0x37,0x34,0x39, - 0x33,0x37,0x35,0x30,0x33,0x37,0x35,0x31, - 0x33,0x37,0x35,0x32,0x33,0x37,0x35,0x33, - 0x33,0x37,0x35,0x34,0x33,0x37,0x35,0x35, - 0x33,0x37,0x35,0x36,0x33,0x37,0x35,0x37, - 0x33,0x37,0x35,0x38,0x33,0x37,0x35,0x39, - 0x33,0x37,0x36,0x30,0x33,0x37,0x36,0x31, - 0x33,0x37,0x36,0x32,0x33,0x37,0x36,0x33, - 0x33,0x37,0x36,0x34,0x33,0x37,0x36,0x35, - 0x33,0x37,0x36,0x36,0x33,0x37,0x36,0x37, - 0x33,0x37,0x36,0x38,0x33,0x37,0x36,0x39, - 0x33,0x37,0x37,0x30,0x33,0x37,0x37,0x31, - 0x33,0x37,0x37,0x32,0x33,0x37,0x37,0x33, - 0x33,0x37,0x37,0x34,0x33,0x37,0x37,0x35, - 0x33,0x37,0x37,0x36,0x33,0x37,0x37,0x37, - 0x33,0x37,0x37,0x38,0x33,0x37,0x37,0x39, - 0x33,0x37,0x38,0x30,0x33,0x37,0x38,0x31, - 0x33,0x37,0x38,0x32,0x33,0x37,0x38,0x33, - 0x33,0x37,0x38,0x34,0x33,0x37,0x38,0x35, - 0x33,0x37,0x38,0x36,0x33,0x37,0x38,0x37, - 0x33,0x37,0x38,0x38,0x33,0x37,0x38,0x39, - 0x33,0x37,0x39,0x30,0x33,0x37,0x39,0x31, - 0x33,0x37,0x39,0x32,0x33,0x37,0x39,0x33, - 0x33,0x37,0x39,0x34,0x33,0x37,0x39,0x35, - 0x33,0x37,0x39,0x36,0x33,0x37,0x39,0x37, - 0x33,0x37,0x39,0x38,0x33,0x37,0x39,0x39, - 0x33,0x38,0x30,0x30,0x33,0x38,0x30,0x31, - 0x33,0x38,0x30,0x32,0x33,0x38,0x30,0x33, - 0x33,0x38,0x30,0x34,0x33,0x38,0x30,0x35, - 0x33,0x38,0x30,0x36,0x33,0x38,0x30,0x37, - 0x33,0x38,0x30,0x38,0x33,0x38,0x30,0x39, - 0x33,0x38,0x31,0x30,0x33,0x38,0x31,0x31, - 0x33,0x38,0x31,0x32,0x33,0x38,0x31,0x33, - 0x33,0x38,0x31,0x34,0x33,0x38,0x31,0x35, - 0x33,0x38,0x31,0x36,0x33,0x38,0x31,0x37, - 0x33,0x38,0x31,0x38,0x33,0x38,0x31,0x39, - 0x33,0x38,0x32,0x30,0x33,0x38,0x32,0x31, - 0x33,0x38,0x32,0x32,0x33,0x38,0x32,0x33, - 0x33,0x38,0x32,0x34,0x33,0x38,0x32,0x35, - 0x33,0x38,0x32,0x36,0x33,0x38,0x32,0x37, - 0x33,0x38,0x32,0x38,0x33,0x38,0x32,0x39, - 0x33,0x38,0x33,0x30,0x33,0x38,0x33,0x31, - 0x33,0x38,0x33,0x32,0x33,0x38,0x33,0x33, - 0x33,0x38,0x33,0x34,0x33,0x38,0x33,0x35, - 0x33,0x38,0x33,0x36,0x33,0x38,0x33,0x37, - 0x33,0x38,0x33,0x38,0x33,0x38,0x33,0x39, - 0x33,0x38,0x34,0x30,0x33,0x38,0x34,0x31, - 0x33,0x38,0x34,0x32,0x33,0x38,0x34,0x33, - 0x33,0x38,0x34,0x34,0x33,0x38,0x34,0x35, - 0x33,0x38,0x34,0x36,0x33,0x38,0x34,0x37, - 0x33,0x38,0x34,0x38,0x33,0x38,0x34,0x39, - 0x33,0x38,0x35,0x30,0x33,0x38,0x35,0x31, - 0x33,0x38,0x35,0x32,0x33,0x38,0x35,0x33, - 0x33,0x38,0x35,0x34,0x33,0x38,0x35,0x35, - 0x33,0x38,0x35,0x36,0x33,0x38,0x35,0x37, - 0x33,0x38,0x35,0x38,0x33,0x38,0x35,0x39, - 0x33,0x38,0x36,0x30,0x33,0x38,0x36,0x31, - 0x33,0x38,0x36,0x32,0x33,0x38,0x36,0x33, - 0x33,0x38,0x36,0x34,0x33,0x38,0x36,0x35, - 0x33,0x38,0x36,0x36,0x33,0x38,0x36,0x37, - 0x33,0x38,0x36,0x38,0x33,0x38,0x36,0x39, - 0x33,0x38,0x37,0x30,0x33,0x38,0x37,0x31, - 0x33,0x38,0x37,0x32,0x33,0x38,0x37,0x33, - 0x33,0x38,0x37,0x34,0x33,0x38,0x37,0x35, - 0x33,0x38,0x37,0x36,0x33,0x38,0x37,0x37, - 0x33,0x38,0x37,0x38,0x33,0x38,0x37,0x39, - 0x33,0x38,0x38,0x30,0x33,0x38,0x38,0x31, - 0x33,0x38,0x38,0x32,0x33,0x38,0x38,0x33, - 0x33,0x38,0x38,0x34,0x33,0x38,0x38,0x35, - 0x33,0x38,0x38,0x36,0x33,0x38,0x38,0x37, - 0x33,0x38,0x38,0x38,0x33,0x38,0x38,0x39, - 0x33,0x38,0x39,0x30,0x33,0x38,0x39,0x31, - 0x33,0x38,0x39,0x32,0x33,0x38,0x39,0x33, - 0x33,0x38,0x39,0x34,0x33,0x38,0x39,0x35, - 0x33,0x38,0x39,0x36,0x33,0x38,0x39,0x37, - 0x33,0x38,0x39,0x38,0x33,0x38,0x39,0x39, - 0x33,0x39,0x30,0x30,0x33,0x39,0x30,0x31, - 0x33,0x39,0x30,0x32,0x33,0x39,0x30,0x33, - 0x33,0x39,0x30,0x34,0x33,0x39,0x30,0x35, - 0x33,0x39,0x30,0x36,0x33,0x39,0x30,0x37, - 0x33,0x39,0x30,0x38,0x33,0x39,0x30,0x39, - 0x33,0x39,0x31,0x30,0x33,0x39,0x31,0x31, - 0x33,0x39,0x31,0x32,0x33,0x39,0x31,0x33, - 0x33,0x39,0x31,0x34,0x33,0x39,0x31,0x35, - 0x33,0x39,0x31,0x36,0x33,0x39,0x31,0x37, - 0x33,0x39,0x31,0x38,0x33,0x39,0x31,0x39, - 0x33,0x39,0x32,0x30,0x33,0x39,0x32,0x31, - 0x33,0x39,0x32,0x32,0x33,0x39,0x32,0x33, - 0x33,0x39,0x32,0x34,0x33,0x39,0x32,0x35, - 0x33,0x39,0x32,0x36,0x33,0x39,0x32,0x37, - 0x33,0x39,0x32,0x38,0x33,0x39,0x32,0x39, - 0x33,0x39,0x33,0x30,0x33,0x39,0x33,0x31, - 0x33,0x39,0x33,0x32,0x33,0x39,0x33,0x33, - 0x33,0x39,0x33,0x34,0x33,0x39,0x33,0x35, - 0x33,0x39,0x33,0x36,0x33,0x39,0x33,0x37, - 0x33,0x39,0x33,0x38,0x33,0x39,0x33,0x39, - 0x33,0x39,0x34,0x30,0x33,0x39,0x34,0x31, - 0x33,0x39,0x34,0x32,0x33,0x39,0x34,0x33, - 0x33,0x39,0x34,0x34,0x33,0x39,0x34,0x35, - 0x33,0x39,0x34,0x36,0x33,0x39,0x34,0x37, - 0x33,0x39,0x34,0x38,0x33,0x39,0x34,0x39, - 0x33,0x39,0x35,0x30,0x33,0x39,0x35,0x31, - 0x33,0x39,0x35,0x32,0x33,0x39,0x35,0x33, - 0x33,0x39,0x35,0x34,0x33,0x39,0x35,0x35, - 0x33,0x39,0x35,0x36,0x33,0x39,0x35,0x37, - 0x33,0x39,0x35,0x38,0x33,0x39,0x35,0x39, - 0x33,0x39,0x36,0x30,0x33,0x39,0x36,0x31, - 0x33,0x39,0x36,0x32,0x33,0x39,0x36,0x33, - 0x33,0x39,0x36,0x34,0x33,0x39,0x36,0x35, - 0x33,0x39,0x36,0x36,0x33,0x39,0x36,0x37, - 0x33,0x39,0x36,0x38,0x33,0x39,0x36,0x39, - 0x33,0x39,0x37,0x30,0x33,0x39,0x37,0x31, - 0x33,0x39,0x37,0x32,0x33,0x39,0x37,0x33, - 0x33,0x39,0x37,0x34,0x33,0x39,0x37,0x35, - 0x33,0x39,0x37,0x36,0x33,0x39,0x37,0x37, - 0x33,0x39,0x37,0x38,0x33,0x39,0x37,0x39, - 0x33,0x39,0x38,0x30,0x33,0x39,0x38,0x31, - 0x33,0x39,0x38,0x32,0x33,0x39,0x38,0x33, - 0x33,0x39,0x38,0x34,0x33,0x39,0x38,0x35, - 0x33,0x39,0x38,0x36,0x33,0x39,0x38,0x37, - 0x33,0x39,0x38,0x38,0x33,0x39,0x38,0x39, - 0x33,0x39,0x39,0x30,0x33,0x39,0x39,0x31, - 0x33,0x39,0x39,0x32,0x33,0x39,0x39,0x33, - 0x33,0x39,0x39,0x34,0x33,0x39,0x39,0x35, - 0x33,0x39,0x39,0x36,0x33,0x39,0x39,0x37, - 0x33,0x39,0x39,0x38,0x33,0x39,0x39,0x39, - 0x34,0x30,0x30,0x30,0x34,0x30,0x30,0x31, - 0x34,0x30,0x30,0x32,0x34,0x30,0x30,0x33, - 0x34,0x30,0x30,0x34,0x34,0x30,0x30,0x35, - 0x34,0x30,0x30,0x36,0x34,0x30,0x30,0x37, - 0x34,0x30,0x30,0x38,0x34,0x30,0x30,0x39, - 0x34,0x30,0x31,0x30,0x34,0x30,0x31,0x31, - 0x34,0x30,0x31,0x32,0x34,0x30,0x31,0x33, - 0x34,0x30,0x31,0x34,0x34,0x30,0x31,0x35, - 0x34,0x30,0x31,0x36,0x34,0x30,0x31,0x37, - 0x34,0x30,0x31,0x38,0x34,0x30,0x31,0x39, - 0x34,0x30,0x32,0x30,0x34,0x30,0x32,0x31, - 0x34,0x30,0x32,0x32,0x34,0x30,0x32,0x33, - 0x34,0x30,0x32,0x34,0x34,0x30,0x32,0x35, - 0x34,0x30,0x32,0x36,0x34,0x30,0x32,0x37, - 0x34,0x30,0x32,0x38,0x34,0x30,0x32,0x39, - 0x34,0x30,0x33,0x30,0x34,0x30,0x33,0x31, - 0x34,0x30,0x33,0x32,0x34,0x30,0x33,0x33, - 0x34,0x30,0x33,0x34,0x34,0x30,0x33,0x35, - 0x34,0x30,0x33,0x36,0x34,0x30,0x33,0x37, - 0x34,0x30,0x33,0x38,0x34,0x30,0x33,0x39, - 0x34,0x30,0x34,0x30,0x34,0x30,0x34,0x31, - 0x34,0x30,0x34,0x32,0x34,0x30,0x34,0x33, - 0x34,0x30,0x34,0x34,0x34,0x30,0x34,0x35, - 0x34,0x30,0x34,0x36,0x34,0x30,0x34,0x37, - 0x34,0x30,0x34,0x38,0x34,0x30,0x34,0x39, - 0x34,0x30,0x35,0x30,0x34,0x30,0x35,0x31, - 0x34,0x30,0x35,0x32,0x34,0x30,0x35,0x33, - 0x34,0x30,0x35,0x34,0x34,0x30,0x35,0x35, - 0x34,0x30,0x35,0x36,0x34,0x30,0x35,0x37, - 0x34,0x30,0x35,0x38,0x34,0x30,0x35,0x39, - 0x34,0x30,0x36,0x30,0x34,0x30,0x36,0x31, - 0x34,0x30,0x36,0x32,0x34,0x30,0x36,0x33, - 0x34,0x30,0x36,0x34,0x34,0x30,0x36,0x35, - 0x34,0x30,0x36,0x36,0x34,0x30,0x36,0x37, - 0x34,0x30,0x36,0x38,0x34,0x30,0x36,0x39, - 0x34,0x30,0x37,0x30,0x34,0x30,0x37,0x31, - 0x34,0x30,0x37,0x32,0x34,0x30,0x37,0x33, - 0x34,0x30,0x37,0x34,0x34,0x30,0x37,0x35, - 0x34,0x30,0x37,0x36,0x34,0x30,0x37,0x37, - 0x34,0x30,0x37,0x38,0x34,0x30,0x37,0x39, - 0x34,0x30,0x38,0x30,0x34,0x30,0x38,0x31, - 0x34,0x30,0x38,0x32,0x34,0x30,0x38,0x33, - 0x34,0x30,0x38,0x34,0x34,0x30,0x38,0x35, - 0x34,0x30,0x38,0x36,0x34,0x30,0x38,0x37, - 0x34,0x30,0x38,0x38,0x34,0x30,0x38,0x39, - 0x34,0x30,0x39,0x30,0x34,0x30,0x39,0x31, - 0x34,0x30,0x39,0x32,0x34,0x30,0x39,0x33, - 0x34,0x30,0x39,0x34,0x34,0x30,0x39,0x35}; - - static const int32_t gDigitCount[] = { - 1,1,1,1,1,1,1,1, - 1,1,2,2,2,2,2,2, - 2,2,2,2,2,2,2,2, - 2,2,2,2,2,2,2,2, - 2,2,2,2,2,2,2,2, - 2,2,2,2,2,2,2,2, - 2,2,2,2,2,2,2,2, - 2,2,2,2,2,2,2,2, - 2,2,2,2,2,2,2,2, - 2,2,2,2,2,2,2,2, - 2,2,2,2,2,2,2,2, - 2,2,2,2,2,2,2,2, - 2,2,2,2,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4}; - -U_NAMESPACE_BEGIN - - -IntDigitCountRange::IntDigitCountRange(int32_t min, int32_t max) { - fMin = min < 0 ? 0 : min; - fMax = max < fMin ? fMin : max; -} - -int32_t -IntDigitCountRange::pin(int32_t digitCount) const { - return digitCount < fMin ? fMin : (digitCount < fMax ? digitCount : fMax); -} - -int32_t -SmallIntFormatter::estimateDigitCount( - int32_t positiveValue, const IntDigitCountRange &range) { - if (positiveValue >= gMaxFastInt) { - return range.getMax(); - } - return range.pin(gDigitCount[positiveValue]); -} - -UBool -SmallIntFormatter::canFormat( - int32_t positiveValue, const IntDigitCountRange &range) { - return (positiveValue < gMaxFastInt && range.getMin() <= 4); -} - -UnicodeString & -SmallIntFormatter::format( - int32_t smallPositiveValue, - const IntDigitCountRange &range, - UnicodeString &appendTo) { - int32_t digits = range.pin(gDigitCount[smallPositiveValue]); - - // Always emit at least '0' - if (digits == 0) { - return appendTo.append((UChar) 0x30); - } - return appendTo.append(gDigits, ((smallPositiveValue + 1) << 2) - digits, digits); -} - -U_NAMESPACE_END - diff --git a/icu4c/source/i18n/smallintformatter.h b/icu4c/source/i18n/smallintformatter.h deleted file mode 100644 index b309b55c18..0000000000 --- a/icu4c/source/i18n/smallintformatter.h +++ /dev/null @@ -1,90 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* Copyright (C) 2015, International Business Machines -* Corporation and others. All Rights Reserved. -******************************************************************************* -* smallintformatter.h -* -* created on: 2015jan06 -* created by: Travis Keep -*/ - -#ifndef __SMALLINTFORMATTER_H__ -#define __SMALLINTFORMATTER_H__ - -#include "unicode/uobject.h" -#include "unicode/utypes.h" - -U_NAMESPACE_BEGIN - -class UnicodeString; - -/** - * A representation an acceptable range of digit counts for integers. - */ -class U_I18N_API IntDigitCountRange : public UMemory { -public: - /** - * No constraints: 0 up to INT32_MAX - */ - IntDigitCountRange() : fMin(0), fMax(INT32_MAX) { } - IntDigitCountRange(int32_t min, int32_t max); - int32_t pin(int32_t digitCount) const; - int32_t getMax() const { return fMax; } - int32_t getMin() const { return fMin; } -private: - int32_t fMin; - int32_t fMax; -}; - - -/** - * A formatter for small, positive integers. - */ -class U_I18N_API SmallIntFormatter : public UMemory { -public: - /** - * Estimates the actual digit count needed to format positiveValue - * using the given range of digit counts. - * Returns a value that is at least the actual digit count needed. - * - * @param positiveValue the value to format - * @param range the acceptable range of digit counts. - */ - static int32_t estimateDigitCount( - int32_t positiveValue, const IntDigitCountRange &range); - - /** - * Returns TRUE if this class can format positiveValue using - * the given range of digit counts. - * - * @param positiveValue the value to format - * @param range the acceptable range of digit counts. - */ - static UBool canFormat( - int32_t positiveValue, const IntDigitCountRange &range); - - /** - * Formats positiveValue using the given range of digit counts. - * Always uses standard digits '0' through '9'. Formatted value is - * left padded with '0' as necessary to achieve minimum digit count. - * Does not produce any grouping separators or trailing decimal point. - * Calling format to format a value with a particular digit count range - * when canFormat indicates that the same value and digit count range - * cannot be formatted results in undefined behavior. - * - * @param positiveValue the value to format - * @param range the acceptable range of digit counts. - */ - static UnicodeString &format( - int32_t positiveValue, - const IntDigitCountRange &range, - UnicodeString &appendTo); - -}; - -U_NAMESPACE_END - -#endif // __SMALLINTFORMATTER_H__ diff --git a/icu4c/source/i18n/unicode/plurrule.h b/icu4c/source/i18n/unicode/plurrule.h index d372d79c84..7a1ad1d1a1 100644 --- a/icu4c/source/i18n/unicode/plurrule.h +++ b/icu4c/source/i18n/unicode/plurrule.h @@ -44,7 +44,6 @@ U_NAMESPACE_BEGIN class Hashtable; class IFixedDecimal; -class VisibleDigitsWithExponent; class RuleChain; class PluralRuleParser; class PluralKeywordEnumeration; @@ -368,10 +367,6 @@ public: * @internal */ UnicodeString select(const IFixedDecimal &number) const; - /** - * @internal - */ - UnicodeString select(const VisibleDigitsWithExponent &number) const; #endif /* U_HIDE_INTERNAL_API */ /** diff --git a/icu4c/source/i18n/valueformatter.cpp b/icu4c/source/i18n/valueformatter.cpp deleted file mode 100644 index e769f369d4..0000000000 --- a/icu4c/source/i18n/valueformatter.cpp +++ /dev/null @@ -1,223 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* Copyright (C) 2015, International Business Machines Corporation and -* others. All Rights Reserved. -******************************************************************************* -*/ - -#include "unicode/plurrule.h" -#include "unicode/unistr.h" -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING - -#include "digitformatter.h" -#include "digitgrouping.h" -#include "digitinterval.h" -#include "digitlst.h" -#include "precision.h" -#include "plurrule_impl.h" -#include "smallintformatter.h" -#include "uassert.h" -#include "valueformatter.h" -#include "visibledigits.h" - -U_NAMESPACE_BEGIN - -ValueFormatter::~ValueFormatter() {} - -VisibleDigitsWithExponent & -ValueFormatter::toVisibleDigitsWithExponent( - int64_t value, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const { - switch (fType) { - case kFixedDecimal: - return fFixedPrecision->initVisibleDigitsWithExponent( - value, digits, status); - break; - case kScientificNotation: - return fScientificPrecision->initVisibleDigitsWithExponent( - value, digits, status); - break; - default: - U_ASSERT(FALSE); - break; - } - return digits; -} - -VisibleDigitsWithExponent & -ValueFormatter::toVisibleDigitsWithExponent( - DigitList &value, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const { - switch (fType) { - case kFixedDecimal: - return fFixedPrecision->initVisibleDigitsWithExponent( - value, digits, status); - break; - case kScientificNotation: - return fScientificPrecision->initVisibleDigitsWithExponent( - value, digits, status); - break; - default: - U_ASSERT(FALSE); - break; - } - return digits; -} - -static UBool isNoGrouping( - const DigitGrouping &grouping, - int32_t value, - const FixedPrecision &precision) { - IntDigitCountRange range( - precision.fMin.getIntDigitCount(), - precision.fMax.getIntDigitCount()); - return grouping.isNoGrouping(value, range); -} - -UBool -ValueFormatter::isFastFormattable(int32_t value) const { - switch (fType) { - case kFixedDecimal: - { - if (value == INT32_MIN) { - return FALSE; - } - if (value < 0) { - value = -value; - } - return fFixedPrecision->isFastFormattable() && fFixedOptions->isFastFormattable() && isNoGrouping(*fGrouping, value, *fFixedPrecision); - } - case kScientificNotation: - return FALSE; - default: - U_ASSERT(FALSE); - break; - } - return FALSE; -} - -DigitList & -ValueFormatter::round(DigitList &value, UErrorCode &status) const { - if (value.isNaN() || value.isInfinite()) { - return value; - } - switch (fType) { - case kFixedDecimal: - return fFixedPrecision->round(value, 0, status); - case kScientificNotation: - return fScientificPrecision->round(value, status); - default: - U_ASSERT(FALSE); - break; - } - return value; -} - -UnicodeString & -ValueFormatter::formatInt32( - int32_t value, - FieldPositionHandler &handler, - UnicodeString &appendTo) const { - switch (fType) { - case kFixedDecimal: - { - IntDigitCountRange range( - fFixedPrecision->fMin.getIntDigitCount(), - fFixedPrecision->fMax.getIntDigitCount()); - return fDigitFormatter->formatPositiveInt32( - value, - range, - handler, - appendTo); - } - break; - case kScientificNotation: - default: - U_ASSERT(FALSE); - break; - } - return appendTo; -} - -UnicodeString & -ValueFormatter::format( - const VisibleDigitsWithExponent &value, - FieldPositionHandler &handler, - UnicodeString &appendTo) const { - switch (fType) { - case kFixedDecimal: - return fDigitFormatter->format( - value.getMantissa(), - *fGrouping, - *fFixedOptions, - handler, - appendTo); - break; - case kScientificNotation: - return fDigitFormatter->format( - value, - *fScientificOptions, - handler, - appendTo); - break; - default: - U_ASSERT(FALSE); - break; - } - return appendTo; -} - -int32_t -ValueFormatter::countChar32(const VisibleDigitsWithExponent &value) const { - switch (fType) { - case kFixedDecimal: - return fDigitFormatter->countChar32( - value.getMantissa(), - *fGrouping, - *fFixedOptions); - break; - case kScientificNotation: - return fDigitFormatter->countChar32( - value, - *fScientificOptions); - break; - default: - U_ASSERT(FALSE); - break; - } - return 0; -} - -void -ValueFormatter::prepareFixedDecimalFormatting( - const DigitFormatter &formatter, - const DigitGrouping &grouping, - const FixedPrecision &precision, - const DigitFormatterOptions &options) { - fType = kFixedDecimal; - fDigitFormatter = &formatter; - fGrouping = &grouping; - fFixedPrecision = &precision; - fFixedOptions = &options; -} - -void -ValueFormatter::prepareScientificFormatting( - const DigitFormatter &formatter, - const ScientificPrecision &precision, - const SciFormatterOptions &options) { - fType = kScientificNotation; - fDigitFormatter = &formatter; - fScientificPrecision = &precision; - fScientificOptions = &options; -} - -U_NAMESPACE_END - -#endif /* !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/valueformatter.h b/icu4c/source/i18n/valueformatter.h deleted file mode 100644 index 2802f187bb..0000000000 --- a/icu4c/source/i18n/valueformatter.h +++ /dev/null @@ -1,161 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* Copyright (C) 2015, International Business Machines Corporation and * -* others. All Rights Reserved. * -******************************************************************************* -*/ - -#ifndef VALUEFORMATTER_H -#define VALUEFORMATTER_H - -#if !UCONFIG_NO_FORMATTING - -#include "unicode/uobject.h" -#include "unicode/utypes.h" - - - -U_NAMESPACE_BEGIN - -class UnicodeString; -class DigitList; -class FieldPositionHandler; -class DigitGrouping; -class PluralRules; -class FixedPrecision; -class DigitFormatter; -class DigitFormatterOptions; -class ScientificPrecision; -class SciFormatterOptions; -class FixedDecimal; -class VisibleDigitsWithExponent; - - -/** - * A closure around rounding and formatting a value. As these instances are - * designed to be short lived (they only exist while formatting a value), they - * do not own their own attributes. Rather the caller maintains ownership of - * all attributes. A caller first calls a prepareXXX method on an instance - * to share its data before using that instance. Using an - * instance without first calling a prepareXXX method results in an - * assertion error and a program crash. - */ -class U_I18N_API ValueFormatter : public UObject { -public: - ValueFormatter() : fType(kFormatTypeCount) { - } - - virtual ~ValueFormatter(); - - /** - * This function is here only to support the protected round() method - * in DecimalFormat. It serves no ther purpose than that. - * - * @param value this value is rounded in place. - * @param status any error returned here. - */ - DigitList &round(DigitList &value, UErrorCode &status) const; - - /** - * Returns TRUE if the absolute value of value can be fast formatted - * using ValueFormatter::formatInt32. - */ - UBool isFastFormattable(int32_t value) const; - - /** - * Converts value to a VisibleDigitsWithExponent. - * Result may be fixed point or scientific. - */ - VisibleDigitsWithExponent &toVisibleDigitsWithExponent( - int64_t value, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const; - - /** - * Converts value to a VisibleDigitsWithExponent. - * Result may be fixed point or scientific. - */ - VisibleDigitsWithExponent &toVisibleDigitsWithExponent( - DigitList &value, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const; - - /** - * formats positiveValue and appends to appendTo. Returns appendTo. - * @param positiveValue If negative, no negative sign is formatted. - * @param handler stores the field positions - * @param appendTo formatted value appended here. - */ - UnicodeString &format( - const VisibleDigitsWithExponent &positiveValue, - FieldPositionHandler &handler, - UnicodeString &appendTo) const; - - - /** - * formats positiveValue and appends to appendTo. Returns appendTo. - * value must be positive. Calling formatInt32 to format a value when - * isFastFormattable indicates that the value cannot be fast formatted - * results in undefined behavior. - */ - UnicodeString &formatInt32( - int32_t positiveValue, - FieldPositionHandler &handler, - UnicodeString &appendTo) const; - - /** - * Returns the number of code points needed to format. - * @param positiveValue if negative, the negative sign is not included - * in count. - */ - int32_t countChar32( - const VisibleDigitsWithExponent &positiveValue) const; - - /** - * Prepares this instance for fixed decimal formatting. - */ - void prepareFixedDecimalFormatting( - const DigitFormatter &formatter, - const DigitGrouping &grouping, - const FixedPrecision &precision, - const DigitFormatterOptions &options); - - /** - * Prepares this instance for scientific formatting. - */ - void prepareScientificFormatting( - const DigitFormatter &formatter, - const ScientificPrecision &precision, - const SciFormatterOptions &options); - -private: - ValueFormatter(const ValueFormatter &); - ValueFormatter &operator=(const ValueFormatter &); - enum FormatType { - kFixedDecimal, - kScientificNotation, - kFormatTypeCount - }; - - FormatType fType; - - // for fixed decimal and scientific formatting - const DigitFormatter *fDigitFormatter; - - // for fixed decimal formatting - const FixedPrecision *fFixedPrecision; - const DigitFormatterOptions *fFixedOptions; - const DigitGrouping *fGrouping; - - // for scientific formatting - const ScientificPrecision *fScientificPrecision; - const SciFormatterOptions *fScientificOptions; -}; - -U_NAMESPACE_END - -#endif /* !UCONFIG_NO_FORMATTING */ - -#endif /* VALUEFORMATTER_H */ diff --git a/icu4c/source/i18n/visibledigits.cpp b/icu4c/source/i18n/visibledigits.cpp deleted file mode 100644 index 03cfc68d25..0000000000 --- a/icu4c/source/i18n/visibledigits.cpp +++ /dev/null @@ -1,186 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* - * Copyright (C) 2016, International Business Machines - * Corporation and others. All Rights Reserved. - * - * file name: visibledigits.cpp - */ - -#include - -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING - -#include "cstring.h" -#include "decNumber.h" -#include "digitlst.h" -#include "uassert.h" -#include "visibledigits.h" - -static const int32_t kNegative = 1; -static const int32_t kInfinite = 2; -static const int32_t kNaN = 4; - -U_NAMESPACE_BEGIN - -void VisibleDigits::setNegative() { - fFlags |= kNegative; -} - -void VisibleDigits::setNaN() { - fFlags |= kNaN; -} - -void VisibleDigits::setInfinite() { - fFlags |= kInfinite; -} - -void VisibleDigits::clear() { - fInterval.clear(); - fDigits.clear(); - fExponent = 0; - fFlags = 0; - fAbsIntValue = 0LL; - fAbsIntValueSet = FALSE; - fAbsDoubleValue = 0.0; - fAbsDoubleValueSet = FALSE; -} - -UBool VisibleDigits::isNegative() const { - return (fFlags & kNegative); -} - -UBool VisibleDigits::isNaN() const { - return (fFlags & kNaN); -} - -UBool VisibleDigits::isInfinite() const { - return (fFlags & kInfinite); -} - -int32_t VisibleDigits::getDigitByExponent(int32_t digitPos) const { - if (digitPos < fExponent || digitPos >= fExponent + fDigits.length()) { - return 0; - } - const char *ptr = fDigits.data(); - return ptr[digitPos - fExponent]; -} - -UBool VisibleDigits::isOverMaxDigits() const { - return (fExponent + fDigits.length() > fInterval.getMostSignificantExclusive()); -} - -UBool VisibleDigits::isNaNOrInfinity() const { - return (fFlags & (kInfinite | kNaN)) != 0; -} - -double VisibleDigits::computeAbsDoubleValue() const { - // Take care of NaN and infinity - if (isNaN()) { - return uprv_getNaN(); - } - if (isInfinite()) { - return uprv_getInfinity(); - } - - // stack allocate a decNumber to hold MAX_DBL_DIGITS+3 significant digits - struct { - decNumber decNum; - char digits[MAX_DBL_DIGITS+3]; - } decNumberWithStorage; - decNumber *numberPtr = &decNumberWithStorage.decNum; - - int32_t mostSig = fInterval.getMostSignificantExclusive(); - int32_t mostSigNonZero = fExponent + fDigits.length(); - int32_t end = mostSig > mostSigNonZero ? mostSigNonZero : mostSig; - int32_t leastSig = fInterval.getLeastSignificantInclusive(); - int32_t start = leastSig > fExponent ? leastSig : fExponent; - if (end <= start) { - return 0.0; - } - if (start < end - (MAX_DBL_DIGITS+3)) { - start = end - (MAX_DBL_DIGITS+3); - } - uint8_t *pos = numberPtr->lsu; - const char *src = &(fDigits.data()[start - fExponent]); - for (int32_t i = start; i < end; ++i) { - *pos++ = (uint8_t) (*src++); - } - numberPtr->exponent = start; - numberPtr->bits = 0; - numberPtr->digits = end - start; - char str[MAX_DBL_DIGITS+18]; - uprv_decNumberToString(numberPtr, str); - U_ASSERT(uprv_strlen(str) < MAX_DBL_DIGITS+18); - char *unused = NULL; - return DigitList::decimalStrToDouble(str, &unused); -} - -void VisibleDigits::getFixedDecimal( - double &source, int64_t &intValue, int64_t &f, int64_t &t, int32_t &v, UBool &hasIntValue) const { - source = 0.0; - intValue = 0; - f = 0; - t = 0; - v = 0; - hasIntValue = FALSE; - if (isNaNOrInfinity()) { - return; - } - - // source - if (fAbsDoubleValueSet) { - source = fAbsDoubleValue; - } else { - source = computeAbsDoubleValue(); - } - - // visible decimal digits - v = fInterval.getFracDigitCount(); - - // intValue - - // If we initialized from an int64 just use that instead of - // calculating - if (fAbsIntValueSet) { - intValue = fAbsIntValue; - } else { - int32_t startPos = fInterval.getMostSignificantExclusive(); - if (startPos > 18) { - startPos = 18; - } - // process the integer digits - for (int32_t i = startPos - 1; i >= 0; --i) { - intValue = intValue * 10LL + getDigitByExponent(i); - } - if (intValue == 0LL && startPos > 0) { - intValue = 100000000000000000LL; - } - } - - // f (decimal digits) - // skip over any leading 0's in fraction digits. - int32_t idx = -1; - for (; idx >= -v && getDigitByExponent(idx) == 0; --idx) - ; - - // Only process up to first 18 non zero fraction digits for decimalDigits - // since that is all we can fit into an int64. - for (int32_t i = idx; i >= -v && i > idx - 18; --i) { - f = f * 10LL + getDigitByExponent(i); - } - - // If we have no decimal digits, we don't have an integer value - hasIntValue = (f == 0LL); - - // t (decimal digits without trailing zeros) - t = f; - while (t > 0 && t % 10LL == 0) { - t /= 10; - } -} - -U_NAMESPACE_END -#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/visibledigits.h b/icu4c/source/i18n/visibledigits.h deleted file mode 100644 index 35ac06924d..0000000000 --- a/icu4c/source/i18n/visibledigits.h +++ /dev/null @@ -1,162 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* * Copyright (C) 2015, International Business Machines -* Corporation and others. All Rights Reserved. -******************************************************************************* -* visibledigits.h -* -* created on: 2015jun20 -* created by: Travis Keep -*/ - -#ifndef __VISIBLEDIGITS_H__ -#define __VISIBLEDIGITS_H__ - -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING - -#include "unicode/uobject.h" - -#include "charstr.h" -#include "digitinterval.h" - -U_NAMESPACE_BEGIN - -class DigitList; - -/** - * VisibleDigits represents the digits visible for formatting. - * Once initialized using a FixedPrecision instance, VisibleDigits instances - * remain unchanged until they are initialized again. A VisibleDigits with - * a numeric value equal to 3.0 could be "3", "3.0", "3.00" or even "003.0" - * depending on settings of the FixedPrecision instance used to initialize it. - */ -class U_I18N_API VisibleDigits : public UMemory { -public: - VisibleDigits() : fExponent(0), fFlags(0), fAbsIntValue(0), fAbsIntValueSet(FALSE), fAbsDoubleValue(0.0), fAbsDoubleValueSet(FALSE) { } - - UBool isNegative() const; - UBool isNaN() const; - UBool isInfinite() const; - UBool isNaNOrInfinity() const; - - /** - * Gets the digit at particular exponent, if number is 987.6, then - * getDigit(2) == 9 and gitDigit(0) == 7 and gitDigit(-1) == 6. - * If isNaN() or isInfinity() return TRUE, then the result of this - * function is undefined. - */ - int32_t getDigitByExponent(int32_t digitPos) const; - - /** - * Returns the digit interval which indicates the leftmost and rightmost - * position of this instance. - * If isNaN() or isInfinity() return TRUE, then the result of this - * function is undefined. - */ - const DigitInterval &getInterval() const { return fInterval; } - - /** - * Gets the parameters needed to create a FixedDecimal. - */ - void getFixedDecimal(double &source, int64_t &intValue, int64_t &f, int64_t &t, int32_t &v, UBool &hasIntValue) const; - - -private: - /** - * The digits, least significant first. Both the least and most - * significant digit in this list are non-zero; however, digits in the - * middle may be zero. This field contains values between (char) 0, and - * (char) 9 inclusive. - */ - CharString fDigits; - - /** - * The range of displayable digits. This field is needed to account for - * any leading and trailing zeros which are not stored in fDigits. - */ - DigitInterval fInterval; - - /** - * The exponent value of the least significant digit in fDigits. For - * example, fExponent = 2 and fDigits = {7, 8, 5} represents 58700. - */ - int32_t fExponent; - - /** - * Contains flags such as NaN, Inf, and negative. - */ - int32_t fFlags; - - /** - * Contains the absolute value of the digits left of the decimal place - * if fAbsIntValueSet is TRUE - */ - int64_t fAbsIntValue; - - /** - * Indicates whether or not fAbsIntValue is set. - */ - UBool fAbsIntValueSet; - - /** - * Contains the absolute value of the value this instance represents - * if fAbsDoubleValueSet is TRUE - */ - double fAbsDoubleValue; - - /** - * Indicates whether or not fAbsDoubleValue is set. - */ - UBool fAbsDoubleValueSet; - - void setNegative(); - void setNaN(); - void setInfinite(); - void clear(); - double computeAbsDoubleValue() const; - UBool isOverMaxDigits() const; - - VisibleDigits(const VisibleDigits &); - VisibleDigits &operator=(const VisibleDigits &); - - friend class FixedPrecision; - friend class VisibleDigitsWithExponent; -}; - -/** - * A VisibleDigits with a possible exponent. - */ -class U_I18N_API VisibleDigitsWithExponent : public UMemory { -public: - VisibleDigitsWithExponent() : fHasExponent(FALSE) { } - const VisibleDigits &getMantissa() const { return fMantissa; } - const VisibleDigits *getExponent() const { - return fHasExponent ? &fExponent : NULL; - } - void clear() { - fMantissa.clear(); - fExponent.clear(); - fHasExponent = FALSE; - } - UBool isNegative() const { return fMantissa.isNegative(); } - UBool isNaN() const { return fMantissa.isNaN(); } - UBool isInfinite() const { return fMantissa.isInfinite(); } -private: - VisibleDigitsWithExponent(const VisibleDigitsWithExponent &); - VisibleDigitsWithExponent &operator=( - const VisibleDigitsWithExponent &); - VisibleDigits fMantissa; - VisibleDigits fExponent; - UBool fHasExponent; - - friend class ScientificPrecision; - friend class FixedPrecision; -}; - - -U_NAMESPACE_END -#endif /* #if !UCONFIG_NO_FORMATTING */ -#endif // __VISIBLEDIGITS_H__ diff --git a/icu4c/source/test/intltest/Makefile.in b/icu4c/source/test/intltest/Makefile.in index abdb68d755..6699d319fe 100644 --- a/icu4c/source/test/intltest/Makefile.in +++ b/icu4c/source/test/intltest/Makefile.in @@ -61,7 +61,7 @@ windttst.o winnmtst.o winutil.o csdetest.o tzrulets.o tzoffloc.o tzfmttst.o ssea tufmtts.o itspoof.o simplethread.o bidiconf.o locnmtst.o dcfmtest.o alphaindextst.o listformattertest.o genderinfotest.o compactdecimalformattest.o regiontst.o \ reldatefmttest.o simpleformattertest.o measfmttest.o numfmtspectest.o unifiedcachetest.o quantityformattertest.o \ scientificnumberformattertest.o datadrivennumberformattestsuite.o \ -numberformattesttuple.o numberformat2test.o pluralmaptest.o \ +numberformattesttuple.o pluralmaptest.o \ numbertest_affixutils.o numbertest_api.o numbertest_decimalquantity.o \ numbertest_modifiers.o numbertest_patternmodifier.o numbertest_patternstring.o \ numbertest_stringbuilder.o numbertest_stringsegment.o numbertest_unisets.o \ diff --git a/icu4c/source/test/intltest/datadrivennumberformattestsuite.h b/icu4c/source/test/intltest/datadrivennumberformattestsuite.h index 1f70284e4c..4363e900ba 100644 --- a/icu4c/source/test/intltest/datadrivennumberformattestsuite.h +++ b/icu4c/source/test/intltest/datadrivennumberformattestsuite.h @@ -9,6 +9,7 @@ #ifndef _DATADRIVENNUMBERFORMATTESTSUITE_H__ #define _DATADRIVENNUMBERFORMATTESTSUITE_H__ +#include "cmemory.h" #include "unicode/utypes.h" #if !UCONFIG_NO_FORMATTING diff --git a/icu4c/source/test/intltest/itformat.cpp b/icu4c/source/test/intltest/itformat.cpp index 049e648917..01df9e5227 100644 --- a/icu4c/source/test/intltest/itformat.cpp +++ b/icu4c/source/test/intltest/itformat.cpp @@ -85,7 +85,6 @@ extern IntlTest *createTimeUnitTest(); extern IntlTest *createMeasureFormatTest(); extern IntlTest *createNumberFormatSpecificationTest(); extern IntlTest *createScientificNumberFormatterTest(); -extern IntlTest *createNumberFormat2Test(); #define TESTCLASS(id, TestClass) \ @@ -229,16 +228,7 @@ void IntlTestFormat::runIndexedTest( int32_t index, UBool exec, const char* &nam callTest(*test, par); } break; - case 50: - name = "NumberFormat2Test"; - if (exec) { - logln("NumberFormat2Test test---"); - logln((UnicodeString)""); - LocalPointer test(createNumberFormat2Test()); - callTest(*test, par); - } - break; - TESTCLASS(51,NumberTest); + TESTCLASS(50,NumberTest); default: name = ""; break; //needed to end loop } if (exec) { diff --git a/icu4c/source/test/intltest/numberformattesttuple.h b/icu4c/source/test/intltest/numberformattesttuple.h index 685c3d698e..f30de79deb 100644 --- a/icu4c/source/test/intltest/numberformattesttuple.h +++ b/icu4c/source/test/intltest/numberformattesttuple.h @@ -13,7 +13,6 @@ #if !UCONFIG_NO_FORMATTING -#include "decimalformatpattern.h" #include "unicode/decimfmt.h" #include "unicode/ucurr.h" diff --git a/icu4c/source/test/intltest/numfmtst.cpp b/icu4c/source/test/intltest/numfmtst.cpp index a257e12f8b..cf59e849c6 100644 --- a/icu4c/source/test/intltest/numfmtst.cpp +++ b/icu4c/source/test/intltest/numfmtst.cpp @@ -620,7 +620,7 @@ void NumberFormatTest::runIndexedTest( int32_t index, UBool exec, const char* &n TESTCASE_AUTO(TestFractionalDigitsForCurrency); TESTCASE_AUTO(TestFormatCurrencyPlural); TESTCASE_AUTO(Test11868); - TESTCASE_AUTO(Test10727_RoundingZero); +// TESTCASE_AUTO(Test10727_RoundingZero); TESTCASE_AUTO(Test11376_getAndSetPositivePrefix); TESTCASE_AUTO(Test11475_signRecognition); TESTCASE_AUTO(Test11640_getAffixes); From 8a50c335fa62ad127d51b9b08c6a5acafc7a1992 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Tue, 13 Mar 2018 09:25:41 +0000 Subject: [PATCH 034/129] ICU-13634 Adding new field structure to DecimalFormat and wiring up constructors. X-SVN-Rev: 41099 --- icu4c/source/i18n/Makefile.in | 3 +- icu4c/source/i18n/decimfmt.cpp | 108 +++++++- icu4c/source/i18n/number_fluent.cpp | 7 + icu4c/source/i18n/number_mapper.cpp | 23 ++ icu4c/source/i18n/number_mapper.h | 63 +++++ icu4c/source/i18n/number_patternstring.h | 8 +- icu4c/source/i18n/numparse_impl.h | 5 + icu4c/source/i18n/unicode/decimfmt.h | 232 ++++++++++-------- icu4c/source/i18n/unicode/numberformatter.h | 7 + .../com/ibm/icu/number/NumberFormatter.java | 7 +- .../ibm/icu/number/NumberPropertyMapper.java | 9 + 11 files changed, 354 insertions(+), 118 deletions(-) create mode 100644 icu4c/source/i18n/number_mapper.cpp create mode 100644 icu4c/source/i18n/number_mapper.h diff --git a/icu4c/source/i18n/Makefile.in b/icu4c/source/i18n/Makefile.in index 83c0e3c69d..a1d186b435 100644 --- a/icu4c/source/i18n/Makefile.in +++ b/icu4c/source/i18n/Makefile.in @@ -107,7 +107,8 @@ double-conversion.o double-conversion-bignum-dtoa.o double-conversion-bignum.o \ double-conversion-cached-powers.o double-conversion-diy-fp.o double-conversion-fast-dtoa.o \ numparse_stringsegment.o numparse_unisets.o numparse_parsednumber.o \ numparse_impl.o numparse_symbols.o numparse_decimal.o numparse_scientific.o \ -numparse_currency.o numparse_affixes.o numparse_compositions.o +numparse_currency.o numparse_affixes.o numparse_compositions.o \ +number_mapper.o ## Header files to install diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp index dac10b80a7..ca2eb5ca06 100644 --- a/icu4c/source/i18n/decimfmt.cpp +++ b/icu4c/source/i18n/decimfmt.cpp @@ -9,25 +9,67 @@ // Helpful in toString methods and elsewhere. #define UNISTR_FROM_STRING_EXPLICIT +#include "unicode/errorcode.h" #include "unicode/decimfmt.h" #include "number_decimalquantity.h" +#include "number_types.h" +#include "numparse_impl.h" +#include "number_mapper.h" +#include "number_patternstring.h" using namespace icu; using namespace icu::number; using namespace icu::number::impl; +using namespace icu::numparse; +using namespace icu::numparse::impl; using ERoundingMode = icu::DecimalFormat::ERoundingMode; using EPadPosition = icu::DecimalFormat::EPadPosition; -DecimalFormat::DecimalFormat(UErrorCode& status) {} +DecimalFormat::DecimalFormat(UErrorCode& status) + : DecimalFormat(nullptr, status) { +} -DecimalFormat::DecimalFormat(const UnicodeString& pattern, UErrorCode& status) {} +DecimalFormat::DecimalFormat(const UnicodeString& pattern, UErrorCode& status) + : DecimalFormat(nullptr, status) { + setPropertiesFromPattern(pattern, IGNORE_ROUNDING_IF_CURRENCY, status); + refreshFormatter(status); +} DecimalFormat::DecimalFormat(const UnicodeString& pattern, DecimalFormatSymbols* symbolsToAdopt, - UErrorCode& status) {} + UErrorCode& status) + : DecimalFormat(symbolsToAdopt, status) { + setPropertiesFromPattern(pattern, IGNORE_ROUNDING_IF_CURRENCY, status); +} DecimalFormat::DecimalFormat(const UnicodeString& pattern, DecimalFormatSymbols* symbolsToAdopt, - UNumberFormatStyle style, UErrorCode& status) {} + UNumberFormatStyle style, UErrorCode& status) + : DecimalFormat(symbolsToAdopt, status) { + // If choice is a currency type, ignore the rounding information. + if (style == UNumberFormatStyle::UNUM_CURRENCY || style == UNumberFormatStyle::UNUM_CURRENCY_ISO || + style == UNumberFormatStyle::UNUM_CURRENCY_ACCOUNTING || + style == UNumberFormatStyle::UNUM_CASH_CURRENCY || + style == UNumberFormatStyle::UNUM_CURRENCY_STANDARD || + style == UNumberFormatStyle::UNUM_CURRENCY_PLURAL) { + setPropertiesFromPattern(pattern, IGNORE_ROUNDING_ALWAYS, status); + } else { + setPropertiesFromPattern(pattern, IGNORE_ROUNDING_IF_CURRENCY, status); + } + refreshFormatter(status); +} + +DecimalFormat::DecimalFormat(const DecimalFormatSymbols* symbolsToAdopt, UErrorCode& status) { + properties = new DecimalFormatProperties(); + exportedProperties = new DecimalFormatProperties(); + if (symbolsToAdopt == nullptr) { + symbols = new DecimalFormatSymbols(status); + } else { + symbols = symbolsToAdopt; + } + if (properties == nullptr || exportedProperties == nullptr || symbols == nullptr) { + status = U_MEMORY_ALLOCATION_ERROR; + } +} void DecimalFormat::setParseAllInput(UNumberFormatAttributeValue value) {} @@ -43,12 +85,30 @@ void DecimalFormat::setParseIntegerOnly(UBool value) {} void DecimalFormat::setContext(UDisplayContext value, UErrorCode& status) {} DecimalFormat::DecimalFormat(const UnicodeString& pattern, DecimalFormatSymbols* symbolsToAdopt, - UParseError& parseError, UErrorCode& status) {} + UParseError& parseError, UErrorCode& status) + : DecimalFormat(symbolsToAdopt, status) { + // TODO: What is parseError for? + setPropertiesFromPattern(pattern, IGNORE_ROUNDING_IF_CURRENCY, status); + refreshFormatter(status); +} DecimalFormat::DecimalFormat(const UnicodeString& pattern, const DecimalFormatSymbols& symbols, - UErrorCode& status) {} + UErrorCode& status) + : DecimalFormat(new DecimalFormatSymbols(symbols), status) { + setPropertiesFromPattern(pattern, IGNORE_ROUNDING_IF_CURRENCY, status); + refreshFormatter(status); +} -DecimalFormat::DecimalFormat(const DecimalFormat& source) {} +DecimalFormat::DecimalFormat(const DecimalFormat& source) { + properties = new DecimalFormatProperties(); + exportedProperties = new DecimalFormatProperties(); + symbols = new DecimalFormatSymbols(*source.symbols); + if (properties == nullptr || exportedProperties == nullptr || symbols == nullptr) { + return; + } + ErrorCode localStatus; + refreshFormatter(localStatus); +} DecimalFormat& DecimalFormat::operator=(const DecimalFormat& rhs) {} @@ -239,5 +299,39 @@ UClassID DecimalFormat::getStaticClassID() {} UClassID DecimalFormat::getDynamicClassID() const {} +/** Rebuilds the formatter object from the property bag. */ +void DecimalFormat::refreshFormatter(UErrorCode& status) { + if (exportedProperties == nullptr) { + // exportedProperties is null only when the formatter is not ready yet. + // The only time when this happens is during legacy deserialization. + return; + } + Locale locale = getLocale(ULOC_ACTUAL_LOCALE, status); + if (U_FAILURE(status)) { + // Constructor + locale = symbols->getLocale(ULOC_ACTUAL_LOCALE, status); + } + if (U_FAILURE(status)) { + // Deserialization + locale = symbols->getLocale(); + } + if (U_FAILURE(status)) { + return; + } + + *formatter = NumberPropertyMapper::create(*properties, *symbols, *exportedProperties, status).locale( + locale); + parser = NumberParserImpl::createParserFromProperties(*properties, *symbols, false, false, status); + parserWithCurrency = NumberParserImpl::createParserFromProperties( + *properties, *symbols, true, false, status); +} + +void DecimalFormat::setPropertiesFromPattern(const UnicodeString& pattern, int32_t ignoreRounding, + UErrorCode& status) { + // Cast workaround to get around putting the enum in the public header file + auto actualIgnoreRounding = static_cast(ignoreRounding); + PatternParser::parseToExistingProperties(pattern, *properties, actualIgnoreRounding, status); +} + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/number_fluent.cpp b/icu4c/source/i18n/number_fluent.cpp index 27113106c5..190b94bf15 100644 --- a/icu4c/source/i18n/number_fluent.cpp +++ b/icu4c/source/i18n/number_fluent.cpp @@ -139,6 +139,13 @@ Derived NumberFormatterSettings::threshold(int32_t threshold) const { return copy; } +template +Derived NumberFormatterSettings::macros(impl::MacroProps& macros) const { + Derived copy(*this); + copy.fMacros = macros; + return copy; +} + // Declare all classes that implement NumberFormatterSettings // See https://stackoverflow.com/a/495056/1407170 template diff --git a/icu4c/source/i18n/number_mapper.cpp b/icu4c/source/i18n/number_mapper.cpp new file mode 100644 index 0000000000..ac8cda356f --- /dev/null +++ b/icu4c/source/i18n/number_mapper.cpp @@ -0,0 +1,23 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT + +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + +#include "number_mapper.h" + +using namespace icu; +using namespace icu::number; +using namespace icu::number::impl; + + + + + + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/number_mapper.h b/icu4c/source/i18n/number_mapper.h new file mode 100644 index 0000000000..c3960a665b --- /dev/null +++ b/icu4c/source/i18n/number_mapper.h @@ -0,0 +1,63 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#ifndef __NUMBER_MAPPER_H__ +#define __NUMBER_MAPPER_H__ + +#include "number_types.h" + +U_NAMESPACE_BEGIN namespace number { +namespace impl { + + +/** + * Utilities for converting between a DecimalFormatProperties and a MacroProps. + */ +class NumberPropertyMapper { + public: + /** Convenience method to create a NumberFormatter directly from Properties. */ + static UnlocalizedNumberFormatter create(const DecimalFormatProperties& properties, + const DecimalFormatSymbols& symbols, UErrorCode& status); + + /** Convenience method to create a NumberFormatter directly from Properties. */ + static UnlocalizedNumberFormatter create(const DecimalFormatProperties& properties, + const DecimalFormatSymbols& symbols, + DecimalFormatProperties& exportedProperties, + UErrorCode& status); + + /** + * Convenience method to create a NumberFormatter directly from a pattern string. Something like this + * could become public API if there is demand. + */ + static UnlocalizedNumberFormatter create(const UnicodeString& pattern, + const DecimalFormatSymbols& symbols, UErrorCode& status); + + /** + * Creates a new {@link MacroProps} object based on the content of a {@link DecimalFormatProperties} + * object. In other words, maps Properties to MacroProps. This function is used by the + * JDK-compatibility API to call into the ICU 60 fluent number formatting pipeline. + * + * @param properties + * The property bag to be mapped. + * @param symbols + * The symbols associated with the property bag. + * @param exportedProperties + * A property bag in which to store validated properties. Used by some DecimalFormat + * getters. + * @return A new MacroProps containing all of the information in the Properties. + */ + static void oldToNew(const DecimalFormatProperties& properties, const DecimalFormatSymbols& symbols, + DecimalFormatProperties& exportedProperties, MacroProps& output, + UErrorCode& status); +}; + + +} // namespace impl +} // namespace numparse +U_NAMESPACE_END + +#endif //__NUMBER_MAPPER_H__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/number_patternstring.h b/icu4c/source/i18n/number_patternstring.h index 0a343f6378..d0e62ef21f 100644 --- a/icu4c/source/i18n/number_patternstring.h +++ b/icu4c/source/i18n/number_patternstring.h @@ -139,6 +139,10 @@ struct U_I18N_API ParsedPatternInfo : public AffixPatternProvider, public UMemor friend class PatternParser; }; +enum IgnoreRounding { + IGNORE_ROUNDING_NEVER = 0, IGNORE_ROUNDING_IF_CURRENCY = 1, IGNORE_ROUNDING_ALWAYS = 2 +}; + class U_I18N_API PatternParser { public: /** @@ -157,10 +161,6 @@ class U_I18N_API PatternParser { static void parseToPatternInfo(const UnicodeString& patternString, ParsedPatternInfo& patternInfo, UErrorCode& status); - enum IgnoreRounding { - IGNORE_ROUNDING_NEVER = 0, IGNORE_ROUNDING_IF_CURRENCY = 1, IGNORE_ROUNDING_ALWAYS = 2 - }; - /** * Parses a pattern string into a new property bag. * diff --git a/icu4c/source/i18n/numparse_impl.h b/icu4c/source/i18n/numparse_impl.h index 210dcf4754..901c226a13 100644 --- a/icu4c/source/i18n/numparse_impl.h +++ b/icu4c/source/i18n/numparse_impl.h @@ -14,6 +14,7 @@ #include "unicode/uniset.h" #include "numparse_currency.h" #include "numparse_affixes.h" +#include "number_decimfmtprops.h" U_NAMESPACE_BEGIN namespace numparse { namespace impl { @@ -25,6 +26,10 @@ class NumberParserImpl : public MutableMatcherCollection { static NumberParserImpl* createSimpleParser(const Locale& locale, const UnicodeString& patternString, parse_flags_t parseFlags, UErrorCode& status); + static NumberParserImpl* createParserFromProperties( + const number::impl::DecimalFormatProperties& properties, DecimalFormatSymbols symbols, + bool parseCurrency, bool optimize, UErrorCode& status); + void addMatcher(NumberParseMatcher& matcher) override; void freeze(); diff --git a/icu4c/source/i18n/unicode/decimfmt.h b/icu4c/source/i18n/unicode/decimfmt.h index 1fe5d99d32..b86c2ee447 100644 --- a/icu4c/source/i18n/unicode/decimfmt.h +++ b/icu4c/source/i18n/unicode/decimfmt.h @@ -70,6 +70,13 @@ class VisibleDigitsWithExponent; namespace number { namespace impl { class DecimalQuantity; +struct DecimalFormatProperties; +} +} + +namespace numparse { +namespace impl { +class NumberParserImpl; } } @@ -679,17 +686,14 @@ template class U_I18N_API EnumSet Date: Tue, 13 Mar 2018 10:11:36 +0000 Subject: [PATCH 035/129] ICU-13634 Filling in more methods in decimfmt.cpp X-SVN-Rev: 41100 --- icu4c/source/i18n/decimfmt.cpp | 345 ++++++++++++++++++-- icu4c/source/i18n/number_decimfmtprops.h | 11 +- icu4c/source/i18n/number_fluent.cpp | 11 + icu4c/source/i18n/unicode/decimfmt.h | 36 +- icu4c/source/i18n/unicode/numberformatter.h | 7 + icu4c/source/i18n/unicode/numfmt.h | 2 +- 6 files changed, 372 insertions(+), 40 deletions(-) diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp index ca2eb5ca06..f1b54a7ae5 100644 --- a/icu4c/source/i18n/decimfmt.cpp +++ b/icu4c/source/i18n/decimfmt.cpp @@ -71,18 +71,229 @@ DecimalFormat::DecimalFormat(const DecimalFormatSymbols* symbolsToAdopt, UErrorC } } -void DecimalFormat::setParseAllInput(UNumberFormatAttributeValue value) {} +#if UCONFIG_HAVE_PARSEALLINPUT + +void DecimalFormat::setParseAllInput(UNumberFormatAttributeValue value) { + properties->parseAllInput = value; +} + +#endif DecimalFormat& -DecimalFormat::setAttribute(UNumberFormatAttribute attr, int32_t newvalue, UErrorCode& status) {} +DecimalFormat::setAttribute(UNumberFormatAttribute attr, int32_t newValue, UErrorCode& status) { + if (U_FAILURE(status)) { return *this; } -int32_t DecimalFormat::getAttribute(UNumberFormatAttribute attr, UErrorCode& status) const {} + switch (attr) { + case UNUM_LENIENT_PARSE: + setLenient(newValue != 0); + break; -void DecimalFormat::setGroupingUsed(UBool newValue) {} + case UNUM_PARSE_INT_ONLY: + setParseIntegerOnly(newValue != 0); + break; -void DecimalFormat::setParseIntegerOnly(UBool value) {} + case UNUM_GROUPING_USED: + setGroupingUsed(newValue != 0); + break; -void DecimalFormat::setContext(UDisplayContext value, UErrorCode& status) {} + case UNUM_DECIMAL_ALWAYS_SHOWN: + setDecimalSeparatorAlwaysShown(newValue != 0); + break; + + case UNUM_MAX_INTEGER_DIGITS: + setMaximumIntegerDigits(newValue); + break; + + case UNUM_MIN_INTEGER_DIGITS: + setMinimumIntegerDigits(newValue); + break; + + case UNUM_INTEGER_DIGITS: + setMinimumIntegerDigits(newValue); + setMaximumIntegerDigits(newValue); + break; + + case UNUM_MAX_FRACTION_DIGITS: + setMaximumFractionDigits(newValue); + break; + + case UNUM_MIN_FRACTION_DIGITS: + setMinimumFractionDigits(newValue); + break; + + case UNUM_FRACTION_DIGITS: + setMinimumFractionDigits(newValue); + setMaximumFractionDigits(newValue); + break; + + case UNUM_SIGNIFICANT_DIGITS_USED: + setSignificantDigitsUsed(newValue != 0); + break; + + case UNUM_MAX_SIGNIFICANT_DIGITS: + setMaximumSignificantDigits(newValue); + break; + + case UNUM_MIN_SIGNIFICANT_DIGITS: + setMinimumSignificantDigits(newValue); + break; + + case UNUM_MULTIPLIER: + setMultiplier(newValue); + break; + + case UNUM_GROUPING_SIZE: + setGroupingSize(newValue); + break; + + case UNUM_ROUNDING_MODE: + setRoundingMode((DecimalFormat::ERoundingMode) newValue); + break; + + case UNUM_FORMAT_WIDTH: + setFormatWidth(newValue); + break; + + case UNUM_PADDING_POSITION: + /** The position at which padding will take place. */ + setPadPosition((DecimalFormat::EPadPosition) newValue); + break; + + case UNUM_SECONDARY_GROUPING_SIZE: + setSecondaryGroupingSize(newValue); + break; + +#if UCONFIG_HAVE_PARSEALLINPUT + case UNUM_PARSE_ALL_INPUT: + setParseAllInput((UNumberFormatAttributeValue) newValue); + break; +#endif + + case UNUM_PARSE_NO_EXPONENT: + setParseNoExponent((UBool) newValue); + break; + + case UNUM_PARSE_DECIMAL_MARK_REQUIRED: + setDecimalPatternMatchRequired((UBool) newValue); + break; + + case UNUM_CURRENCY_USAGE: + setCurrencyUsage((UCurrencyUsage) newValue, &status); + break; + + case UNUM_MINIMUM_GROUPING_DIGITS: + setMinimumGroupingDigits(newValue); + break; + + default: + status = U_UNSUPPORTED_ERROR; + break; + } + // TODO: UNUM_SCALE? + // TODO: UNUM_FORMAT_FAIL_IF_MORE_THAN_MAX_DIGITS? + return *this; +} + +int32_t DecimalFormat::getAttribute(UNumberFormatAttribute attr, UErrorCode& status) const { + if (U_FAILURE(status)) { return -1; } + switch (attr) { + case UNUM_LENIENT_PARSE: + return isLenient(); + + case UNUM_PARSE_INT_ONLY: + return isParseIntegerOnly(); + + case UNUM_GROUPING_USED: + return isGroupingUsed(); + + case UNUM_DECIMAL_ALWAYS_SHOWN: + return isDecimalSeparatorAlwaysShown(); + + case UNUM_MAX_INTEGER_DIGITS: + return getMaximumIntegerDigits(); + + case UNUM_MIN_INTEGER_DIGITS: + return getMinimumIntegerDigits(); + + case UNUM_INTEGER_DIGITS: + // TBD: what should this return? + return getMinimumIntegerDigits(); + + case UNUM_MAX_FRACTION_DIGITS: + return getMaximumFractionDigits(); + + case UNUM_MIN_FRACTION_DIGITS: + return getMinimumFractionDigits(); + + case UNUM_FRACTION_DIGITS: + // TBD: what should this return? + return getMinimumFractionDigits(); + + case UNUM_SIGNIFICANT_DIGITS_USED: + return areSignificantDigitsUsed(); + + case UNUM_MAX_SIGNIFICANT_DIGITS: + return getMaximumSignificantDigits(); + + case UNUM_MIN_SIGNIFICANT_DIGITS: + return getMinimumSignificantDigits(); + + case UNUM_MULTIPLIER: + return getMultiplier(); + + case UNUM_GROUPING_SIZE: + return getGroupingSize(); + + case UNUM_ROUNDING_MODE: + return getRoundingMode(); + + case UNUM_FORMAT_WIDTH: + return getFormatWidth(); + + case UNUM_PADDING_POSITION: + return getPadPosition(); + + case UNUM_SECONDARY_GROUPING_SIZE: + return getSecondaryGroupingSize(); + + case UNUM_PARSE_NO_EXPONENT: + return getParseNoExponent(); + + case UNUM_PARSE_DECIMAL_MARK_REQUIRED: + return isDecimalPatternMatchRequired(); + + case UNUM_CURRENCY_USAGE: + return getCurrencyUsage(); + + case UNUM_MINIMUM_GROUPING_DIGITS: + return getMinimumGroupingDigits(); + + default: + status = U_UNSUPPORTED_ERROR; + break; + } + // TODO: UNUM_FORMAT_FAIL_IF_MORE_THAN_MAX_DIGITS? + // TODO: UNUM_SCALE? + + return -1; /* undefined */ +} + +void DecimalFormat::setGroupingUsed(UBool enabled) { + if (enabled) { + // Set to a reasonable default value + properties->groupingSize = 3; + properties->secondaryGroupingSize = 3; + } else { + properties->groupingSize = 0; + properties->secondaryGroupingSize = 0; + } + refreshFormatterNoError(); +} + +void DecimalFormat::setParseIntegerOnly(UBool value) { + properties->parseIntegerOnly = value; + refreshFormatterNoError(); +} DecimalFormat::DecimalFormat(const UnicodeString& pattern, DecimalFormatSymbols* symbolsToAdopt, UParseError& parseError, UErrorCode& status) @@ -106,60 +317,139 @@ DecimalFormat::DecimalFormat(const DecimalFormat& source) { if (properties == nullptr || exportedProperties == nullptr || symbols == nullptr) { return; } - ErrorCode localStatus; - refreshFormatter(localStatus); + refreshFormatterNoError(); } -DecimalFormat& DecimalFormat::operator=(const DecimalFormat& rhs) {} +DecimalFormat& DecimalFormat::operator=(const DecimalFormat& rhs) { + *properties = *rhs.properties; + exportedProperties->clear(); + symbols = new DecimalFormatSymbols(*rhs.symbols); + refreshFormatterNoError(); +} DecimalFormat::~DecimalFormat() = default; -Format* DecimalFormat::clone() const {} +Format* DecimalFormat::clone() const { + return new DecimalFormat(*this); +} -UBool DecimalFormat::operator==(const Format& other) const {} +UBool DecimalFormat::operator==(const Format& other) const { + const DecimalFormat* otherDF = dynamic_cast(&other); + if (otherDF == nullptr) { + return false; + } + return *properties == *otherDF->properties && *symbols == *otherDF->symbols; +} -UnicodeString& DecimalFormat::format(double number, UnicodeString& appendTo, FieldPosition& pos) const {} +UnicodeString& DecimalFormat::format(double number, UnicodeString& appendTo, FieldPosition& pos) const { + ErrorCode localStatus; + FormattedNumber output = formatter->formatDouble(number, localStatus); + output.populateFieldPosition(pos, localStatus); + auto appendable = UnicodeStringAppendable(appendTo); + output.appendTo(appendable); + return appendTo; +} UnicodeString& DecimalFormat::format(double number, UnicodeString& appendTo, FieldPosition& pos, - UErrorCode& status) const {} + UErrorCode& status) const { + FormattedNumber output = formatter->formatDouble(number, status); + output.populateFieldPosition(pos, status); + auto appendable = UnicodeStringAppendable(appendTo); + output.appendTo(appendable); + return appendTo; +} UnicodeString& DecimalFormat::format(double number, UnicodeString& appendTo, FieldPositionIterator* posIter, - UErrorCode& status) const {} + UErrorCode& status) const { + FormattedNumber output = formatter->formatDouble(number, status); + output.populateFieldPositionIterator(*posIter, status); + auto appendable = UnicodeStringAppendable(appendTo); + output.appendTo(appendable); + return appendTo; +} -UnicodeString& DecimalFormat::format(int32_t number, UnicodeString& appendTo, FieldPosition& pos) const {} +UnicodeString& DecimalFormat::format(int32_t number, UnicodeString& appendTo, FieldPosition& pos) const { + return format(static_cast (number), appendTo, pos); +} UnicodeString& DecimalFormat::format(int32_t number, UnicodeString& appendTo, FieldPosition& pos, - UErrorCode& status) const {} + UErrorCode& status) const { + return format(static_cast (number), appendTo, pos, status); +} UnicodeString& DecimalFormat::format(int32_t number, UnicodeString& appendTo, FieldPositionIterator* posIter, - UErrorCode& status) const {} + UErrorCode& status) const { + return format(static_cast (number), appendTo, posIter, status); +} -UnicodeString& DecimalFormat::format(int64_t number, UnicodeString& appendTo, FieldPosition& pos) const {} +UnicodeString& DecimalFormat::format(int64_t number, UnicodeString& appendTo, FieldPosition& pos) const { + ErrorCode localStatus; + FormattedNumber output = formatter->formatInt(number, localStatus); + output.populateFieldPosition(pos, localStatus); + auto appendable = UnicodeStringAppendable(appendTo); + output.appendTo(appendable); + return appendTo; +} UnicodeString& DecimalFormat::format(int64_t number, UnicodeString& appendTo, FieldPosition& pos, - UErrorCode& status) const {} + UErrorCode& status) const { + FormattedNumber output = formatter->formatInt(number, status); + output.populateFieldPosition(pos, status); + auto appendable = UnicodeStringAppendable(appendTo); + output.appendTo(appendable); + return appendTo; +} UnicodeString& DecimalFormat::format(int64_t number, UnicodeString& appendTo, FieldPositionIterator* posIter, - UErrorCode& status) const {} + UErrorCode& status) const { + FormattedNumber output = formatter->formatInt(number, status); + output.populateFieldPositionIterator(*posIter, status); + auto appendable = UnicodeStringAppendable(appendTo); + output.appendTo(appendable); + return appendTo; +} UnicodeString& DecimalFormat::format(StringPiece number, UnicodeString& appendTo, FieldPositionIterator* posIter, - UErrorCode& status) const {} + UErrorCode& status) const { + ErrorCode localStatus; + FormattedNumber output = formatter->formatDecimal(number, localStatus); + output.populateFieldPositionIterator(*posIter, status); + auto appendable = UnicodeStringAppendable(appendTo); + output.appendTo(appendable); + return appendTo; +} UnicodeString& DecimalFormat::format(const DecimalQuantity& number, UnicodeString& appendTo, - FieldPositionIterator* posIter, UErrorCode& status) const {} + FieldPositionIterator* posIter, UErrorCode& status) const { + FormattedNumber output = formatter->formatDecimalQuantity(number, status); + output.populateFieldPositionIterator(*posIter, status); + auto appendable = UnicodeStringAppendable(appendTo); + output.appendTo(appendable); + return appendTo; +} UnicodeString& DecimalFormat::format(const DecimalQuantity& number, UnicodeString& appendTo, FieldPosition& pos, - UErrorCode& status) const {} + UErrorCode& status) const { + FormattedNumber output = formatter->formatDecimalQuantity(number, status); + output.populateFieldPosition(pos, status); + auto appendable = UnicodeStringAppendable(appendTo); + output.appendTo(appendable); + return appendTo; +} void -DecimalFormat::parse(const UnicodeString& text, Formattable& result, ParsePosition& parsePosition) const {} +DecimalFormat::parse(const UnicodeString& text, Formattable& result, ParsePosition& parsePosition) const { + // FIXME +} -CurrencyAmount* DecimalFormat::parseCurrency(const UnicodeString& text, ParsePosition& pos) const {} +CurrencyAmount* DecimalFormat::parseCurrency(const UnicodeString& text, ParsePosition& pos) const { + // FIXME +} const DecimalFormatSymbols* DecimalFormat::getDecimalFormatSymbols(void) const {} @@ -326,6 +616,11 @@ void DecimalFormat::refreshFormatter(UErrorCode& status) { *properties, *symbols, true, false, status); } +void DecimalFormat::refreshFormatterNoError() { + ErrorCode localStatus; + refreshFormatter(localStatus); +} + void DecimalFormat::setPropertiesFromPattern(const UnicodeString& pattern, int32_t ignoreRounding, UErrorCode& status) { // Cast workaround to get around putting the enum in the public header file diff --git a/icu4c/source/i18n/number_decimfmtprops.h b/icu4c/source/i18n/number_decimfmtprops.h index 96356cad45..dd23bbdd1d 100644 --- a/icu4c/source/i18n/number_decimfmtprops.h +++ b/icu4c/source/i18n/number_decimfmtprops.h @@ -35,12 +35,20 @@ namespace impl { struct U_I18N_API CurrencyPluralInfoWrapper { LocalPointer fPtr; - CurrencyPluralInfoWrapper() {} + CurrencyPluralInfoWrapper() = default; + CurrencyPluralInfoWrapper(const CurrencyPluralInfoWrapper& other) { if (!other.fPtr.isNull()) { fPtr.adoptInstead(new CurrencyPluralInfo(*other.fPtr)); } } + + CurrencyPluralInfoWrapper& operator=(const CurrencyPluralInfoWrapper& other) { + if (!other.fPtr.isNull()) { + fPtr.adoptInstead(new CurrencyPluralInfo(*other.fPtr)); + } + return *this; + } }; // Exported as U_I18N_API because it is needed for the unit test PatternStringTest @@ -77,6 +85,7 @@ struct U_I18N_API DecimalFormatProperties { bool parseLenient; bool parseNoExponent; bool parseToBigDecimal; + UNumberFormatAttributeValue parseAllInput; // ICU4C-only //PluralRules pluralRules; UnicodeString positivePrefix; UnicodeString positivePrefixPattern; diff --git a/icu4c/source/i18n/number_fluent.cpp b/icu4c/source/i18n/number_fluent.cpp index 190b94bf15..64596af036 100644 --- a/icu4c/source/i18n/number_fluent.cpp +++ b/icu4c/source/i18n/number_fluent.cpp @@ -309,6 +309,17 @@ FormattedNumber LocalizedNumberFormatter::formatDecimal(StringPiece value, UErro return formatImpl(results, status); } +FormattedNumber LocalizedNumberFormatter::formatDecimalQuantity(const DecimalQuantity& dq, UErrorCode &status) const { + if (U_FAILURE(status)) { return FormattedNumber(U_ILLEGAL_ARGUMENT_ERROR); } + auto results = new NumberFormatterResults(); + if (results == nullptr) { + status = U_MEMORY_ALLOCATION_ERROR; + return FormattedNumber(status); + } + results->quantity = dq; + return formatImpl(results, status); +} + FormattedNumber LocalizedNumberFormatter::formatImpl(impl::NumberFormatterResults *results, UErrorCode &status) const { // fUnsafeCallCount contains memory to be interpreted as an atomic int, most commonly diff --git a/icu4c/source/i18n/unicode/decimfmt.h b/icu4c/source/i18n/unicode/decimfmt.h index b86c2ee447..fd2f747df5 100644 --- a/icu4c/source/i18n/unicode/decimfmt.h +++ b/icu4c/source/i18n/unicode/decimfmt.h @@ -792,7 +792,7 @@ class U_I18N_API DecimalFormat : public NumberFormat { * @return *this - for chaining (example: format.setAttribute(...).setAttribute(...) ) * @stable ICU 51 */ - virtual DecimalFormat& setAttribute(UNumberFormatAttribute attr, int32_t newvalue, UErrorCode& status); + virtual DecimalFormat& setAttribute(UNumberFormatAttribute attr, int32_t newValue, UErrorCode& status); /** * Get an integer @@ -823,17 +823,6 @@ class U_I18N_API DecimalFormat : public NumberFormat { */ void setParseIntegerOnly(UBool value) U_OVERRIDE; - /** - * Set a particular UDisplayContext value in the formatter, such as - * UDISPCTX_CAPITALIZATION_FOR_STANDALONE. - * @param value The UDisplayContext value to set. - * @param status Input/output status. If at entry this indicates a failure - * status, the function will do nothing; otherwise this will be - * updated with any new status from the function. - * @stable ICU 53 - */ - void setContext(UDisplayContext value, UErrorCode& status) U_OVERRIDE; - /** * Create a DecimalFormat from the given pattern and symbols. * Use this constructor when you need to completely customize the @@ -1651,7 +1640,7 @@ class U_I18N_API DecimalFormat : public NumberFormat { UBool isDecimalPatternMatchRequired(void) const; /** - * Allows you to set the behavior of the pattern decimal mark. + * Allows you to set the parse behavior of the pattern decimal mark. * * if TRUE, the input must have a decimal mark if one was specified in the pattern. When * FALSE the decimal mark may be omitted from the input. @@ -1661,6 +1650,24 @@ class U_I18N_API DecimalFormat : public NumberFormat { */ virtual void setDecimalPatternMatchRequired(UBool newValue); + /** + * {@icu} Returns whether to ignore exponents when parsing. + * + * @see #setParseNoExponent + * @internal This API is a technical preview. It may change in an upcoming release. + */ + UBool getParseNoExponent() const; + + /** + * {@icu} Specifies whether to stop parsing when an exponent separator is encountered. For + * example, parses "123E4" to 123 (with parse position 3) instead of 1230000 (with parse position + * 5). + * + * @param value true to prevent exponents from being parsed; false to allow them to be parsed. + * @internal This API is a technical preview. It may change in an upcoming release. + */ + void setParseNoExponent(UBool value); + /** * Synthesizes a pattern string that represents the current state @@ -1974,6 +1981,9 @@ class U_I18N_API DecimalFormat : public NumberFormat { /** Rebuilds the formatter object from the property bag. */ void refreshFormatter(UErrorCode& status); + /** Rebuilds the formatter object, hiding the error code. */ + void refreshFormatterNoError(); + /** * Updates the property bag with settings from the given pattern. * diff --git a/icu4c/source/i18n/unicode/numberformatter.h b/icu4c/source/i18n/unicode/numberformatter.h index 89fd71fe9a..6f2b130894 100644 --- a/icu4c/source/i18n/unicode/numberformatter.h +++ b/icu4c/source/i18n/unicode/numberformatter.h @@ -2027,6 +2027,13 @@ class U_I18N_API LocalizedNumberFormatter */ FormattedNumber formatDecimal(StringPiece value, UErrorCode &status) const; +#ifndef U_HIDE_INTERNAL_API + /** Internal method. + * @internal + */ + FormattedNumber formatDecimalQuantity(const impl::DecimalQuantity& dq, UErrorCode& status) const; +#endif + // Make default copy constructor call the NumberFormatterSettings copy constructor. /** * Returns a copy of this LocalizedNumberFormatter. diff --git a/icu4c/source/i18n/unicode/numfmt.h b/icu4c/source/i18n/unicode/numfmt.h index 8184812ef7..4b9b702e97 100644 --- a/icu4c/source/i18n/unicode/numfmt.h +++ b/icu4c/source/i18n/unicode/numfmt.h @@ -973,7 +973,7 @@ public: * @stable ICU 2.6 */ const char16_t* getCurrency() const; - + /** * Set a particular UDisplayContext value in the formatter, such as * UDISPCTX_CAPITALIZATION_FOR_STANDALONE. From 3ba34b82fbbfcfe1858108cd576619434c5ce76d Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Wed, 14 Mar 2018 06:10:22 +0000 Subject: [PATCH 036/129] ICU-13634 Adding class for slow exhaustive number tests and adding a test for ICU-13616 X-SVN-Rev: 41102 --- .../dev/test/number/DecimalQuantityTest.java | 86 ------------ .../dev/test/number/ExhaustiveNumberTest.java | 124 ++++++++++++++++++ .../test/number/NumberFormatterApiTest.java | 12 +- 3 files changed, 130 insertions(+), 92 deletions(-) create mode 100644 icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/ExhaustiveNumberTest.java diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java index 2e8c2ad761..e33405d12b 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java @@ -9,7 +9,6 @@ import java.math.RoundingMode; import java.text.ParseException; import java.util.ArrayList; import java.util.List; -import java.util.Random; import org.junit.Ignore; import org.junit.Test; @@ -356,91 +355,6 @@ public class DecimalQuantityTest extends TestFmwk { assertNull("Failed health check", fq.checkHealth()); } - @Ignore - @Test - public void testConvertToAccurateDouble() { - // based on https://github.com/google/double-conversion/issues/28 - double[] hardDoubles = { - 1651087494906221570.0, - -5074790912492772E-327, - 83602530019752571E-327, - 2.207817077636718750000000000000, - 1.818351745605468750000000000000, - 3.941719055175781250000000000000, - 3.738609313964843750000000000000, - 3.967735290527343750000000000000, - 1.328025817871093750000000000000, - 3.920967102050781250000000000000, - 1.015235900878906250000000000000, - 1.335227966308593750000000000000, - 1.344520568847656250000000000000, - 2.879127502441406250000000000000, - 3.695838928222656250000000000000, - 1.845344543457031250000000000000, - 3.793952941894531250000000000000, - 3.211402893066406250000000000000, - 2.565971374511718750000000000000, - 0.965156555175781250000000000000, - 2.700004577636718750000000000000, - 0.767097473144531250000000000000, - 1.780448913574218750000000000000, - 2.624839782714843750000000000000, - 1.305290222167968750000000000000, - 3.834922790527343750000000000000, }; - - double[] integerDoubles = { - 51423, - 51423e10, - 4.503599627370496E15, - 6.789512076111555E15, - 9.007199254740991E15, - 9.007199254740992E15 }; - - for (double d : hardDoubles) { - checkDoubleBehavior(d, true, ""); - } - - for (double d : integerDoubles) { - checkDoubleBehavior(d, false, ""); - } - - assertEquals("NaN check failed", - Double.NaN, - new DecimalQuantity_DualStorageBCD(Double.NaN).toDouble()); - assertEquals("Inf check failed", - Double.POSITIVE_INFINITY, - new DecimalQuantity_DualStorageBCD(Double.POSITIVE_INFINITY).toDouble()); - assertEquals("-Inf check failed", - Double.NEGATIVE_INFINITY, - new DecimalQuantity_DualStorageBCD(Double.NEGATIVE_INFINITY).toDouble()); - - // Generate random doubles - String alert = "UNEXPECTED FAILURE: PLEASE REPORT THIS MESSAGE TO THE ICU TEAM: "; - Random rnd = new Random(); - for (int i = 0; i < 10000; i++) { - double d = Double.longBitsToDouble(rnd.nextLong()); - if (Double.isNaN(d) || Double.isInfinite(d)) - continue; - checkDoubleBehavior(d, false, alert); - } - } - - private static void checkDoubleBehavior(double d, boolean explicitRequired, String alert) { - DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD(d); - if (explicitRequired) { - assertTrue(alert + "Should be using approximate double", !fq.explicitExactDouble); - } - assertEquals(alert + "Initial construction from hard double", d, fq.toDouble()); - fq.roundToInfinity(); - if (explicitRequired) { - assertTrue(alert + "Should not be using approximate double", fq.explicitExactDouble); - } - assertDoubleEquals(alert + "After conversion to exact BCD (double)", d, fq.toDouble()); - assertBigDecimalEquals(alert + "After conversion to exact BCD (BigDecimal)", - new BigDecimal(Double.toString(d)), - fq.toBigDecimal()); - } - @Test public void testUseApproximateDoubleWhenAble() { Object[][] cases = { diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/ExhaustiveNumberTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/ExhaustiveNumberTest.java new file mode 100644 index 0000000000..e2cabc8e4a --- /dev/null +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/ExhaustiveNumberTest.java @@ -0,0 +1,124 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html#License +package com.ibm.icu.dev.test.number; + +import java.math.BigDecimal; +import java.util.Random; + +import org.junit.Before; +import org.junit.Test; + +import com.ibm.icu.dev.test.TestFmwk; +import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD; +import com.ibm.icu.number.NumberFormatter; +import com.ibm.icu.number.Rounder; +import com.ibm.icu.util.ULocale; + +/** + * Tests that are disabled except in exhaustive mode due to runtime. + * + * @author sffc + */ +public class ExhaustiveNumberTest extends TestFmwk { + @Before + public void beforeMethod() { + // Disable this test class except for exhaustive mode. + // To enable exhaustive mode, pass the JVM argument "-DICU.exhaustive=10" + org.junit.Assume.assumeTrue(getExhaustiveness() > 5); + } + + @Test + public void unlimitedRoundingBigDecimal() { + BigDecimal ten10000 = BigDecimal.valueOf(10).pow(10000); + BigDecimal longFraction = ten10000.subtract(BigDecimal.ONE).divide(ten10000); + String expected = longFraction.toPlainString(); + String actual = NumberFormatter.withLocale(ULocale.ENGLISH).rounding(Rounder.unlimited()) + .format(longFraction).toString(); + assertEquals("All digits should be displayed", expected, actual); + } + + @Test + public void testConvertToAccurateDouble() { + // based on https://github.com/google/double-conversion/issues/28 + double[] hardDoubles = { + 1651087494906221570.0, + -5074790912492772E-327, + 83602530019752571E-327, + 2.207817077636718750000000000000, + 1.818351745605468750000000000000, + 3.941719055175781250000000000000, + 3.738609313964843750000000000000, + 3.967735290527343750000000000000, + 1.328025817871093750000000000000, + 3.920967102050781250000000000000, + 1.015235900878906250000000000000, + 1.335227966308593750000000000000, + 1.344520568847656250000000000000, + 2.879127502441406250000000000000, + 3.695838928222656250000000000000, + 1.845344543457031250000000000000, + 3.793952941894531250000000000000, + 3.211402893066406250000000000000, + 2.565971374511718750000000000000, + 0.965156555175781250000000000000, + 2.700004577636718750000000000000, + 0.767097473144531250000000000000, + 1.780448913574218750000000000000, + 2.624839782714843750000000000000, + 1.305290222167968750000000000000, + 3.834922790527343750000000000000, }; + + double[] integerDoubles = { + 51423, + 51423e10, + 4.503599627370496E15, + 6.789512076111555E15, + 9.007199254740991E15, + 9.007199254740992E15 }; + + for (double d : hardDoubles) { + checkDoubleBehavior(d, true, ""); + } + + for (double d : integerDoubles) { + checkDoubleBehavior(d, false, ""); + } + + assertEquals("NaN check failed", + Double.NaN, + new DecimalQuantity_DualStorageBCD(Double.NaN).toDouble()); + assertEquals("Inf check failed", + Double.POSITIVE_INFINITY, + new DecimalQuantity_DualStorageBCD(Double.POSITIVE_INFINITY).toDouble()); + assertEquals("-Inf check failed", + Double.NEGATIVE_INFINITY, + new DecimalQuantity_DualStorageBCD(Double.NEGATIVE_INFINITY).toDouble()); + + // Generate random doubles + String alert = "UNEXPECTED FAILURE: PLEASE REPORT THIS MESSAGE TO THE ICU TEAM: "; + Random rnd = new Random(); + for (int i = 0; i < 100000; i++) { + double d = Double.longBitsToDouble(rnd.nextLong()); + if (Double.isNaN(d) || Double.isInfinite(d)) + continue; + checkDoubleBehavior(d, false, alert); + } + } + + private static void checkDoubleBehavior(double d, boolean explicitRequired, String alert) { + DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD(d); + if (explicitRequired) { + assertTrue(alert + "Should be using approximate double", !fq.explicitExactDouble); + } + assertEquals(alert + "Initial construction from hard double", d, fq.toDouble()); + fq.roundToInfinity(); + if (explicitRequired) { + assertTrue(alert + "Should not be using approximate double", fq.explicitExactDouble); + } + DecimalQuantityTest + .assertDoubleEquals(alert + "After conversion to exact BCD (double)", d, fq.toDouble()); + DecimalQuantityTest.assertBigDecimalEquals(alert + "After conversion to exact BCD (BigDecimal)", + new BigDecimal(Double.toString(d)), + fq.toBigDecimal()); + } +} 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 58d709f856..968f7a13a5 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 @@ -2009,7 +2009,7 @@ public class NumberFormatterApiTest { } } - private static void assertFormatDescending( + static void assertFormatDescending( String message, String skeleton, UnlocalizedNumberFormatter f, @@ -2019,7 +2019,7 @@ public class NumberFormatterApiTest { assertFormatDescending(message, skeleton, f, locale, inputs, expected); } - private static void assertFormatDescendingBig( + static void assertFormatDescendingBig( String message, String skeleton, UnlocalizedNumberFormatter f, @@ -2029,7 +2029,7 @@ public class NumberFormatterApiTest { assertFormatDescending(message, skeleton, f, locale, inputs, expected); } - private static void assertFormatDescending( + static void assertFormatDescending( String message, String skeleton, UnlocalizedNumberFormatter f, @@ -2062,7 +2062,7 @@ public class NumberFormatterApiTest { } } - private static void assertFormatSingle( + static void assertFormatSingle( String message, String skeleton, UnlocalizedNumberFormatter f, @@ -2088,7 +2088,7 @@ public class NumberFormatterApiTest { } } - private static void assertFormatSingleMeasure( + static void assertFormatSingleMeasure( String message, String skeleton, UnlocalizedNumberFormatter f, @@ -2114,7 +2114,7 @@ public class NumberFormatterApiTest { } } - private static void assertUndefinedSkeleton(UnlocalizedNumberFormatter f) { + static void assertUndefinedSkeleton(UnlocalizedNumberFormatter f) { try { String skeleton = f.toSkeleton(); fail("Expected toSkeleton to fail, but it passed, producing: " + skeleton); From 73fddf50d03eee97489cce341dad028ac95bb057 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Wed, 14 Mar 2018 09:15:27 +0000 Subject: [PATCH 037/129] ICU-13634 Filling in decimfmt.cpp with implementation ported from Java. X-SVN-Rev: 41103 --- icu4c/source/i18n/decimfmt.cpp | 549 ++++++++++++++---- icu4c/source/i18n/number_decimfmtprops.cpp | 4 +- icu4c/source/i18n/number_decimfmtprops.h | 22 +- icu4c/source/i18n/number_types.h | 4 + icu4c/source/i18n/numparse_symbols.cpp | 2 +- icu4c/source/i18n/numparse_unisets.cpp | 2 +- icu4c/source/i18n/numparse_unisets.h | 2 +- icu4c/source/i18n/unicode/decimfmt.h | 23 +- icu4c/source/i18n/unicode/numberformatter.h | 11 +- .../src/com/ibm/icu/text/DecimalFormat.java | 47 +- 10 files changed, 475 insertions(+), 191 deletions(-) diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp index f1b54a7ae5..7772f330d6 100644 --- a/icu4c/source/i18n/decimfmt.cpp +++ b/icu4c/source/i18n/decimfmt.cpp @@ -1,6 +1,7 @@ // © 2018 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html +#include #include "unicode/utypes.h" #if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT @@ -16,6 +17,8 @@ #include "numparse_impl.h" #include "number_mapper.h" #include "number_patternstring.h" +#include "putilimp.h" +#include "number_utils.h" using namespace icu; using namespace icu::number; @@ -59,22 +62,19 @@ DecimalFormat::DecimalFormat(const UnicodeString& pattern, DecimalFormatSymbols* } DecimalFormat::DecimalFormat(const DecimalFormatSymbols* symbolsToAdopt, UErrorCode& status) { - properties = new DecimalFormatProperties(); - exportedProperties = new DecimalFormatProperties(); + fProperties.adoptInsteadAndCheckErrorCode(new DecimalFormatProperties(), status); + fExportedProperties.adoptInsteadAndCheckErrorCode(new DecimalFormatProperties(), status); if (symbolsToAdopt == nullptr) { - symbols = new DecimalFormatSymbols(status); + fSymbols.adoptInsteadAndCheckErrorCode(new DecimalFormatSymbols(status), status); } else { - symbols = symbolsToAdopt; - } - if (properties == nullptr || exportedProperties == nullptr || symbols == nullptr) { - status = U_MEMORY_ALLOCATION_ERROR; + fSymbols.adoptInsteadAndCheckErrorCode(symbolsToAdopt, status); } } #if UCONFIG_HAVE_PARSEALLINPUT void DecimalFormat::setParseAllInput(UNumberFormatAttributeValue value) { - properties->parseAllInput = value; + fProperties->parseAllInput = value; } #endif @@ -281,22 +281,22 @@ int32_t DecimalFormat::getAttribute(UNumberFormatAttribute attr, UErrorCode& sta void DecimalFormat::setGroupingUsed(UBool enabled) { if (enabled) { // Set to a reasonable default value - properties->groupingSize = 3; - properties->secondaryGroupingSize = 3; + fProperties->groupingSize = 3; + fProperties->secondaryGroupingSize = 3; } else { - properties->groupingSize = 0; - properties->secondaryGroupingSize = 0; + fProperties->groupingSize = 0; + fProperties->secondaryGroupingSize = 0; } refreshFormatterNoError(); } void DecimalFormat::setParseIntegerOnly(UBool value) { - properties->parseIntegerOnly = value; + fProperties->parseIntegerOnly = value; refreshFormatterNoError(); } DecimalFormat::DecimalFormat(const UnicodeString& pattern, DecimalFormatSymbols* symbolsToAdopt, - UParseError& parseError, UErrorCode& status) + UParseError&, UErrorCode& status) : DecimalFormat(symbolsToAdopt, status) { // TODO: What is parseError for? setPropertiesFromPattern(pattern, IGNORE_ROUNDING_IF_CURRENCY, status); @@ -311,20 +311,21 @@ DecimalFormat::DecimalFormat(const UnicodeString& pattern, const DecimalFormatSy } DecimalFormat::DecimalFormat(const DecimalFormat& source) { - properties = new DecimalFormatProperties(); - exportedProperties = new DecimalFormatProperties(); - symbols = new DecimalFormatSymbols(*source.symbols); - if (properties == nullptr || exportedProperties == nullptr || symbols == nullptr) { + fProperties.adoptInstead(new DecimalFormatProperties()); + fExportedProperties.adoptInstead(new DecimalFormatProperties()); + fSymbols.adoptInstead(new DecimalFormatSymbols(*source.fSymbols)); + if (fProperties == nullptr || fExportedProperties == nullptr || fSymbols == nullptr) { return; } refreshFormatterNoError(); } DecimalFormat& DecimalFormat::operator=(const DecimalFormat& rhs) { - *properties = *rhs.properties; - exportedProperties->clear(); - symbols = new DecimalFormatSymbols(*rhs.symbols); + *fProperties = *rhs.fProperties; + fExportedProperties->clear(); + fSymbols.adoptInstead(new DecimalFormatSymbols(*rhs.fSymbols)); refreshFormatterNoError(); + return *this; } DecimalFormat::~DecimalFormat() = default; @@ -334,16 +335,16 @@ Format* DecimalFormat::clone() const { } UBool DecimalFormat::operator==(const Format& other) const { - const DecimalFormat* otherDF = dynamic_cast(&other); + auto* otherDF = dynamic_cast(&other); if (otherDF == nullptr) { return false; } - return *properties == *otherDF->properties && *symbols == *otherDF->symbols; + return *fProperties == *otherDF->fProperties && *fSymbols == *otherDF->fSymbols; } UnicodeString& DecimalFormat::format(double number, UnicodeString& appendTo, FieldPosition& pos) const { ErrorCode localStatus; - FormattedNumber output = formatter->formatDouble(number, localStatus); + FormattedNumber output = fFormatter->formatDouble(number, localStatus); output.populateFieldPosition(pos, localStatus); auto appendable = UnicodeStringAppendable(appendTo); output.appendTo(appendable); @@ -352,7 +353,7 @@ UnicodeString& DecimalFormat::format(double number, UnicodeString& appendTo, Fie UnicodeString& DecimalFormat::format(double number, UnicodeString& appendTo, FieldPosition& pos, UErrorCode& status) const { - FormattedNumber output = formatter->formatDouble(number, status); + FormattedNumber output = fFormatter->formatDouble(number, status); output.populateFieldPosition(pos, status); auto appendable = UnicodeStringAppendable(appendTo); output.appendTo(appendable); @@ -362,7 +363,7 @@ UnicodeString& DecimalFormat::format(double number, UnicodeString& appendTo, Fie UnicodeString& DecimalFormat::format(double number, UnicodeString& appendTo, FieldPositionIterator* posIter, UErrorCode& status) const { - FormattedNumber output = formatter->formatDouble(number, status); + FormattedNumber output = fFormatter->formatDouble(number, status); output.populateFieldPositionIterator(*posIter, status); auto appendable = UnicodeStringAppendable(appendTo); output.appendTo(appendable); @@ -386,7 +387,7 @@ DecimalFormat::format(int32_t number, UnicodeString& appendTo, FieldPositionIter UnicodeString& DecimalFormat::format(int64_t number, UnicodeString& appendTo, FieldPosition& pos) const { ErrorCode localStatus; - FormattedNumber output = formatter->formatInt(number, localStatus); + FormattedNumber output = fFormatter->formatInt(number, localStatus); output.populateFieldPosition(pos, localStatus); auto appendable = UnicodeStringAppendable(appendTo); output.appendTo(appendable); @@ -395,7 +396,7 @@ UnicodeString& DecimalFormat::format(int64_t number, UnicodeString& appendTo, Fi UnicodeString& DecimalFormat::format(int64_t number, UnicodeString& appendTo, FieldPosition& pos, UErrorCode& status) const { - FormattedNumber output = formatter->formatInt(number, status); + FormattedNumber output = fFormatter->formatInt(number, status); output.populateFieldPosition(pos, status); auto appendable = UnicodeStringAppendable(appendTo); output.appendTo(appendable); @@ -405,7 +406,7 @@ UnicodeString& DecimalFormat::format(int64_t number, UnicodeString& appendTo, Fi UnicodeString& DecimalFormat::format(int64_t number, UnicodeString& appendTo, FieldPositionIterator* posIter, UErrorCode& status) const { - FormattedNumber output = formatter->formatInt(number, status); + FormattedNumber output = fFormatter->formatInt(number, status); output.populateFieldPositionIterator(*posIter, status); auto appendable = UnicodeStringAppendable(appendTo); output.appendTo(appendable); @@ -416,7 +417,7 @@ UnicodeString& DecimalFormat::format(StringPiece number, UnicodeString& appendTo, FieldPositionIterator* posIter, UErrorCode& status) const { ErrorCode localStatus; - FormattedNumber output = formatter->formatDecimal(number, localStatus); + FormattedNumber output = fFormatter->formatDecimal(number, localStatus); output.populateFieldPositionIterator(*posIter, status); auto appendable = UnicodeStringAppendable(appendTo); output.appendTo(appendable); @@ -425,7 +426,7 @@ DecimalFormat::format(StringPiece number, UnicodeString& appendTo, FieldPosition UnicodeString& DecimalFormat::format(const DecimalQuantity& number, UnicodeString& appendTo, FieldPositionIterator* posIter, UErrorCode& status) const { - FormattedNumber output = formatter->formatDecimalQuantity(number, status); + FormattedNumber output = fFormatter->formatDecimalQuantity(number, status); output.populateFieldPositionIterator(*posIter, status); auto appendable = UnicodeStringAppendable(appendTo); output.appendTo(appendable); @@ -435,7 +436,7 @@ UnicodeString& DecimalFormat::format(const DecimalQuantity& number, UnicodeStrin UnicodeString& DecimalFormat::format(const DecimalQuantity& number, UnicodeString& appendTo, FieldPosition& pos, UErrorCode& status) const { - FormattedNumber output = formatter->formatDecimalQuantity(number, status); + FormattedNumber output = fFormatter->formatDecimalQuantity(number, status); output.populateFieldPosition(pos, status); auto appendable = UnicodeStringAppendable(appendTo); output.appendTo(appendable); @@ -451,169 +452,459 @@ CurrencyAmount* DecimalFormat::parseCurrency(const UnicodeString& text, ParsePos // FIXME } -const DecimalFormatSymbols* DecimalFormat::getDecimalFormatSymbols(void) const {} +const DecimalFormatSymbols* DecimalFormat::getDecimalFormatSymbols(void) const { + return fSymbols.getAlias(); +} -void DecimalFormat::adoptDecimalFormatSymbols(DecimalFormatSymbols* symbolsToAdopt) {} +void DecimalFormat::adoptDecimalFormatSymbols(DecimalFormatSymbols* symbolsToAdopt) { + if (symbolsToAdopt == nullptr) { + return; // do not allow caller to set fSymbols to NULL + } + fSymbols.adoptInstead(symbolsToAdopt); + refreshFormatterNoError(); +} -void DecimalFormat::setDecimalFormatSymbols(const DecimalFormatSymbols& symbols) {} +void DecimalFormat::setDecimalFormatSymbols(const DecimalFormatSymbols& symbols) { + fSymbols.adoptInstead(new DecimalFormatSymbols(symbols)); + refreshFormatterNoError(); +} -const CurrencyPluralInfo* DecimalFormat::getCurrencyPluralInfo(void) const {} +const CurrencyPluralInfo* DecimalFormat::getCurrencyPluralInfo(void) const { + return fProperties->currencyPluralInfo.fPtr.getAlias(); +} -void DecimalFormat::adoptCurrencyPluralInfo(CurrencyPluralInfo* toAdopt) {} +void DecimalFormat::adoptCurrencyPluralInfo(CurrencyPluralInfo* toAdopt) { + fProperties->currencyPluralInfo.fPtr.adoptInstead(toAdopt); + refreshFormatterNoError(); +} -void DecimalFormat::setCurrencyPluralInfo(const CurrencyPluralInfo& info) {} +void DecimalFormat::setCurrencyPluralInfo(const CurrencyPluralInfo& info) { + *fProperties->currencyPluralInfo.fPtr = info; // copy-assignment operator + refreshFormatterNoError(); +} -UnicodeString& DecimalFormat::getPositivePrefix(UnicodeString& result) const {} +UnicodeString& DecimalFormat::getPositivePrefix(UnicodeString& result) const { + ErrorCode localStatus; + result = fFormatter->formatInt(1, localStatus).getPrefix(localStatus); + return result; +} -void DecimalFormat::setPositivePrefix(const UnicodeString& newValue) {} +void DecimalFormat::setPositivePrefix(const UnicodeString& newValue) { + fProperties->positivePrefix = newValue; + refreshFormatterNoError(); +} -UnicodeString& DecimalFormat::getNegativePrefix(UnicodeString& result) const {} +UnicodeString& DecimalFormat::getNegativePrefix(UnicodeString& result) const { + ErrorCode localStatus; + result = fFormatter->formatInt(-1, localStatus).getPrefix(localStatus); + return result; +} -void DecimalFormat::setNegativePrefix(const UnicodeString& newValue) {} +void DecimalFormat::setNegativePrefix(const UnicodeString& newValue) { + fProperties->negativePrefix = newValue; + refreshFormatterNoError(); +} -UnicodeString& DecimalFormat::getPositiveSuffix(UnicodeString& result) const {} +UnicodeString& DecimalFormat::getPositiveSuffix(UnicodeString& result) const { + ErrorCode localStatus; + result = fFormatter->formatInt(1, localStatus).getSuffix(localStatus); + return result; +} -void DecimalFormat::setPositiveSuffix(const UnicodeString& newValue) {} +void DecimalFormat::setPositiveSuffix(const UnicodeString& newValue) { + fProperties->positiveSuffix = newValue; + refreshFormatterNoError(); +} -UnicodeString& DecimalFormat::getNegativeSuffix(UnicodeString& result) const {} +UnicodeString& DecimalFormat::getNegativeSuffix(UnicodeString& result) const { + ErrorCode localStatus; + result = fFormatter->formatInt(-1, localStatus).getSuffix(localStatus); + return result; +} -void DecimalFormat::setNegativeSuffix(const UnicodeString& newValue) {} +void DecimalFormat::setNegativeSuffix(const UnicodeString& newValue) { + fProperties->negativeSuffix = newValue; + refreshFormatterNoError(); +} -int32_t DecimalFormat::getMultiplier(void) const {} +int32_t DecimalFormat::getMultiplier(void) const { + if (fProperties->multiplier != 1) { + return fProperties->multiplier; + } else if (fProperties->magnitudeMultiplier != 0) { + return static_cast(uprv_pow10(fProperties->magnitudeMultiplier)); + } else { + return 1; + } +} -void DecimalFormat::setMultiplier(int32_t newValue) {} +void DecimalFormat::setMultiplier(int32_t multiplier) { + if (multiplier == 0) { + multiplier = 1; // one being the benign default value for a multiplier. + } -double DecimalFormat::getRoundingIncrement(void) const {} + // Try to convert to a magnitude multiplier first + int delta = 0; + int value = multiplier; + while (value != 1) { + delta++; + int temp = value / 10; + if (temp * 10 != value) { + delta = -1; + break; + } + value = temp; + } + if (delta != -1) { + fProperties->magnitudeMultiplier = delta; + fProperties->multiplier = 1; + } else { + fProperties->magnitudeMultiplier = 0; + fProperties->multiplier = multiplier; + } + refreshFormatterNoError(); +} -void DecimalFormat::setRoundingIncrement(double newValue) {} +double DecimalFormat::getRoundingIncrement(void) const { + return fExportedProperties->roundingIncrement; +} -ERoundingMode DecimalFormat::getRoundingMode(void) const {} +void DecimalFormat::setRoundingIncrement(double newValue) { + fProperties->roundingIncrement = newValue; + refreshFormatterNoError(); +} -void DecimalFormat::setRoundingMode(ERoundingMode roundingMode) {} +ERoundingMode DecimalFormat::getRoundingMode(void) const { + // UNumberFormatRoundingMode and ERoundingMode have the same values. + return static_cast(fExportedProperties->roundingMode.getNoError()); +} -int32_t DecimalFormat::getFormatWidth(void) const {} +void DecimalFormat::setRoundingMode(ERoundingMode roundingMode) { + fProperties->roundingMode = static_cast(roundingMode); + refreshFormatterNoError(); +} -void DecimalFormat::setFormatWidth(int32_t width) {} +int32_t DecimalFormat::getFormatWidth(void) const { + return fProperties->formatWidth; +} -UnicodeString DecimalFormat::getPadCharacterString() const {} +void DecimalFormat::setFormatWidth(int32_t width) { + fProperties->formatWidth = width; + refreshFormatterNoError(); +} -void DecimalFormat::setPadCharacter(const UnicodeString& padChar) {} +UnicodeString DecimalFormat::getPadCharacterString() const { + return fProperties->padString; +} -EPadPosition DecimalFormat::getPadPosition(void) const {} +void DecimalFormat::setPadCharacter(const UnicodeString& padChar) { + if (padChar.length() > 0) { + fProperties->padString = UnicodeString(padChar.char32At(0)); + } else { + fProperties->padString.setToBogus(); + } + refreshFormatterNoError(); +} -void DecimalFormat::setPadPosition(EPadPosition padPos) {} +EPadPosition DecimalFormat::getPadPosition(void) const { + if (fProperties->padPosition.isNull()) { + return EPadPosition::kPadBeforePrefix; + } else { + // UNumberFormatPadPosition and EPadPosition have the same values. + return static_cast(fProperties->padPosition.getNoError()); + } +} -UBool DecimalFormat::isScientificNotation(void) const {} +void DecimalFormat::setPadPosition(EPadPosition padPos) { + fProperties->padPosition = static_cast(padPos); + refreshFormatterNoError(); +} -void DecimalFormat::setScientificNotation(UBool useScientific) {} +UBool DecimalFormat::isScientificNotation(void) const { + return fProperties->minimumExponentDigits != -1; +} -int8_t DecimalFormat::getMinimumExponentDigits(void) const {} +void DecimalFormat::setScientificNotation(UBool useScientific) { + if (useScientific) { + fProperties->minimumExponentDigits = 1; + } else { + fProperties->minimumExponentDigits = -1; + } + refreshFormatterNoError(); +} -void DecimalFormat::setMinimumExponentDigits(int8_t minExpDig) {} +int8_t DecimalFormat::getMinimumExponentDigits(void) const { + return static_cast(fProperties->minimumExponentDigits); +} -UBool DecimalFormat::isExponentSignAlwaysShown(void) const {} +void DecimalFormat::setMinimumExponentDigits(int8_t minExpDig) { + fProperties->minimumExponentDigits = minExpDig; + refreshFormatterNoError(); +} -void DecimalFormat::setExponentSignAlwaysShown(UBool expSignAlways) {} +UBool DecimalFormat::isExponentSignAlwaysShown(void) const { + return fProperties->exponentSignAlwaysShown; +} -int32_t DecimalFormat::getGroupingSize(void) const {} +void DecimalFormat::setExponentSignAlwaysShown(UBool expSignAlways) { + fProperties->exponentSignAlwaysShown = expSignAlways; + refreshFormatterNoError(); +} -void DecimalFormat::setGroupingSize(int32_t newValue) {} +int32_t DecimalFormat::getGroupingSize(void) const { + return fProperties->groupingSize; +} -int32_t DecimalFormat::getSecondaryGroupingSize(void) const {} +void DecimalFormat::setGroupingSize(int32_t newValue) { + fProperties->groupingSize = newValue; + refreshFormatterNoError(); +} -void DecimalFormat::setSecondaryGroupingSize(int32_t newValue) {} +int32_t DecimalFormat::getSecondaryGroupingSize(void) const { + int grouping1 = fProperties->groupingSize; + int grouping2 = fProperties->secondaryGroupingSize; + if (grouping1 == grouping2 || grouping2 < 0) { + return 0; + } + return grouping2; +} -int32_t DecimalFormat::getMinimumGroupingDigits() const {} +void DecimalFormat::setSecondaryGroupingSize(int32_t newValue) { + fProperties->secondaryGroupingSize = newValue; + refreshFormatterNoError(); +} -void DecimalFormat::setMinimumGroupingDigits(int32_t newValue) {} +int32_t DecimalFormat::getMinimumGroupingDigits() const { + return fProperties->minimumGroupingDigits; +} -UBool DecimalFormat::isDecimalSeparatorAlwaysShown(void) const {} +void DecimalFormat::setMinimumGroupingDigits(int32_t newValue) { + fProperties->minimumGroupingDigits = newValue; + refreshFormatterNoError(); +} -void DecimalFormat::setDecimalSeparatorAlwaysShown(UBool newValue) {} +UBool DecimalFormat::isDecimalSeparatorAlwaysShown(void) const { + return fProperties->decimalSeparatorAlwaysShown; +} -UBool DecimalFormat::isDecimalPatternMatchRequired(void) const {} +void DecimalFormat::setDecimalSeparatorAlwaysShown(UBool newValue) { + fProperties->decimalSeparatorAlwaysShown = newValue; + refreshFormatterNoError(); +} -void DecimalFormat::setDecimalPatternMatchRequired(UBool newValue) {} +UBool DecimalFormat::isDecimalPatternMatchRequired(void) const { + return fProperties->decimalPatternMatchRequired; +} -UnicodeString& DecimalFormat::toPattern(UnicodeString& result) const {} +void DecimalFormat::setDecimalPatternMatchRequired(UBool newValue) { + fProperties->decimalPatternMatchRequired = newValue; + refreshFormatterNoError(); +} -UnicodeString& DecimalFormat::toLocalizedPattern(UnicodeString& result) const {} +UBool DecimalFormat::getParseNoExponent() const { + return fProperties->parseNoExponent; +} + +void DecimalFormat::setParseNoExponent(UBool value) { + fProperties->parseNoExponent = value; + refreshFormatterNoError(); +} + +UnicodeString& DecimalFormat::toPattern(UnicodeString& result) const { + // Pull some properties from exportedProperties and others from properties + // to keep affix patterns intact. In particular, pull rounding properties + // so that CurrencyUsage is reflected properly. + // TODO: Consider putting this logic in number_patternstring.cpp instead. + ErrorCode localStatus; + DecimalFormatProperties tprops(*fProperties); + bool useCurrency = ((!tprops.currency.isNull()) || !tprops.currencyPluralInfo.fPtr.isNull() || + !tprops.currencyUsage.isNull() || AffixUtils::hasCurrencySymbols( + UnicodeStringCharSequence(tprops.positivePrefixPattern), localStatus) || + AffixUtils::hasCurrencySymbols( + UnicodeStringCharSequence(tprops.positiveSuffixPattern), localStatus) || + AffixUtils::hasCurrencySymbols( + UnicodeStringCharSequence(tprops.negativePrefixPattern), localStatus) || + AffixUtils::hasCurrencySymbols( + UnicodeStringCharSequence(tprops.negativeSuffixPattern), localStatus)); + if (useCurrency) { + tprops.minimumFractionDigits = fExportedProperties->minimumFractionDigits; + tprops.maximumFractionDigits = fExportedProperties->maximumFractionDigits; + tprops.roundingIncrement = fExportedProperties->roundingIncrement; + } + result = PatternStringUtils::propertiesToPatternString(tprops, localStatus); + return result; +} + +UnicodeString& DecimalFormat::toLocalizedPattern(UnicodeString& result) const { + ErrorCode localStatus; + result = toPattern(result); + result = PatternStringUtils::convertLocalized(result, *fSymbols, true, localStatus); + return result; +} + +void DecimalFormat::applyPattern(const UnicodeString& pattern, UParseError&, UErrorCode& status) { + // TODO: What is parseError for? + applyPattern(pattern, status); +} + +void DecimalFormat::applyPattern(const UnicodeString& pattern, UErrorCode& status) { + setPropertiesFromPattern(pattern, IGNORE_ROUNDING_NEVER, status); + refreshFormatter(status); +} + +void DecimalFormat::applyLocalizedPattern(const UnicodeString& localizedPattern, UParseError&, + UErrorCode& status) { + // TODO: What is parseError for? + applyLocalizedPattern(localizedPattern, status); +} + +void DecimalFormat::applyLocalizedPattern(const UnicodeString& localizedPattern, UErrorCode& status) { + UnicodeString pattern = PatternStringUtils::convertLocalized( + localizedPattern, *fSymbols, false, status); + applyPattern(pattern, status); +} + +void DecimalFormat::setMaximumIntegerDigits(int32_t newValue) { + fProperties->maximumIntegerDigits = newValue; + refreshFormatterNoError(); +} + +void DecimalFormat::setMinimumIntegerDigits(int32_t newValue) { + fProperties->minimumIntegerDigits = newValue; + refreshFormatterNoError(); +} + +void DecimalFormat::setMaximumFractionDigits(int32_t newValue) { + fProperties->maximumFractionDigits = newValue; + refreshFormatterNoError(); +} + +void DecimalFormat::setMinimumFractionDigits(int32_t newValue) { + fProperties->minimumFractionDigits = newValue; + refreshFormatterNoError(); +} + +int32_t DecimalFormat::getMinimumSignificantDigits() const { + return fExportedProperties->minimumSignificantDigits; +} + +int32_t DecimalFormat::getMaximumSignificantDigits() const { + return fExportedProperties->maximumSignificantDigits; +} + +void DecimalFormat::setMinimumSignificantDigits(int32_t value) { + int32_t max = fProperties->maximumSignificantDigits; + if (max >= 0 && max < value) { + fProperties->maximumSignificantDigits = value; + } + fProperties->minimumSignificantDigits = value; + refreshFormatterNoError(); +} + +void DecimalFormat::setMaximumSignificantDigits(int32_t value) { + int32_t min = fProperties->minimumSignificantDigits; + if (min >= 0 && min > value) { + fProperties->minimumSignificantDigits = value; + } + fProperties->maximumSignificantDigits = value; + refreshFormatterNoError(); +} + +UBool DecimalFormat::areSignificantDigitsUsed() const { + return fProperties->minimumSignificantDigits != -1 || fProperties->maximumSignificantDigits != -1; +} + +void DecimalFormat::setSignificantDigitsUsed(UBool useSignificantDigits) { + if (useSignificantDigits) { + // These are the default values from the old implementation. + fProperties->minimumSignificantDigits = 1; + fProperties->maximumSignificantDigits = 6; + } else { + fProperties->minimumSignificantDigits = -1; + fProperties->maximumSignificantDigits = -1; + } + refreshFormatterNoError(); +} + +void DecimalFormat::setCurrency(const char16_t* theCurrency, UErrorCode& ec) { + fProperties->currency = CurrencyUnit(theCurrency, ec); + // TODO: Set values in fSymbols, too? + refreshFormatterNoError(); +} + +void DecimalFormat::setCurrency(const char16_t* theCurrency) { + ErrorCode localStatus; + setCurrency(theCurrency, localStatus); +} + +void DecimalFormat::setCurrencyUsage(UCurrencyUsage newUsage, UErrorCode* ec) { + fProperties->currencyUsage = newUsage; + refreshFormatter(*ec); +} + +UCurrencyUsage DecimalFormat::getCurrencyUsage() const { + // CurrencyUsage is not exported, so we have to get it from the input property bag. + // TODO: Should we export CurrencyUsage instead? + if (fProperties->currencyUsage.isNull()) { + return UCURR_USAGE_STANDARD; + } + return fProperties->currencyUsage.getNoError(); +} void -DecimalFormat::applyPattern(const UnicodeString& pattern, UParseError& parseError, UErrorCode& status) {} - -void DecimalFormat::applyPattern(const UnicodeString& pattern, UErrorCode& status) {} - -void DecimalFormat::applyLocalizedPattern(const UnicodeString& pattern, UParseError& parseError, - UErrorCode& status) {} - -void DecimalFormat::applyLocalizedPattern(const UnicodeString& pattern, UErrorCode& status) {} - -void DecimalFormat::setMaximumIntegerDigits(int32_t newValue) {} - -void DecimalFormat::setMinimumIntegerDigits(int32_t newValue) {} - -void DecimalFormat::setMaximumFractionDigits(int32_t newValue) {} - -void DecimalFormat::setMinimumFractionDigits(int32_t newValue) {} - -int32_t DecimalFormat::getMinimumSignificantDigits() const {} - -int32_t DecimalFormat::getMaximumSignificantDigits() const {} - -void DecimalFormat::setMinimumSignificantDigits(int32_t min) {} - -void DecimalFormat::setMaximumSignificantDigits(int32_t max) {} - -UBool DecimalFormat::areSignificantDigitsUsed() const {} - -void DecimalFormat::setSignificantDigitsUsed(UBool useSignificantDigits) {} - -void DecimalFormat::setCurrency(const char16_t* theCurrency, UErrorCode& ec) {} - -void DecimalFormat::setCurrency(const char16_t* theCurrency) {} - -void DecimalFormat::setCurrencyUsage(UCurrencyUsage newUsage, UErrorCode* ec) {} - -UCurrencyUsage DecimalFormat::getCurrencyUsage() const {} - -void -DecimalFormat::formatToDecimalQuantity(double number, DecimalQuantity& output, UErrorCode& status) const {} +DecimalFormat::formatToDecimalQuantity(double number, DecimalQuantity& output, UErrorCode& status) const { + // TODO + status = U_UNSUPPORTED_ERROR; +} void DecimalFormat::formatToDecimalQuantity(const Formattable& number, DecimalQuantity& output, - UErrorCode& status) const {} + UErrorCode& status) const { + // TODO + status = U_UNSUPPORTED_ERROR; +} -number::LocalizedNumberFormatter DecimalFormat::toNumberFormatter() const {} +number::LocalizedNumberFormatter DecimalFormat::toNumberFormatter() const { + return *fFormatter; +} -UClassID DecimalFormat::getStaticClassID() {} +UClassID DecimalFormat::getStaticClassID() { + // TODO +} -UClassID DecimalFormat::getDynamicClassID() const {} +UClassID DecimalFormat::getDynamicClassID() const { + // TODO +} /** Rebuilds the formatter object from the property bag. */ void DecimalFormat::refreshFormatter(UErrorCode& status) { - if (exportedProperties == nullptr) { - // exportedProperties is null only when the formatter is not ready yet. + if (fExportedProperties == nullptr) { + // fExportedProperties is null only when the formatter is not ready yet. // The only time when this happens is during legacy deserialization. return; } Locale locale = getLocale(ULOC_ACTUAL_LOCALE, status); if (U_FAILURE(status)) { // Constructor - locale = symbols->getLocale(ULOC_ACTUAL_LOCALE, status); + locale = fSymbols->getLocale(ULOC_ACTUAL_LOCALE, status); } if (U_FAILURE(status)) { // Deserialization - locale = symbols->getLocale(); + locale = fSymbols->getLocale(); } if (U_FAILURE(status)) { return; } - *formatter = NumberPropertyMapper::create(*properties, *symbols, *exportedProperties, status).locale( - locale); - parser = NumberParserImpl::createParserFromProperties(*properties, *symbols, false, false, status); - parserWithCurrency = NumberParserImpl::createParserFromProperties( - *properties, *symbols, true, false, status); + fFormatter.adoptInsteadAndCheckErrorCode( + new LocalizedNumberFormatter( + NumberPropertyMapper::create( + *fProperties, *fSymbols, *fExportedProperties, status).locale( + locale)), status); + fParser.adoptInsteadAndCheckErrorCode( + NumberParserImpl::createParserFromProperties( + *fProperties, *fSymbols, false, false, status), status); + fParserWithCurrency.adoptInsteadAndCheckErrorCode( + NumberParserImpl::createParserFromProperties( + *fProperties, *fSymbols, true, false, status), status); } void DecimalFormat::refreshFormatterNoError() { @@ -625,7 +916,7 @@ void DecimalFormat::setPropertiesFromPattern(const UnicodeString& pattern, int32 UErrorCode& status) { // Cast workaround to get around putting the enum in the public header file auto actualIgnoreRounding = static_cast(ignoreRounding); - PatternParser::parseToExistingProperties(pattern, *properties, actualIgnoreRounding, status); + PatternParser::parseToExistingProperties(pattern, *fProperties, actualIgnoreRounding, status); } diff --git a/icu4c/source/i18n/number_decimfmtprops.cpp b/icu4c/source/i18n/number_decimfmtprops.cpp index cc57cfce6a..425f11490c 100644 --- a/icu4c/source/i18n/number_decimfmtprops.cpp +++ b/icu4c/source/i18n/number_decimfmtprops.cpp @@ -34,7 +34,7 @@ void DecimalFormatProperties::clear() { minimumGroupingDigits = -1; minimumIntegerDigits = -1; minimumSignificantDigits = -1; - multiplier = 0; + multiplier = 1; negativePrefix.setToBogus(); negativePrefixPattern.setToBogus(); negativeSuffix.setToBogus(); @@ -46,6 +46,7 @@ void DecimalFormatProperties::clear() { parseLenient = false; parseNoExponent = false; parseToBigDecimal = false; + parseAllInput = UNUM_MAYBE; positivePrefix.setToBogus(); positivePrefixPattern.setToBogus(); positiveSuffix.setToBogus(); @@ -88,6 +89,7 @@ bool DecimalFormatProperties::operator==(const DecimalFormatProperties &other) c eq = eq && parseLenient == other.parseLenient; eq = eq && parseNoExponent == other.parseNoExponent; eq = eq && parseToBigDecimal == other.parseToBigDecimal; + eq = eq && parseAllInput == other.parseAllInput; eq = eq && positivePrefix == other.positivePrefix; eq = eq && positivePrefixPattern == other.positivePrefixPattern; eq = eq && positiveSuffix == other.positiveSuffix; diff --git a/icu4c/source/i18n/number_decimfmtprops.h b/icu4c/source/i18n/number_decimfmtprops.h index dd23bbdd1d..890aa1dee8 100644 --- a/icu4c/source/i18n/number_decimfmtprops.h +++ b/icu4c/source/i18n/number_decimfmtprops.h @@ -30,22 +30,22 @@ template class U_I18N_API LocalPointer; namespace number { namespace impl { -// TODO: Figure out a nicer way to deal with CurrencyPluralInfo. // Exported as U_I18N_API because it is a public member field of exported DecimalFormatProperties -struct U_I18N_API CurrencyPluralInfoWrapper { - LocalPointer fPtr; +template +struct U_I18N_API CopyableLocalPointer { + LocalPointer fPtr; - CurrencyPluralInfoWrapper() = default; + CopyableLocalPointer() = default; - CurrencyPluralInfoWrapper(const CurrencyPluralInfoWrapper& other) { + CopyableLocalPointer(const CopyableLocalPointer& other) { if (!other.fPtr.isNull()) { - fPtr.adoptInstead(new CurrencyPluralInfo(*other.fPtr)); + fPtr.adoptInstead(new T(*other.fPtr)); } } - CurrencyPluralInfoWrapper& operator=(const CurrencyPluralInfoWrapper& other) { + CopyableLocalPointer& operator=(const CopyableLocalPointer& other) { if (!other.fPtr.isNull()) { - fPtr.adoptInstead(new CurrencyPluralInfo(*other.fPtr)); + fPtr.adoptInstead(new T(*other.fPtr)); } return *this; } @@ -57,7 +57,7 @@ struct U_I18N_API DecimalFormatProperties { public: NullableValue compactStyle; NullableValue currency; - CurrencyPluralInfoWrapper currencyPluralInfo; + CopyableLocalPointer currencyPluralInfo; NullableValue currencyUsage; bool decimalPatternMatchRequired; bool decimalSeparatorAlwaysShown; @@ -100,9 +100,9 @@ struct U_I18N_API DecimalFormatProperties { //DecimalFormatProperties(const DecimalFormatProperties &other) = default; - DecimalFormatProperties &operator=(const DecimalFormatProperties &other) = default; + DecimalFormatProperties& operator=(const DecimalFormatProperties& other) = default; - bool operator==(const DecimalFormatProperties &other) const; + bool operator==(const DecimalFormatProperties& other) const; void clear(); }; diff --git a/icu4c/source/i18n/number_types.h b/icu4c/source/i18n/number_types.h index 3e83312538..35a94bcded 100644 --- a/icu4c/source/i18n/number_types.h +++ b/icu4c/source/i18n/number_types.h @@ -301,6 +301,10 @@ class U_I18N_API NullableValue { return fValue; } + T getNoError() const { + return fValue; + } + private: bool fNull; T fValue; diff --git a/icu4c/source/i18n/numparse_symbols.cpp b/icu4c/source/i18n/numparse_symbols.cpp index 6492f1321f..0f35e31353 100644 --- a/icu4c/source/i18n/numparse_symbols.cpp +++ b/icu4c/source/i18n/numparse_symbols.cpp @@ -102,7 +102,7 @@ void IgnorablesMatcher::accept(StringSegment&, ParsedNumber&) const { InfinityMatcher::InfinityMatcher(const DecimalFormatSymbols& dfs) - : SymbolMatcher(dfs.getConstSymbol(DecimalFormatSymbols::kInfinitySymbol), unisets::INFINITY) { + : SymbolMatcher(dfs.getConstSymbol(DecimalFormatSymbols::kInfinitySymbol), unisets::INFINITY_KEY) { } bool InfinityMatcher::isDisabled(const ParsedNumber& result) const { diff --git a/icu4c/source/i18n/numparse_unisets.cpp b/icu4c/source/i18n/numparse_unisets.cpp index 0a8ec2bebb..3e7c9b0253 100644 --- a/icu4c/source/i18n/numparse_unisets.cpp +++ b/icu4c/source/i18n/numparse_unisets.cpp @@ -90,7 +90,7 @@ void U_CALLCONV initNumberParseUniSets(UErrorCode& status) { gUnicodeSets[PERCENT_SIGN] = new UnicodeSet(u"[%٪]", status); gUnicodeSets[PERMILLE_SIGN] = new UnicodeSet(u"[‰؉]", status); - gUnicodeSets[INFINITY] = new UnicodeSet(u"[∞]", status); + gUnicodeSets[INFINITY_KEY] = new UnicodeSet(u"[∞]", status); gUnicodeSets[DIGITS] = new UnicodeSet(u"[:digit:]", status); gUnicodeSets[NAN_LEAD] = new UnicodeSet( diff --git a/icu4c/source/i18n/numparse_unisets.h b/icu4c/source/i18n/numparse_unisets.h index 27f609dc5d..d66d4adab9 100644 --- a/icu4c/source/i18n/numparse_unisets.h +++ b/icu4c/source/i18n/numparse_unisets.h @@ -43,7 +43,7 @@ enum Key { PLUS_SIGN, PERCENT_SIGN, PERMILLE_SIGN, - INFINITY, + INFINITY_KEY, // INFINITY is defined in cmath // Other DIGITS, diff --git a/icu4c/source/i18n/unicode/decimfmt.h b/icu4c/source/i18n/unicode/decimfmt.h index fd2f747df5..443aa1a532 100644 --- a/icu4c/source/i18n/unicode/decimfmt.h +++ b/icu4c/source/i18n/unicode/decimfmt.h @@ -2004,32 +2004,27 @@ class U_I18N_API DecimalFormat : public NumberFormat { /** * The property bag corresponding to user-specified settings and settings from the pattern string. - * In principle this should be final, but serialize and clone won't work if it is final. Does not - * need to be volatile because the reference never changes. */ - number::impl::DecimalFormatProperties* properties; + LocalPointer fProperties; /** - * The symbols for the current locale. Volatile because threads may read and write at the same - * time. + * The symbols for the current locale. */ - const DecimalFormatSymbols* symbols; + LocalPointer fSymbols; /** * The pre-computed formatter object. Setters cause this to be re-computed atomically. The {@link - * #format} method uses the formatter directly without needing to synchronize. Volatile because - * threads may read and write at the same time. + * #format} method uses the formatter directly without needing to synchronize. */ - number::LocalizedNumberFormatter* formatter; + LocalPointer fFormatter; /** - * The effective properties as exported from the formatter object. Volatile because threads may - * read and write at the same time. + * The effective properties as exported from the formatter object. Used by the getters. */ - number::impl::DecimalFormatProperties* exportedProperties; + LocalPointer fExportedProperties; - const numparse::impl::NumberParserImpl* parser; - const numparse::impl::NumberParserImpl* parserWithCurrency; + LocalPointer fParser; + LocalPointer fParserWithCurrency; }; diff --git a/icu4c/source/i18n/unicode/numberformatter.h b/icu4c/source/i18n/unicode/numberformatter.h index 6f2b130894..a50e9d6dce 100644 --- a/icu4c/source/i18n/unicode/numberformatter.h +++ b/icu4c/source/i18n/unicode/numberformatter.h @@ -17,6 +17,7 @@ #include "unicode/plurrule.h" #include "unicode/ucurr.h" #include "unicode/unum.h" +#include "unicode/uobject.h" #ifndef U_HIDE_DRAFT_API @@ -2144,12 +2145,20 @@ class U_I18N_API FormattedNumber : public UMemory { void populateFieldPositionIterator(FieldPositionIterator &iterator, UErrorCode &status); #ifndef U_HIDE_INTERNAL_API + /** * Get an IFixedDecimal for plural rule selection. * Internal, not intended for public use. * @internal */ - const IFixedDecimal& getFixedDecimal(UErrorCode &status) const; + const IFixedDecimal& getFixedDecimal(UErrorCode& status) const; + + /** @internal */ + const UnicodeString getPrefix(UErrorCode& status) const; + + /** @internal */ + const UnicodeString getSuffix(UErrorCode& status) const; + #endif /** diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java index d9bd61a4a4..dc7cd640fd 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java @@ -1132,7 +1132,7 @@ public class DecimalFormat extends NumberFormat { // Try to convert to a magnitude multiplier first int delta = 0; int value = multiplier; - while (multiplier != 1) { + while (value != 1) { delta++; int temp = value / 10; if (temp * 10 != value) { @@ -1143,7 +1143,9 @@ public class DecimalFormat extends NumberFormat { } if (delta != -1) { properties.setMagnitudeMultiplier(delta); + properties.setMultiplier(null); } else { + properties.setMagnitudeMultiplier(0); properties.setMultiplier(java.math.BigDecimal.valueOf(multiplier)); } refreshFormatter(); @@ -1867,6 +1869,7 @@ public class DecimalFormat extends NumberFormat { if (enabled) { // Set to a reasonable default value properties.setGroupingSize(3); + properties.setSecondaryGroupingSize(3); } else { properties.setGroupingSize(0); properties.setSecondaryGroupingSize(0); @@ -1919,7 +1922,7 @@ public class DecimalFormat extends NumberFormat { if (grouping1 == grouping2 || grouping2 < 0) { return 0; } - return properties.getSecondaryGroupingSize(); + return grouping2; } /** @@ -1953,12 +1956,7 @@ public class DecimalFormat extends NumberFormat { */ @Deprecated public synchronized int getMinimumGroupingDigits() { - // Only 1 and 2 are supported right now. - if (properties.getMinimumGroupingDigits() == 2) { - return 2; - } else { - return 1; - } + return properties.getMinimumGroupingDigits(); } /** @@ -2437,8 +2435,15 @@ public class DecimalFormat extends NumberFormat { // to keep affix patterns intact. In particular, pull rounding properties // so that CurrencyUsage is reflected properly. // TODO: Consider putting this logic in PatternString.java instead. - DecimalFormatProperties tprops = threadLocalProperties.get().copyFrom(properties); - if (useCurrency(properties)) { + DecimalFormatProperties tprops = new DecimalFormatProperties().copyFrom(properties); + boolean useCurrency = ((tprops.getCurrency() != null) + || tprops.getCurrencyPluralInfo() != null + || tprops.getCurrencyUsage() != null + || AffixUtils.hasCurrencySymbols(tprops.getPositivePrefixPattern()) + || AffixUtils.hasCurrencySymbols(tprops.getPositiveSuffixPattern()) + || AffixUtils.hasCurrencySymbols(tprops.getNegativePrefixPattern()) + || AffixUtils.hasCurrencySymbols(tprops.getNegativeSuffixPattern())); + if (useCurrency) { tprops.setMinimumFractionDigits(exportedProperties.getMinimumFractionDigits()); tprops.setMaximumFractionDigits(exportedProperties.getMaximumFractionDigits()); tprops.setRoundingIncrement(exportedProperties.getRoundingIncrement()); @@ -2482,14 +2487,6 @@ public class DecimalFormat extends NumberFormat { return formatter.format(number).getFixedDecimal(); } - private static final ThreadLocal threadLocalProperties = - new ThreadLocal() { - @Override - protected DecimalFormatProperties initialValue() { - return new DecimalFormatProperties(); - } - }; - /** Rebuilds the formatter object from the property bag. */ void refreshFormatter() { if (exportedProperties == null) { @@ -2535,20 +2532,6 @@ public class DecimalFormat extends NumberFormat { } } - /** - * Returns true if the currency is set in The property bag or if currency symbols are present in - * the prefix/suffix pattern. - */ - private static boolean useCurrency(DecimalFormatProperties properties) { - return ((properties.getCurrency() != null) - || properties.getCurrencyPluralInfo() != null - || properties.getCurrencyUsage() != null - || AffixUtils.hasCurrencySymbols(properties.getPositivePrefixPattern()) - || AffixUtils.hasCurrencySymbols(properties.getPositiveSuffixPattern()) - || AffixUtils.hasCurrencySymbols(properties.getNegativePrefixPattern()) - || AffixUtils.hasCurrencySymbols(properties.getNegativeSuffixPattern())); - } - /** * Updates the property bag with settings from the given pattern. * From f5d2257d345f7ad2a0c4c917c9bb397f28350524 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Wed, 14 Mar 2018 10:41:27 +0000 Subject: [PATCH 038/129] ICU-13634 Implementing localized pattern converter and other pieces. X-SVN-Rev: 41104 --- icu4c/source/i18n/number_fluent.cpp | 109 ++++++++----- icu4c/source/i18n/number_mapper.cpp | 9 +- icu4c/source/i18n/number_patternstring.cpp | 145 +++++++++++++++++- icu4c/source/i18n/numparse_impl.cpp | 8 + icu4c/source/i18n/unicode/numberformatter.h | 5 +- icu4c/source/test/intltest/numbertest.h | 1 + .../intltest/numbertest_patternstring.cpp | 41 +++-- .../test/intltest/numbertest_unisets.cpp | 2 +- .../src/com/ibm/icu/text/DecimalFormat.java | 5 +- 9 files changed, 266 insertions(+), 59 deletions(-) diff --git a/icu4c/source/i18n/number_fluent.cpp b/icu4c/source/i18n/number_fluent.cpp index 64596af036..b5ff4532a5 100644 --- a/icu4c/source/i18n/number_fluent.cpp +++ b/icu4c/source/i18n/number_fluent.cpp @@ -16,7 +16,7 @@ using namespace icu::number; using namespace icu::number::impl; template -Derived NumberFormatterSettings::notation(const Notation ¬ation) const { +Derived NumberFormatterSettings::notation(const Notation& notation) const { Derived copy(*this); // NOTE: Slicing is OK. copy.fMacros.notation = notation; @@ -24,7 +24,7 @@ Derived NumberFormatterSettings::notation(const Notation ¬ation) con } template -Derived NumberFormatterSettings::unit(const icu::MeasureUnit &unit) const { +Derived NumberFormatterSettings::unit(const icu::MeasureUnit& unit) const { Derived copy(*this); // NOTE: Slicing occurs here. However, CurrencyUnit can be restored from MeasureUnit. // TimeUnit may be affected, but TimeUnit is not as relevant to number formatting. @@ -33,13 +33,13 @@ Derived NumberFormatterSettings::unit(const icu::MeasureUnit &unit) con } template -Derived NumberFormatterSettings::adoptUnit(icu::MeasureUnit *unit) const { +Derived NumberFormatterSettings::adoptUnit(icu::MeasureUnit* unit) const { Derived copy(*this); // Just copy the unit into the MacroProps by value, and delete it since we have ownership. // NOTE: Slicing occurs here. However, CurrencyUnit can be restored from MeasureUnit. // TimeUnit may be affected, but TimeUnit is not as relevant to number formatting. if (unit != nullptr) { - // TODO: On nullptr, reset to default value? + // TODO: On nullptr, reset to default value? copy.fMacros.unit = *unit; delete unit; } @@ -47,7 +47,7 @@ Derived NumberFormatterSettings::adoptUnit(icu::MeasureUnit *unit) cons } template -Derived NumberFormatterSettings::perUnit(const icu::MeasureUnit &perUnit) const { +Derived NumberFormatterSettings::perUnit(const icu::MeasureUnit& perUnit) const { Derived copy(*this); // See comments above about slicing. copy.fMacros.perUnit = perUnit; @@ -55,11 +55,11 @@ Derived NumberFormatterSettings::perUnit(const icu::MeasureUnit &perUni } template -Derived NumberFormatterSettings::adoptPerUnit(icu::MeasureUnit *perUnit) const { +Derived NumberFormatterSettings::adoptPerUnit(icu::MeasureUnit* perUnit) const { Derived copy(*this); // See comments above about slicing and ownership. if (perUnit != nullptr) { - // TODO: On nullptr, reset to default value? + // TODO: On nullptr, reset to default value? copy.fMacros.perUnit = *perUnit; delete perUnit; } @@ -67,7 +67,7 @@ Derived NumberFormatterSettings::adoptPerUnit(icu::MeasureUnit *perUnit } template -Derived NumberFormatterSettings::rounding(const Rounder &rounder) const { +Derived NumberFormatterSettings::rounding(const Rounder& rounder) const { Derived copy(*this); // NOTE: Slicing is OK. copy.fMacros.rounder = rounder; @@ -75,7 +75,7 @@ Derived NumberFormatterSettings::rounding(const Rounder &rounder) const } template -Derived NumberFormatterSettings::grouping(const UGroupingStrategy &strategy) const { +Derived NumberFormatterSettings::grouping(const UGroupingStrategy& strategy) const { Derived copy(*this); // NOTE: This is slightly different than how the setting is stored in Java // because we want to put it on the stack. @@ -84,49 +84,49 @@ Derived NumberFormatterSettings::grouping(const UGroupingStrategy &stra } template -Derived NumberFormatterSettings::integerWidth(const IntegerWidth &style) const { +Derived NumberFormatterSettings::integerWidth(const IntegerWidth& style) const { Derived copy(*this); copy.fMacros.integerWidth = style; return copy; } template -Derived NumberFormatterSettings::symbols(const DecimalFormatSymbols &symbols) const { +Derived NumberFormatterSettings::symbols(const DecimalFormatSymbols& symbols) const { Derived copy(*this); copy.fMacros.symbols.setTo(symbols); return copy; } template -Derived NumberFormatterSettings::adoptSymbols(NumberingSystem *ns) const { +Derived NumberFormatterSettings::adoptSymbols(NumberingSystem* ns) const { Derived copy(*this); copy.fMacros.symbols.setTo(ns); return copy; } template -Derived NumberFormatterSettings::unitWidth(const UNumberUnitWidth &width) const { +Derived NumberFormatterSettings::unitWidth(const UNumberUnitWidth& width) const { Derived copy(*this); copy.fMacros.unitWidth = width; return copy; } template -Derived NumberFormatterSettings::sign(const UNumberSignDisplay &style) const { +Derived NumberFormatterSettings::sign(const UNumberSignDisplay& style) const { Derived copy(*this); copy.fMacros.sign = style; return copy; } template -Derived NumberFormatterSettings::decimal(const UNumberDecimalSeparatorDisplay &style) const { +Derived NumberFormatterSettings::decimal(const UNumberDecimalSeparatorDisplay& style) const { Derived copy(*this); copy.fMacros.decimal = style; return copy; } template -Derived NumberFormatterSettings::padding(const Padder &padder) const { +Derived NumberFormatterSettings::padding(const Padder& padder) const { Derived copy(*this); copy.fMacros.padder = padder; return copy; @@ -159,38 +159,38 @@ UnlocalizedNumberFormatter NumberFormatter::with() { return result; } -LocalizedNumberFormatter NumberFormatter::withLocale(const Locale &locale) { +LocalizedNumberFormatter NumberFormatter::withLocale(const Locale& locale) { return with().locale(locale); } // Make the child class constructor that takes the parent class call the parent class's copy constructor UnlocalizedNumberFormatter::UnlocalizedNumberFormatter( - const NumberFormatterSettings &other) + const NumberFormatterSettings& other) : NumberFormatterSettings(other) { } // Make the child class constructor that takes the parent class call the parent class's copy constructor // For LocalizedNumberFormatter, also copy over the extra fields LocalizedNumberFormatter::LocalizedNumberFormatter( - const NumberFormatterSettings &other) + const NumberFormatterSettings& other) : NumberFormatterSettings(other) { // No additional copies required } -LocalizedNumberFormatter::LocalizedNumberFormatter(const MacroProps ¯os, const Locale &locale) { +LocalizedNumberFormatter::LocalizedNumberFormatter(const MacroProps& macros, const Locale& locale) { fMacros = macros; fMacros.locale = locale; } -LocalizedNumberFormatter UnlocalizedNumberFormatter::locale(const Locale &locale) const { +LocalizedNumberFormatter UnlocalizedNumberFormatter::locale(const Locale& locale) const { return LocalizedNumberFormatter(fMacros, locale); } -SymbolsWrapper::SymbolsWrapper(const SymbolsWrapper &other) { +SymbolsWrapper::SymbolsWrapper(const SymbolsWrapper& other) { doCopyFrom(other); } -SymbolsWrapper &SymbolsWrapper::operator=(const SymbolsWrapper &other) { +SymbolsWrapper& SymbolsWrapper::operator=(const SymbolsWrapper& other) { if (this == &other) { return *this; } @@ -203,19 +203,19 @@ SymbolsWrapper::~SymbolsWrapper() { doCleanup(); } -void SymbolsWrapper::setTo(const DecimalFormatSymbols &dfs) { +void SymbolsWrapper::setTo(const DecimalFormatSymbols& dfs) { doCleanup(); fType = SYMPTR_DFS; fPtr.dfs = new DecimalFormatSymbols(dfs); } -void SymbolsWrapper::setTo(const NumberingSystem *ns) { +void SymbolsWrapper::setTo(const NumberingSystem* ns) { doCleanup(); fType = SYMPTR_NS; fPtr.ns = ns; } -void SymbolsWrapper::doCopyFrom(const SymbolsWrapper &other) { +void SymbolsWrapper::doCopyFrom(const SymbolsWrapper& other) { fType = other.fType; switch (fType) { case SYMPTR_NONE: @@ -276,7 +276,7 @@ LocalizedNumberFormatter::~LocalizedNumberFormatter() { delete fCompiled; } -FormattedNumber LocalizedNumberFormatter::formatInt(int64_t value, UErrorCode &status) const { +FormattedNumber LocalizedNumberFormatter::formatInt(int64_t value, UErrorCode& status) const { if (U_FAILURE(status)) { return FormattedNumber(U_ILLEGAL_ARGUMENT_ERROR); } auto results = new NumberFormatterResults(); if (results == nullptr) { @@ -287,7 +287,7 @@ FormattedNumber LocalizedNumberFormatter::formatInt(int64_t value, UErrorCode &s return formatImpl(results, status); } -FormattedNumber LocalizedNumberFormatter::formatDouble(double value, UErrorCode &status) const { +FormattedNumber LocalizedNumberFormatter::formatDouble(double value, UErrorCode& status) const { if (U_FAILURE(status)) { return FormattedNumber(U_ILLEGAL_ARGUMENT_ERROR); } auto results = new NumberFormatterResults(); if (results == nullptr) { @@ -298,7 +298,7 @@ FormattedNumber LocalizedNumberFormatter::formatDouble(double value, UErrorCode return formatImpl(results, status); } -FormattedNumber LocalizedNumberFormatter::formatDecimal(StringPiece value, UErrorCode &status) const { +FormattedNumber LocalizedNumberFormatter::formatDecimal(StringPiece value, UErrorCode& status) const { if (U_FAILURE(status)) { return FormattedNumber(U_ILLEGAL_ARGUMENT_ERROR); } auto results = new NumberFormatterResults(); if (results == nullptr) { @@ -309,7 +309,8 @@ FormattedNumber LocalizedNumberFormatter::formatDecimal(StringPiece value, UErro return formatImpl(results, status); } -FormattedNumber LocalizedNumberFormatter::formatDecimalQuantity(const DecimalQuantity& dq, UErrorCode &status) const { +FormattedNumber +LocalizedNumberFormatter::formatDecimalQuantity(const DecimalQuantity& dq, UErrorCode& status) const { if (U_FAILURE(status)) { return FormattedNumber(U_ILLEGAL_ARGUMENT_ERROR); } auto results = new NumberFormatterResults(); if (results == nullptr) { @@ -321,15 +322,16 @@ FormattedNumber LocalizedNumberFormatter::formatDecimalQuantity(const DecimalQua } FormattedNumber -LocalizedNumberFormatter::formatImpl(impl::NumberFormatterResults *results, UErrorCode &status) const { +LocalizedNumberFormatter::formatImpl(impl::NumberFormatterResults* results, UErrorCode& status) const { // fUnsafeCallCount contains memory to be interpreted as an atomic int, most commonly // std::atomic. Since the type of atomic int is platform-dependent, we cast the // bytes in fUnsafeCallCount to u_atomic_int32_t, a typedef for the platform-dependent // atomic int type defined in umutex.h. - static_assert(sizeof(u_atomic_int32_t) <= sizeof(fUnsafeCallCount), - "Atomic integer size on this platform exceeds the size allocated by fUnsafeCallCount"); + static_assert( + sizeof(u_atomic_int32_t) <= sizeof(fUnsafeCallCount), + "Atomic integer size on this platform exceeds the size allocated by fUnsafeCallCount"); u_atomic_int32_t* callCount = reinterpret_cast( - const_cast(this)->fUnsafeCallCount); + const_cast(this)->fUnsafeCallCount); // A positive value in the atomic int indicates that the data structure is not yet ready; // a negative value indicates that it is ready. If, after the increment, the atomic int @@ -343,10 +345,9 @@ LocalizedNumberFormatter::formatImpl(impl::NumberFormatterResults *results, UErr if (currentCount == fMacros.threshold && fMacros.threshold > 0) { // Build the data structure and then use it (slow to fast path). - const NumberFormatterImpl* compiled = - NumberFormatterImpl::fromMacros(fMacros, status); + const NumberFormatterImpl* compiled = NumberFormatterImpl::fromMacros(fMacros, status); U_ASSERT(fCompiled == nullptr); - const_cast(this)->fCompiled = compiled; + const_cast(this)->fCompiled = compiled; umtx_storeRelease(*callCount, INT32_MIN); compiled->apply(results->quantity, results->string, status); } else if (currentCount < 0) { @@ -375,7 +376,7 @@ UnicodeString FormattedNumber::toString() const { return fResults->string.toUnicodeString(); } -Appendable &FormattedNumber::appendTo(Appendable &appendable) { +Appendable& FormattedNumber::appendTo(Appendable& appendable) { if (fResults == nullptr) { // TODO: http://bugs.icu-project.org/trac/ticket/13437 return appendable; @@ -384,7 +385,7 @@ Appendable &FormattedNumber::appendTo(Appendable &appendable) { return appendable; } -void FormattedNumber::populateFieldPosition(FieldPosition &fieldPosition, UErrorCode &status) { +void FormattedNumber::populateFieldPosition(FieldPosition& fieldPosition, UErrorCode& status) { if (U_FAILURE(status)) { return; } if (fResults == nullptr) { status = fErrorCode; @@ -393,8 +394,7 @@ void FormattedNumber::populateFieldPosition(FieldPosition &fieldPosition, UError fResults->string.populateFieldPosition(fieldPosition, 0, status); } -void -FormattedNumber::populateFieldPositionIterator(FieldPositionIterator &iterator, UErrorCode &status) { +void FormattedNumber::populateFieldPositionIterator(FieldPositionIterator& iterator, UErrorCode& status) { if (U_FAILURE(status)) { return; } if (fResults == nullptr) { status = fErrorCode; @@ -403,6 +403,33 @@ FormattedNumber::populateFieldPositionIterator(FieldPositionIterator &iterator, fResults->string.populateFieldPositionIterator(iterator, status); } +void FormattedNumber::getDecimalQuantity(DecimalQuantity& output, UErrorCode& status) const { + if (U_FAILURE(status)) { return; } + if (fResults == nullptr) { + status = fErrorCode; + return; + } + output = fResults->quantity; +} + +const UnicodeString FormattedNumber::getPrefix(UErrorCode& status) const { + if (fResults == nullptr) { + status = fErrorCode; + return {}; + } + // FIXME + return {}; +} + +const UnicodeString FormattedNumber::getSuffix(UErrorCode& status) const { + if (fResults == nullptr) { + status = fErrorCode; + return {}; + } + // FIXME + return {}; +} + FormattedNumber::~FormattedNumber() { delete fResults; } diff --git a/icu4c/source/i18n/number_mapper.cpp b/icu4c/source/i18n/number_mapper.cpp index ac8cda356f..b1e596671d 100644 --- a/icu4c/source/i18n/number_mapper.cpp +++ b/icu4c/source/i18n/number_mapper.cpp @@ -16,7 +16,14 @@ using namespace icu::number; using namespace icu::number::impl; - +UnlocalizedNumberFormatter NumberPropertyMapper::create(const DecimalFormatProperties& properties, + const DecimalFormatSymbols& symbols, + DecimalFormatProperties& exportedProperties, + UErrorCode& status) { + // TODO + status = U_UNSUPPORTED_ERROR; + return NumberFormatter::with(); +} diff --git a/icu4c/source/i18n/number_patternstring.cpp b/icu4c/source/i18n/number_patternstring.cpp index 68f55001bb..7bdd6fb71d 100644 --- a/icu4c/source/i18n/number_patternstring.cpp +++ b/icu4c/source/i18n/number_patternstring.cpp @@ -5,6 +5,11 @@ #if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT +#define UNISTR_FROM_CHAR_EXPLICIT + #include "uassert.h" #include "number_patternstring.h" #include "unicode/utf16.h" @@ -633,7 +638,7 @@ UnicodeString PatternStringUtils::propertiesToPatternString(const DecimalFormatP int groupingSize = uprv_min(properties.secondaryGroupingSize, dosMax); int firstGroupingSize = uprv_min(properties.groupingSize, dosMax); int paddingWidth = uprv_min(properties.formatWidth, dosMax); - NullableValue paddingLocation = properties.padPosition; + NullableValue paddingLocation = properties.padPosition; UnicodeString paddingString = properties.padString; int minInt = uprv_max(uprv_min(properties.minimumIntegerDigits, dosMax), 0); int maxInt = uprv_min(properties.maximumIntegerDigits, dosMax); @@ -841,6 +846,144 @@ int PatternStringUtils::escapePaddingString(UnicodeString input, UnicodeString& return output.length() - startLength; } +UnicodeString +PatternStringUtils::convertLocalized(UnicodeString input, DecimalFormatSymbols symbols, bool toLocalized, + UErrorCode& status) { + // Construct a table of strings to be converted between localized and standard. + static constexpr int32_t LEN = 21; + UnicodeString table[LEN][2]; + int standIdx = toLocalized ? 0 : 1; + int localIdx = toLocalized ? 1 : 0; + table[0][standIdx] = u"%"; + table[0][localIdx] = symbols.getConstSymbol(DecimalFormatSymbols::kPercentSymbol); + table[1][standIdx] = u"‰"; + table[1][localIdx] = symbols.getConstSymbol(DecimalFormatSymbols::kPerMillSymbol); + table[2][standIdx] = u"."; + table[2][localIdx] = symbols.getConstSymbol(DecimalFormatSymbols::kDecimalSeparatorSymbol); + table[3][standIdx] = u","; + table[3][localIdx] = symbols.getConstSymbol(DecimalFormatSymbols::kGroupingSeparatorSymbol); + table[4][standIdx] = u"-"; + table[4][localIdx] = symbols.getConstSymbol(DecimalFormatSymbols::kMinusSignSymbol); + table[5][standIdx] = u"+"; + table[5][localIdx] = symbols.getConstSymbol(DecimalFormatSymbols::kPlusSignSymbol); + table[6][standIdx] = u";"; + table[6][localIdx] = symbols.getConstSymbol(DecimalFormatSymbols::kPatternSeparatorSymbol); + table[7][standIdx] = u"@"; + table[7][localIdx] = symbols.getConstSymbol(DecimalFormatSymbols::kSignificantDigitSymbol); + table[8][standIdx] = u"E"; + table[8][localIdx] = symbols.getConstSymbol(DecimalFormatSymbols::kExponentialSymbol); + table[9][standIdx] = u"*"; + table[9][localIdx] = symbols.getConstSymbol(DecimalFormatSymbols::kPadEscapeSymbol); + table[10][standIdx] = u"#"; + table[10][localIdx] = symbols.getConstSymbol(DecimalFormatSymbols::kDigitSymbol); + for (int i = 0; i < 10; i++) { + table[11 + i][standIdx] = u'0' + i; + table[11 + i][localIdx] = symbols.getConstDigitSymbol(i); + } + + // Special case: quotes are NOT allowed to be in any localIdx strings. + // Substitute them with '’' instead. + for (int32_t i = 0; i < LEN; i++) { + table[i][localIdx].findAndReplace(u'\'', u'’'); + } + + // Iterate through the string and convert. + // State table: + // 0 => base state + // 1 => first char inside a quoted sequence in input and output string + // 2 => inside a quoted sequence in input and output string + // 3 => first char after a close quote in input string; + // close quote still needs to be written to output string + // 4 => base state in input string; inside quoted sequence in output string + // 5 => first char inside a quoted sequence in input string; + // inside quoted sequence in output string + UnicodeString result; + int state = 0; + for (int offset = 0; offset < input.length(); offset++) { + UChar ch = input.charAt(offset); + + // Handle a quote character (state shift) + if (ch == u'\'') { + if (state == 0) { + result.append(u'\''); + state = 1; + continue; + } else if (state == 1) { + result.append(u'\''); + state = 0; + continue; + } else if (state == 2) { + state = 3; + continue; + } else if (state == 3) { + result.append(u'\''); + result.append(u'\''); + state = 1; + continue; + } else if (state == 4) { + state = 5; + continue; + } else { + U_ASSERT(state == 5); + result.append(u'\''); + result.append(u'\''); + state = 4; + continue; + } + } + + if (state == 0 || state == 3 || state == 4) { + for (auto& pair : table) { + // Perform a greedy match on this symbol string + UnicodeString temp = input.tempSubString(offset, pair[0].length()); + if (temp == pair[0]) { + // Skip ahead past this region for the next iteration + offset += pair[0].length() - 1; + if (state == 3 || state == 4) { + result.append(u'\''); + state = 0; + } + result.append(pair[1]); + goto continue_outer; + } + } + // No replacement found. Check if a special quote is necessary + for (auto& pair : table) { + UnicodeString temp = input.tempSubString(offset, pair[1].length()); + if (temp == pair[1]) { + if (state == 0) { + result.append(u'\''); + state = 4; + } + result.append(ch); + goto continue_outer; + } + } + // Still nothing. Copy the char verbatim. (Add a close quote if necessary) + if (state == 3 || state == 4) { + result.append(u'\''); + state = 0; + } + result.append(ch); + } else { + U_ASSERT(state == 1 || state == 2 || state == 5); + result.append(ch); + state = 2; + } + continue_outer:; + } + // Resolve final quotes + if (state == 3 || state == 4) { + result.append(u'\''); + state = 0; + } + if (state != 0) { + // Malformed localized pattern: unterminated quote + status = U_PATTERN_SYNTAX_ERROR; + } + return result; +} + void PatternStringUtils::patternInfoToStringBuilder(const AffixPatternProvider& patternInfo, bool isPrefix, int8_t signum, UNumberSignDisplay signDisplay, StandardPlural::Form plural, diff --git a/icu4c/source/i18n/numparse_impl.cpp b/icu4c/source/i18n/numparse_impl.cpp index 06efe9d1b1..3e04723be1 100644 --- a/icu4c/source/i18n/numparse_impl.cpp +++ b/icu4c/source/i18n/numparse_impl.cpp @@ -74,6 +74,14 @@ NumberParserImpl::createSimpleParser(const Locale& locale, const UnicodeString& return parser; } +NumberParserImpl* NumberParserImpl::createParserFromProperties( + const number::impl::DecimalFormatProperties& properties, DecimalFormatSymbols symbols, + bool parseCurrency, bool optimize, UErrorCode& status) { + // TODO + status = U_UNSUPPORTED_ERROR; + return nullptr; +} + NumberParserImpl::NumberParserImpl(parse_flags_t parseFlags, bool computeLeads) : fParseFlags(parseFlags), fComputeLeads(computeLeads) { } diff --git a/icu4c/source/i18n/unicode/numberformatter.h b/icu4c/source/i18n/unicode/numberformatter.h index a50e9d6dce..56d67668e7 100644 --- a/icu4c/source/i18n/unicode/numberformatter.h +++ b/icu4c/source/i18n/unicode/numberformatter.h @@ -2147,11 +2147,10 @@ class U_I18N_API FormattedNumber : public UMemory { #ifndef U_HIDE_INTERNAL_API /** - * Get an IFixedDecimal for plural rule selection. - * Internal, not intended for public use. + * Gets the raw DecimalQuantity for plural rule selection. * @internal */ - const IFixedDecimal& getFixedDecimal(UErrorCode& status) const; + void getDecimalQuantity(impl::DecimalQuantity& output, UErrorCode& status) const; /** @internal */ const UnicodeString getPrefix(UErrorCode& status) const; diff --git a/icu4c/source/test/intltest/numbertest.h b/icu4c/source/test/intltest/numbertest.h index 9ee15f4ece..3c2a62a805 100644 --- a/icu4c/source/test/intltest/numbertest.h +++ b/icu4c/source/test/intltest/numbertest.h @@ -166,6 +166,7 @@ class PatternModifierTest : public IntlTest { class PatternStringTest : public IntlTest { public: + void testLocalized(); void testToPatternSimple(); void testExceptionOnInvalid(); void testBug13117(); diff --git a/icu4c/source/test/intltest/numbertest_patternstring.cpp b/icu4c/source/test/intltest/numbertest_patternstring.cpp index aa2c2b4736..67d6cce6ca 100644 --- a/icu4c/source/test/intltest/numbertest_patternstring.cpp +++ b/icu4c/source/test/intltest/numbertest_patternstring.cpp @@ -8,19 +8,42 @@ #include "numbertest.h" #include "number_patternstring.h" -void PatternStringTest::runIndexedTest(int32_t index, UBool exec, const char *&name, char *) { +void PatternStringTest::runIndexedTest(int32_t index, UBool exec, const char*& name, char*) { if (exec) { logln("TestSuite PatternStringTest: "); } TESTCASE_AUTO_BEGIN; + TESTCASE_AUTO(testLocalized); TESTCASE_AUTO(testToPatternSimple); TESTCASE_AUTO(testExceptionOnInvalid); TESTCASE_AUTO(testBug13117); TESTCASE_AUTO_END; } +void PatternStringTest::testLocalized() { + IcuTestErrorCode status(*this, "testLocalized"); + DecimalFormatSymbols symbols(Locale::getEnglish(), status); + symbols.setSymbol(DecimalFormatSymbols::kDecimalSeparatorSymbol, u"a", status); + symbols.setSymbol(DecimalFormatSymbols::kPercentSymbol, u"b", status); + symbols.setSymbol(DecimalFormatSymbols::kMinusSignSymbol, u".", status); + symbols.setSymbol(DecimalFormatSymbols::kPlusSignSymbol, u"'", status); + + UnicodeString standard = u"+-abcb''a''#,##0.0%'a%'"; + UnicodeString localized = u"’.'ab'c'b''a'''#,##0a0b'a%'"; + UnicodeString toStandard = u"+-'ab'c'b''a'''#,##0.0%'a%'"; + + assertEquals( + "standard to localized", + localized, + PatternStringUtils::convertLocalized(standard, symbols, true, status)); + assertEquals( + "localized to standard", + toStandard, + PatternStringUtils::convertLocalized(localized, symbols, false, status)); +} + void PatternStringTest::testToPatternSimple() { - const char16_t *cases[][2] = {{u"#", u"0"}, + const char16_t* cases[][2] = {{u"#", u"0"}, {u"0", u"0"}, {u"#0", u"0"}, {u"###", u"0"}, @@ -42,12 +65,12 @@ void PatternStringTest::testToPatternSimple() { {u"*'நி'##0", u"*'நி'##0"},}; UErrorCode status = U_ZERO_ERROR; - for (const char16_t **cas : cases) { + for (const char16_t** cas : cases) { UnicodeString input(cas[0]); UnicodeString output(cas[1]); DecimalFormatProperties properties = PatternParser::parseToProperties( - input, PatternParser::IGNORE_ROUNDING_NEVER, status); + input, IGNORE_ROUNDING_NEVER, status); assertSuccess(input, status); UnicodeString actual = PatternStringUtils::propertiesToPatternString(properties, status); assertEquals(input, output, actual); @@ -55,7 +78,7 @@ void PatternStringTest::testToPatternSimple() { } void PatternStringTest::testExceptionOnInvalid() { - static const char16_t *invalidPatterns[] = { + static const char16_t* invalidPatterns[] = { u"#.#.#", u"0#", u"0#.", @@ -80,13 +103,9 @@ void PatternStringTest::testExceptionOnInvalid() { void PatternStringTest::testBug13117() { UErrorCode status = U_ZERO_ERROR; DecimalFormatProperties expected = PatternParser::parseToProperties( - u"0", - PatternParser::IGNORE_ROUNDING_NEVER, - status); + u"0", IGNORE_ROUNDING_NEVER, status); DecimalFormatProperties actual = PatternParser::parseToProperties( - u"0;", - PatternParser::IGNORE_ROUNDING_NEVER, - status); + u"0;", IGNORE_ROUNDING_NEVER, status); assertSuccess("Spot 1", status); assertTrue("Should not consume negative subpattern", expected == actual); } diff --git a/icu4c/source/test/intltest/numbertest_unisets.cpp b/icu4c/source/test/intltest/numbertest_unisets.cpp index ed7fb08d83..f0623b2bd1 100644 --- a/icu4c/source/test/intltest/numbertest_unisets.cpp +++ b/icu4c/source/test/intltest/numbertest_unisets.cpp @@ -45,7 +45,7 @@ void UniSetsTest::testSetCoverage() { const UnicodeSet &minusSign = *get(unisets::MINUS_SIGN); const UnicodeSet &percent = *get(unisets::PERCENT_SIGN); const UnicodeSet &permille = *get(unisets::PERMILLE_SIGN); - const UnicodeSet &infinity = *get(unisets::INFINITY); + const UnicodeSet &infinity = *get(unisets::INFINITY_KEY); const UnicodeSet &nanLead = *get(unisets::NAN_LEAD); const UnicodeSet &scientificLead = *get(unisets::SCIENTIFIC_LEAD); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java index dc7cd640fd..c0939e39ae 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java @@ -1956,7 +1956,10 @@ public class DecimalFormat extends NumberFormat { */ @Deprecated public synchronized int getMinimumGroupingDigits() { - return properties.getMinimumGroupingDigits(); + if (properties.getMinimumGroupingDigits() > 0) { + return properties.getMinimumGroupingDigits(); + } + return 1; } /** From 1a95c170d2bec74d4f44205f07bea3c49bc85811 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Thu, 15 Mar 2018 07:46:56 +0000 Subject: [PATCH 039/129] ICU-13634 Number property mapper is building. Currently there is a linker error. X-SVN-Rev: 41107 --- icu4c/source/i18n/number_grouping.cpp | 9 + icu4c/source/i18n/number_mapper.cpp | 298 +++++++++++++++++- icu4c/source/i18n/number_mapper.h | 102 +++++- icu4c/source/i18n/number_notation.cpp | 13 + icu4c/source/i18n/number_padding.cpp | 13 + icu4c/source/i18n/number_patternstring.cpp | 5 + icu4c/source/i18n/number_patternstring.h | 2 + icu4c/source/i18n/number_types.h | 4 + .../i18n/unicode/compactdecimalformat.h | 2 +- icu4c/source/i18n/unicode/decimfmt.h | 8 +- .../src/com/ibm/icu/impl/number/Padder.java | 6 + .../ibm/icu/number/NumberPropertyMapper.java | 10 +- 12 files changed, 451 insertions(+), 21 deletions(-) diff --git a/icu4c/source/i18n/number_grouping.cpp b/icu4c/source/i18n/number_grouping.cpp index 1f4a163d7b..a77e505a22 100644 --- a/icu4c/source/i18n/number_grouping.cpp +++ b/icu4c/source/i18n/number_grouping.cpp @@ -51,6 +51,15 @@ Grouper Grouper::forStrategy(UGroupingStrategy grouping) { } } +Grouper Grouper::forProperties(const DecimalFormatProperties& properties) { + auto grouping1 = static_cast(properties.groupingSize); + auto grouping2 = static_cast(properties.secondaryGroupingSize); + auto minGrouping = static_cast(properties.minimumGroupingDigits); + grouping1 = grouping1 > 0 ? grouping1 : grouping2 > 0 ? grouping2 : grouping1; + grouping2 = grouping2 > 0 ? grouping2 : grouping1; + return {grouping1, grouping2, minGrouping}; +} + void Grouper::setLocaleData(const impl::ParsedPatternInfo &patternInfo, const Locale& locale) { if (fGrouping1 != -2 && fGrouping2 != -4) { return; diff --git a/icu4c/source/i18n/number_mapper.cpp b/icu4c/source/i18n/number_mapper.cpp index b1e596671d..949f88aadd 100644 --- a/icu4c/source/i18n/number_mapper.cpp +++ b/icu4c/source/i18n/number_mapper.cpp @@ -10,6 +10,7 @@ #define UNISTR_FROM_STRING_EXPLICIT #include "number_mapper.h" +#include "number_patternstring.h" using namespace icu; using namespace icu::number; @@ -18,13 +19,306 @@ using namespace icu::number::impl; UnlocalizedNumberFormatter NumberPropertyMapper::create(const DecimalFormatProperties& properties, const DecimalFormatSymbols& symbols, + DecimalFormatWarehouse& warehouse, + UErrorCode& status) { + return NumberFormatter::with().macros(oldToNew(properties, symbols, warehouse, nullptr, status)); +} + +UnlocalizedNumberFormatter NumberPropertyMapper::create(const DecimalFormatProperties& properties, + const DecimalFormatSymbols& symbols, + DecimalFormatWarehouse& warehouse, DecimalFormatProperties& exportedProperties, UErrorCode& status) { + return NumberFormatter::with().macros( + oldToNew( + properties, symbols, warehouse, &exportedProperties, status)); +} + +MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& properties, + const DecimalFormatSymbols& symbols, + DecimalFormatWarehouse& warehouse, + DecimalFormatProperties* exportedProperties, + UErrorCode& status) { + MacroProps macros; + Locale locale = symbols.getLocale(); + + ///////////// + // SYMBOLS // + ///////////// + + macros.symbols.setTo(symbols); + + ////////////////// + // PLURAL RULES // + ////////////////// + // TODO - status = U_UNSUPPORTED_ERROR; - return NumberFormatter::with(); + + ///////////// + // AFFIXES // + ///////////// + + AffixPatternProvider* affixProvider; + if (properties.currencyPluralInfo.fPtr.isNull()) { + warehouse.currencyPluralInfoAPP.setToBogus(); + warehouse.propertiesAPP.setTo(properties); + affixProvider = &warehouse.propertiesAPP; + } else { + warehouse.currencyPluralInfoAPP.setTo(*properties.currencyPluralInfo.fPtr); + warehouse.propertiesAPP.setToBogus(); + affixProvider = &warehouse.currencyPluralInfoAPP; + } + macros.affixProvider = affixProvider; + + /////////// + // UNITS // + /////////// + + bool useCurrency = ( + !properties.currency.isNull() || !properties.currencyPluralInfo.fPtr.isNull() || + !properties.currencyUsage.isNull() || affixProvider->hasCurrencySign()); + // TODO: CustomSymbolCurrency + CurrencyUnit currency = {u"USD", status}; + UCurrencyUsage currencyUsage = properties.currencyUsage.getOrDefault(UCURR_USAGE_STANDARD); + if (useCurrency) { + // NOTE: Slicing is OK. + macros.unit = currency; // NOLINT + } + + /////////////////////// + // ROUNDING STRATEGY // + /////////////////////// + + int32_t maxInt = properties.maximumIntegerDigits; + int32_t minInt = properties.minimumIntegerDigits; + int32_t maxFrac = properties.maximumFractionDigits; + int32_t minFrac = properties.minimumFractionDigits; + int32_t minSig = properties.minimumSignificantDigits; + int32_t maxSig = properties.maximumSignificantDigits; + double roundingIncrement = properties.roundingIncrement; + RoundingMode roundingMode = properties.roundingMode.getOrDefault(UNUM_ROUND_HALFEVEN); + bool explicitMinMaxFrac = minFrac != -1 || maxFrac != -1; + bool explicitMinMaxSig = minSig != -1 || maxSig != -1; + // Resolve min/max frac for currencies, required for the validation logic and for when minFrac or + // maxFrac was + // set (but not both) on a currency instance. + // NOTE: Increments are handled in "Rounder.constructCurrency()". + if (useCurrency && (minFrac == -1 || maxFrac == -1)) { + int32_t digits = ucurr_getDefaultFractionDigitsForUsage( + currency.getISOCurrency(), currencyUsage, &status); + if (minFrac == -1 && maxFrac == -1) { + minFrac = digits; + maxFrac = digits; + } else if (minFrac == -1) { + minFrac = std::min(maxFrac, digits); + } else /* if (maxFrac == -1) */ { + maxFrac = std::max(minFrac, digits); + } + } + // Validate min/max int/frac. + // For backwards compatibility, minimum overrides maximum if the two conflict. + // The following logic ensures that there is always a minimum of at least one digit. + if (minInt == 0 && maxFrac != 0) { + // Force a digit after the decimal point. + minFrac = minFrac <= 0 ? 1 : minFrac; + maxFrac = maxFrac < 0 ? -1 : maxFrac < minFrac ? minFrac : maxFrac; + minInt = 0; + maxInt = maxInt < 0 ? -1 : maxInt > kMaxIntFracSig ? -1 : maxInt; + } else { + // Force a digit before the decimal point. + minFrac = minFrac < 0 ? 0 : minFrac; + maxFrac = maxFrac < 0 ? -1 : maxFrac < minFrac ? minFrac : maxFrac; + minInt = minInt <= 0 ? 1 : minInt > kMaxIntFracSig ? 1 : minInt; + maxInt = maxInt < 0 ? -1 : maxInt < minInt ? minInt : maxInt > kMaxIntFracSig ? -1 : maxInt; + } + Rounder rounding; + if (!properties.currencyUsage.isNull()) { + rounding = Rounder::constructCurrency(currencyUsage).withCurrency(currency); + } else if (roundingIncrement != 0.0) { + rounding = Rounder::constructIncrement(roundingIncrement, minFrac); + } else if (explicitMinMaxSig) { + minSig = minSig < 1 ? 1 : minSig > kMaxIntFracSig ? kMaxIntFracSig : minSig; + maxSig = maxSig < 0 ? kMaxIntFracSig : maxSig < minSig ? minSig : maxSig > kMaxIntFracSig + ? kMaxIntFracSig : maxSig; + rounding = Rounder::constructSignificant(minSig, maxSig); + } else if (explicitMinMaxFrac) { + rounding = Rounder::constructFraction(minFrac, maxFrac); + } else if (useCurrency) { + rounding = Rounder::constructCurrency(currencyUsage); + } + if (!rounding.isBogus()) { + rounding = rounding.withMode(roundingMode); + macros.rounder = rounding; + } + + /////////////////// + // INTEGER WIDTH // + /////////////////// + + macros.integerWidth = IntegerWidth::zeroFillTo(minInt).truncateAt(maxInt); + + /////////////////////// + // GROUPING STRATEGY // + /////////////////////// + + macros.grouper = Grouper::forProperties(properties); + + ///////////// + // PADDING // + ///////////// + + if (properties.formatWidth != -1) { + macros.padder = Padder::forProperties(properties); + } + + /////////////////////////////// + // DECIMAL MARK ALWAYS SHOWN // + /////////////////////////////// + + macros.decimal = properties.decimalSeparatorAlwaysShown ? UNUM_DECIMAL_SEPARATOR_ALWAYS + : UNUM_DECIMAL_SEPARATOR_AUTO; + + /////////////////////// + // SIGN ALWAYS SHOWN // + /////////////////////// + + macros.sign = properties.signAlwaysShown ? UNUM_SIGN_ALWAYS : UNUM_SIGN_AUTO; + + ///////////////////////// + // SCIENTIFIC NOTATION // + ///////////////////////// + + if (properties.minimumExponentDigits != -1) { + // Scientific notation is required. + // This whole section feels like a hack, but it is needed for regression tests. + // The mapping from property bag to scientific notation is nontrivial due to LDML rules. + if (maxInt > 8) { + // But #13110: The maximum of 8 digits has unknown origins and is not in the spec. + // If maxInt is greater than 8, it is set to minInt, even if minInt is greater than 8. + maxInt = minInt; + macros.integerWidth = IntegerWidth::zeroFillTo(minInt).truncateAt(maxInt); + } else if (maxInt > minInt && minInt > 1) { + // Bug #13289: if maxInt > minInt > 1, then minInt should be 1. + minInt = 1; + macros.integerWidth = IntegerWidth::zeroFillTo(minInt).truncateAt(maxInt); + } + int engineering = maxInt < 0 ? -1 : maxInt; + macros.notation = ScientificNotation( + // Engineering interval: + static_cast(engineering), + // Enforce minimum integer digits (for patterns like "000.00E0"): + (engineering == minInt), + // Minimum exponent digits: + static_cast(properties.minimumExponentDigits), + // Exponent sign always shown: + properties.exponentSignAlwaysShown ? UNUM_SIGN_ALWAYS : UNUM_SIGN_AUTO); + // Scientific notation also involves overriding the rounding mode. + // TODO: Overriding here is a bit of a hack. Should this logic go earlier? + if (macros.rounder.fType == Rounder::RounderType::RND_FRACTION) { + // For the purposes of rounding, get the original min/max int/frac, since the local + // variables + // have been manipulated for display purposes. + int minInt_ = properties.minimumIntegerDigits; + int minFrac_ = properties.minimumFractionDigits; + int maxFrac_ = properties.maximumFractionDigits; + if (minInt_ == 0 && maxFrac_ == 0) { + // Patterns like "#E0" and "##E0", which mean no rounding! + macros.rounder = Rounder::unlimited().withMode(roundingMode); + } else if (minInt_ == 0 && minFrac_ == 0) { + // Patterns like "#.##E0" (no zeros in the mantissa), which mean round to maxFrac+1 + macros.rounder = Rounder::constructSignificant(1, maxFrac_ + 1).withMode(roundingMode); + } else { + // All other scientific patterns, which mean round to minInt+maxFrac + macros.rounder = Rounder::constructSignificant(minInt_ + minFrac_, minInt_ + maxFrac_) + .withMode(roundingMode); + } + } + } + + ////////////////////// + // COMPACT NOTATION // + ////////////////////// + + if (!properties.compactStyle.isNull()) { + if (properties.compactStyle.getNoError() == UNumberCompactStyle::UNUM_LONG) { + macros.notation = Notation::compactLong(); + } else { + macros.notation = Notation::compactShort(); + } + // Do not forward the affix provider. + macros.affixProvider = nullptr; + } + + ///////////////// + // MULTIPLIERS // + ///////////////// + + if (properties.magnitudeMultiplier != 0) { + macros.multiplier = MultiplierImpl::magnitude(properties.magnitudeMultiplier); + } else if (properties.multiplier != 1) { + macros.multiplier = MultiplierImpl::integer(properties.multiplier); + } + + ////////////////////// + // PROPERTY EXPORTS // + ////////////////////// + + if (exportedProperties != nullptr) { + + exportedProperties->roundingMode = roundingMode; + exportedProperties->minimumIntegerDigits = minInt; + exportedProperties->maximumIntegerDigits = maxInt == -1 ? INT32_MAX : maxInt; + + Rounder rounding_; + if (rounding.fType == Rounder::RounderType::RND_CURRENCY) { + rounding_ = rounding.withCurrency(currency, status); + } else { + rounding_ = rounding; + } + int minFrac_ = minFrac; + int maxFrac_ = maxFrac; + int minSig_ = minSig; + int maxSig_ = maxSig; + double increment_ = 0.0; + if (rounding_.fType == Rounder::RounderType::RND_FRACTION) { + minFrac_ = rounding_.fUnion.fracSig.fMinFrac; + maxFrac_ = rounding_.fUnion.fracSig.fMaxFrac; + } else if (rounding_.fType == Rounder::RounderType::RND_INCREMENT) { + increment_ = rounding_.fUnion.increment.fIncrement; + minFrac_ = rounding_.fUnion.increment.fMinFrac; + maxFrac_ = rounding_.fUnion.increment.fMinFrac; + } else if (rounding_.fType == Rounder::RounderType::RND_SIGNIFICANT) { + minSig_ = rounding_.fUnion.fracSig.fMinSig; + maxSig_ = rounding_.fUnion.fracSig.fMaxSig; + } + + exportedProperties->minimumFractionDigits = minFrac_; + exportedProperties->maximumFractionDigits = maxFrac_; + exportedProperties->minimumSignificantDigits = minSig_; + exportedProperties->maximumSignificantDigits = maxSig_; + exportedProperties->roundingIncrement = increment_; + } + + return macros; } +void PropertiesAffixPatternProvider::setTo(const DecimalFormatProperties& properties) { + // TODO +} + +bool PropertiesAffixPatternProvider::hasBody() const { + return true; +} + + +void CurrencyPluralInfoAffixProvider::setTo(const CurrencyPluralInfo& cpi) { + // TODO +} + +bool CurrencyPluralInfoAffixProvider::hasBody() const { + return affixesByPlural[StandardPlural::OTHER].hasBody(); +} + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/number_mapper.h b/icu4c/source/i18n/number_mapper.h index c3960a665b..842b183fd5 100644 --- a/icu4c/source/i18n/number_mapper.h +++ b/icu4c/source/i18n/number_mapper.h @@ -8,11 +8,96 @@ #define __NUMBER_MAPPER_H__ #include "number_types.h" +#include "unicode/currpinf.h" +#include "standardplural.h" +#include "number_patternstring.h" U_NAMESPACE_BEGIN namespace number { namespace impl { +class PropertiesAffixPatternProvider : public AffixPatternProvider, public UMemory { + public: + bool isBogus() const; + + void setTo(const DecimalFormatProperties& properties); + + void setToBogus(); + + // AffixPatternProvider Methods: + + char16_t charAt(int flags, int i) const U_OVERRIDE; + + int length(int flags) const U_OVERRIDE; + + UnicodeString getString(int flags) const U_OVERRIDE; + + bool hasCurrencySign() const U_OVERRIDE; + + bool positiveHasPlusSign() const U_OVERRIDE; + + bool hasNegativeSubpattern() const U_OVERRIDE; + + bool negativeHasMinusSign() const U_OVERRIDE; + + bool containsSymbolType(AffixPatternType, UErrorCode&) const U_OVERRIDE; + + virtual bool hasBody() const U_OVERRIDE; + + private: + UnicodeString posPrefix; + UnicodeString posSuffix; + UnicodeString negPrefix; + UnicodeString negSuffix; + + bool fBogus{true}; +}; + + +class CurrencyPluralInfoAffixProvider : public AffixPatternProvider, public UMemory { + public: + bool isBogus() const; + + void setTo(const CurrencyPluralInfo& cpi); + + void setToBogus(); + + // AffixPatternProvider Methods: + + char16_t charAt(int flags, int i) const U_OVERRIDE; + + int length(int flags) const U_OVERRIDE; + + UnicodeString getString(int flags) const U_OVERRIDE; + + bool hasCurrencySign() const U_OVERRIDE; + + bool positiveHasPlusSign() const U_OVERRIDE; + + bool hasNegativeSubpattern() const U_OVERRIDE; + + bool negativeHasMinusSign() const U_OVERRIDE; + + bool containsSymbolType(AffixPatternType, UErrorCode&) const U_OVERRIDE; + + virtual bool hasBody() const U_OVERRIDE; + + private: + ParsedPatternInfo affixesByPlural[StandardPlural::COUNT]; + + bool fBogus{true}; +}; + + +/** + * A struct for ownership of a few objects needed for formatting. + */ +struct DecimalFormatWarehouse { + PropertiesAffixPatternProvider propertiesAPP; + CurrencyPluralInfoAffixProvider currencyPluralInfoAPP; +}; + + /** * Utilities for converting between a DecimalFormatProperties and a MacroProps. */ @@ -20,21 +105,16 @@ class NumberPropertyMapper { public: /** Convenience method to create a NumberFormatter directly from Properties. */ static UnlocalizedNumberFormatter create(const DecimalFormatProperties& properties, - const DecimalFormatSymbols& symbols, UErrorCode& status); + const DecimalFormatSymbols& symbols, + DecimalFormatWarehouse& warehouse, UErrorCode& status); /** Convenience method to create a NumberFormatter directly from Properties. */ static UnlocalizedNumberFormatter create(const DecimalFormatProperties& properties, const DecimalFormatSymbols& symbols, + DecimalFormatWarehouse& warehouse, DecimalFormatProperties& exportedProperties, UErrorCode& status); - /** - * Convenience method to create a NumberFormatter directly from a pattern string. Something like this - * could become public API if there is demand. - */ - static UnlocalizedNumberFormatter create(const UnicodeString& pattern, - const DecimalFormatSymbols& symbols, UErrorCode& status); - /** * Creates a new {@link MacroProps} object based on the content of a {@link DecimalFormatProperties} * object. In other words, maps Properties to MacroProps. This function is used by the @@ -49,9 +129,9 @@ class NumberPropertyMapper { * getters. * @return A new MacroProps containing all of the information in the Properties. */ - static void oldToNew(const DecimalFormatProperties& properties, const DecimalFormatSymbols& symbols, - DecimalFormatProperties& exportedProperties, MacroProps& output, - UErrorCode& status); + static MacroProps oldToNew(const DecimalFormatProperties& properties, + const DecimalFormatSymbols& symbols, DecimalFormatWarehouse& warehouse, + DecimalFormatProperties* exportedProperties, UErrorCode& status); }; diff --git a/icu4c/source/i18n/number_notation.cpp b/icu4c/source/i18n/number_notation.cpp index f4ad333354..da61433b1c 100644 --- a/icu4c/source/i18n/number_notation.cpp +++ b/icu4c/source/i18n/number_notation.cpp @@ -36,6 +36,19 @@ ScientificNotation Notation::engineering() { return {NTN_SCIENTIFIC, union_}; } +ScientificNotation::ScientificNotation(int8_t fEngineeringInterval, bool fRequireMinInt, + impl::digits_t fMinExponentDigits, + UNumberSignDisplay fExponentSignDisplay) { + ScientificSettings settings; + settings.fEngineeringInterval = fEngineeringInterval; + settings.fRequireMinInt = fRequireMinInt; + settings.fMinExponentDigits = fMinExponentDigits; + settings.fExponentSignDisplay = fExponentSignDisplay; + NotationUnion union_; + union_.scientific = settings; + *this = {NTN_SCIENTIFIC, union_}; +} + Notation Notation::compactShort() { NotationUnion union_; union_.compactStyle = CompactStyle::UNUM_SHORT; diff --git a/icu4c/source/i18n/number_padding.cpp b/icu4c/source/i18n/number_padding.cpp index b1db3490cd..3c470df504 100644 --- a/icu4c/source/i18n/number_padding.cpp +++ b/icu4c/source/i18n/number_padding.cpp @@ -8,6 +8,7 @@ #include "unicode/numberformatter.h" #include "number_types.h" #include "number_stringbuilder.h" +#include "number_decimfmtprops.h" using namespace icu; using namespace icu::number; @@ -15,6 +16,8 @@ using namespace icu::number::impl; namespace { +static UChar32 kFallbackPadChar = 0x0020; + int32_t addPaddingHelper(UChar32 paddingCp, int32_t requiredPadding, NumberStringBuilder &string, int32_t index, UErrorCode &status) { @@ -47,6 +50,16 @@ Padder Padder::codePoints(UChar32 cp, int32_t targetWidth, UNumberFormatPadPosit } } +Padder Padder::forProperties(const DecimalFormatProperties& properties) { + UChar32 padCp; + if (properties.padString.length() > 0) { + padCp = properties.padString.char32At(0); + } else { + padCp = kFallbackPadChar; + } + return {padCp, properties.formatWidth, properties.padPosition.getOrDefault(UNUM_PAD_BEFORE_PREFIX)}; +} + int32_t Padder::padAndApply(const Modifier &mod1, const Modifier &mod2, NumberStringBuilder &string, int32_t leftIndex, int32_t rightIndex, UErrorCode &status) const { diff --git a/icu4c/source/i18n/number_patternstring.cpp b/icu4c/source/i18n/number_patternstring.cpp index 7bdd6fb71d..7ec0297c6e 100644 --- a/icu4c/source/i18n/number_patternstring.cpp +++ b/icu4c/source/i18n/number_patternstring.cpp @@ -32,6 +32,11 @@ PatternParser::parseToProperties(const UnicodeString& pattern, IgnoreRounding ig return properties; } +DecimalFormatProperties PatternParser::parseToProperties(const UnicodeString& pattern, + UErrorCode& status) { + return parseToProperties(pattern, IGNORE_ROUNDING_NEVER, status); +} + void PatternParser::parseToExistingProperties(const UnicodeString& pattern, DecimalFormatProperties& properties, IgnoreRounding ignoreRounding, UErrorCode& status) { diff --git a/icu4c/source/i18n/number_patternstring.h b/icu4c/source/i18n/number_patternstring.h index d0e62ef21f..742ee3547c 100644 --- a/icu4c/source/i18n/number_patternstring.h +++ b/icu4c/source/i18n/number_patternstring.h @@ -177,6 +177,8 @@ class U_I18N_API PatternParser { static DecimalFormatProperties parseToProperties(const UnicodeString& pattern, IgnoreRounding ignoreRounding, UErrorCode& status); + static DecimalFormatProperties parseToProperties(const UnicodeString& pattern, UErrorCode& status); + /** * Parses a pattern string into an existing property bag. All properties that can be encoded into a pattern string * will be overwritten with either their default value or with the value coming from the pattern string. Properties diff --git a/icu4c/source/i18n/number_types.h b/icu4c/source/i18n/number_types.h index 35a94bcded..ac0188f498 100644 --- a/icu4c/source/i18n/number_types.h +++ b/icu4c/source/i18n/number_types.h @@ -305,6 +305,10 @@ class U_I18N_API NullableValue { return fValue; } + T getOrDefault(T defaultValue) const { + return fNull ? defaultValue : fValue; + } + private: bool fNull; T fValue; diff --git a/icu4c/source/i18n/unicode/compactdecimalformat.h b/icu4c/source/i18n/unicode/compactdecimalformat.h index f5911176c8..e5d27de05a 100644 --- a/icu4c/source/i18n/unicode/compactdecimalformat.h +++ b/icu4c/source/i18n/unicode/compactdecimalformat.h @@ -112,7 +112,7 @@ public: * other classes have different class IDs. * @stable ICU 51 */ - virtual UClassID getDynamicClassID() const; + virtual UClassID getDynamicClassID() const U_OVERRIDE; }; U_NAMESPACE_END diff --git a/icu4c/source/i18n/unicode/decimfmt.h b/icu4c/source/i18n/unicode/decimfmt.h index 443aa1a532..88d50c7a89 100644 --- a/icu4c/source/i18n/unicode/decimfmt.h +++ b/icu4c/source/i18n/unicode/decimfmt.h @@ -42,7 +42,6 @@ #include "unicode/stringpiece.h" #include "unicode/curramt.h" #include "unicode/enumset.h" -#include "unicode/numberformatter.h" #ifndef U_HIDE_INTERNAL_API /** @@ -68,9 +67,11 @@ class PluralRules; class VisibleDigitsWithExponent; namespace number { +class LocalizedNumberFormatter; namespace impl { class DecimalQuantity; struct DecimalFormatProperties; +struct DecimalFormatWarehouse; } } @@ -1948,7 +1949,7 @@ class U_I18N_API DecimalFormat : public NumberFormat { * @return An instance of LocalizedNumberFormatter with the same behavior as this DecimalFormat. * @draft ICU 62 */ - number::LocalizedNumberFormatter toNumberFormatter() const; + const number::LocalizedNumberFormatter& toNumberFormatter() const; /** * Return the class ID for this class. This is useful only for @@ -2023,6 +2024,9 @@ class U_I18N_API DecimalFormat : public NumberFormat { */ LocalPointer fExportedProperties; + /** A field for a few additional helper object that need ownership. */ + LocalPointer fWarehouse; + LocalPointer fParser; LocalPointer fParserWithCurrency; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Padder.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Padder.java index efb4cec095..97a310900d 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Padder.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Padder.java @@ -66,6 +66,12 @@ public class Padder { } } + public static Padder forProperties(DecimalFormatProperties properties) { + return new Padder(properties.getPadString(), + properties.getFormatWidth(), + properties.getPadPosition()); + } + public boolean isValid() { return targetWidth > 0; } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberPropertyMapper.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberPropertyMapper.java index 168b43989c..0d33059eda 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberPropertyMapper.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberPropertyMapper.java @@ -54,6 +54,8 @@ final class NumberPropertyMapper { /** * Convenience method to create a NumberFormatter directly from a pattern string. Something like this * could become public API if there is demand. + * + * NOTE: This appears to be dead code. */ public static UnlocalizedNumberFormatter create(String pattern, DecimalFormatSymbols symbols) { DecimalFormatProperties properties = PatternStringParser.parseToProperties(pattern); @@ -159,13 +161,13 @@ final class NumberPropertyMapper { if (minInt == 0 && maxFrac != 0) { // Force a digit after the decimal point. minFrac = minFrac <= 0 ? 1 : minFrac; - maxFrac = maxFrac < 0 ? Integer.MAX_VALUE : maxFrac < minFrac ? minFrac : maxFrac; + maxFrac = maxFrac < 0 ? -1 : maxFrac < minFrac ? minFrac : maxFrac; minInt = 0; maxInt = maxInt < 0 ? -1 : maxInt > RoundingUtils.MAX_INT_FRAC_SIG ? -1 : maxInt; } else { // Force a digit before the decimal point. minFrac = minFrac < 0 ? 0 : minFrac; - maxFrac = maxFrac < 0 ? Integer.MAX_VALUE : maxFrac < minFrac ? minFrac : maxFrac; + maxFrac = maxFrac < 0 ? -1 : maxFrac < minFrac ? minFrac : maxFrac; minInt = minInt <= 0 ? 1 : minInt > RoundingUtils.MAX_INT_FRAC_SIG ? 1 : minInt; maxInt = maxInt < 0 ? -1 : maxInt < minInt ? minInt : maxInt > RoundingUtils.MAX_INT_FRAC_SIG ? -1 : maxInt; @@ -210,9 +212,7 @@ final class NumberPropertyMapper { ///////////// if (properties.getFormatWidth() != -1) { - macros.padder = new Padder(properties.getPadString(), - properties.getFormatWidth(), - properties.getPadPosition()); + macros.padder = Padder.forProperties(properties); } /////////////////////////////// From 00a23a07f783bcf5184a3589a4a8628582d14ced Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Thu, 15 Mar 2018 10:08:26 +0000 Subject: [PATCH 040/129] ICU-13634 The property mapper appears to be basically functional; data passes from the old API through the mapper into the new API and then back out through the old API again. X-SVN-Rev: 41108 --- icu4c/source/i18n/Makefile.in | 2 +- icu4c/source/i18n/decimfmt.cpp | 23 +-- icu4c/source/i18n/number_formatimpl.cpp | 10 +- icu4c/source/i18n/number_mapper.cpp | 181 ++++++++++++++++++++++-- icu4c/source/i18n/number_mapper.h | 38 +++-- icu4c/source/i18n/number_multiplier.cpp | 47 ++++++ icu4c/source/i18n/number_multiplier.h | 34 +++++ icu4c/source/i18n/number_utils.h | 2 + 8 files changed, 302 insertions(+), 35 deletions(-) create mode 100644 icu4c/source/i18n/number_multiplier.cpp create mode 100644 icu4c/source/i18n/number_multiplier.h diff --git a/icu4c/source/i18n/Makefile.in b/icu4c/source/i18n/Makefile.in index a1d186b435..326ce2170f 100644 --- a/icu4c/source/i18n/Makefile.in +++ b/icu4c/source/i18n/Makefile.in @@ -108,7 +108,7 @@ double-conversion-cached-powers.o double-conversion-diy-fp.o double-conversion-f numparse_stringsegment.o numparse_unisets.o numparse_parsednumber.o \ numparse_impl.o numparse_symbols.o numparse_decimal.o numparse_scientific.o \ numparse_currency.o numparse_affixes.o numparse_compositions.o \ -number_mapper.o +number_mapper.o number_multiplier.o ## Header files to install diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp index 7772f330d6..a601269049 100644 --- a/icu4c/source/i18n/decimfmt.cpp +++ b/icu4c/source/i18n/decimfmt.cpp @@ -64,6 +64,7 @@ DecimalFormat::DecimalFormat(const UnicodeString& pattern, DecimalFormatSymbols* DecimalFormat::DecimalFormat(const DecimalFormatSymbols* symbolsToAdopt, UErrorCode& status) { fProperties.adoptInsteadAndCheckErrorCode(new DecimalFormatProperties(), status); fExportedProperties.adoptInsteadAndCheckErrorCode(new DecimalFormatProperties(), status); + fWarehouse.adoptInsteadAndCheckErrorCode(new DecimalFormatWarehouse(), status); if (symbolsToAdopt == nullptr) { fSymbols.adoptInsteadAndCheckErrorCode(new DecimalFormatSymbols(status), status); } else { @@ -313,8 +314,10 @@ DecimalFormat::DecimalFormat(const UnicodeString& pattern, const DecimalFormatSy DecimalFormat::DecimalFormat(const DecimalFormat& source) { fProperties.adoptInstead(new DecimalFormatProperties()); fExportedProperties.adoptInstead(new DecimalFormatProperties()); + fWarehouse.adoptInstead(new DecimalFormatWarehouse()); fSymbols.adoptInstead(new DecimalFormatSymbols(*source.fSymbols)); - if (fProperties == nullptr || fExportedProperties == nullptr || fSymbols == nullptr) { + if (fProperties == nullptr || fExportedProperties == nullptr || fWarehouse == nullptr || + fSymbols == nullptr) { return; } refreshFormatterNoError(); @@ -862,7 +865,7 @@ void DecimalFormat::formatToDecimalQuantity(const Formattable& number, DecimalQu status = U_UNSUPPORTED_ERROR; } -number::LocalizedNumberFormatter DecimalFormat::toNumberFormatter() const { +const number::LocalizedNumberFormatter& DecimalFormat::toNumberFormatter() const { return *fFormatter; } @@ -897,14 +900,16 @@ void DecimalFormat::refreshFormatter(UErrorCode& status) { fFormatter.adoptInsteadAndCheckErrorCode( new LocalizedNumberFormatter( NumberPropertyMapper::create( - *fProperties, *fSymbols, *fExportedProperties, status).locale( + *fProperties, *fSymbols, *fWarehouse, *fExportedProperties, status).locale( locale)), status); - fParser.adoptInsteadAndCheckErrorCode( - NumberParserImpl::createParserFromProperties( - *fProperties, *fSymbols, false, false, status), status); - fParserWithCurrency.adoptInsteadAndCheckErrorCode( - NumberParserImpl::createParserFromProperties( - *fProperties, *fSymbols, true, false, status), status); + + // fParser.adoptInsteadAndCheckErrorCode( + // NumberParserImpl::createParserFromProperties( + // *fProperties, *fSymbols, false, false, status), status); + + // fParserWithCurrency.adoptInsteadAndCheckErrorCode( + // NumberParserImpl::createParserFromProperties( + // *fProperties, *fSymbols, true, false, status), status); } void DecimalFormat::refreshFormatterNoError() { diff --git a/icu4c/source/i18n/number_formatimpl.cpp b/icu4c/source/i18n/number_formatimpl.cpp index 795c3d1348..04344b368f 100644 --- a/icu4c/source/i18n/number_formatimpl.cpp +++ b/icu4c/source/i18n/number_formatimpl.cpp @@ -276,6 +276,12 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe, /// START POPULATING THE DEFAULT MICROPROPS AND BUILDING THE MICROPROPS GENERATOR /// ///////////////////////////////////////////////////////////////////////////////////// + // Multiplier (compatibility mode value). + if (macros.multiplier.isValid()) { + fMicros.helpers.multiplier.setAndChain(macros.multiplier, chain); + chain = &fMicros.helpers.multiplier; + } + // Rounding strategy if (!macros.rounder.isBogus()) { fMicros.rounding = macros.rounder; @@ -342,7 +348,9 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe, // Middle modifier (patterns, positive/negative, currency symbols, percent) auto patternModifier = new MutablePatternModifier(false); fPatternModifier.adoptInstead(patternModifier); - patternModifier->setPatternInfo(fPatternInfo.getAlias()); + patternModifier->setPatternInfo( + macros.affixProvider != nullptr ? macros.affixProvider + : static_cast(fPatternInfo.getAlias())); patternModifier->setPatternAttributes(fMicros.sign, isPermille); if (patternModifier->needsPlurals()) { patternModifier->setSymbols( diff --git a/icu4c/source/i18n/number_mapper.cpp b/icu4c/source/i18n/number_mapper.cpp index 949f88aadd..0f5567c442 100644 --- a/icu4c/source/i18n/number_mapper.cpp +++ b/icu4c/source/i18n/number_mapper.cpp @@ -11,6 +11,8 @@ #include "number_mapper.h" #include "number_patternstring.h" +#include "unicode/errorcode.h" +#include "number_utils.h" using namespace icu; using namespace icu::number; @@ -61,10 +63,10 @@ MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& propert AffixPatternProvider* affixProvider; if (properties.currencyPluralInfo.fPtr.isNull()) { warehouse.currencyPluralInfoAPP.setToBogus(); - warehouse.propertiesAPP.setTo(properties); + warehouse.propertiesAPP.setTo(properties, status); affixProvider = &warehouse.propertiesAPP; } else { - warehouse.currencyPluralInfoAPP.setTo(*properties.currencyPluralInfo.fPtr); + warehouse.currencyPluralInfoAPP.setTo(*properties.currencyPluralInfo.fPtr, status); warehouse.propertiesAPP.setToBogus(); affixProvider = &warehouse.currencyPluralInfoAPP; } @@ -229,8 +231,8 @@ MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& propert macros.rounder = Rounder::constructSignificant(1, maxFrac_ + 1).withMode(roundingMode); } else { // All other scientific patterns, which mean round to minInt+maxFrac - macros.rounder = Rounder::constructSignificant(minInt_ + minFrac_, minInt_ + maxFrac_) - .withMode(roundingMode); + macros.rounder = Rounder::constructSignificant( + minInt_ + minFrac_, minInt_ + maxFrac_).withMode(roundingMode); } } } @@ -254,9 +256,9 @@ MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& propert ///////////////// if (properties.magnitudeMultiplier != 0) { - macros.multiplier = MultiplierImpl::magnitude(properties.magnitudeMultiplier); + macros.multiplier = Multiplier::magnitude(properties.magnitudeMultiplier); } else if (properties.multiplier != 1) { - macros.multiplier = MultiplierImpl::integer(properties.multiplier); + macros.multiplier = Multiplier::integer(properties.multiplier); } ////////////////////// @@ -303,8 +305,127 @@ MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& propert } -void PropertiesAffixPatternProvider::setTo(const DecimalFormatProperties& properties) { - // TODO +void PropertiesAffixPatternProvider::setTo(const DecimalFormatProperties& properties, UErrorCode&) { + // There are two ways to set affixes in DecimalFormat: via the pattern string (applyPattern), and via the + // explicit setters (setPositivePrefix and friends). The way to resolve the settings is as follows: + // + // 1) If the explicit setting is present for the field, use it. + // 2) Otherwise, follows UTS 35 rules based on the pattern string. + // + // Importantly, the explicit setters affect only the one field they override. If you set the positive + // prefix, that should not affect the negative prefix. Since it is impossible for the user of this class + // to know whether the origin for a string was the override or the pattern, we have to say that we always + // have a negative subpattern and perform all resolution logic here. + + // Convenience: Extract the properties into local variables. + // Variables are named with three chars: [p/n][p/s][o/p] + // [p/n] => p for positive, n for negative + // [p/s] => p for prefix, s for suffix + // [o/p] => o for escaped custom override string, p for pattern string + UnicodeString ppo = AffixUtils::escape(UnicodeStringCharSequence(properties.positivePrefix)); + UnicodeString pso = AffixUtils::escape(UnicodeStringCharSequence(properties.positiveSuffix)); + UnicodeString npo = AffixUtils::escape(UnicodeStringCharSequence(properties.negativePrefix)); + UnicodeString nso = AffixUtils::escape(UnicodeStringCharSequence(properties.negativeSuffix)); + const UnicodeString& ppp = properties.positivePrefixPattern; + const UnicodeString& psp = properties.positiveSuffixPattern; + const UnicodeString& npp = properties.negativePrefixPattern; + const UnicodeString& nsp = properties.negativeSuffixPattern; + + if (!properties.positivePrefix.isBogus()) { + posPrefix = ppo; + } else if (!ppp.isBogus()) { + posPrefix = ppp; + } else { + // UTS 35: Default positive prefix is empty string. + posPrefix = u""; + } + + if (!properties.positiveSuffix.isBogus()) { + posSuffix = pso; + } else if (!psp.isBogus()) { + posSuffix = psp; + } else { + // UTS 35: Default positive suffix is empty string. + posSuffix = u""; + } + + if (!properties.negativePrefix.isBogus()) { + negPrefix = npo; + } else if (!npp.isBogus()) { + negPrefix = npp; + } else { + // UTS 35: Default negative prefix is "-" with positive prefix. + // Important: We prepend the "-" to the pattern, not the override! + negPrefix = ppp.isBogus() ? u"-" : u"-" + ppp; + } + + if (!properties.negativeSuffix.isBogus()) { + negSuffix = nso; + } else if (!nsp.isBogus()) { + negSuffix = nsp; + } else { + // UTS 35: Default negative prefix is the positive prefix. + negSuffix = psp.isBogus() ? u"" : psp; + } +} + +char16_t PropertiesAffixPatternProvider::charAt(int flags, int i) const { + return getStringInternal(flags).charAt(i); +} + +int PropertiesAffixPatternProvider::length(int flags) const { + return getStringInternal(flags).length(); +} + +UnicodeString PropertiesAffixPatternProvider::getString(int32_t flags) const { + return getStringInternal(flags); +} + +const UnicodeString& PropertiesAffixPatternProvider::getStringInternal(int32_t flags) const { + bool prefix = (flags & AFFIX_PREFIX) != 0; + bool negative = (flags & AFFIX_NEGATIVE_SUBPATTERN) != 0; + if (prefix && negative) { + return negPrefix; + } else if (prefix) { + return posPrefix; + } else if (negative) { + return negSuffix; + } else { + return posSuffix; + } +} + +bool PropertiesAffixPatternProvider::positiveHasPlusSign() const { + // TODO: Change the internal APIs to propagate out the error? + ErrorCode localStatus; + return AffixUtils::containsType(UnicodeStringCharSequence(posPrefix), TYPE_PLUS_SIGN, localStatus) || + AffixUtils::containsType(UnicodeStringCharSequence(posSuffix), TYPE_PLUS_SIGN, localStatus); +} + +bool PropertiesAffixPatternProvider::hasNegativeSubpattern() const { + // See comments in the constructor for more information on why this is always true. + return true; +} + +bool PropertiesAffixPatternProvider::negativeHasMinusSign() const { + ErrorCode localStatus; + return AffixUtils::containsType(UnicodeStringCharSequence(negPrefix), TYPE_MINUS_SIGN, localStatus) || + AffixUtils::containsType(UnicodeStringCharSequence(negSuffix), TYPE_MINUS_SIGN, localStatus); +} + +bool PropertiesAffixPatternProvider::hasCurrencySign() const { + ErrorCode localStatus; + return AffixUtils::hasCurrencySymbols(UnicodeStringCharSequence(posPrefix), localStatus) || + AffixUtils::hasCurrencySymbols(UnicodeStringCharSequence(posSuffix), localStatus) || + AffixUtils::hasCurrencySymbols(UnicodeStringCharSequence(negPrefix), localStatus) || + AffixUtils::hasCurrencySymbols(UnicodeStringCharSequence(negSuffix), localStatus); +} + +bool PropertiesAffixPatternProvider::containsSymbolType(AffixPatternType type, UErrorCode& status) const { + return AffixUtils::containsType(UnicodeStringCharSequence(posPrefix), type, status) || + AffixUtils::containsType(UnicodeStringCharSequence(posSuffix), type, status) || + AffixUtils::containsType(UnicodeStringCharSequence(negPrefix), type, status) || + AffixUtils::containsType(UnicodeStringCharSequence(negSuffix), type, status); } bool PropertiesAffixPatternProvider::hasBody() const { @@ -312,8 +433,48 @@ bool PropertiesAffixPatternProvider::hasBody() const { } -void CurrencyPluralInfoAffixProvider::setTo(const CurrencyPluralInfo& cpi) { - // TODO +void CurrencyPluralInfoAffixProvider::setTo(const CurrencyPluralInfo& cpi, UErrorCode& status) { + for (int32_t plural = 0; plural < StandardPlural::COUNT; plural++) { + const char* keyword = StandardPlural::getKeyword(static_cast(plural)); + UnicodeString patternString; + patternString = cpi.getCurrencyPluralPattern(keyword, patternString); + PatternParser::parseToPatternInfo(patternString, affixesByPlural[plural], status); + } +} + +char16_t CurrencyPluralInfoAffixProvider::charAt(int32_t flags, int32_t i) const { + int32_t pluralOrdinal = (flags & AFFIX_PLURAL_MASK); + return affixesByPlural[pluralOrdinal].charAt(flags, i); +} + +int32_t CurrencyPluralInfoAffixProvider::length(int32_t flags) const { + int32_t pluralOrdinal = (flags & AFFIX_PLURAL_MASK); + return affixesByPlural[pluralOrdinal].length(flags); +} + +UnicodeString CurrencyPluralInfoAffixProvider::getString(int32_t flags) const { + int32_t pluralOrdinal = (flags & AFFIX_PLURAL_MASK); + return affixesByPlural[pluralOrdinal].getString(flags); +} + +bool CurrencyPluralInfoAffixProvider::positiveHasPlusSign() const { + return affixesByPlural[StandardPlural::OTHER].positiveHasPlusSign(); +} + +bool CurrencyPluralInfoAffixProvider::hasNegativeSubpattern() const { + return affixesByPlural[StandardPlural::OTHER].hasNegativeSubpattern(); +} + +bool CurrencyPluralInfoAffixProvider::negativeHasMinusSign() const { + return affixesByPlural[StandardPlural::OTHER].negativeHasMinusSign(); +} + +bool CurrencyPluralInfoAffixProvider::hasCurrencySign() const { + return affixesByPlural[StandardPlural::OTHER].hasCurrencySign(); +} + +bool CurrencyPluralInfoAffixProvider::containsSymbolType(AffixPatternType type, UErrorCode& status) const { + return affixesByPlural[StandardPlural::OTHER].containsSymbolType(type, status); } bool CurrencyPluralInfoAffixProvider::hasBody() const { diff --git a/icu4c/source/i18n/number_mapper.h b/icu4c/source/i18n/number_mapper.h index 842b183fd5..d9af795499 100644 --- a/icu4c/source/i18n/number_mapper.h +++ b/icu4c/source/i18n/number_mapper.h @@ -18,19 +18,23 @@ namespace impl { class PropertiesAffixPatternProvider : public AffixPatternProvider, public UMemory { public: - bool isBogus() const; + bool isBogus() const { + return fBogus; + } - void setTo(const DecimalFormatProperties& properties); + void setToBogus() { + fBogus = true; + } - void setToBogus(); + void setTo(const DecimalFormatProperties& properties, UErrorCode& status); // AffixPatternProvider Methods: - char16_t charAt(int flags, int i) const U_OVERRIDE; + char16_t charAt(int32_t flags, int32_t i) const U_OVERRIDE; - int length(int flags) const U_OVERRIDE; + int32_t length(int32_t flags) const U_OVERRIDE; - UnicodeString getString(int flags) const U_OVERRIDE; + UnicodeString getString(int32_t flags) const U_OVERRIDE; bool hasCurrencySign() const U_OVERRIDE; @@ -42,7 +46,7 @@ class PropertiesAffixPatternProvider : public AffixPatternProvider, public UMemo bool containsSymbolType(AffixPatternType, UErrorCode&) const U_OVERRIDE; - virtual bool hasBody() const U_OVERRIDE; + bool hasBody() const U_OVERRIDE; private: UnicodeString posPrefix; @@ -50,25 +54,31 @@ class PropertiesAffixPatternProvider : public AffixPatternProvider, public UMemo UnicodeString negPrefix; UnicodeString negSuffix; + const UnicodeString& getStringInternal(int32_t flags) const; + bool fBogus{true}; }; class CurrencyPluralInfoAffixProvider : public AffixPatternProvider, public UMemory { public: - bool isBogus() const; + bool isBogus() const { + return fBogus; + } - void setTo(const CurrencyPluralInfo& cpi); + void setToBogus() { + fBogus = true; + } - void setToBogus(); + void setTo(const CurrencyPluralInfo& cpi, UErrorCode& status); // AffixPatternProvider Methods: - char16_t charAt(int flags, int i) const U_OVERRIDE; + char16_t charAt(int32_t flags, int32_t i) const U_OVERRIDE; - int length(int flags) const U_OVERRIDE; + int32_t length(int32_t flags) const U_OVERRIDE; - UnicodeString getString(int flags) const U_OVERRIDE; + UnicodeString getString(int32_t flags) const U_OVERRIDE; bool hasCurrencySign() const U_OVERRIDE; @@ -80,7 +90,7 @@ class CurrencyPluralInfoAffixProvider : public AffixPatternProvider, public UMem bool containsSymbolType(AffixPatternType, UErrorCode&) const U_OVERRIDE; - virtual bool hasBody() const U_OVERRIDE; + bool hasBody() const U_OVERRIDE; private: ParsedPatternInfo affixesByPlural[StandardPlural::COUNT]; diff --git a/icu4c/source/i18n/number_multiplier.cpp b/icu4c/source/i18n/number_multiplier.cpp new file mode 100644 index 0000000000..ca445ba4dd --- /dev/null +++ b/icu4c/source/i18n/number_multiplier.cpp @@ -0,0 +1,47 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT + +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + +#include "number_types.h" +#include "number_multiplier.h" + +using namespace icu; +using namespace icu::number; +using namespace icu::number::impl; + + +Multiplier::Multiplier(int32_t magnitudeMultiplier, int32_t multiplier) + : magnitudeMultiplier(magnitudeMultiplier), multiplier(multiplier) {} + +Multiplier Multiplier::magnitude(int32_t magnitudeMultiplier) { + return {magnitudeMultiplier, 1}; +} + +Multiplier Multiplier::integer(int32_t multiplier) { + return {0, multiplier}; +} + + +void MultiplierChain::setAndChain(const Multiplier& multiplier, const MicroPropsGenerator* parent) { + this->multiplier = multiplier; + this->parent = parent; +} + +void +MultiplierChain::processQuantity(DecimalQuantity& quantity, MicroProps& micros, UErrorCode& status) const { + parent->processQuantity(quantity, micros, status); + quantity.adjustMagnitude(multiplier.magnitudeMultiplier); + if (multiplier.multiplier != 1) { + quantity.multiplyBy(multiplier.multiplier); + } +} + + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/number_multiplier.h b/icu4c/source/i18n/number_multiplier.h new file mode 100644 index 0000000000..93d103dd84 --- /dev/null +++ b/icu4c/source/i18n/number_multiplier.h @@ -0,0 +1,34 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#ifndef __SOURCE_NUMBER_MULTIPLIER_H__ +#define __SOURCE_NUMBER_MULTIPLIER_H__ + +#include "numparse_types.h" + +U_NAMESPACE_BEGIN namespace number { +namespace impl { + + +class MultiplierChain : public MicroPropsGenerator, public UMemory { + public: + void setAndChain(const Multiplier& other, const MicroPropsGenerator* parent); + + void processQuantity(DecimalQuantity& quantity, MicroProps& micros, + UErrorCode& status) const U_OVERRIDE; + + private: + Multiplier multiplier; + const MicroPropsGenerator *parent; +}; + + +} // namespace impl +} // namespace number +U_NAMESPACE_END + +#endif //__SOURCE_NUMBER_MULTIPLIER_H__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/number_utils.h b/icu4c/source/i18n/number_utils.h index a889c69eb7..1e5494dc81 100644 --- a/icu4c/source/i18n/number_utils.h +++ b/icu4c/source/i18n/number_utils.h @@ -13,6 +13,7 @@ #include "number_scientific.h" #include "number_patternstring.h" #include "number_modifiers.h" +#include "number_multiplier.h" U_NAMESPACE_BEGIN namespace number { namespace impl { @@ -73,6 +74,7 @@ struct MicroProps : public MicroPropsGenerator { ScientificModifier scientificModifier; EmptyModifier emptyWeakModifier{false}; EmptyModifier emptyStrongModifier{true}; + MultiplierChain multiplier; } helpers; From 9828c560141eedf784c3d9b14969d69579f4c6b1 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Fri, 16 Mar 2018 09:20:43 +0000 Subject: [PATCH 041/129] ICU-13634 Fixing remaining build warnings. In principle, formatting should work fully. Not yet tested. X-SVN-Rev: 41109 --- icu4c/source/i18n/compactdecimalformat.cpp | 27 +++++++++--- icu4c/source/i18n/decimfmt.cpp | 42 ++++++++++++------- icu4c/source/i18n/numparse_impl.cpp | 15 +++---- .../i18n/unicode/compactdecimalformat.h | 5 ++- icu4c/source/i18n/unicode/decimfmt.h | 9 ++-- 5 files changed, 65 insertions(+), 33 deletions(-) diff --git a/icu4c/source/i18n/compactdecimalformat.cpp b/icu4c/source/i18n/compactdecimalformat.cpp index b2e9fc5fe2..18dacb3e32 100644 --- a/icu4c/source/i18n/compactdecimalformat.cpp +++ b/icu4c/source/i18n/compactdecimalformat.cpp @@ -10,23 +10,38 @@ #define UNISTR_FROM_STRING_EXPLICIT #include "unicode/compactdecimalformat.h" +#include "number_decimfmtprops.h" using namespace icu; +UOBJECT_DEFINE_RTTI_IMPLEMENTATION(CompactDecimalFormat) + + CompactDecimalFormat* CompactDecimalFormat::createInstance(const Locale& inLocale, UNumberCompactStyle style, - UErrorCode& status) {} + UErrorCode& status) { + return new CompactDecimalFormat(inLocale, style, status); +} + +CompactDecimalFormat::CompactDecimalFormat(const Locale& inLocale, UNumberCompactStyle style, + UErrorCode& status) + : DecimalFormat(new DecimalFormatSymbols(inLocale, status), status) { + // Minimal properties: let the non-shim code path do most of the logic for us. + fProperties->compactStyle = style; + fProperties->groupingSize = -2; // do not forward grouping information + fProperties->minimumGroupingDigits = 2; + refreshFormatter(status); +} CompactDecimalFormat::CompactDecimalFormat(const CompactDecimalFormat& source) = default; CompactDecimalFormat::~CompactDecimalFormat() = default; -CompactDecimalFormat& CompactDecimalFormat::operator=(const CompactDecimalFormat& rhs) {} - -UClassID CompactDecimalFormat::getStaticClassID() {} - -UClassID CompactDecimalFormat::getDynamicClassID() const {} +CompactDecimalFormat& CompactDecimalFormat::operator=(const CompactDecimalFormat& rhs) { + DecimalFormat::operator=(rhs); + return *this; +} #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp index a601269049..07b5661d01 100644 --- a/icu4c/source/i18n/decimfmt.cpp +++ b/icu4c/source/i18n/decimfmt.cpp @@ -29,6 +29,9 @@ using ERoundingMode = icu::DecimalFormat::ERoundingMode; using EPadPosition = icu::DecimalFormat::EPadPosition; +UOBJECT_DEFINE_RTTI_IMPLEMENTATION(DecimalFormat) + + DecimalFormat::DecimalFormat(UErrorCode& status) : DecimalFormat(nullptr, status) { } @@ -446,13 +449,14 @@ DecimalFormat::format(const DecimalQuantity& number, UnicodeString& appendTo, Fi return appendTo; } -void -DecimalFormat::parse(const UnicodeString& text, Formattable& result, ParsePosition& parsePosition) const { +void DecimalFormat::parse(const UnicodeString& /*text*/, Formattable& /*result*/, + ParsePosition& /*parsePosition*/) const { // FIXME } -CurrencyAmount* DecimalFormat::parseCurrency(const UnicodeString& text, ParsePosition& pos) const { +CurrencyAmount* DecimalFormat::parseCurrency(const UnicodeString& /*text*/, ParsePosition& /*pos*/) const { // FIXME + return nullptr; } const DecimalFormatSymbols* DecimalFormat::getDecimalFormatSymbols(void) const { @@ -855,28 +859,36 @@ UCurrencyUsage DecimalFormat::getCurrencyUsage() const { void DecimalFormat::formatToDecimalQuantity(double number, DecimalQuantity& output, UErrorCode& status) const { - // TODO - status = U_UNSUPPORTED_ERROR; + fFormatter->formatDouble(number, status).getDecimalQuantity(output, status); } void DecimalFormat::formatToDecimalQuantity(const Formattable& number, DecimalQuantity& output, UErrorCode& status) const { - // TODO - status = U_UNSUPPORTED_ERROR; + // Check if the Formattable is a DecimalQuantity + DecimalQuantity* dq = number.getDecimalQuantity(); + if (dq != nullptr) { + fFormatter->formatDecimalQuantity(*dq, status).getDecimalQuantity(output, status); + return; + } + + // If not, it must be Double, Long (int32_t), or Int64: + switch (number.getType()) { + case Formattable::kDouble: + fFormatter->formatDouble(number.getDouble(), status).getDecimalQuantity(output, status); + break; + case Formattable::kLong: + fFormatter->formatInt(number.getLong(), status).getDecimalQuantity(output, status); + break; + case Formattable::kInt64: + default: + fFormatter->formatInt(number.getInt64(), status).getDecimalQuantity(output, status); + } } const number::LocalizedNumberFormatter& DecimalFormat::toNumberFormatter() const { return *fFormatter; } -UClassID DecimalFormat::getStaticClassID() { - // TODO -} - -UClassID DecimalFormat::getDynamicClassID() const { - // TODO -} - /** Rebuilds the formatter object from the property bag. */ void DecimalFormat::refreshFormatter(UErrorCode& status) { if (fExportedProperties == nullptr) { diff --git a/icu4c/source/i18n/numparse_impl.cpp b/icu4c/source/i18n/numparse_impl.cpp index 3e04723be1..1d29aee758 100644 --- a/icu4c/source/i18n/numparse_impl.cpp +++ b/icu4c/source/i18n/numparse_impl.cpp @@ -74,13 +74,14 @@ NumberParserImpl::createSimpleParser(const Locale& locale, const UnicodeString& return parser; } -NumberParserImpl* NumberParserImpl::createParserFromProperties( - const number::impl::DecimalFormatProperties& properties, DecimalFormatSymbols symbols, - bool parseCurrency, bool optimize, UErrorCode& status) { - // TODO - status = U_UNSUPPORTED_ERROR; - return nullptr; -} +//NumberParserImpl* +//NumberParserImpl::createParserFromProperties(const number::impl::DecimalFormatProperties& properties, +// DecimalFormatSymbols symbols, bool parseCurrency, +// bool optimize, UErrorCode& status) { +// // TODO +// status = U_UNSUPPORTED_ERROR; +// return nullptr; +//} NumberParserImpl::NumberParserImpl(parse_flags_t parseFlags, bool computeLeads) : fParseFlags(parseFlags), fComputeLeads(computeLeads) { diff --git a/icu4c/source/i18n/unicode/compactdecimalformat.h b/icu4c/source/i18n/unicode/compactdecimalformat.h index e5d27de05a..194e8de30b 100644 --- a/icu4c/source/i18n/unicode/compactdecimalformat.h +++ b/icu4c/source/i18n/unicode/compactdecimalformat.h @@ -112,7 +112,10 @@ public: * other classes have different class IDs. * @stable ICU 51 */ - virtual UClassID getDynamicClassID() const U_OVERRIDE; + UClassID getDynamicClassID() const U_OVERRIDE; + + private: + CompactDecimalFormat(const Locale& inLocale, UNumberCompactStyle style, UErrorCode& status); }; U_NAMESPACE_END diff --git a/icu4c/source/i18n/unicode/decimfmt.h b/icu4c/source/i18n/unicode/decimfmt.h index 88d50c7a89..75dc8d5f44 100644 --- a/icu4c/source/i18n/unicode/decimfmt.h +++ b/icu4c/source/i18n/unicode/decimfmt.h @@ -60,11 +60,9 @@ class CurrencyPluralInfo; class Hashtable; class UnicodeSet; class FieldPositionHandler; -class DecimalFormatStaticSets; class FixedDecimal; -class DecimalFormatImpl; class PluralRules; -class VisibleDigitsWithExponent; +class CompactDecimalFormat; namespace number { class LocalizedNumberFormatter; @@ -1975,7 +1973,7 @@ class U_I18N_API DecimalFormat : public NumberFormat { * other classes have different class IDs. * @stable ICU 2.0 */ - virtual UClassID getDynamicClassID(void) const U_OVERRIDE; + UClassID getDynamicClassID(void) const U_OVERRIDE; private: @@ -2030,6 +2028,9 @@ class U_I18N_API DecimalFormat : public NumberFormat { LocalPointer fParser; LocalPointer fParserWithCurrency; + // Allow child class CompactDecimalFormat to access fProperties: + friend class CompactDecimalFormat; + }; U_NAMESPACE_END From 4fad01c342bbd18b428e6d4c6c0613a34b2c2ead Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Sat, 17 Mar 2018 01:31:52 +0000 Subject: [PATCH 042/129] ICU-13634 Adding pipeline for custom currency symbols. X-SVN-Rev: 41119 --- icu4c/source/i18n/Makefile.in | 2 +- icu4c/source/i18n/compactdecimalformat.cpp | 1 + icu4c/source/i18n/decimfmt.cpp | 16 +-- icu4c/source/i18n/number_currencysymbols.cpp | 115 ++++++++++++++++++ icu4c/source/i18n/number_currencysymbols.h | 82 +++++++++++++ icu4c/source/i18n/number_formatimpl.cpp | 13 +- icu4c/source/i18n/number_formatimpl.h | 7 +- icu4c/source/i18n/number_mapper.cpp | 8 +- icu4c/source/i18n/number_mapper.h | 2 + icu4c/source/i18n/number_patternmodifier.cpp | 44 ++----- icu4c/source/i18n/number_patternmodifier.h | 12 +- .../intltest/numbertest_patternmodifier.cpp | 12 +- icu4c/source/test/intltest/numfmtst.cpp | 4 +- 13 files changed, 253 insertions(+), 65 deletions(-) create mode 100644 icu4c/source/i18n/number_currencysymbols.cpp create mode 100644 icu4c/source/i18n/number_currencysymbols.h diff --git a/icu4c/source/i18n/Makefile.in b/icu4c/source/i18n/Makefile.in index 326ce2170f..e0fc77fee0 100644 --- a/icu4c/source/i18n/Makefile.in +++ b/icu4c/source/i18n/Makefile.in @@ -108,7 +108,7 @@ double-conversion-cached-powers.o double-conversion-diy-fp.o double-conversion-f numparse_stringsegment.o numparse_unisets.o numparse_parsednumber.o \ numparse_impl.o numparse_symbols.o numparse_decimal.o numparse_scientific.o \ numparse_currency.o numparse_affixes.o numparse_compositions.o \ -number_mapper.o number_multiplier.o +number_mapper.o number_multiplier.o number_currencysymbols.o ## Header files to install diff --git a/icu4c/source/i18n/compactdecimalformat.cpp b/icu4c/source/i18n/compactdecimalformat.cpp index 18dacb3e32..93033dc8fe 100644 --- a/icu4c/source/i18n/compactdecimalformat.cpp +++ b/icu4c/source/i18n/compactdecimalformat.cpp @@ -27,6 +27,7 @@ CompactDecimalFormat::createInstance(const Locale& inLocale, UNumberCompactStyle CompactDecimalFormat::CompactDecimalFormat(const Locale& inLocale, UNumberCompactStyle style, UErrorCode& status) : DecimalFormat(new DecimalFormatSymbols(inLocale, status), status) { + if (U_FAILURE(status)) return; // Minimal properties: let the non-shim code path do most of the logic for us. fProperties->compactStyle = style; fProperties->groupingSize = -2; // do not forward grouping information diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp index 07b5661d01..3308f60932 100644 --- a/icu4c/source/i18n/decimfmt.cpp +++ b/icu4c/source/i18n/decimfmt.cpp @@ -46,6 +46,7 @@ DecimalFormat::DecimalFormat(const UnicodeString& pattern, DecimalFormatSymbols* UErrorCode& status) : DecimalFormat(symbolsToAdopt, status) { setPropertiesFromPattern(pattern, IGNORE_ROUNDING_IF_CURRENCY, status); + refreshFormatter(status); } DecimalFormat::DecimalFormat(const UnicodeString& pattern, DecimalFormatSymbols* symbolsToAdopt, @@ -896,18 +897,9 @@ void DecimalFormat::refreshFormatter(UErrorCode& status) { // The only time when this happens is during legacy deserialization. return; } - Locale locale = getLocale(ULOC_ACTUAL_LOCALE, status); - if (U_FAILURE(status)) { - // Constructor - locale = fSymbols->getLocale(ULOC_ACTUAL_LOCALE, status); - } - if (U_FAILURE(status)) { - // Deserialization - locale = fSymbols->getLocale(); - } - if (U_FAILURE(status)) { - return; - } + + // In C++, fSymbols is the source of truth for the locale. + Locale locale = fSymbols->getLocale(); fFormatter.adoptInsteadAndCheckErrorCode( new LocalizedNumberFormatter( diff --git a/icu4c/source/i18n/number_currencysymbols.cpp b/icu4c/source/i18n/number_currencysymbols.cpp new file mode 100644 index 0000000000..0c946f9fc1 --- /dev/null +++ b/icu4c/source/i18n/number_currencysymbols.cpp @@ -0,0 +1,115 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT + +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + +#include "numparse_types.h" +#include "number_currencysymbols.h" + +using namespace icu; +using namespace icu::number; +using namespace icu::number::impl; + + +CurrencySymbols::CurrencySymbols(CurrencyUnit currency, const Locale& locale, UErrorCode& status) + : fCurrency(currency), fLocaleName(locale.getName(), status) {} + +UnicodeString CurrencySymbols::getNarrowCurrencySymbol(UErrorCode& status) const { + return loadSymbol(UCURR_NARROW_SYMBOL_NAME, status); +} + +UnicodeString CurrencySymbols::loadSymbol(UCurrNameStyle selector, UErrorCode& status) const { + UBool ignoredIsChoiceFormatFillIn = FALSE; + int32_t symbolLen = 0; + const char16_t* symbol = ucurr_getName( + fCurrency.getISOCurrency(), + fLocaleName.data(), + selector, + &ignoredIsChoiceFormatFillIn, + &symbolLen, + &status); + // Readonly-aliasing char16_t* constructor: + return UnicodeString(TRUE, symbol, symbolLen); +} + +UnicodeString CurrencySymbols::getPluralName(StandardPlural::Form plural, UErrorCode& status) const { + UBool isChoiceFormat = FALSE; + int32_t symbolLen = 0; + const char16_t* symbol = ucurr_getPluralName( + fCurrency.getISOCurrency(), + fLocaleName.data(), + &isChoiceFormat, + StandardPlural::getKeyword(plural), + &symbolLen, + &status); + // Readonly-aliasing char16_t* constructor: + return UnicodeString(TRUE, symbol, symbolLen); +} + + +CurrencyDataSymbols::CurrencyDataSymbols(CurrencyUnit currency, const Locale& locale, UErrorCode& status) + : CurrencySymbols(currency, locale, status) {} + +UnicodeString CurrencyDataSymbols::getCurrencySymbol(UErrorCode& status) const { + return loadSymbol(UCURR_SYMBOL_NAME, status); +} + +UnicodeString CurrencyDataSymbols::getIntlCurrencySymbol(UErrorCode&) const { + // Readonly-aliasing char16_t* constructor: + return UnicodeString(TRUE, fCurrency.getISOCurrency(), 3); +} + + +CurrencyCustomSymbols::CurrencyCustomSymbols(CurrencyUnit currency, const Locale& locale, + const DecimalFormatSymbols& symbols, UErrorCode& status) + : CurrencySymbols(currency, locale, status) { + // Hit the data bundle if the DecimalFormatSymbols version is not custom. + // Note: the CurrencyDataSymbols implementation waits to hit the data bundle until requested. + if (symbols.isCustomCurrencySymbol()) { + fCurrencySymbol = symbols.getConstSymbol(DecimalFormatSymbols::kCurrencySymbol); + } else { + fCurrencySymbol = loadSymbol(UCURR_SYMBOL_NAME, status); + } + if (symbols.isCustomIntlCurrencySymbol()) { + fIntlCurrencySymbol = symbols.getConstSymbol(DecimalFormatSymbols::kIntlCurrencySymbol); + } else { + // UnicodeString copy constructor since we don't know about the lifetime of the CurrencyUnit + fIntlCurrencySymbol = UnicodeString(currency.getISOCurrency(), 3); + } +} + +UnicodeString CurrencyCustomSymbols::getCurrencySymbol(UErrorCode&) const { + return fCurrencySymbol; +} + +UnicodeString CurrencyCustomSymbols::getIntlCurrencySymbol(UErrorCode&) const { + return fIntlCurrencySymbol; +} + + +CurrencyUnit +icu::number::impl::resolveCurrency(const DecimalFormatProperties& properties, const Locale& locale, + UErrorCode& status) { + if (!properties.currency.isNull()) { + return properties.currency.getNoError(); + } else { + UErrorCode localStatus = U_ZERO_ERROR; + char16_t buf[4] = {}; + ucurr_forLocale(locale.getName(), buf, 4, &localStatus); + if (U_SUCCESS(localStatus)) { + return CurrencyUnit(buf, status); + } else { + // Default currency (XXX) + return CurrencyUnit(); + } + } +} + + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/number_currencysymbols.h b/icu4c/source/i18n/number_currencysymbols.h new file mode 100644 index 0000000000..63810b0043 --- /dev/null +++ b/icu4c/source/i18n/number_currencysymbols.h @@ -0,0 +1,82 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#ifndef __SOURCE_NUMBER_CURRENCYSYMBOLS_H__ +#define __SOURCE_NUMBER_CURRENCYSYMBOLS_H__ + +#include "numparse_types.h" +#include "charstr.h" +#include "number_decimfmtprops.h" + +U_NAMESPACE_BEGIN namespace number { +namespace impl { + + +class CurrencySymbols { + public: + CurrencySymbols() = default; // default constructor: leaves class in valid but undefined state + + explicit CurrencySymbols(CurrencyUnit currency, const Locale& locale, UErrorCode& status); + + virtual UnicodeString getCurrencySymbol(UErrorCode& status) const = 0; + + virtual UnicodeString getIntlCurrencySymbol(UErrorCode& status) const = 0; + + // Put narrow and plural symbols in the base class since there is no API for overriding them + UnicodeString getNarrowCurrencySymbol(UErrorCode& status) const; + + UnicodeString getPluralName(StandardPlural::Form plural, UErrorCode& status) const; + + protected: + CurrencyUnit fCurrency; + CharString fLocaleName; + + UnicodeString loadSymbol(UCurrNameStyle selector, UErrorCode& status) const; +}; + + +class CurrencyDataSymbols : public CurrencySymbols, public UMemory { + public: + CurrencyDataSymbols() = default; // default constructor: leaves class in valid but undefined state + + CurrencyDataSymbols(CurrencyUnit currency, const Locale& locale, UErrorCode& status); + + UnicodeString getCurrencySymbol(UErrorCode& status) const U_OVERRIDE; + + UnicodeString getIntlCurrencySymbol(UErrorCode& status) const U_OVERRIDE; +}; + + +class CurrencyCustomSymbols : public CurrencySymbols, public UMemory { + public: + CurrencyCustomSymbols() = default; // default constructor: leaves class in valid but undefined state + + CurrencyCustomSymbols(CurrencyUnit currency, const Locale& locale, const DecimalFormatSymbols& symbols, + UErrorCode& status); + + UnicodeString getCurrencySymbol(UErrorCode& status) const U_OVERRIDE; + + UnicodeString getIntlCurrencySymbol(UErrorCode& status) const U_OVERRIDE; + + private: + UnicodeString fCurrencySymbol; + UnicodeString fIntlCurrencySymbol; +}; + + +/** + * Resolves the effective currency from the property bag. + */ +CurrencyUnit +resolveCurrency(const DecimalFormatProperties& properties, const Locale& locale, UErrorCode& status); + + +} // namespace impl +} // namespace numparse +U_NAMESPACE_END + +#endif //__SOURCE_NUMBER_CURRENCYSYMBOLS_H__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/number_formatimpl.cpp b/icu4c/source/i18n/number_formatimpl.cpp index 04344b368f..3edf73e197 100644 --- a/icu4c/source/i18n/number_formatimpl.cpp +++ b/icu4c/source/i18n/number_formatimpl.cpp @@ -100,6 +100,7 @@ struct CurrencyFormatInfoResult { CurrencyFormatInfoResult getCurrencyFormatInfo(const Locale& locale, const char* isoCode, UErrorCode& status) { // TODO: Load this data in a centralized location like ICU4J? + // TODO: Move this into the CurrencySymbols class? // TODO: Parts of this same data are loaded in dcfmtsym.cpp; should clean up. CurrencyFormatInfoResult result = {false, nullptr, nullptr, nullptr}; if (U_FAILURE(status)) { return result; } @@ -204,6 +205,14 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe, if (isCurrency) { currency = CurrencyUnit(macros.unit, status); // Restore CurrencyUnit from MeasureUnit } + CurrencySymbols* currencySymbols; + if (macros.currencySymbols != nullptr) { + // Used by the DecimalFormat code path + currencySymbols = macros.currencySymbols; + } else { + fWarehouse.fCurrencyDataSymbols = {currency, macros.locale, status}; + currencySymbols = &fWarehouse.fCurrencyDataSymbols; + } UNumberUnitWidth unitWidth = UNUM_UNIT_WIDTH_SHORT; if (macros.unitWidth != UNUM_UNIT_WIDTH_COUNT) { unitWidth = macros.unitWidth; @@ -355,11 +364,11 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe, if (patternModifier->needsPlurals()) { patternModifier->setSymbols( fMicros.symbols, - currency, + currencySymbols, unitWidth, resolvePluralRules(macros.rules, macros.locale, status)); } else { - patternModifier->setSymbols(fMicros.symbols, currency, unitWidth, nullptr); + patternModifier->setSymbols(fMicros.symbols, currencySymbols, unitWidth, nullptr); } if (safe) { fImmutablePatternModifier.adoptInstead(patternModifier->createImmutableAndChain(chain, status)); diff --git a/icu4c/source/i18n/number_formatimpl.h b/icu4c/source/i18n/number_formatimpl.h index cbc04ba30d..dff500ab94 100644 --- a/icu4c/source/i18n/number_formatimpl.h +++ b/icu4c/source/i18n/number_formatimpl.h @@ -50,7 +50,7 @@ class NumberFormatterImpl : public UMemory { MicroProps fMicros; // Other fields possibly used by the number formatting pipeline: - // TODO: Convert some of these LocalPointers to value objects to reduce the number of news? + // TODO: Convert more of these LocalPointers to value objects to reduce the number of news? LocalPointer fSymbols; LocalPointer fRules; LocalPointer fPatternInfo; @@ -60,6 +60,11 @@ class NumberFormatterImpl : public UMemory { LocalPointer fLongNameHandler; LocalPointer fCompactHandler; + // Value objects possibly used by the number formatting pipeline: + struct Warehouse { + CurrencyDataSymbols fCurrencyDataSymbols; + } fWarehouse; + NumberFormatterImpl(const MacroProps ¯os, bool safe, UErrorCode &status); diff --git a/icu4c/source/i18n/number_mapper.cpp b/icu4c/source/i18n/number_mapper.cpp index 0f5567c442..aeaa647d01 100644 --- a/icu4c/source/i18n/number_mapper.cpp +++ b/icu4c/source/i18n/number_mapper.cpp @@ -13,6 +13,7 @@ #include "number_patternstring.h" #include "unicode/errorcode.h" #include "number_utils.h" +#include "number_currencysymbols.h" using namespace icu; using namespace icu::number; @@ -79,13 +80,16 @@ MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& propert bool useCurrency = ( !properties.currency.isNull() || !properties.currencyPluralInfo.fPtr.isNull() || !properties.currencyUsage.isNull() || affixProvider->hasCurrencySign()); - // TODO: CustomSymbolCurrency - CurrencyUnit currency = {u"USD", status}; + CurrencyUnit currency = resolveCurrency(properties, locale, status); UCurrencyUsage currencyUsage = properties.currencyUsage.getOrDefault(UCURR_USAGE_STANDARD); if (useCurrency) { // NOTE: Slicing is OK. macros.unit = currency; // NOLINT } + if (symbols.isCustomCurrencySymbol() || symbols.isCustomIntlCurrencySymbol()) { + warehouse.currencyCustomSymbols = {currency, locale, symbols, status}; + macros.currencySymbols = &warehouse.currencyCustomSymbols; + } /////////////////////// // ROUNDING STRATEGY // diff --git a/icu4c/source/i18n/number_mapper.h b/icu4c/source/i18n/number_mapper.h index d9af795499..7c08eecfb6 100644 --- a/icu4c/source/i18n/number_mapper.h +++ b/icu4c/source/i18n/number_mapper.h @@ -11,6 +11,7 @@ #include "unicode/currpinf.h" #include "standardplural.h" #include "number_patternstring.h" +#include "number_currencysymbols.h" U_NAMESPACE_BEGIN namespace number { namespace impl { @@ -105,6 +106,7 @@ class CurrencyPluralInfoAffixProvider : public AffixPatternProvider, public UMem struct DecimalFormatWarehouse { PropertiesAffixPatternProvider propertiesAPP; CurrencyPluralInfoAffixProvider currencyPluralInfoAPP; + CurrencyCustomSymbols currencyCustomSymbols; }; diff --git a/icu4c/source/i18n/number_patternmodifier.cpp b/icu4c/source/i18n/number_patternmodifier.cpp index b77f559a26..5b6c6bdb04 100644 --- a/icu4c/source/i18n/number_patternmodifier.cpp +++ b/icu4c/source/i18n/number_patternmodifier.cpp @@ -27,13 +27,12 @@ void MutablePatternModifier::setPatternAttributes(UNumberSignDisplay signDisplay this->perMilleReplacesPercent = perMille; } -void MutablePatternModifier::setSymbols(const DecimalFormatSymbols* symbols, const CurrencyUnit& currency, +void MutablePatternModifier::setSymbols(const DecimalFormatSymbols* symbols, + const CurrencySymbols* currencySymbols, const UNumberUnitWidth unitWidth, const PluralRules* rules) { U_ASSERT((rules != nullptr) == needsPlurals()); this->symbols = symbols; - uprv_memcpy(static_cast(this->currencyCode), - currency.getISOCurrency(), - sizeof(char16_t) * 4); + this->currencySymbols = currencySymbols; this->unitWidth = unitWidth; this->rules = rules; } @@ -249,6 +248,7 @@ void MutablePatternModifier::prepareAffix(bool isPrefix) { } UnicodeString MutablePatternModifier::getSymbol(AffixPatternType type) const { + UErrorCode localStatus = U_ZERO_ERROR; switch (type) { case AffixPatternType::TYPE_MINUS_SIGN: return symbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kMinusSignSymbol); @@ -261,45 +261,23 @@ UnicodeString MutablePatternModifier::getSymbol(AffixPatternType type) const { case AffixPatternType::TYPE_CURRENCY_SINGLE: { // UnitWidth ISO and HIDDEN overrides the singular currency symbol. if (unitWidth == UNumberUnitWidth::UNUM_UNIT_WIDTH_ISO_CODE) { - return UnicodeString(currencyCode, 3); + return currencySymbols->getIntlCurrencySymbol(localStatus); } else if (unitWidth == UNumberUnitWidth::UNUM_UNIT_WIDTH_HIDDEN) { return UnicodeString(); + } else if (unitWidth == UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW) { + return currencySymbols->getNarrowCurrencySymbol(localStatus); } else { - UCurrNameStyle selector = (unitWidth == UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW) - ? UCurrNameStyle::UCURR_NARROW_SYMBOL_NAME - : UCurrNameStyle::UCURR_SYMBOL_NAME; - UErrorCode status = U_ZERO_ERROR; - UBool isChoiceFormat = FALSE; - int32_t symbolLen = 0; - const char16_t* symbol = ucurr_getName( - currencyCode, - symbols->getLocale().getName(), - selector, - &isChoiceFormat, - &symbolLen, - &status); - return UnicodeString(symbol, symbolLen); + return currencySymbols->getCurrencySymbol(localStatus); } } case AffixPatternType::TYPE_CURRENCY_DOUBLE: - return UnicodeString(currencyCode, 3); - case AffixPatternType::TYPE_CURRENCY_TRIPLE: { + return currencySymbols->getIntlCurrencySymbol(localStatus); + case AffixPatternType::TYPE_CURRENCY_TRIPLE: // NOTE: This is the code path only for patterns containing "¤¤¤". // Plural currencies set via the API are formatted in LongNameHandler. // This code path is used by DecimalFormat via CurrencyPluralInfo. U_ASSERT(plural != StandardPlural::Form::COUNT); - UErrorCode status = U_ZERO_ERROR; - UBool isChoiceFormat = FALSE; - int32_t symbolLen = 0; - const char16_t* symbol = ucurr_getPluralName( - currencyCode, - symbols->getLocale().getName(), - &isChoiceFormat, - StandardPlural::getKeyword(plural), - &symbolLen, - &status); - return UnicodeString(symbol, symbolLen); - } + return currencySymbols->getPluralName(plural, localStatus); case AffixPatternType::TYPE_CURRENCY_QUAD: return UnicodeString(u"\uFFFD"); case AffixPatternType::TYPE_CURRENCY_QUINT: diff --git a/icu4c/source/i18n/number_patternmodifier.h b/icu4c/source/i18n/number_patternmodifier.h index ddce46337e..74b6f8b83b 100644 --- a/icu4c/source/i18n/number_patternmodifier.h +++ b/icu4c/source/i18n/number_patternmodifier.h @@ -13,6 +13,7 @@ #include "number_types.h" #include "number_modifiers.h" #include "number_utils.h" +#include "number_currencysymbols.h" U_NAMESPACE_BEGIN @@ -110,17 +111,16 @@ class U_I18N_API MutablePatternModifier * * @param symbols * The desired instance of DecimalFormatSymbols. - * @param currency - * The currency to be used when substituting currency values into the affixes. + * @param currencySymbols + * The currency symbols to be used when substituting currency values into the affixes. * @param unitWidth * The width used to render currencies. * @param rules * Required if the triple currency sign, "¤¤¤", appears in the pattern, which can be determined from the * convenience method {@link #needsPlurals()}. */ - void - setSymbols(const DecimalFormatSymbols *symbols, const CurrencyUnit ¤cy, UNumberUnitWidth unitWidth, - const PluralRules *rules); + void setSymbols(const DecimalFormatSymbols* symbols, const CurrencySymbols* currencySymbols, + UNumberUnitWidth unitWidth, const PluralRules* rules); /** * Sets attributes of the current number being processed. @@ -201,7 +201,7 @@ class U_I18N_API MutablePatternModifier // Symbol details (initialized in setSymbols) const DecimalFormatSymbols *symbols; UNumberUnitWidth unitWidth; - char16_t currencyCode[4]; + const CurrencySymbols *currencySymbols; const PluralRules *rules; // Number details (initialized in setNumberProperties) diff --git a/icu4c/source/test/intltest/numbertest_patternmodifier.cpp b/icu4c/source/test/intltest/numbertest_patternmodifier.cpp index 07f840576b..bd8acc9697 100644 --- a/icu4c/source/test/intltest/numbertest_patternmodifier.cpp +++ b/icu4c/source/test/intltest/numbertest_patternmodifier.cpp @@ -28,9 +28,9 @@ void PatternModifierTest::testBasic() { mod.setPatternInfo(&patternInfo); mod.setPatternAttributes(UNUM_SIGN_AUTO, false); DecimalFormatSymbols symbols(Locale::getEnglish(), status); - CurrencyUnit currency(u"USD", status); + CurrencyDataSymbols currencySymbols({u"USD", status}, "en", status); assertSuccess("Spot 2", status); - mod.setSymbols(&symbols, currency, UNUM_UNIT_WIDTH_SHORT, nullptr); + mod.setSymbols(&symbols, ¤cySymbols, UNUM_UNIT_WIDTH_SHORT, nullptr); mod.setNumberProperties(1, StandardPlural::Form::COUNT); assertEquals("Pattern a0b", u"a", getPrefix(mod, status)); @@ -88,9 +88,9 @@ void PatternModifierTest::testPatternWithNoPlaceholder() { mod.setPatternInfo(&patternInfo); mod.setPatternAttributes(UNUM_SIGN_AUTO, false); DecimalFormatSymbols symbols(Locale::getEnglish(), status); - CurrencyUnit currency(u"USD", status); + CurrencyDataSymbols currencySymbols({u"USD", status}, "en", status); assertSuccess("Spot 2", status); - mod.setSymbols(&symbols, currency, UNUM_UNIT_WIDTH_SHORT, nullptr); + mod.setSymbols(&symbols, ¤cySymbols, UNUM_UNIT_WIDTH_SHORT, nullptr); mod.setNumberProperties(1, StandardPlural::Form::COUNT); // Unsafe Code Path @@ -124,10 +124,10 @@ void PatternModifierTest::testMutableEqualsImmutable() { mod.setPatternInfo(&patternInfo); mod.setPatternAttributes(UNUM_SIGN_AUTO, false); DecimalFormatSymbols symbols(Locale::getEnglish(), status); - CurrencyUnit currency(u"USD", status); + CurrencyDataSymbols currencySymbols({u"USD", status}, "en", status); assertSuccess("Spot 2", status); if (U_FAILURE(status)) { return; } - mod.setSymbols(&symbols, currency, UNUM_UNIT_WIDTH_SHORT, nullptr); + mod.setSymbols(&symbols, ¤cySymbols, UNUM_UNIT_WIDTH_SHORT, nullptr); DecimalQuantity fq; fq.setToInt(1); diff --git a/icu4c/source/test/intltest/numfmtst.cpp b/icu4c/source/test/intltest/numfmtst.cpp index cf59e849c6..8eb7b57ec8 100644 --- a/icu4c/source/test/intltest/numfmtst.cpp +++ b/icu4c/source/test/intltest/numfmtst.cpp @@ -1236,7 +1236,7 @@ NumberFormatTest::TestCurrency(void) UnicodeString s; currencyFmt->format(1.50, s); logln((UnicodeString)"Un pauvre ici a..........." + s); if (!(s==CharsToUnicodeString("1,50\\u00A0$"))) - errln((UnicodeString)"FAIL: Expected 1,50$"); + errln((UnicodeString)"FAIL: Expected 1,50$ but got " + s); delete currencyFmt; s.truncate(0); char loc[256]={0}; @@ -1246,7 +1246,7 @@ NumberFormatTest::TestCurrency(void) currencyFmt->format(1.50, s); logln((UnicodeString)"Un pauvre en Allemagne a.." + s); if (!(s==CharsToUnicodeString("1,50\\u00A0DM"))) - errln((UnicodeString)"FAIL: Expected 1,50DM"); + errln((UnicodeString)"FAIL: Expected 1,50DM but got " + s); delete currencyFmt; s.truncate(0); len = uloc_canonicalize("fr_FR_PREEURO", loc, 256, &status); From 2edb4ec82ae45a1e4a839f90014437c37867f09d Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Sat, 17 Mar 2018 07:23:08 +0000 Subject: [PATCH 043/129] ICU-13634 Formatting section of data-driven test file is passing. X-SVN-Rev: 41120 --- icu4c/source/i18n/decimfmt.cpp | 2 +- icu4c/source/i18n/number_decimalquantity.cpp | 17 +++-- icu4c/source/i18n/number_decimalquantity.h | 3 +- icu4c/source/i18n/number_patternstring.cpp | 7 +- icu4c/source/i18n/number_rounding.cpp | 34 +++++++++- icu4c/source/i18n/number_roundingutils.h | 6 ++ icu4c/source/i18n/number_scientific.cpp | 3 + icu4c/source/i18n/numparse_parsednumber.cpp | 2 +- .../intltest/numbertest_patternstring.cpp | 2 + .../numberformattestspecification.txt | 66 +++++++++---------- .../number/DecimalQuantity_AbstractBCD.java | 7 +- .../DecimalQuantity_DualStorageBCD.java | 3 +- .../src/com/ibm/icu/text/DecimalFormat.java | 2 +- .../dev/test/number/PatternStringTest.java | 2 + 14 files changed, 101 insertions(+), 55 deletions(-) diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp index 3308f60932..510039e86b 100644 --- a/icu4c/source/i18n/decimfmt.cpp +++ b/icu4c/source/i18n/decimfmt.cpp @@ -287,7 +287,7 @@ void DecimalFormat::setGroupingUsed(UBool enabled) { if (enabled) { // Set to a reasonable default value fProperties->groupingSize = 3; - fProperties->secondaryGroupingSize = 3; + fProperties->secondaryGroupingSize = -1; } else { fProperties->groupingSize = 0; fProperties->secondaryGroupingSize = 0; diff --git a/icu4c/source/i18n/number_decimalquantity.cpp b/icu4c/source/i18n/number_decimalquantity.cpp index 9798f932d8..6200726446 100644 --- a/icu4c/source/i18n/number_decimalquantity.cpp +++ b/icu4c/source/i18n/number_decimalquantity.cpp @@ -161,7 +161,7 @@ uint64_t DecimalQuantity::getPositionFingerprint() const { } void DecimalQuantity::roundToIncrement(double roundingIncrement, RoundingMode roundingMode, - int32_t minMaxFrac, UErrorCode& status) { + int32_t maxFrac, UErrorCode& status) { // TODO: This is innefficient. Improve? // TODO: Should we convert to decNumber instead? double temp = toDouble(); @@ -173,7 +173,8 @@ void DecimalQuantity::roundToIncrement(double roundingIncrement, RoundingMode ro setToDouble(temp); // Since we reset the value to a double, we need to specify the rounding boundary // in order to get the DecimalQuantity out of approximation mode. - roundToMagnitude(-minMaxFrac, roundingMode, status); + // NOTE: In Java, we have minMaxFrac, but in C++, the two are differentiated. + roundToMagnitude(-maxFrac, roundingMode, status); } void DecimalQuantity::multiplyBy(int32_t multiplicand) { @@ -454,6 +455,7 @@ int64_t DecimalQuantity::toLong() const { for (int32_t magnitude = scale + precision - 1; magnitude >= 0; magnitude--) { result = result * 10 + getDigitPos(magnitude - scale); } + if (isNegative()) { result = -result; } return result; } @@ -485,15 +487,15 @@ bool DecimalQuantity::fitsInLong() const { // The largest int64 is: 9,223,372,036,854,775,807 for (int p = 0; p < precision; p++) { int8_t digit = getDigit(18 - p); - static int8_t INT64_BCD[] = { 9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 7 }; + static int8_t INT64_BCD[] = { 9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 8 }; if (digit < INT64_BCD[p]) { return true; } else if (digit > INT64_BCD[p]) { return false; } } - // Exactly equal to max long. - return true; + // Exactly equal to max long plus one. + return isNegative(); } double DecimalQuantity::toDouble() const { @@ -725,8 +727,8 @@ UnicodeString DecimalQuantity::toPlainString() const { sb.append(u'-'); } for (int m = getUpperDisplayMagnitude(); m >= getLowerDisplayMagnitude(); m--) { + if (m == -1) { sb.append(u'.'); } sb.append(getDigit(m) + u'0'); - if (m == 0) { sb.append(u'.'); } } return sb; } @@ -1046,12 +1048,13 @@ UnicodeString DecimalQuantity::toString() const { snprintf( buffer8, sizeof(buffer8), - "", + "", (lOptPos > 999 ? 999 : lOptPos), lReqPos, rReqPos, (rOptPos < -999 ? -999 : rOptPos), (usingBytes ? "bytes" : "long"), + (isNegative() ? "-" : ""), (precision == 0 ? "0" : digits.getAlias()), "E", scale); diff --git a/icu4c/source/i18n/number_decimalquantity.h b/icu4c/source/i18n/number_decimalquantity.h index a3c329e265..a776ddc48a 100644 --- a/icu4c/source/i18n/number_decimalquantity.h +++ b/icu4c/source/i18n/number_decimalquantity.h @@ -71,7 +71,7 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { * @param mathContext The {@link RoundingMode} to use if rounding is necessary. */ void roundToIncrement(double roundingIncrement, RoundingMode roundingMode, - int32_t minMaxFrac, UErrorCode& status); + int32_t maxFrac, UErrorCode& status); /** * Rounds the number to a specified magnitude (power of ten). @@ -130,7 +130,6 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { /** * Returns whether or not a Long can fully represent the value stored in this DecimalQuantity. - * Assumes that the DecimalQuantity is positive. */ bool fitsInLong() const; diff --git a/icu4c/source/i18n/number_patternstring.cpp b/icu4c/source/i18n/number_patternstring.cpp index 7ec0297c6e..8ea130fb4a 100644 --- a/icu4c/source/i18n/number_patternstring.cpp +++ b/icu4c/source/i18n/number_patternstring.cpp @@ -14,6 +14,7 @@ #include "number_patternstring.h" #include "unicode/utf16.h" #include "number_utils.h" +#include "number_roundingutils.h" using namespace icu; using namespace icu::number; @@ -706,11 +707,11 @@ UnicodeString PatternStringUtils::propertiesToPatternString(const DecimalFormatP } } else if (roundingInterval != 0.0) { // Rounding Interval. - digitsStringScale = minFrac; + digitsStringScale = -roundingutils::doubleFractionLength(roundingInterval); // TODO: Check for DoS here? DecimalQuantity incrementQuantity; incrementQuantity.setToDouble(roundingInterval); - incrementQuantity.adjustMagnitude(minFrac); + incrementQuantity.adjustMagnitude(-digitsStringScale); incrementQuantity.roundToMagnitude(0, kDefaultMode, status); UnicodeString str = incrementQuantity.toPlainString(); if (str.charAt(0) == u'-') { @@ -809,7 +810,7 @@ UnicodeString PatternStringUtils::propertiesToPatternString(const DecimalFormatP sb.append(AffixUtils::escape(UnicodeStringCharSequence(np))); // Copy the positive digit format into the negative. // This is optional; the pattern is the same as if '#' were appended here instead. - sb.append(sb, afterPrefixPos, beforeSuffixPos); + sb.append(sb, afterPrefixPos, beforeSuffixPos - afterPrefixPos); if (!nsp.isBogus()) { sb.append(nsp); } diff --git a/icu4c/source/i18n/number_rounding.cpp b/icu4c/source/i18n/number_rounding.cpp index fd4dafdf98..5f794228db 100644 --- a/icu4c/source/i18n/number_rounding.cpp +++ b/icu4c/source/i18n/number_rounding.cpp @@ -9,11 +9,16 @@ #include "unicode/numberformatter.h" #include "number_types.h" #include "number_decimalquantity.h" +#include "double-conversion.h" +#include "number_roundingutils.h" using namespace icu; using namespace icu::number; using namespace icu::number::impl; + +using double_conversion::DoubleToStringConverter; + namespace { int32_t getRoundingMagnitudeFraction(int maxFrac) { @@ -46,6 +51,26 @@ int32_t getDisplayMagnitudeSignificant(const DecimalQuantity &value, int minSig) } +digits_t roundingutils::doubleFractionLength(double input) { + char buffer[DoubleToStringConverter::kBase10MaximalLength + 1]; + bool sign; // unused; always positive + int32_t length; + int32_t point; + DoubleToStringConverter::DoubleToAscii( + input, + DoubleToStringConverter::DtoaMode::SHORTEST, + 0, + buffer, + sizeof(buffer), + &sign, + &length, + &point + ); + + return static_cast(length - point); +} + + Rounder Rounder::unlimited() { return Rounder(RND_NONE, {}, kDefaultMode); } @@ -225,6 +250,8 @@ IncrementRounder Rounder::constructIncrement(double increment, int32_t minFrac) IncrementSettings settings; settings.fIncrement = increment; settings.fMinFrac = static_cast(minFrac); + // One of the few pre-computed quantities: + settings.fMaxFrac = roundingutils::doubleFractionLength(increment); RounderUnion union_; union_.increment = settings; return {RND_INCREMENT, union_, kDefaultMode}; @@ -335,8 +362,11 @@ void Rounder::apply(impl::DecimalQuantity &value, UErrorCode& status) const { case RND_INCREMENT: value.roundToIncrement( - fUnion.increment.fIncrement, fRoundingMode, fUnion.increment.fMinFrac, status); - value.setFractionLength(fUnion.increment.fMinFrac, fUnion.increment.fMinFrac); + fUnion.increment.fIncrement, + fRoundingMode, + fUnion.increment.fMaxFrac, + status); + value.setFractionLength(fUnion.increment.fMinFrac, INT32_MAX); break; case RND_CURRENCY: diff --git a/icu4c/source/i18n/number_roundingutils.h b/icu4c/source/i18n/number_roundingutils.h index 6868ee0b86..460235a0d6 100644 --- a/icu4c/source/i18n/number_roundingutils.h +++ b/icu4c/source/i18n/number_roundingutils.h @@ -131,6 +131,12 @@ inline bool roundsAtMidpoint(int roundingMode) { } } +/** + * Computes the number of fraction digits in a double. Used for computing maxFrac for an increment. + * Calls into the DoubleToStringConverter library to do so. + */ +digits_t doubleFractionLength(double input); + } // namespace roundingutils } // namespace impl } // namespace number diff --git a/icu4c/source/i18n/number_scientific.cpp b/icu4c/source/i18n/number_scientific.cpp index 548ce625ad..1d8b020e88 100644 --- a/icu4c/source/i18n/number_scientific.cpp +++ b/icu4c/source/i18n/number_scientific.cpp @@ -116,6 +116,9 @@ void ScientificHandler::processQuantity(DecimalQuantity &quantity, MicroProps &m ScientificModifier &mod = micros.helpers.scientificModifier; mod.set(exponent, this); micros.modInner = &mod; + + // We already performed rounding. Do not perform it again. + micros.rounding = Rounder::constructPassThrough(); } int32_t ScientificHandler::getMultiplier(int32_t magnitude) const { diff --git a/icu4c/source/i18n/numparse_parsednumber.cpp b/icu4c/source/i18n/numparse_parsednumber.cpp index 4cf85a0f43..c9b68a245e 100644 --- a/icu4c/source/i18n/numparse_parsednumber.cpp +++ b/icu4c/source/i18n/numparse_parsednumber.cpp @@ -70,7 +70,7 @@ double ParsedNumber::getDouble() const { return l; } - // TODO: MIN_LONG + // TODO: MIN_LONG. It is supported in quantity.toLong() if quantity had the negative flag. double d = quantity.toDouble(); if (0 != (flags & FLAG_NEGATIVE)) { d *= -1; diff --git a/icu4c/source/test/intltest/numbertest_patternstring.cpp b/icu4c/source/test/intltest/numbertest_patternstring.cpp index 67d6cce6ca..b0b1deb3d2 100644 --- a/icu4c/source/test/intltest/numbertest_patternstring.cpp +++ b/icu4c/source/test/intltest/numbertest_patternstring.cpp @@ -50,6 +50,7 @@ void PatternStringTest::testToPatternSimple() { {u"0.##", u"0.##"}, {u"0.00", u"0.00"}, {u"0.00#", u"0.00#"}, + {u"0.05", u"0.05"}, {u"#E0", u"#E0"}, {u"0E0", u"0E0"}, {u"#00E00", u"#00E00"}, @@ -57,6 +58,7 @@ void PatternStringTest::testToPatternSimple() { {u"#;#", u"0;0"}, // ignore a negative prefix pattern of '-' since that is the default: {u"#;-#", u"0"}, + {u"pp#,000;(#)", u"pp#,000;(#,000)"}, {u"**##0", u"**##0"}, {u"*'x'##0", u"*x##0"}, {u"a''b0", u"a''b0"}, diff --git a/icu4c/source/test/testdata/numberformattestspecification.txt b/icu4c/source/test/testdata/numberformattestspecification.txt index 1f11f8da7c..f74a6566aa 100644 --- a/icu4c/source/test/testdata/numberformattestspecification.txt +++ b/icu4c/source/test/testdata/numberformattestspecification.txt @@ -318,8 +318,8 @@ minIntegerDigits maxIntegerDigits minFractionDigits maxFractionDigits output bre 1 1 0 0 3E8 // JDK gives E0 instead of allowing for unlimited precision 0 0 0 0 2.99792458E8 K -// JDK gives .299792E9; Q gives 2.99792E8 -0 1 0 5 2.9979E8 KQ +// J gives 2.9979E8 +0 1 0 5 2.99792E8 J // JDK gives 300E6 0 3 0 0 299.792458E6 K // JDK gives 299.8E6 (maybe maxInt + maxFrac instead of minInt + maxFrac)? @@ -335,8 +335,8 @@ minIntegerDigits maxIntegerDigits minFractionDigits maxFractionDigits output bre 0 0 1 5 .29979E9 // JDK gives E0 0 0 1 0 2.99792458E8 K -// JDK and Q give .2998E9 -0 0 0 4 2.998E8 KQ +// J gives 2.998E8 +0 0 0 4 .29979E9 J // According to the spec, if maxInt>minInt and minInt>1, then set // Context: #13289 2 8 1 6 2.9979246E8 K @@ -382,12 +382,12 @@ begin format maxIntegerDigits output breaks 123 1 3 0 0 0 -// Q ignores max integer if it is less than zero and prints "123" -123 -2147483648 0 Q +// C and Q ignore max integer if it is less than zero and prints "123" +123 -2147483648 0 CQ 12345 1 5 -12345 -2147483648 0 Q +12345 -2147483648 0 CQ 5.3 1 5.3 -5.3 -2147483648 .3 Q +5.3 -2147483648 .3 CQ test patterns with zero set locale en @@ -550,12 +550,12 @@ currency currencyUsage toPattern breaks // These work in J, but it prepends an extra hash sign to the pattern. // C does not print the currency rounding information in the pattern. // K does not support this feature. -USD standard 0.00 CJK -CHF standard 0.00 CJK -CZK standard 0.00 CJK -USD cash 0.00 CJK -CHF cash 0.05 CJK -CZK cash 0 CJK +USD standard 0.00 JK +CHF standard 0.00 JK +CZK standard 0.00 JK +USD cash 0.00 JK +CHF cash 0.05 JK +CZK cash 0 JK test currency rounding set locale en @@ -637,8 +637,8 @@ begin format output breaks Inf [\u221e] -Inf (\u221e) K -// Q prints the affixes -NaN NaN KQ +// J does not print the affixes +NaN [NaN] J test nan and infinity with multiplication set locale en @@ -652,18 +652,18 @@ NaN NaN K test nan and infinity with padding set locale en_US set pattern $$$0.00$ -set formatWidth 7 +set formatWidth 8 begin format padPosition output breaks -Inf beforePrefix $$$\u221e$ K -Inf afterPrefix $$$ \u221e$ K -Inf beforeSuffix $$$\u221e $ K -Inf afterSuffix $$$\u221e$ K -// Q gets $$$NaN$ -NaN beforePrefix NaN KQ -NaN afterPrefix NaN KQ -NaN beforeSuffix NaN KQ -NaN afterSuffix NaN KQ +Inf beforePrefix $$$\u221e$ K +Inf afterPrefix $$$ \u221e$ K +Inf beforeSuffix $$$\u221e $ K +Inf afterSuffix $$$\u221e$ K +// J does not print the affixes +NaN beforePrefix $$$NaN$ J +NaN afterPrefix $$$ NaN$ J +NaN beforeSuffix $$$NaN $ J +NaN afterSuffix $$$NaN$ J test apply formerly localized patterns begin @@ -689,25 +689,25 @@ test toPattern set locale en begin pattern toPattern breaks -// All of the "S" failures in this section are because of functionally equivalent patterns +// All of the C and S failures in this section are because of functionally equivalent patterns // JDK doesn't support any patterns with padding or both negative prefix and suffix // Breaks ICU4J See ticket 11671 **0,000 **0,000 JK **##0,000 **##0,000 K **###0,000 **###0,000 K -**####0,000 **#,##0,000 KS +**####0,000 **#,##0,000 CKS ###,000. #,000. -0,000 #0,000 S +0,000 #0,000 CS .00 #.00 -000 #000 S -000,000 #,000,000 S +000 #000 CS +000,000 #,000,000 CS pp#,000 pp#,000 -00.## #00.## S +00.## #00.## CS #,#00.025 #,#00.025 // No secondary grouping in JDK #,##,###.02500 #,##,###.02500 K pp#,000;(#) pp#,000;(#,000) K -**####,##,##0.0##;(#) **#,##,##,##0.0##;**(##,##,##0.0##) KS +**####,##,##0.0##;(#) **#,##,##,##0.0##;**(##,##,##0.0##) CKS // No significant digits in JDK @@### @@### K @,@#,### @,@#,### K 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 2e1b416db3..9bd291d9cf 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 @@ -585,11 +585,10 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { return result; } - static final byte[] INT64_BCD = { 9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 7 }; + static final byte[] INT64_BCD = { 9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 8 }; /** * Returns whether or not a Long can fully represent the value stored in this DecimalQuantity. - * Assumes that the DecimalQuantity is positive. */ public boolean fitsInLong() { if (isZero()) { @@ -615,8 +614,8 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { return false; } } - // Exactly equal to max long. - return true; + // Exactly equal to max long plus one. + return isNegative(); } /** diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java index 55e4e18bc5..67fb1e3066 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java @@ -415,12 +415,13 @@ public final class DecimalQuantity_DualStorageBCD extends DecimalQuantity_Abstra @Override public String toString() { - return String.format("", + return String.format("", (lOptPos > 1000 ? "999" : String.valueOf(lOptPos)), lReqPos, rReqPos, (rOptPos < -1000 ? "-999" : String.valueOf(rOptPos)), (usingBytes ? "bytes" : "long"), + (isNegative() ? "-" : ""), toNumberString()); } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java index c0939e39ae..db6917f63c 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java @@ -1869,7 +1869,7 @@ public class DecimalFormat extends NumberFormat { if (enabled) { // Set to a reasonable default value properties.setGroupingSize(3); - properties.setSecondaryGroupingSize(3); + properties.setSecondaryGroupingSize(-1); } else { properties.setGroupingSize(0); properties.setSecondaryGroupingSize(0); diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PatternStringTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PatternStringTest.java index 93afd75376..e5823804f2 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PatternStringTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PatternStringTest.java @@ -42,12 +42,14 @@ public class PatternStringTest { { "0.##", "0.##" }, { "0.00", "0.00" }, { "0.00#", "0.00#" }, + { "0.05", "0.05" }, { "#E0", "#E0" }, { "0E0", "0E0" }, { "#00E00", "#00E00" }, { "#,##0", "#,##0" }, { "#;#", "0;0" }, { "#;-#", "0" }, // ignore a negative prefix pattern of '-' since that is the default + { "pp#,000;(#)", "pp#,000;(#,000)" }, { "**##0", "**##0" }, { "*'x'##0", "*x##0" }, { "a''b0", "a''b0" }, From c940df09e781939d74fb83dcb299cc00c3d1647e Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Sat, 17 Mar 2018 07:24:02 +0000 Subject: [PATCH 044/129] ICU-13644 Adds move operators and related boilerplate to NumberFormatter classes. Includes a handful of other changes made to these files on my branch for ICU-13634 . X-SVN-Rev: 41121 --- icu4c/source/i18n/number_fluent.cpp | 334 +++++++++++++-- icu4c/source/i18n/unicode/numberformatter.h | 395 ++++++++++++++++-- icu4c/source/test/intltest/numbertest.h | 1 + icu4c/source/test/intltest/numbertest_api.cpp | 76 ++++ 4 files changed, 743 insertions(+), 63 deletions(-) diff --git a/icu4c/source/i18n/number_fluent.cpp b/icu4c/source/i18n/number_fluent.cpp index b5ff4532a5..372e6f18d8 100644 --- a/icu4c/source/i18n/number_fluent.cpp +++ b/icu4c/source/i18n/number_fluent.cpp @@ -1,6 +1,7 @@ // © 2017 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html +#include #include "unicode/utypes.h" #if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT @@ -16,7 +17,7 @@ using namespace icu::number; using namespace icu::number::impl; template -Derived NumberFormatterSettings::notation(const Notation& notation) const { +Derived NumberFormatterSettings::notation(const Notation& notation) const & { Derived copy(*this); // NOTE: Slicing is OK. copy.fMacros.notation = notation; @@ -24,7 +25,15 @@ Derived NumberFormatterSettings::notation(const Notation& notation) con } template -Derived NumberFormatterSettings::unit(const icu::MeasureUnit& unit) const { +Derived NumberFormatterSettings::notation(const Notation& notation) && { + Derived move(std::move(*this)); + // NOTE: Slicing is OK. + move.fMacros.notation = notation; + return move; +} + +template +Derived NumberFormatterSettings::unit(const icu::MeasureUnit& unit) const & { Derived copy(*this); // NOTE: Slicing occurs here. However, CurrencyUnit can be restored from MeasureUnit. // TimeUnit may be affected, but TimeUnit is not as relevant to number formatting. @@ -33,21 +42,41 @@ Derived NumberFormatterSettings::unit(const icu::MeasureUnit& unit) con } template -Derived NumberFormatterSettings::adoptUnit(icu::MeasureUnit* unit) const { +Derived NumberFormatterSettings::unit(const icu::MeasureUnit& unit) && { + Derived move(std::move(*this)); + // See comments above about slicing. + move.fMacros.unit = unit; + return move; +} + +template +Derived NumberFormatterSettings::adoptUnit(icu::MeasureUnit* unit) const & { Derived copy(*this); - // Just copy the unit into the MacroProps by value, and delete it since we have ownership. + // Just move the unit into the MacroProps by value, and delete it since we have ownership. // NOTE: Slicing occurs here. However, CurrencyUnit can be restored from MeasureUnit. // TimeUnit may be affected, but TimeUnit is not as relevant to number formatting. if (unit != nullptr) { // TODO: On nullptr, reset to default value? - copy.fMacros.unit = *unit; + copy.fMacros.unit = std::move(*unit); delete unit; } return copy; } template -Derived NumberFormatterSettings::perUnit(const icu::MeasureUnit& perUnit) const { +Derived NumberFormatterSettings::adoptUnit(icu::MeasureUnit* unit) && { + Derived move(std::move(*this)); + // See comments above about slicing and ownership. + if (unit != nullptr) { + // TODO: On nullptr, reset to default value? + move.fMacros.unit = std::move(*unit); + delete unit; + } + return move; +} + +template +Derived NumberFormatterSettings::perUnit(const icu::MeasureUnit& perUnit) const & { Derived copy(*this); // See comments above about slicing. copy.fMacros.perUnit = perUnit; @@ -55,19 +84,39 @@ Derived NumberFormatterSettings::perUnit(const icu::MeasureUnit& perUni } template -Derived NumberFormatterSettings::adoptPerUnit(icu::MeasureUnit* perUnit) const { +Derived NumberFormatterSettings::perUnit(const icu::MeasureUnit& perUnit) && { + Derived copy(*this); + // See comments above about slicing. + copy.fMacros.perUnit = perUnit; + return copy; +} + +template +Derived NumberFormatterSettings::adoptPerUnit(icu::MeasureUnit* perUnit) const & { + Derived move(std::move(*this)); + // See comments above about slicing and ownership. + if (perUnit != nullptr) { + // TODO: On nullptr, reset to default value? + move.fMacros.perUnit = std::move(*perUnit); + delete perUnit; + } + return move; +} + +template +Derived NumberFormatterSettings::adoptPerUnit(icu::MeasureUnit* perUnit) && { Derived copy(*this); // See comments above about slicing and ownership. if (perUnit != nullptr) { // TODO: On nullptr, reset to default value? - copy.fMacros.perUnit = *perUnit; + copy.fMacros.perUnit = std::move(*perUnit); delete perUnit; } return copy; } template -Derived NumberFormatterSettings::rounding(const Rounder& rounder) const { +Derived NumberFormatterSettings::rounding(const Rounder& rounder) const & { Derived copy(*this); // NOTE: Slicing is OK. copy.fMacros.rounder = rounder; @@ -75,7 +124,15 @@ Derived NumberFormatterSettings::rounding(const Rounder& rounder) const } template -Derived NumberFormatterSettings::grouping(const UGroupingStrategy& strategy) const { +Derived NumberFormatterSettings::rounding(const Rounder& rounder) && { + Derived move(std::move(*this)); + // NOTE: Slicing is OK. + move.fMacros.rounder = rounder; + return move; +} + +template +Derived NumberFormatterSettings::grouping(const UGroupingStrategy& strategy) const & { Derived copy(*this); // NOTE: This is slightly different than how the setting is stored in Java // because we want to put it on the stack. @@ -84,68 +141,152 @@ Derived NumberFormatterSettings::grouping(const UGroupingStrategy& stra } template -Derived NumberFormatterSettings::integerWidth(const IntegerWidth& style) const { +Derived NumberFormatterSettings::grouping(const UGroupingStrategy& strategy) && { + Derived move(std::move(*this)); + move.fMacros.grouper = Grouper::forStrategy(strategy); + return move; +} + +template +Derived NumberFormatterSettings::integerWidth(const IntegerWidth& style) const & { Derived copy(*this); copy.fMacros.integerWidth = style; return copy; } template -Derived NumberFormatterSettings::symbols(const DecimalFormatSymbols& symbols) const { +Derived NumberFormatterSettings::integerWidth(const IntegerWidth& style) && { + Derived move(std::move(*this)); + move.fMacros.integerWidth = style; + return move; +} + +template +Derived NumberFormatterSettings::symbols(const DecimalFormatSymbols& symbols) const & { Derived copy(*this); copy.fMacros.symbols.setTo(symbols); return copy; } template -Derived NumberFormatterSettings::adoptSymbols(NumberingSystem* ns) const { +Derived NumberFormatterSettings::symbols(const DecimalFormatSymbols& symbols) && { + Derived move(std::move(*this)); + move.fMacros.symbols.setTo(symbols); + return move; +} + +template +Derived NumberFormatterSettings::adoptSymbols(NumberingSystem* ns) const & { Derived copy(*this); copy.fMacros.symbols.setTo(ns); return copy; } template -Derived NumberFormatterSettings::unitWidth(const UNumberUnitWidth& width) const { +Derived NumberFormatterSettings::adoptSymbols(NumberingSystem* ns) && { + Derived move(std::move(*this)); + move.fMacros.symbols.setTo(ns); + return move; +} + +template +Derived NumberFormatterSettings::unitWidth(const UNumberUnitWidth& width) const & { Derived copy(*this); copy.fMacros.unitWidth = width; return copy; } template -Derived NumberFormatterSettings::sign(const UNumberSignDisplay& style) const { +Derived NumberFormatterSettings::unitWidth(const UNumberUnitWidth& width) && { + Derived move(std::move(*this)); + move.fMacros.unitWidth = width; + return move; +} + +template +Derived NumberFormatterSettings::sign(const UNumberSignDisplay& style) const & { Derived copy(*this); copy.fMacros.sign = style; return copy; } template -Derived NumberFormatterSettings::decimal(const UNumberDecimalSeparatorDisplay& style) const { +Derived NumberFormatterSettings::sign(const UNumberSignDisplay& style) && { + Derived move(std::move(*this)); + move.fMacros.sign = style; + return move; +} + +template +Derived NumberFormatterSettings::decimal(const UNumberDecimalSeparatorDisplay& style) const & { Derived copy(*this); copy.fMacros.decimal = style; return copy; } template -Derived NumberFormatterSettings::padding(const Padder& padder) const { +Derived NumberFormatterSettings::decimal(const UNumberDecimalSeparatorDisplay& style) && { + Derived move(std::move(*this)); + move.fMacros.decimal = style; + return move; +} + +template +Derived NumberFormatterSettings::padding(const Padder& padder) const & { Derived copy(*this); copy.fMacros.padder = padder; return copy; } template -Derived NumberFormatterSettings::threshold(int32_t threshold) const { +Derived NumberFormatterSettings::padding(const Padder& padder) && { + Derived move(std::move(*this)); + move.fMacros.padder = padder; + return move; +} + +template +Derived NumberFormatterSettings::threshold(int32_t threshold) const & { Derived copy(*this); copy.fMacros.threshold = threshold; return copy; } template -Derived NumberFormatterSettings::macros(impl::MacroProps& macros) const { +Derived NumberFormatterSettings::threshold(int32_t threshold) && { + Derived move(std::move(*this)); + move.fMacros.threshold = threshold; + return move; +} + +template +Derived NumberFormatterSettings::macros(const impl::MacroProps& macros) const & { Derived copy(*this); copy.fMacros = macros; return copy; } +template +Derived NumberFormatterSettings::macros(const impl::MacroProps& macros) && { + Derived move(std::move(*this)); + move.fMacros = macros; + return move; +} + +template +Derived NumberFormatterSettings::macros(impl::MacroProps&& macros) const & { + Derived copy(*this); + copy.fMacros = std::move(macros); + return copy; +} + +template +Derived NumberFormatterSettings::macros(impl::MacroProps&& macros) && { + Derived move(std::move(*this)); + move.fMacros = std::move(macros); + return move; +} + // Declare all classes that implement NumberFormatterSettings // See https://stackoverflow.com/a/495056/1407170 template @@ -163,18 +304,82 @@ LocalizedNumberFormatter NumberFormatter::withLocale(const Locale& locale) { return with().locale(locale); } -// Make the child class constructor that takes the parent class call the parent class's copy constructor -UnlocalizedNumberFormatter::UnlocalizedNumberFormatter( - const NumberFormatterSettings& other) - : NumberFormatterSettings(other) { + +template +using NFS = NumberFormatterSettings; +using LNF = LocalizedNumberFormatter; +using UNF = UnlocalizedNumberFormatter; + +UnlocalizedNumberFormatter::UnlocalizedNumberFormatter(const UNF& other) + : UNF(static_cast&>(other)) {} + +UnlocalizedNumberFormatter::UnlocalizedNumberFormatter(const NFS& other) + : NFS(other) { + // No additional fields to assign } -// Make the child class constructor that takes the parent class call the parent class's copy constructor -// For LocalizedNumberFormatter, also copy over the extra fields -LocalizedNumberFormatter::LocalizedNumberFormatter( - const NumberFormatterSettings& other) - : NumberFormatterSettings(other) { - // No additional copies required +UnlocalizedNumberFormatter::UnlocalizedNumberFormatter(UNF&& src) U_NOEXCEPT + : UNF(static_cast&&>(src)) {} + +UnlocalizedNumberFormatter::UnlocalizedNumberFormatter(NFS&& src) U_NOEXCEPT + : NFS(std::move(src)) { + // No additional fields to assign +} + +UnlocalizedNumberFormatter& UnlocalizedNumberFormatter::operator=(const UNF& other) { + NFS::operator=(static_cast&>(other)); + // No additional fields to assign + return *this; +} + +UnlocalizedNumberFormatter& UnlocalizedNumberFormatter::operator=(UNF&& src) U_NOEXCEPT { + NFS::operator=(static_cast&&>(src)); + // No additional fields to assign + return *this; +} + +LocalizedNumberFormatter::LocalizedNumberFormatter(const LNF& other) + : LNF(static_cast&>(other)) {} + +LocalizedNumberFormatter::LocalizedNumberFormatter(const NFS& other) + : NFS(other) { + // No additional fields to assign (let call count and compiled formatter reset to defaults) +} + +LocalizedNumberFormatter::LocalizedNumberFormatter(LocalizedNumberFormatter&& src) U_NOEXCEPT + : LNF(static_cast&&>(src)) {} + +LocalizedNumberFormatter::LocalizedNumberFormatter(NFS&& src) U_NOEXCEPT + : NFS(std::move(src)) { + // For the move operators, copy over the call count and compiled formatter. + auto&& srcAsLNF = static_cast(src); + fCompiled = srcAsLNF.fCompiled; + uprv_memcpy(fUnsafeCallCount, srcAsLNF.fUnsafeCallCount, sizeof(fUnsafeCallCount)); + // Reset the source object to leave it in a safe state. + srcAsLNF.fCompiled = nullptr; + uprv_memset(srcAsLNF.fUnsafeCallCount, 0, sizeof(fUnsafeCallCount)); +} + +LocalizedNumberFormatter& LocalizedNumberFormatter::operator=(const LNF& other) { + NFS::operator=(static_cast&>(other)); + // No additional fields to assign (let call count and compiled formatter reset to defaults) + return *this; +} + +LocalizedNumberFormatter& LocalizedNumberFormatter::operator=(LNF&& src) U_NOEXCEPT { + NFS::operator=(static_cast&&>(src)); + // For the move operators, copy over the call count and compiled formatter. + fCompiled = src.fCompiled; + uprv_memcpy(fUnsafeCallCount, src.fUnsafeCallCount, sizeof(fUnsafeCallCount)); + // Reset the source object to leave it in a safe state. + src.fCompiled = nullptr; + uprv_memset(src.fUnsafeCallCount, 0, sizeof(fUnsafeCallCount)); + return *this; +} + + +LocalizedNumberFormatter::~LocalizedNumberFormatter() { + delete fCompiled; } LocalizedNumberFormatter::LocalizedNumberFormatter(const MacroProps& macros, const Locale& locale) { @@ -182,14 +387,27 @@ LocalizedNumberFormatter::LocalizedNumberFormatter(const MacroProps& macros, con fMacros.locale = locale; } -LocalizedNumberFormatter UnlocalizedNumberFormatter::locale(const Locale& locale) const { +LocalizedNumberFormatter::LocalizedNumberFormatter(MacroProps&& macros, const Locale& locale) { + fMacros = std::move(macros); + fMacros.locale = locale; +} + +LocalizedNumberFormatter UnlocalizedNumberFormatter::locale(const Locale& locale) const & { return LocalizedNumberFormatter(fMacros, locale); } +LocalizedNumberFormatter UnlocalizedNumberFormatter::locale(const Locale& locale) && { + return LocalizedNumberFormatter(std::move(fMacros), locale); +} + SymbolsWrapper::SymbolsWrapper(const SymbolsWrapper& other) { doCopyFrom(other); } +SymbolsWrapper::SymbolsWrapper(SymbolsWrapper&& src) U_NOEXCEPT { + doMoveFrom(std::move(src)); +} + SymbolsWrapper& SymbolsWrapper::operator=(const SymbolsWrapper& other) { if (this == &other) { return *this; @@ -199,6 +417,15 @@ SymbolsWrapper& SymbolsWrapper::operator=(const SymbolsWrapper& other) { return *this; } +SymbolsWrapper& SymbolsWrapper::operator=(SymbolsWrapper&& src) U_NOEXCEPT { + if (this == &src) { + return *this; + } + doCleanup(); + doMoveFrom(std::move(src)); + return *this; +} + SymbolsWrapper::~SymbolsWrapper() { doCleanup(); } @@ -240,6 +467,23 @@ void SymbolsWrapper::doCopyFrom(const SymbolsWrapper& other) { } } +void SymbolsWrapper::doMoveFrom(SymbolsWrapper&& src) { + fType = src.fType; + switch (fType) { + case SYMPTR_NONE: + // No action necessary + break; + case SYMPTR_DFS: + fPtr.dfs = src.fPtr.dfs; + src.fPtr.dfs = nullptr; + break; + case SYMPTR_NS: + fPtr.ns = src.fPtr.ns; + src.fPtr.ns = nullptr; + break; + } +} + void SymbolsWrapper::doCleanup() { switch (fType) { case SYMPTR_NONE: @@ -272,8 +516,22 @@ const NumberingSystem* SymbolsWrapper::getNumberingSystem() const { return fPtr.ns; } -LocalizedNumberFormatter::~LocalizedNumberFormatter() { - delete fCompiled; + +FormattedNumber::FormattedNumber(FormattedNumber&& src) U_NOEXCEPT + : fResults(src.fResults), fErrorCode(src.fErrorCode) { + // Disown src.fResults to prevent double-deletion + src.fResults = nullptr; + src.fErrorCode = U_INVALID_STATE_ERROR; +} + +FormattedNumber& FormattedNumber::operator=(FormattedNumber&& src) U_NOEXCEPT { + delete fResults; + fResults = src.fResults; + fErrorCode = src.fErrorCode; + // Disown src.fResults to prevent double-deletion + src.fResults = nullptr; + src.fErrorCode = U_INVALID_STATE_ERROR; + return *this; } FormattedNumber LocalizedNumberFormatter::formatInt(int64_t value, UErrorCode& status) const { @@ -330,7 +588,7 @@ LocalizedNumberFormatter::formatImpl(impl::NumberFormatterResults* results, UErr static_assert( sizeof(u_atomic_int32_t) <= sizeof(fUnsafeCallCount), "Atomic integer size on this platform exceeds the size allocated by fUnsafeCallCount"); - u_atomic_int32_t* callCount = reinterpret_cast( + auto* callCount = reinterpret_cast( const_cast(this)->fUnsafeCallCount); // A positive value in the atomic int indicates that the data structure is not yet ready; @@ -368,6 +626,16 @@ LocalizedNumberFormatter::formatImpl(impl::NumberFormatterResults* results, UErr } } +const impl::NumberFormatterImpl* LocalizedNumberFormatter::getCompiled() const { + return fCompiled; +} + +int32_t LocalizedNumberFormatter::getCallCount() const { + auto* callCount = reinterpret_cast( + const_cast(this)->fUnsafeCallCount); + return umtx_loadAcquire(*callCount); +} + UnicodeString FormattedNumber::toString() const { if (fResults == nullptr) { // TODO: http://bugs.icu-project.org/trac/ticket/13437 diff --git a/icu4c/source/i18n/unicode/numberformatter.h b/icu4c/source/i18n/unicode/numberformatter.h index 56d67668e7..4f964fb1d6 100644 --- a/icu4c/source/i18n/unicode/numberformatter.h +++ b/icu4c/source/i18n/unicode/numberformatter.h @@ -441,6 +441,11 @@ class ScientificHandler; class CompactHandler; class Modifier; class NumberStringBuilder; +class AffixPatternProvider; +class NumberPropertyMapper; +struct DecimalFormatProperties; +class MultiplierChain; +class CurrencySymbols; } // namespace impl @@ -691,7 +696,14 @@ class U_I18N_API ScientificNotation : public Notation { // Inherit constructor using Notation::Notation; + // Raw constructor for NumberPropertyMapper + ScientificNotation(int8_t fEngineeringInterval, bool fRequireMinInt, impl::digits_t fMinExponentDigits, + UNumberSignDisplay fExponentSignDisplay); + friend class Notation; + + // So that NumberPropertyMapper can create instances + friend class impl::NumberPropertyMapper; }; // Reserve extra names in case they are added as classes in the future: @@ -931,6 +943,7 @@ class U_I18N_API Rounder : public UMemory { struct IncrementSettings { double fIncrement; impl::digits_t fMinFrac; + impl::digits_t fMaxFrac; } increment; // For RND_INCREMENT UCurrencyUsage currencyUsage; // For RND_CURRENCY UErrorCode errorCode; // For RND_ERROR @@ -1011,6 +1024,9 @@ class U_I18N_API Rounder : public UMemory { // To allow NumberFormatterImpl to access isBogus() and other internal methods: friend class impl::NumberFormatterImpl; + // To allow NumberPropertyMapper to create instances from DecimalFormatProperties: + friend class impl::NumberPropertyMapper; + // To give access to apply() and chooseMultiplierAndApply(): friend class impl::MutablePatternModifier; friend class impl::LongNameHandler; @@ -1246,12 +1262,18 @@ class U_I18N_API SymbolsWrapper : public UMemory { /** @internal */ SymbolsWrapper(const SymbolsWrapper &other); + /** @internal */ + SymbolsWrapper(SymbolsWrapper&& src) U_NOEXCEPT; + /** @internal */ ~SymbolsWrapper(); /** @internal */ SymbolsWrapper &operator=(const SymbolsWrapper &other); + /** @internal */ + SymbolsWrapper &operator=(SymbolsWrapper&& src) U_NOEXCEPT; + /** * The provided object is copied, but we do not adopt it. * @internal @@ -1312,6 +1334,8 @@ class U_I18N_API SymbolsWrapper : public UMemory { void doCopyFrom(const SymbolsWrapper &other); + void doMoveFrom(SymbolsWrapper&& src); + void doCleanup(); }; @@ -1321,6 +1345,12 @@ class U_I18N_API Grouper : public UMemory { /** @internal */ static Grouper forStrategy(UGroupingStrategy grouping); + /** + * Resolve the values in Properties to a Grouper object. + * @internal + */ + static Grouper forProperties(const DecimalFormatProperties& properties); + // Future: static Grouper forProperties(DecimalFormatProperties& properties); /** @internal */ @@ -1385,6 +1415,9 @@ class U_I18N_API Padder : public UMemory { /** @internal */ static Padder codePoints(UChar32 cp, int32_t targetWidth, UNumberFormatPadPosition position); + /** @internal */ + static Padder forProperties(const DecimalFormatProperties& properties); + private: UChar32 fWidth; // -3 = error; -2 = bogus; -1 = no padding union { @@ -1433,6 +1466,38 @@ class U_I18N_API Padder : public UMemory { friend class impl::NumberFormatterImpl; }; +/** @internal */ +class U_I18N_API Multiplier : public UMemory { + public: + /** @internal */ + static Multiplier magnitude(int32_t magnitudeMultiplier); + + /** @internal */ + static Multiplier integer(int32_t multiplier); + + private: + int32_t magnitudeMultiplier; + int32_t multiplier; + + Multiplier(int32_t magnitudeMultiplier, int32_t multiplier); + + Multiplier() : magnitudeMultiplier(0), multiplier(1) {} + + bool isValid() const { + return magnitudeMultiplier != 0 || multiplier != 1; + } + + // To allow MacroProps/MicroProps to initialize empty instances: + friend struct MacroProps; + friend struct MicroProps; + + // To allow NumberFormatterImpl to access isBogus() and perform other operations: + friend class impl::NumberFormatterImpl; + + // To allow the helper class MultiplierChain access to private fields: + friend class impl::MultiplierChain; +}; + /** @internal */ struct U_I18N_API MacroProps : public UMemory { /** @internal */ @@ -1470,13 +1535,24 @@ struct U_I18N_API MacroProps : public UMemory { /** @internal */ UNumberDecimalSeparatorDisplay decimal = UNUM_DECIMAL_SEPARATOR_COUNT; + Multiplier multiplier; // = Multiplier(); (bogus) + + AffixPatternProvider* affixProvider = nullptr; // no ownership + /** @internal */ PluralRules *rules = nullptr; // no ownership + /** @internal */ + CurrencySymbols *currencySymbols = nullptr; // no ownership + /** @internal */ int32_t threshold = DEFAULT_THRESHOLD; + + /** @internal */ Locale locale; + // NOTE: Uses default copy and move constructors. + /** * Check all members for errors. * @internal @@ -1525,7 +1601,18 @@ class U_I18N_API NumberFormatterSettings { * @see Notation * @draft ICU 60 */ - Derived notation(const Notation ¬ation) const; + Derived notation(const Notation ¬ation) const &; + + /** + * Overload of notation() for use on an rvalue reference. + * + * @param notation + * The notation strategy to use. + * @return The fluent chain. + * @see #notation + * @draft ICU 62 + */ + Derived notation(const Notation ¬ation) &&; /** * Specifies the unit (unit of measure, currency, or percent) to associate with rendered numbers. @@ -1571,7 +1658,18 @@ class U_I18N_API NumberFormatterSettings { * @see #perUnit * @draft ICU 60 */ - Derived unit(const icu::MeasureUnit &unit) const; + Derived unit(const icu::MeasureUnit &unit) const &; + + /** + * Overload of unit() for use on an rvalue reference. + * + * @param unit + * The unit to render. + * @return The fluent chain. + * @see #unit + * @draft ICU 62 + */ + Derived unit(const icu::MeasureUnit &unit) &&; /** * Like unit(), but takes ownership of a pointer. Convenient for use with the MeasureFormat factory @@ -1588,7 +1686,18 @@ class U_I18N_API NumberFormatterSettings { * @see MeasureUnit * @draft ICU 60 */ - Derived adoptUnit(icu::MeasureUnit *unit) const; + Derived adoptUnit(icu::MeasureUnit *unit) const &; + + /** + * Overload of adoptUnit() for use on an rvalue reference. + * + * @param unit + * The unit to render. + * @return The fluent chain. + * @see #adoptUnit + * @draft ICU 62 + */ + Derived adoptUnit(icu::MeasureUnit *unit) &&; /** * Sets a unit to be used in the denominator. For example, to format "3 m/s", pass METER to the unit and SECOND to @@ -1607,7 +1716,18 @@ class U_I18N_API NumberFormatterSettings { * @see #unit * @draft ICU 61 */ - Derived perUnit(const icu::MeasureUnit &perUnit) const; + Derived perUnit(const icu::MeasureUnit &perUnit) const &; + + /** + * Overload of perUnit() for use on an rvalue reference. + * + * @param perUnit + * The unit to render in the denominator. + * @return The fluent chain. + * @see #perUnit + * @draft ICU 62 + */ + Derived perUnit(const icu::MeasureUnit &perUnit) &&; /** * Like perUnit(), but takes ownership of a pointer. Convenient for use with the MeasureFormat factory @@ -1626,7 +1746,18 @@ class U_I18N_API NumberFormatterSettings { * @see MeasureUnit * @draft ICU 61 */ - Derived adoptPerUnit(icu::MeasureUnit *perUnit) const; + Derived adoptPerUnit(icu::MeasureUnit *perUnit) const &; + + /** + * Overload of adoptPerUnit() for use on an rvalue reference. + * + * @param perUnit + * The unit to render in the denominator. + * @return The fluent chain. + * @see #adoptPerUnit + * @draft ICU 62 + */ + Derived adoptPerUnit(icu::MeasureUnit *perUnit) &&; /** * Specifies the rounding strategy to use when formatting numbers. @@ -1656,10 +1787,20 @@ class U_I18N_API NumberFormatterSettings { * The rounding strategy to use. * @return The fluent chain. * @see Rounder - * @provisional This API might change or be removed in a future release. * @draft ICU 60 */ - Derived rounding(const Rounder &rounder) const; + Derived rounding(const Rounder &rounder) const &; + + /** + * Overload of rounding() for use on an rvalue reference. + * + * @param rounder + * The rounding strategy to use. + * @return The fluent chain. + * @see #rounding + * @draft ICU 62 + */ + Derived rounding(const Rounder& rounder) &&; /** * Specifies the grouping strategy to use when formatting numbers. @@ -1688,7 +1829,19 @@ class U_I18N_API NumberFormatterSettings { * @return The fluent chain. * @draft ICU 61 */ - Derived grouping(const UGroupingStrategy &strategy) const; + Derived grouping(const UGroupingStrategy &strategy) const &; + + /** + * Overload of grouping() for use on an rvalue reference. + * + * @param rounder + * The grouping strategy to use. + * @return The fluent chain. + * @see #grouping + * @provisional This API might change or be removed in a future release. + * @draft ICU 62 + */ + Derived grouping(const UGroupingStrategy& rounder) &&; /** * Specifies the minimum and maximum number of digits to render before the decimal mark. @@ -1714,7 +1867,18 @@ class U_I18N_API NumberFormatterSettings { * @see IntegerWidth * @draft ICU 60 */ - Derived integerWidth(const IntegerWidth &style) const; + Derived integerWidth(const IntegerWidth &style) const &; + + /** + * Overload of integerWidth() for use on an rvalue reference. + * + * @param style + * The integer width to use. + * @return The fluent chain. + * @see #integerWidth + * @draft ICU 62 + */ + Derived integerWidth(const IntegerWidth &style) &&; /** * Specifies the symbols (decimal separator, grouping separator, percent sign, numerals, etc.) to use when rendering @@ -1756,7 +1920,18 @@ class U_I18N_API NumberFormatterSettings { * @see DecimalFormatSymbols * @draft ICU 60 */ - Derived symbols(const DecimalFormatSymbols &symbols) const; + Derived symbols(const DecimalFormatSymbols &symbols) const &; + + /** + * Overload of symbols() for use on an rvalue reference. + * + * @param symbols + * The DecimalFormatSymbols to use. + * @return The fluent chain. + * @see #symbols + * @draft ICU 62 + */ + Derived symbols(const DecimalFormatSymbols &symbols) &&; /** * Specifies that the given numbering system should be used when fetching symbols. @@ -1791,7 +1966,18 @@ class U_I18N_API NumberFormatterSettings { * @see NumberingSystem * @draft ICU 60 */ - Derived adoptSymbols(NumberingSystem *symbols) const; + Derived adoptSymbols(NumberingSystem *symbols) const &; + + /** + * Overload of adoptSymbols() for use on an rvalue reference. + * + * @param symbols + * The NumberingSystem to use. + * @return The fluent chain. + * @see #adoptSymbols + * @draft ICU 62 + */ + Derived adoptSymbols(NumberingSystem *symbols) &&; /** * Sets the width of the unit (measure unit or currency). Most common values: @@ -1818,7 +2004,18 @@ class U_I18N_API NumberFormatterSettings { * @see UNumberUnitWidth * @draft ICU 60 */ - Derived unitWidth(const UNumberUnitWidth &width) const; + Derived unitWidth(const UNumberUnitWidth &width) const &; + + /** + * Overload of unitWidth() for use on an rvalue reference. + * + * @param width + * The width to use when rendering numbers. + * @return The fluent chain. + * @see #unitWidth + * @draft ICU 62 + */ + Derived unitWidth(const UNumberUnitWidth &width) &&; /** * Sets the plus/minus sign display strategy. Most common values: @@ -1839,14 +2036,25 @@ class U_I18N_API NumberFormatterSettings { *

* The default is AUTO sign display. * - * @param width + * @param style * The sign display strategy to use when rendering numbers. * @return The fluent chain * @see UNumberSignDisplay * @provisional This API might change or be removed in a future release. * @draft ICU 60 */ - Derived sign(const UNumberSignDisplay &width) const; + Derived sign(const UNumberSignDisplay &style) const &; + + /** + * Overload of sign() for use on an rvalue reference. + * + * @param style + * The sign display strategy to use when rendering numbers. + * @return The fluent chain. + * @see #sign + * @draft ICU 62 + */ + Derived sign(const UNumberSignDisplay &style) &&; /** * Sets the decimal separator display strategy. This affects integer numbers with no fraction part. Most common @@ -1867,23 +2075,37 @@ class U_I18N_API NumberFormatterSettings { *

* The default is AUTO decimal separator display. * - * @param width + * @param style * The decimal separator display strategy to use when rendering numbers. * @return The fluent chain * @see UNumberDecimalSeparatorDisplay * @provisional This API might change or be removed in a future release. * @draft ICU 60 */ - Derived decimal(const UNumberDecimalSeparatorDisplay &width) const; + Derived decimal(const UNumberDecimalSeparatorDisplay &style) const &; + + /** + * Overload of decimal() for use on an rvalue reference. + * + * @param style + * The decimal separator display strategy to use when rendering numbers. + * @return The fluent chain. + * @see #sign + * @draft ICU 62 + */ + Derived decimal(const UNumberDecimalSeparatorDisplay &style) &&; #ifndef U_HIDE_INTERNAL_API /** - * Set the padding strategy. May be added to ICU 61; see #13338. + * Set the padding strategy. May be added in the future; see #13338. * * @internal ICU 60: This API is ICU internal only. */ - Derived padding(const impl::Padder &padder) const; + Derived padding(const impl::Padder &padder) const &; + + /** @internal */ + Derived padding(const impl::Padder &padder) &&; /** * Internal fluent setter to support a custom regulation threshold. A threshold of 1 causes the data structures to @@ -1891,14 +2113,26 @@ class U_I18N_API NumberFormatterSettings { * * @internal ICU 60: This API is ICU internal only. */ - Derived threshold(int32_t threshold) const; + Derived threshold(int32_t threshold) const &; + + /** @internal */ + Derived threshold(int32_t threshold) &&; /** * Internal fluent setter to overwrite the entire macros object. * * @internal ICU 60: This API is ICU internal only. */ - Derived macros(impl::MacroProps& macros) const; + Derived macros(const impl::MacroProps& macros) const &; + + /** @internal */ + Derived macros(const impl::MacroProps& macros) &&; + + /** @internal */ + Derived macros(impl::MacroProps&& macros) const &; + + /** @internal */ + Derived macros(impl::MacroProps&& macros) &&; #endif /* U_HIDE_INTERNAL_API */ @@ -1917,6 +2151,8 @@ class U_I18N_API NumberFormatterSettings { return U_FAILURE(outErrorCode); } + // NOTE: Uses default copy and move constructors. + protected: impl::MacroProps fMacros; @@ -1954,21 +2190,58 @@ class U_I18N_API UnlocalizedNumberFormatter * @return The fluent chain. * @draft ICU 60 */ - LocalizedNumberFormatter locale(const icu::Locale &locale) const; + LocalizedNumberFormatter locale(const icu::Locale &locale) const &; + + /** + * Overload of locale() for use on an rvalue reference. + * + * @param locale + * The locale to use when loading data for number formatting. + * @return The fluent chain. + * @see #locale + * @draft ICU 62 + */ + LocalizedNumberFormatter locale(const icu::Locale &locale) &&; + + /** + * Default constructor: puts the formatter into a valid but undefined state. + * + * @draft ICU 62 + */ + UnlocalizedNumberFormatter() = default; // Make default copy constructor call the NumberFormatterSettings copy constructor. /** * Returns a copy of this UnlocalizedNumberFormatter. * @draft ICU 60 */ - UnlocalizedNumberFormatter(const UnlocalizedNumberFormatter &other) : UnlocalizedNumberFormatter( - static_cast &>(other)) {} + UnlocalizedNumberFormatter(const UnlocalizedNumberFormatter &other); + + /** + * Move constructor: + * The source UnlocalizedNumberFormatter will be left in a valid but undefined state. + * @draft ICU 62 + */ + UnlocalizedNumberFormatter(UnlocalizedNumberFormatter&& src) U_NOEXCEPT; + + /** + * Copy assignment operator. + * @draft ICU 62 + */ + UnlocalizedNumberFormatter& operator=(const UnlocalizedNumberFormatter& other); + + /** + * Move assignment operator: + * The source UnlocalizedNumberFormatter will be left in a valid but undefined state. + * @draft ICU 62 + */ + UnlocalizedNumberFormatter& operator=(UnlocalizedNumberFormatter&& src) U_NOEXCEPT; private: - UnlocalizedNumberFormatter() = default; + explicit UnlocalizedNumberFormatter(const NumberFormatterSettings& other); explicit UnlocalizedNumberFormatter( - const NumberFormatterSettings &other); + NumberFormatterSettings&& src) U_NOEXCEPT; // To give the fluent setters access to this class's constructor: friend class NumberFormatterSettings; @@ -2026,22 +2299,62 @@ class U_I18N_API LocalizedNumberFormatter * @return A FormattedNumber object; call .toString() to get the string. * @draft ICU 60 */ - FormattedNumber formatDecimal(StringPiece value, UErrorCode &status) const; + FormattedNumber formatDecimal(StringPiece value, UErrorCode& status) const; #ifndef U_HIDE_INTERNAL_API + /** Internal method. * @internal */ FormattedNumber formatDecimalQuantity(const impl::DecimalQuantity& dq, UErrorCode& status) const; + + /** + * Internal method for testing. + * @internal + */ + const impl::NumberFormatterImpl* getCompiled() const; + + /** + * Internal method for testing. + * @internal + */ + int32_t getCallCount() const; + #endif + /** + * Default constructor: puts the formatter into a valid but undefined state. + * + * @draft ICU 62 + */ + LocalizedNumberFormatter() = default; + // Make default copy constructor call the NumberFormatterSettings copy constructor. /** * Returns a copy of this LocalizedNumberFormatter. * @draft ICU 60 */ - LocalizedNumberFormatter(const LocalizedNumberFormatter &other) : LocalizedNumberFormatter( - static_cast &>(other)) {} + LocalizedNumberFormatter(const LocalizedNumberFormatter &other); + + /** + * Move constructor: + * The source LocalizedNumberFormatter will be left in a valid but undefined state. + * @draft ICU 62 + */ + LocalizedNumberFormatter(LocalizedNumberFormatter&& src) U_NOEXCEPT; + + /** + * Copy assignment operator. + * @draft ICU 62 + */ + LocalizedNumberFormatter& operator=(const LocalizedNumberFormatter& other); + + /** + * Move assignment operator: + * The source LocalizedNumberFormatter will be left in a valid but undefined state. + * @draft ICU 62 + */ + LocalizedNumberFormatter& operator=(LocalizedNumberFormatter&& src) U_NOEXCEPT; /** * Destruct this LocalizedNumberFormatter, cleaning up any memory it might own. @@ -2050,15 +2363,19 @@ class U_I18N_API LocalizedNumberFormatter ~LocalizedNumberFormatter(); private: + // Note: fCompiled can't be a LocalPointer because impl::NumberFormatterImpl is defined in an internal + // header, and LocalPointer needs the full class definition in order to delete the instance. const impl::NumberFormatterImpl* fCompiled {nullptr}; char fUnsafeCallCount[8] {}; // internally cast to u_atomic_int32_t - LocalizedNumberFormatter() = default; + explicit LocalizedNumberFormatter(const NumberFormatterSettings& other); - explicit LocalizedNumberFormatter(const NumberFormatterSettings &other); + explicit LocalizedNumberFormatter(NumberFormatterSettings&& src) U_NOEXCEPT; LocalizedNumberFormatter(const impl::MacroProps ¯os, const Locale &locale); + LocalizedNumberFormatter(impl::MacroProps &¯os, const Locale &locale); + /** * This is the core entrypoint to the number formatting pipeline. It performs self-regulation: a static code path * for the first few calls, and compiling a more efficient data structure if called repeatedly. @@ -2160,6 +2477,24 @@ class U_I18N_API FormattedNumber : public UMemory { #endif + // Don't allow copying of FormattedNumber, but moving is okay. + FormattedNumber(const FormattedNumber&) = delete; + FormattedNumber& operator=(const FormattedNumber&) = delete; + + /** + * Move constructor: + * Leaves the source FormattedNumber in an undefined state. + * @draft ICU 62 + */ + FormattedNumber(FormattedNumber&& src) U_NOEXCEPT; + + /** + * Move assignment: + * Leaves the source FormattedNumber in an undefined state. + * @draft ICU 62 + */ + FormattedNumber& operator=(FormattedNumber&& src) U_NOEXCEPT; + /** * Destruct an instance of FormattedNumber, cleaning up any memory it might own. * @draft ICU 60 diff --git a/icu4c/source/test/intltest/numbertest.h b/icu4c/source/test/intltest/numbertest.h index 3c2a62a805..410d9ba316 100644 --- a/icu4c/source/test/intltest/numbertest.h +++ b/icu4c/source/test/intltest/numbertest.h @@ -68,6 +68,7 @@ class NumberFormatterApiTest : public IntlTest { void formatTypes(); void errors(); void validRanges(); + void copyMove(); void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0); diff --git a/icu4c/source/test/intltest/numbertest_api.cpp b/icu4c/source/test/intltest/numbertest_api.cpp index 5b1c46f1e5..e158e7a8c7 100644 --- a/icu4c/source/test/intltest/numbertest_api.cpp +++ b/icu4c/source/test/intltest/numbertest_api.cpp @@ -11,6 +11,7 @@ #include "unicode/numberformatter.h" #include "number_types.h" #include "numbertest.h" +#include "unicode/utypes.h" // Horrible workaround for the lack of a status code in the constructor... UErrorCode globalNumberFormatterApiTestStatus = U_ZERO_ERROR; @@ -77,6 +78,7 @@ void NumberFormatterApiTest::runIndexedTest(int32_t index, UBool exec, const cha TESTCASE_AUTO(formatTypes); TESTCASE_AUTO(errors); TESTCASE_AUTO(validRanges); + TESTCASE_AUTO(copyMove); TESTCASE_AUTO_END; } @@ -1840,6 +1842,80 @@ void NumberFormatterApiTest::validRanges() { VALID_RANGE_ONEARG(integerWidth, IntegerWidth::zeroFillTo(0).truncateAt, -1); } +void NumberFormatterApiTest::copyMove() { + IcuTestErrorCode status(*this, "copyMove"); + + // Default constructors + LocalizedNumberFormatter l1; + assertEquals("Initial behavior", u"10", l1.formatInt(10, status).toString()); + assertEquals("Initial call count", 1, l1.getCallCount()); + assertTrue("Initial compiled", l1.getCompiled() == nullptr); + + // Setup + l1 = NumberFormatter::withLocale("en").unit(NoUnit::percent()).threshold(3); + assertEquals("Initial behavior", u"10%", l1.formatInt(10, status).toString()); + assertEquals("Initial call count", 1, l1.getCallCount()); + assertTrue("Initial compiled", l1.getCompiled() == nullptr); + l1.formatInt(123, status); + assertEquals("Still not compiled", 2, l1.getCallCount()); + assertTrue("Still not compiled", l1.getCompiled() == nullptr); + l1.formatInt(123, status); + assertEquals("Compiled", u"10%", l1.formatInt(10, status).toString()); + assertEquals("Compiled", INT32_MIN, l1.getCallCount()); + assertTrue("Compiled", l1.getCompiled() != nullptr); + + // Copy constructor + LocalizedNumberFormatter l2 = l1; + assertEquals("[constructor] Copy behavior", u"10%", l2.formatInt(10, status).toString()); + assertEquals("[constructor] Copy should not have compiled state", 1, l2.getCallCount()); + assertTrue("[constructor] Copy should not have compiled state", l2.getCompiled() == nullptr); + + // Move constructor + LocalizedNumberFormatter l3 = std::move(l1); + assertEquals("[constructor] Move behavior", u"10%", l3.formatInt(10, status).toString()); + assertEquals("[constructor] Move *should* have compiled state", INT32_MIN, l3.getCallCount()); + assertTrue("[constructor] Move *should* have compiled state", l3.getCompiled() != nullptr); + assertEquals("[constructor] Source should be reset after move", 0, l1.getCallCount()); + assertTrue("[constructor] Source should be reset after move", l1.getCompiled() == nullptr); + + // Reset l1 and l2 to check for macro-props copying for behavior testing + l1 = NumberFormatter::withLocale("en"); + l2 = NumberFormatter::withLocale("en"); + + // Copy assignment + l1 = l3; + assertEquals("[assignment] Copy behavior", u"10%", l1.formatInt(10, status).toString()); + assertEquals("[assignment] Copy should not have compiled state", 1, l1.getCallCount()); + assertTrue("[assignment] Copy should not have compiled state", l1.getCompiled() == nullptr); + + // Move assignment + l2 = std::move(l3); + assertEquals("[assignment] Move behavior", u"10%", l2.formatInt(10, status).toString()); + assertEquals("[assignment] Move *should* have compiled state", INT32_MIN, l2.getCallCount()); + assertTrue("[assignment] Move *should* have compiled state", l2.getCompiled() != nullptr); + assertEquals("[assignment] Source should be reset after move", 0, l3.getCallCount()); + assertTrue("[assignment] Source should be reset after move", l3.getCompiled() == nullptr); + + // Coverage tests for UnlocalizedNumberFormatter + UnlocalizedNumberFormatter u1; + assertEquals("Default behavior", u"10", u1.locale("en").formatInt(10, status).toString()); + u1 = u1.unit(NoUnit::percent()); + assertEquals("Copy assignment", u"10%", u1.locale("en").formatInt(10, status).toString()); + UnlocalizedNumberFormatter u2 = u1; + assertEquals("Copy constructor", u"10%", u2.locale("en").formatInt(10, status).toString()); + UnlocalizedNumberFormatter u3 = std::move(u1); + assertEquals("Move constructor", u"10%", u3.locale("en").formatInt(10, status).toString()); + u1 = NumberFormatter::with(); + u1 = std::move(u2); + assertEquals("Move assignment", u"10%", u1.locale("en").formatInt(10, status).toString()); + + // FormattedNumber move operators + FormattedNumber result = l1.formatInt(10, status); + assertEquals("FormattedNumber move constructor", u"10%", result.toString()); + result = l1.formatInt(20, status); + assertEquals("FormattedNumber move assignment", u"20%", result.toString()); +} + void NumberFormatterApiTest::assertFormatDescending(const UnicodeString &message, const UnlocalizedNumberFormatter &f, From 369f3484d852a0fa18626e97d29b70cc805a7b6d Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Wed, 21 Mar 2018 01:37:18 +0000 Subject: [PATCH 045/129] ICU-13644 Minor refactoring changes in ICU4J. X-SVN-Rev: 41129 --- .../impl/number/DecimalFormatProperties.java | 39 +++++++- .../impl/number/parse/NumberParserImpl.java | 88 +++++++------------ .../icu/impl/number/parse/ParsedNumber.java | 2 +- .../icu/impl/number/parse/ParsingUtils.java | 2 +- ...atcher.java => RequireAffixValidator.java} | 2 +- ...her.java => RequireCurrencyValidator.java} | 4 +- ... => RequireDecimalSeparatorValidator.java} | 10 +-- ...her.java => RequireExponentValidator.java} | 2 +- ...tcher.java => RequireNumberValidator.java} | 2 +- .../src/com/ibm/icu/text/DecimalFormat.java | 2 +- 10 files changed, 82 insertions(+), 71 deletions(-) rename icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/{RequireAffixMatcher.java => RequireAffixValidator.java} (89%) rename icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/{RequireCurrencyMatcher.java => RequireCurrencyValidator.java} (70%) rename icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/{RequireDecimalSeparatorMatcher.java => RequireDecimalSeparatorValidator.java} (62%) rename icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/{RequireExponentMatcher.java => RequireExponentValidator.java} (87%) rename icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/{RequireNumberMatcher.java => RequireNumberValidator.java} (88%) diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalFormatProperties.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalFormatProperties.java index 94ddef3dde..d89f62556d 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalFormatProperties.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalFormatProperties.java @@ -16,7 +16,6 @@ import java.util.ArrayList; import java.util.Map; import com.ibm.icu.impl.number.Padder.PadPosition; -import com.ibm.icu.impl.number.parse.NumberParserImpl.ParseMode; import com.ibm.icu.text.CompactDecimalFormat.CompactStyle; import com.ibm.icu.text.CurrencyPluralInfo; import com.ibm.icu.text.PluralRules; @@ -30,6 +29,40 @@ public class DecimalFormatProperties implements Cloneable, Serializable { /** Auto-generated. */ private static final long serialVersionUID = 4095518955889349243L; + /** Controls the set of rules for parsing a string from the old DecimalFormat API. */ + public static enum ParseMode { + /** + * Lenient mode should be used if you want to accept malformed user input. It will use heuristics + * to attempt to parse through typographical errors in the string. + */ + LENIENT, + + /** + * Strict mode should be used if you want to require that the input is well-formed. More + * specifically, it differs from lenient mode in the following ways: + * + *

+ */ + STRICT, + } + // The setters in this class should NOT have any side-effects or perform any validation. It is // up to the consumer of the property bag to deal with property validation. @@ -44,7 +77,7 @@ public class DecimalFormatProperties implements Cloneable, Serializable { /| or #equals(), but it will NOT catch if you forget to add it to #hashCode(). |/ /+--------------------------------------------------------------------------------------------*/ - private transient Map> compactCustomData; + private transient Map> compactCustomData; // ICU4J-only private transient CompactStyle compactStyle; private transient Currency currency; private transient CurrencyPluralInfo currencyPluralInfo; @@ -55,7 +88,7 @@ public class DecimalFormatProperties implements Cloneable, Serializable { private transient int formatWidth; private transient int groupingSize; private transient int magnitudeMultiplier; - private transient MathContext mathContext; + private transient MathContext mathContext; // ICU4J-only private transient int maximumFractionDigits; private transient int maximumIntegerDigits; private transient int maximumSignificantDigits; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java index 01678fa75b..647cb9bb16 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java @@ -11,6 +11,7 @@ import com.ibm.icu.impl.StringSegment; import com.ibm.icu.impl.number.AffixPatternProvider; import com.ibm.icu.impl.number.CustomSymbolCurrency; import com.ibm.icu.impl.number.DecimalFormatProperties; +import com.ibm.icu.impl.number.DecimalFormatProperties.ParseMode; import com.ibm.icu.impl.number.Grouper; import com.ibm.icu.impl.number.PatternStringParser; import com.ibm.icu.impl.number.PatternStringParser.ParsedPatternInfo; @@ -31,45 +32,10 @@ import com.ibm.icu.util.ULocale; */ public class NumberParserImpl { - // TODO: Find a better place for this enum. - /** Controls the set of rules for parsing a string. */ - public static enum ParseMode { - /** - * Lenient mode should be used if you want to accept malformed user input. It will use heuristics - * to attempt to parse through typographical errors in the string. - */ - LENIENT, - - /** - * Strict mode should be used if you want to require that the input is well-formed. More - * specifically, it differs from lenient mode in the following ways: - * - *
    - *
  • Grouping widths must match the grouping settings. For example, "12,3,45" will fail if the - * grouping width is 3, as in the pattern "#,##0". - *
  • The string must contain a complete prefix and suffix. For example, if the pattern is - * "{#};(#)", then "{123}" or "(123)" would match, but "{123", "123}", and "123" would all fail. - * (The latter strings would be accepted in lenient mode.) - *
  • Whitespace may not appear at arbitrary places in the string. In lenient mode, whitespace - * is allowed to occur arbitrarily before and after prefixes and exponent separators. - *
  • Leading grouping separators are not allowed, as in ",123". - *
  • Minus and plus signs can only appear if specified in the pattern. In lenient mode, a plus - * or minus sign can always precede a number. - *
  • The set of characters that can be interpreted as a decimal or grouping separator is - * smaller. - *
  • If currency parsing is enabled, currencies must only appear where - * specified in either the current pattern string or in a valid pattern string for the current - * locale. For example, if the pattern is "¤0.00", then "$1.23" would match, but "1.23$" would - * fail to match. - *
- */ - STRICT, - } - - public static NumberParserImpl createSimpleParser( - ULocale locale, - String pattern, - int parseFlags) { + /** + * Creates a parser with most default options. Used for testing, not production. + */ + public static NumberParserImpl createSimpleParser(ULocale locale, String pattern, int parseFlags) { NumberParserImpl parser = new NumberParserImpl(parseFlags); DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale); @@ -97,12 +63,15 @@ public class NumberParserImpl { parser.addMatcher(PaddingMatcher.getInstance("@")); parser.addMatcher(ScientificMatcher.getInstance(symbols, grouper)); parser.addMatcher(CurrencyNamesMatcher.getInstance(locale)); - parser.addMatcher(new RequireNumberMatcher()); + parser.addMatcher(new RequireNumberValidator()); parser.freeze(); return parser; } + /** + * Parses the string without returning a NumberParserImpl. Used for testing, not production. + */ public static Number parseStatic( String input, ParsePosition ppos, @@ -120,6 +89,9 @@ public class NumberParserImpl { } } + /** + * Parses the string without returning a NumberParserImpl. Used for testing, not production. + */ public static CurrencyAmount parseStaticCurrency( String input, ParsePosition ppos, @@ -130,16 +102,8 @@ public class NumberParserImpl { parser.parse(input, true, result); if (result.success()) { ppos.setIndex(result.charEnd); - // TODO: Clean this up - Currency currency; - if (result.currencyCode != null) { - currency = Currency.getInstance(result.currencyCode); - } else { - assert 0 != (result.flags & ParsedNumber.FLAG_HAS_DEFAULT_CURRENCY); - currency = CustomSymbolCurrency - .resolve(properties.getCurrency(), symbols.getULocale(), symbols); - } - return new CurrencyAmount(result.getNumber(), currency); + assert result.currencyCode != null; + return new CurrencyAmount(result.getNumber(), Currency.getInstance(result.currencyCode)); } else { ppos.setErrorIndex(result.charEnd); return null; @@ -152,6 +116,20 @@ public class NumberParserImpl { return createParserFromProperties(properties, symbols, false, optimize); } + /** + * Creates a parser from the given DecimalFormatProperties. This is the endpoint used by + * DecimalFormat in production code. + * + * @param properties + * The property bag. + * @param symbols + * The locale's symbols. + * @param parseCurrency + * True to force a currency match and use monetary separators; false otherwise. + * @param optimize + * True to construct the lead-chars; false to disable. + * @return An immutable parser object. + */ public static NumberParserImpl createParserFromProperties( DecimalFormatProperties properties, DecimalFormatSymbols symbols, @@ -244,20 +222,20 @@ public class NumberParserImpl { /// VALIDATORS /// ////////////////// - parser.addMatcher(new RequireNumberMatcher()); + parser.addMatcher(new RequireNumberValidator()); if (isStrict) { - parser.addMatcher(new RequireAffixMatcher()); + parser.addMatcher(new RequireAffixValidator()); } if (isStrict && properties.getMinimumExponentDigits() > 0) { - parser.addMatcher(new RequireExponentMatcher()); + parser.addMatcher(new RequireExponentValidator()); } if (parseCurrency) { - parser.addMatcher(new RequireCurrencyMatcher()); + parser.addMatcher(new RequireCurrencyValidator()); } if (properties.getDecimalPatternMatchRequired()) { boolean patternHasDecimalSeparator = properties.getDecimalSeparatorAlwaysShown() || properties.getMaximumFractionDigits() != 0; - parser.addMatcher(RequireDecimalSeparatorMatcher.getInstance(patternHasDecimalSeparator)); + parser.addMatcher(RequireDecimalSeparatorValidator.getInstance(patternHasDecimalSeparator)); } if (properties.getMultiplier() != null) { // We need to use a math context in order to prevent non-terminating decimal expansions. diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java index 5f49d85244..7dd5ac1c69 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java @@ -52,7 +52,7 @@ public class ParsedNumber { public static final int FLAG_PERCENT = 0x0002; public static final int FLAG_PERMILLE = 0x0004; public static final int FLAG_HAS_EXPONENT = 0x0008; - public static final int FLAG_HAS_DEFAULT_CURRENCY = 0x0010; + // public static final int FLAG_HAS_DEFAULT_CURRENCY = 0x0010; // no longer used public static final int FLAG_HAS_DECIMAL_SEPARATOR = 0x0020; public static final int FLAG_NAN = 0x0040; public static final int FLAG_INFINITY = 0x0080; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsingUtils.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsingUtils.java index 8b3ed1899a..c46a471705 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsingUtils.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsingUtils.java @@ -21,7 +21,7 @@ public class ParsingUtils { public static final int PARSE_FLAG_USE_FULL_AFFIXES = 0x0100; public static final int PARSE_FLAG_EXACT_AFFIX = 0x0200; public static final int PARSE_FLAG_PLUS_SIGN_ALLOWED = 0x0400; - public static final int PARSE_FLAG_OPTIMIZE = 0x1000; + public static final int PARSE_FLAG_OPTIMIZE = 0x0800; public static void putLeadCodePoints(UnicodeSet input, UnicodeSet output) { for (EntryRange range : input.ranges()) { diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RequireAffixMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RequireAffixValidator.java similarity index 89% rename from icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RequireAffixMatcher.java rename to icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RequireAffixValidator.java index f1dbdd4b11..d2c89c8a4c 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RequireAffixMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RequireAffixValidator.java @@ -6,7 +6,7 @@ package com.ibm.icu.impl.number.parse; * @author sffc * */ -public class RequireAffixMatcher extends ValidationMatcher { +public class RequireAffixValidator extends ValidationMatcher { @Override public void postProcess(ParsedNumber result) { diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RequireCurrencyMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RequireCurrencyValidator.java similarity index 70% rename from icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RequireCurrencyMatcher.java rename to icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RequireCurrencyValidator.java index c561f695e5..27966b9fca 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RequireCurrencyMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RequireCurrencyValidator.java @@ -6,11 +6,11 @@ package com.ibm.icu.impl.number.parse; * @author sffc * */ -public class RequireCurrencyMatcher extends ValidationMatcher { +public class RequireCurrencyValidator extends ValidationMatcher { @Override public void postProcess(ParsedNumber result) { - if (result.currencyCode == null && 0 == (result.flags & ParsedNumber.FLAG_HAS_DEFAULT_CURRENCY)) { + if (result.currencyCode == null) { result.flags |= ParsedNumber.FLAG_FAIL; } } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RequireDecimalSeparatorMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RequireDecimalSeparatorValidator.java similarity index 62% rename from icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RequireDecimalSeparatorMatcher.java rename to icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RequireDecimalSeparatorValidator.java index 6c536e8c09..bdda99a053 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RequireDecimalSeparatorMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RequireDecimalSeparatorValidator.java @@ -6,18 +6,18 @@ package com.ibm.icu.impl.number.parse; * @author sffc * */ -public class RequireDecimalSeparatorMatcher extends ValidationMatcher { +public class RequireDecimalSeparatorValidator extends ValidationMatcher { - private static final RequireDecimalSeparatorMatcher A = new RequireDecimalSeparatorMatcher(true); - private static final RequireDecimalSeparatorMatcher B = new RequireDecimalSeparatorMatcher(false); + private static final RequireDecimalSeparatorValidator A = new RequireDecimalSeparatorValidator(true); + private static final RequireDecimalSeparatorValidator B = new RequireDecimalSeparatorValidator(false); private final boolean patternHasDecimalSeparator; - public static RequireDecimalSeparatorMatcher getInstance(boolean patternHasDecimalSeparator) { + public static RequireDecimalSeparatorValidator getInstance(boolean patternHasDecimalSeparator) { return patternHasDecimalSeparator ? A : B; } - private RequireDecimalSeparatorMatcher(boolean patternHasDecimalSeparator) { + private RequireDecimalSeparatorValidator(boolean patternHasDecimalSeparator) { this.patternHasDecimalSeparator = patternHasDecimalSeparator; } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RequireExponentMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RequireExponentValidator.java similarity index 87% rename from icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RequireExponentMatcher.java rename to icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RequireExponentValidator.java index 59586bc64e..a9fd72d3d8 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RequireExponentMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RequireExponentValidator.java @@ -6,7 +6,7 @@ package com.ibm.icu.impl.number.parse; * @author sffc * */ -public class RequireExponentMatcher extends ValidationMatcher { +public class RequireExponentValidator extends ValidationMatcher { @Override public void postProcess(ParsedNumber result) { diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RequireNumberMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RequireNumberValidator.java similarity index 88% rename from icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RequireNumberMatcher.java rename to icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RequireNumberValidator.java index 13d6a31aef..5e588e851a 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RequireNumberMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RequireNumberValidator.java @@ -6,7 +6,7 @@ package com.ibm.icu.impl.number.parse; * @author sffc * */ -public class RequireNumberMatcher extends ValidationMatcher { +public class RequireNumberValidator extends ValidationMatcher { @Override public void postProcess(ParsedNumber result) { diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java index db6917f63c..7906416463 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java @@ -14,11 +14,11 @@ import java.text.ParsePosition; import com.ibm.icu.impl.number.AffixUtils; import com.ibm.icu.impl.number.DecimalFormatProperties; +import com.ibm.icu.impl.number.DecimalFormatProperties.ParseMode; import com.ibm.icu.impl.number.Padder.PadPosition; import com.ibm.icu.impl.number.PatternStringParser; import com.ibm.icu.impl.number.PatternStringUtils; import com.ibm.icu.impl.number.parse.NumberParserImpl; -import com.ibm.icu.impl.number.parse.NumberParserImpl.ParseMode; import com.ibm.icu.impl.number.parse.ParsedNumber; import com.ibm.icu.lang.UCharacter; import com.ibm.icu.math.BigDecimal; From 8b4c36746891fb68ac1547a58f5b54ed20ad59c6 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Wed, 21 Mar 2018 05:17:28 +0000 Subject: [PATCH 046/129] ICU-13644 Property mapper for parsing is building. Refactoring CurrencySymbols a bit. X-SVN-Rev: 41130 --- icu4c/source/i18n/Makefile.in | 2 +- icu4c/source/i18n/number_currencysymbols.cpp | 77 +++++---- icu4c/source/i18n/number_currencysymbols.h | 48 ++---- icu4c/source/i18n/number_decimfmtprops.cpp | 4 +- icu4c/source/i18n/number_decimfmtprops.h | 36 ++++- icu4c/source/i18n/number_formatimpl.cpp | 4 +- icu4c/source/i18n/number_formatimpl.h | 2 +- icu4c/source/i18n/number_mapper.cpp | 6 +- icu4c/source/i18n/number_mapper.h | 8 +- icu4c/source/i18n/numparse_affixes.cpp | 3 +- icu4c/source/i18n/numparse_affixes.h | 6 +- icu4c/source/i18n/numparse_currency.cpp | 8 +- icu4c/source/i18n/numparse_currency.h | 5 +- icu4c/source/i18n/numparse_impl.cpp | 150 +++++++++++++++--- icu4c/source/i18n/numparse_impl.h | 12 +- icu4c/source/i18n/numparse_validators.cpp | 83 ++++++++++ icu4c/source/i18n/numparse_validators.h | 85 ++++++++++ .../source/test/intltest/numbertest_parse.cpp | 28 ++-- .../intltest/numbertest_patternmodifier.cpp | 6 +- 19 files changed, 442 insertions(+), 131 deletions(-) create mode 100644 icu4c/source/i18n/numparse_validators.cpp create mode 100644 icu4c/source/i18n/numparse_validators.h diff --git a/icu4c/source/i18n/Makefile.in b/icu4c/source/i18n/Makefile.in index e0fc77fee0..e249995b47 100644 --- a/icu4c/source/i18n/Makefile.in +++ b/icu4c/source/i18n/Makefile.in @@ -107,7 +107,7 @@ double-conversion.o double-conversion-bignum-dtoa.o double-conversion-bignum.o \ double-conversion-cached-powers.o double-conversion-diy-fp.o double-conversion-fast-dtoa.o \ numparse_stringsegment.o numparse_unisets.o numparse_parsednumber.o \ numparse_impl.o numparse_symbols.o numparse_decimal.o numparse_scientific.o \ -numparse_currency.o numparse_affixes.o numparse_compositions.o \ +numparse_currency.o numparse_affixes.o numparse_compositions.o numparse_validators.o \ number_mapper.o number_multiplier.o number_currencysymbols.o diff --git a/icu4c/source/i18n/number_currencysymbols.cpp b/icu4c/source/i18n/number_currencysymbols.cpp index 0c946f9fc1..051212fb6b 100644 --- a/icu4c/source/i18n/number_currencysymbols.cpp +++ b/icu4c/source/i18n/number_currencysymbols.cpp @@ -18,12 +18,39 @@ using namespace icu::number::impl; CurrencySymbols::CurrencySymbols(CurrencyUnit currency, const Locale& locale, UErrorCode& status) - : fCurrency(currency), fLocaleName(locale.getName(), status) {} + : fCurrency(currency), fLocaleName(locale.getName(), status) { + fCurrencySymbol.setToBogus(); + fIntlCurrencySymbol.setToBogus(); +} + +CurrencySymbols::CurrencySymbols(CurrencyUnit currency, const Locale& locale, + const DecimalFormatSymbols& symbols, UErrorCode& status) + : CurrencySymbols(currency, locale, status) { + // If either of the overrides is present, save it in the local UnicodeString. + if (symbols.isCustomCurrencySymbol()) { + fCurrencySymbol = symbols.getConstSymbol(DecimalFormatSymbols::kCurrencySymbol); + } + if (symbols.isCustomIntlCurrencySymbol()) { + fIntlCurrencySymbol = symbols.getConstSymbol(DecimalFormatSymbols::kIntlCurrencySymbol); + } +} + +const char16_t* CurrencySymbols::getIsoCode() const { + return fCurrency.getISOCurrency(); +} UnicodeString CurrencySymbols::getNarrowCurrencySymbol(UErrorCode& status) const { + // Note: currently no override is available for narrow currency symbol return loadSymbol(UCURR_NARROW_SYMBOL_NAME, status); } +UnicodeString CurrencySymbols::getCurrencySymbol(UErrorCode& status) const { + if (!fCurrencySymbol.isBogus()) { + return fCurrencySymbol; + } + return loadSymbol(UCURR_SYMBOL_NAME, status); +} + UnicodeString CurrencySymbols::loadSymbol(UCurrNameStyle selector, UErrorCode& status) const { UBool ignoredIsChoiceFormatFillIn = FALSE; int32_t symbolLen = 0; @@ -38,6 +65,14 @@ UnicodeString CurrencySymbols::loadSymbol(UCurrNameStyle selector, UErrorCode& s return UnicodeString(TRUE, symbol, symbolLen); } +UnicodeString CurrencySymbols::getIntlCurrencySymbol(UErrorCode&) const { + if (!fIntlCurrencySymbol.isBogus()) { + return fIntlCurrencySymbol; + } + // Readonly-aliasing char16_t* constructor: + return UnicodeString(TRUE, fCurrency.getISOCurrency(), 3); +} + UnicodeString CurrencySymbols::getPluralName(StandardPlural::Form plural, UErrorCode& status) const { UBool isChoiceFormat = FALSE; int32_t symbolLen = 0; @@ -53,46 +88,6 @@ UnicodeString CurrencySymbols::getPluralName(StandardPlural::Form plural, UError } -CurrencyDataSymbols::CurrencyDataSymbols(CurrencyUnit currency, const Locale& locale, UErrorCode& status) - : CurrencySymbols(currency, locale, status) {} - -UnicodeString CurrencyDataSymbols::getCurrencySymbol(UErrorCode& status) const { - return loadSymbol(UCURR_SYMBOL_NAME, status); -} - -UnicodeString CurrencyDataSymbols::getIntlCurrencySymbol(UErrorCode&) const { - // Readonly-aliasing char16_t* constructor: - return UnicodeString(TRUE, fCurrency.getISOCurrency(), 3); -} - - -CurrencyCustomSymbols::CurrencyCustomSymbols(CurrencyUnit currency, const Locale& locale, - const DecimalFormatSymbols& symbols, UErrorCode& status) - : CurrencySymbols(currency, locale, status) { - // Hit the data bundle if the DecimalFormatSymbols version is not custom. - // Note: the CurrencyDataSymbols implementation waits to hit the data bundle until requested. - if (symbols.isCustomCurrencySymbol()) { - fCurrencySymbol = symbols.getConstSymbol(DecimalFormatSymbols::kCurrencySymbol); - } else { - fCurrencySymbol = loadSymbol(UCURR_SYMBOL_NAME, status); - } - if (symbols.isCustomIntlCurrencySymbol()) { - fIntlCurrencySymbol = symbols.getConstSymbol(DecimalFormatSymbols::kIntlCurrencySymbol); - } else { - // UnicodeString copy constructor since we don't know about the lifetime of the CurrencyUnit - fIntlCurrencySymbol = UnicodeString(currency.getISOCurrency(), 3); - } -} - -UnicodeString CurrencyCustomSymbols::getCurrencySymbol(UErrorCode&) const { - return fCurrencySymbol; -} - -UnicodeString CurrencyCustomSymbols::getIntlCurrencySymbol(UErrorCode&) const { - return fIntlCurrencySymbol; -} - - CurrencyUnit icu::number::impl::resolveCurrency(const DecimalFormatProperties& properties, const Locale& locale, UErrorCode& status) { diff --git a/icu4c/source/i18n/number_currencysymbols.h b/icu4c/source/i18n/number_currencysymbols.h index 63810b0043..eab1a661db 100644 --- a/icu4c/source/i18n/number_currencysymbols.h +++ b/icu4c/source/i18n/number_currencysymbols.h @@ -15,55 +15,37 @@ U_NAMESPACE_BEGIN namespace number { namespace impl { -class CurrencySymbols { +class CurrencySymbols : public UMemory { public: CurrencySymbols() = default; // default constructor: leaves class in valid but undefined state - explicit CurrencySymbols(CurrencyUnit currency, const Locale& locale, UErrorCode& status); + /** Creates an instance in which all symbols are loaded from data. */ + CurrencySymbols(CurrencyUnit currency, const Locale& locale, UErrorCode& status); - virtual UnicodeString getCurrencySymbol(UErrorCode& status) const = 0; + /** Creates an instance in which some symbols might be pre-populated. */ + CurrencySymbols(CurrencyUnit currency, const Locale& locale, const DecimalFormatSymbols& symbols, + UErrorCode& status); - virtual UnicodeString getIntlCurrencySymbol(UErrorCode& status) const = 0; + const char16_t* getIsoCode() const; - // Put narrow and plural symbols in the base class since there is no API for overriding them UnicodeString getNarrowCurrencySymbol(UErrorCode& status) const; + UnicodeString getCurrencySymbol(UErrorCode& status) const; + + UnicodeString getIntlCurrencySymbol(UErrorCode& status) const; + UnicodeString getPluralName(StandardPlural::Form plural, UErrorCode& status) const; protected: + // Required fields: CurrencyUnit fCurrency; CharString fLocaleName; - UnicodeString loadSymbol(UCurrNameStyle selector, UErrorCode& status) const; -}; - - -class CurrencyDataSymbols : public CurrencySymbols, public UMemory { - public: - CurrencyDataSymbols() = default; // default constructor: leaves class in valid but undefined state - - CurrencyDataSymbols(CurrencyUnit currency, const Locale& locale, UErrorCode& status); - - UnicodeString getCurrencySymbol(UErrorCode& status) const U_OVERRIDE; - - UnicodeString getIntlCurrencySymbol(UErrorCode& status) const U_OVERRIDE; -}; - - -class CurrencyCustomSymbols : public CurrencySymbols, public UMemory { - public: - CurrencyCustomSymbols() = default; // default constructor: leaves class in valid but undefined state - - CurrencyCustomSymbols(CurrencyUnit currency, const Locale& locale, const DecimalFormatSymbols& symbols, - UErrorCode& status); - - UnicodeString getCurrencySymbol(UErrorCode& status) const U_OVERRIDE; - - UnicodeString getIntlCurrencySymbol(UErrorCode& status) const U_OVERRIDE; - - private: + // Optional fields: UnicodeString fCurrencySymbol; UnicodeString fIntlCurrencySymbol; + + UnicodeString loadSymbol(UCurrNameStyle selector, UErrorCode& status) const; }; diff --git a/icu4c/source/i18n/number_decimfmtprops.cpp b/icu4c/source/i18n/number_decimfmtprops.cpp index 425f11490c..54fd6a1ef1 100644 --- a/icu4c/source/i18n/number_decimfmtprops.cpp +++ b/icu4c/source/i18n/number_decimfmtprops.cpp @@ -43,7 +43,7 @@ void DecimalFormatProperties::clear() { padString.setToBogus(); parseCaseSensitive = false; parseIntegerOnly = false; - parseLenient = false; + parseMode.nullify(); parseNoExponent = false; parseToBigDecimal = false; parseAllInput = UNUM_MAYBE; @@ -86,7 +86,7 @@ bool DecimalFormatProperties::operator==(const DecimalFormatProperties &other) c eq = eq && padString == other.padString; eq = eq && parseCaseSensitive == other.parseCaseSensitive; eq = eq && parseIntegerOnly == other.parseIntegerOnly; - eq = eq && parseLenient == other.parseLenient; + eq = eq && parseMode == other.parseMode; eq = eq && parseNoExponent == other.parseNoExponent; eq = eq && parseToBigDecimal == other.parseToBigDecimal; eq = eq && parseAllInput == other.parseAllInput; diff --git a/icu4c/source/i18n/number_decimfmtprops.h b/icu4c/source/i18n/number_decimfmtprops.h index 890aa1dee8..6632dfc5a2 100644 --- a/icu4c/source/i18n/number_decimfmtprops.h +++ b/icu4c/source/i18n/number_decimfmtprops.h @@ -51,6 +51,40 @@ struct U_I18N_API CopyableLocalPointer { } }; +/** Controls the set of rules for parsing a string from the old DecimalFormat API. */ +enum ParseMode { + /** + * Lenient mode should be used if you want to accept malformed user input. It will use heuristics + * to attempt to parse through typographical errors in the string. + */ + PARSE_MODE_LENIENT, + + /** + * Strict mode should be used if you want to require that the input is well-formed. More + * specifically, it differs from lenient mode in the following ways: + * + *
    + *
  • Grouping widths must match the grouping settings. For example, "12,3,45" will fail if the + * grouping width is 3, as in the pattern "#,##0". + *
  • The string must contain a complete prefix and suffix. For example, if the pattern is + * "{#};(#)", then "{123}" or "(123)" would match, but "{123", "123}", and "123" would all fail. + * (The latter strings would be accepted in lenient mode.) + *
  • Whitespace may not appear at arbitrary places in the string. In lenient mode, whitespace + * is allowed to occur arbitrarily before and after prefixes and exponent separators. + *
  • Leading grouping separators are not allowed, as in ",123". + *
  • Minus and plus signs can only appear if specified in the pattern. In lenient mode, a plus + * or minus sign can always precede a number. + *
  • The set of characters that can be interpreted as a decimal or grouping separator is + * smaller. + *
  • If currency parsing is enabled, currencies must only appear where + * specified in either the current pattern string or in a valid pattern string for the current + * locale. For example, if the pattern is "¤0.00", then "$1.23" would match, but "1.23$" would + * fail to match. + *
+ */ + PARSE_MODE_STRICT, +}; + // Exported as U_I18N_API because it is needed for the unit test PatternStringTest struct U_I18N_API DecimalFormatProperties { @@ -82,7 +116,7 @@ struct U_I18N_API DecimalFormatProperties { UnicodeString padString; bool parseCaseSensitive; bool parseIntegerOnly; - bool parseLenient; + NullableValue parseMode; bool parseNoExponent; bool parseToBigDecimal; UNumberFormatAttributeValue parseAllInput; // ICU4C-only diff --git a/icu4c/source/i18n/number_formatimpl.cpp b/icu4c/source/i18n/number_formatimpl.cpp index 3edf73e197..102a786a22 100644 --- a/icu4c/source/i18n/number_formatimpl.cpp +++ b/icu4c/source/i18n/number_formatimpl.cpp @@ -210,8 +210,8 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe, // Used by the DecimalFormat code path currencySymbols = macros.currencySymbols; } else { - fWarehouse.fCurrencyDataSymbols = {currency, macros.locale, status}; - currencySymbols = &fWarehouse.fCurrencyDataSymbols; + fWarehouse.fCurrencySymbols = {currency, macros.locale, status}; + currencySymbols = &fWarehouse.fCurrencySymbols; } UNumberUnitWidth unitWidth = UNUM_UNIT_WIDTH_SHORT; if (macros.unitWidth != UNUM_UNIT_WIDTH_COUNT) { diff --git a/icu4c/source/i18n/number_formatimpl.h b/icu4c/source/i18n/number_formatimpl.h index dff500ab94..13c22aec75 100644 --- a/icu4c/source/i18n/number_formatimpl.h +++ b/icu4c/source/i18n/number_formatimpl.h @@ -62,7 +62,7 @@ class NumberFormatterImpl : public UMemory { // Value objects possibly used by the number formatting pipeline: struct Warehouse { - CurrencyDataSymbols fCurrencyDataSymbols; + CurrencySymbols fCurrencySymbols; } fWarehouse; diff --git a/icu4c/source/i18n/number_mapper.cpp b/icu4c/source/i18n/number_mapper.cpp index aeaa647d01..8477401101 100644 --- a/icu4c/source/i18n/number_mapper.cpp +++ b/icu4c/source/i18n/number_mapper.cpp @@ -86,10 +86,8 @@ MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& propert // NOTE: Slicing is OK. macros.unit = currency; // NOLINT } - if (symbols.isCustomCurrencySymbol() || symbols.isCustomIntlCurrencySymbol()) { - warehouse.currencyCustomSymbols = {currency, locale, symbols, status}; - macros.currencySymbols = &warehouse.currencyCustomSymbols; - } + warehouse.currencySymbols = {currency, locale, symbols, status}; + macros.currencySymbols = &warehouse.currencySymbols; /////////////////////// // ROUNDING STRATEGY // diff --git a/icu4c/source/i18n/number_mapper.h b/icu4c/source/i18n/number_mapper.h index 7c08eecfb6..5183b1195f 100644 --- a/icu4c/source/i18n/number_mapper.h +++ b/icu4c/source/i18n/number_mapper.h @@ -29,6 +29,12 @@ class PropertiesAffixPatternProvider : public AffixPatternProvider, public UMemo void setTo(const DecimalFormatProperties& properties, UErrorCode& status); + PropertiesAffixPatternProvider() = default; // puts instance in valid but undefined state + + PropertiesAffixPatternProvider(const DecimalFormatProperties& properties, UErrorCode& status) { + setTo(properties, status); + } + // AffixPatternProvider Methods: char16_t charAt(int32_t flags, int32_t i) const U_OVERRIDE; @@ -106,7 +112,7 @@ class CurrencyPluralInfoAffixProvider : public AffixPatternProvider, public UMem struct DecimalFormatWarehouse { PropertiesAffixPatternProvider propertiesAPP; CurrencyPluralInfoAffixProvider currencyPluralInfoAPP; - CurrencyCustomSymbols currencyCustomSymbols; + CurrencySymbols currencySymbols; }; diff --git a/icu4c/source/i18n/numparse_affixes.cpp b/icu4c/source/i18n/numparse_affixes.cpp index 97ba4a1c66..e4dd8b7662 100644 --- a/icu4c/source/i18n/numparse_affixes.cpp +++ b/icu4c/source/i18n/numparse_affixes.cpp @@ -190,8 +190,7 @@ NumberParseMatcher& AffixTokenMatcherWarehouse::permille() { } NumberParseMatcher& AffixTokenMatcherWarehouse::currency(UErrorCode& status) { - return fCurrency = {{fSetupData->locale, status}, - {fSetupData->currencyCode, fSetupData->currency1, fSetupData->currency2}}; + return fCurrency = {{fSetupData->locale, status}, {fSetupData->currencySymbols, status}}; } IgnorablesMatcher& AffixTokenMatcherWarehouse::ignorables() { diff --git a/icu4c/source/i18n/numparse_affixes.h b/icu4c/source/i18n/numparse_affixes.h index 59789e0395..8a09983c9e 100644 --- a/icu4c/source/i18n/numparse_affixes.h +++ b/icu4c/source/i18n/numparse_affixes.h @@ -11,6 +11,7 @@ #include "numparse_symbols.h" #include "numparse_currency.h" #include "number_affixutils.h" +#include "number_currencysymbols.h" #include @@ -23,6 +24,7 @@ class AffixPatternMatcher; using ::icu::number::impl::AffixPatternProvider; using ::icu::number::impl::TokenConsumer; +using ::icu::number::impl::CurrencySymbols; class CodePointMatcher : public NumberParseMatcher, public UMemory { @@ -73,9 +75,7 @@ class CodePointMatcherWarehouse : public UMemory { struct AffixTokenMatcherSetupData { - const UChar* currencyCode; - const UnicodeString& currency1; - const UnicodeString& currency2; + const CurrencySymbols& currencySymbols; const DecimalFormatSymbols& dfs; IgnorablesMatcher& ignorables; const Locale& locale; diff --git a/icu4c/source/i18n/numparse_currency.cpp b/icu4c/source/i18n/numparse_currency.cpp index b3a317ef71..064ba73fc0 100644 --- a/icu4c/source/i18n/numparse_currency.cpp +++ b/icu4c/source/i18n/numparse_currency.cpp @@ -75,10 +75,10 @@ UnicodeString CurrencyNamesMatcher::toString() const { } -CurrencyCustomMatcher::CurrencyCustomMatcher(const char16_t* currencyCode, const UnicodeString& currency1, - const UnicodeString& currency2) - : fCurrency1(currency1), fCurrency2(currency2) { - utils::copyCurrencyCode(fCurrencyCode, currencyCode); +CurrencyCustomMatcher::CurrencyCustomMatcher(const CurrencySymbols& currencySymbols, UErrorCode& status) + : fCurrency1(currencySymbols.getCurrencySymbol(status)), + fCurrency2(currencySymbols.getIntlCurrencySymbol(status)) { + utils::copyCurrencyCode(fCurrencyCode, currencySymbols.getIsoCode()); } bool CurrencyCustomMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCode&) const { diff --git a/icu4c/source/i18n/numparse_currency.h b/icu4c/source/i18n/numparse_currency.h index 3d8cb3a2bf..dd0490a9af 100644 --- a/icu4c/source/i18n/numparse_currency.h +++ b/icu4c/source/i18n/numparse_currency.h @@ -10,10 +10,12 @@ #include "numparse_types.h" #include "numparse_compositions.h" #include "charstr.h" +#include "number_currencysymbols.h" U_NAMESPACE_BEGIN namespace numparse { namespace impl { +using ::icu::number::impl::CurrencySymbols; /** * Matches currencies according to all available strings in locale data. @@ -46,8 +48,7 @@ class CurrencyCustomMatcher : public NumberParseMatcher, public UMemory { public: CurrencyCustomMatcher() = default; // WARNING: Leaves the object in an unusable state - CurrencyCustomMatcher(const char16_t* currencyCode, const UnicodeString& currency1, - const UnicodeString& currency2); + CurrencyCustomMatcher(const CurrencySymbols& currencySymbols, UErrorCode& status); bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; diff --git a/icu4c/source/i18n/numparse_impl.cpp b/icu4c/source/i18n/numparse_impl.cpp index 1d29aee758..32d904f99a 100644 --- a/icu4c/source/i18n/numparse_impl.cpp +++ b/icu4c/source/i18n/numparse_impl.cpp @@ -9,6 +9,9 @@ // Helpful in toString methods and elsewhere. #define UNISTR_FROM_STRING_EXPLICIT +#include +#include +#include #include "number_types.h" #include "number_patternstring.h" #include "numparse_types.h" @@ -16,11 +19,9 @@ #include "numparse_symbols.h" #include "numparse_decimal.h" #include "unicode/numberformatter.h" - -#include -#include -#include #include "cstr.h" +#include "number_mapper.h" +#include "numparse_unisets.h" using namespace icu; using namespace icu::number; @@ -33,22 +34,23 @@ NumberParserImpl* NumberParserImpl::createSimpleParser(const Locale& locale, const UnicodeString& patternString, parse_flags_t parseFlags, UErrorCode& status) { - auto* parser = new NumberParserImpl(parseFlags, true); + LocalPointer parser(new NumberParserImpl(parseFlags, true)); DecimalFormatSymbols symbols(locale, status); parser->fLocalMatchers.ignorables = {unisets::DEFAULT_IGNORABLES}; IgnorablesMatcher& ignorables = parser->fLocalMatchers.ignorables; - const UChar currencyCode[] = u"USD"; - UnicodeString currency1(u"IU$"); - UnicodeString currency2(u"ICU"); + DecimalFormatSymbols dfs(locale, status); + dfs.setSymbol(DecimalFormatSymbols::kCurrencySymbol, u"IU$", status); + dfs.setSymbol(DecimalFormatSymbols::kIntlCurrencySymbol, u"ICU", status); + CurrencySymbols currencySymbols({u"ICU", status}, locale, dfs, status); ParsedPatternInfo patternInfo; PatternParser::parseToPatternInfo(patternString, patternInfo, status); // The following statements set up the affix matchers. AffixTokenMatcherSetupData affixSetupData = { - currencyCode, currency1, currency2, symbols, ignorables, locale}; + currencySymbols, symbols, ignorables, locale}; parser->fLocalMatchers.affixTokenMatcherWarehouse = {&affixSetupData}; parser->fLocalMatchers.affixMatcherWarehouse = {&parser->fLocalMatchers.affixTokenMatcherWarehouse}; parser->fLocalMatchers.affixMatcherWarehouse.createAffixMatchers( @@ -71,17 +73,129 @@ NumberParserImpl::createSimpleParser(const Locale& locale, const UnicodeString& // parser.addMatcher(new RequireNumberMatcher()); parser->freeze(); - return parser; + return parser.orphan(); } -//NumberParserImpl* -//NumberParserImpl::createParserFromProperties(const number::impl::DecimalFormatProperties& properties, -// DecimalFormatSymbols symbols, bool parseCurrency, -// bool optimize, UErrorCode& status) { -// // TODO -// status = U_UNSUPPORTED_ERROR; -// return nullptr; -//} +NumberParserImpl* +NumberParserImpl::createParserFromProperties(const number::impl::DecimalFormatProperties& properties, + const DecimalFormatSymbols& symbols, bool parseCurrency, + bool computeLeads, UErrorCode& status) { + Locale locale = symbols.getLocale(); + PropertiesAffixPatternProvider patternInfo(properties, status); + CurrencyUnit currency = resolveCurrency(properties, locale, status); + CurrencySymbols currencySymbols(currency, locale, symbols, status); + bool isStrict = properties.parseMode.getOrDefault(PARSE_MODE_STRICT) == PARSE_MODE_STRICT; + Grouper grouper = Grouper::forProperties(properties); + int parseFlags = 0; + // Fraction grouping is disabled by default because it has never been supported in DecimalFormat + parseFlags |= PARSE_FLAG_FRACTION_GROUPING_DISABLED; + if (!properties.parseCaseSensitive) { + parseFlags |= PARSE_FLAG_IGNORE_CASE; + } + if (properties.parseIntegerOnly) { + parseFlags |= PARSE_FLAG_INTEGER_ONLY; + } + if (properties.signAlwaysShown) { + parseFlags |= PARSE_FLAG_PLUS_SIGN_ALLOWED; + } + if (isStrict) { + parseFlags |= PARSE_FLAG_STRICT_GROUPING_SIZE; + parseFlags |= PARSE_FLAG_STRICT_SEPARATORS; + parseFlags |= PARSE_FLAG_USE_FULL_AFFIXES; + parseFlags |= PARSE_FLAG_EXACT_AFFIX; + } else { + parseFlags |= PARSE_FLAG_INCLUDE_UNPAIRED_AFFIXES; + } + if (grouper.getPrimary() <= 0) { + parseFlags |= PARSE_FLAG_GROUPING_DISABLED; + } + if (parseCurrency || patternInfo.hasCurrencySign()) { + parseFlags |= PARSE_FLAG_MONETARY_SEPARATORS; + } + if (computeLeads) { + parseFlags |= PARSE_FLAG_OPTIMIZE; + } + IgnorablesMatcher ignorables(isStrict ? unisets::DEFAULT_IGNORABLES : unisets::STRICT_IGNORABLES); + + LocalPointer parser(new NumberParserImpl(parseFlags, status)); + + ////////////////////// + /// AFFIX MATCHERS /// + ////////////////////// + + // The following statements set up the affix matchers. + AffixTokenMatcherSetupData affixSetupData = { + currencySymbols, + symbols, + ignorables, + locale}; + parser->fLocalMatchers.affixTokenMatcherWarehouse = {&affixSetupData}; + parser->fLocalMatchers.affixMatcherWarehouse = {&parser->fLocalMatchers.affixTokenMatcherWarehouse}; + parser->fLocalMatchers.affixMatcherWarehouse.createAffixMatchers( + patternInfo, *parser, ignorables, parseFlags, status); + + //////////////////////// + /// CURRENCY MATCHER /// + //////////////////////// + + if (parseCurrency || patternInfo.hasCurrencySign()) { + parser->addMatcher(parser->fLocalMatchers.currencyCustom = {currencySymbols, status}); + parser->addMatcher(parser->fLocalMatchers.currencyNames = {locale, status}); + } + + /////////////////////////////// + /// OTHER STANDARD MATCHERS /// + /////////////////////////////// + + if (!isStrict) { + parser->addMatcher(parser->fLocalMatchers.plusSign = {symbols, false}); + parser->addMatcher(parser->fLocalMatchers.minusSign = {symbols, false}); + parser->addMatcher(parser->fLocalMatchers.nan = {symbols}); + parser->addMatcher(parser->fLocalMatchers.percent = {symbols}); + parser->addMatcher(parser->fLocalMatchers.permille = {symbols}); + } + parser->addMatcher(parser->fLocalMatchers.infinity = {symbols}); + UnicodeString padString = properties.padString; + if (!padString.isBogus() && !ignorables.getSet()->contains(padString)) { + parser->addMatcher(parser->fLocalMatchers.padding = {padString}); + } + parser->addMatcher(parser->fLocalMatchers.ignorables); + parser->addMatcher(parser->fLocalMatchers.decimal = {symbols, grouper, parseFlags}); + if (!properties.parseNoExponent) { + parser->addMatcher(parser->fLocalMatchers.scientific = {symbols, grouper}); + } + + ////////////////// + /// VALIDATORS /// + ////////////////// + + parser->addMatcher(parser->fLocalValidators.number = {}); + if (isStrict) { + parser->addMatcher(parser->fLocalValidators.affix = {}); + } + if (isStrict && properties.minimumExponentDigits > 0) { + parser->addMatcher(parser->fLocalValidators.exponent = {}); + } + if (parseCurrency) { + parser->addMatcher(parser->fLocalValidators.currency = {}); + } + if (properties.decimalPatternMatchRequired) { + bool patternHasDecimalSeparator = + properties.decimalSeparatorAlwaysShown || properties.maximumFractionDigits != 0; + parser->addMatcher(parser->fLocalValidators.decimalSeparator = {patternHasDecimalSeparator}); + } + + // TODO: MULTIPLIER +// if (properties.getMultiplier() != null) { +// // We need to use a math context in order to prevent non-terminating decimal expansions. +// // This is only used when dividing by the multiplier. +// parser.addMatcher(new MultiplierHandler(properties.getMultiplier(), +// RoundingUtils.getMathContextOr34Digits(properties))); +// } + + parser->freeze(); + return parser.orphan(); +} NumberParserImpl::NumberParserImpl(parse_flags_t parseFlags, bool computeLeads) : fParseFlags(parseFlags), fComputeLeads(computeLeads) { diff --git a/icu4c/source/i18n/numparse_impl.h b/icu4c/source/i18n/numparse_impl.h index 901c226a13..d05d7a24ac 100644 --- a/icu4c/source/i18n/numparse_impl.h +++ b/icu4c/source/i18n/numparse_impl.h @@ -15,6 +15,8 @@ #include "numparse_currency.h" #include "numparse_affixes.h" #include "number_decimfmtprops.h" +#include "unicode/localpointer.h" +#include "numparse_validators.h" U_NAMESPACE_BEGIN namespace numparse { namespace impl { @@ -27,7 +29,7 @@ class NumberParserImpl : public MutableMatcherCollection { parse_flags_t parseFlags, UErrorCode& status); static NumberParserImpl* createParserFromProperties( - const number::impl::DecimalFormatProperties& properties, DecimalFormatSymbols symbols, + const number::impl::DecimalFormatProperties& properties, const DecimalFormatSymbols& symbols, bool parseCurrency, bool optimize, UErrorCode& status); void addMatcher(NumberParseMatcher& matcher) override; @@ -64,9 +66,17 @@ class NumberParserImpl : public MutableMatcherCollection { DecimalMatcher decimal; ScientificMatcher scientific; CurrencyNamesMatcher currencyNames; + CurrencyCustomMatcher currencyCustom; AffixMatcherWarehouse affixMatcherWarehouse; AffixTokenMatcherWarehouse affixTokenMatcherWarehouse; } fLocalMatchers; + struct { + RequireAffixValidator affix; + RequireCurrencyValidator currency; + RequireDecimalSeparatorValidator decimalSeparator; + RequireExponentValidator exponent; + RequireNumberValidator number; + } fLocalValidators; NumberParserImpl(parse_flags_t parseFlags, bool computeLeads); diff --git a/icu4c/source/i18n/numparse_validators.cpp b/icu4c/source/i18n/numparse_validators.cpp new file mode 100644 index 0000000000..724b0cf031 --- /dev/null +++ b/icu4c/source/i18n/numparse_validators.cpp @@ -0,0 +1,83 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT + +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + +#include "numparse_types.h" +#include "numparse_validators.h" +#include "numparse_unisets.h" + +using namespace icu; +using namespace icu::numparse; +using namespace icu::numparse::impl; + + +void RequireAffixValidator::postProcess(ParsedNumber& result) const { + if (result.prefix.isBogus() || result.suffix.isBogus()) { + // We saw a prefix or a suffix but not both. Fail the parse. + result.flags |= FLAG_FAIL; + } +} + +UnicodeString RequireAffixValidator::toString() const { + return u""; +} + + +void RequireCurrencyValidator::postProcess(ParsedNumber& result) const { + if (result.currencyCode[0] == 0) { + result.flags |= FLAG_FAIL; + } +} + +UnicodeString RequireCurrencyValidator::toString() const { + return u""; +} + + +RequireDecimalSeparatorValidator::RequireDecimalSeparatorValidator(bool patternHasDecimalSeparator) + : fPatternHasDecimalSeparator(patternHasDecimalSeparator) { +} + +void RequireDecimalSeparatorValidator::postProcess(ParsedNumber& result) const { + bool parseHasDecimalSeparator = 0 != (result.flags & FLAG_HAS_DECIMAL_SEPARATOR); + if (parseHasDecimalSeparator != fPatternHasDecimalSeparator) { + result.flags |= FLAG_FAIL; + } +} + +UnicodeString RequireDecimalSeparatorValidator::toString() const { + return u""; +} + + +void RequireExponentValidator::postProcess(ParsedNumber& result) const { + if (0 == (result.flags & FLAG_HAS_EXPONENT)) { + result.flags |= FLAG_FAIL; + } +} + +UnicodeString RequireExponentValidator::toString() const { + return u""; +} + + +void RequireNumberValidator::postProcess(ParsedNumber& result) const { + // Require that a number is matched. + if (!result.seenNumber()) { + result.flags |= FLAG_FAIL; + } +} + +UnicodeString RequireNumberValidator::toString() const { + return u""; +} + + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/numparse_validators.h b/icu4c/source/i18n/numparse_validators.h new file mode 100644 index 0000000000..d158b234fd --- /dev/null +++ b/icu4c/source/i18n/numparse_validators.h @@ -0,0 +1,85 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#ifndef __SOURCE_NUMPARSE_VALIDATORS_H__ +#define __SOURCE_NUMPARSE_VALIDATORS_H__ + +#include "numparse_types.h" +#include "numparse_unisets.h" + +U_NAMESPACE_BEGIN namespace numparse { +namespace impl { + + +class ValidationMatcher : public NumberParseMatcher { + public: + bool match(StringSegment&, ParsedNumber&, UErrorCode&) const U_OVERRIDE { + // No-op + return false; + } + + const UnicodeSet& getLeadCodePoints() U_OVERRIDE { + // No-op + return *unisets::get(unisets::EMPTY); + } + + virtual void postProcess(ParsedNumber& result) const U_OVERRIDE = 0; +}; + + +class RequireAffixValidator : public ValidationMatcher, public UMemory { + public: + void postProcess(ParsedNumber& result) const U_OVERRIDE; + + UnicodeString toString() const U_OVERRIDE; +}; + + +class RequireCurrencyValidator : public ValidationMatcher, public UMemory { + public: + void postProcess(ParsedNumber& result) const U_OVERRIDE; + + UnicodeString toString() const U_OVERRIDE; +}; + + +class RequireDecimalSeparatorValidator : public ValidationMatcher, public UMemory { + public: + RequireDecimalSeparatorValidator() = default; // leaves instance in valid but undefined state + + RequireDecimalSeparatorValidator(bool patternHasDecimalSeparator); + + void postProcess(ParsedNumber& result) const U_OVERRIDE; + + UnicodeString toString() const U_OVERRIDE; + + private: + bool fPatternHasDecimalSeparator; +}; + + +class RequireExponentValidator : public ValidationMatcher, public UMemory { + public: + void postProcess(ParsedNumber& result) const U_OVERRIDE; + + UnicodeString toString() const U_OVERRIDE; +}; + + +class RequireNumberValidator : public ValidationMatcher, public UMemory { + public: + void postProcess(ParsedNumber& result) const U_OVERRIDE; + + UnicodeString toString() const U_OVERRIDE; +}; + + +} // namespace impl +} // namespace numparse +U_NAMESPACE_END + +#endif //__SOURCE_NUMPARSE_VALIDATORS_H__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/test/intltest/numbertest_parse.cpp b/icu4c/source/test/intltest/numbertest_parse.cpp index 16323b52db..160bab4fbc 100644 --- a/icu4c/source/test/intltest/numbertest_parse.cpp +++ b/icu4c/source/test/intltest/numbertest_parse.cpp @@ -216,13 +216,15 @@ void NumberParserTest::testCurrencyAnyMatcher() { IcuTestErrorCode status(*this, "testCurrencyAnyMatcher"); IgnorablesMatcher ignorables(unisets::DEFAULT_IGNORABLES); + Locale locale = Locale::getEnglish(); + + DecimalFormatSymbols dfs(locale, status); + dfs.setSymbol(DecimalFormatSymbols::kCurrencySymbol, u"IU$", status); + dfs.setSymbol(DecimalFormatSymbols::kIntlCurrencySymbol, u"ICU", status); + CurrencySymbols currencySymbols({u"ICU", status}, locale, dfs, status); + AffixTokenMatcherSetupData affixSetupData = { - u"ICU", - u"IU$", - u"ICU", - {"en", status}, - ignorables, - "en"}; + currencySymbols, {"en", status}, ignorables, "en"}; AffixTokenMatcherWarehouse warehouse(&affixSetupData); NumberParseMatcher& matcher = warehouse.currency(status); @@ -254,14 +256,16 @@ void NumberParserTest::testCurrencyAnyMatcher() { void NumberParserTest::testAffixPatternMatcher() { IcuTestErrorCode status(*this, "testAffixPatternMatcher"); + Locale locale = Locale::getEnglish(); IgnorablesMatcher ignorables(unisets::DEFAULT_IGNORABLES); + + DecimalFormatSymbols dfs(locale, status); + dfs.setSymbol(DecimalFormatSymbols::kCurrencySymbol, u"IU$", status); + dfs.setSymbol(DecimalFormatSymbols::kIntlCurrencySymbol, u"ICU", status); + CurrencySymbols currencySymbols({u"ICU", status}, locale, dfs, status); + AffixTokenMatcherSetupData affixSetupData = { - u"USD", - u"foo", - u"bar", - {"en", status}, - ignorables, - "en"}; + currencySymbols, {"en", status}, ignorables, "en"}; AffixTokenMatcherWarehouse warehouse(&affixSetupData); static const struct TestCase { diff --git a/icu4c/source/test/intltest/numbertest_patternmodifier.cpp b/icu4c/source/test/intltest/numbertest_patternmodifier.cpp index bd8acc9697..df599d5d0b 100644 --- a/icu4c/source/test/intltest/numbertest_patternmodifier.cpp +++ b/icu4c/source/test/intltest/numbertest_patternmodifier.cpp @@ -28,7 +28,7 @@ void PatternModifierTest::testBasic() { mod.setPatternInfo(&patternInfo); mod.setPatternAttributes(UNUM_SIGN_AUTO, false); DecimalFormatSymbols symbols(Locale::getEnglish(), status); - CurrencyDataSymbols currencySymbols({u"USD", status}, "en", status); + CurrencySymbols currencySymbols({u"USD", status}, "en", status); assertSuccess("Spot 2", status); mod.setSymbols(&symbols, ¤cySymbols, UNUM_UNIT_WIDTH_SHORT, nullptr); @@ -88,7 +88,7 @@ void PatternModifierTest::testPatternWithNoPlaceholder() { mod.setPatternInfo(&patternInfo); mod.setPatternAttributes(UNUM_SIGN_AUTO, false); DecimalFormatSymbols symbols(Locale::getEnglish(), status); - CurrencyDataSymbols currencySymbols({u"USD", status}, "en", status); + CurrencySymbols currencySymbols({u"USD", status}, "en", status); assertSuccess("Spot 2", status); mod.setSymbols(&symbols, ¤cySymbols, UNUM_UNIT_WIDTH_SHORT, nullptr); mod.setNumberProperties(1, StandardPlural::Form::COUNT); @@ -124,7 +124,7 @@ void PatternModifierTest::testMutableEqualsImmutable() { mod.setPatternInfo(&patternInfo); mod.setPatternAttributes(UNUM_SIGN_AUTO, false); DecimalFormatSymbols symbols(Locale::getEnglish(), status); - CurrencyDataSymbols currencySymbols({u"USD", status}, "en", status); + CurrencySymbols currencySymbols({u"USD", status}, "en", status); assertSuccess("Spot 2", status); if (U_FAILURE(status)) { return; } mod.setSymbols(&symbols, ¤cySymbols, UNUM_UNIT_WIDTH_SHORT, nullptr); From 01916cad11f284b2423846ab4ebfa47f33bc6f76 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Wed, 21 Mar 2018 06:30:29 +0000 Subject: [PATCH 047/129] ICU-13634 Changes NumberParseMatcher getLeadCodePoints() to smokeTest() in C++ and Java. The new method is more versatile and eliminates the requirement to maintain two code paths for "lead chars" and "no lead chars". X-SVN-Rev: 41131 --- icu4c/source/i18n/numparse_affixes.cpp | 28 ++------ icu4c/source/i18n/numparse_affixes.h | 4 +- icu4c/source/i18n/numparse_compositions.cpp | 29 ++++++-- icu4c/source/i18n/numparse_compositions.h | 8 ++- icu4c/source/i18n/numparse_currency.cpp | 42 +++--------- icu4c/source/i18n/numparse_currency.h | 8 +-- icu4c/source/i18n/numparse_decimal.cpp | 28 ++++---- icu4c/source/i18n/numparse_decimal.h | 2 +- icu4c/source/i18n/numparse_impl.cpp | 47 +++---------- icu4c/source/i18n/numparse_impl.h | 11 +-- icu4c/source/i18n/numparse_scientific.cpp | 20 ++---- icu4c/source/i18n/numparse_scientific.h | 2 +- icu4c/source/i18n/numparse_stringsegment.cpp | 13 +++- icu4c/source/i18n/numparse_symbols.cpp | 27 +------- icu4c/source/i18n/numparse_symbols.h | 4 +- icu4c/source/i18n/numparse_types.h | 35 +++++----- icu4c/source/i18n/numparse_unisets.cpp | 3 - icu4c/source/i18n/numparse_unisets.h | 2 - icu4c/source/i18n/numparse_validators.h | 6 +- .../source/test/intltest/numbertest_parse.cpp | 7 +- .../test/intltest/numbertest_unisets.cpp | 7 -- .../src/com/ibm/icu/impl/StringSegment.java | 13 ++++ .../icu/impl/number/parse/AffixMatcher.java | 13 +--- .../ibm/icu/impl/number/parse/AnyMatcher.java | 17 ++--- .../impl/number/parse/CodePointMatcher.java | 5 +- .../number/parse/CurrencyCustomMatcher.java | 8 +-- .../number/parse/CurrencyNamesMatcher.java | 20 ++++-- .../icu/impl/number/parse/DecimalMatcher.java | 24 ++++--- .../ibm/icu/impl/number/parse/NanMatcher.java | 12 ---- .../impl/number/parse/NumberParseMatcher.java | 17 +++-- .../impl/number/parse/NumberParserImpl.java | 35 ++-------- .../impl/number/parse/ScientificMatcher.java | 11 +-- .../icu/impl/number/parse/SeriesMatcher.java | 7 +- .../icu/impl/number/parse/SymbolMatcher.java | 12 +--- .../number/parse/UnicodeSetStaticCache.java | 6 -- .../impl/number/parse/ValidationMatcher.java | 5 +- .../dev/test/number/ExhaustiveNumberTest.java | 60 +++++++++++++++++ .../icu/dev/test/number/NumberParserTest.java | 7 +- .../icu/dev/test/number/PropertiesTest.java | 2 +- .../number/UnicodeSetStaticCacheTest.java | 67 +------------------ 40 files changed, 266 insertions(+), 408 deletions(-) diff --git a/icu4c/source/i18n/numparse_affixes.cpp b/icu4c/source/i18n/numparse_affixes.cpp index e4dd8b7662..159d70dcda 100644 --- a/icu4c/source/i18n/numparse_affixes.cpp +++ b/icu4c/source/i18n/numparse_affixes.cpp @@ -206,21 +206,15 @@ CodePointMatcher::CodePointMatcher(UChar32 cp) : fCp(cp) {} bool CodePointMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCode&) const { - if (segment.matches(fCp)) { + if (segment.startsWith(fCp)) { segment.adjustOffsetByCodePoint(); result.setCharsConsumed(segment); } return false; } -const UnicodeSet& CodePointMatcher::getLeadCodePoints() { - if (fLocalLeadCodePoints.isNull()) { - auto* leadCodePoints = new UnicodeSet(); - leadCodePoints->add(fCp); - leadCodePoints->freeze(); - fLocalLeadCodePoints.adoptInstead(leadCodePoints); - } - return *fLocalLeadCodePoints; +bool CodePointMatcher::smokeTest(const StringSegment& segment) const { + return segment.startsWith(fCp); } UnicodeString CodePointMatcher::toString() const { @@ -427,19 +421,9 @@ bool AffixMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCod } } -const UnicodeSet& AffixMatcher::getLeadCodePoints() { - if (fLocalLeadCodePoints.isNull()) { - auto* leadCodePoints = new UnicodeSet(); - if (fPrefix != nullptr) { - leadCodePoints->addAll(fPrefix->getLeadCodePoints()); - } - if (fSuffix != nullptr) { - leadCodePoints->addAll(fSuffix->getLeadCodePoints()); - } - leadCodePoints->freeze(); - fLocalLeadCodePoints.adoptInstead(leadCodePoints); - } - return *fLocalLeadCodePoints; +bool AffixMatcher::smokeTest(const StringSegment& segment) const { + return (fPrefix != nullptr && fPrefix->smokeTest(segment)) || + (fSuffix != nullptr && fSuffix->smokeTest(segment)); } void AffixMatcher::postProcess(ParsedNumber& result) const { diff --git a/icu4c/source/i18n/numparse_affixes.h b/icu4c/source/i18n/numparse_affixes.h index 8a09983c9e..3b79457b6d 100644 --- a/icu4c/source/i18n/numparse_affixes.h +++ b/icu4c/source/i18n/numparse_affixes.h @@ -35,7 +35,7 @@ class CodePointMatcher : public NumberParseMatcher, public UMemory { bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; - const UnicodeSet& getLeadCodePoints() override; + bool smokeTest(const StringSegment& segment) const override; UnicodeString toString() const override; @@ -190,7 +190,7 @@ class AffixMatcher : public NumberParseMatcher, public UMemory { void postProcess(ParsedNumber& result) const override; - const UnicodeSet& getLeadCodePoints() override; + bool smokeTest(const StringSegment& segment) const override; int8_t compareTo(const AffixMatcher& rhs) const; diff --git a/icu4c/source/i18n/numparse_compositions.cpp b/icu4c/source/i18n/numparse_compositions.cpp index 11e1cbcdf0..d254c07349 100644 --- a/icu4c/source/i18n/numparse_compositions.cpp +++ b/icu4c/source/i18n/numparse_compositions.cpp @@ -38,9 +38,19 @@ bool AnyMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCode& return maybeMore; } +bool AnyMatcher::smokeTest(const StringSegment& segment) const { + // NOTE: The range-based for loop calls the virtual begin() and end() methods. + for (auto& matcher : *this) { + if (matcher->smokeTest(segment)) { + return true; + } + } + return false; +} + void AnyMatcher::postProcess(ParsedNumber& result) const { // NOTE: The range-based for loop calls the virtual begin() and end() methods. - for (auto* matcher : *this) { + for (auto& matcher : *this) { matcher->postProcess(result); } } @@ -83,6 +93,17 @@ bool SeriesMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCo return maybeMore; } +bool SeriesMatcher::smokeTest(const StringSegment& segment) const { + // NOTE: The range-based for loop calls the virtual begin() and end() methods. + // NOTE: We only want the first element. Use the for loop for boundary checking. + for (auto& matcher : *this) { + // SeriesMatchers are never allowed to start with a Flexible matcher. + U_ASSERT(!matcher->isFlexible()); + return matcher->smokeTest(segment); + } + return false; +} + void SeriesMatcher::postProcess(ParsedNumber& result) const { // NOTE: The range-based for loop calls the virtual begin() and end() methods. for (auto* matcher : *this) { @@ -99,12 +120,6 @@ ArraySeriesMatcher::ArraySeriesMatcher(MatcherArray& matchers, int32_t matchersL : fMatchers(std::move(matchers)), fMatchersLen(matchersLen) { } -const UnicodeSet& ArraySeriesMatcher::getLeadCodePoints() { - // SeriesMatchers are never allowed to start with a Flexible matcher. - U_ASSERT(!fMatchers[0]->isFlexible()); - return fMatchers[0]->getLeadCodePoints(); -} - int32_t ArraySeriesMatcher::length() const { return fMatchersLen; } diff --git a/icu4c/source/i18n/numparse_compositions.h b/icu4c/source/i18n/numparse_compositions.h index 91db6fa2ca..8d61ab46d3 100644 --- a/icu4c/source/i18n/numparse_compositions.h +++ b/icu4c/source/i18n/numparse_compositions.h @@ -42,6 +42,8 @@ class AnyMatcher : public CompositionMatcher { public: bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; + bool smokeTest(const StringSegment& segment) const override; + void postProcess(ParsedNumber& result) const override; protected: @@ -61,6 +63,8 @@ class SeriesMatcher : public CompositionMatcher { public: bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; + bool smokeTest(const StringSegment& segment) const override; + void postProcess(ParsedNumber& result) const override; virtual int32_t length() const = 0; @@ -80,13 +84,11 @@ class ArraySeriesMatcher : public SeriesMatcher { public: ArraySeriesMatcher(); // WARNING: Leaves the object in an unusable state - typedef MaybeStackArray MatcherArray; + typedef MaybeStackArray MatcherArray; /** The array is std::move'd */ ArraySeriesMatcher(MatcherArray& matchers, int32_t matchersLen); - const UnicodeSet& getLeadCodePoints() override; - UnicodeString toString() const override; int32_t length() const override; diff --git a/icu4c/source/i18n/numparse_currency.cpp b/icu4c/source/i18n/numparse_currency.cpp index 064ba73fc0..61f07acce2 100644 --- a/icu4c/source/i18n/numparse_currency.cpp +++ b/icu4c/source/i18n/numparse_currency.cpp @@ -21,7 +21,12 @@ using namespace icu::numparse::impl; CurrencyNamesMatcher::CurrencyNamesMatcher(const Locale& locale, UErrorCode& status) - : fLocaleName(locale.getName(), -1, status) {} + : fLocaleName(locale.getName(), -1, status) { + uprv_currencyLeads(fLocaleName.data(), fLeadCodePoints, status); + // Always apply case mapping closure for currencies + fLeadCodePoints.closeOver(USET_ADD_CASE_MAPPINGS); + fLeadCodePoints.freeze(); +} bool CurrencyNamesMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const { if (result.currencyCode[0] != 0) { @@ -57,17 +62,8 @@ bool CurrencyNamesMatcher::match(StringSegment& segment, ParsedNumber& result, U return partialMatch; } -const UnicodeSet& CurrencyNamesMatcher::getLeadCodePoints() { - if (fLocalLeadCodePoints.isNull()) { - ErrorCode status; - auto* leadCodePoints = new UnicodeSet(); - uprv_currencyLeads(fLocaleName.data(), *leadCodePoints, status); - // Always apply case mapping closure for currencies - leadCodePoints->closeOver(USET_ADD_CASE_MAPPINGS); - leadCodePoints->freeze(); - fLocalLeadCodePoints.adoptInstead(leadCodePoints); - } - return *fLocalLeadCodePoints; +bool CurrencyNamesMatcher::smokeTest(const StringSegment& segment) const { + return segment.startsWith(fLeadCodePoints); } UnicodeString CurrencyNamesMatcher::toString() const { @@ -103,15 +99,8 @@ bool CurrencyCustomMatcher::match(StringSegment& segment, ParsedNumber& result, return overlap1 == segment.length() || overlap2 == segment.length(); } -const UnicodeSet& CurrencyCustomMatcher::getLeadCodePoints() { - if (fLocalLeadCodePoints.isNull()) { - auto* leadCodePoints = new UnicodeSet(); - utils::putLeadCodePoint(fCurrency1, leadCodePoints); - utils::putLeadCodePoint(fCurrency2, leadCodePoints); - leadCodePoints->freeze(); - fLocalLeadCodePoints.adoptInstead(leadCodePoints); - } - return *fLocalLeadCodePoints; +bool CurrencyCustomMatcher::smokeTest(const StringSegment& segment) const { + return segment.startsWith(fCurrency1) || segment.startsWith(fCurrency2); } UnicodeString CurrencyCustomMatcher::toString() const { @@ -144,17 +133,6 @@ CurrencyAnyMatcher& CurrencyAnyMatcher::operator=(CurrencyAnyMatcher&& src) U_NO return *this; } -const UnicodeSet& CurrencyAnyMatcher::getLeadCodePoints() { - if (fLocalLeadCodePoints.isNull()) { - auto* leadCodePoints = new UnicodeSet(); - leadCodePoints->addAll(fNamesMatcher.getLeadCodePoints()); - leadCodePoints->addAll(fCustomMatcher.getLeadCodePoints()); - leadCodePoints->freeze(); - fLocalLeadCodePoints.adoptInstead(leadCodePoints); - } - return *fLocalLeadCodePoints; -} - const NumberParseMatcher* const* CurrencyAnyMatcher::begin() const { return fMatcherArray; } diff --git a/icu4c/source/i18n/numparse_currency.h b/icu4c/source/i18n/numparse_currency.h index dd0490a9af..1c2a57d2c1 100644 --- a/icu4c/source/i18n/numparse_currency.h +++ b/icu4c/source/i18n/numparse_currency.h @@ -11,6 +11,7 @@ #include "numparse_compositions.h" #include "charstr.h" #include "number_currencysymbols.h" +#include "unicode/uniset.h" U_NAMESPACE_BEGIN namespace numparse { namespace impl { @@ -32,7 +33,7 @@ class CurrencyNamesMatcher : public NumberParseMatcher, public UMemory { bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; - const UnicodeSet& getLeadCodePoints() override; + bool smokeTest(const StringSegment& segment) const override; UnicodeString toString() const override; @@ -41,6 +42,7 @@ class CurrencyNamesMatcher : public NumberParseMatcher, public UMemory { // Locale has a non-trivial default constructor. CharString fLocaleName; + UnicodeSet fLeadCodePoints; }; @@ -52,7 +54,7 @@ class CurrencyCustomMatcher : public NumberParseMatcher, public UMemory { bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; - const UnicodeSet& getLeadCodePoints() override; + bool smokeTest(const StringSegment& segment) const override; UnicodeString toString() const override; @@ -78,8 +80,6 @@ class CurrencyAnyMatcher : public AnyMatcher, public UMemory { CurrencyAnyMatcher& operator=(CurrencyAnyMatcher&& src) U_NOEXCEPT; - const UnicodeSet& getLeadCodePoints() override; - UnicodeString toString() const override; protected: diff --git a/icu4c/source/i18n/numparse_decimal.cpp b/icu4c/source/i18n/numparse_decimal.cpp index 33b6821ff2..7ce0b19044 100644 --- a/icu4c/source/i18n/numparse_decimal.cpp +++ b/icu4c/source/i18n/numparse_decimal.cpp @@ -295,25 +295,23 @@ bool DecimalMatcher::match(StringSegment& segment, ParsedNumber& result, int8_t return segment.length() == 0 || hasPartialPrefix; } -const UnicodeSet& DecimalMatcher::getLeadCodePoints() { +bool DecimalMatcher::smokeTest(const StringSegment& segment) const { + // The common case uses a static leadSet for efficiency. if (fLocalDigitStrings.isNull() && leadSet != nullptr) { - return *leadSet; + return segment.startsWith(*leadSet); } - - if (fLocalLeadCodePoints.isNull()) { - auto* leadCodePoints = new UnicodeSet(); - // Assumption: the sets are all single code points. - leadCodePoints->addAll(*unisets::get(unisets::DIGITS)); - leadCodePoints->addAll(*separatorSet); - if (!fLocalDigitStrings.isNull()) { - for (int i = 0; i < 10; i++) { - utils::putLeadCodePoint(fLocalDigitStrings[i], leadCodePoints); - } + if (segment.startsWith(*separatorSet) || u_isdigit(segment.getCodePoint())) { + return true; + } + if (fLocalDigitStrings.isNull()) { + return false; + } + for (int i = 0; i < 10; i++) { + if (segment.startsWith(fLocalDigitStrings[i])) { + return true; } - leadCodePoints->freeze(); - fLocalLeadCodePoints.adoptInstead(leadCodePoints); } - return *fLocalLeadCodePoints; + return false; } UnicodeString DecimalMatcher::toString() const { diff --git a/icu4c/source/i18n/numparse_decimal.h b/icu4c/source/i18n/numparse_decimal.h index 78ad074f19..f3ddcd8d61 100644 --- a/icu4c/source/i18n/numparse_decimal.h +++ b/icu4c/source/i18n/numparse_decimal.h @@ -27,7 +27,7 @@ class DecimalMatcher : public NumberParseMatcher, public UMemory { bool match(StringSegment& segment, ParsedNumber& result, int8_t exponentSign, UErrorCode& status) const; - const UnicodeSet& getLeadCodePoints() override; + bool smokeTest(const StringSegment& segment) const override; UnicodeString toString() const override; diff --git a/icu4c/source/i18n/numparse_impl.cpp b/icu4c/source/i18n/numparse_impl.cpp index 32d904f99a..82c69d4024 100644 --- a/icu4c/source/i18n/numparse_impl.cpp +++ b/icu4c/source/i18n/numparse_impl.cpp @@ -34,7 +34,7 @@ NumberParserImpl* NumberParserImpl::createSimpleParser(const Locale& locale, const UnicodeString& patternString, parse_flags_t parseFlags, UErrorCode& status) { - LocalPointer parser(new NumberParserImpl(parseFlags, true)); + LocalPointer parser(new NumberParserImpl(parseFlags)); DecimalFormatSymbols symbols(locale, status); parser->fLocalMatchers.ignorables = {unisets::DEFAULT_IGNORABLES}; @@ -117,7 +117,7 @@ NumberParserImpl::createParserFromProperties(const number::impl::DecimalFormatPr } IgnorablesMatcher ignorables(isStrict ? unisets::DEFAULT_IGNORABLES : unisets::STRICT_IGNORABLES); - LocalPointer parser(new NumberParserImpl(parseFlags, status)); + LocalPointer parser(new NumberParserImpl(parseFlags)); ////////////////////// /// AFFIX MATCHERS /// @@ -197,52 +197,22 @@ NumberParserImpl::createParserFromProperties(const number::impl::DecimalFormatPr return parser.orphan(); } -NumberParserImpl::NumberParserImpl(parse_flags_t parseFlags, bool computeLeads) - : fParseFlags(parseFlags), fComputeLeads(computeLeads) { +NumberParserImpl::NumberParserImpl(parse_flags_t parseFlags) + : fParseFlags(parseFlags) { } NumberParserImpl::~NumberParserImpl() { - if (fComputeLeads) { - for (int32_t i = 0; i < fNumMatchers; i++) { - delete (fLeads[i]); - } - } fNumMatchers = 0; } void NumberParserImpl::addMatcher(NumberParseMatcher& matcher) { if (fNumMatchers + 1 > fMatchers.getCapacity()) { fMatchers.resize(fNumMatchers * 2, fNumMatchers); - if (fComputeLeads) { - // The two arrays should grow in tandem: - U_ASSERT(fNumMatchers >= fLeads.getCapacity()); - fLeads.resize(fNumMatchers * 2, fNumMatchers); - } } - fMatchers[fNumMatchers] = &matcher; - - if (fComputeLeads) { - addLeadCodePointsForMatcher(matcher); - } - fNumMatchers++; } -void NumberParserImpl::addLeadCodePointsForMatcher(NumberParseMatcher& matcher) { - const UnicodeSet& leadCodePoints = matcher.getLeadCodePoints(); - // TODO: Avoid the clone operation here. - if (0 != (fParseFlags & PARSE_FLAG_IGNORE_CASE)) { - auto* copy = dynamic_cast(leadCodePoints.cloneAsThawed()); - copy->closeOver(USET_ADD_CASE_MAPPINGS); - copy->freeze(); - fLeads[fNumMatchers] = copy; - } else { - // FIXME: new here because we still take ownership - fLeads[fNumMatchers] = new UnicodeSet(leadCodePoints); - } -} - void NumberParserImpl::freeze() { fFrozen = true; } @@ -276,12 +246,11 @@ void NumberParserImpl::parseGreedyRecursive(StringSegment& segment, ParsedNumber } int initialOffset = segment.getOffset(); - int leadCp = segment.getCodePoint(); for (int32_t i = 0; i < fNumMatchers; i++) { - if (fComputeLeads && !fLeads[i]->contains(leadCp)) { + const NumberParseMatcher* matcher = fMatchers[i]; + if (!matcher->smokeTest(segment)) { continue; } - const NumberParseMatcher* matcher = fMatchers[i]; matcher->match(segment, result, status); if (U_FAILURE(status)) { return; @@ -313,8 +282,10 @@ void NumberParserImpl::parseLongestRecursive(StringSegment& segment, ParsedNumbe int initialOffset = segment.getOffset(); for (int32_t i = 0; i < fNumMatchers; i++) { - // TODO: Check leadChars here? const NumberParseMatcher* matcher = fMatchers[i]; + if (!matcher->smokeTest(segment)) { + continue; + } // In a non-greedy parse, we attempt all possible matches and pick the best. for (int32_t charsToConsume = 0; charsToConsume < segment.length();) { diff --git a/icu4c/source/i18n/numparse_impl.h b/icu4c/source/i18n/numparse_impl.h index d05d7a24ac..e740038414 100644 --- a/icu4c/source/i18n/numparse_impl.h +++ b/icu4c/source/i18n/numparse_impl.h @@ -32,6 +32,11 @@ class NumberParserImpl : public MutableMatcherCollection { const number::impl::DecimalFormatProperties& properties, const DecimalFormatSymbols& symbols, bool parseCurrency, bool optimize, UErrorCode& status); + /** + * Does NOT take ownership of the matcher. The matcher MUST remain valid for the lifespan of the + * NumberParserImpl. + * @param matcher The matcher to reference. + */ void addMatcher(NumberParseMatcher& matcher) override; void freeze(); @@ -48,8 +53,6 @@ class NumberParserImpl : public MutableMatcherCollection { int32_t fNumMatchers = 0; // NOTE: The stack capacity for fMatchers and fLeads should be the same MaybeStackArray fMatchers; - MaybeStackArray fLeads; - bool fComputeLeads; bool fFrozen = false; // WARNING: All of these matchers start in an undefined state (default-constructed). @@ -78,9 +81,7 @@ class NumberParserImpl : public MutableMatcherCollection { RequireNumberValidator number; } fLocalValidators; - NumberParserImpl(parse_flags_t parseFlags, bool computeLeads); - - void addLeadCodePointsForMatcher(NumberParseMatcher& matcher); + explicit NumberParserImpl(parse_flags_t parseFlags); void parseGreedyRecursive(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const; diff --git a/icu4c/source/i18n/numparse_scientific.cpp b/icu4c/source/i18n/numparse_scientific.cpp index bcb777101a..7e2dc52c94 100644 --- a/icu4c/source/i18n/numparse_scientific.cpp +++ b/icu4c/source/i18n/numparse_scientific.cpp @@ -44,10 +44,10 @@ bool ScientificMatcher::match(StringSegment& segment, ParsedNumber& result, UErr // Allow a sign, and then try to match digits. int8_t exponentSign = 1; - if (segment.matches(*unisets::get(unisets::MINUS_SIGN))) { + if (segment.startsWith(*unisets::get(unisets::MINUS_SIGN))) { exponentSign = -1; segment.adjustOffsetByCodePoint(); - } else if (segment.matches(*unisets::get(unisets::PLUS_SIGN))) { + } else if (segment.startsWith(*unisets::get(unisets::PLUS_SIGN))) { segment.adjustOffsetByCodePoint(); } @@ -71,20 +71,8 @@ bool ScientificMatcher::match(StringSegment& segment, ParsedNumber& result, UErr return false; } -const UnicodeSet& ScientificMatcher::getLeadCodePoints() { - UChar32 leadCp = fExponentSeparatorString.char32At(0); - const UnicodeSet* s = unisets::get(unisets::SCIENTIFIC_LEAD); - if (s->contains(leadCp)) { - return *s; - } - - if (fLocalLeadCodePoints.isNull()) { - auto* leadCodePoints = new UnicodeSet(); - leadCodePoints->add(leadCp); - leadCodePoints->freeze(); - fLocalLeadCodePoints.adoptInstead(leadCodePoints); - } - return *fLocalLeadCodePoints; +bool ScientificMatcher::smokeTest(const StringSegment& segment) const { + return segment.startsWith(fExponentSeparatorString); } UnicodeString ScientificMatcher::toString() const { diff --git a/icu4c/source/i18n/numparse_scientific.h b/icu4c/source/i18n/numparse_scientific.h index 0265c10608..4084fda1a7 100644 --- a/icu4c/source/i18n/numparse_scientific.h +++ b/icu4c/source/i18n/numparse_scientific.h @@ -25,7 +25,7 @@ class ScientificMatcher : public NumberParseMatcher, public UMemory { bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; - const UnicodeSet& getLeadCodePoints() override; + bool smokeTest(const StringSegment& segment) const override; UnicodeString toString() const override; diff --git a/icu4c/source/i18n/numparse_stringsegment.cpp b/icu4c/source/i18n/numparse_stringsegment.cpp index b2dcdc3642..a3d6f15102 100644 --- a/icu4c/source/i18n/numparse_stringsegment.cpp +++ b/icu4c/source/i18n/numparse_stringsegment.cpp @@ -75,11 +75,11 @@ UChar32 StringSegment::getCodePoint() const { } } -bool StringSegment::matches(UChar32 otherCp) const { +bool StringSegment::startsWith(UChar32 otherCp) const { return codePointsEqual(getCodePoint(), otherCp, fFoldCase); } -bool StringSegment::matches(const UnicodeSet& uniset) const { +bool StringSegment::startsWith(const UnicodeSet& uniset) const { // TODO: Move UnicodeSet case-folding logic here. // TODO: Handle string matches here instead of separately. UChar32 cp = getCodePoint(); @@ -89,6 +89,15 @@ bool StringSegment::matches(const UnicodeSet& uniset) const { return uniset.contains(cp); } +bool StringSegment::startsWith(const UnicodeString& other) const { + if (other.isBogus() || other.length() == 0 || length() == 0) { + return false; + } + int cp1 = getCodePoint(); + int cp2 = other.char32At(0); + return codePointsEqual(cp1, cp2, fFoldCase); +} + int32_t StringSegment::getCommonPrefixLength(const UnicodeString& other) { return getPrefixLengthInternal(other, fFoldCase); } diff --git a/icu4c/source/i18n/numparse_symbols.cpp b/icu4c/source/i18n/numparse_symbols.cpp index 0f35e31353..51922752b6 100644 --- a/icu4c/source/i18n/numparse_symbols.cpp +++ b/icu4c/source/i18n/numparse_symbols.cpp @@ -58,20 +58,8 @@ bool SymbolMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCo return overlap == segment.length(); } -const UnicodeSet& SymbolMatcher::getLeadCodePoints() { - if (fString.isEmpty()) { - // Assumption: for sets from UnicodeSetStaticCache, uniSet == leadCodePoints. - return *fUniSet; - } - - if (fLocalLeadCodePoints.isNull()) { - auto* leadCodePoints = new UnicodeSet(); - utils::putLeadCodePoints(fUniSet, leadCodePoints); - utils::putLeadCodePoint(fString, leadCodePoints); - leadCodePoints->freeze(); - fLocalLeadCodePoints.adoptInstead(leadCodePoints); - } - return *fLocalLeadCodePoints; +bool SymbolMatcher::smokeTest(const StringSegment& segment) const { + return segment.startsWith(*fUniSet) || segment.startsWith(fString); } UnicodeString SymbolMatcher::toString() const { @@ -134,17 +122,6 @@ NanMatcher::NanMatcher(const DecimalFormatSymbols& dfs) : SymbolMatcher(dfs.getConstSymbol(DecimalFormatSymbols::kNaNSymbol), unisets::EMPTY) { } -const UnicodeSet& NanMatcher::getLeadCodePoints() { - // Overriding this here to allow use of statically allocated sets - int leadCp = fString.char32At(0); - const UnicodeSet* s = unisets::get(unisets::NAN_LEAD); - if (s->contains(leadCp)) { - return *s; - } - - return SymbolMatcher::getLeadCodePoints(); -} - bool NanMatcher::isDisabled(const ParsedNumber& result) const { return result.seenNumber(); } diff --git a/icu4c/source/i18n/numparse_symbols.h b/icu4c/source/i18n/numparse_symbols.h index 2cc107101d..7264734fc0 100644 --- a/icu4c/source/i18n/numparse_symbols.h +++ b/icu4c/source/i18n/numparse_symbols.h @@ -28,7 +28,7 @@ class SymbolMatcher : public NumberParseMatcher, public UMemory { bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; - const UnicodeSet& getLeadCodePoints() override; + bool smokeTest(const StringSegment& segment) const override; UnicodeString toString() const override; @@ -96,8 +96,6 @@ class NanMatcher : public SymbolMatcher { NanMatcher(const DecimalFormatSymbols& dfs); - const UnicodeSet& getLeadCodePoints() override; - protected: bool isDisabled(const ParsedNumber& result) const override; diff --git a/icu4c/source/i18n/numparse_types.h b/icu4c/source/i18n/numparse_types.h index 3f27da05e2..2f8d31fc76 100644 --- a/icu4c/source/i18n/numparse_types.h +++ b/icu4c/source/i18n/numparse_types.h @@ -26,7 +26,7 @@ enum ResultFlags { FLAG_PERCENT = 0x0002, FLAG_PERMILLE = 0x0004, FLAG_HAS_EXPONENT = 0x0008, - FLAG_HAS_DEFAULT_CURRENCY = 0x0010, + // FLAG_HAS_DEFAULT_CURRENCY = 0x0010, // no longer used FLAG_HAS_DECIMAL_SEPARATOR = 0x0020, FLAG_NAN = 0x0040, FLAG_INFINITY = 0x0080, @@ -46,6 +46,7 @@ enum ParseFlags { PARSE_FLAG_USE_FULL_AFFIXES = 0x0100, PARSE_FLAG_EXACT_AFFIX = 0x0200, PARSE_FLAG_PLUS_SIGN_ALLOWED = 0x0400, + PARSE_FLAG_OPTIMIZE = 0x0800, }; @@ -216,12 +217,18 @@ class StringSegment : public UMemory, public ::icu::number::impl::CharSequence { *

* This method will perform case folding if case folding is enabled for the parser. */ - bool matches(UChar32 otherCp) const; + bool startsWith(UChar32 otherCp) const; /** * Returns true if the first code point of this StringSegment is in the given UnicodeSet. */ - bool matches(const UnicodeSet& uniset) const; + bool startsWith(const UnicodeSet& uniset) const; + + /** + * Returns true if there is at least one code point of overlap between this StringSegment and the + * given UnicodeString. + */ + bool startsWith(const UnicodeString& other) const; /** * Returns the length of the prefix shared by this StringSegment and the given CharSequence. For @@ -294,17 +301,18 @@ class NumberParseMatcher { virtual bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const = 0; /** - * Should return a set representing all possible chars (UTF-16 code units) that could be the first - * char that this matcher can consume. This method is only called during construction phase, and its - * return value is used to skip this matcher unless a segment begins with a char in this set. To make - * this matcher always run, return {@link UnicodeSet#ALL_CODE_POINTS}. + * Performs a fast "smoke check" for whether or not this matcher could possibly match against the + * given string segment. The test should be as fast as possible but also as restrictive as possible. + * For example, matchers can maintain a UnicodeSet of all code points that count possibly start a + * match. Matchers should use the {@link StringSegment#startsWith} method in order to correctly + * handle case folding. * - * The returned UnicodeSet does not need adoption and is guaranteed to be alive for as long as the - * object that returned it. - * - * This method is NOT thread-safe. + * @param segment + * The segment to check against. + * @return true if the matcher might be able to match against this segment; false if it definitely + * will not be able to match. */ - virtual const UnicodeSet& getLeadCodePoints() = 0; + virtual bool smokeTest(const StringSegment& segment) const = 0; /** * Method called at the end of a parse, after all matchers have failed to consume any more chars. @@ -324,9 +332,6 @@ class NumberParseMatcher { protected: // No construction except by subclasses! NumberParseMatcher() = default; - - // Optional ownership of the leadCodePoints set - LocalPointer fLocalLeadCodePoints; }; diff --git a/icu4c/source/i18n/numparse_unisets.cpp b/icu4c/source/i18n/numparse_unisets.cpp index 3e7c9b0253..eb2f6c1e9d 100644 --- a/icu4c/source/i18n/numparse_unisets.cpp +++ b/icu4c/source/i18n/numparse_unisets.cpp @@ -93,9 +93,6 @@ void U_CALLCONV initNumberParseUniSets(UErrorCode& status) { gUnicodeSets[INFINITY_KEY] = new UnicodeSet(u"[∞]", status); gUnicodeSets[DIGITS] = new UnicodeSet(u"[:digit:]", status); - gUnicodeSets[NAN_LEAD] = new UnicodeSet( - u"[NnТтmeՈոс¤НнчTtsҳ\u975e\u1002\u0e9a\u10d0\u0f68\u0644\u0646]", status); - gUnicodeSets[SCIENTIFIC_LEAD] = new UnicodeSet(u"[Ee×·е\u0627]", status); gUnicodeSets[CWCF] = new UnicodeSet(u"[:CWCF:]", status); gUnicodeSets[DIGITS_OR_ALL_SEPARATORS] = computeUnion(DIGITS, ALL_SEPARATORS); diff --git a/icu4c/source/i18n/numparse_unisets.h b/icu4c/source/i18n/numparse_unisets.h index d66d4adab9..97a44ea860 100644 --- a/icu4c/source/i18n/numparse_unisets.h +++ b/icu4c/source/i18n/numparse_unisets.h @@ -47,8 +47,6 @@ enum Key { // Other DIGITS, - NAN_LEAD, - SCIENTIFIC_LEAD, CWCF, // Combined Separators with Digits (for lead code points) diff --git a/icu4c/source/i18n/numparse_validators.h b/icu4c/source/i18n/numparse_validators.h index d158b234fd..817ec9cb8d 100644 --- a/icu4c/source/i18n/numparse_validators.h +++ b/icu4c/source/i18n/numparse_validators.h @@ -21,12 +21,12 @@ class ValidationMatcher : public NumberParseMatcher { return false; } - const UnicodeSet& getLeadCodePoints() U_OVERRIDE { + bool smokeTest(const StringSegment&) const U_OVERRIDE { // No-op - return *unisets::get(unisets::EMPTY); + return false; } - virtual void postProcess(ParsedNumber& result) const U_OVERRIDE = 0; + void postProcess(ParsedNumber& result) const U_OVERRIDE = 0; }; diff --git a/icu4c/source/test/intltest/numbertest_parse.cpp b/icu4c/source/test/intltest/numbertest_parse.cpp index 160bab4fbc..d97141a489 100644 --- a/icu4c/source/test/intltest/numbertest_parse.cpp +++ b/icu4c/source/test/intltest/numbertest_parse.cpp @@ -176,10 +176,9 @@ void NumberParserTest::testSeriesMatcher() { matchers[4] = &m4; ArraySeriesMatcher series(matchers, 5); - assertEquals( - "Lead set should be equal to lead set of lead matcher", - *unisets::get(unisets::PLUS_SIGN), - series.getLeadCodePoints()); + assertFalse("", series.smokeTest(StringSegment(u"x", false))); + assertFalse("", series.smokeTest(StringSegment(u"-", false))); + assertTrue("", series.smokeTest(StringSegment(u"+", false))); static const struct TestCase { const char16_t* input; diff --git a/icu4c/source/test/intltest/numbertest_unisets.cpp b/icu4c/source/test/intltest/numbertest_unisets.cpp index f0623b2bd1..74f65e4b41 100644 --- a/icu4c/source/test/intltest/numbertest_unisets.cpp +++ b/icu4c/source/test/intltest/numbertest_unisets.cpp @@ -46,8 +46,6 @@ void UniSetsTest::testSetCoverage() { const UnicodeSet &percent = *get(unisets::PERCENT_SIGN); const UnicodeSet &permille = *get(unisets::PERMILLE_SIGN); const UnicodeSet &infinity = *get(unisets::INFINITY_KEY); - const UnicodeSet &nanLead = *get(unisets::NAN_LEAD); - const UnicodeSet &scientificLead = *get(unisets::SCIENTIFIC_LEAD); int32_t localeCount; const Locale* allAvailableLocales = Locale::getAvailableLocales(localeCount); @@ -66,11 +64,6 @@ void UniSetsTest::testSetCoverage() { ASSERT_IN_SET(percent, dfs.getConstSymbol(DecimalFormatSymbols::kPercentSymbol)); ASSERT_IN_SET(permille, dfs.getConstSymbol(DecimalFormatSymbols::kPerMillSymbol)); ASSERT_IN_SET(infinity, dfs.getConstSymbol(DecimalFormatSymbols::kInfinitySymbol)); - ASSERT_IN_SET(nanLead, dfs.getConstSymbol(DecimalFormatSymbols::kNaNSymbol).char32At(0)); - ASSERT_IN_SET(nanLead, - u_foldCase(dfs.getConstSymbol(DecimalFormatSymbols::kNaNSymbol).char32At(0), 0)); - ASSERT_IN_SET(scientificLead, - u_foldCase(dfs.getConstSymbol(DecimalFormatSymbols::kExponentialSymbol).char32At(0), 0)); } } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/StringSegment.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/StringSegment.java index 750ad1d4bd..7808bddc6b 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/StringSegment.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/StringSegment.java @@ -134,6 +134,19 @@ public class StringSegment implements CharSequence { return uniset.contains(cp); } + /** + * Returns true if there is at least one code point of overlap between this StringSegment and the + * given CharSequence. Null-safe. + */ + public boolean startsWith(CharSequence other) { + if (other == null || other.length() == 0 || length() == 0) { + return false; + } + int cp1 = Character.codePointAt(this, 0); + int cp2 = Character.codePointAt(other, 0); + return codePointsEqual(cp1, cp2, foldCase); + } + /** * Returns the length of the prefix shared by this StringSegment and the given CharSequence. For * example, if this string segment is "aab", and the char sequence is "aac", this method returns 2, diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixMatcher.java index e8c0acfb8e..6f51158140 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixMatcher.java @@ -13,7 +13,6 @@ import com.ibm.icu.impl.number.AffixPatternProvider; import com.ibm.icu.impl.number.AffixUtils; import com.ibm.icu.impl.number.PatternStringUtils; import com.ibm.icu.number.NumberFormatter.SignDisplay; -import com.ibm.icu.text.UnicodeSet; /** * @author sffc @@ -206,15 +205,9 @@ public class AffixMatcher implements NumberParseMatcher { } @Override - public UnicodeSet getLeadCodePoints() { - UnicodeSet leadCodePoints = new UnicodeSet(); - if (prefix != null) { - leadCodePoints.addAll(prefix.getLeadCodePoints()); - } - if (suffix != null) { - leadCodePoints.addAll(suffix.getLeadCodePoints()); - } - return leadCodePoints.freeze(); + public boolean smokeTest(StringSegment segment) { + return (prefix != null && prefix.smokeTest(segment)) + || (suffix != null && suffix.smokeTest(segment)); } @Override diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AnyMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AnyMatcher.java index 005f8cc9b5..e5359ead47 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AnyMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AnyMatcher.java @@ -6,7 +6,6 @@ import java.util.ArrayList; import java.util.List; import com.ibm.icu.impl.StringSegment; -import com.ibm.icu.text.UnicodeSet; /** * Composes a number of matchers, and succeeds if any of the matchers succeed. Always greedily chooses @@ -58,22 +57,18 @@ public class AnyMatcher implements NumberParseMatcher { } @Override - public UnicodeSet getLeadCodePoints() { + public boolean smokeTest(StringSegment segment) { assert frozen; if (matchers == null) { - return UnicodeSet.EMPTY; + return false; } - if (matchers.size() == 1) { - return matchers.get(0).getLeadCodePoints(); - } - - UnicodeSet leadCodePoints = new UnicodeSet(); for (int i = 0; i < matchers.size(); i++) { - NumberParseMatcher matcher = matchers.get(i); - leadCodePoints.addAll(matcher.getLeadCodePoints()); + if (matchers.get(i).smokeTest(segment)) { + return true; + } } - return leadCodePoints.freeze(); + return false; } @Override diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CodePointMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CodePointMatcher.java index afa6f68a66..8e0fc60fb0 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CodePointMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CodePointMatcher.java @@ -3,7 +3,6 @@ package com.ibm.icu.impl.number.parse; import com.ibm.icu.impl.StringSegment; -import com.ibm.icu.text.UnicodeSet; /** * Matches a single code point, performing no other logic. @@ -33,8 +32,8 @@ public class CodePointMatcher implements NumberParseMatcher { } @Override - public UnicodeSet getLeadCodePoints() { - return new UnicodeSet().add(cp).freeze(); + public boolean smokeTest(StringSegment segment) { + return segment.startsWith(cp); } @Override diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyCustomMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyCustomMatcher.java index d008a0686c..019af7504f 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyCustomMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyCustomMatcher.java @@ -3,7 +3,6 @@ package com.ibm.icu.impl.number.parse; import com.ibm.icu.impl.StringSegment; -import com.ibm.icu.text.UnicodeSet; import com.ibm.icu.util.Currency; import com.ibm.icu.util.ULocale; @@ -52,11 +51,8 @@ public class CurrencyCustomMatcher implements NumberParseMatcher { } @Override - public UnicodeSet getLeadCodePoints() { - UnicodeSet leadCodePoints = new UnicodeSet(); - ParsingUtils.putLeadCodePoint(currency1, leadCodePoints); - ParsingUtils.putLeadCodePoint(currency2, leadCodePoints); - return leadCodePoints.freeze(); + public boolean smokeTest(StringSegment segment) { + return segment.startsWith(currency1) || segment.startsWith(currency2); } @Override diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyNamesMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyNamesMatcher.java index 0fcadbb9cb..be2acd42f9 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyNamesMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyNamesMatcher.java @@ -23,6 +23,8 @@ public class CurrencyNamesMatcher implements NumberParseMatcher { private final TextTrieMap longNameTrie; private final TextTrieMap symbolTrie; + private final UnicodeSet leadCodePoints; + public static CurrencyNamesMatcher getInstance(ULocale locale) { // TODO: Pre-compute some of the more popular locales? return new CurrencyNamesMatcher(locale); @@ -33,6 +35,15 @@ public class CurrencyNamesMatcher implements NumberParseMatcher { // case folding on long-names but not symbols. longNameTrie = Currency.getParsingTrie(locale, Currency.LONG_NAME); symbolTrie = Currency.getParsingTrie(locale, Currency.SYMBOL_NAME); + + // Compute the full set of characters that could be the first in a currency to allow for + // efficient smoke test. + leadCodePoints = new UnicodeSet(); + longNameTrie.putLeadCodePoints(leadCodePoints); + symbolTrie.putLeadCodePoints(leadCodePoints); + // Always apply case mapping closure for currencies + leadCodePoints.closeOver(UnicodeSet.ADD_CASE_MAPPINGS); + leadCodePoints.freeze(); } @Override @@ -55,13 +66,8 @@ public class CurrencyNamesMatcher implements NumberParseMatcher { } @Override - public UnicodeSet getLeadCodePoints() { - UnicodeSet leadCodePoints = new UnicodeSet(); - longNameTrie.putLeadCodePoints(leadCodePoints); - symbolTrie.putLeadCodePoints(leadCodePoints); - // Always apply case mapping closure for currencies - leadCodePoints.closeOver(UnicodeSet.ADD_CASE_MAPPINGS); - return leadCodePoints.freeze(); + public boolean smokeTest(StringSegment segment) { + return segment.startsWith(leadCodePoints); } @Override diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/DecimalMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/DecimalMatcher.java index 9ba745d97d..5a622611da 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/DecimalMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/DecimalMatcher.java @@ -329,21 +329,23 @@ public class DecimalMatcher implements NumberParseMatcher { } @Override - public UnicodeSet getLeadCodePoints() { + public boolean smokeTest(StringSegment segment) { + // The common case uses a static leadSet for efficiency. if (digitStrings == null && leadSet != null) { - return leadSet; + return segment.startsWith(leadSet); } - - UnicodeSet leadCodePoints = new UnicodeSet(); - // Assumption: the sets are all single code points. - leadCodePoints.addAll(UnicodeSetStaticCache.get(Key.DIGITS)); - leadCodePoints.addAll(separatorSet); - if (digitStrings != null) { - for (int i = 0; i < digitStrings.length; i++) { - ParsingUtils.putLeadCodePoint(digitStrings[i], leadCodePoints); + if (segment.startsWith(separatorSet) || UCharacter.isDigit(segment.getCodePoint())) { + return true; + } + if (digitStrings == null) { + return false; + } + for (int i = 0; i < digitStrings.length; i++) { + if (segment.startsWith(digitStrings[i])) { + return true; } } - return leadCodePoints.freeze(); + return false; } @Override diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NanMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NanMatcher.java index f78d0e80d1..46a6773a55 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NanMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NanMatcher.java @@ -27,18 +27,6 @@ public class NanMatcher extends SymbolMatcher { super(symbolString, UnicodeSet.EMPTY); } - @Override - public UnicodeSet getLeadCodePoints() { - // Overriding this here to allow use of statically allocated sets - int leadCp = string.codePointAt(0); - UnicodeSet s = UnicodeSetStaticCache.get(UnicodeSetStaticCache.Key.NAN_LEAD); - if (s.contains(leadCp)) { - return s; - } else { - return super.getLeadCodePoints(); - } - } - @Override protected boolean isDisabled(ParsedNumber result) { return result.seenNumber(); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParseMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParseMatcher.java index 9bdc289b16..14e9c7e1d2 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParseMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParseMatcher.java @@ -3,7 +3,6 @@ package com.ibm.icu.impl.number.parse; import com.ibm.icu.impl.StringSegment; -import com.ibm.icu.text.UnicodeSet; /** * The core interface implemented by all matchers used for number parsing. @@ -45,12 +44,18 @@ public interface NumberParseMatcher { public boolean match(StringSegment segment, ParsedNumber result); /** - * Should return a set representing all possible chars (UTF-16 code units) that could be the first - * char that this matcher can consume. This method is only called during construction phase, and its - * return value is used to skip this matcher unless a segment begins with a char in this set. To make - * this matcher always run, return {@link UnicodeSet#ALL_CODE_POINTS}. + * Performs a fast "smoke check" for whether or not this matcher could possibly match against the + * given string segment. The test should be as fast as possible but also as restrictive as possible. + * For example, matchers can maintain a UnicodeSet of all code points that count possibly start a + * match. Matchers should use the {@link StringSegment#startsWith} method in order to correctly + * handle case folding. + * + * @param segment + * The segment to check against. + * @return true if the matcher might be able to match against this segment; false if it definitely + * will not be able to match. */ - public UnicodeSet getLeadCodePoints(); + public boolean smokeTest(StringSegment segment); /** * Method called at the end of a parse, after all matchers have failed to consume any more chars. diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java index 647cb9bb16..ffd101fa01 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java @@ -19,7 +19,6 @@ import com.ibm.icu.impl.number.PropertiesAffixPatternProvider; import com.ibm.icu.impl.number.RoundingUtils; import com.ibm.icu.number.NumberFormatter.GroupingStrategy; import com.ibm.icu.text.DecimalFormatSymbols; -import com.ibm.icu.text.UnicodeSet; import com.ibm.icu.util.Currency; import com.ibm.icu.util.CurrencyAmount; import com.ibm.icu.util.ULocale; @@ -250,7 +249,6 @@ public class NumberParserImpl { private final int parseFlags; private final List matchers; - private final List leads; private boolean frozen; /** @@ -261,11 +259,6 @@ public class NumberParserImpl { */ public NumberParserImpl(int parseFlags) { matchers = new ArrayList(); - if (0 != (parseFlags & ParsingUtils.PARSE_FLAG_OPTIMIZE)) { - leads = new ArrayList(); - } else { - leads = null; - } this.parseFlags = parseFlags; frozen = false; } @@ -273,30 +266,11 @@ public class NumberParserImpl { public void addMatcher(NumberParseMatcher matcher) { assert !frozen; this.matchers.add(matcher); - if (leads != null) { - addLeadCodePointsForMatcher(matcher); - } } public void addMatchers(Collection matchers) { assert !frozen; this.matchers.addAll(matchers); - if (leads != null) { - for (NumberParseMatcher matcher : matchers) { - addLeadCodePointsForMatcher(matcher); - } - } - } - - private void addLeadCodePointsForMatcher(NumberParseMatcher matcher) { - UnicodeSet leadCodePoints = matcher.getLeadCodePoints(); - assert leadCodePoints.isFrozen(); - // TODO: Avoid the clone operation here. - if (0 != (parseFlags & ParsingUtils.PARSE_FLAG_IGNORE_CASE)) { - leadCodePoints = leadCodePoints.cloneAsThawed().closeOver(UnicodeSet.ADD_CASE_MAPPINGS) - .freeze(); - } - this.leads.add(leadCodePoints); } public void freeze() { @@ -343,12 +317,11 @@ public class NumberParserImpl { } int initialOffset = segment.getOffset(); - int leadCp = segment.getCodePoint(); for (int i = 0; i < matchers.size(); i++) { - if (leads != null && !leads.get(i).contains(leadCp)) { + NumberParseMatcher matcher = matchers.get(i); + if (!matcher.smokeTest(segment)) { continue; } - NumberParseMatcher matcher = matchers.get(i); matcher.match(segment, result); if (segment.getOffset() != initialOffset) { // In a greedy parse, recurse on only the first match. @@ -377,8 +350,10 @@ public class NumberParserImpl { int initialOffset = segment.getOffset(); for (int i = 0; i < matchers.size(); i++) { - // TODO: Check leadChars here? NumberParseMatcher matcher = matchers.get(i); + if (!matcher.smokeTest(segment)) { + continue; + } // In a non-greedy parse, we attempt all possible matches and pick the best. for (int charsToConsume = 0; charsToConsume < segment.length();) { diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ScientificMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ScientificMatcher.java index a297a50006..8fdd5b5bd8 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ScientificMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ScientificMatcher.java @@ -5,7 +5,6 @@ package com.ibm.icu.impl.number.parse; import com.ibm.icu.impl.StringSegment; import com.ibm.icu.impl.number.Grouper; import com.ibm.icu.text.DecimalFormatSymbols; -import com.ibm.icu.text.UnicodeSet; /** * @author sffc @@ -78,14 +77,8 @@ public class ScientificMatcher implements NumberParseMatcher { } @Override - public UnicodeSet getLeadCodePoints() { - int leadCp = exponentSeparatorString.codePointAt(0); - UnicodeSet s = UnicodeSetStaticCache.get(UnicodeSetStaticCache.Key.SCIENTIFIC_LEAD); - if (s.contains(leadCp)) { - return s; - } else { - return new UnicodeSet().add(leadCp).freeze(); - } + public boolean smokeTest(StringSegment segment) { + return segment.startsWith(exponentSeparatorString); } @Override diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/SeriesMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/SeriesMatcher.java index 8c8c67f573..f12a81fd35 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/SeriesMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/SeriesMatcher.java @@ -6,7 +6,6 @@ import java.util.ArrayList; import java.util.List; import com.ibm.icu.impl.StringSegment; -import com.ibm.icu.text.UnicodeSet; /** * Composes a number of matchers, running one after another. Matches the input string only if all of the @@ -82,15 +81,15 @@ public class SeriesMatcher implements NumberParseMatcher { } @Override - public UnicodeSet getLeadCodePoints() { + public boolean smokeTest(StringSegment segment) { assert frozen; if (matchers == null) { - return UnicodeSet.EMPTY; + return false; } // SeriesMatchers are never allowed to start with a Flexible matcher. assert !(matchers.get(0) instanceof NumberParseMatcher.Flexible); - return matchers.get(0).getLeadCodePoints(); + return matchers.get(0).smokeTest(segment); } @Override diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/SymbolMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/SymbolMatcher.java index 8ca6d92f30..f5b27c1d12 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/SymbolMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/SymbolMatcher.java @@ -59,16 +59,8 @@ public abstract class SymbolMatcher implements NumberParseMatcher { } @Override - public UnicodeSet getLeadCodePoints() { - if (string.isEmpty()) { - // Assumption: for sets from UnicodeSetStaticCache, uniSet == leadCodePoints. - return uniSet; - } - - UnicodeSet leadCodePoints = new UnicodeSet(); - ParsingUtils.putLeadCodePoints(uniSet, leadCodePoints); - ParsingUtils.putLeadCodePoint(string, leadCodePoints); - return leadCodePoints.freeze(); + public boolean smokeTest(StringSegment segment) { + return segment.startsWith(uniSet) || segment.startsWith(string); } @Override diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/UnicodeSetStaticCache.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/UnicodeSetStaticCache.java index edc0e99114..cba2dc9384 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/UnicodeSetStaticCache.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/UnicodeSetStaticCache.java @@ -49,8 +49,6 @@ public class UnicodeSetStaticCache { // Other DIGITS, - NAN_LEAD, - SCIENTIFIC_LEAD, CWCF, // TODO: Check if this is being used and remove it if not. // Combined Separators with Digits (for lead code points) @@ -112,10 +110,6 @@ public class UnicodeSetStaticCache { unicodeSets.put(Key.INFINITY, new UnicodeSet("[∞]").freeze()); unicodeSets.put(Key.DIGITS, new UnicodeSet("[:digit:]").freeze()); - unicodeSets.put(Key.NAN_LEAD, - new UnicodeSet("[NnТтmeՈոс¤НнчTtsҳ\u975e\u1002\u0e9a\u10d0\u0f68\u0644\u0646]") - .freeze()); - unicodeSets.put(Key.SCIENTIFIC_LEAD, new UnicodeSet("[Ee×·е\u0627]").freeze()); unicodeSets.put(Key.CWCF, new UnicodeSet("[:CWCF:]").freeze()); unicodeSets.put(Key.DIGITS_OR_ALL_SEPARATORS, computeUnion(Key.DIGITS, Key.ALL_SEPARATORS)); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ValidationMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ValidationMatcher.java index 913dcf83a1..399e156c6b 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ValidationMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ValidationMatcher.java @@ -3,7 +3,6 @@ package com.ibm.icu.impl.number.parse; import com.ibm.icu.impl.StringSegment; -import com.ibm.icu.text.UnicodeSet; /** * A Matcher used only for post-process validation, not for consuming characters at runtime. @@ -16,8 +15,8 @@ public abstract class ValidationMatcher implements NumberParseMatcher { } @Override - public UnicodeSet getLeadCodePoints() { - return UnicodeSet.EMPTY; + public boolean smokeTest(StringSegment segment) { + return false; } } diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/ExhaustiveNumberTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/ExhaustiveNumberTest.java index e2cabc8e4a..51dbe11ab8 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/ExhaustiveNumberTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/ExhaustiveNumberTest.java @@ -2,6 +2,8 @@ // License & terms of use: http://www.unicode.org/copyright.html#License package com.ibm.icu.dev.test.number; +import static com.ibm.icu.impl.number.parse.UnicodeSetStaticCache.get; + import java.math.BigDecimal; import java.util.Random; @@ -10,8 +12,12 @@ import org.junit.Test; import com.ibm.icu.dev.test.TestFmwk; import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD; +import com.ibm.icu.impl.number.parse.UnicodeSetStaticCache.Key; +import com.ibm.icu.lang.UCharacter; import com.ibm.icu.number.NumberFormatter; import com.ibm.icu.number.Rounder; +import com.ibm.icu.text.DecimalFormatSymbols; +import com.ibm.icu.text.UnicodeSet; import com.ibm.icu.util.ULocale; /** @@ -27,6 +33,60 @@ public class ExhaustiveNumberTest extends TestFmwk { org.junit.Assume.assumeTrue(getExhaustiveness() > 5); } + @Test + public void testSetCoverage() { + // Lenient comma/period should be supersets of strict comma/period; + // it also makes the coverage logic cheaper. + assertTrue("COMMA should be superset of STRICT_COMMA", + get(Key.COMMA).containsAll(get(Key.STRICT_COMMA))); + assertTrue("PERIOD should be superset of STRICT_PERIOD", + get(Key.PERIOD).containsAll(get(Key.STRICT_PERIOD))); + + UnicodeSet decimals = get(Key.STRICT_COMMA).cloneAsThawed().addAll(get(Key.STRICT_PERIOD)) + .freeze(); + UnicodeSet grouping = decimals.cloneAsThawed().addAll(get(Key.OTHER_GROUPING_SEPARATORS)) + .freeze(); + UnicodeSet plusSign = get(Key.PLUS_SIGN); + UnicodeSet minusSign = get(Key.MINUS_SIGN); + UnicodeSet percent = get(Key.PERCENT_SIGN); + UnicodeSet permille = get(Key.PERMILLE_SIGN); + UnicodeSet infinity = get(Key.INFINITY); + + for (ULocale locale : ULocale.getAvailableLocales()) { + DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(locale); + + assertInSet(locale, decimals, dfs.getDecimalSeparatorString()); + assertInSet(locale, grouping, dfs.getGroupingSeparatorString()); + assertInSet(locale, plusSign, dfs.getPlusSignString()); + assertInSet(locale, minusSign, dfs.getMinusSignString()); + assertInSet(locale, percent, dfs.getPercentString()); + assertInSet(locale, permille, dfs.getPerMillString()); + assertInSet(locale, infinity, dfs.getInfinity()); + } + } + + static void assertInSet(ULocale locale, UnicodeSet set, String str) { + if (str.codePointCount(0, str.length()) != 1) { + // Ignore locale strings with more than one code point (usually a bidi mark) + return; + } + assertInSet(locale, set, str.codePointAt(0)); + } + + static void assertInSet(ULocale locale, UnicodeSet set, int cp) { + // If this test case fails, add the specified code point to the corresponding set in + // UnicodeSetStaticCache.java and numparse_unisets.cpp + assertTrue( + locale + + " U+" + + Integer.toHexString(cp) + + " (" + + UCharacter.toString(cp) + + ") should be in " + + set, + set.contains(cp)); + } + @Test public void unlimitedRoundingBigDecimal() { BigDecimal ten10000 = BigDecimal.valueOf(10).pow(10000); diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java index a522a28997..6009be7d15 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java @@ -3,6 +3,7 @@ package com.ibm.icu.dev.test.number; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import org.junit.Test; @@ -21,8 +22,6 @@ import com.ibm.icu.impl.number.parse.ParsingUtils; import com.ibm.icu.impl.number.parse.PercentMatcher; import com.ibm.icu.impl.number.parse.PlusSignMatcher; import com.ibm.icu.impl.number.parse.SeriesMatcher; -import com.ibm.icu.impl.number.parse.UnicodeSetStaticCache; -import com.ibm.icu.impl.number.parse.UnicodeSetStaticCache.Key; import com.ibm.icu.text.DecimalFormatSymbols; import com.ibm.icu.util.Currency; import com.ibm.icu.util.ULocale; @@ -196,7 +195,9 @@ public class NumberParserTest { series.addMatcher(IgnorablesMatcher.DEFAULT); series.freeze(); - assertEquals(UnicodeSetStaticCache.get(Key.PLUS_SIGN), series.getLeadCodePoints()); + assertFalse(series.smokeTest(new StringSegment("x", false))); + assertFalse(series.smokeTest(new StringSegment("-", false))); + assertTrue(series.smokeTest(new StringSegment("+", false))); Object[][] cases = new Object[][] { { "", 0, true }, diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PropertiesTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PropertiesTest.java index bb557cc970..4529f6b916 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PropertiesTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PropertiesTest.java @@ -30,9 +30,9 @@ import org.junit.Test; import com.ibm.icu.dev.test.serializable.SerializableTestUtility; import com.ibm.icu.impl.number.DecimalFormatProperties; +import com.ibm.icu.impl.number.DecimalFormatProperties.ParseMode; import com.ibm.icu.impl.number.Padder.PadPosition; import com.ibm.icu.impl.number.PatternStringParser; -import com.ibm.icu.impl.number.parse.NumberParserImpl.ParseMode; import com.ibm.icu.text.CompactDecimalFormat.CompactStyle; import com.ibm.icu.text.CurrencyPluralInfo; import com.ibm.icu.text.MeasureFormat.FormatWidth; diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/UnicodeSetStaticCacheTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/UnicodeSetStaticCacheTest.java index 7aec4f77f1..ca239267bb 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/UnicodeSetStaticCacheTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/UnicodeSetStaticCacheTest.java @@ -8,82 +8,17 @@ import static org.junit.Assert.assertTrue; import org.junit.Test; import com.ibm.icu.impl.number.parse.UnicodeSetStaticCache.Key; -import com.ibm.icu.lang.UCharacter; -import com.ibm.icu.text.DecimalFormatSymbols; -import com.ibm.icu.text.UnicodeSet; -import com.ibm.icu.util.ULocale; /** + * This test class is thin; most of it was moved to ExhaustiveNumberTest. * @author sffc - * */ public class UnicodeSetStaticCacheTest { - @Test - public void testSetCoverage() { - // Lenient comma/period should be supersets of strict comma/period; - // it also makes the coverage logic cheaper. - assertTrue("COMMA should be superset of STRICT_COMMA", - get(Key.COMMA).containsAll(get(Key.STRICT_COMMA))); - assertTrue("PERIOD should be superset of STRICT_PERIOD", - get(Key.PERIOD).containsAll(get(Key.STRICT_PERIOD))); - - UnicodeSet decimals = get(Key.STRICT_COMMA).cloneAsThawed().addAll(get(Key.STRICT_PERIOD)) - .freeze(); - UnicodeSet grouping = decimals.cloneAsThawed().addAll(get(Key.OTHER_GROUPING_SEPARATORS)) - .freeze(); - UnicodeSet plusSign = get(Key.PLUS_SIGN); - UnicodeSet minusSign = get(Key.MINUS_SIGN); - UnicodeSet percent = get(Key.PERCENT_SIGN); - UnicodeSet permille = get(Key.PERMILLE_SIGN); - UnicodeSet infinity = get(Key.INFINITY); - UnicodeSet nanLead = get(Key.NAN_LEAD); - UnicodeSet scientificLead = get(Key.SCIENTIFIC_LEAD); - - for (ULocale locale : ULocale.getAvailableLocales()) { - DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(locale); - - assertInSet(locale, decimals, dfs.getDecimalSeparatorString()); - assertInSet(locale, grouping, dfs.getGroupingSeparatorString()); - assertInSet(locale, plusSign, dfs.getPlusSignString()); - assertInSet(locale, minusSign, dfs.getMinusSignString()); - assertInSet(locale, percent, dfs.getPercentString()); - assertInSet(locale, permille, dfs.getPerMillString()); - assertInSet(locale, infinity, dfs.getInfinity()); - assertInSet(locale, nanLead, dfs.getNaN().codePointAt(0)); - assertInSet(locale, nanLead, UCharacter.foldCase(dfs.getNaN(), true).codePointAt(0)); - assertInSet(locale, - scientificLead, - UCharacter.foldCase(dfs.getExponentSeparator(), true).codePointAt(0)); - } - } - @Test public void testFrozen() { for (Key key : Key.values()) { assertTrue(get(key).isFrozen()); } } - - static void assertInSet(ULocale locale, UnicodeSet set, String str) { - if (str.codePointCount(0, str.length()) != 1) { - // Ignore locale strings with more than one code point (usually a bidi mark) - return; - } - assertInSet(locale, set, str.codePointAt(0)); - } - - static void assertInSet(ULocale locale, UnicodeSet set, int cp) { - // If this test case fails, add the specified code point to the corresponding set in - // UnicodeSetStaticCache.java and numparse_unisets.cpp - assertTrue( - locale - + " U+" - + Integer.toHexString(cp) - + " (" - + UCharacter.toString(cp) - + ") should be in " - + set, - set.contains(cp)); - } } From 0b6e991bb0cce52b74cccd433455d6738eab5854 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Wed, 21 Mar 2018 06:33:37 +0000 Subject: [PATCH 048/129] ICU-13634 C and J, removing the obsolete "optimize" parameter for NumberParserImpl. X-SVN-Rev: 41132 --- icu4c/source/i18n/numparse_impl.cpp | 10 ++-------- icu4c/source/i18n/numparse_impl.h | 2 +- icu4c/source/test/intltest/numfmtst.cpp | 6 ++++++ .../icu/impl/number/parse/NumberParserImpl.java | 16 +++++----------- .../ibm/icu/impl/number/parse/ParsingUtils.java | 2 +- .../core/src/com/ibm/icu/text/DecimalFormat.java | 4 ++-- .../icu/dev/test/number/NumberParserTest.java | 9 ++++----- 7 files changed, 21 insertions(+), 28 deletions(-) diff --git a/icu4c/source/i18n/numparse_impl.cpp b/icu4c/source/i18n/numparse_impl.cpp index 82c69d4024..e894a1eaab 100644 --- a/icu4c/source/i18n/numparse_impl.cpp +++ b/icu4c/source/i18n/numparse_impl.cpp @@ -79,7 +79,7 @@ NumberParserImpl::createSimpleParser(const Locale& locale, const UnicodeString& NumberParserImpl* NumberParserImpl::createParserFromProperties(const number::impl::DecimalFormatProperties& properties, const DecimalFormatSymbols& symbols, bool parseCurrency, - bool computeLeads, UErrorCode& status) { + UErrorCode& status) { Locale locale = symbols.getLocale(); PropertiesAffixPatternProvider patternInfo(properties, status); CurrencyUnit currency = resolveCurrency(properties, locale, status); @@ -112,9 +112,6 @@ NumberParserImpl::createParserFromProperties(const number::impl::DecimalFormatPr if (parseCurrency || patternInfo.hasCurrencySign()) { parseFlags |= PARSE_FLAG_MONETARY_SEPARATORS; } - if (computeLeads) { - parseFlags |= PARSE_FLAG_OPTIMIZE; - } IgnorablesMatcher ignorables(isStrict ? unisets::DEFAULT_IGNORABLES : unisets::STRICT_IGNORABLES); LocalPointer parser(new NumberParserImpl(parseFlags)); @@ -125,10 +122,7 @@ NumberParserImpl::createParserFromProperties(const number::impl::DecimalFormatPr // The following statements set up the affix matchers. AffixTokenMatcherSetupData affixSetupData = { - currencySymbols, - symbols, - ignorables, - locale}; + currencySymbols, symbols, ignorables, locale}; parser->fLocalMatchers.affixTokenMatcherWarehouse = {&affixSetupData}; parser->fLocalMatchers.affixMatcherWarehouse = {&parser->fLocalMatchers.affixTokenMatcherWarehouse}; parser->fLocalMatchers.affixMatcherWarehouse.createAffixMatchers( diff --git a/icu4c/source/i18n/numparse_impl.h b/icu4c/source/i18n/numparse_impl.h index e740038414..96a259a55f 100644 --- a/icu4c/source/i18n/numparse_impl.h +++ b/icu4c/source/i18n/numparse_impl.h @@ -30,7 +30,7 @@ class NumberParserImpl : public MutableMatcherCollection { static NumberParserImpl* createParserFromProperties( const number::impl::DecimalFormatProperties& properties, const DecimalFormatSymbols& symbols, - bool parseCurrency, bool optimize, UErrorCode& status); + bool parseCurrency, UErrorCode& status); /** * Does NOT take ownership of the matcher. The matcher MUST remain valid for the lifespan of the diff --git a/icu4c/source/test/intltest/numfmtst.cpp b/icu4c/source/test/intltest/numfmtst.cpp index 8eb7b57ec8..46e119756a 100644 --- a/icu4c/source/test/intltest/numfmtst.cpp +++ b/icu4c/source/test/intltest/numfmtst.cpp @@ -403,6 +403,8 @@ UBool NumberFormatTestDataDriven::isParsePass( const NumberFormatTestTuple &tuple, UnicodeString &appendErrorMessage, UErrorCode &status) { + return TRUE; +#if 0 if (U_FAILURE(status)) { return FALSE; } @@ -458,12 +460,15 @@ UBool NumberFormatTestDataDriven::isParsePass( return FALSE; } return TRUE; +#endif } UBool NumberFormatTestDataDriven::isParseCurrencyPass( const NumberFormatTestTuple &tuple, UnicodeString &appendErrorMessage, UErrorCode &status) { + return TRUE; +#if 0 if (U_FAILURE(status)) { return FALSE; } @@ -507,6 +512,7 @@ UBool NumberFormatTestDataDriven::isParseCurrencyPass( return FALSE; } return TRUE; +#endif } //#define NUMFMTST_CACHE_DEBUG 1 diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java index ffd101fa01..95e313651c 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java @@ -76,7 +76,7 @@ public class NumberParserImpl { ParsePosition ppos, DecimalFormatProperties properties, DecimalFormatSymbols symbols) { - NumberParserImpl parser = createParserFromProperties(properties, symbols, false, false); + NumberParserImpl parser = createParserFromProperties(properties, symbols, false); ParsedNumber result = new ParsedNumber(); parser.parse(input, true, result); if (result.success()) { @@ -96,7 +96,7 @@ public class NumberParserImpl { ParsePosition ppos, DecimalFormatProperties properties, DecimalFormatSymbols symbols) { - NumberParserImpl parser = createParserFromProperties(properties, symbols, true, false); + NumberParserImpl parser = createParserFromProperties(properties, symbols, true); ParsedNumber result = new ParsedNumber(); parser.parse(input, true, result); if (result.success()) { @@ -109,10 +109,10 @@ public class NumberParserImpl { } } - public static NumberParserImpl createDefaultParserForLocale(ULocale loc, boolean optimize) { + public static NumberParserImpl createDefaultParserForLocale(ULocale loc) { DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(loc); DecimalFormatProperties properties = PatternStringParser.parseToProperties("0"); - return createParserFromProperties(properties, symbols, false, optimize); + return createParserFromProperties(properties, symbols, false); } /** @@ -125,15 +125,12 @@ public class NumberParserImpl { * The locale's symbols. * @param parseCurrency * True to force a currency match and use monetary separators; false otherwise. - * @param optimize - * True to construct the lead-chars; false to disable. * @return An immutable parser object. */ public static NumberParserImpl createParserFromProperties( DecimalFormatProperties properties, DecimalFormatSymbols symbols, - boolean parseCurrency, - boolean optimize) { + boolean parseCurrency) { ULocale locale = symbols.getULocale(); AffixPatternProvider patternInfo = new PropertiesAffixPatternProvider(properties); @@ -166,9 +163,6 @@ public class NumberParserImpl { if (parseCurrency || patternInfo.hasCurrencySign()) { parseFlags |= ParsingUtils.PARSE_FLAG_MONETARY_SEPARATORS; } - if (optimize) { - parseFlags |= ParsingUtils.PARSE_FLAG_OPTIMIZE; - } IgnorablesMatcher ignorables = isStrict ? IgnorablesMatcher.STRICT : IgnorablesMatcher.DEFAULT; NumberParserImpl parser = new NumberParserImpl(parseFlags); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsingUtils.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsingUtils.java index c46a471705..ec63ce68f4 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsingUtils.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsingUtils.java @@ -21,7 +21,7 @@ public class ParsingUtils { public static final int PARSE_FLAG_USE_FULL_AFFIXES = 0x0100; public static final int PARSE_FLAG_EXACT_AFFIX = 0x0200; public static final int PARSE_FLAG_PLUS_SIGN_ALLOWED = 0x0400; - public static final int PARSE_FLAG_OPTIMIZE = 0x0800; + // public static final int PARSE_FLAG_OPTIMIZE = 0x0800; // no longer used public static void putLeadCodePoints(UnicodeSet input, UnicodeSet output) { for (EntryRange range : input.ranges()) { diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java index 7906416463..15c750362e 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java @@ -2508,8 +2508,8 @@ public class DecimalFormat extends NumberFormat { } assert locale != null; formatter = NumberFormatter.fromDecimalFormat(properties, symbols, exportedProperties).locale(locale); - parser = NumberParserImpl.createParserFromProperties(properties, symbols, false, false); - parserWithCurrency = NumberParserImpl.createParserFromProperties(properties, symbols, true, false); + parser = NumberParserImpl.createParserFromProperties(properties, symbols, false); + parserWithCurrency = NumberParserImpl.createParserFromProperties(properties, symbols, true); } /** diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java index 6009be7d15..b35cd789c9 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java @@ -305,7 +305,7 @@ public class NumberParserTest { properties.setGroupingSize(0); DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(ULocale.ENGLISH); NumberParserImpl parser = NumberParserImpl - .createParserFromProperties(properties, symbols, false, true); + .createParserFromProperties(properties, symbols, false); ParsedNumber result = new ParsedNumber(); parser.parse("12,345.678", true, result); assertEquals("Should not parse with grouping separator", @@ -330,9 +330,8 @@ public class NumberParserTest { int expectedCaseSensitiveChars = (Integer) cas[2]; int expectedCaseFoldingChars = (Integer) cas[3]; - NumberParserImpl caseSensitiveParser = NumberParserImpl.createSimpleParser(ULocale.ENGLISH, - patternString, - ParsingUtils.PARSE_FLAG_OPTIMIZE); + NumberParserImpl caseSensitiveParser = NumberParserImpl + .createSimpleParser(ULocale.ENGLISH, patternString, 0); ParsedNumber result = new ParsedNumber(); caseSensitiveParser.parse(inputString, true, result); assertEquals("Case-Sensitive: " + inputString + " on " + patternString, @@ -341,7 +340,7 @@ public class NumberParserTest { NumberParserImpl caseFoldingParser = NumberParserImpl.createSimpleParser(ULocale.ENGLISH, patternString, - ParsingUtils.PARSE_FLAG_IGNORE_CASE | ParsingUtils.PARSE_FLAG_OPTIMIZE); + ParsingUtils.PARSE_FLAG_IGNORE_CASE); result = new ParsedNumber(); caseFoldingParser.parse(inputString, true, result); assertEquals("Folded: " + inputString + " on " + patternString, From e3180662e2dc53fa343018ce72e5ce7a7b10f56c Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Wed, 21 Mar 2018 09:48:55 +0000 Subject: [PATCH 049/129] ICU-13634 Parsing wrapper is working; data-driven file is updated and passing. The C++ and Java implementations have almost identical behavior according to the data-driven test file, with the only differences seeming to involve overflow and extremely large numbers. X-SVN-Rev: 41134 --- icu4c/source/i18n/decimfmt.cpp | 99 ++++++-- icu4c/source/i18n/numparse_impl.cpp | 5 +- icu4c/source/i18n/numparse_parsednumber.cpp | 35 +++ icu4c/source/i18n/numparse_types.h | 4 +- icu4c/source/i18n/unicode/decimfmt.h | 41 +++- icu4c/source/i18n/unicode/unum.h | 16 +- .../datadrivennumberformattestsuite.cpp | 2 +- icu4c/source/test/intltest/numfmtst.cpp | 10 +- .../numberformattestspecification.txt | 216 +++++++++--------- 9 files changed, 291 insertions(+), 137 deletions(-) diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp index 510039e86b..5fd757daee 100644 --- a/icu4c/source/i18n/decimfmt.cpp +++ b/icu4c/source/i18n/decimfmt.cpp @@ -190,6 +190,14 @@ DecimalFormat::setAttribute(UNumberFormatAttribute attr, int32_t newValue, UErro setMinimumGroupingDigits(newValue); break; + case UNUM_PARSE_CASE_SENSITIVE: + setParseCaseSensitive(static_cast(newValue)); + break; + + case UNUM_SIGN_ALWAYS_SHOWN: + setSignAlwaysShown(static_cast(newValue)); + break; + default: status = U_UNSUPPORTED_ERROR; break; @@ -262,7 +270,7 @@ int32_t DecimalFormat::getAttribute(UNumberFormatAttribute attr, UErrorCode& sta return getSecondaryGroupingSize(); case UNUM_PARSE_NO_EXPONENT: - return getParseNoExponent(); + return isParseNoExponent(); case UNUM_PARSE_DECIMAL_MARK_REQUIRED: return isDecimalPatternMatchRequired(); @@ -273,6 +281,12 @@ int32_t DecimalFormat::getAttribute(UNumberFormatAttribute attr, UErrorCode& sta case UNUM_MINIMUM_GROUPING_DIGITS: return getMinimumGroupingDigits(); + case UNUM_PARSE_CASE_SENSITIVE: + return isParseCaseSensitive(); + + case UNUM_SIGN_ALWAYS_SHOWN: + return isSignAlwaysShown(); + default: status = U_UNSUPPORTED_ERROR; break; @@ -296,10 +310,17 @@ void DecimalFormat::setGroupingUsed(UBool enabled) { } void DecimalFormat::setParseIntegerOnly(UBool value) { + NumberFormat::setParseIntegerOnly(value); // to set field for compatibility fProperties->parseIntegerOnly = value; refreshFormatterNoError(); } +void DecimalFormat::setLenient(UBool enable) { + NumberFormat::setLenient(enable); // to set field for compatibility + fProperties->parseMode = enable ? PARSE_MODE_LENIENT : PARSE_MODE_STRICT; + refreshFormatterNoError(); +} + DecimalFormat::DecimalFormat(const UnicodeString& pattern, DecimalFormatSymbols* symbolsToAdopt, UParseError&, UErrorCode& status) : DecimalFormat(symbolsToAdopt, status) { @@ -450,14 +471,46 @@ DecimalFormat::format(const DecimalQuantity& number, UnicodeString& appendTo, Fi return appendTo; } -void DecimalFormat::parse(const UnicodeString& /*text*/, Formattable& /*result*/, - ParsePosition& /*parsePosition*/) const { - // FIXME +void DecimalFormat::parse(const UnicodeString& text, Formattable& output, + ParsePosition& parsePosition) const { + if (parsePosition.getIndex() < 0 || parsePosition.getIndex() >= text.length()) { + return; + } + + ErrorCode status; + ParsedNumber result; + // Note: if this is a currency instance, currencies will be matched despite the fact that we are not in the + // parseCurrency method (backwards compatibility) + int32_t startIndex = parsePosition.getIndex(); + fParser->parse(text, startIndex, true, result, status); + if (result.success()) { + parsePosition.setIndex(result.charEnd); + result.populateFormattable(output); + } else { + parsePosition.setErrorIndex(startIndex + result.charEnd); + } } -CurrencyAmount* DecimalFormat::parseCurrency(const UnicodeString& /*text*/, ParsePosition& /*pos*/) const { - // FIXME - return nullptr; +CurrencyAmount* DecimalFormat::parseCurrency(const UnicodeString& text, ParsePosition& parsePosition) const { + if (parsePosition.getIndex() < 0 || parsePosition.getIndex() >= text.length()) { + return nullptr; + } + + ErrorCode status; + ParsedNumber result; + // Note: if this is a currency instance, currencies will be matched despite the fact that we are not in the + // parseCurrency method (backwards compatibility) + int32_t startIndex = parsePosition.getIndex(); + fParserWithCurrency->parse(text, startIndex, true, result, status); + if (result.success()) { + parsePosition.setIndex(result.charEnd); + Formattable formattable; + result.populateFormattable(formattable); + return new CurrencyAmount(formattable, result.currencyCode, status); + } else { + parsePosition.setErrorIndex(startIndex + result.charEnd); + return nullptr; + } } const DecimalFormatSymbols* DecimalFormat::getDecimalFormatSymbols(void) const { @@ -535,6 +588,15 @@ void DecimalFormat::setNegativeSuffix(const UnicodeString& newValue) { refreshFormatterNoError(); } +UBool DecimalFormat::isSignAlwaysShown() const { + return fProperties->signAlwaysShown; +} + +void DecimalFormat::setSignAlwaysShown(UBool value) { + fProperties->signAlwaysShown = value; + refreshFormatterNoError(); +} + int32_t DecimalFormat::getMultiplier(void) const { if (fProperties->multiplier != 1) { return fProperties->multiplier; @@ -708,7 +770,7 @@ void DecimalFormat::setDecimalPatternMatchRequired(UBool newValue) { refreshFormatterNoError(); } -UBool DecimalFormat::getParseNoExponent() const { +UBool DecimalFormat::isParseNoExponent() const { return fProperties->parseNoExponent; } @@ -717,6 +779,15 @@ void DecimalFormat::setParseNoExponent(UBool value) { refreshFormatterNoError(); } +UBool DecimalFormat::isParseCaseSensitive() const { + return fProperties->parseCaseSensitive; +} + +void DecimalFormat::setParseCaseSensitive(UBool value) { + fProperties->parseCaseSensitive = value; + refreshFormatterNoError(); +} + UnicodeString& DecimalFormat::toPattern(UnicodeString& result) const { // Pull some properties from exportedProperties and others from properties // to keep affix patterns intact. In particular, pull rounding properties @@ -907,13 +978,13 @@ void DecimalFormat::refreshFormatter(UErrorCode& status) { *fProperties, *fSymbols, *fWarehouse, *fExportedProperties, status).locale( locale)), status); - // fParser.adoptInsteadAndCheckErrorCode( - // NumberParserImpl::createParserFromProperties( - // *fProperties, *fSymbols, false, false, status), status); + fParser.adoptInsteadAndCheckErrorCode( + NumberParserImpl::createParserFromProperties( + *fProperties, *fSymbols, false, status), status); - // fParserWithCurrency.adoptInsteadAndCheckErrorCode( - // NumberParserImpl::createParserFromProperties( - // *fProperties, *fSymbols, true, false, status), status); + fParserWithCurrency.adoptInsteadAndCheckErrorCode( + NumberParserImpl::createParserFromProperties( + *fProperties, *fSymbols, true, status), status); } void DecimalFormat::refreshFormatterNoError() { diff --git a/icu4c/source/i18n/numparse_impl.cpp b/icu4c/source/i18n/numparse_impl.cpp index e894a1eaab..d8338ae0cf 100644 --- a/icu4c/source/i18n/numparse_impl.cpp +++ b/icu4c/source/i18n/numparse_impl.cpp @@ -112,10 +112,13 @@ NumberParserImpl::createParserFromProperties(const number::impl::DecimalFormatPr if (parseCurrency || patternInfo.hasCurrencySign()) { parseFlags |= PARSE_FLAG_MONETARY_SEPARATORS; } - IgnorablesMatcher ignorables(isStrict ? unisets::DEFAULT_IGNORABLES : unisets::STRICT_IGNORABLES); LocalPointer parser(new NumberParserImpl(parseFlags)); + parser->fLocalMatchers.ignorables = { + isStrict ? unisets::STRICT_IGNORABLES : unisets::DEFAULT_IGNORABLES}; + IgnorablesMatcher& ignorables = parser->fLocalMatchers.ignorables; + ////////////////////// /// AFFIX MATCHERS /// ////////////////////// diff --git a/icu4c/source/i18n/numparse_parsednumber.cpp b/icu4c/source/i18n/numparse_parsednumber.cpp index c9b68a245e..b9cc54e627 100644 --- a/icu4c/source/i18n/numparse_parsednumber.cpp +++ b/icu4c/source/i18n/numparse_parsednumber.cpp @@ -10,9 +10,12 @@ #define UNISTR_FROM_STRING_EXPLICIT #include "numparse_types.h" +#include "number_decimalquantity.h" #include using namespace icu; +using namespace icu::number; +using namespace icu::number::impl; using namespace icu::numparse; using namespace icu::numparse::impl; @@ -78,6 +81,38 @@ double ParsedNumber::getDouble() const { return d; } +void ParsedNumber::populateFormattable(Formattable& output) const { + bool sawNegative = 0 != (flags & FLAG_NEGATIVE); + bool sawNaN = 0 != (flags & FLAG_NAN); + bool sawInfinity = 0 != (flags & FLAG_INFINITY); + + // Check for NaN, infinity, and -0.0 + if (sawNaN) { + output.setDouble(NAN); + return; + } + if (sawInfinity) { + if (sawNegative) { + output.setDouble(-INFINITY); + return; + } else { + output.setDouble(INFINITY); + return; + } + } + if (quantity.isZero() && sawNegative) { + output.setDouble(-0.0); + return; + } + + // All other numbers + LocalPointer actualQuantity(new DecimalQuantity(quantity)); + if (0 != (flags & FLAG_NEGATIVE)) { + actualQuantity->multiplyBy(-1); + } + output.adoptDecimalQuantity(actualQuantity.orphan()); +} + bool ParsedNumber::isBetterThan(const ParsedNumber& other) { // Favor results with strictly more characters consumed. return charEnd > other.charEnd; diff --git a/icu4c/source/i18n/numparse_types.h b/icu4c/source/i18n/numparse_types.h index 2f8d31fc76..dfaadd9cb9 100644 --- a/icu4c/source/i18n/numparse_types.h +++ b/icu4c/source/i18n/numparse_types.h @@ -46,7 +46,7 @@ enum ParseFlags { PARSE_FLAG_USE_FULL_AFFIXES = 0x0100, PARSE_FLAG_EXACT_AFFIX = 0x0200, PARSE_FLAG_PLUS_SIGN_ALLOWED = 0x0400, - PARSE_FLAG_OPTIMIZE = 0x0800, + // PARSE_FLAG_OPTIMIZE = 0x0800, // no longer used }; @@ -156,6 +156,8 @@ class ParsedNumber { double getDouble() const; + void populateFormattable(Formattable& output) const; + bool isBetterThan(const ParsedNumber& other); }; diff --git a/icu4c/source/i18n/unicode/decimfmt.h b/icu4c/source/i18n/unicode/decimfmt.h index 75dc8d5f44..dcae4e2ae0 100644 --- a/icu4c/source/i18n/unicode/decimfmt.h +++ b/icu4c/source/i18n/unicode/decimfmt.h @@ -822,6 +822,15 @@ class U_I18N_API DecimalFormat : public NumberFormat { */ void setParseIntegerOnly(UBool value) U_OVERRIDE; + /** + * Sets whether lenient parsing should be enabled (it is off by default). + * + * @param enable \c TRUE if lenient parsing should be used, + * \c FALSE otherwise. + * @stable ICU 4.8 + */ + void setLenient(UBool enable) U_OVERRIDE; + /** * Create a DecimalFormat from the given pattern and symbols. * Use this constructor when you need to completely customize the @@ -1266,6 +1275,19 @@ class U_I18N_API DecimalFormat : public NumberFormat { */ virtual void setNegativeSuffix(const UnicodeString& newValue); + /** + * Whether to show the plus sign on positive (non-negative) numbers; for example, "+12" + * @internal Technical Preview + */ + UBool isSignAlwaysShown() const; + + /** + * Set whether to show the plus sign on positive (non-negative) numbers; for example, "+12" + * @param value The new setting for whether to show plus sign on positive numbers + * @internal Technical Preview + */ + void setSignAlwaysShown(UBool value); + /** * Get the multiplier for use in percent, permill, etc. * For a percentage, set the suffixes to have "%" and the multiplier to be 100. @@ -1655,7 +1677,7 @@ class U_I18N_API DecimalFormat : public NumberFormat { * @see #setParseNoExponent * @internal This API is a technical preview. It may change in an upcoming release. */ - UBool getParseNoExponent() const; + UBool isParseNoExponent() const; /** * {@icu} Specifies whether to stop parsing when an exponent separator is encountered. For @@ -1667,6 +1689,23 @@ class U_I18N_API DecimalFormat : public NumberFormat { */ void setParseNoExponent(UBool value); + /** + * {@icu} Returns whether parsing is sensitive to case (lowercase/uppercase). + * + * @see #setParseCaseSensitive + * @internal This API is a technical preview. It may change in an upcoming release. + */ + UBool isParseCaseSensitive() const; + + /** + * {@icu} Whether to pay attention to case when parsing; default is to ignore case (perform + * case-folding). For example, "A" == "a" in case-insensitive but not case-sensitive mode. + * + * Currency codes are never case-folded. For example, "us$1.00" will not parse in case-insensitive + * mode, even though "US$1.00" parses. + */ + void setParseCaseSensitive(UBool value); + /** * Synthesizes a pattern string that represents the current state diff --git a/icu4c/source/i18n/unicode/unum.h b/icu4c/source/i18n/unicode/unum.h index ff251ff269..99864a169b 100644 --- a/icu4c/source/i18n/unicode/unum.h +++ b/icu4c/source/i18n/unicode/unum.h @@ -1052,7 +1052,7 @@ typedef enum UNumberFormatAttribute { * Default: 0 (unset) * @stable ICU 50 */ - UNUM_PARSE_NO_EXPONENT, + UNUM_PARSE_NO_EXPONENT = 0x1001, /** * if this attribute is set to 1, specifies that, if the pattern contains a @@ -1067,7 +1067,19 @@ typedef enum UNumberFormatAttribute { /* The following cannot be #ifndef U_HIDE_INTERNAL_API, needed in .h file variable declararions */ /** Limit of boolean attributes. * @internal */ - UNUM_LIMIT_BOOLEAN_ATTRIBUTE = 0x1003 + UNUM_LIMIT_BOOLEAN_ATTRIBUTE = 0x1003, + + /** + * Whether parsing is sensitive to case (lowercase/uppercase). + * @internal This API is a technical preview. It may change in an upcoming release. + */ + UNUM_PARSE_CASE_SENSITIVE = 0x1004, + + /** + * Formatting: whether to show the plus sign on non-negative numbers. + * @internal This API is a technical preview. It may change in an upcoming release. + */ + UNUM_SIGN_ALWAYS_SHOWN = 0x1005, } UNumberFormatAttribute; /** diff --git a/icu4c/source/test/intltest/datadrivennumberformattestsuite.cpp b/icu4c/source/test/intltest/datadrivennumberformattestsuite.cpp index 9af8fdfd52..c8f10d1326 100644 --- a/icu4c/source/test/intltest/datadrivennumberformattestsuite.cpp +++ b/icu4c/source/test/intltest/datadrivennumberformattestsuite.cpp @@ -99,7 +99,7 @@ void DataDrivenNumberFormatTestSuite::run(const char *fileName, UBool runAllTest : breaksC(); UBool actualSuccess = isPass(fTuple, errorMessage, status); if (shouldFail && actualSuccess) { - showFailure("Expected failure, but passed"); + showFailure("Expected failure, but passed: " + errorMessage); break; } else if (!shouldFail && !actualSuccess) { showFailure(errorMessage); diff --git a/icu4c/source/test/intltest/numfmtst.cpp b/icu4c/source/test/intltest/numfmtst.cpp index 46e119756a..35825b62cf 100644 --- a/icu4c/source/test/intltest/numfmtst.cpp +++ b/icu4c/source/test/intltest/numfmtst.cpp @@ -231,7 +231,7 @@ static void adjustDecimalFormat( fmt.setNegativeSuffix(tuple.negativeSuffix); } if (tuple.signAlwaysShownFlag) { - // Not currently supported + fmt.setSignAlwaysShown(tuple.signAlwaysShown != 0); } if (tuple.localizedPatternFlag) { UErrorCode status = U_ZERO_ERROR; @@ -259,7 +259,7 @@ static void adjustDecimalFormat( } } if (tuple.parseCaseSensitiveFlag) { - // TODO: Fill this in when support is added in ICU4C + fmt.setParseCaseSensitive(tuple.parseCaseSensitive != 0); } } @@ -403,8 +403,6 @@ UBool NumberFormatTestDataDriven::isParsePass( const NumberFormatTestTuple &tuple, UnicodeString &appendErrorMessage, UErrorCode &status) { - return TRUE; -#if 0 if (U_FAILURE(status)) { return FALSE; } @@ -460,15 +458,12 @@ UBool NumberFormatTestDataDriven::isParsePass( return FALSE; } return TRUE; -#endif } UBool NumberFormatTestDataDriven::isParseCurrencyPass( const NumberFormatTestTuple &tuple, UnicodeString &appendErrorMessage, UErrorCode &status) { - return TRUE; -#if 0 if (U_FAILURE(status)) { return FALSE; } @@ -512,7 +507,6 @@ UBool NumberFormatTestDataDriven::isParseCurrencyPass( return FALSE; } return TRUE; -#endif } //#define NUMFMTST_CACHE_DEBUG 1 diff --git a/icu4c/source/test/testdata/numberformattestspecification.txt b/icu4c/source/test/testdata/numberformattestspecification.txt index f74a6566aa..8aa1bb44ea 100644 --- a/icu4c/source/test/testdata/numberformattestspecification.txt +++ b/icu4c/source/test/testdata/numberformattestspecification.txt @@ -736,7 +736,7 @@ parse output breaks 5,347.25 5347.25 JK (5,347.25 -5347.25 J // S is successful at parsing this as -5347.25 in lenient mode --5,347.25 -5347.25 CJK +-5,347.25 -5347.25 JK +3.52E4 35200 (34.8E-3) -0.0348 // JDK stops parsing at the spaces. JDK doesn't see space as a grouping separator @@ -745,7 +745,7 @@ parse output breaks // J doesn't allow trailing separators before E but C does (34,,25,E-1) -342.5 J (34 25 E-1) -342.5 JK -(34,,25 E-1) -342.5 CJK +(34,,25 E-1) -342.5 JK // Spaces are not allowed after exponent symbol // C parses up to the E but J bails (34 25E -1) -3425 JK @@ -796,16 +796,14 @@ set locale en set pattern #,##0.0###+;#- begin parse output breaks -// C sees this as -3426, don't understand why. // J and K just bail. -3426 3426 JKC +3426 3426 JK 3426+ 3426 -// J bails; C and K see -34 -34 d1+ 34 JKC +// J bails; K sees -34 +34 d1+ 34 JK // JDK sees this as -1234 for some reason // J bails b/c of trailing separators -// C parses until trailing separators, but sees -1234 -1,234,,,+ 1234 JKC +1,234,,,+ 1234 JK 1,234- -1234 // J bails because of trailing separators 1,234,- -1234 J @@ -861,8 +859,8 @@ parse output breaks +1,234.5 1234.5 // Comma after decimal means parse to a comma +1,23,456.78,9 123456.78 -// C and J fail upon seeing the second decimal point -+1,23,456.78.9 123456.78 CJ +// J fails upon seeing the second decimal point ++1,23,456.78.9 123456.78 J +79 79 +79 79 + 79 fail @@ -875,10 +873,10 @@ set pattern #E0 set lenient 0 begin parse output breaks -123 fail CJK +123 fail JK 123E1 1230 123E0 123 -123E fail CJK +123E fail JK test parse strict without prefix/suffix set locale en @@ -900,7 +898,7 @@ begin parse output breaks 35 35 // S accepts leading plus signs -+35 35 CJK ++35 35 JK -35 -35 2.63 2 -39.99 -39 @@ -1027,19 +1025,19 @@ $53.45 fail USD J 53.45 USD 53.45 USD 53.45 GBP 53.45 GBP USD 53.45 53.45 USD J -53.45USD 53.45 USD CJ +53.45USD 53.45 USD J USD53.45 53.45 USD (7.92) USD -7.92 USD (7.92) GBP -7.92 GBP (7.926) USD -7.926 USD -(7.926 USD) -7.926 USD CJ -(USD 7.926) -7.926 USD CJ -USD (7.926) -7.926 USD CJ -USD (7.92) -7.92 USD CJ -(7.92)USD -7.92 USD CJ -USD(7.92) -7.92 USD CJ +(7.926 USD) -7.926 USD J +(USD 7.926) -7.926 USD J +USD (7.926) -7.926 USD J +USD (7.92) -7.92 USD J +(7.92)USD -7.92 USD J +USD(7.92) -7.92 USD J (8) USD -8 USD --8 USD -8 USD C +-8 USD -8 USD 67 USD 67 USD 53.45$ fail USD US Dollars 53.45 53.45 USD J @@ -1047,20 +1045,20 @@ US Dollars 53.45 53.45 USD J US Dollar 53.45 53.45 USD J 53.45 US Dollar 53.45 USD US Dollars53.45 53.45 USD -53.45US Dollars 53.45 USD CJ +53.45US Dollars 53.45 USD J US Dollar53.45 53.45 USD US Dollat53.45 fail USD -53.45US Dollar 53.45 USD CJ -US Dollars (53.45) -53.45 USD CJ +53.45US Dollar 53.45 USD J +US Dollars (53.45) -53.45 USD J (53.45) US Dollars -53.45 USD (53.45) Euros -53.45 EUR -US Dollar (53.45) -53.45 USD CJ +US Dollar (53.45) -53.45 USD J (53.45) US Dollar -53.45 USD -US Dollars(53.45) -53.45 USD CJ -(53.45)US Dollars -53.45 USD CJ -US Dollar(53.45) -53.45 USD CJ +US Dollars(53.45) -53.45 USD J +(53.45)US Dollars -53.45 USD J +US Dollar(53.45) -53.45 USD J US Dollat(53.45) fail USD -(53.45)US Dollar -53.45 USD CJ +(53.45)US Dollar -53.45 USD J test parse currency ISO negative @@ -1074,14 +1072,14 @@ $53.45 fail USD J 53.45 USD 53.45 USD 53.45 GBP 53.45 GBP USD 53.45 53.45 USD J -53.45USD 53.45 USD CJ +53.45USD 53.45 USD J USD53.45 53.45 USD -7.92 USD -7.92 USD -7.92 GBP -7.92 GBP -7.926 USD -7.926 USD -USD -7.926 -7.926 USD CJ --7.92USD -7.92 USD CJ -USD-7.92 -7.92 USD CJ +USD -7.926 -7.926 USD J +-7.92USD -7.92 USD J +USD-7.92 -7.92 USD J -8 USD -8 USD 67 USD 67 USD 53.45$ fail USD @@ -1090,10 +1088,10 @@ US Dollars 53.45 53.45 USD J US Dollar 53.45 53.45 USD J 53.45 US Dollar 53.45 USD US Dollars53.45 53.45 USD -53.45US Dollars 53.45 USD CJ +53.45US Dollars 53.45 USD J US Dollar53.45 53.45 USD US Dollat53.45 fail USD -53.45US Dollar 53.45 USD CJ +53.45US Dollar 53.45 USD J test parse currency long @@ -1108,19 +1106,19 @@ $53.45 fail USD J 53.45 USD 53.45 USD 53.45 GBP 53.45 GBP USD 53.45 53.45 USD J -53.45USD 53.45 USD CJ +53.45USD 53.45 USD J USD53.45 53.45 USD (7.92) USD -7.92 USD (7.92) GBP -7.92 GBP (7.926) USD -7.926 USD -(7.926 USD) -7.926 USD CJ -(USD 7.926) -7.926 USD CJ -USD (7.926) -7.926 USD CJ -USD (7.92) -7.92 USD CJ -(7.92)USD -7.92 USD CJ -USD(7.92) -7.92 USD CJ +(7.926 USD) -7.926 USD J +(USD 7.926) -7.926 USD J +USD (7.926) -7.926 USD J +USD (7.92) -7.92 USD J +(7.92)USD -7.92 USD J +USD(7.92) -7.92 USD J (8) USD -8 USD --8 USD -8 USD C +-8 USD -8 USD 67 USD 67 USD // J throws a NullPointerException on the next case 53.45$ fail USD @@ -1129,10 +1127,10 @@ US Dollars 53.45 53.45 USD J US Dollar 53.45 53.45 USD J 53.45 US Dollar 53.45 USD US Dollars53.45 53.45 USD -53.45US Dollars 53.45 USD CJ +53.45US Dollars 53.45 USD J US Dollar53.45 53.45 USD US Dollat53.45 fail USD -53.45US Dollar 53.45 USD CJ +53.45US Dollar 53.45 USD J test parse currency short @@ -1146,19 +1144,19 @@ $53.45 fail USD J 53.45 USD 53.45 USD 53.45 GBP 53.45 GBP USD 53.45 53.45 USD J -53.45USD 53.45 USD CJ +53.45USD 53.45 USD J USD53.45 53.45 USD (7.92) USD -7.92 USD (7.92) GBP -7.92 GBP (7.926) USD -7.926 USD -(7.926 USD) -7.926 USD CJ -(USD 7.926) -7.926 USD CJ -USD (7.926) -7.926 USD CJ -USD (7.92) -7.92 USD CJ -(7.92)USD -7.92 USD CJ -USD(7.92) -7.92 USD CJ +(7.926 USD) -7.926 USD J +(USD 7.926) -7.926 USD J +USD (7.926) -7.926 USD J +USD (7.92) -7.92 USD J +(7.92)USD -7.92 USD J +USD(7.92) -7.92 USD J (8) USD -8 USD --8 USD -8 USD C +-8 USD -8 USD 67 USD 67 USD 53.45$ fail USD US Dollars 53.45 53.45 USD J @@ -1166,10 +1164,10 @@ US Dollars 53.45 53.45 USD J US Dollar 53.45 53.45 USD J 53.45 US Dollar 53.45 USD US Dollars53.45 53.45 USD -53.45US Dollars 53.45 USD CJ +53.45US Dollars 53.45 USD J US Dollar53.45 53.45 USD US Dollat53.45 fail USD -53.45US Dollar 53.45 USD CJ +53.45US Dollar 53.45 USD J test parse currency short prefix @@ -1180,12 +1178,12 @@ parse output outputCurrency breaks 53.45 fail GBP £53.45 53.45 GBP $53.45 fail USD J -53.45 USD 53.45 USD C -53.45 GBP 53.45 GBP C +53.45 USD 53.45 USD +53.45 GBP 53.45 GBP USD 53.45 53.45 USD J -53.45USD 53.45 USD CJ +53.45USD 53.45 USD J USD53.45 53.45 USD -// P fails these because '(' is an incomplete prefix. +// C and P fail these because '(' is an incomplete prefix. (7.92) USD -7.92 USD CJP (7.92) GBP -7.92 GBP CJP (7.926) USD -7.926 USD CJP @@ -1196,17 +1194,17 @@ USD (7.92) -7.92 USD CJP (7.92)USD -7.92 USD CJP USD(7.92) -7.92 USD CJP (8) USD -8 USD CJP --8 USD -8 USD C -67 USD 67 USD C +-8 USD -8 USD +67 USD 67 USD 53.45$ fail USD US Dollars 53.45 53.45 USD J 53.45 US Dollars 53.45 USD US Dollar 53.45 53.45 USD J 53.45 US Dollar 53.45 USD US Dollars53.45 53.45 USD -53.45US Dollars 53.45 USD CJ +53.45US Dollars 53.45 USD J US Dollar53.45 53.45 USD -53.45US Dollar 53.45 USD CJ +53.45US Dollar 53.45 USD J test format foreign currency set locale fa_IR @@ -1225,10 +1223,10 @@ parse output outputCurrency breaks \u0631\u06cc\u0627\u0644 \u06F1\u06F2\u06F3\u06F5 1235 IRR IRR \u06F1\u06F2\u06F3\u06F5 1235 IRR // P fails here because this currency name is in the Trie only, but it has the same prefix as the non-Trie currency -\u0631\u06cc\u0627\u0644 \u0627\u06cc\u0631\u0627\u0646 \u06F1\u06F2\u06F3\u06F5 1235 IRR P +\u0631\u06cc\u0627\u0644 \u0627\u06cc\u0631\u0627\u0646 \u06F1\u06F2\u06F3\u06F5 1235 IRR CP IRR 1235 1235 IRR \u0631\u06cc\u0627\u0644 1235 1235 IRR -\u0631\u06cc\u0627\u0644 \u0627\u06cc\u0631\u0627\u0646 1235 1235 IRR P +\u0631\u06cc\u0627\u0644 \u0627\u06cc\u0631\u0627\u0646 1235 1235 IRR CP test parse foreign currency ISO set pattern \u00a4\u00a4 0.00;\u00a4\u00a4 -# @@ -1237,10 +1235,10 @@ begin parse output outputCurrency breaks \u0631\u06cc\u0627\u0644 \u06F1\u06F2\u06F3\u06F5 1235 IRR IRR \u06F1\u06F2\u06F3\u06F5 1235 IRR -\u0631\u06cc\u0627\u0644 \u0627\u06cc\u0631\u0627\u0646 \u06F1\u06F2\u06F3\u06F5 1235 IRR P +\u0631\u06cc\u0627\u0644 \u0627\u06cc\u0631\u0627\u0646 \u06F1\u06F2\u06F3\u06F5 1235 IRR CP IRR 1235 1235 IRR \u0631\u06cc\u0627\u0644 1235 1235 IRR -\u0631\u06cc\u0627\u0644 \u0627\u06cc\u0631\u0627\u0646 1235 1235 IRR P +\u0631\u06cc\u0627\u0644 \u0627\u06cc\u0631\u0627\u0646 1235 1235 IRR CP test parse foreign currency full set pattern \u00a4\u00a4\u00a4 0.00;\u00a4\u00a4\u00a4 -# @@ -1249,10 +1247,10 @@ begin parse output outputCurrency breaks \u0631\u06cc\u0627\u0644 \u06F1\u06F2\u06F3\u06F5 1235 IRR IRR \u06F1\u06F2\u06F3\u06F5 1235 IRR -\u0631\u06cc\u0627\u0644 \u0627\u06cc\u0631\u0627\u0646 \u06F1\u06F2\u06F3\u06F5 1235 IRR P +\u0631\u06cc\u0627\u0644 \u0627\u06cc\u0631\u0627\u0646 \u06F1\u06F2\u06F3\u06F5 1235 IRR CP IRR 1235 1235 IRR \u0631\u06cc\u0627\u0644 1235 1235 IRR -\u0631\u06cc\u0627\u0644 \u0627\u06cc\u0631\u0627\u0646 1235 1235 IRR P +\u0631\u06cc\u0627\u0644 \u0627\u06cc\u0631\u0627\u0646 1235 1235 IRR CP test parse currency with foreign symbols symbol english set pattern \u00a4 0.00;\u00a4 (#) @@ -1294,10 +1292,10 @@ begin parse currency output breaks $52.41 USD 52.41 USD52.41 USD 52.41 K -\u20ac52.41 USD fail P -EUR52.41 USD fail P -$52.41 EUR fail P -USD52.41 EUR fail P +\u20ac52.41 USD fail CP +EUR52.41 USD fail CP +$52.41 EUR fail CP +USD52.41 EUR fail CP \u20ac52.41 EUR 52.41 K EUR52.41 EUR 52.41 @@ -1307,11 +1305,11 @@ set locale en_US set lenient 0 begin parse output outputCurrency breaks -$53.45 53.45 USD P +$53.45 53.45 USD CP 53.45 USD 53.45 USD USD 53.45 fail USD 53.45USD fail USD -USD53.45 53.45 USD P +USD53.45 53.45 USD CP (7.92) USD -7.92 USD (7.92) EUR -7.92 EUR (7.926) USD -7.926 USD @@ -1329,9 +1327,9 @@ US Dollars 53.45 fail USD 53.45 US Dollars 53.45 USD US Dollar 53.45 fail USD 53.45 US Dollar 53.45 USD -US Dollars53.45 53.45 USD P +US Dollars53.45 53.45 USD CP 53.45US Dollars fail USD -US Dollar53.45 53.45 USD P +US Dollar53.45 53.45 USD CP US Dollat53.45 fail USD 53.45US Dollar fail USD US Dollars (53.45) fail USD @@ -1360,14 +1358,14 @@ begin pattern parse output breaks // K doesn't support this feature. 0 123 123 -0 123. fail CJK -0 1.23 fail CJK +0 123. fail JK +0 1.23 fail JK 0 -513 -513 -0 -513. fail CJK -0 -5.13 fail CJK +0 -513. fail JK +0 -5.13 fail JK 0.0 123 fail K -0.0 123. 123 C -0.0 1.23 1.23 C +0.0 123. 123 +0.0 1.23 1.23 0.0 -513 fail K 0.0 -513. -513 0.0 -5.13 -5.13 @@ -1397,17 +1395,17 @@ Aa1.23 0 1.23 AA1.23 1 fail // J and K do not support case-insensitive parsing for prefix/suffix. // J supports it for the exponent separator, but not K. -AA1.23 0 1.23 CJK +AA1.23 0 1.23 JK aa1.23 1 fail -aa1.23 0 1.23 CJK +aa1.23 0 1.23 JK Aa1.23E3 1 1230 Aa1.23E3 0 1230 -Aa1.23e3 1 1.23 CJ +Aa1.23e3 1 1.23 J Aa1.23e3 0 1230 K NaN 1 NaN K NaN 0 NaN K nan 1 fail -nan 0 NaN CJK +nan 0 NaN JK test parse infinity and scientific notation overflow set locale en @@ -1422,12 +1420,12 @@ NaN NaN K -1E-99999999999999 -0.0 1E2147483648 Inf K 1E2147483647 Inf K -1E2147483646 1E2147483646 +1E2147483646 1E2147483646 C 1E-2147483649 0 1E-2147483648 0 // P returns zero here 1E-2147483647 1E-2147483647 P -1E-2147483646 1E-2147483646 +1E-2147483646 1E-2147483646 C test format push limits set locale en @@ -1439,7 +1437,7 @@ maxFractionDigits format output breaks 100 987654321987654321 987654321987654321.00 C 100 987654321.987654321 987654321.987654321 C 100 9999999999999.9950000000001 9999999999999.9950000000001 C -2 9999999999999.9950000000001 10000000000000.00 C +2 9999999999999.9950000000001 10000000000000.00 2 9999999.99499999 9999999.99 // K doesn't support halfDown rounding mode? 2 9999999.995 9999999.99 K @@ -1491,13 +1489,13 @@ y gh56 -56 JK y g h56 -56 JK // S stops parsing after the 'i' for these and returns -56 // C stops before the 'i' and gets 56 -56ijk -56 CJK -56i jk -56 CJK -56ij k -56 CJK -56i‎j‎k -56 CJK -56ijk -56 CJK -56i j‎k -56 CJK -56‎i jk -56 CJK +56ijk -56 JK +56i jk -56 JK +56ij k -56 JK +56i‎j‎k -56 JK +56ijk -56 JK +56i j‎k -56 JK +56‎i jk -56 JK // S and C get 56 (accepts ' ' gs grouping); J and K get null 5 6 fail CP 5‎6 5 JK @@ -1546,7 +1544,7 @@ parse output breaks 55% 0.55 // J and K get null // P requires the symbol to be present and gets 55 -55 0.55 JKP +55 0.55 CJKP test trailing grouping separators in pattern // This test is for #13115 @@ -1565,8 +1563,8 @@ begin pattern format output breaks 0 -15 -15 0; -15 -15 -// C, J, and K still prepend a '-' even though the pattern says otherwise -0;0 -15 15 CJK +// J and K still prepend a '-' even though the pattern says otherwise +0;0 -15 15 JK test percentage multiplier parsing // This test is for #13129 @@ -1582,9 +1580,9 @@ set pattern 0 set signAlwaysShown 1 begin format output breaks -// C, J and K do not support this feature -42 +42 CJK -0 +0 CJK +// J and K do not support this feature +42 +42 JK +0 +0 JK -42 -42 test parse strict with plus sign @@ -1595,14 +1593,14 @@ begin lenient parse output breaks 1 42 42 1 -42 -42 -1 +42 42 CJK +1 +42 42 JK 1 0 0 -1 +0 0 CJK -0 42 fail CJK +1 +0 0 JK +0 42 fail JK 0 -42 -42 -0 +42 42 CJK -0 0 fail CJK -0 +0 0 CJK +0 +42 42 JK +0 0 fail JK +0 +0 0 JK From 23d76d88630ecee02515e2c8f5c8769cc795ae23 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Fri, 23 Mar 2018 00:56:16 +0000 Subject: [PATCH 050/129] ICU-13634 Adding integer overflow logic to ICU4C number pipeline in places where it is in ICU4J. X-SVN-Rev: 41136 --- icu4c/source/common/putil.cpp | 24 ++++++++++++++++++ icu4c/source/common/putilimp.h | 26 ++++++++++++++++++++ icu4c/source/i18n/number_decimalquantity.cpp | 10 +++++--- icu4c/source/i18n/number_decimalquantity.h | 3 ++- icu4c/source/i18n/numparse_decimal.cpp | 10 +++++--- icu4c/source/test/cintltst/putiltst.c | 14 +++++++++++ 6 files changed, 78 insertions(+), 9 deletions(-) diff --git a/icu4c/source/common/putil.cpp b/icu4c/source/common/putil.cpp index 83f08ac070..452e2fd79c 100644 --- a/icu4c/source/common/putil.cpp +++ b/icu4c/source/common/putil.cpp @@ -533,6 +533,30 @@ uprv_fmin(double x, double y) return (x > y ? y : x); } +#include + +U_CAPI UBool U_EXPORT2 +uprv_add32_overflow(int32_t a, int32_t b, int32_t* res) { + // NOTE: Some compilers (GCC, Clang) have primitives available, like __builtin_add_overflow. + // This function could be optimized by calling one of those primitives. + auto a64 = static_cast(a); + auto b64 = static_cast(b); + int64_t res64 = a64 + b64; + *res = static_cast(res64); + return res64 != *res; +} + +U_CAPI UBool U_EXPORT2 +uprv_mul32_overflow(int32_t a, int32_t b, int32_t* res) { + // NOTE: Some compilers (GCC, Clang) have primitives available, like __builtin_mul_overflow. + // This function could be optimized by calling one of those primitives. + auto a64 = static_cast(a); + auto b64 = static_cast(b); + int64_t res64 = a64 * b64; + *res = static_cast(res64); + return res64 != *res; +} + /** * Truncates the given double. * trunc(3.3) = 3.0, trunc (-3.3) = -3.0 diff --git a/icu4c/source/common/putilimp.h b/icu4c/source/common/putilimp.h index eb9b5380f1..8b858df9e3 100644 --- a/icu4c/source/common/putilimp.h +++ b/icu4c/source/common/putilimp.h @@ -391,6 +391,32 @@ U_INTERNAL double U_EXPORT2 uprv_log(double d); */ U_INTERNAL double U_EXPORT2 uprv_round(double x); +/** + * Adds the signed integers a and b, storing the result in res. + * Checks for signed integer overflow. + * Similar to the GCC/Clang extension __builtin_add_overflow + * + * @param a The first operand. + * @param b The second operand. + * @param res a + b + * @return true if overflow occurred; false if no overflow occurred. + * @internal + */ +U_INTERNAL UBool U_EXPORT2 uprv_add32_overflow(int32_t a, int32_t b, int32_t* res); + +/** + * Multiplies the signed integers a and b, storing the result in res. + * Checks for signed integer overflow. + * Similar to the GCC/Clang extension __builtin_mul_overflow + * + * @param a The first multiplicand. + * @param b The second multiplicand. + * @param res a * b + * @return true if overflow occurred; false if no overflow occurred. + * @internal + */ +U_INTERNAL UBool U_EXPORT2 uprv_mul32_overflow(int32_t a, int32_t b, int32_t* res); + #if 0 /** * Returns the number of digits after the decimal point in a double number x. diff --git a/icu4c/source/i18n/number_decimalquantity.cpp b/icu4c/source/i18n/number_decimalquantity.cpp index 6200726446..c00d1a126a 100644 --- a/icu4c/source/i18n/number_decimalquantity.cpp +++ b/icu4c/source/i18n/number_decimalquantity.cpp @@ -192,12 +192,14 @@ int32_t DecimalQuantity::getMagnitude() const { return scale + precision - 1; } -void DecimalQuantity::adjustMagnitude(int32_t delta) { +bool DecimalQuantity::adjustMagnitude(int32_t delta) { if (precision != 0) { - // TODO: How to handle overflow cases? - scale += delta; - origDelta += delta; + // i.e., scale += delta; origDelta += delta + bool overflow = uprv_add32_overflow(scale, delta, &scale); + overflow = uprv_add32_overflow(origDelta, delta, &origDelta) || overflow; + return overflow; } + return false; } StandardPlural::Form DecimalQuantity::getStandardPlural(const PluralRules *rules) const { diff --git a/icu4c/source/i18n/number_decimalquantity.h b/icu4c/source/i18n/number_decimalquantity.h index a776ddc48a..495ba80ec1 100644 --- a/icu4c/source/i18n/number_decimalquantity.h +++ b/icu4c/source/i18n/number_decimalquantity.h @@ -100,8 +100,9 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { * this method with delta=-3 will change the value to "1.23456". * * @param delta The number of magnitudes of ten to change by. + * @return true if integer overflow occured; false otherwise. */ - void adjustMagnitude(int32_t delta); + bool adjustMagnitude(int32_t delta); /** * @return The power of ten corresponding to the most significant nonzero digit. diff --git a/icu4c/source/i18n/numparse_decimal.cpp b/icu4c/source/i18n/numparse_decimal.cpp index 7ce0b19044..3853e5a9a9 100644 --- a/icu4c/source/i18n/numparse_decimal.cpp +++ b/icu4c/source/i18n/numparse_decimal.cpp @@ -164,9 +164,11 @@ bool DecimalMatcher::match(StringSegment& segment, ParsedNumber& result, int8_t // Save the digit in the DecimalQuantity or scientific adjustment. if (exponentSign != 0) { - int nextExponent = digit + exponent * 10; - if (nextExponent < exponent) { - // Overflow + int32_t nextExponent; + // i.e., nextExponent = exponent * 10 + digit + UBool overflow = uprv_mul32_overflow(exponent, 10, &nextExponent) || + uprv_add32_overflow(nextExponent, digit, &nextExponent); + if (overflow) { exponent = INT32_MAX; } else { exponent = nextExponent; @@ -278,7 +280,7 @@ bool DecimalMatcher::match(StringSegment& segment, ParsedNumber& result, int8_t U_ASSERT(!result.quantity.bogus); bool overflow = (exponent == INT32_MAX); if (!overflow) { - result.quantity.adjustMagnitude(exponentSign * exponent); + overflow = result.quantity.adjustMagnitude(exponentSign * exponent); } if (overflow) { if (exponentSign == -1) { diff --git a/icu4c/source/test/cintltst/putiltst.c b/icu4c/source/test/cintltst/putiltst.c index b99d9fca9c..1c3e073041 100644 --- a/icu4c/source/test/cintltst/putiltst.c +++ b/icu4c/source/test/cintltst/putiltst.c @@ -128,6 +128,20 @@ static void TestPUtilAPI(void){ log_err("ERROR: uprv_isInfinite failed.\n"); } + log_verbose("Testing the APIs uprv_add32_overflow and uprv_mul32_overflow\n"); + int32_t overflow_result; + doAssert(FALSE, uprv_add32_overflow(INT32_MAX - 2, 1, &overflow_result), "should not overflow"); + doAssert(INT32_MAX - 1, overflow_result, "should equal INT32_MAX - 1"); + doAssert(FALSE, uprv_add32_overflow(INT32_MAX - 2, 2, &overflow_result), "should not overflow"); + doAssert(INT32_MAX, overflow_result, "should equal exactly INT32_MAX"); + doAssert(TRUE, uprv_add32_overflow(INT32_MAX - 2, 3, &overflow_result), "should overflow"); + doAssert(FALSE, uprv_mul32_overflow(INT32_MAX / 5, 4, &overflow_result), "should not overflow"); + doAssert(INT32_MAX / 5 * 4, overflow_result, "should equal INT32_MAX / 5 * 4"); + doAssert(TRUE, uprv_mul32_overflow(INT32_MAX / 5, 6, &overflow_result), "should overflow"); + // Test on negative numbers: + doAssert(FALSE, uprv_add32_overflow(-3, -2, &overflow_result), "should not overflow"); + doAssert(-5, overflow_result, "should equal -5"); + #if 0 log_verbose("Testing the API uprv_digitsAfterDecimal()....\n"); doAssert(uprv_digitsAfterDecimal(value1), 3, "uprv_digitsAfterDecimal() failed."); From b4ad6242b3c2a8064c7dc1ab210d70a56e3df646 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Fri, 23 Mar 2018 00:58:31 +0000 Subject: [PATCH 051/129] ICU-8610 Changing to CharsTrie implementation for stem lookup for better portability to C++. X-SVN-Rev: 41137 --- .../ibm/icu/number/NumberSkeletonImpl.java | 434 ++++++++++++++---- .../dev/test/number/NumberSkeletonTest.java | 41 +- 2 files changed, 382 insertions(+), 93 deletions(-) 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 378087288c..be6443969a 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 @@ -19,10 +19,14 @@ import com.ibm.icu.number.NumberFormatter.SignDisplay; import com.ibm.icu.number.NumberFormatter.UnitWidth; import com.ibm.icu.text.DecimalFormatSymbols; import com.ibm.icu.text.NumberingSystem; +import com.ibm.icu.util.BytesTrie; +import com.ibm.icu.util.CharsTrie; +import com.ibm.icu.util.CharsTrieBuilder; import com.ibm.icu.util.Currency; import com.ibm.icu.util.Currency.CurrencyUsage; import com.ibm.icu.util.MeasureUnit; import com.ibm.icu.util.NoUnit; +import com.ibm.icu.util.StringTrieBuilder; /** * @author sffc @@ -52,6 +56,103 @@ class NumberSkeletonImpl { DECIMAL_DISPLAY } + static enum ActualStem { + // Section 1: Stems that do not require an option: + STEM_COMPACT_SHORT, + STEM_COMPACT_LONG, + STEM_SCIENTIFIC, + STEM_ENGINEERING, + STEM_NOTATION_SIMPLE, + STEM_BASE_UNIT, + STEM_PERCENT, + STEM_PERMILLE, + STEM_ROUND_INTEGER, + STEM_ROUND_UNLIMITED, + STEM_ROUND_CURRENCY_STANDARD, + STEM_ROUND_CURRENCY_CASH, + STEM_GROUP_OFF, + STEM_GROUP_MIN2, + STEM_GROUP_AUTO, + STEM_GROUP_ON_ALIGNED, + STEM_GROUP_THOUSANDS, + STEM_LATIN, + STEM_UNIT_WIDTH_NARROW, + STEM_UNIT_WIDTH_SHORT, + STEM_UNIT_WIDTH_FULL_NAME, + STEM_UNIT_WIDTH_ISO_CODE, + STEM_UNIT_WIDTH_HIDDEN, + STEM_SIGN_AUTO, + STEM_SIGN_ALWAYS, + STEM_SIGN_NEVER, + STEM_SIGN_ACCOUNTING, + STEM_SIGN_ACCOUNTING_ALWAYS, + STEM_SIGN_EXCEPT_ZERO, + STEM_SIGN_ACCOUNTING_EXCEPT_ZERO, + STEM_DECIMAL_AUTO, + STEM_DECIMAL_ALWAYS, + + // Section 2: Stems that DO require an option: + STEM_ROUND_INCREMENT, + STEM_MEASURE_UNIT, + STEM_PER_MEASURE_UNIT, + STEM_CURRENCY, + STEM_INTEGER_WIDTH, + STEM_NUMBERING_SYSTEM, + }; + + static final ActualStem[] ACTUAL_STEM_VALUES = ActualStem.values(); + + static final String SERIALIZED_STEM_TRIE = buildStemTrie(); + + static String buildStemTrie() { + CharsTrieBuilder b = new CharsTrieBuilder(); + + // Section 1: + b.add("compact-short", ActualStem.STEM_COMPACT_SHORT.ordinal()); + b.add("compact-long", ActualStem.STEM_COMPACT_LONG.ordinal()); + b.add("scientific", ActualStem.STEM_SCIENTIFIC.ordinal()); + b.add("engineering", ActualStem.STEM_ENGINEERING.ordinal()); + b.add("notation-simple", ActualStem.STEM_NOTATION_SIMPLE.ordinal()); + b.add("base-unit", ActualStem.STEM_BASE_UNIT.ordinal()); + b.add("percent", ActualStem.STEM_PERCENT.ordinal()); + b.add("permille", ActualStem.STEM_PERMILLE.ordinal()); + b.add("round-integer", ActualStem.STEM_ROUND_INTEGER.ordinal()); + b.add("round-unlimited", ActualStem.STEM_ROUND_UNLIMITED.ordinal()); + b.add("round-currency-standard", ActualStem.STEM_ROUND_CURRENCY_STANDARD.ordinal()); + b.add("round-currency-cash", ActualStem.STEM_ROUND_CURRENCY_CASH.ordinal()); + b.add("group-off", ActualStem.STEM_GROUP_OFF.ordinal()); + b.add("group-min2", ActualStem.STEM_GROUP_MIN2.ordinal()); + b.add("group-auto", ActualStem.STEM_GROUP_AUTO.ordinal()); + b.add("group-on-aligned", ActualStem.STEM_GROUP_ON_ALIGNED.ordinal()); + b.add("group-thousands", ActualStem.STEM_GROUP_THOUSANDS.ordinal()); + b.add("latin", ActualStem.STEM_LATIN.ordinal()); + b.add("unit-width-narrow", ActualStem.STEM_UNIT_WIDTH_NARROW.ordinal()); + b.add("unit-width-short", ActualStem.STEM_UNIT_WIDTH_SHORT.ordinal()); + b.add("unit-width-full-name", ActualStem.STEM_UNIT_WIDTH_FULL_NAME.ordinal()); + b.add("unit-width-iso-code", ActualStem.STEM_UNIT_WIDTH_ISO_CODE.ordinal()); + b.add("unit-width-hidden", ActualStem.STEM_UNIT_WIDTH_HIDDEN.ordinal()); + b.add("sign-auto", ActualStem.STEM_SIGN_AUTO.ordinal()); + b.add("sign-always", ActualStem.STEM_SIGN_ALWAYS.ordinal()); + b.add("sign-never", ActualStem.STEM_SIGN_NEVER.ordinal()); + b.add("sign-accounting", ActualStem.STEM_SIGN_ACCOUNTING.ordinal()); + b.add("sign-accounting-always", ActualStem.STEM_SIGN_ACCOUNTING_ALWAYS.ordinal()); + b.add("sign-except-zero", ActualStem.STEM_SIGN_EXCEPT_ZERO.ordinal()); + b.add("sign-accounting-except-zero", ActualStem.STEM_SIGN_ACCOUNTING_EXCEPT_ZERO.ordinal()); + b.add("decimal-auto", ActualStem.STEM_DECIMAL_AUTO.ordinal()); + b.add("decimal-always", ActualStem.STEM_DECIMAL_ALWAYS.ordinal()); + + // Section 2: + b.add("round-increment", ActualStem.STEM_ROUND_INCREMENT.ordinal()); + b.add("measure-unit", ActualStem.STEM_MEASURE_UNIT.ordinal()); + b.add("per-measure-unit", ActualStem.STEM_PER_MEASURE_UNIT.ordinal()); + b.add("currency", ActualStem.STEM_CURRENCY.ordinal()); + b.add("integer-width", ActualStem.STEM_INTEGER_WIDTH.ordinal()); + b.add("numbering-system", ActualStem.STEM_NUMBERING_SYSTEM.ordinal()); + + // TODO: Use SLOW or FAST here? + return b.buildCharSequence(StringTrieBuilder.Option.FAST).toString(); + } + static class SkeletonDataStructure { final Map stemsToTypes; final Map stemsToValues; @@ -186,28 +287,58 @@ class NumberSkeletonImpl { ///// private static MacroProps parseSkeleton(String skeletonString) { + // Add a trailing whitespace to the end of the skeleton string to make code cleaner. + skeletonString += " "; + MacroProps macros = new MacroProps(); - StringSegment segment = new StringSegment(skeletonString + " ", false); + StringSegment segment = new StringSegment(skeletonString, false); + CharsTrie stemTrie = new CharsTrie(SERIALIZED_STEM_TRIE, 0); StemType stem = null; int offset = 0; while (offset < segment.length()) { int cp = segment.codePointAt(offset); - boolean isWhiteSpace = PatternProps.isWhiteSpace(cp); - if (offset > 0 && (isWhiteSpace || cp == '/')) { + boolean isTokenSeparator = PatternProps.isWhiteSpace(cp); + boolean isOptionSeparator = (cp == '/'); + + if (!isTokenSeparator && !isOptionSeparator) { + // Non-separator token; consume it. + offset += Character.charCount(cp); + if (stem == null) { + // We are currently consuming a stem. + // Go to the next state in the stem trie. + stemTrie.nextForCodePoint(cp); + } + continue; + } + + // We are looking at a token or option separator. + // If the segment is nonempty, parse it and reset the segment. + // Otherwise, make sure it is a valid repeating separator. + if (offset != 0) { segment.setLength(offset); if (stem == null) { - stem = parseStem(segment, macros); + // The first separator after the start of a token. Parse it as a stem. + stem = parseStem2(segment, stemTrie, macros); + stemTrie.reset(); } else { + // A separator after the first separator of a token. Parse it as an option. stem = parseOption(stem, segment, macros); } segment.resetLength(); segment.adjustOffset(offset + 1); offset = 0; + + } else if (stem != null) { + // A separator ('/' or whitespace) following an option separator ('/') + throw new SkeletonSyntaxException("Unexpected separator character", segment); + } else { - offset += Character.charCount(cp); + // Two spaces in a row; this is OK. + segment.adjustOffset(Character.charCount(cp)); } - if (isWhiteSpace && stem != null) { - // Check for stems that require an option + + // Make sure we aren't in a state requiring an option, and then reset the state. + if (isTokenSeparator && stem != null) { switch (stem) { case MAYBE_INCREMENT_ROUNDER: case MEASURE_UNIT: @@ -226,92 +357,221 @@ class NumberSkeletonImpl { return macros; } - private static StemType parseStem(CharSequence content, MacroProps macros) { - // First try: exact match with a literal stem - StemType stem = skeletonData.stemToType(content); - if (stem != null) { - Object value = skeletonData.stemToValue(content); - switch (stem) { - case COMPACT_NOTATION: - case SCIENTIFIC_NOTATION: - case SIMPLE_NOTATION: - checkNull(macros.notation, content); - macros.notation = (Notation) value; - break; - case NO_UNIT: - checkNull(macros.unit, content); - macros.unit = (NoUnit) value; - break; - case ROUNDER: - checkNull(macros.rounder, content); - macros.rounder = (Rounder) value; - break; - case GROUPING: - checkNull(macros.grouping, content); - macros.grouping = value; - break; - case LATIN: - checkNull(macros.symbols, content); - macros.symbols = value; - break; - case UNIT_WIDTH: - checkNull(macros.unitWidth, content); - macros.unitWidth = (UnitWidth) value; - break; - case SIGN_DISPLAY: - checkNull(macros.sign, content); - macros.sign = (SignDisplay) value; - break; - case DECIMAL_DISPLAY: - checkNull(macros.decimal, content); - macros.decimal = (DecimalSeparatorDisplay) value; - break; - default: - assert false; - } - return stem; - } - - // Second try: literal stems that require an option - if (content.equals("round-increment")) { - checkNull(macros.rounder, content); - return StemType.MAYBE_INCREMENT_ROUNDER; - } else if (content.equals("measure-unit")) { - checkNull(macros.unit, content); - return StemType.MEASURE_UNIT; - } else if (content.equals("per-measure-unit")) { - checkNull(macros.perUnit, content); - return StemType.PER_MEASURE_UNIT; - } else if (content.equals("currency")) { - checkNull(macros.unit, content); - return StemType.CURRENCY; - } else if (content.equals("integer-width")) { - checkNull(macros.integerWidth, content); - return StemType.INTEGER_WIDTH; - } else if (content.equals("numbering-system")) { - checkNull(macros.symbols, content); - return StemType.NUMBERING_SYSTEM; - } - - // Third try: stem "blueprint" syntax + private static StemType parseStem2(CharSequence content, CharsTrie stemTrie, MacroProps macros) { + // First check for "blueprint" stems, which start with a "signal char" switch (content.charAt(0)) { case '.': - stem = StemType.FRACTION_ROUNDER; checkNull(macros.rounder, content); parseFractionStem(content, macros); - break; + return StemType.FRACTION_ROUNDER; case '@': - stem = StemType.ROUNDER; checkNull(macros.rounder, content); parseDigitsStem(content, macros); - break; - } - if (stem != null) { - return stem; + return StemType.ROUNDER; } - // Still no hits: throw an exception - throw new SkeletonSyntaxException("Unknown stem", content); + // Now look at the stemsTrie, which is already be pointing at our stem. + BytesTrie.Result stemResult = stemTrie.current(); + + if (stemResult != BytesTrie.Result.INTERMEDIATE_VALUE + && stemResult != BytesTrie.Result.FINAL_VALUE) { + throw new SkeletonSyntaxException("Unknown stem", content); + } + + ActualStem stemEnum = ACTUAL_STEM_VALUES[stemTrie.getValue()]; + switch (stemEnum) { + + // Stems with meaning on their own, not requiring an option: + + case STEM_COMPACT_SHORT: + checkNull(macros.notation, content); + macros.notation = Notation.compactShort(); + return StemType.COMPACT_NOTATION; + + case STEM_COMPACT_LONG: + checkNull(macros.notation, content); + macros.notation = Notation.compactLong(); + return StemType.COMPACT_NOTATION; + + case STEM_SCIENTIFIC: + checkNull(macros.notation, content); + macros.notation = Notation.scientific(); + return StemType.SCIENTIFIC_NOTATION; + + case STEM_ENGINEERING: + checkNull(macros.notation, content); + macros.notation = Notation.engineering(); + return StemType.SCIENTIFIC_NOTATION; + + case STEM_NOTATION_SIMPLE: + checkNull(macros.notation, content); + macros.notation = Notation.simple(); + return StemType.SIMPLE_NOTATION; + + case STEM_BASE_UNIT: + checkNull(macros.unit, content); + macros.unit = NoUnit.BASE; + return StemType.NO_UNIT; + + case STEM_PERCENT: + checkNull(macros.unit, content); + macros.unit = NoUnit.PERCENT; + return StemType.NO_UNIT; + + case STEM_PERMILLE: + checkNull(macros.unit, content); + macros.unit = NoUnit.PERMILLE; + return StemType.NO_UNIT; + + case STEM_ROUND_INTEGER: + checkNull(macros.rounder, content); + macros.rounder = Rounder.integer(); + return StemType.ROUNDER; + + case STEM_ROUND_UNLIMITED: + checkNull(macros.rounder, content); + macros.rounder = Rounder.unlimited(); + return StemType.ROUNDER; + + case STEM_ROUND_CURRENCY_STANDARD: + checkNull(macros.rounder, content); + macros.rounder = Rounder.currency(CurrencyUsage.STANDARD); + return StemType.ROUNDER; + + case STEM_ROUND_CURRENCY_CASH: + checkNull(macros.rounder, content); + macros.rounder = Rounder.currency(CurrencyUsage.CASH); + return StemType.ROUNDER; + + case STEM_GROUP_OFF: + checkNull(macros.grouping, content); + macros.grouping = GroupingStrategy.OFF; + return StemType.GROUPING; + + case STEM_GROUP_MIN2: + checkNull(macros.grouping, content); + macros.grouping = GroupingStrategy.MIN2; + return StemType.GROUPING; + + case STEM_GROUP_AUTO: + checkNull(macros.grouping, content); + macros.grouping = GroupingStrategy.AUTO; + return StemType.GROUPING; + + case STEM_GROUP_ON_ALIGNED: + checkNull(macros.grouping, content); + macros.grouping = GroupingStrategy.ON_ALIGNED; + return StemType.GROUPING; + + case STEM_GROUP_THOUSANDS: + checkNull(macros.grouping, content); + macros.grouping = GroupingStrategy.THOUSANDS; + return StemType.GROUPING; + + case STEM_LATIN: + checkNull(macros.symbols, content); + macros.symbols = NumberingSystem.LATIN; + return StemType.LATIN; + + case STEM_UNIT_WIDTH_NARROW: + checkNull(macros.unitWidth, content); + macros.unitWidth = UnitWidth.NARROW; + return StemType.UNIT_WIDTH; + + case STEM_UNIT_WIDTH_SHORT: + checkNull(macros.unitWidth, content); + macros.unitWidth = UnitWidth.SHORT; + return StemType.UNIT_WIDTH; + + case STEM_UNIT_WIDTH_FULL_NAME: + checkNull(macros.unitWidth, content); + macros.unitWidth = UnitWidth.FULL_NAME; + return StemType.UNIT_WIDTH; + + case STEM_UNIT_WIDTH_ISO_CODE: + checkNull(macros.unitWidth, content); + macros.unitWidth = UnitWidth.ISO_CODE; + return StemType.UNIT_WIDTH; + + case STEM_UNIT_WIDTH_HIDDEN: + checkNull(macros.unitWidth, content); + macros.unitWidth = UnitWidth.HIDDEN; + return StemType.UNIT_WIDTH; + + case STEM_SIGN_AUTO: + checkNull(macros.sign, content); + macros.sign = SignDisplay.AUTO; + return StemType.SIGN_DISPLAY; + + case STEM_SIGN_ALWAYS: + checkNull(macros.sign, content); + macros.sign = SignDisplay.ALWAYS; + return StemType.SIGN_DISPLAY; + + case STEM_SIGN_NEVER: + checkNull(macros.sign, content); + macros.sign = SignDisplay.NEVER; + return StemType.SIGN_DISPLAY; + + case STEM_SIGN_ACCOUNTING: + checkNull(macros.sign, content); + macros.sign = SignDisplay.ACCOUNTING; + return StemType.SIGN_DISPLAY; + + case STEM_SIGN_ACCOUNTING_ALWAYS: + checkNull(macros.sign, content); + macros.sign = SignDisplay.ACCOUNTING_ALWAYS; + return StemType.SIGN_DISPLAY; + + case STEM_SIGN_EXCEPT_ZERO: + checkNull(macros.sign, content); + macros.sign = SignDisplay.EXCEPT_ZERO; + return StemType.SIGN_DISPLAY; + + case STEM_SIGN_ACCOUNTING_EXCEPT_ZERO: + checkNull(macros.sign, content); + macros.sign = SignDisplay.ACCOUNTING_EXCEPT_ZERO; + return StemType.SIGN_DISPLAY; + + case STEM_DECIMAL_AUTO: + checkNull(macros.decimal, content); + macros.decimal = DecimalSeparatorDisplay.AUTO; + return StemType.DECIMAL_DISPLAY; + + case STEM_DECIMAL_ALWAYS: + checkNull(macros.decimal, content); + macros.decimal = DecimalSeparatorDisplay.ALWAYS; + return StemType.DECIMAL_DISPLAY; + + // Stems requiring an option: + + case STEM_ROUND_INCREMENT: + checkNull(macros.rounder, content); + return StemType.MAYBE_INCREMENT_ROUNDER; + + case STEM_MEASURE_UNIT: + checkNull(macros.unit, content); + return StemType.MEASURE_UNIT; + + case STEM_PER_MEASURE_UNIT: + checkNull(macros.perUnit, content); + return StemType.PER_MEASURE_UNIT; + + case STEM_CURRENCY: + checkNull(macros.unit, content); + return StemType.CURRENCY; + + case STEM_INTEGER_WIDTH: + checkNull(macros.integerWidth, content); + return StemType.INTEGER_WIDTH; + + case STEM_NUMBERING_SYSTEM: + checkNull(macros.symbols, content); + return StemType.NUMBERING_SYSTEM; + + default: + throw new AssertionError(); + } } private static StemType parseOption(StemType stem, CharSequence content, MacroProps macros) { @@ -454,9 +714,9 @@ class NumberSkeletonImpl { return true; } - private static void generateExponentWidthOption(int minInt, int maxInt, StringBuilder sb) { + private static void generateExponentWidthOption(int minExponentDigits, StringBuilder sb) { sb.append('+'); - appendMultiple(sb, 'e', minInt); + appendMultiple(sb, 'e', minExponentDigits); } private static boolean parseExponentSignOption(CharSequence content, MacroProps macros) { @@ -793,7 +1053,7 @@ class NumberSkeletonImpl { } if (impl.minExponentDigits > 1) { sb.append('/'); - generateExponentWidthOption(impl.minExponentDigits, -1, sb); + generateExponentWidthOption(impl.minExponentDigits, sb); } if (impl.exponentSignDisplay != SignDisplay.AUTO) { sb.append('/'); 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 78756355f2..b3e51f166f 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 @@ -3,6 +3,7 @@ package com.ibm.icu.dev.test.number; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -13,6 +14,7 @@ import org.junit.Test; import com.ibm.icu.number.NumberFormatter; import com.ibm.icu.number.Rounder; import com.ibm.icu.number.SkeletonSyntaxException; +import com.ibm.icu.util.ULocale; /** * @author sffc @@ -143,7 +145,7 @@ public class NumberSkeletonTest { @Test public void unknownTokens() { - String[] cases = { "measure-unit/foo-bar", "numbering-system/dummy" }; + String[] cases = { "maesure-unit", "measure-unit/foo-bar", "numbering-system/dummy" }; for (String cas : cases) { try { @@ -158,16 +160,16 @@ public class NumberSkeletonTest { @Test public void stemsRequiringOption() { String[] stems = { "round-increment", "currency", "measure-unit", "integer-width", }; - String[] suffixes = { "", "/", " scientific", "/ scientific" }; + String[] suffixes = { "", "/ceiling", " scientific", "/ceiling scientific" }; for (String stem : stems) { for (String suffix : suffixes) { + String skeletonString = stem + suffix; try { - NumberFormatter.fromSkeleton(stem + suffix); - fail(); + NumberFormatter.fromSkeleton(skeletonString); + fail(skeletonString); } catch (SkeletonSyntaxException expected) { - assertTrue(expected.getMessage(), - expected.getMessage().contains("requires an option")); + // Success } } } @@ -190,6 +192,33 @@ public class NumberSkeletonTest { } } + @Test + public void flexibleSeparators() { + String[][] cases = { + { "round-integer group-off", "5142" }, + { "round-integer group-off", "5142" }, + { "round-integer/ceiling group-off", "5143" }, + { "round-integer//ceiling group-off", null }, + { "round-integer/ceiling group-off", "5143" }, + { "round-integer//ceiling group-off", null }, + { "round-integer/ group-off", null }, + { "round-integer// group-off", null } }; + + for (String[] cas : cases) { + String skeleton = cas[0]; + String expected = cas[1]; + + try { + String actual = NumberFormatter.fromSkeleton(skeleton).locale(ULocale.ENGLISH) + .format(5142.3).toString(); + assertEquals(skeleton, expected, actual); + } catch (SkeletonSyntaxException e) { + // Expected failure? + assertNull(skeleton, expected); + } + } + } + @Test public void roundingModeNames() { for (RoundingMode mode : RoundingMode.values()) { From 929b26360d6f4d41eade89ecb92e4b72e1147138 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Fri, 23 Mar 2018 01:14:56 +0000 Subject: [PATCH 052/129] ICU-8610 Changing parsing state variable from "CharSequence content" to "StringSegment segment" for easier portability. In C++, the segment will be marked const in the arguments. X-SVN-Rev: 41138 --- .../ibm/icu/number/NumberSkeletonImpl.java | 252 +++++++++--------- 1 file changed, 126 insertions(+), 126 deletions(-) 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 be6443969a..479bd57bfa 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 @@ -357,16 +357,16 @@ class NumberSkeletonImpl { return macros; } - private static StemType parseStem2(CharSequence content, CharsTrie stemTrie, MacroProps macros) { + private static StemType parseStem2(StringSegment segment, CharsTrie stemTrie, MacroProps macros) { // First check for "blueprint" stems, which start with a "signal char" - switch (content.charAt(0)) { + switch (segment.charAt(0)) { case '.': - checkNull(macros.rounder, content); - parseFractionStem(content, macros); + checkNull(macros.rounder, segment); + parseFractionStem(segment, macros); return StemType.FRACTION_ROUNDER; case '@': - checkNull(macros.rounder, content); - parseDigitsStem(content, macros); + checkNull(macros.rounder, segment); + parseDigitsStem(segment, macros); return StemType.ROUNDER; } @@ -375,7 +375,7 @@ class NumberSkeletonImpl { if (stemResult != BytesTrie.Result.INTERMEDIATE_VALUE && stemResult != BytesTrie.Result.FINAL_VALUE) { - throw new SkeletonSyntaxException("Unknown stem", content); + throw new SkeletonSyntaxException("Unknown stem", segment); } ActualStem stemEnum = ACTUAL_STEM_VALUES[stemTrie.getValue()]; @@ -384,189 +384,189 @@ class NumberSkeletonImpl { // Stems with meaning on their own, not requiring an option: case STEM_COMPACT_SHORT: - checkNull(macros.notation, content); + checkNull(macros.notation, segment); macros.notation = Notation.compactShort(); return StemType.COMPACT_NOTATION; case STEM_COMPACT_LONG: - checkNull(macros.notation, content); + checkNull(macros.notation, segment); macros.notation = Notation.compactLong(); return StemType.COMPACT_NOTATION; case STEM_SCIENTIFIC: - checkNull(macros.notation, content); + checkNull(macros.notation, segment); macros.notation = Notation.scientific(); return StemType.SCIENTIFIC_NOTATION; case STEM_ENGINEERING: - checkNull(macros.notation, content); + checkNull(macros.notation, segment); macros.notation = Notation.engineering(); return StemType.SCIENTIFIC_NOTATION; case STEM_NOTATION_SIMPLE: - checkNull(macros.notation, content); + checkNull(macros.notation, segment); macros.notation = Notation.simple(); return StemType.SIMPLE_NOTATION; case STEM_BASE_UNIT: - checkNull(macros.unit, content); + checkNull(macros.unit, segment); macros.unit = NoUnit.BASE; return StemType.NO_UNIT; case STEM_PERCENT: - checkNull(macros.unit, content); + checkNull(macros.unit, segment); macros.unit = NoUnit.PERCENT; return StemType.NO_UNIT; case STEM_PERMILLE: - checkNull(macros.unit, content); + checkNull(macros.unit, segment); macros.unit = NoUnit.PERMILLE; return StemType.NO_UNIT; case STEM_ROUND_INTEGER: - checkNull(macros.rounder, content); + checkNull(macros.rounder, segment); macros.rounder = Rounder.integer(); return StemType.ROUNDER; case STEM_ROUND_UNLIMITED: - checkNull(macros.rounder, content); + checkNull(macros.rounder, segment); macros.rounder = Rounder.unlimited(); return StemType.ROUNDER; case STEM_ROUND_CURRENCY_STANDARD: - checkNull(macros.rounder, content); + checkNull(macros.rounder, segment); macros.rounder = Rounder.currency(CurrencyUsage.STANDARD); return StemType.ROUNDER; case STEM_ROUND_CURRENCY_CASH: - checkNull(macros.rounder, content); + checkNull(macros.rounder, segment); macros.rounder = Rounder.currency(CurrencyUsage.CASH); return StemType.ROUNDER; case STEM_GROUP_OFF: - checkNull(macros.grouping, content); + checkNull(macros.grouping, segment); macros.grouping = GroupingStrategy.OFF; return StemType.GROUPING; case STEM_GROUP_MIN2: - checkNull(macros.grouping, content); + checkNull(macros.grouping, segment); macros.grouping = GroupingStrategy.MIN2; return StemType.GROUPING; case STEM_GROUP_AUTO: - checkNull(macros.grouping, content); + checkNull(macros.grouping, segment); macros.grouping = GroupingStrategy.AUTO; return StemType.GROUPING; case STEM_GROUP_ON_ALIGNED: - checkNull(macros.grouping, content); + checkNull(macros.grouping, segment); macros.grouping = GroupingStrategy.ON_ALIGNED; return StemType.GROUPING; case STEM_GROUP_THOUSANDS: - checkNull(macros.grouping, content); + checkNull(macros.grouping, segment); macros.grouping = GroupingStrategy.THOUSANDS; return StemType.GROUPING; case STEM_LATIN: - checkNull(macros.symbols, content); + checkNull(macros.symbols, segment); macros.symbols = NumberingSystem.LATIN; return StemType.LATIN; case STEM_UNIT_WIDTH_NARROW: - checkNull(macros.unitWidth, content); + checkNull(macros.unitWidth, segment); macros.unitWidth = UnitWidth.NARROW; return StemType.UNIT_WIDTH; case STEM_UNIT_WIDTH_SHORT: - checkNull(macros.unitWidth, content); + checkNull(macros.unitWidth, segment); macros.unitWidth = UnitWidth.SHORT; return StemType.UNIT_WIDTH; case STEM_UNIT_WIDTH_FULL_NAME: - checkNull(macros.unitWidth, content); + checkNull(macros.unitWidth, segment); macros.unitWidth = UnitWidth.FULL_NAME; return StemType.UNIT_WIDTH; case STEM_UNIT_WIDTH_ISO_CODE: - checkNull(macros.unitWidth, content); + checkNull(macros.unitWidth, segment); macros.unitWidth = UnitWidth.ISO_CODE; return StemType.UNIT_WIDTH; case STEM_UNIT_WIDTH_HIDDEN: - checkNull(macros.unitWidth, content); + checkNull(macros.unitWidth, segment); macros.unitWidth = UnitWidth.HIDDEN; return StemType.UNIT_WIDTH; case STEM_SIGN_AUTO: - checkNull(macros.sign, content); + checkNull(macros.sign, segment); macros.sign = SignDisplay.AUTO; return StemType.SIGN_DISPLAY; case STEM_SIGN_ALWAYS: - checkNull(macros.sign, content); + checkNull(macros.sign, segment); macros.sign = SignDisplay.ALWAYS; return StemType.SIGN_DISPLAY; case STEM_SIGN_NEVER: - checkNull(macros.sign, content); + checkNull(macros.sign, segment); macros.sign = SignDisplay.NEVER; return StemType.SIGN_DISPLAY; case STEM_SIGN_ACCOUNTING: - checkNull(macros.sign, content); + checkNull(macros.sign, segment); macros.sign = SignDisplay.ACCOUNTING; return StemType.SIGN_DISPLAY; case STEM_SIGN_ACCOUNTING_ALWAYS: - checkNull(macros.sign, content); + checkNull(macros.sign, segment); macros.sign = SignDisplay.ACCOUNTING_ALWAYS; return StemType.SIGN_DISPLAY; case STEM_SIGN_EXCEPT_ZERO: - checkNull(macros.sign, content); + checkNull(macros.sign, segment); macros.sign = SignDisplay.EXCEPT_ZERO; return StemType.SIGN_DISPLAY; case STEM_SIGN_ACCOUNTING_EXCEPT_ZERO: - checkNull(macros.sign, content); + checkNull(macros.sign, segment); macros.sign = SignDisplay.ACCOUNTING_EXCEPT_ZERO; return StemType.SIGN_DISPLAY; case STEM_DECIMAL_AUTO: - checkNull(macros.decimal, content); + checkNull(macros.decimal, segment); macros.decimal = DecimalSeparatorDisplay.AUTO; return StemType.DECIMAL_DISPLAY; case STEM_DECIMAL_ALWAYS: - checkNull(macros.decimal, content); + checkNull(macros.decimal, segment); macros.decimal = DecimalSeparatorDisplay.ALWAYS; return StemType.DECIMAL_DISPLAY; // Stems requiring an option: case STEM_ROUND_INCREMENT: - checkNull(macros.rounder, content); + checkNull(macros.rounder, segment); return StemType.MAYBE_INCREMENT_ROUNDER; case STEM_MEASURE_UNIT: - checkNull(macros.unit, content); + checkNull(macros.unit, segment); return StemType.MEASURE_UNIT; case STEM_PER_MEASURE_UNIT: - checkNull(macros.perUnit, content); + checkNull(macros.perUnit, segment); return StemType.PER_MEASURE_UNIT; case STEM_CURRENCY: - checkNull(macros.unit, content); + checkNull(macros.unit, segment); return StemType.CURRENCY; case STEM_INTEGER_WIDTH: - checkNull(macros.integerWidth, content); + checkNull(macros.integerWidth, segment); return StemType.INTEGER_WIDTH; case STEM_NUMBERING_SYSTEM: - checkNull(macros.symbols, content); + checkNull(macros.symbols, segment); return StemType.NUMBERING_SYSTEM; default: @@ -574,28 +574,28 @@ class NumberSkeletonImpl { } } - private static StemType parseOption(StemType stem, CharSequence content, MacroProps macros) { + private static StemType parseOption(StemType stem, StringSegment segment, MacroProps macros) { ///// Required options: ///// switch (stem) { case CURRENCY: - parseCurrencyOption(content, macros); + parseCurrencyOption(segment, macros); return StemType.OTHER; case MEASURE_UNIT: - parseMeasureUnitOption(content, macros); + parseMeasureUnitOption(segment, macros); return StemType.OTHER; case PER_MEASURE_UNIT: - parseMeasurePerUnitOption(content, macros); + parseMeasurePerUnitOption(segment, macros); return StemType.OTHER; case MAYBE_INCREMENT_ROUNDER: - parseIncrementOption(content, macros); + parseIncrementOption(segment, macros); return StemType.ROUNDER; case INTEGER_WIDTH: - parseIntegerWidthOption(content, macros); + parseIntegerWidthOption(segment, macros); return StemType.OTHER; case NUMBERING_SYSTEM: - parseNumberingSystemOption(content, macros); + parseNumberingSystemOption(segment, macros); return StemType.OTHER; } @@ -604,10 +604,10 @@ class NumberSkeletonImpl { // Scientific options switch (stem) { case SCIENTIFIC_NOTATION: - if (parseExponentWidthOption(content, macros)) { + if (parseExponentWidthOption(segment, macros)) { return StemType.SCIENTIFIC_NOTATION; } - if (parseExponentSignOption(content, macros)) { + if (parseExponentSignOption(segment, macros)) { return StemType.SCIENTIFIC_NOTATION; } } @@ -615,7 +615,7 @@ class NumberSkeletonImpl { // Frac-sig option switch (stem) { case FRACTION_ROUNDER: - if (parseFracSigOption(content, macros)) { + if (parseFracSigOption(segment, macros)) { return StemType.ROUNDER; } } @@ -625,13 +625,13 @@ class NumberSkeletonImpl { case ROUNDER: case FRACTION_ROUNDER: case CURRENCY_ROUNDER: - if (parseRoundingModeOption(content, macros)) { + if (parseRoundingModeOption(segment, macros)) { return StemType.ROUNDER; } } // Unknown option - throw new SkeletonSyntaxException("Invalid option", content); + throw new SkeletonSyntaxException("Invalid option", segment); } private static void generateSkeleton(MacroProps macros, StringBuilder sb) { @@ -693,20 +693,20 @@ class NumberSkeletonImpl { ///// - private static boolean parseExponentWidthOption(CharSequence content, MacroProps macros) { - if (content.charAt(0) != '+') { + private static boolean parseExponentWidthOption(StringSegment segment, MacroProps macros) { + if (segment.charAt(0) != '+') { return false; } int offset = 1; int minExp = 0; - for (; offset < content.length(); offset++) { - if (content.charAt(offset) == 'e') { + for (; offset < segment.length(); offset++) { + if (segment.charAt(offset) == 'e') { minExp++; } else { break; } } - if (offset < content.length()) { + if (offset < segment.length()) { return false; } // Use the public APIs to enforce bounds checking @@ -719,8 +719,8 @@ class NumberSkeletonImpl { appendMultiple(sb, 'e', minExponentDigits); } - private static boolean parseExponentSignOption(CharSequence content, MacroProps macros) { - Object value = skeletonData.stemToValue(content); + private static boolean parseExponentSignOption(StringSegment segment, MacroProps macros) { + Object value = skeletonData.stemToValue(segment); if (value != null && value instanceof SignDisplay) { macros.notation = ((ScientificNotation) macros.notation) .withExponentSignDisplay((SignDisplay) value); @@ -733,27 +733,27 @@ class NumberSkeletonImpl { sb.append(currency.getCurrencyCode()); } - private static void parseCurrencyOption(CharSequence content, MacroProps macros) { - String currencyCode = content.subSequence(0, content.length()).toString(); + private static void parseCurrencyOption(StringSegment segment, MacroProps macros) { + String currencyCode = segment.subSequence(0, segment.length()).toString(); try { macros.unit = Currency.getInstance(currencyCode); } catch (IllegalArgumentException e) { - throw new SkeletonSyntaxException("Invalid currency", content, e); + throw new SkeletonSyntaxException("Invalid currency", segment, e); } } - private static void parseMeasureUnitOption(CharSequence content, MacroProps macros) { + private static void parseMeasureUnitOption(StringSegment segment, MacroProps macros) { // NOTE: The category (type) of the unit is guaranteed to be a valid subtag (alphanumeric) // http://unicode.org/reports/tr35/#Validity_Data int firstHyphen = 0; - while (firstHyphen < content.length() && content.charAt(firstHyphen) != '-') { + while (firstHyphen < segment.length() && segment.charAt(firstHyphen) != '-') { firstHyphen++; } - if (firstHyphen == content.length()) { - throw new SkeletonSyntaxException("Invalid measure unit option", content); + if (firstHyphen == segment.length()) { + throw new SkeletonSyntaxException("Invalid measure unit option", segment); } - String type = content.subSequence(0, firstHyphen).toString(); - String subType = content.subSequence(firstHyphen + 1, content.length()).toString(); + String type = segment.subSequence(0, firstHyphen).toString(); + String subType = segment.subSequence(firstHyphen + 1, segment.length()).toString(); Set units = MeasureUnit.getAvailable(type); for (MeasureUnit unit : units) { if (subType.equals(unit.getSubtype())) { @@ -761,42 +761,42 @@ class NumberSkeletonImpl { return; } } - throw new SkeletonSyntaxException("Unknown measure unit", content); + throw new SkeletonSyntaxException("Unknown measure unit", segment); } private static void generateMeasureUnitOption(MeasureUnit unit, StringBuilder sb) { sb.append(unit.getType() + "-" + unit.getSubtype()); } - private static void parseMeasurePerUnitOption(CharSequence content, MacroProps macros) { + private static void parseMeasurePerUnitOption(StringSegment segment, MacroProps macros) { // A little bit of a hack: safe the current unit (numerator), call the main measure unit parsing // code, put back the numerator unit, and put the new unit into per-unit. MeasureUnit numerator = macros.unit; - parseMeasureUnitOption(content, macros); + parseMeasureUnitOption(segment, macros); macros.perUnit = macros.unit; macros.unit = numerator; } - private static void parseFractionStem(CharSequence content, MacroProps macros) { - assert content.charAt(0) == '.'; + private static void parseFractionStem(StringSegment segment, MacroProps macros) { + assert segment.charAt(0) == '.'; int offset = 1; int minFrac = 0; int maxFrac; - for (; offset < content.length(); offset++) { - if (content.charAt(offset) == '0') { + for (; offset < segment.length(); offset++) { + if (segment.charAt(offset) == '0') { minFrac++; } else { break; } } - if (offset < content.length()) { - if (content.charAt(offset) == '+') { + if (offset < segment.length()) { + if (segment.charAt(offset) == '+') { maxFrac = -1; offset++; } else { maxFrac = minFrac; - for (; offset < content.length(); offset++) { - if (content.charAt(offset) == '#') { + for (; offset < segment.length(); offset++) { + if (segment.charAt(offset) == '#') { maxFrac++; } else { break; @@ -806,8 +806,8 @@ class NumberSkeletonImpl { } else { maxFrac = minFrac; } - if (offset < content.length()) { - throw new SkeletonSyntaxException("Invalid fraction stem", content); + if (offset < segment.length()) { + throw new SkeletonSyntaxException("Invalid fraction stem", segment); } // Use the public APIs to enforce bounds checking if (maxFrac == -1) { @@ -831,26 +831,26 @@ class NumberSkeletonImpl { } } - private static void parseDigitsStem(CharSequence content, MacroProps macros) { - assert content.charAt(0) == '@'; + private static void parseDigitsStem(StringSegment segment, MacroProps macros) { + assert segment.charAt(0) == '@'; int offset = 0; int minSig = 0; int maxSig; - for (; offset < content.length(); offset++) { - if (content.charAt(offset) == '@') { + for (; offset < segment.length(); offset++) { + if (segment.charAt(offset) == '@') { minSig++; } else { break; } } - if (offset < content.length()) { - if (content.charAt(offset) == '+') { + if (offset < segment.length()) { + if (segment.charAt(offset) == '+') { maxSig = -1; offset++; } else { maxSig = minSig; - for (; offset < content.length(); offset++) { - if (content.charAt(offset) == '#') { + for (; offset < segment.length(); offset++) { + if (segment.charAt(offset) == '#') { maxSig++; } else { break; @@ -860,8 +860,8 @@ class NumberSkeletonImpl { } else { maxSig = minSig; } - if (offset < content.length()) { - throw new SkeletonSyntaxException("Invalid significant digits stem", content); + if (offset < segment.length()) { + throw new SkeletonSyntaxException("Invalid significant digits stem", segment); } // Use the public APIs to enforce bounds checking if (maxSig == -1) { @@ -880,15 +880,15 @@ class NumberSkeletonImpl { } } - private static boolean parseFracSigOption(CharSequence content, MacroProps macros) { - if (content.charAt(0) != '@') { + private static boolean parseFracSigOption(StringSegment segment, MacroProps macros) { + if (segment.charAt(0) != '@') { return false; } int offset = 0; int minSig = 0; int maxSig; - for (; offset < content.length(); offset++) { - if (content.charAt(offset) == '@') { + for (; offset < segment.length(); offset++) { + if (segment.charAt(offset) == '@') { minSig++; } else { break; @@ -899,17 +899,17 @@ class NumberSkeletonImpl { // Valid: @#, @##, @### // Invalid: @, @@, @@@ // Invalid: @@#, @@##, @@@# - if (offset < content.length()) { - if (content.charAt(offset) == '+') { + if (offset < segment.length()) { + if (segment.charAt(offset) == '+') { maxSig = -1; offset++; } else if (minSig > 1) { // @@#, @@##, @@@# - throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", content); + throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", segment); } else { maxSig = minSig; - for (; offset < content.length(); offset++) { - if (content.charAt(offset) == '#') { + for (; offset < segment.length(); offset++) { + if (segment.charAt(offset) == '#') { maxSig++; } else { break; @@ -918,10 +918,10 @@ class NumberSkeletonImpl { } } else { // @, @@, @@@ - throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", content); + throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", segment); } - if (offset < content.length()) { - throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", content); + if (offset < segment.length()) { + throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", segment); } FractionRounder oldRounder = (FractionRounder) macros.rounder; @@ -933,14 +933,14 @@ class NumberSkeletonImpl { return true; } - private static void parseIncrementOption(CharSequence content, MacroProps macros) { - // Call content.subSequence() because content.toString() doesn't create a clean string. - String str = content.subSequence(0, content.length()).toString(); + 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(); BigDecimal increment; try { increment = new BigDecimal(str); } catch (NumberFormatException e) { - throw new SkeletonSyntaxException("Invalid rounding increment", content, e); + throw new SkeletonSyntaxException("Invalid rounding increment", segment, e); } macros.rounder = Rounder.increment(increment); } @@ -949,9 +949,9 @@ class NumberSkeletonImpl { sb.append(increment.toPlainString()); } - private static boolean parseRoundingModeOption(CharSequence content, MacroProps macros) { + private static boolean parseRoundingModeOption(StringSegment segment, MacroProps macros) { for (int rm = 0; rm < ROUNDING_MODE_STRINGS.length; rm++) { - if (content.equals(ROUNDING_MODE_STRINGS[rm])) { + if (segment.equals(ROUNDING_MODE_STRINGS[rm])) { macros.rounder = macros.rounder.withMode(RoundingMode.valueOf(rm)); return true; } @@ -964,26 +964,26 @@ class NumberSkeletonImpl { sb.append(option); } - private static void parseIntegerWidthOption(CharSequence content, MacroProps macros) { + private static void parseIntegerWidthOption(StringSegment segment, MacroProps macros) { int offset = 0; int minInt = 0; int maxInt; - if (content.charAt(0) == '+') { + if (segment.charAt(0) == '+') { maxInt = -1; offset++; } else { maxInt = 0; } - for (; offset < content.length(); offset++) { - if (content.charAt(offset) == '#') { + for (; offset < segment.length(); offset++) { + if (segment.charAt(offset) == '#') { maxInt++; } else { break; } } - if (offset < content.length()) { - for (; offset < content.length(); offset++) { - if (content.charAt(offset) == '0') { + if (offset < segment.length()) { + for (; offset < segment.length(); offset++) { + if (segment.charAt(offset) == '0') { minInt++; } else { break; @@ -993,8 +993,8 @@ class NumberSkeletonImpl { if (maxInt != -1) { maxInt += minInt; } - if (offset < content.length()) { - throw new SkeletonSyntaxException("Invalid integer width stem", content); + if (offset < segment.length()) { + throw new SkeletonSyntaxException("Invalid integer width stem", segment); } // Use the public APIs to enforce bounds checking if (maxInt == -1) { @@ -1013,11 +1013,11 @@ class NumberSkeletonImpl { appendMultiple(sb, '0', minInt); } - private static void parseNumberingSystemOption(CharSequence content, MacroProps macros) { - String nsName = content.subSequence(0, content.length()).toString(); + private static void parseNumberingSystemOption(StringSegment segment, MacroProps macros) { + String nsName = segment.subSequence(0, segment.length()).toString(); NumberingSystem ns = NumberingSystem.getInstanceByName(nsName); if (ns == null) { - throw new SkeletonSyntaxException("Unknown numbering system", content); + throw new SkeletonSyntaxException("Unknown numbering system", segment); } macros.symbols = ns; } From 1c034cea336f0da9e66b5c3eb5acdc3ebde5c234 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Fri, 23 Mar 2018 01:54:25 +0000 Subject: [PATCH 053/129] ICU-8610 Refactoring stem switch statement to use helper methods. X-SVN-Rev: 41139 --- .../ibm/icu/number/NumberSkeletonImpl.java | 282 ++++++++++-------- .../dev/test/number/NumberSkeletonTest.java | 8 +- 2 files changed, 156 insertions(+), 134 deletions(-) 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 479bd57bfa..c8c9ce8b42 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 @@ -102,6 +102,117 @@ class NumberSkeletonImpl { static final ActualStem[] ACTUAL_STEM_VALUES = ActualStem.values(); + private static Notation stemToNotation(ActualStem stem) { + switch (stem) { + case STEM_COMPACT_SHORT: + return Notation.compactShort(); + case STEM_COMPACT_LONG: + return Notation.compactLong(); + case STEM_SCIENTIFIC: + return Notation.scientific(); + case STEM_ENGINEERING: + return Notation.engineering(); + case STEM_NOTATION_SIMPLE: + return Notation.simple(); + default: + return null; + } + } + + private static MeasureUnit stemToUnit(ActualStem stem) { + switch (stem) { + case STEM_BASE_UNIT: + return NoUnit.BASE; + case STEM_PERCENT: + return NoUnit.PERCENT; + case STEM_PERMILLE: + return NoUnit.PERMILLE; + default: + return null; + } + } + + private static Rounder stemToRounder(ActualStem stem) { + switch (stem) { + case STEM_ROUND_INTEGER: + return Rounder.integer(); + case STEM_ROUND_UNLIMITED: + return Rounder.unlimited(); + case STEM_ROUND_CURRENCY_STANDARD: + return Rounder.currency(CurrencyUsage.STANDARD); + case STEM_ROUND_CURRENCY_CASH: + return Rounder.currency(CurrencyUsage.CASH); + default: + return null; + } + } + + private static GroupingStrategy stemToGrouper(ActualStem stem) { + switch (stem) { + case STEM_GROUP_OFF: + return GroupingStrategy.OFF; + case STEM_GROUP_MIN2: + return GroupingStrategy.MIN2; + case STEM_GROUP_AUTO: + return GroupingStrategy.AUTO; + case STEM_GROUP_ON_ALIGNED: + return GroupingStrategy.ON_ALIGNED; + case STEM_GROUP_THOUSANDS: + return GroupingStrategy.THOUSANDS; + default: + return null; + } + } + + private static UnitWidth stemToUnitWidth(ActualStem stem) { + switch (stem) { + case STEM_UNIT_WIDTH_NARROW: + return UnitWidth.NARROW; + case STEM_UNIT_WIDTH_SHORT: + return UnitWidth.SHORT; + case STEM_UNIT_WIDTH_FULL_NAME: + return UnitWidth.FULL_NAME; + case STEM_UNIT_WIDTH_ISO_CODE: + return UnitWidth.ISO_CODE; + case STEM_UNIT_WIDTH_HIDDEN: + return UnitWidth.HIDDEN; + default: + return null; + } + } + + private static SignDisplay stemToSignDisplay(ActualStem stem) { + switch (stem) { + case STEM_SIGN_AUTO: + return SignDisplay.AUTO; + case STEM_SIGN_ALWAYS: + return SignDisplay.ALWAYS; + case STEM_SIGN_NEVER: + return SignDisplay.NEVER; + case STEM_SIGN_ACCOUNTING: + return SignDisplay.ACCOUNTING; + case STEM_SIGN_ACCOUNTING_ALWAYS: + return SignDisplay.ACCOUNTING_ALWAYS; + case STEM_SIGN_EXCEPT_ZERO: + return SignDisplay.EXCEPT_ZERO; + case STEM_SIGN_ACCOUNTING_EXCEPT_ZERO: + return SignDisplay.ACCOUNTING_EXCEPT_ZERO; + default: + return null; + } + } + + private static DecimalSeparatorDisplay stemToDecimalSeparatorDisplay(ActualStem stem) { + switch (stem) { + case STEM_DECIMAL_AUTO: + return DecimalSeparatorDisplay.AUTO; + case STEM_DECIMAL_ALWAYS: + return DecimalSeparatorDisplay.ALWAYS; + default: + return null; + } + } + static final String SERIALIZED_STEM_TRIE = buildStemTrie(); static String buildStemTrie() { @@ -154,30 +265,16 @@ class NumberSkeletonImpl { } static class SkeletonDataStructure { - final Map stemsToTypes; - final Map stemsToValues; final Map valuesToStems; SkeletonDataStructure() { - stemsToTypes = new HashMap(); - stemsToValues = new HashMap(); valuesToStems = new HashMap(); } public void put(StemType stemType, String content, Object value) { - stemsToTypes.put(content, stemType); - stemsToValues.put(content, value); valuesToStems.put(value, content); } - public StemType stemToType(CharSequence content) { - return stemsToTypes.get(content); - } - - public Object stemToValue(CharSequence content) { - return stemsToValues.get(content); - } - public String valueToStem(Object value) { return valuesToStems.get(value); } @@ -367,7 +464,7 @@ class NumberSkeletonImpl { case '@': checkNull(macros.rounder, segment); parseDigitsStem(segment, macros); - return StemType.ROUNDER; + return StemType.OTHER; } // Now look at the stemsTrie, which is already be pointing at our stem. @@ -384,164 +481,79 @@ class NumberSkeletonImpl { // Stems with meaning on their own, not requiring an option: case STEM_COMPACT_SHORT: - checkNull(macros.notation, segment); - macros.notation = Notation.compactShort(); - return StemType.COMPACT_NOTATION; - case STEM_COMPACT_LONG: - checkNull(macros.notation, segment); - macros.notation = Notation.compactLong(); - return StemType.COMPACT_NOTATION; - case STEM_SCIENTIFIC: - checkNull(macros.notation, segment); - macros.notation = Notation.scientific(); - return StemType.SCIENTIFIC_NOTATION; - case STEM_ENGINEERING: - checkNull(macros.notation, segment); - macros.notation = Notation.engineering(); - return StemType.SCIENTIFIC_NOTATION; - case STEM_NOTATION_SIMPLE: checkNull(macros.notation, segment); - macros.notation = Notation.simple(); - return StemType.SIMPLE_NOTATION; + macros.notation = stemToNotation(stemEnum); + switch (stemEnum) { + case STEM_SCIENTIFIC: + case STEM_ENGINEERING: + return StemType.SCIENTIFIC_NOTATION; // allows for scientific options + default: + return StemType.OTHER; + } case STEM_BASE_UNIT: - checkNull(macros.unit, segment); - macros.unit = NoUnit.BASE; - return StemType.NO_UNIT; - case STEM_PERCENT: - checkNull(macros.unit, segment); - macros.unit = NoUnit.PERCENT; - return StemType.NO_UNIT; - case STEM_PERMILLE: checkNull(macros.unit, segment); - macros.unit = NoUnit.PERMILLE; - return StemType.NO_UNIT; + macros.unit = stemToUnit(stemEnum); + return StemType.OTHER; case STEM_ROUND_INTEGER: - checkNull(macros.rounder, segment); - macros.rounder = Rounder.integer(); - return StemType.ROUNDER; - case STEM_ROUND_UNLIMITED: - checkNull(macros.rounder, segment); - macros.rounder = Rounder.unlimited(); - return StemType.ROUNDER; - case STEM_ROUND_CURRENCY_STANDARD: - checkNull(macros.rounder, segment); - macros.rounder = Rounder.currency(CurrencyUsage.STANDARD); - return StemType.ROUNDER; - case STEM_ROUND_CURRENCY_CASH: checkNull(macros.rounder, segment); - macros.rounder = Rounder.currency(CurrencyUsage.CASH); - return StemType.ROUNDER; + macros.rounder = stemToRounder(stemEnum); + switch (stemEnum) { + case STEM_ROUND_INTEGER: + return StemType.FRACTION_ROUNDER; // allows for "round-integer/@##" + default: + return StemType.ROUNDER; // allows for rounding mode options + } case STEM_GROUP_OFF: - checkNull(macros.grouping, segment); - macros.grouping = GroupingStrategy.OFF; - return StemType.GROUPING; - case STEM_GROUP_MIN2: - checkNull(macros.grouping, segment); - macros.grouping = GroupingStrategy.MIN2; - return StemType.GROUPING; - case STEM_GROUP_AUTO: - checkNull(macros.grouping, segment); - macros.grouping = GroupingStrategy.AUTO; - return StemType.GROUPING; - case STEM_GROUP_ON_ALIGNED: - checkNull(macros.grouping, segment); - macros.grouping = GroupingStrategy.ON_ALIGNED; - return StemType.GROUPING; - case STEM_GROUP_THOUSANDS: checkNull(macros.grouping, segment); - macros.grouping = GroupingStrategy.THOUSANDS; - return StemType.GROUPING; + macros.grouping = stemToGrouper(stemEnum); + return StemType.OTHER; case STEM_LATIN: checkNull(macros.symbols, segment); macros.symbols = NumberingSystem.LATIN; - return StemType.LATIN; + return StemType.OTHER; case STEM_UNIT_WIDTH_NARROW: - checkNull(macros.unitWidth, segment); - macros.unitWidth = UnitWidth.NARROW; - return StemType.UNIT_WIDTH; - case STEM_UNIT_WIDTH_SHORT: - checkNull(macros.unitWidth, segment); - macros.unitWidth = UnitWidth.SHORT; - return StemType.UNIT_WIDTH; - case STEM_UNIT_WIDTH_FULL_NAME: - checkNull(macros.unitWidth, segment); - macros.unitWidth = UnitWidth.FULL_NAME; - return StemType.UNIT_WIDTH; - case STEM_UNIT_WIDTH_ISO_CODE: - checkNull(macros.unitWidth, segment); - macros.unitWidth = UnitWidth.ISO_CODE; - return StemType.UNIT_WIDTH; - case STEM_UNIT_WIDTH_HIDDEN: checkNull(macros.unitWidth, segment); - macros.unitWidth = UnitWidth.HIDDEN; - return StemType.UNIT_WIDTH; + macros.unitWidth = stemToUnitWidth(stemEnum); + return StemType.OTHER; case STEM_SIGN_AUTO: - checkNull(macros.sign, segment); - macros.sign = SignDisplay.AUTO; - return StemType.SIGN_DISPLAY; - case STEM_SIGN_ALWAYS: - checkNull(macros.sign, segment); - macros.sign = SignDisplay.ALWAYS; - return StemType.SIGN_DISPLAY; - case STEM_SIGN_NEVER: - checkNull(macros.sign, segment); - macros.sign = SignDisplay.NEVER; - return StemType.SIGN_DISPLAY; - case STEM_SIGN_ACCOUNTING: - checkNull(macros.sign, segment); - macros.sign = SignDisplay.ACCOUNTING; - return StemType.SIGN_DISPLAY; - case STEM_SIGN_ACCOUNTING_ALWAYS: - checkNull(macros.sign, segment); - macros.sign = SignDisplay.ACCOUNTING_ALWAYS; - return StemType.SIGN_DISPLAY; - case STEM_SIGN_EXCEPT_ZERO: - checkNull(macros.sign, segment); - macros.sign = SignDisplay.EXCEPT_ZERO; - return StemType.SIGN_DISPLAY; - case STEM_SIGN_ACCOUNTING_EXCEPT_ZERO: checkNull(macros.sign, segment); - macros.sign = SignDisplay.ACCOUNTING_EXCEPT_ZERO; - return StemType.SIGN_DISPLAY; + macros.sign = stemToSignDisplay(stemEnum); + return StemType.OTHER; case STEM_DECIMAL_AUTO: - checkNull(macros.decimal, segment); - macros.decimal = DecimalSeparatorDisplay.AUTO; - return StemType.DECIMAL_DISPLAY; - case STEM_DECIMAL_ALWAYS: checkNull(macros.decimal, segment); - macros.decimal = DecimalSeparatorDisplay.ALWAYS; - return StemType.DECIMAL_DISPLAY; + macros.decimal = stemToDecimalSeparatorDisplay(stemEnum); + return StemType.OTHER; // Stems requiring an option: @@ -720,13 +732,19 @@ class NumberSkeletonImpl { } private static boolean parseExponentSignOption(StringSegment segment, MacroProps macros) { - Object value = skeletonData.stemToValue(segment); - if (value != null && value instanceof SignDisplay) { - macros.notation = ((ScientificNotation) macros.notation) - .withExponentSignDisplay((SignDisplay) value); - return true; + // Get the sign display type out of the CharsTrie data structure. + // TODO: Make this more efficient (avoid object allocation)? It shouldn't be very hot code. + CharsTrie tempStemTrie = new CharsTrie(SERIALIZED_STEM_TRIE, 0); + BytesTrie.Result result = tempStemTrie.next(segment, 0, segment.length()); + if (result != BytesTrie.Result.INTERMEDIATE_VALUE && result != BytesTrie.Result.FINAL_VALUE) { + return false; } - return false; + SignDisplay sign = stemToSignDisplay(ACTUAL_STEM_VALUES[tempStemTrie.getValue()]); + if (sign == null) { + return false; + } + macros.notation = ((ScientificNotation) macros.notation).withExponentSignDisplay(sign); + return true; } private static void generateCurrencyOption(Currency currency, StringBuilder sb) { 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 b3e51f166f..8423b95aba 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 @@ -94,7 +94,10 @@ public class NumberSkeletonTest { "decimal-always", "latin", "numbering-system/arab", - "numbering-system/latn" }; + "numbering-system/latn", + "round-integer/@##", + "round-integer/ceiling", + "round-currency-cash/ceiling" }; for (String cas : cases) { try { @@ -131,7 +134,8 @@ public class NumberSkeletonTest { "measure-unit/foo", "integer-width/xxx", "integer-width/0+", - "integer-width/+0#", }; + "integer-width/+0#", + "scientific/foo"}; for (String cas : cases) { try { From fa6c8972eab244dbd61cdf7ecf01f9af89e68c96 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Fri, 23 Mar 2018 03:44:18 +0000 Subject: [PATCH 054/129] ICU-8610 Removing last bit of old hash map data structure from number skeleton code. X-SVN-Rev: 41140 --- .../ibm/icu/number/NumberSkeletonImpl.java | 232 +++++++++--------- 1 file changed, 120 insertions(+), 112 deletions(-) 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 c8c9ce8b42..26c40f9760 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 @@ -4,8 +4,6 @@ package com.ibm.icu.number; import java.math.BigDecimal; import java.math.RoundingMode; -import java.util.HashMap; -import java.util.Map; import java.util.Set; import com.ibm.icu.impl.CacheBase; @@ -147,7 +145,7 @@ class NumberSkeletonImpl { } } - private static GroupingStrategy stemToGrouper(ActualStem stem) { + private static GroupingStrategy stemToGroupingStrategy(ActualStem stem) { switch (stem) { case STEM_GROUP_OFF: return GroupingStrategy.OFF; @@ -213,6 +211,91 @@ class NumberSkeletonImpl { } } + private static void groupingStrategyToStemString(GroupingStrategy value, StringBuilder sb) { + switch (value) { + case OFF: + sb.append("group-off"); + break; + case MIN2: + sb.append("group-min2"); + break; + case AUTO: + sb.append("group-auto"); + break; + case ON_ALIGNED: + sb.append("group-on-aligned"); + break; + case THOUSANDS: + sb.append("group-thousands"); + break; + default: + throw new AssertionError(); + } + } + + private static void unitWidthToStemString(UnitWidth value, StringBuilder sb) { + switch (value) { + case NARROW: + sb.append("unit-width-narrow"); + break; + case SHORT: + sb.append("unit-width-short"); + break; + case FULL_NAME: + sb.append("unit-width-full-name"); + break; + case ISO_CODE: + sb.append("unit-width-iso-code"); + break; + case HIDDEN: + sb.append("unit-width-hidden"); + break; + default: + throw new AssertionError(); + } + } + + private static void signDisplayToStemString(SignDisplay value, StringBuilder sb) { + switch (value) { + case AUTO: + sb.append("sign-auto"); + break; + case ALWAYS: + sb.append("sign-always"); + break; + case NEVER: + sb.append("sign-never"); + break; + case ACCOUNTING: + sb.append("sign-accounting"); + break; + case ACCOUNTING_ALWAYS: + sb.append("sign-accounting-always"); + break; + case EXCEPT_ZERO: + sb.append("sign-except-zero"); + break; + case ACCOUNTING_EXCEPT_ZERO: + sb.append("sign-accounting-except-zero"); + break; + default: + throw new AssertionError(); + } + } + + private static void decimalSeparatorDisplayToStemString(DecimalSeparatorDisplay value, StringBuilder sb) { + switch (value) { + case AUTO: + sb.append("decimal-auto"); + break; + case ALWAYS: + sb.append("decimal-always"); + break; + default: + throw new AssertionError(); + } + } + static final String SERIALIZED_STEM_TRIE = buildStemTrie(); static String buildStemTrie() { @@ -264,67 +347,6 @@ class NumberSkeletonImpl { return b.buildCharSequence(StringTrieBuilder.Option.FAST).toString(); } - static class SkeletonDataStructure { - final Map valuesToStems; - - SkeletonDataStructure() { - valuesToStems = new HashMap(); - } - - public void put(StemType stemType, String content, Object value) { - valuesToStems.put(value, content); - } - - public String valueToStem(Object value) { - return valuesToStems.get(value); - } - } - - static final SkeletonDataStructure skeletonData = new SkeletonDataStructure(); - - static { - SkeletonDataStructure d = skeletonData; // abbreviate for shorter lines - d.put(StemType.COMPACT_NOTATION, "compact-short", Notation.compactShort()); - d.put(StemType.COMPACT_NOTATION, "compact-long", Notation.compactLong()); - d.put(StemType.SCIENTIFIC_NOTATION, "scientific", Notation.scientific()); - d.put(StemType.SCIENTIFIC_NOTATION, "engineering", Notation.engineering()); - d.put(StemType.SIMPLE_NOTATION, "notation-simple", Notation.simple()); - - d.put(StemType.NO_UNIT, "base-unit", NoUnit.BASE); - d.put(StemType.NO_UNIT, "percent", NoUnit.PERCENT); - d.put(StemType.NO_UNIT, "permille", NoUnit.PERMILLE); - - d.put(StemType.ROUNDER, "round-integer", Rounder.integer()); - d.put(StemType.ROUNDER, "round-unlimited", Rounder.unlimited()); - d.put(StemType.ROUNDER, "round-currency-standard", Rounder.currency(CurrencyUsage.STANDARD)); - d.put(StemType.ROUNDER, "round-currency-cash", Rounder.currency(CurrencyUsage.CASH)); - - d.put(StemType.GROUPING, "group-off", GroupingStrategy.OFF); - d.put(StemType.GROUPING, "group-min2", GroupingStrategy.MIN2); - d.put(StemType.GROUPING, "group-auto", GroupingStrategy.AUTO); - d.put(StemType.GROUPING, "group-on-aligned", GroupingStrategy.ON_ALIGNED); - d.put(StemType.GROUPING, "group-thousands", GroupingStrategy.THOUSANDS); - - d.put(StemType.LATIN, "latin", NumberingSystem.LATIN); - - d.put(StemType.UNIT_WIDTH, "unit-width-narrow", UnitWidth.NARROW); - d.put(StemType.UNIT_WIDTH, "unit-width-short", UnitWidth.SHORT); - d.put(StemType.UNIT_WIDTH, "unit-width-full-name", UnitWidth.FULL_NAME); - d.put(StemType.UNIT_WIDTH, "unit-width-iso-code", UnitWidth.ISO_CODE); - d.put(StemType.UNIT_WIDTH, "unit-width-hidden", UnitWidth.HIDDEN); - - d.put(StemType.SIGN_DISPLAY, "sign-auto", SignDisplay.AUTO); - d.put(StemType.SIGN_DISPLAY, "sign-always", SignDisplay.ALWAYS); - d.put(StemType.SIGN_DISPLAY, "sign-never", SignDisplay.NEVER); - d.put(StemType.SIGN_DISPLAY, "sign-accounting", SignDisplay.ACCOUNTING); - d.put(StemType.SIGN_DISPLAY, "sign-accounting-always", SignDisplay.ACCOUNTING_ALWAYS); - d.put(StemType.SIGN_DISPLAY, "sign-except-zero", SignDisplay.EXCEPT_ZERO); - d.put(StemType.SIGN_DISPLAY, "sign-accounting-except-zero", SignDisplay.ACCOUNTING_EXCEPT_ZERO); - - d.put(StemType.DECIMAL_DISPLAY, "decimal-auto", DecimalSeparatorDisplay.AUTO); - d.put(StemType.DECIMAL_DISPLAY, "decimal-always", DecimalSeparatorDisplay.ALWAYS); - } - static final String[] ROUNDING_MODE_STRINGS = { "up", "down", @@ -521,7 +543,7 @@ class NumberSkeletonImpl { case STEM_GROUP_ON_ALIGNED: case STEM_GROUP_THOUSANDS: checkNull(macros.grouping, segment); - macros.grouping = stemToGrouper(stemEnum); + macros.grouping = stemToGroupingStrategy(stemEnum); return StemType.OTHER; case STEM_LATIN: @@ -1047,21 +1069,19 @@ class NumberSkeletonImpl { ///// private static boolean generateNotationValue(MacroProps macros, StringBuilder sb) { - // Check for literals - String literal = skeletonData.valueToStem(macros.notation); - if ("notation-simple".equals(literal)) { - return false; // Default value - } else if (literal != null) { - sb.append(literal); - return true; - } - - // Generate the stem if (macros.notation instanceof CompactNotation) { - // Compact notation generated from custom data (not supported in skeleton) - // The other compact notations are literals - throw new UnsupportedOperationException( - "Cannot generate number skeleton with custom compact data"); + if (macros.notation == Notation.compactLong()) { + sb.append("compact-long"); + return true; + } else if (macros.notation == Notation.compactShort()) { + sb.append("compact-short"); + return true; + } else { + // Compact notation generated from custom data (not supported in skeleton) + // The other compact notations are literals + throw new UnsupportedOperationException( + "Cannot generate number skeleton with custom compact data"); + } } else if (macros.notation instanceof ScientificNotation) { ScientificNotation impl = (ScientificNotation) macros.notation; if (impl.engineeringInterval == 3) { @@ -1075,33 +1095,33 @@ class NumberSkeletonImpl { } if (impl.exponentSignDisplay != SignDisplay.AUTO) { sb.append('/'); - sb.append(skeletonData.valueToStem(impl.exponentSignDisplay)); + signDisplayToStemString(impl.exponentSignDisplay, sb); } return true; } else { - // SimpleNotation should be handled by a literal - throw new AssertionError(); + assert macros.notation instanceof SimpleNotation; + // Default value is not shown in normalized form + return false; } } private static boolean generateUnitValue(MacroProps macros, StringBuilder sb) { - // Check for literals - String literal = skeletonData.valueToStem(macros.unit); - if ("base-unit".equals(literal)) { - return false; // Default value - } else if (literal != null) { - sb.append(literal); - return true; - } - - // Generate the stem if (macros.unit instanceof Currency) { sb.append("currency/"); generateCurrencyOption((Currency) macros.unit, sb); return true; } else if (macros.unit instanceof NoUnit) { - // This should be taken care of by the literals. - throw new AssertionError(); + if (macros.unit == NoUnit.PERCENT) { + sb.append("percent"); + return true; + } else if (macros.unit == NoUnit.PERMILLE) { + sb.append("permille"); + return true; + } else { + assert macros.unit == NoUnit.BASE; + // Default value is not shown in normalized form + return false; + } } else { sb.append("measure-unit/"); generateMeasureUnitOption(macros.unit, sb); @@ -1122,14 +1142,6 @@ class NumberSkeletonImpl { } private static boolean generateRoundingValue(MacroProps macros, StringBuilder sb) { - // Check for literals - String literal = skeletonData.valueToStem(macros.rounder); - if (literal != null) { - sb.append(literal); - return true; - } - - // Generate the stem if (macros.rounder instanceof Rounder.InfiniteRounderImpl) { sb.append("round-unlimited"); } else if (macros.rounder instanceof Rounder.FractionRounderImpl) { @@ -1151,6 +1163,8 @@ class NumberSkeletonImpl { Rounder.IncrementRounderImpl impl = (Rounder.IncrementRounderImpl) macros.rounder; sb.append("round-increment/"); generateIncrementOption(impl.increment, sb); + } else if (macros.rounder instanceof Rounder.InfiniteRounderImpl) { + sb.append("round-unlimited"); } else { assert macros.rounder instanceof Rounder.CurrencyRounderImpl; Rounder.CurrencyRounderImpl impl = (Rounder.CurrencyRounderImpl) macros.rounder; @@ -1176,7 +1190,7 @@ class NumberSkeletonImpl { if (macros.grouping == GroupingStrategy.AUTO) { return false; // Default value } - appendExpectedLiteral(macros.grouping, sb); + groupingStrategyToStemString((GroupingStrategy) macros.grouping, sb); return true; } else { throw new UnsupportedOperationException( @@ -1214,7 +1228,7 @@ class NumberSkeletonImpl { if (macros.unitWidth == UnitWidth.SHORT) { return false; // Default value } - appendExpectedLiteral(macros.unitWidth, sb); + unitWidthToStemString(macros.unitWidth, sb); return true; } @@ -1222,7 +1236,7 @@ class NumberSkeletonImpl { if (macros.sign == SignDisplay.AUTO) { return false; // Default value } - appendExpectedLiteral(macros.sign, sb); + signDisplayToStemString(macros.sign, sb); return true; } @@ -1230,7 +1244,7 @@ class NumberSkeletonImpl { if (macros.decimal == DecimalSeparatorDisplay.AUTO) { return false; // Default value } - appendExpectedLiteral(macros.decimal, sb); + decimalSeparatorDisplayToStemString(macros.decimal, sb); return true; } @@ -1247,10 +1261,4 @@ class NumberSkeletonImpl { sb.appendCodePoint(cp); } } - - private static void appendExpectedLiteral(Object value, StringBuilder sb) { - String literal = skeletonData.valueToStem(value); - assert literal != null; - sb.append(literal); - } } From c725920cff1a4b5f615000e0d5c704e5a00f6354 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Fri, 23 Mar 2018 04:40:01 +0000 Subject: [PATCH 055/129] ICU-8610 Refactoring and renaming entities in Java implementation. Adding lots of comments. Should be ready to start C++ port. X-SVN-Rev: 41141 --- .../ibm/icu/number/NumberSkeletonImpl.java | 1696 +++++++++-------- .../dev/test/number/NumberSkeletonTest.java | 41 +- 2 files changed, 905 insertions(+), 832 deletions(-) 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 26c40f9760..8154f819cb 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 @@ -32,29 +32,35 @@ import com.ibm.icu.util.StringTrieBuilder; */ class NumberSkeletonImpl { - static enum StemType { - OTHER, - COMPACT_NOTATION, - SCIENTIFIC_NOTATION, - SIMPLE_NOTATION, - NO_UNIT, - CURRENCY, - MEASURE_UNIT, - PER_MEASURE_UNIT, - ROUNDER, - FRACTION_ROUNDER, - MAYBE_INCREMENT_ROUNDER, - CURRENCY_ROUNDER, - GROUPING, - INTEGER_WIDTH, - LATIN, - NUMBERING_SYSTEM, - UNIT_WIDTH, - SIGN_DISPLAY, - DECIMAL_DISPLAY + /** + * While parsing a skeleton, this enum records what type of option we expect to find next. + */ + static enum ParseState { + // Section 0: We expect whitespace or a stem, but not an option: + STATE_NULL, + + // Section 1: We might accept an option, but it is not required: + STATE_SCIENTIFIC, + STATE_ROUNDER, + STATE_FRACTION_ROUNDER, + + // Section 2: An option is required: + STATE_INCREMENT_ROUNDER, + STATE_MEASURE_UNIT, + STATE_PER_MEASURE_UNIT, + STATE_CURRENCY_UNIT, + STATE_INTEGER_WIDTH, + STATE_NUMBERING_SYSTEM, } - static enum ActualStem { + /** + * All possible stem literals have an entry in the StemEnum. The enum name is the kebab case stem + * string literal written in upper snake case. + * + * @see StemToObject + * @see #SERIALIZED_STEM_TRIE + */ + static enum StemEnum { // Section 1: Stems that do not require an option: STEM_COMPACT_SHORT, STEM_COMPACT_LONG, @@ -98,255 +104,272 @@ class NumberSkeletonImpl { STEM_NUMBERING_SYSTEM, }; - static final ActualStem[] ACTUAL_STEM_VALUES = ActualStem.values(); + /** For mapping from ordinal back to StemEnum in Java. */ + static final StemEnum[] STEM_ENUM_VALUES = StemEnum.values(); - private static Notation stemToNotation(ActualStem stem) { - switch (stem) { - case STEM_COMPACT_SHORT: - return Notation.compactShort(); - case STEM_COMPACT_LONG: - return Notation.compactLong(); - case STEM_SCIENTIFIC: - return Notation.scientific(); - case STEM_ENGINEERING: - return Notation.engineering(); - case STEM_NOTATION_SIMPLE: - return Notation.simple(); - default: - return null; + /** + * Utility class for methods that convert from StemEnum to corresponding objects or enums. This + * applies to only the "Section 1" stems, those that are well-defined without an option. + */ + static final class StemToObject { + + private static Notation notation(StemEnum stem) { + switch (stem) { + case STEM_COMPACT_SHORT: + return Notation.compactShort(); + case STEM_COMPACT_LONG: + return Notation.compactLong(); + case STEM_SCIENTIFIC: + return Notation.scientific(); + case STEM_ENGINEERING: + return Notation.engineering(); + case STEM_NOTATION_SIMPLE: + return Notation.simple(); + default: + return null; + } + } + + private static MeasureUnit unit(StemEnum stem) { + switch (stem) { + case STEM_BASE_UNIT: + return NoUnit.BASE; + case STEM_PERCENT: + return NoUnit.PERCENT; + case STEM_PERMILLE: + return NoUnit.PERMILLE; + default: + return null; + } + } + + private static Rounder rounder(StemEnum stem) { + switch (stem) { + case STEM_ROUND_INTEGER: + return Rounder.integer(); + case STEM_ROUND_UNLIMITED: + return Rounder.unlimited(); + case STEM_ROUND_CURRENCY_STANDARD: + return Rounder.currency(CurrencyUsage.STANDARD); + case STEM_ROUND_CURRENCY_CASH: + return Rounder.currency(CurrencyUsage.CASH); + default: + return null; + } + } + + private static GroupingStrategy groupingStrategy(StemEnum stem) { + switch (stem) { + case STEM_GROUP_OFF: + return GroupingStrategy.OFF; + case STEM_GROUP_MIN2: + return GroupingStrategy.MIN2; + case STEM_GROUP_AUTO: + return GroupingStrategy.AUTO; + case STEM_GROUP_ON_ALIGNED: + return GroupingStrategy.ON_ALIGNED; + case STEM_GROUP_THOUSANDS: + return GroupingStrategy.THOUSANDS; + default: + return null; + } + } + + private static UnitWidth unitWidth(StemEnum stem) { + switch (stem) { + case STEM_UNIT_WIDTH_NARROW: + return UnitWidth.NARROW; + case STEM_UNIT_WIDTH_SHORT: + return UnitWidth.SHORT; + case STEM_UNIT_WIDTH_FULL_NAME: + return UnitWidth.FULL_NAME; + case STEM_UNIT_WIDTH_ISO_CODE: + return UnitWidth.ISO_CODE; + case STEM_UNIT_WIDTH_HIDDEN: + return UnitWidth.HIDDEN; + default: + return null; + } + } + + private static SignDisplay signDisplay(StemEnum stem) { + switch (stem) { + case STEM_SIGN_AUTO: + return SignDisplay.AUTO; + case STEM_SIGN_ALWAYS: + return SignDisplay.ALWAYS; + case STEM_SIGN_NEVER: + return SignDisplay.NEVER; + case STEM_SIGN_ACCOUNTING: + return SignDisplay.ACCOUNTING; + case STEM_SIGN_ACCOUNTING_ALWAYS: + return SignDisplay.ACCOUNTING_ALWAYS; + case STEM_SIGN_EXCEPT_ZERO: + return SignDisplay.EXCEPT_ZERO; + case STEM_SIGN_ACCOUNTING_EXCEPT_ZERO: + return SignDisplay.ACCOUNTING_EXCEPT_ZERO; + default: + return null; + } + } + + private static DecimalSeparatorDisplay decimalSeparatorDisplay(StemEnum stem) { + switch (stem) { + case STEM_DECIMAL_AUTO: + return DecimalSeparatorDisplay.AUTO; + case STEM_DECIMAL_ALWAYS: + return DecimalSeparatorDisplay.ALWAYS; + default: + return null; + } } } - private static MeasureUnit stemToUnit(ActualStem stem) { - switch (stem) { - case STEM_BASE_UNIT: - return NoUnit.BASE; - case STEM_PERCENT: - return NoUnit.PERCENT; - case STEM_PERMILLE: - return NoUnit.PERMILLE; - default: - return null; - } - } - - private static Rounder stemToRounder(ActualStem stem) { - switch (stem) { - case STEM_ROUND_INTEGER: - return Rounder.integer(); - case STEM_ROUND_UNLIMITED: - return Rounder.unlimited(); - case STEM_ROUND_CURRENCY_STANDARD: - return Rounder.currency(CurrencyUsage.STANDARD); - case STEM_ROUND_CURRENCY_CASH: - return Rounder.currency(CurrencyUsage.CASH); - default: - return null; - } - } - - private static GroupingStrategy stemToGroupingStrategy(ActualStem stem) { - switch (stem) { - case STEM_GROUP_OFF: - return GroupingStrategy.OFF; - case STEM_GROUP_MIN2: - return GroupingStrategy.MIN2; - case STEM_GROUP_AUTO: - return GroupingStrategy.AUTO; - case STEM_GROUP_ON_ALIGNED: - return GroupingStrategy.ON_ALIGNED; - case STEM_GROUP_THOUSANDS: - return GroupingStrategy.THOUSANDS; - default: - return null; - } - } - - private static UnitWidth stemToUnitWidth(ActualStem stem) { - switch (stem) { - case STEM_UNIT_WIDTH_NARROW: - return UnitWidth.NARROW; - case STEM_UNIT_WIDTH_SHORT: - return UnitWidth.SHORT; - case STEM_UNIT_WIDTH_FULL_NAME: - return UnitWidth.FULL_NAME; - case STEM_UNIT_WIDTH_ISO_CODE: - return UnitWidth.ISO_CODE; - case STEM_UNIT_WIDTH_HIDDEN: - return UnitWidth.HIDDEN; - default: - return null; - } - } - - private static SignDisplay stemToSignDisplay(ActualStem stem) { - switch (stem) { - case STEM_SIGN_AUTO: - return SignDisplay.AUTO; - case STEM_SIGN_ALWAYS: - return SignDisplay.ALWAYS; - case STEM_SIGN_NEVER: - return SignDisplay.NEVER; - case STEM_SIGN_ACCOUNTING: - return SignDisplay.ACCOUNTING; - case STEM_SIGN_ACCOUNTING_ALWAYS: - return SignDisplay.ACCOUNTING_ALWAYS; - case STEM_SIGN_EXCEPT_ZERO: - return SignDisplay.EXCEPT_ZERO; - case STEM_SIGN_ACCOUNTING_EXCEPT_ZERO: - return SignDisplay.ACCOUNTING_EXCEPT_ZERO; - default: - return null; - } - } - - private static DecimalSeparatorDisplay stemToDecimalSeparatorDisplay(ActualStem stem) { - switch (stem) { - case STEM_DECIMAL_AUTO: - return DecimalSeparatorDisplay.AUTO; - case STEM_DECIMAL_ALWAYS: - return DecimalSeparatorDisplay.ALWAYS; - default: - return null; - } - } - - private static void groupingStrategyToStemString(GroupingStrategy value, StringBuilder sb) { - switch (value) { - case OFF: - sb.append("group-off"); - break; - case MIN2: - sb.append("group-min2"); - break; - case AUTO: - sb.append("group-auto"); - break; - case ON_ALIGNED: - sb.append("group-on-aligned"); - break; - case THOUSANDS: - sb.append("group-thousands"); - break; - default: - throw new AssertionError(); - } - } - - private static void unitWidthToStemString(UnitWidth value, StringBuilder sb) { - switch (value) { - case NARROW: - sb.append("unit-width-narrow"); - break; - case SHORT: - sb.append("unit-width-short"); - break; - case FULL_NAME: - sb.append("unit-width-full-name"); - break; - case ISO_CODE: - sb.append("unit-width-iso-code"); - break; - case HIDDEN: - sb.append("unit-width-hidden"); - break; - default: - throw new AssertionError(); - } - } - - private static void signDisplayToStemString(SignDisplay value, StringBuilder sb) { - switch (value) { - case AUTO: - sb.append("sign-auto"); - break; - case ALWAYS: - sb.append("sign-always"); - break; - case NEVER: - sb.append("sign-never"); - break; - case ACCOUNTING: - sb.append("sign-accounting"); - break; - case ACCOUNTING_ALWAYS: - sb.append("sign-accounting-always"); - break; - case EXCEPT_ZERO: - sb.append("sign-except-zero"); - break; - case ACCOUNTING_EXCEPT_ZERO: - sb.append("sign-accounting-except-zero"); - break; - default: - throw new AssertionError(); - } - } - - private static void decimalSeparatorDisplayToStemString(DecimalSeparatorDisplay value, StringBuilder sb) { - switch (value) { - case AUTO: - sb.append("decimal-auto"); - break; - case ALWAYS: - sb.append("decimal-always"); - break; - default: - throw new AssertionError(); + /** + * Utility class for methods that convert from enums to stem strings. More complex object conversions + * take place in ObjectToStemString. + */ + static final class EnumToStemString { + + private static void groupingStrategy(GroupingStrategy value, StringBuilder sb) { + switch (value) { + case OFF: + sb.append("group-off"); + break; + case MIN2: + sb.append("group-min2"); + break; + case AUTO: + sb.append("group-auto"); + break; + case ON_ALIGNED: + sb.append("group-on-aligned"); + break; + case THOUSANDS: + sb.append("group-thousands"); + break; + default: + throw new AssertionError(); + } + } + + private static void unitWidth(UnitWidth value, StringBuilder sb) { + switch (value) { + case NARROW: + sb.append("unit-width-narrow"); + break; + case SHORT: + sb.append("unit-width-short"); + break; + case FULL_NAME: + sb.append("unit-width-full-name"); + break; + case ISO_CODE: + sb.append("unit-width-iso-code"); + break; + case HIDDEN: + sb.append("unit-width-hidden"); + break; + default: + throw new AssertionError(); + } + } + + private static void signDisplay(SignDisplay value, StringBuilder sb) { + switch (value) { + case AUTO: + sb.append("sign-auto"); + break; + case ALWAYS: + sb.append("sign-always"); + break; + case NEVER: + sb.append("sign-never"); + break; + case ACCOUNTING: + sb.append("sign-accounting"); + break; + case ACCOUNTING_ALWAYS: + sb.append("sign-accounting-always"); + break; + case EXCEPT_ZERO: + sb.append("sign-except-zero"); + break; + case ACCOUNTING_EXCEPT_ZERO: + sb.append("sign-accounting-except-zero"); + break; + default: + throw new AssertionError(); + } + } + + private static void decimalSeparatorDisplay(DecimalSeparatorDisplay value, StringBuilder sb) { + switch (value) { + case AUTO: + sb.append("decimal-auto"); + break; + case ALWAYS: + sb.append("decimal-always"); + break; + default: + throw new AssertionError(); + } } } + /** A data structure for mapping from stem strings to the stem enum. Built at startup. */ static final String SERIALIZED_STEM_TRIE = buildStemTrie(); static String buildStemTrie() { CharsTrieBuilder b = new CharsTrieBuilder(); // Section 1: - b.add("compact-short", ActualStem.STEM_COMPACT_SHORT.ordinal()); - b.add("compact-long", ActualStem.STEM_COMPACT_LONG.ordinal()); - b.add("scientific", ActualStem.STEM_SCIENTIFIC.ordinal()); - b.add("engineering", ActualStem.STEM_ENGINEERING.ordinal()); - b.add("notation-simple", ActualStem.STEM_NOTATION_SIMPLE.ordinal()); - b.add("base-unit", ActualStem.STEM_BASE_UNIT.ordinal()); - b.add("percent", ActualStem.STEM_PERCENT.ordinal()); - b.add("permille", ActualStem.STEM_PERMILLE.ordinal()); - b.add("round-integer", ActualStem.STEM_ROUND_INTEGER.ordinal()); - b.add("round-unlimited", ActualStem.STEM_ROUND_UNLIMITED.ordinal()); - b.add("round-currency-standard", ActualStem.STEM_ROUND_CURRENCY_STANDARD.ordinal()); - b.add("round-currency-cash", ActualStem.STEM_ROUND_CURRENCY_CASH.ordinal()); - b.add("group-off", ActualStem.STEM_GROUP_OFF.ordinal()); - b.add("group-min2", ActualStem.STEM_GROUP_MIN2.ordinal()); - b.add("group-auto", ActualStem.STEM_GROUP_AUTO.ordinal()); - b.add("group-on-aligned", ActualStem.STEM_GROUP_ON_ALIGNED.ordinal()); - b.add("group-thousands", ActualStem.STEM_GROUP_THOUSANDS.ordinal()); - b.add("latin", ActualStem.STEM_LATIN.ordinal()); - b.add("unit-width-narrow", ActualStem.STEM_UNIT_WIDTH_NARROW.ordinal()); - b.add("unit-width-short", ActualStem.STEM_UNIT_WIDTH_SHORT.ordinal()); - b.add("unit-width-full-name", ActualStem.STEM_UNIT_WIDTH_FULL_NAME.ordinal()); - b.add("unit-width-iso-code", ActualStem.STEM_UNIT_WIDTH_ISO_CODE.ordinal()); - b.add("unit-width-hidden", ActualStem.STEM_UNIT_WIDTH_HIDDEN.ordinal()); - b.add("sign-auto", ActualStem.STEM_SIGN_AUTO.ordinal()); - b.add("sign-always", ActualStem.STEM_SIGN_ALWAYS.ordinal()); - b.add("sign-never", ActualStem.STEM_SIGN_NEVER.ordinal()); - b.add("sign-accounting", ActualStem.STEM_SIGN_ACCOUNTING.ordinal()); - b.add("sign-accounting-always", ActualStem.STEM_SIGN_ACCOUNTING_ALWAYS.ordinal()); - b.add("sign-except-zero", ActualStem.STEM_SIGN_EXCEPT_ZERO.ordinal()); - b.add("sign-accounting-except-zero", ActualStem.STEM_SIGN_ACCOUNTING_EXCEPT_ZERO.ordinal()); - b.add("decimal-auto", ActualStem.STEM_DECIMAL_AUTO.ordinal()); - b.add("decimal-always", ActualStem.STEM_DECIMAL_ALWAYS.ordinal()); + b.add("compact-short", StemEnum.STEM_COMPACT_SHORT.ordinal()); + b.add("compact-long", StemEnum.STEM_COMPACT_LONG.ordinal()); + b.add("scientific", StemEnum.STEM_SCIENTIFIC.ordinal()); + b.add("engineering", StemEnum.STEM_ENGINEERING.ordinal()); + b.add("notation-simple", StemEnum.STEM_NOTATION_SIMPLE.ordinal()); + b.add("base-unit", StemEnum.STEM_BASE_UNIT.ordinal()); + b.add("percent", StemEnum.STEM_PERCENT.ordinal()); + b.add("permille", StemEnum.STEM_PERMILLE.ordinal()); + b.add("round-integer", StemEnum.STEM_ROUND_INTEGER.ordinal()); + b.add("round-unlimited", StemEnum.STEM_ROUND_UNLIMITED.ordinal()); + b.add("round-currency-standard", StemEnum.STEM_ROUND_CURRENCY_STANDARD.ordinal()); + b.add("round-currency-cash", StemEnum.STEM_ROUND_CURRENCY_CASH.ordinal()); + b.add("group-off", StemEnum.STEM_GROUP_OFF.ordinal()); + b.add("group-min2", StemEnum.STEM_GROUP_MIN2.ordinal()); + b.add("group-auto", StemEnum.STEM_GROUP_AUTO.ordinal()); + b.add("group-on-aligned", StemEnum.STEM_GROUP_ON_ALIGNED.ordinal()); + b.add("group-thousands", StemEnum.STEM_GROUP_THOUSANDS.ordinal()); + b.add("latin", StemEnum.STEM_LATIN.ordinal()); + b.add("unit-width-narrow", StemEnum.STEM_UNIT_WIDTH_NARROW.ordinal()); + b.add("unit-width-short", StemEnum.STEM_UNIT_WIDTH_SHORT.ordinal()); + b.add("unit-width-full-name", StemEnum.STEM_UNIT_WIDTH_FULL_NAME.ordinal()); + b.add("unit-width-iso-code", StemEnum.STEM_UNIT_WIDTH_ISO_CODE.ordinal()); + b.add("unit-width-hidden", StemEnum.STEM_UNIT_WIDTH_HIDDEN.ordinal()); + b.add("sign-auto", StemEnum.STEM_SIGN_AUTO.ordinal()); + b.add("sign-always", StemEnum.STEM_SIGN_ALWAYS.ordinal()); + b.add("sign-never", StemEnum.STEM_SIGN_NEVER.ordinal()); + b.add("sign-accounting", StemEnum.STEM_SIGN_ACCOUNTING.ordinal()); + 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("decimal-auto", StemEnum.STEM_DECIMAL_AUTO.ordinal()); + b.add("decimal-always", StemEnum.STEM_DECIMAL_ALWAYS.ordinal()); // Section 2: - b.add("round-increment", ActualStem.STEM_ROUND_INCREMENT.ordinal()); - b.add("measure-unit", ActualStem.STEM_MEASURE_UNIT.ordinal()); - b.add("per-measure-unit", ActualStem.STEM_PER_MEASURE_UNIT.ordinal()); - b.add("currency", ActualStem.STEM_CURRENCY.ordinal()); - b.add("integer-width", ActualStem.STEM_INTEGER_WIDTH.ordinal()); - b.add("numbering-system", ActualStem.STEM_NUMBERING_SYSTEM.ordinal()); + b.add("round-increment", StemEnum.STEM_ROUND_INCREMENT.ordinal()); + b.add("measure-unit", StemEnum.STEM_MEASURE_UNIT.ordinal()); + b.add("per-measure-unit", StemEnum.STEM_PER_MEASURE_UNIT.ordinal()); + b.add("currency", StemEnum.STEM_CURRENCY.ordinal()); + b.add("integer-width", StemEnum.STEM_INTEGER_WIDTH.ordinal()); + b.add("numbering-system", StemEnum.STEM_NUMBERING_SYSTEM.ordinal()); // TODO: Use SLOW or FAST here? return b.buildCharSequence(StringTrieBuilder.Option.FAST).toString(); } + /** Kebab case versions of the rounding mode enum. */ static final String[] ROUNDING_MODE_STRINGS = { "up", "down", @@ -357,6 +380,9 @@ class NumberSkeletonImpl { "half-even", "unnecessary" }; + ///// ENTRYPOINT FUNCTIONS ///// + + /** Cache for parsed skeleton strings. */ private static final CacheBase cache = new SoftCache() { @Override protected UnlocalizedNumberFormatter createInstance(String skeletonString, Void unused) { @@ -403,8 +429,11 @@ class NumberSkeletonImpl { return sb.toString(); } - ///// + ///// MAIN PARSING FUNCTIONS ///// + /** + * Converts from a skeleton string to a MacroProps. This method contains the primary parse loop. + */ private static MacroProps parseSkeleton(String skeletonString) { // Add a trailing whitespace to the end of the skeleton string to make code cleaner. skeletonString += " "; @@ -412,8 +441,9 @@ class NumberSkeletonImpl { MacroProps macros = new MacroProps(); StringSegment segment = new StringSegment(skeletonString, false); CharsTrie stemTrie = new CharsTrie(SERIALIZED_STEM_TRIE, 0); - StemType stem = null; + ParseState stem = ParseState.STATE_NULL; int offset = 0; + // Primary skeleton parse loop: while (offset < segment.length()) { int cp = segment.codePointAt(offset); boolean isTokenSeparator = PatternProps.isWhiteSpace(cp); @@ -422,7 +452,7 @@ class NumberSkeletonImpl { if (!isTokenSeparator && !isOptionSeparator) { // Non-separator token; consume it. offset += Character.charCount(cp); - if (stem == null) { + if (stem == ParseState.STATE_NULL) { // We are currently consuming a stem. // Go to the next state in the stem trie. stemTrie.nextForCodePoint(cp); @@ -435,58 +465,75 @@ class NumberSkeletonImpl { // Otherwise, make sure it is a valid repeating separator. if (offset != 0) { segment.setLength(offset); - if (stem == null) { + if (stem == ParseState.STATE_NULL) { // The first separator after the start of a token. Parse it as a stem. - stem = parseStem2(segment, stemTrie, macros); + stem = parseStem(segment, stemTrie, macros); stemTrie.reset(); } else { // A separator after the first separator of a token. Parse it as an option. stem = parseOption(stem, segment, macros); } segment.resetLength(); - segment.adjustOffset(offset + 1); + + // Consume the segment: + segment.adjustOffset(offset); offset = 0; - } else if (stem != null) { + } else if (stem != ParseState.STATE_NULL) { // A separator ('/' or whitespace) following an option separator ('/') + segment.setLength(Character.charCount(cp)); // for error message throw new SkeletonSyntaxException("Unexpected separator character", segment); } else { // Two spaces in a row; this is OK. - segment.adjustOffset(Character.charCount(cp)); } - // Make sure we aren't in a state requiring an option, and then reset the state. - if (isTokenSeparator && stem != null) { + // Does the current stem forbid options? + if (isOptionSeparator && stem == ParseState.STATE_NULL) { + segment.setLength(Character.charCount(cp)); // for error message + throw new SkeletonSyntaxException("Unexpected option separator", segment); + } + + // Does the current stem require an option? + if (isTokenSeparator && stem != ParseState.STATE_NULL) { switch (stem) { - case MAYBE_INCREMENT_ROUNDER: - case MEASURE_UNIT: - case PER_MEASURE_UNIT: - case CURRENCY: - case INTEGER_WIDTH: - case NUMBERING_SYSTEM: + case STATE_INCREMENT_ROUNDER: + case STATE_MEASURE_UNIT: + case STATE_PER_MEASURE_UNIT: + case STATE_CURRENCY_UNIT: + case STATE_INTEGER_WIDTH: + case STATE_NUMBERING_SYSTEM: + segment.setLength(Character.charCount(cp)); // for error message throw new SkeletonSyntaxException("Stem requires an option", segment); default: break; } - stem = null; + stem = ParseState.STATE_NULL; } + + // Consume the separator: + segment.adjustOffset(Character.charCount(cp)); } - assert stem == null; + assert stem == ParseState.STATE_NULL; return macros; } - private static StemType parseStem2(StringSegment segment, CharsTrie stemTrie, MacroProps macros) { + /** + * Given that the current segment represents an stem, parse it and save the result. + * + * @return The next state after parsing this stem, corresponding to what subset of options to expect. + */ + private static ParseState parseStem(StringSegment segment, CharsTrie stemTrie, MacroProps macros) { // First check for "blueprint" stems, which start with a "signal char" switch (segment.charAt(0)) { case '.': checkNull(macros.rounder, segment); - parseFractionStem(segment, macros); - return StemType.FRACTION_ROUNDER; + BlueprintHelpers.parseFractionStem(segment, macros); + return ParseState.STATE_FRACTION_ROUNDER; case '@': checkNull(macros.rounder, segment); - parseDigitsStem(segment, macros); - return StemType.OTHER; + BlueprintHelpers.parseDigitsStem(segment, macros); + return ParseState.STATE_NULL; } // Now look at the stemsTrie, which is already be pointing at our stem. @@ -497,8 +544,8 @@ class NumberSkeletonImpl { throw new SkeletonSyntaxException("Unknown stem", segment); } - ActualStem stemEnum = ACTUAL_STEM_VALUES[stemTrie.getValue()]; - switch (stemEnum) { + StemEnum stem = STEM_ENUM_VALUES[stemTrie.getValue()]; + switch (stem) { // Stems with meaning on their own, not requiring an option: @@ -508,33 +555,33 @@ class NumberSkeletonImpl { case STEM_ENGINEERING: case STEM_NOTATION_SIMPLE: checkNull(macros.notation, segment); - macros.notation = stemToNotation(stemEnum); - switch (stemEnum) { + macros.notation = StemToObject.notation(stem); + switch (stem) { case STEM_SCIENTIFIC: case STEM_ENGINEERING: - return StemType.SCIENTIFIC_NOTATION; // allows for scientific options + return ParseState.STATE_SCIENTIFIC; // allows for scientific options default: - return StemType.OTHER; + return ParseState.STATE_NULL; } case STEM_BASE_UNIT: case STEM_PERCENT: case STEM_PERMILLE: checkNull(macros.unit, segment); - macros.unit = stemToUnit(stemEnum); - return StemType.OTHER; + macros.unit = StemToObject.unit(stem); + return ParseState.STATE_NULL; case STEM_ROUND_INTEGER: case STEM_ROUND_UNLIMITED: case STEM_ROUND_CURRENCY_STANDARD: case STEM_ROUND_CURRENCY_CASH: checkNull(macros.rounder, segment); - macros.rounder = stemToRounder(stemEnum); - switch (stemEnum) { + macros.rounder = StemToObject.rounder(stem); + switch (stem) { case STEM_ROUND_INTEGER: - return StemType.FRACTION_ROUNDER; // allows for "round-integer/@##" + return ParseState.STATE_FRACTION_ROUNDER; // allows for "round-integer/@##" default: - return StemType.ROUNDER; // allows for rounding mode options + return ParseState.STATE_ROUNDER; // allows for rounding mode options } case STEM_GROUP_OFF: @@ -543,13 +590,13 @@ class NumberSkeletonImpl { case STEM_GROUP_ON_ALIGNED: case STEM_GROUP_THOUSANDS: checkNull(macros.grouping, segment); - macros.grouping = stemToGroupingStrategy(stemEnum); - return StemType.OTHER; + macros.grouping = StemToObject.groupingStrategy(stem); + return ParseState.STATE_NULL; case STEM_LATIN: checkNull(macros.symbols, segment); macros.symbols = NumberingSystem.LATIN; - return StemType.OTHER; + return ParseState.STATE_NULL; case STEM_UNIT_WIDTH_NARROW: case STEM_UNIT_WIDTH_SHORT: @@ -557,8 +604,8 @@ class NumberSkeletonImpl { case STEM_UNIT_WIDTH_ISO_CODE: case STEM_UNIT_WIDTH_HIDDEN: checkNull(macros.unitWidth, segment); - macros.unitWidth = stemToUnitWidth(stemEnum); - return StemType.OTHER; + macros.unitWidth = StemToObject.unitWidth(stem); + return ParseState.STATE_NULL; case STEM_SIGN_AUTO: case STEM_SIGN_ALWAYS: @@ -568,99 +615,104 @@ class NumberSkeletonImpl { case STEM_SIGN_EXCEPT_ZERO: case STEM_SIGN_ACCOUNTING_EXCEPT_ZERO: checkNull(macros.sign, segment); - macros.sign = stemToSignDisplay(stemEnum); - return StemType.OTHER; + macros.sign = StemToObject.signDisplay(stem); + return ParseState.STATE_NULL; case STEM_DECIMAL_AUTO: case STEM_DECIMAL_ALWAYS: checkNull(macros.decimal, segment); - macros.decimal = stemToDecimalSeparatorDisplay(stemEnum); - return StemType.OTHER; + macros.decimal = StemToObject.decimalSeparatorDisplay(stem); + return ParseState.STATE_NULL; // Stems requiring an option: case STEM_ROUND_INCREMENT: checkNull(macros.rounder, segment); - return StemType.MAYBE_INCREMENT_ROUNDER; + return ParseState.STATE_INCREMENT_ROUNDER; case STEM_MEASURE_UNIT: checkNull(macros.unit, segment); - return StemType.MEASURE_UNIT; + return ParseState.STATE_MEASURE_UNIT; case STEM_PER_MEASURE_UNIT: checkNull(macros.perUnit, segment); - return StemType.PER_MEASURE_UNIT; + return ParseState.STATE_PER_MEASURE_UNIT; case STEM_CURRENCY: checkNull(macros.unit, segment); - return StemType.CURRENCY; + return ParseState.STATE_CURRENCY_UNIT; case STEM_INTEGER_WIDTH: checkNull(macros.integerWidth, segment); - return StemType.INTEGER_WIDTH; + return ParseState.STATE_INTEGER_WIDTH; case STEM_NUMBERING_SYSTEM: checkNull(macros.symbols, segment); - return StemType.NUMBERING_SYSTEM; + return ParseState.STATE_NUMBERING_SYSTEM; default: throw new AssertionError(); } } - private static StemType parseOption(StemType stem, StringSegment segment, MacroProps macros) { + /** + * Given that the current segment represents an option, parse it and save the result. + * + * @return The next state after parsing this option, corresponding to what subset of options to + * expect next. + */ + private static ParseState parseOption(ParseState stem, StringSegment segment, MacroProps macros) { ///// Required options: ///// switch (stem) { - case CURRENCY: - parseCurrencyOption(segment, macros); - return StemType.OTHER; - case MEASURE_UNIT: - parseMeasureUnitOption(segment, macros); - return StemType.OTHER; - case PER_MEASURE_UNIT: - parseMeasurePerUnitOption(segment, macros); - return StemType.OTHER; - case MAYBE_INCREMENT_ROUNDER: - parseIncrementOption(segment, macros); - return StemType.ROUNDER; - case INTEGER_WIDTH: - parseIntegerWidthOption(segment, macros); - return StemType.OTHER; - case NUMBERING_SYSTEM: - parseNumberingSystemOption(segment, macros); - return StemType.OTHER; + case STATE_CURRENCY_UNIT: + BlueprintHelpers.parseCurrencyOption(segment, macros); + return ParseState.STATE_NULL; + case STATE_MEASURE_UNIT: + BlueprintHelpers.parseMeasureUnitOption(segment, macros); + return ParseState.STATE_NULL; + case STATE_PER_MEASURE_UNIT: + BlueprintHelpers.parseMeasurePerUnitOption(segment, macros); + return ParseState.STATE_NULL; + case STATE_INCREMENT_ROUNDER: + BlueprintHelpers.parseIncrementOption(segment, macros); + return ParseState.STATE_ROUNDER; + case STATE_INTEGER_WIDTH: + BlueprintHelpers.parseIntegerWidthOption(segment, macros); + return ParseState.STATE_NULL; + case STATE_NUMBERING_SYSTEM: + BlueprintHelpers.parseNumberingSystemOption(segment, macros); + return ParseState.STATE_NULL; } ///// Non-required options: ///// // Scientific options switch (stem) { - case SCIENTIFIC_NOTATION: - if (parseExponentWidthOption(segment, macros)) { - return StemType.SCIENTIFIC_NOTATION; + case STATE_SCIENTIFIC: + if (BlueprintHelpers.parseExponentWidthOption(segment, macros)) { + return ParseState.STATE_SCIENTIFIC; } - if (parseExponentSignOption(segment, macros)) { - return StemType.SCIENTIFIC_NOTATION; + if (BlueprintHelpers.parseExponentSignOption(segment, macros)) { + return ParseState.STATE_SCIENTIFIC; } } // Frac-sig option switch (stem) { - case FRACTION_ROUNDER: - if (parseFracSigOption(segment, macros)) { - return StemType.ROUNDER; + case STATE_FRACTION_ROUNDER: + if (BlueprintHelpers.parseFracSigOption(segment, macros)) { + return ParseState.STATE_ROUNDER; } } // Rounding mode option switch (stem) { - case ROUNDER: - case FRACTION_ROUNDER: - case CURRENCY_ROUNDER: - if (parseRoundingModeOption(segment, macros)) { - return StemType.ROUNDER; + case STATE_ROUNDER: + case STATE_FRACTION_ROUNDER: + if (BlueprintHelpers.parseRoundingModeOption(segment, macros)) { + return ParseState.STATE_ROUNDER; } } @@ -668,36 +720,38 @@ class NumberSkeletonImpl { throw new SkeletonSyntaxException("Invalid option", segment); } + ///// MAIN SKELETON GENERATION FUNCTION ///// + private static void generateSkeleton(MacroProps macros, StringBuilder sb) { // Supported options - if (macros.notation != null && generateNotationValue(macros, sb)) { + if (macros.notation != null && GeneratorHelpers.notation(macros, sb)) { sb.append(' '); } - if (macros.unit != null && generateUnitValue(macros, sb)) { + if (macros.unit != null && GeneratorHelpers.unit(macros, sb)) { sb.append(' '); } - if (macros.perUnit != null && generatePerUnitValue(macros, sb)) { + if (macros.perUnit != null && GeneratorHelpers.perUnit(macros, sb)) { sb.append(' '); } - if (macros.rounder != null && generateRoundingValue(macros, sb)) { + if (macros.rounder != null && GeneratorHelpers.rounding(macros, sb)) { sb.append(' '); } - if (macros.grouping != null && generateGroupingValue(macros, sb)) { + if (macros.grouping != null && GeneratorHelpers.grouping(macros, sb)) { sb.append(' '); } - if (macros.integerWidth != null && generateIntegerWidthValue(macros, sb)) { + if (macros.integerWidth != null && GeneratorHelpers.integerWidth(macros, sb)) { sb.append(' '); } - if (macros.symbols != null && generateSymbolsValue(macros, sb)) { + if (macros.symbols != null && GeneratorHelpers.symbols(macros, sb)) { sb.append(' '); } - if (macros.unitWidth != null && generateUnitWidthValue(macros, sb)) { + if (macros.unitWidth != null && GeneratorHelpers.unitWidth(macros, sb)) { sb.append(' '); } - if (macros.sign != null && generateSignValue(macros, sb)) { + if (macros.sign != null && GeneratorHelpers.sign(macros, sb)) { sb.append(' '); } - if (macros.decimal != null && generateDecimalValue(macros, sb)) { + if (macros.decimal != null && GeneratorHelpers.decimal(macros, sb)) { sb.append(' '); } @@ -725,530 +779,542 @@ class NumberSkeletonImpl { } } - ///// + ///// BLUEPRINT HELPER FUNCTIONS (stem and options that cannot be interpreted literally) ///// - private static boolean parseExponentWidthOption(StringSegment segment, MacroProps macros) { - if (segment.charAt(0) != '+') { - return false; - } - int offset = 1; - int minExp = 0; - for (; offset < segment.length(); offset++) { - if (segment.charAt(offset) == 'e') { - minExp++; - } else { - break; + static final class BlueprintHelpers { + private static boolean parseExponentWidthOption(StringSegment segment, MacroProps macros) { + if (segment.charAt(0) != '+') { + return false; } - } - if (offset < segment.length()) { - return false; - } - // Use the public APIs to enforce bounds checking - macros.notation = ((ScientificNotation) macros.notation).withMinExponentDigits(minExp); - return true; - } - - private static void generateExponentWidthOption(int minExponentDigits, StringBuilder sb) { - sb.append('+'); - appendMultiple(sb, 'e', minExponentDigits); - } - - private static boolean parseExponentSignOption(StringSegment segment, MacroProps macros) { - // Get the sign display type out of the CharsTrie data structure. - // TODO: Make this more efficient (avoid object allocation)? It shouldn't be very hot code. - CharsTrie tempStemTrie = new CharsTrie(SERIALIZED_STEM_TRIE, 0); - BytesTrie.Result result = tempStemTrie.next(segment, 0, segment.length()); - if (result != BytesTrie.Result.INTERMEDIATE_VALUE && result != BytesTrie.Result.FINAL_VALUE) { - return false; - } - SignDisplay sign = stemToSignDisplay(ACTUAL_STEM_VALUES[tempStemTrie.getValue()]); - if (sign == null) { - return false; - } - macros.notation = ((ScientificNotation) macros.notation).withExponentSignDisplay(sign); - return true; - } - - private static void generateCurrencyOption(Currency currency, StringBuilder sb) { - sb.append(currency.getCurrencyCode()); - } - - private static void parseCurrencyOption(StringSegment segment, MacroProps macros) { - String currencyCode = segment.subSequence(0, segment.length()).toString(); - try { - macros.unit = Currency.getInstance(currencyCode); - } catch (IllegalArgumentException e) { - throw new SkeletonSyntaxException("Invalid currency", segment, e); - } - } - - private static void parseMeasureUnitOption(StringSegment segment, MacroProps macros) { - // NOTE: The category (type) of the unit is guaranteed to be a valid subtag (alphanumeric) - // http://unicode.org/reports/tr35/#Validity_Data - int firstHyphen = 0; - while (firstHyphen < segment.length() && segment.charAt(firstHyphen) != '-') { - firstHyphen++; - } - if (firstHyphen == segment.length()) { - throw new SkeletonSyntaxException("Invalid measure unit option", segment); - } - String type = segment.subSequence(0, firstHyphen).toString(); - String subType = segment.subSequence(firstHyphen + 1, segment.length()).toString(); - Set units = MeasureUnit.getAvailable(type); - for (MeasureUnit unit : units) { - if (subType.equals(unit.getSubtype())) { - macros.unit = unit; - return; - } - } - throw new SkeletonSyntaxException("Unknown measure unit", segment); - } - - private static void generateMeasureUnitOption(MeasureUnit unit, StringBuilder sb) { - sb.append(unit.getType() + "-" + unit.getSubtype()); - } - - private static void parseMeasurePerUnitOption(StringSegment segment, MacroProps macros) { - // A little bit of a hack: safe the current unit (numerator), call the main measure unit parsing - // code, put back the numerator unit, and put the new unit into per-unit. - MeasureUnit numerator = macros.unit; - parseMeasureUnitOption(segment, macros); - macros.perUnit = macros.unit; - macros.unit = numerator; - } - - private static void parseFractionStem(StringSegment segment, MacroProps macros) { - assert segment.charAt(0) == '.'; - int offset = 1; - int minFrac = 0; - int maxFrac; - for (; offset < segment.length(); offset++) { - if (segment.charAt(offset) == '0') { - minFrac++; - } else { - break; - } - } - if (offset < segment.length()) { - if (segment.charAt(offset) == '+') { - maxFrac = -1; - offset++; - } else { - maxFrac = minFrac; - for (; offset < segment.length(); offset++) { - if (segment.charAt(offset) == '#') { - maxFrac++; - } else { - break; - } - } - } - } else { - maxFrac = minFrac; - } - if (offset < segment.length()) { - throw new SkeletonSyntaxException("Invalid fraction stem", segment); - } - // Use the public APIs to enforce bounds checking - if (maxFrac == -1) { - macros.rounder = Rounder.minFraction(minFrac); - } else { - macros.rounder = Rounder.minMaxFraction(minFrac, maxFrac); - } - } - - private static void generateFractionStem(int minFrac, int maxFrac, StringBuilder sb) { - if (minFrac == 0 && maxFrac == 0) { - sb.append("round-integer"); - return; - } - sb.append('.'); - appendMultiple(sb, '0', minFrac); - if (maxFrac == -1) { - sb.append('+'); - } else { - appendMultiple(sb, '#', maxFrac - minFrac); - } - } - - private static void parseDigitsStem(StringSegment segment, MacroProps macros) { - assert segment.charAt(0) == '@'; - int offset = 0; - int minSig = 0; - int maxSig; - for (; offset < segment.length(); offset++) { - if (segment.charAt(offset) == '@') { - minSig++; - } else { - break; - } - } - if (offset < segment.length()) { - if (segment.charAt(offset) == '+') { - maxSig = -1; - offset++; - } else { - maxSig = minSig; - for (; offset < segment.length(); offset++) { - if (segment.charAt(offset) == '#') { - maxSig++; - } else { - break; - } - } - } - } else { - maxSig = minSig; - } - if (offset < segment.length()) { - throw new SkeletonSyntaxException("Invalid significant digits stem", segment); - } - // Use the public APIs to enforce bounds checking - if (maxSig == -1) { - macros.rounder = Rounder.minDigits(minSig); - } else { - macros.rounder = Rounder.minMaxDigits(minSig, maxSig); - } - } - - private static void generateDigitsStem(int minSig, int maxSig, StringBuilder sb) { - appendMultiple(sb, '@', minSig); - if (maxSig == -1) { - sb.append('+'); - } else { - appendMultiple(sb, '#', maxSig - minSig); - } - } - - private static boolean parseFracSigOption(StringSegment segment, MacroProps macros) { - if (segment.charAt(0) != '@') { - return false; - } - int offset = 0; - int minSig = 0; - int maxSig; - for (; offset < segment.length(); offset++) { - if (segment.charAt(offset) == '@') { - minSig++; - } else { - break; - } - } - // For the frac-sig option, there must be minSig or maxSig but not both. - // Valid: @+, @@+, @@@+ - // Valid: @#, @##, @### - // Invalid: @, @@, @@@ - // Invalid: @@#, @@##, @@@# - if (offset < segment.length()) { - if (segment.charAt(offset) == '+') { - maxSig = -1; - offset++; - } else if (minSig > 1) { - // @@#, @@##, @@@# - throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", segment); - } else { - maxSig = minSig; - for (; offset < segment.length(); offset++) { - if (segment.charAt(offset) == '#') { - maxSig++; - } else { - break; - } - } - } - } else { - // @, @@, @@@ - throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", segment); - } - if (offset < segment.length()) { - throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", segment); - } - - FractionRounder oldRounder = (FractionRounder) macros.rounder; - if (maxSig == -1) { - macros.rounder = oldRounder.withMinDigits(minSig); - } else { - macros.rounder = oldRounder.withMaxDigits(maxSig); - } - return true; - } - - 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(); - BigDecimal increment; - try { - increment = new BigDecimal(str); - } catch (NumberFormatException e) { - throw new SkeletonSyntaxException("Invalid rounding increment", segment, e); - } - macros.rounder = Rounder.increment(increment); - } - - private static void generateIncrementOption(BigDecimal increment, StringBuilder sb) { - sb.append(increment.toPlainString()); - } - - private static boolean parseRoundingModeOption(StringSegment segment, MacroProps macros) { - for (int rm = 0; rm < ROUNDING_MODE_STRINGS.length; rm++) { - if (segment.equals(ROUNDING_MODE_STRINGS[rm])) { - macros.rounder = macros.rounder.withMode(RoundingMode.valueOf(rm)); - return true; - } - } - return false; - } - - private static void generateRoundingModeOption(RoundingMode mode, StringBuilder sb) { - String option = ROUNDING_MODE_STRINGS[mode.ordinal()]; - sb.append(option); - } - - private static void parseIntegerWidthOption(StringSegment segment, MacroProps macros) { - int offset = 0; - int minInt = 0; - int maxInt; - if (segment.charAt(0) == '+') { - maxInt = -1; - offset++; - } else { - maxInt = 0; - } - for (; offset < segment.length(); offset++) { - if (segment.charAt(offset) == '#') { - maxInt++; - } else { - break; - } - } - if (offset < segment.length()) { + int offset = 1; + int minExp = 0; for (; offset < segment.length(); offset++) { - if (segment.charAt(offset) == '0') { - minInt++; + if (segment.charAt(offset) == 'e') { + minExp++; } else { break; } } + if (offset < segment.length()) { + return false; + } + // Use the public APIs to enforce bounds checking + macros.notation = ((ScientificNotation) macros.notation).withMinExponentDigits(minExp); + return true; } - if (maxInt != -1) { - maxInt += minInt; - } - if (offset < segment.length()) { - throw new SkeletonSyntaxException("Invalid integer width stem", segment); - } - // Use the public APIs to enforce bounds checking - if (maxInt == -1) { - macros.integerWidth = IntegerWidth.zeroFillTo(minInt); - } else { - macros.integerWidth = IntegerWidth.zeroFillTo(minInt).truncateAt(maxInt); - } - } - private static void generateIntegerWidthOption(int minInt, int maxInt, StringBuilder sb) { - if (maxInt == -1) { + private static void generateExponentWidthOption(int minExponentDigits, StringBuilder sb) { sb.append('+'); - } else { - appendMultiple(sb, '#', maxInt - minInt); + appendMultiple(sb, 'e', minExponentDigits); } - appendMultiple(sb, '0', minInt); - } - private static void parseNumberingSystemOption(StringSegment segment, MacroProps macros) { - String nsName = segment.subSequence(0, segment.length()).toString(); - NumberingSystem ns = NumberingSystem.getInstanceByName(nsName); - if (ns == null) { - throw new SkeletonSyntaxException("Unknown numbering system", segment); + private static boolean parseExponentSignOption(StringSegment segment, MacroProps macros) { + // Get the sign display type out of the CharsTrie data structure. + // TODO: Make this more efficient (avoid object allocation)? It shouldn't be very hot code. + CharsTrie tempStemTrie = new CharsTrie(SERIALIZED_STEM_TRIE, 0); + BytesTrie.Result result = tempStemTrie.next(segment, 0, segment.length()); + if (result != BytesTrie.Result.INTERMEDIATE_VALUE + && result != BytesTrie.Result.FINAL_VALUE) { + return false; + } + SignDisplay sign = StemToObject.signDisplay(STEM_ENUM_VALUES[tempStemTrie.getValue()]); + if (sign == null) { + return false; + } + macros.notation = ((ScientificNotation) macros.notation).withExponentSignDisplay(sign); + return true; } - macros.symbols = ns; - } - private static void generateNumberingSystemOption(NumberingSystem ns, StringBuilder sb) { - sb.append(ns.getName()); - } + private static void parseCurrencyOption(StringSegment segment, MacroProps macros) { + String currencyCode = segment.subSequence(0, segment.length()).toString(); + try { + macros.unit = Currency.getInstance(currencyCode); + } catch (IllegalArgumentException e) { + throw new SkeletonSyntaxException("Invalid currency", segment, e); + } + } - ///// + private static void generateCurrencyOption(Currency currency, StringBuilder sb) { + sb.append(currency.getCurrencyCode()); + } - private static boolean generateNotationValue(MacroProps macros, StringBuilder sb) { - if (macros.notation instanceof CompactNotation) { - if (macros.notation == Notation.compactLong()) { - sb.append("compact-long"); - return true; - } else if (macros.notation == Notation.compactShort()) { - sb.append("compact-short"); - return true; + private static void parseMeasureUnitOption(StringSegment segment, MacroProps macros) { + // NOTE: The category (type) of the unit is guaranteed to be a valid subtag (alphanumeric) + // http://unicode.org/reports/tr35/#Validity_Data + int firstHyphen = 0; + while (firstHyphen < segment.length() && segment.charAt(firstHyphen) != '-') { + firstHyphen++; + } + if (firstHyphen == segment.length()) { + throw new SkeletonSyntaxException("Invalid measure unit option", segment); + } + String type = segment.subSequence(0, firstHyphen).toString(); + String subType = segment.subSequence(firstHyphen + 1, segment.length()).toString(); + Set units = MeasureUnit.getAvailable(type); + for (MeasureUnit unit : units) { + if (subType.equals(unit.getSubtype())) { + macros.unit = unit; + return; + } + } + throw new SkeletonSyntaxException("Unknown measure unit", segment); + } + + private static void generateMeasureUnitOption(MeasureUnit unit, StringBuilder sb) { + sb.append(unit.getType() + "-" + unit.getSubtype()); + } + + private static void parseMeasurePerUnitOption(StringSegment segment, MacroProps macros) { + // A little bit of a hack: safe the current unit (numerator), call the main measure unit + // parsing + // code, put back the numerator unit, and put the new unit into per-unit. + MeasureUnit numerator = macros.unit; + parseMeasureUnitOption(segment, macros); + macros.perUnit = macros.unit; + macros.unit = numerator; + } + + private static void parseFractionStem(StringSegment segment, MacroProps macros) { + assert segment.charAt(0) == '.'; + int offset = 1; + int minFrac = 0; + int maxFrac; + for (; offset < segment.length(); offset++) { + if (segment.charAt(offset) == '0') { + minFrac++; + } else { + break; + } + } + if (offset < segment.length()) { + if (segment.charAt(offset) == '+') { + maxFrac = -1; + offset++; + } else { + maxFrac = minFrac; + for (; offset < segment.length(); offset++) { + if (segment.charAt(offset) == '#') { + maxFrac++; + } else { + break; + } + } + } } else { - // Compact notation generated from custom data (not supported in skeleton) - // The other compact notations are literals - throw new UnsupportedOperationException( - "Cannot generate number skeleton with custom compact data"); + maxFrac = minFrac; } - } else if (macros.notation instanceof ScientificNotation) { - ScientificNotation impl = (ScientificNotation) macros.notation; - if (impl.engineeringInterval == 3) { - sb.append("engineering"); + if (offset < segment.length()) { + throw new SkeletonSyntaxException("Invalid fraction stem", segment); + } + // Use the public APIs to enforce bounds checking + if (maxFrac == -1) { + macros.rounder = Rounder.minFraction(minFrac); } else { - sb.append("scientific"); + macros.rounder = Rounder.minMaxFraction(minFrac, maxFrac); } - if (impl.minExponentDigits > 1) { - sb.append('/'); - generateExponentWidthOption(impl.minExponentDigits, sb); + } + + private static void generateFractionStem(int minFrac, int maxFrac, StringBuilder sb) { + if (minFrac == 0 && maxFrac == 0) { + sb.append("round-integer"); + return; } - if (impl.exponentSignDisplay != SignDisplay.AUTO) { - sb.append('/'); - signDisplayToStemString(impl.exponentSignDisplay, sb); + sb.append('.'); + appendMultiple(sb, '0', minFrac); + if (maxFrac == -1) { + sb.append('+'); + } else { + appendMultiple(sb, '#', maxFrac - minFrac); + } + } + + private static void parseDigitsStem(StringSegment segment, MacroProps macros) { + assert segment.charAt(0) == '@'; + int offset = 0; + int minSig = 0; + int maxSig; + for (; offset < segment.length(); offset++) { + if (segment.charAt(offset) == '@') { + minSig++; + } else { + break; + } + } + if (offset < segment.length()) { + if (segment.charAt(offset) == '+') { + maxSig = -1; + offset++; + } else { + maxSig = minSig; + for (; offset < segment.length(); offset++) { + if (segment.charAt(offset) == '#') { + maxSig++; + } else { + break; + } + } + } + } else { + maxSig = minSig; + } + if (offset < segment.length()) { + throw new SkeletonSyntaxException("Invalid significant digits stem", segment); + } + // Use the public APIs to enforce bounds checking + if (maxSig == -1) { + macros.rounder = Rounder.minDigits(minSig); + } else { + macros.rounder = Rounder.minMaxDigits(minSig, maxSig); + } + } + + private static void generateDigitsStem(int minSig, int maxSig, StringBuilder sb) { + appendMultiple(sb, '@', minSig); + if (maxSig == -1) { + sb.append('+'); + } else { + appendMultiple(sb, '#', maxSig - minSig); + } + } + + private static boolean parseFracSigOption(StringSegment segment, MacroProps macros) { + if (segment.charAt(0) != '@') { + return false; + } + int offset = 0; + int minSig = 0; + int maxSig; + for (; offset < segment.length(); offset++) { + if (segment.charAt(offset) == '@') { + minSig++; + } else { + break; + } + } + // For the frac-sig option, there must be minSig or maxSig but not both. + // Valid: @+, @@+, @@@+ + // Valid: @#, @##, @### + // Invalid: @, @@, @@@ + // Invalid: @@#, @@##, @@@# + if (offset < segment.length()) { + if (segment.charAt(offset) == '+') { + maxSig = -1; + offset++; + } else if (minSig > 1) { + // @@#, @@##, @@@# + throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", + segment); + } else { + maxSig = minSig; + for (; offset < segment.length(); offset++) { + if (segment.charAt(offset) == '#') { + maxSig++; + } else { + break; + } + } + } + } else { + // @, @@, @@@ + throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", segment); + } + if (offset < segment.length()) { + throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", segment); + } + + FractionRounder oldRounder = (FractionRounder) macros.rounder; + if (maxSig == -1) { + macros.rounder = oldRounder.withMinDigits(minSig); + } else { + macros.rounder = oldRounder.withMaxDigits(maxSig); } return true; - } else { - assert macros.notation instanceof SimpleNotation; - // Default value is not shown in normalized form + } + + 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(); + BigDecimal increment; + try { + increment = new BigDecimal(str); + } catch (NumberFormatException e) { + throw new SkeletonSyntaxException("Invalid rounding increment", segment, e); + } + macros.rounder = Rounder.increment(increment); + } + + private static void generateIncrementOption(BigDecimal increment, StringBuilder sb) { + sb.append(increment.toPlainString()); + } + + private static boolean parseRoundingModeOption(StringSegment segment, MacroProps macros) { + for (int rm = 0; rm < ROUNDING_MODE_STRINGS.length; rm++) { + if (segment.equals(ROUNDING_MODE_STRINGS[rm])) { + macros.rounder = macros.rounder.withMode(RoundingMode.valueOf(rm)); + return true; + } + } return false; } + + private static void generateRoundingModeOption(RoundingMode mode, StringBuilder sb) { + String option = ROUNDING_MODE_STRINGS[mode.ordinal()]; + sb.append(option); + } + + private static void parseIntegerWidthOption(StringSegment segment, MacroProps macros) { + int offset = 0; + int minInt = 0; + int maxInt; + if (segment.charAt(0) == '+') { + maxInt = -1; + offset++; + } else { + maxInt = 0; + } + for (; offset < segment.length(); offset++) { + if (segment.charAt(offset) == '#') { + maxInt++; + } else { + break; + } + } + if (offset < segment.length()) { + for (; offset < segment.length(); offset++) { + if (segment.charAt(offset) == '0') { + minInt++; + } else { + break; + } + } + } + if (maxInt != -1) { + maxInt += minInt; + } + if (offset < segment.length()) { + throw new SkeletonSyntaxException("Invalid integer width stem", segment); + } + // Use the public APIs to enforce bounds checking + if (maxInt == -1) { + macros.integerWidth = IntegerWidth.zeroFillTo(minInt); + } else { + macros.integerWidth = IntegerWidth.zeroFillTo(minInt).truncateAt(maxInt); + } + } + + private static void generateIntegerWidthOption(int minInt, int maxInt, StringBuilder sb) { + if (maxInt == -1) { + sb.append('+'); + } else { + appendMultiple(sb, '#', maxInt - minInt); + } + appendMultiple(sb, '0', minInt); + } + + private static void parseNumberingSystemOption(StringSegment segment, MacroProps macros) { + String nsName = segment.subSequence(0, segment.length()).toString(); + NumberingSystem ns = NumberingSystem.getInstanceByName(nsName); + if (ns == null) { + throw new SkeletonSyntaxException("Unknown numbering system", segment); + } + macros.symbols = ns; + } + + private static void generateNumberingSystemOption(NumberingSystem ns, StringBuilder sb) { + sb.append(ns.getName()); + } } - private static boolean generateUnitValue(MacroProps macros, StringBuilder sb) { - if (macros.unit instanceof Currency) { - sb.append("currency/"); - generateCurrencyOption((Currency) macros.unit, sb); - return true; - } else if (macros.unit instanceof NoUnit) { - if (macros.unit == NoUnit.PERCENT) { - sb.append("percent"); - return true; - } else if (macros.unit == NoUnit.PERMILLE) { - sb.append("permille"); + ///// STEM GENERATION HELPER FUNCTIONS ///// + + static final class GeneratorHelpers { + + private static boolean notation(MacroProps macros, StringBuilder sb) { + if (macros.notation instanceof CompactNotation) { + if (macros.notation == Notation.compactLong()) { + sb.append("compact-long"); + return true; + } else if (macros.notation == Notation.compactShort()) { + sb.append("compact-short"); + return true; + } else { + // Compact notation generated from custom data (not supported in skeleton) + // The other compact notations are literals + throw new UnsupportedOperationException( + "Cannot generate number skeleton with custom compact data"); + } + } else if (macros.notation instanceof ScientificNotation) { + ScientificNotation impl = (ScientificNotation) macros.notation; + if (impl.engineeringInterval == 3) { + sb.append("engineering"); + } else { + sb.append("scientific"); + } + if (impl.minExponentDigits > 1) { + sb.append('/'); + BlueprintHelpers.generateExponentWidthOption(impl.minExponentDigits, sb); + } + if (impl.exponentSignDisplay != SignDisplay.AUTO) { + sb.append('/'); + EnumToStemString.signDisplay(impl.exponentSignDisplay, sb); + } return true; } else { - assert macros.unit == NoUnit.BASE; + assert macros.notation instanceof SimpleNotation; // Default value is not shown in normalized form return false; } - } else { - sb.append("measure-unit/"); - generateMeasureUnitOption(macros.unit, sb); - return true; } - } - private static boolean generatePerUnitValue(MacroProps macros, StringBuilder sb) { - // Per-units are currently expected to be only MeasureUnits. - if (macros.unit instanceof Currency || macros.unit instanceof NoUnit) { - throw new UnsupportedOperationException( - "Cannot generate number skeleton with per-unit that is not a standard measure unit"); - } else { - sb.append("per-measure-unit/"); - generateMeasureUnitOption(macros.perUnit, sb); - return true; - } - } - - private static boolean generateRoundingValue(MacroProps macros, StringBuilder sb) { - if (macros.rounder instanceof Rounder.InfiniteRounderImpl) { - sb.append("round-unlimited"); - } else if (macros.rounder instanceof Rounder.FractionRounderImpl) { - Rounder.FractionRounderImpl impl = (Rounder.FractionRounderImpl) macros.rounder; - generateFractionStem(impl.minFrac, impl.maxFrac, sb); - } else if (macros.rounder instanceof Rounder.SignificantRounderImpl) { - Rounder.SignificantRounderImpl impl = (Rounder.SignificantRounderImpl) macros.rounder; - generateDigitsStem(impl.minSig, impl.maxSig, sb); - } else if (macros.rounder instanceof Rounder.FracSigRounderImpl) { - Rounder.FracSigRounderImpl impl = (Rounder.FracSigRounderImpl) macros.rounder; - generateFractionStem(impl.minFrac, impl.maxFrac, sb); - sb.append('/'); - if (impl.minSig == -1) { - generateDigitsStem(1, impl.maxSig, sb); + private static boolean unit(MacroProps macros, StringBuilder sb) { + if (macros.unit instanceof Currency) { + sb.append("currency/"); + BlueprintHelpers.generateCurrencyOption((Currency) macros.unit, sb); + return true; + } else if (macros.unit instanceof NoUnit) { + if (macros.unit == NoUnit.PERCENT) { + sb.append("percent"); + return true; + } else if (macros.unit == NoUnit.PERMILLE) { + sb.append("permille"); + return true; + } else { + assert macros.unit == NoUnit.BASE; + // Default value is not shown in normalized form + return false; + } } else { - generateDigitsStem(impl.minSig, -1, sb); - } - } else if (macros.rounder instanceof Rounder.IncrementRounderImpl) { - Rounder.IncrementRounderImpl impl = (Rounder.IncrementRounderImpl) macros.rounder; - sb.append("round-increment/"); - generateIncrementOption(impl.increment, sb); - } else if (macros.rounder instanceof Rounder.InfiniteRounderImpl) { - sb.append("round-unlimited"); - } else { - assert macros.rounder instanceof Rounder.CurrencyRounderImpl; - Rounder.CurrencyRounderImpl impl = (Rounder.CurrencyRounderImpl) macros.rounder; - if (impl.usage == CurrencyUsage.STANDARD) { - sb.append("round-currency-standard"); - } else { - sb.append("round-currency-cash"); + sb.append("measure-unit/"); + BlueprintHelpers.generateMeasureUnitOption(macros.unit, sb); + return true; } } - // Generate the options - if (macros.rounder.mathContext != Rounder.DEFAULT_MATH_CONTEXT) { - sb.append('/'); - generateRoundingModeOption(macros.rounder.mathContext.getRoundingMode(), sb); + private static boolean perUnit(MacroProps macros, StringBuilder sb) { + // Per-units are currently expected to be only MeasureUnits. + if (macros.unit instanceof Currency || macros.unit instanceof NoUnit) { + throw new UnsupportedOperationException( + "Cannot generate number skeleton with per-unit that is not a standard measure unit"); + } else { + sb.append("per-measure-unit/"); + BlueprintHelpers.generateMeasureUnitOption(macros.perUnit, sb); + return true; + } } - // NOTE: Always return true for rounding because the default value depends on other options. - return true; - } + private static boolean rounding(MacroProps macros, StringBuilder sb) { + if (macros.rounder instanceof Rounder.InfiniteRounderImpl) { + sb.append("round-unlimited"); + } else if (macros.rounder instanceof Rounder.FractionRounderImpl) { + Rounder.FractionRounderImpl impl = (Rounder.FractionRounderImpl) macros.rounder; + BlueprintHelpers.generateFractionStem(impl.minFrac, impl.maxFrac, sb); + } else if (macros.rounder instanceof Rounder.SignificantRounderImpl) { + Rounder.SignificantRounderImpl impl = (Rounder.SignificantRounderImpl) macros.rounder; + BlueprintHelpers.generateDigitsStem(impl.minSig, impl.maxSig, sb); + } else if (macros.rounder instanceof Rounder.FracSigRounderImpl) { + Rounder.FracSigRounderImpl impl = (Rounder.FracSigRounderImpl) macros.rounder; + BlueprintHelpers.generateFractionStem(impl.minFrac, impl.maxFrac, sb); + sb.append('/'); + if (impl.minSig == -1) { + BlueprintHelpers.generateDigitsStem(1, impl.maxSig, sb); + } else { + BlueprintHelpers.generateDigitsStem(impl.minSig, -1, sb); + } + } else if (macros.rounder instanceof Rounder.IncrementRounderImpl) { + Rounder.IncrementRounderImpl impl = (Rounder.IncrementRounderImpl) macros.rounder; + sb.append("round-increment/"); + BlueprintHelpers.generateIncrementOption(impl.increment, sb); + } else if (macros.rounder instanceof Rounder.InfiniteRounderImpl) { + sb.append("round-unlimited"); + } else { + assert macros.rounder instanceof Rounder.CurrencyRounderImpl; + Rounder.CurrencyRounderImpl impl = (Rounder.CurrencyRounderImpl) macros.rounder; + if (impl.usage == CurrencyUsage.STANDARD) { + sb.append("round-currency-standard"); + } else { + sb.append("round-currency-cash"); + } + } - private static boolean generateGroupingValue(MacroProps macros, StringBuilder sb) { - if (macros.grouping instanceof GroupingStrategy) { - if (macros.grouping == GroupingStrategy.AUTO) { + // Generate the options + if (macros.rounder.mathContext != Rounder.DEFAULT_MATH_CONTEXT) { + sb.append('/'); + BlueprintHelpers.generateRoundingModeOption(macros.rounder.mathContext.getRoundingMode(), + sb); + } + + // NOTE: Always return true for rounding because the default value depends on other options. + return true; + } + + private static boolean grouping(MacroProps macros, StringBuilder sb) { + if (macros.grouping instanceof GroupingStrategy) { + if (macros.grouping == GroupingStrategy.AUTO) { + return false; // Default value + } + EnumToStemString.groupingStrategy((GroupingStrategy) macros.grouping, sb); + return true; + } else { + throw new UnsupportedOperationException( + "Cannot generate number skeleton with custom Grouper"); + } + } + + private static boolean integerWidth(MacroProps macros, StringBuilder sb) { + if (macros.integerWidth.equals(IntegerWidth.DEFAULT)) { + return false; // Default + } + sb.append("integer-width/"); + BlueprintHelpers.generateIntegerWidthOption(macros.integerWidth.minInt, + macros.integerWidth.maxInt, + sb); + return true; + } + + private static boolean symbols(MacroProps macros, StringBuilder sb) { + if (macros.symbols instanceof NumberingSystem) { + NumberingSystem ns = (NumberingSystem) macros.symbols; + if (ns.getName().equals("latn")) { + sb.append("latin"); + } else { + sb.append("numbering-system/"); + BlueprintHelpers.generateNumberingSystemOption(ns, sb); + } + return true; + } else { + assert macros.symbols instanceof DecimalFormatSymbols; + throw new UnsupportedOperationException( + "Cannot generate number skeleton with custom DecimalFormatSymbols"); + } + } + + private static boolean unitWidth(MacroProps macros, StringBuilder sb) { + if (macros.unitWidth == UnitWidth.SHORT) { return false; // Default value } - groupingStrategyToStemString((GroupingStrategy) macros.grouping, sb); + EnumToStemString.unitWidth(macros.unitWidth, sb); return true; - } else { - throw new UnsupportedOperationException( - "Cannot generate number skeleton with custom Grouper"); } - } - private static boolean generateIntegerWidthValue(MacroProps macros, StringBuilder sb) { - if (macros.integerWidth.equals(IntegerWidth.DEFAULT)) { - return false; // Default - } - sb.append("integer-width/"); - generateIntegerWidthOption(macros.integerWidth.minInt, macros.integerWidth.maxInt, sb); - return true; - } - - private static boolean generateSymbolsValue(MacroProps macros, StringBuilder sb) { - if (macros.symbols instanceof NumberingSystem) { - NumberingSystem ns = (NumberingSystem) macros.symbols; - if (ns.getName().equals("latn")) { - sb.append("latin"); - } else { - sb.append("numbering-system/"); - generateNumberingSystemOption(ns, sb); + private static boolean sign(MacroProps macros, StringBuilder sb) { + if (macros.sign == SignDisplay.AUTO) { + return false; // Default value } + EnumToStemString.signDisplay(macros.sign, sb); return true; - } else { - assert macros.symbols instanceof DecimalFormatSymbols; - throw new UnsupportedOperationException( - "Cannot generate number skeleton with custom DecimalFormatSymbols"); } + + private static boolean decimal(MacroProps macros, StringBuilder sb) { + if (macros.decimal == DecimalSeparatorDisplay.AUTO) { + return false; // Default value + } + EnumToStemString.decimalSeparatorDisplay(macros.decimal, sb); + return true; + } + } - private static boolean generateUnitWidthValue(MacroProps macros, StringBuilder sb) { - if (macros.unitWidth == UnitWidth.SHORT) { - return false; // Default value - } - unitWidthToStemString(macros.unitWidth, sb); - return true; - } - - private static boolean generateSignValue(MacroProps macros, StringBuilder sb) { - if (macros.sign == SignDisplay.AUTO) { - return false; // Default value - } - signDisplayToStemString(macros.sign, sb); - return true; - } - - private static boolean generateDecimalValue(MacroProps macros, StringBuilder sb) { - if (macros.decimal == DecimalSeparatorDisplay.AUTO) { - return false; // Default value - } - decimalSeparatorDisplayToStemString(macros.decimal, sb); - return true; - } - - ///// + ///// OTHER UTILITY FUNCTIONS ///// private static void checkNull(Object value, CharSequence content) { if (value != null) { 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 8423b95aba..4d5509f4f1 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 @@ -3,7 +3,6 @@ package com.ibm.icu.dev.test.number; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -129,13 +128,12 @@ public class NumberSkeletonTest { "scientific/ee", "round-increment/xxx", "round-increment/0.1.2", - "group-thousands/foo", "currency/dummy", "measure-unit/foo", "integer-width/xxx", "integer-width/0+", "integer-width/+0#", - "scientific/foo"}; + "scientific/foo" }; for (String cas : cases) { try { @@ -161,6 +159,25 @@ public class NumberSkeletonTest { } } + @Test + public void unexpectedTokens() { + String[] cases = { + "group-thousands/foo", + "round-integer//ceiling group-off", + "round-integer//ceiling group-off", + "round-integer/ group-off", + "round-integer// group-off" }; + + for (String cas : cases) { + try { + NumberFormatter.fromSkeleton(cas); + fail(); + } catch (SkeletonSyntaxException expected) { + assertTrue(expected.getMessage(), expected.getMessage().contains("Unexpected")); + } + } + } + @Test public void stemsRequiringOption() { String[] stems = { "round-increment", "currency", "measure-unit", "integer-width", }; @@ -202,24 +219,14 @@ public class NumberSkeletonTest { { "round-integer group-off", "5142" }, { "round-integer group-off", "5142" }, { "round-integer/ceiling group-off", "5143" }, - { "round-integer//ceiling group-off", null }, - { "round-integer/ceiling group-off", "5143" }, - { "round-integer//ceiling group-off", null }, - { "round-integer/ group-off", null }, - { "round-integer// group-off", null } }; + { "round-integer/ceiling group-off", "5143" }, }; for (String[] cas : cases) { String skeleton = cas[0]; String expected = cas[1]; - - try { - String actual = NumberFormatter.fromSkeleton(skeleton).locale(ULocale.ENGLISH) - .format(5142.3).toString(); - assertEquals(skeleton, expected, actual); - } catch (SkeletonSyntaxException e) { - // Expected failure? - assertNull(skeleton, expected); - } + String actual = NumberFormatter.fromSkeleton(skeleton).locale(ULocale.ENGLISH).format(5142.3) + .toString(); + assertEquals(skeleton, expected, actual); } } From d8f2d8ce6e3a3c7ad7b48083ce145a107e389689 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Fri, 23 Mar 2018 06:46:19 +0000 Subject: [PATCH 056/129] ICU-8610 Dirty commit of C++ work so far. Probably does not build. X-SVN-Rev: 41142 --- icu4c/source/i18n/number_skeletons.cpp | 563 ++++++++++++++++++ icu4c/source/i18n/number_skeletons.h | 299 ++++++++++ icu4c/source/i18n/numparse_stringsegment.cpp | 4 +- icu4c/source/i18n/numparse_types.h | 4 +- icu4c/source/i18n/ucln_in.h | 1 + icu4c/source/i18n/unicode/numberformatter.h | 32 +- .../ibm/icu/number/NumberSkeletonImpl.java | 138 +++-- 7 files changed, 966 insertions(+), 75 deletions(-) create mode 100644 icu4c/source/i18n/number_skeletons.cpp create mode 100644 icu4c/source/i18n/number_skeletons.h diff --git a/icu4c/source/i18n/number_skeletons.cpp b/icu4c/source/i18n/number_skeletons.cpp new file mode 100644 index 0000000000..1431459a4a --- /dev/null +++ b/icu4c/source/i18n/number_skeletons.cpp @@ -0,0 +1,563 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT + +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + +#include "number_skeletons.h" +#include "umutex.h" +#include "ucln_in.h" +#include "hash.h" +#include "patternprops.h" +#include "unicode/ucharstriebuilder.h" + +using namespace icu; +using namespace icu::number; +using namespace icu::number::impl; +using namespace icu::number::impl::skeleton; + +static constexpr UErrorCode U_NUMBER_SKELETON_SYNTAX_ERROR = U_ILLEGAL_ARGUMENT_ERROR; + +namespace { + +icu::UInitOnce gNumberSkeletonsInitOnce = U_INITONCE_INITIALIZER; + +char16_t* kSerializedStemTrie = nullptr; + +UBool U_CALLCONV cleanupNumberSkeletons() { + uprv_free(kSerializedStemTrie); + kSerializedStemTrie = nullptr; +} + +void U_CALLCONV initNumberSkeletons(UErrorCode& status) { + ucln_i18n_registerCleanup(UCLN_I18N_NUMBER_SKELETONS, cleanupNumberSkeletons); + + UCharsTrieBuilder b(status); + if (U_FAILURE(status)) { return; } + + // Section 1: + b.add(u"compact-short", STEM_COMPACT_SHORT, status); + b.add(u"compact-long", STEM_COMPACT_LONG, status); + b.add(u"scientific", STEM_SCIENTIFIC, status); + b.add(u"engineering", STEM_ENGINEERING, status); + b.add(u"notation-simple", STEM_NOTATION_SIMPLE, status); + b.add(u"base-unit", STEM_BASE_UNIT, status); + b.add(u"percent", STEM_PERCENT, status); + b.add(u"permille", STEM_PERMILLE, status); + b.add(u"round-integer", STEM_ROUND_INTEGER, status); + b.add(u"round-unlimited", STEM_ROUND_UNLIMITED, status); + b.add(u"round-currency-standard", STEM_ROUND_CURRENCY_STANDARD, status); + b.add(u"round-currency-cash", STEM_ROUND_CURRENCY_CASH, status); + b.add(u"group-off", STEM_GROUP_OFF, status); + b.add(u"group-min2", STEM_GROUP_MIN2, status); + b.add(u"group-auto", STEM_GROUP_AUTO, status); + b.add(u"group-on-aligned", STEM_GROUP_ON_ALIGNED, status); + b.add(u"group-thousands", STEM_GROUP_THOUSANDS, status); + b.add(u"latin", STEM_LATIN, status); + b.add(u"unit-width-narrow", STEM_UNIT_WIDTH_NARROW, status); + b.add(u"unit-width-short", STEM_UNIT_WIDTH_SHORT, status); + b.add(u"unit-width-full-name", STEM_UNIT_WIDTH_FULL_NAME, status); + b.add(u"unit-width-iso-code", STEM_UNIT_WIDTH_ISO_CODE, status); + b.add(u"unit-width-hidden", STEM_UNIT_WIDTH_HIDDEN, status); + b.add(u"sign-auto", STEM_SIGN_AUTO, status); + b.add(u"sign-always", STEM_SIGN_ALWAYS, status); + b.add(u"sign-never", STEM_SIGN_NEVER, status); + b.add(u"sign-accounting", STEM_SIGN_ACCOUNTING, 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"decimal-auto", STEM_DECIMAL_AUTO, status); + b.add(u"decimal-always", STEM_DECIMAL_ALWAYS, status); + if (U_FAILURE(status)) { return; } + + // Section 2: + b.add(u"round-increment", STEM_ROUND_INCREMENT, status); + b.add(u"measure-unit", STEM_MEASURE_UNIT, status); + b.add(u"per-measure-unit", STEM_PER_MEASURE_UNIT, status); + b.add(u"currency", STEM_CURRENCY, status); + b.add(u"integer-width", STEM_INTEGER_WIDTH, status); + b.add(u"numbering-system", STEM_NUMBERING_SYSTEM, status); + if (U_FAILURE(status)) { return; } + + // Build the CharsTrie + // TODO: Use SLOW or FAST here? + UnicodeString result; + b.buildUnicodeString(USTRINGTRIE_BUILD_FAST, result, status); + if (U_FAILURE(status)) { return; } + + // Copy the result into the global constant pointer + size_t numBytes = result.length() * sizeof(char16_t); + kSerializedStemTrie = static_cast(uprv_malloc(numBytes)); + uprv_memcpy(kSerializedStemTrie, result.getBuffer(), numBytes); +} + + +#define CHECK_NULL(seen, field, status) void; /* for auto-format line wraping */ \ +{ \ + if ((seen).field) { \ + (status) = U_NUMBER_SKELETON_SYNTAX_ERROR; \ + return STATE_NULL; \ + } \ + (seen).field = true; \ +} + + +} // anonymous namespace + + +Notation stem_to_object::notation(skeleton::StemEnum stem) { + switch (stem) { + case STEM_COMPACT_SHORT: + return Notation::compactShort(); + case STEM_COMPACT_LONG: + return Notation::compactLong(); + case STEM_SCIENTIFIC: + return Notation::scientific(); + case STEM_ENGINEERING: + return Notation::engineering(); + case STEM_NOTATION_SIMPLE: + return Notation::simple(); + default: + U_ASSERT(false); + } +} + +MeasureUnit stem_to_object::unit(skeleton::StemEnum stem) { + switch (stem) { + case STEM_BASE_UNIT: + // Slicing is okay + return NoUnit::base(); // NOLINT + case STEM_PERCENT: + // Slicing is okay + return NoUnit::percent(); // NOLINT + case STEM_PERMILLE: + // Slicing is okay + return NoUnit::permille(); // NOLINT + default: + U_ASSERT(false); + } +} + +Rounder stem_to_object::rounder(skeleton::StemEnum stem) { + switch (stem) { + case STEM_ROUND_INTEGER: + return Rounder::integer(); + case STEM_ROUND_UNLIMITED: + return Rounder::unlimited(); + case STEM_ROUND_CURRENCY_STANDARD: + return Rounder::currency(UCURR_USAGE_STANDARD); + case STEM_ROUND_CURRENCY_CASH: + return Rounder::currency(UCURR_USAGE_CASH); + default: + U_ASSERT(false); + } +} + +UGroupingStrategy stem_to_object::groupingStrategy(skeleton::StemEnum stem) { + switch (stem) { + case STEM_GROUP_OFF: + return UNUM_GROUPING_OFF; + case STEM_GROUP_MIN2: + return UNUM_GROUPING_MIN2; + case STEM_GROUP_AUTO: + return UNUM_GROUPING_AUTO; + case STEM_GROUP_ON_ALIGNED: + return UNUM_GROUPING_ON_ALIGNED; + case STEM_GROUP_THOUSANDS: + return UNUM_GROUPING_THOUSANDS; + default: + return UNUM_GROUPING_COUNT; // for objects, throw; for enums, return COUNT + } +} + +UNumberUnitWidth stem_to_object::unitWidth(skeleton::StemEnum stem) { + switch (stem) { + case STEM_UNIT_WIDTH_NARROW: + return UNUM_UNIT_WIDTH_NARROW; + case STEM_UNIT_WIDTH_SHORT: + return UNUM_UNIT_WIDTH_SHORT; + case STEM_UNIT_WIDTH_FULL_NAME: + return UNUM_UNIT_WIDTH_FULL_NAME; + case STEM_UNIT_WIDTH_ISO_CODE: + return UNUM_UNIT_WIDTH_ISO_CODE; + case STEM_UNIT_WIDTH_HIDDEN: + return UNUM_UNIT_WIDTH_HIDDEN; + default: + return UNUM_UNIT_WIDTH_COUNT; // for objects, throw; for enums, return COUNT + } +} + +UNumberSignDisplay stem_to_object::signDisplay(skeleton::StemEnum stem) { + switch (stem) { + case STEM_SIGN_AUTO: + return UNUM_SIGN_AUTO; + case STEM_SIGN_ALWAYS: + return UNUM_SIGN_ALWAYS; + case STEM_SIGN_NEVER: + return UNUM_SIGN_NEVER; + case STEM_SIGN_ACCOUNTING: + return UNUM_SIGN_ACCOUNTING; + case STEM_SIGN_ACCOUNTING_ALWAYS: + return UNUM_SIGN_ACCOUNTING_ALWAYS; + case STEM_SIGN_EXCEPT_ZERO: + return UNUM_SIGN_EXCEPT_ZERO; + case STEM_SIGN_ACCOUNTING_EXCEPT_ZERO: + return UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO; + default: + return UNUM_SIGN_COUNT; // for objects, throw; for enums, return COUNT + } +} + +UNumberDecimalSeparatorDisplay stem_to_object::decimalSeparatorDisplay(skeleton::StemEnum stem) { + switch (stem) { + case STEM_DECIMAL_AUTO: + return UNUM_DECIMAL_SEPARATOR_AUTO; + case STEM_DECIMAL_ALWAYS: + return UNUM_DECIMAL_SEPARATOR_ALWAYS; + default: + return UNUM_DECIMAL_SEPARATOR_COUNT; // for objects, throw; for enums, return COUNT + } +} + + +void enum_to_stem_string::groupingStrategy(UGroupingStrategy value, UnicodeString& sb) { + switch (value) { + case UNUM_GROUPING_OFF: + sb.append(u"group-off", -1); + break; + case UNUM_GROUPING_MIN2: + sb.append(u"group-min2", -1); + break; + case UNUM_GROUPING_AUTO: + sb.append(u"group-auto", -1); + break; + case UNUM_GROUPING_ON_ALIGNED: + sb.append(u"group-on-aligned", -1); + break; + case UNUM_GROUPING_THOUSANDS: + sb.append(u"group-thousands", -1); + break; + default: + U_ASSERT(false); + } +} + +void enum_to_stem_string::unitWidth(UNumberUnitWidth value, UnicodeString& sb) { + switch (value) { + case UNUM_UNIT_WIDTH_NARROW: + sb.append(u"unit-width-narrow", -1); + break; + case UNUM_UNIT_WIDTH_SHORT: + sb.append(u"unit-width-short", -1); + break; + case UNUM_UNIT_WIDTH_FULL_NAME: + sb.append(u"unit-width-full-name", -1); + break; + case UNUM_UNIT_WIDTH_ISO_CODE: + sb.append(u"unit-width-iso-code", -1); + break; + case UNUM_UNIT_WIDTH_HIDDEN: + sb.append(u"unit-width-hidden", -1); + break; + default: + U_ASSERT(false); + } +} + +void enum_to_stem_string::signDisplay(UNumberSignDisplay value, UnicodeString& sb) { + switch (value) { + case UNUM_SIGN_AUTO: + sb.append(u"sign-auto", -1); + break; + case UNUM_SIGN_ALWAYS: + sb.append(u"sign-always", -1); + break; + case UNUM_SIGN_NEVER: + sb.append(u"sign-never", -1); + break; + case UNUM_SIGN_ACCOUNTING: + sb.append(u"sign-accounting", -1); + break; + case UNUM_SIGN_ACCOUNTING_ALWAYS: + sb.append(u"sign-accounting-always", -1); + break; + case UNUM_SIGN_EXCEPT_ZERO: + sb.append(u"sign-except-zero", -1); + break; + case UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO: + sb.append(u"sign-accounting-except-zero", -1); + break; + default: + U_ASSERT(false); + } +} + +void +enum_to_stem_string::decimalSeparatorDisplay(UNumberDecimalSeparatorDisplay value, UnicodeString& sb) { + switch (value) { + case UNUM_DECIMAL_SEPARATOR_AUTO: + sb.append(u"decimal-auto", -1); + break; + case UNUM_DECIMAL_SEPARATOR_ALWAYS: + sb.append(u"decimal-always", -1); + break; + default: + U_ASSERT(false); + } +} + + +UnlocalizedNumberFormatter skeleton::create(const UnicodeString& skeletonString, UErrorCode& status) { + if (U_FAILURE(status)) { return {}; } + umtx_initOnce(gNumberSkeletonsInitOnce, &initNumberSkeletons, status); + if (U_FAILURE(status)) { return {}; } + + MacroProps macros = parseSkeleton(skeletonString, status); + return NumberFormatter::with().macros(macros); +} + +UnicodeString skeleton::generate(const MacroProps& macros, UErrorCode& status) { + if (U_FAILURE(status)) { return {}; } + umtx_initOnce(gNumberSkeletonsInitOnce, &initNumberSkeletons, status); + if (U_FAILURE(status)) { return {}; } + + UnicodeString sb; + generateSkeleton(macros, sb, status); + return sb; +} + +MacroProps skeleton::parseSkeleton(const UnicodeString& skeletonString, UErrorCode& status) { + // Add a trailing whitespace to the end of the skeleton string to make code cleaner. + UnicodeString tempSkeletonString(skeletonString); + tempSkeletonString.append(u' '); + + SeenMacroProps seen; + MacroProps macros; + StringSegment segment(skeletonString, false); + UCharsTrie stemTrie(kSerializedStemTrie); + ParseState stem = STATE_NULL; + int offset = 0; + + // Primary skeleton parse loop: + while (offset < segment.length()) { + int cp = segment.codePointAt(offset); + bool isTokenSeparator = PatternProps::isWhiteSpace(cp); + bool isOptionSeparator = (cp == u'/'); + + if (!isTokenSeparator && !isOptionSeparator) { + // Non-separator token; consume it. + offset += U16_LENGTH(cp); + if (stem == STATE_NULL) { + // We are currently consuming a stem. + // Go to the next state in the stem trie. + stemTrie.nextForCodePoint(cp); + } + continue; + } + + // We are looking at a token or option separator. + // If the segment is nonempty, parse it and reset the segment. + // Otherwise, make sure it is a valid repeating separator. + if (offset != 0) { + segment.setLength(offset); + if (stem == STATE_NULL) { + // The first separator after the start of a token. Parse it as a stem. + stem = parseStem(segment, stemTrie, seen, macros, status); + stemTrie.reset(); + } else { + // A separator after the first separator of a token. Parse it as an option. + stem = parseOption(stem, segment, macros, status); + } + segment.resetLength(); + + // Consume the segment: + segment.adjustOffset(offset); + offset = 0; + + } else if (stem != STATE_NULL) { + // A separator ('/' or whitespace) following an option separator ('/') + // segment.setLength(U16_LENGTH(cp)); // for error message + // throw new SkeletonSyntaxException("Unexpected separator character", segment); + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return macros; + + } else { + // Two spaces in a row; this is OK. + } + + // Does the current stem forbid options? + if (isOptionSeparator && stem == STATE_NULL) { + // segment.setLength(U16_LENGTH(cp)); // for error message + // throw new SkeletonSyntaxException("Unexpected option separator", segment); + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return macros; + } + + // Does the current stem require an option? + if (isTokenSeparator && stem != STATE_NULL) { + switch (stem) { + case STATE_INCREMENT_ROUNDER: + case STATE_MEASURE_UNIT: + case STATE_PER_MEASURE_UNIT: + case STATE_CURRENCY_UNIT: + case STATE_INTEGER_WIDTH: + case STATE_NUMBERING_SYSTEM: + // segment.setLength(U16_LENGTH(cp)); // for error message + // throw new SkeletonSyntaxException("Stem requires an option", segment); + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return macros; + default: + break; + } + stem = STATE_NULL; + } + + // Consume the separator: + segment.adjustOffset(U16_LENGTH(cp)); + } + U_ASSERT(stem == STATE_NULL); + return macros; +} + +ParseState +skeleton::parseStem(const StringSegment& segment, const UCharsTrie& stemTrie, SeenMacroProps& seen, + MacroProps& macros, UErrorCode& status) { + // First check for "blueprint" stems, which start with a "signal char" + switch (segment.charAt(0)) { + case u'.': + CHECK_NULL(seen, rounder, status); + blueprint_helpers::parseFractionStem(segment, macros, status); + return STATE_FRACTION_ROUNDER; + case u'@': + CHECK_NULL(seen, rounder, status); + blueprint_helpers::parseDigitsStem(segment, macros, status); + return STATE_NULL; + } + + // Now look at the stemsTrie, which is already be pointing at our stem. + UStringTrieResult stemResult = stemTrie.current(); + + if (stemResult != USTRINGTRIE_INTERMEDIATE_VALUE && stemResult != USTRINGTRIE_FINAL_VALUE) { + // throw new SkeletonSyntaxException("Unknown stem", segment); + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return STATE_NULL; + } + + auto stem = static_cast(stemTrie.getValue()); + switch (stem) { + + // Stems with meaning on their own, not requiring an option: + + case STEM_COMPACT_SHORT: + case STEM_COMPACT_LONG: + case STEM_SCIENTIFIC: + case STEM_ENGINEERING: + case STEM_NOTATION_SIMPLE: + CHECK_NULL(seen, notation, status); + macros.notation = stem_to_object::notation(stem); + switch (stem) { + case STEM_SCIENTIFIC: + case STEM_ENGINEERING: + return STATE_SCIENTIFIC; // allows for scientific options + default: + return STATE_NULL; + } + + case STEM_BASE_UNIT: + case STEM_PERCENT: + case STEM_PERMILLE: + CHECK_NULL(seen, unit, status); + macros.unit = stem_to_object::unit(stem); + return STATE_NULL; + + case STEM_ROUND_INTEGER: + case STEM_ROUND_UNLIMITED: + case STEM_ROUND_CURRENCY_STANDARD: + case STEM_ROUND_CURRENCY_CASH: + CHECK_NULL(seen, rounder, status); + macros.rounder = stem_to_object::rounder(stem); + switch (stem) { + case STEM_ROUND_INTEGER: + return STATE_FRACTION_ROUNDER; // allows for "round-integer/@##" + default: + return STATE_ROUNDER; // allows for rounding mode options + } + + case STEM_GROUP_OFF: + case STEM_GROUP_MIN2: + case STEM_GROUP_AUTO: + case STEM_GROUP_ON_ALIGNED: + case STEM_GROUP_THOUSANDS: + CHECK_NULL(seen, grouper, status); + macros.grouper = Grouper::forStrategy(stem_to_object::groupingStrategy(stem)); + return STATE_NULL; + + case STEM_LATIN: + CHECK_NULL(seen, symbols, status); + macros.symbols.setTo(NumberingSystem::createInstanceByName("latn", status)); + return STATE_NULL; + + case STEM_UNIT_WIDTH_NARROW: + case STEM_UNIT_WIDTH_SHORT: + case STEM_UNIT_WIDTH_FULL_NAME: + case STEM_UNIT_WIDTH_ISO_CODE: + case STEM_UNIT_WIDTH_HIDDEN: + CHECK_NULL(seen, unitWidth, status); + macros.unitWidth = stem_to_object::unitWidth(stem); + return STATE_NULL; + + case STEM_SIGN_AUTO: + case STEM_SIGN_ALWAYS: + case STEM_SIGN_NEVER: + case STEM_SIGN_ACCOUNTING: + case STEM_SIGN_ACCOUNTING_ALWAYS: + case STEM_SIGN_EXCEPT_ZERO: + case STEM_SIGN_ACCOUNTING_EXCEPT_ZERO: + CHECK_NULL(seen, sign, status); + macros.sign = stem_to_object::signDisplay(stem); + return STATE_NULL; + + case STEM_DECIMAL_AUTO: + case STEM_DECIMAL_ALWAYS: + CHECK_NULL(seen, decimal, status); + macros.decimal = stem_to_object::decimalSeparatorDisplay(stem); + return STATE_NULL; + + // Stems requiring an option: + + case STEM_ROUND_INCREMENT: + CHECK_NULL(seen, rounder, status); + return STATE_INCREMENT_ROUNDER; + + case STEM_MEASURE_UNIT: + CHECK_NULL(seen, unit, status); + return STATE_MEASURE_UNIT; + + case STEM_PER_MEASURE_UNIT: + CHECK_NULL(seen, perUnit, status); + return STATE_PER_MEASURE_UNIT; + + case STEM_CURRENCY: + CHECK_NULL(seen, unit, status); + return STATE_CURRENCY_UNIT; + + case STEM_INTEGER_WIDTH: + CHECK_NULL(seen, integerWidth, status); + return STATE_INTEGER_WIDTH; + + case STEM_NUMBERING_SYSTEM: + CHECK_NULL(seen, symbols, status); + return STATE_NUMBERING_SYSTEM; + + default: + U_ASSERT(false); + } +} + + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/number_skeletons.h b/icu4c/source/i18n/number_skeletons.h new file mode 100644 index 0000000000..74f2fd193f --- /dev/null +++ b/icu4c/source/i18n/number_skeletons.h @@ -0,0 +1,299 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#ifndef __SOURCE_NUMBER_SKELETONS_H__ +#define __SOURCE_NUMBER_SKELETONS_H__ + +#include "number_types.h" +#include "numparse_types.h" +#include "unicode/ucharstrie.h" + +using icu::numparse::impl::StringSegment; + +U_NAMESPACE_BEGIN namespace number { +namespace impl { + +// Forward-declaration +struct SeenMacroProps; + +// namespace for enums and entrypoint functions +namespace skeleton { + +/** + * While parsing a skeleton, this enum records what type of option we expect to find next. + */ +enum ParseState { + + // Section 0: We expect whitespace or a stem, but not an option: + + STATE_NULL, + + // Section 1: We might accept an option, but it is not required: + + STATE_SCIENTIFIC, + STATE_ROUNDER, + STATE_FRACTION_ROUNDER, + + // Section 2: An option is required: + + STATE_INCREMENT_ROUNDER, + STATE_MEASURE_UNIT, + STATE_PER_MEASURE_UNIT, + STATE_CURRENCY_UNIT, + STATE_INTEGER_WIDTH, + STATE_NUMBERING_SYSTEM, +}; + +/** + * All possible stem literals have an entry in the StemEnum. The enum name is the kebab case stem + * string literal written in upper snake case. + * + * @see StemToObject + * @see #SERIALIZED_STEM_TRIE + */ +enum StemEnum { + + // Section 1: Stems that do not require an option: + + STEM_COMPACT_SHORT, + STEM_COMPACT_LONG, + STEM_SCIENTIFIC, + STEM_ENGINEERING, + STEM_NOTATION_SIMPLE, + STEM_BASE_UNIT, + STEM_PERCENT, + STEM_PERMILLE, + STEM_ROUND_INTEGER, + STEM_ROUND_UNLIMITED, + STEM_ROUND_CURRENCY_STANDARD, + STEM_ROUND_CURRENCY_CASH, + STEM_GROUP_OFF, + STEM_GROUP_MIN2, + STEM_GROUP_AUTO, + STEM_GROUP_ON_ALIGNED, + STEM_GROUP_THOUSANDS, + STEM_LATIN, + STEM_UNIT_WIDTH_NARROW, + STEM_UNIT_WIDTH_SHORT, + STEM_UNIT_WIDTH_FULL_NAME, + STEM_UNIT_WIDTH_ISO_CODE, + STEM_UNIT_WIDTH_HIDDEN, + STEM_SIGN_AUTO, + STEM_SIGN_ALWAYS, + STEM_SIGN_NEVER, + STEM_SIGN_ACCOUNTING, + STEM_SIGN_ACCOUNTING_ALWAYS, + STEM_SIGN_EXCEPT_ZERO, + STEM_SIGN_ACCOUNTING_EXCEPT_ZERO, + STEM_DECIMAL_AUTO, + STEM_DECIMAL_ALWAYS, + + // Section 2: Stems that DO require an option: + + STEM_ROUND_INCREMENT, + STEM_MEASURE_UNIT, + STEM_PER_MEASURE_UNIT, + STEM_CURRENCY, + STEM_INTEGER_WIDTH, + STEM_NUMBERING_SYSTEM, +}; + +/** + * Creates a NumberFormatter corresponding to the given skeleton string. + * + * @param skeletonString + * A number skeleton string, possibly not in its shortest form. + * @return An UnlocalizedNumberFormatter with behavior defined by the given skeleton string. + */ +UnlocalizedNumberFormatter create(const UnicodeString& skeletonString, UErrorCode& status); + +/** + * Create a skeleton string corresponding to the given NumberFormatter. + * + * @param macros + * The NumberFormatter options object. + * @return A skeleton string in normalized form. + */ +UnicodeString generate(const MacroProps& macros, UErrorCode& status); + +/** + * Converts from a skeleton string to a MacroProps. This method contains the primary parse loop. + * + * Internal: use the create() endpoint instead of this function. + */ +MacroProps parseSkeleton(const UnicodeString& skeletonString, UErrorCode& status); + +/** + * Given that the current segment represents an stem, parse it and save the result. + * + * @return The next state after parsing this stem, corresponding to what subset of options to expect. + */ +ParseState parseStem(const StringSegment& segment, const UCharsTrie& stemTrie, SeenMacroProps& seen, + MacroProps& macros, UErrorCode& status); + +/** + * Given that the current segment represents an option, parse it and save the result. + * + * @return The next state after parsing this option, corresponding to what subset of options to + * expect next. + */ +ParseState +parseOption(ParseState stem, const StringSegment& segment, MacroProps& macros, UErrorCode& status); + +/** + * Main skeleton generator function. Appends the normalized skeleton for the MacroProps to the given + * StringBuilder. + * + * Internal: use the create() endpoint instead of this function. + */ +void generateSkeleton(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + +} // namespace skeleton + + +/** + * Namespace for utility methods that convert from StemEnum to corresponding objects or enums. This + * applies to only the "Section 1" stems, those that are well-defined without an option. + */ +namespace stem_to_object { + +Notation notation(skeleton::StemEnum stem); + +MeasureUnit unit(skeleton::StemEnum stem); + +Rounder rounder(skeleton::StemEnum stem); + +UGroupingStrategy groupingStrategy(skeleton::StemEnum stem); + +UNumberUnitWidth unitWidth(skeleton::StemEnum stem); + +UNumberSignDisplay signDisplay(skeleton::StemEnum stem); + +UNumberDecimalSeparatorDisplay decimalSeparatorDisplay(skeleton::StemEnum stem); + +} // namespace stem_to_object + +/** + * Namespace for utility methods that convert from enums to stem strings. More complex object conversions + * take place in the object_to_stem_string namespace. + */ +namespace enum_to_stem_string { + +void groupingStrategy(UGroupingStrategy value, UnicodeString& sb); + +void unitWidth(UNumberUnitWidth value, UnicodeString& sb); + +void signDisplay(UNumberSignDisplay value, UnicodeString& sb); + +void decimalSeparatorDisplay(UNumberDecimalSeparatorDisplay value, UnicodeString& sb); + +} // namespace enum_to_stem_string + +/** + * Namespace for utility methods for processing stems and options that cannot be interpreted literally. + */ +namespace blueprint_helpers { + +/** @return Whether we successfully found and parsed an exponent width option. */ +bool parseExponentWidthOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status); + +void generateExponentWidthOption(int32_t xyz, UnicodeString& sb, UErrorCode& status); + +/** @return Whether we successfully found and parsed an exponent sign option. */ +bool parseExponentSignOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status); + +void parseCurrencyOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status); + +void generateCurrencyOption(const CurrencyUnit& currency, UnicodeString& sb, UErrorCode& status); + +void parseMeasureUnitOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status); + +void generateMeasureUnitOption(const MeasureUnit& measureUnit, UnicodeString& sb, UErrorCode& status); + +void parseMeasurePerUnitOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status); + +void parseFractionStem(const StringSegment& segment, MacroProps& macros, UErrorCode& status); + +void generateFractionStem(int32_t minFrac, int32_t maxFrac, UnicodeString& sb, UErrorCode& status); + +void parseDigitsStem(const StringSegment& segment, MacroProps& macros, UErrorCode& status); + +void generateDigitsStem(int32_t minSig, int32_t maxSig, UnicodeString& sb, UErrorCode& status); + +/** @return Whether we successfully found and parsed a frac-sig option. */ +bool parseFracSigOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status); + +void parseIncrementOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status); + +void generateIncrementOption(double increment, UnicodeString& sb, UErrorCode& status); + +/** @return Whether we successfully found and parsed a rounding mode. */ +bool parseRoundingModeOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status); + +void generateRoundingModeOption(RoundingMode, UnicodeString& sb, UErrorCode& status); + +void parseIntegerWidthOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status); + +void generateIntegerWidthOption(int32_t minInt, int32_t maxInt, UnicodeString& sb, UErrorCode& status); + +void parseNumberingSystemOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status); + +void generateNumberingSystemOption(NumberingSystem, UnicodeString& sb, UErrorCode& status); + +} // namespace blueprint_helpers + +/** + * Namespace for utility methods for generating a token corresponding to each macro-prop. Each method + * returns whether or not a token was written to the string builder. + */ +namespace generator_helpers { + +bool notation(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + +bool unit(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + +bool perUnit(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + +bool rounding(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + +bool grouping(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + +bool integerWidth(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + +bool symbols(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + +bool unitWidth(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + +bool sign(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + +bool decimal(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + +} // namespace generator_helpers + +/** + * Struct for null-checking. + * In Java, we can just check the object reference. In C++, we need a different method. + */ +struct SeenMacroProps { + bool notation = false; + bool unit = false; + bool perUnit = false; + bool rounder = false; + bool grouper = false; + bool padder = false; + bool integerWidth = false; + bool symbols = false; + bool unitWidth = false; + bool sign = false; + bool decimal = false; +}; + +} // namespace impl +} // namespace number +U_NAMESPACE_END + +#endif //__SOURCE_NUMBER_SKELETONS_H__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/numparse_stringsegment.cpp b/icu4c/source/i18n/numparse_stringsegment.cpp index a3d6f15102..e3bdc68c25 100644 --- a/icu4c/source/i18n/numparse_stringsegment.cpp +++ b/icu4c/source/i18n/numparse_stringsegment.cpp @@ -20,9 +20,9 @@ using namespace icu::numparse; using namespace icu::numparse::impl; -StringSegment::StringSegment(const UnicodeString& str, parse_flags_t parseFlags) +StringSegment::StringSegment(const UnicodeString& str, bool ignoreCase) : fStr(str), fStart(0), fEnd(str.length()), - fFoldCase(0 != (parseFlags & PARSE_FLAG_IGNORE_CASE)) {} + fFoldCase(ignoreCase) {} int32_t StringSegment::getOffset() const { return fStart; diff --git a/icu4c/source/i18n/numparse_types.h b/icu4c/source/i18n/numparse_types.h index dfaadd9cb9..b02bb249ce 100644 --- a/icu4c/source/i18n/numparse_types.h +++ b/icu4c/source/i18n/numparse_types.h @@ -170,7 +170,7 @@ class ParsedNumber { */ class StringSegment : public UMemory, public ::icu::number::impl::CharSequence { public: - explicit StringSegment(const UnicodeString& str, parse_flags_t parseFlags); + StringSegment(const UnicodeString& str, bool ignoreCase); int32_t getOffset() const; @@ -248,6 +248,8 @@ class StringSegment : public UMemory, public ::icu::number::impl::CharSequence { */ int32_t getCaseSensitivePrefixLength(const UnicodeString& other); + bool operator==(const UnicodeString& other) const; + private: const UnicodeString fStr; int32_t fStart; diff --git a/icu4c/source/i18n/ucln_in.h b/icu4c/source/i18n/ucln_in.h index d9e8741e7f..dc447ca898 100644 --- a/icu4c/source/i18n/ucln_in.h +++ b/icu4c/source/i18n/ucln_in.h @@ -26,6 +26,7 @@ as the functions are suppose to be called. It's usually best to have child dependencies called first. */ typedef enum ECleanupI18NType { UCLN_I18N_START = -1, + UCLN_I18N_NUMBER_SKELETONS, UCLN_I18N_NUMPARSE_UNISETS, UCLN_I18N_CURRENCY_SPACING, UCLN_I18N_SPOOF, diff --git a/icu4c/source/i18n/unicode/numberformatter.h b/icu4c/source/i18n/unicode/numberformatter.h index 4f964fb1d6..87b1c6f196 100644 --- a/icu4c/source/i18n/unicode/numberformatter.h +++ b/icu4c/source/i18n/unicode/numberformatter.h @@ -105,7 +105,7 @@ typedef enum UNumberUnitWidth { * * @draft ICU 60 */ - UNUM_UNIT_WIDTH_NARROW, + UNUM_UNIT_WIDTH_NARROW, /** * Print an abbreviated version of the unit name. Similar to NARROW, but use a slightly wider abbreviation or @@ -121,7 +121,7 @@ typedef enum UNumberUnitWidth { * * @draft ICU 60 */ - UNUM_UNIT_WIDTH_SHORT, + UNUM_UNIT_WIDTH_SHORT, /** * Print the full name of the unit, without any abbreviations. @@ -132,7 +132,7 @@ typedef enum UNumberUnitWidth { * * @draft ICU 60 */ - UNUM_UNIT_WIDTH_FULL_NAME, + UNUM_UNIT_WIDTH_FULL_NAME, /** * Use the three-digit ISO XXX code in place of the symbol for displaying currencies. The behavior of this @@ -143,7 +143,7 @@ typedef enum UNumberUnitWidth { * * @draft ICU 60 */ - UNUM_UNIT_WIDTH_ISO_CODE, + UNUM_UNIT_WIDTH_ISO_CODE, /** * Format the number according to the specified unit, but do not display the unit. For currencies, apply @@ -152,14 +152,14 @@ typedef enum UNumberUnitWidth { * * @draft ICU 60 */ - UNUM_UNIT_WIDTH_HIDDEN, + UNUM_UNIT_WIDTH_HIDDEN, /** * One more than the highest UNumberUnitWidth value. * * @internal ICU 60: The numeric value may change over time; see ICU ticket #12420. */ - UNUM_UNIT_WIDTH_COUNT + UNUM_UNIT_WIDTH_COUNT } UNumberUnitWidth; /** @@ -186,7 +186,8 @@ typedef enum UNumberUnitWidth { * Note: This enum specifies the strategy for grouping sizes. To set which character to use as the * grouping separator, use the "symbols" setter. * - * @draft ICU 61 + * @draft ICU 61 -- TODO: This should be renamed to UNumberGroupingStrategy before promoting to stable, + * for consistency with the other enums. */ typedef enum UGroupingStrategy { /** @@ -249,7 +250,14 @@ typedef enum UGroupingStrategy { * * @draft ICU 61 */ - UNUM_GROUPING_THOUSANDS + UNUM_GROUPING_THOUSANDS, + + /** + * One more than the highest UNumberSignDisplay value. + * + * @internal ICU 62: The numeric value may change over time; see ICU ticket #12420. + */ + UNUM_GROUPING_COUNT } UGroupingStrategy; @@ -363,22 +371,22 @@ typedef enum UNumberDecimalSeparatorDisplay { * * @draft ICU 60 */ - UNUM_DECIMAL_SEPARATOR_AUTO, + UNUM_DECIMAL_SEPARATOR_AUTO, /** * Always show the decimal separator, even if there are no digits to display after the separator. * * @draft ICU 60 */ - UNUM_DECIMAL_SEPARATOR_ALWAYS, + UNUM_DECIMAL_SEPARATOR_ALWAYS, /** * One more than the highest UNumberDecimalSeparatorDisplay value. * * @internal ICU 60: The numeric value may change over time; see ICU ticket #12420. */ - UNUM_DECIMAL_SEPARATOR_COUNT -} UNumberDecimalMarkDisplay; + UNUM_DECIMAL_SEPARATOR_COUNT +} UNumberDecimalSeparatorDisplay; U_NAMESPACE_BEGIN 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 8154f819cb..19ea62f68b 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 @@ -107,6 +107,59 @@ class NumberSkeletonImpl { /** For mapping from ordinal back to StemEnum in Java. */ static final StemEnum[] STEM_ENUM_VALUES = StemEnum.values(); + /** A data structure for mapping from stem strings to the stem enum. Built at startup. */ + static final String SERIALIZED_STEM_TRIE = buildStemTrie(); + + static String buildStemTrie() { + CharsTrieBuilder b = new CharsTrieBuilder(); + + // Section 1: + b.add("compact-short", StemEnum.STEM_COMPACT_SHORT.ordinal()); + b.add("compact-long", StemEnum.STEM_COMPACT_LONG.ordinal()); + b.add("scientific", StemEnum.STEM_SCIENTIFIC.ordinal()); + b.add("engineering", StemEnum.STEM_ENGINEERING.ordinal()); + b.add("notation-simple", StemEnum.STEM_NOTATION_SIMPLE.ordinal()); + b.add("base-unit", StemEnum.STEM_BASE_UNIT.ordinal()); + b.add("percent", StemEnum.STEM_PERCENT.ordinal()); + b.add("permille", StemEnum.STEM_PERMILLE.ordinal()); + b.add("round-integer", StemEnum.STEM_ROUND_INTEGER.ordinal()); + b.add("round-unlimited", StemEnum.STEM_ROUND_UNLIMITED.ordinal()); + b.add("round-currency-standard", StemEnum.STEM_ROUND_CURRENCY_STANDARD.ordinal()); + b.add("round-currency-cash", StemEnum.STEM_ROUND_CURRENCY_CASH.ordinal()); + b.add("group-off", StemEnum.STEM_GROUP_OFF.ordinal()); + b.add("group-min2", StemEnum.STEM_GROUP_MIN2.ordinal()); + b.add("group-auto", StemEnum.STEM_GROUP_AUTO.ordinal()); + b.add("group-on-aligned", StemEnum.STEM_GROUP_ON_ALIGNED.ordinal()); + b.add("group-thousands", StemEnum.STEM_GROUP_THOUSANDS.ordinal()); + b.add("latin", StemEnum.STEM_LATIN.ordinal()); + b.add("unit-width-narrow", StemEnum.STEM_UNIT_WIDTH_NARROW.ordinal()); + b.add("unit-width-short", StemEnum.STEM_UNIT_WIDTH_SHORT.ordinal()); + b.add("unit-width-full-name", StemEnum.STEM_UNIT_WIDTH_FULL_NAME.ordinal()); + b.add("unit-width-iso-code", StemEnum.STEM_UNIT_WIDTH_ISO_CODE.ordinal()); + b.add("unit-width-hidden", StemEnum.STEM_UNIT_WIDTH_HIDDEN.ordinal()); + b.add("sign-auto", StemEnum.STEM_SIGN_AUTO.ordinal()); + b.add("sign-always", StemEnum.STEM_SIGN_ALWAYS.ordinal()); + b.add("sign-never", StemEnum.STEM_SIGN_NEVER.ordinal()); + b.add("sign-accounting", StemEnum.STEM_SIGN_ACCOUNTING.ordinal()); + 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("decimal-auto", StemEnum.STEM_DECIMAL_AUTO.ordinal()); + b.add("decimal-always", StemEnum.STEM_DECIMAL_ALWAYS.ordinal()); + + // Section 2: + b.add("round-increment", StemEnum.STEM_ROUND_INCREMENT.ordinal()); + b.add("measure-unit", StemEnum.STEM_MEASURE_UNIT.ordinal()); + b.add("per-measure-unit", StemEnum.STEM_PER_MEASURE_UNIT.ordinal()); + b.add("currency", StemEnum.STEM_CURRENCY.ordinal()); + b.add("integer-width", StemEnum.STEM_INTEGER_WIDTH.ordinal()); + b.add("numbering-system", StemEnum.STEM_NUMBERING_SYSTEM.ordinal()); + + // Build the CharsTrie + // TODO: Use SLOW or FAST here? + return b.buildCharSequence(StringTrieBuilder.Option.FAST).toString(); + } + /** * Utility class for methods that convert from StemEnum to corresponding objects or enums. This * applies to only the "Section 1" stems, those that are well-defined without an option. @@ -126,7 +179,7 @@ class NumberSkeletonImpl { case STEM_NOTATION_SIMPLE: return Notation.simple(); default: - return null; + throw new AssertionError(); } } @@ -139,7 +192,7 @@ class NumberSkeletonImpl { case STEM_PERMILLE: return NoUnit.PERMILLE; default: - return null; + throw new AssertionError(); } } @@ -154,7 +207,7 @@ class NumberSkeletonImpl { case STEM_ROUND_CURRENCY_CASH: return Rounder.currency(CurrencyUsage.CASH); default: - return null; + throw new AssertionError(); } } @@ -171,7 +224,7 @@ class NumberSkeletonImpl { case STEM_GROUP_THOUSANDS: return GroupingStrategy.THOUSANDS; default: - return null; + return null; // for objects, throw; for enums, return null } } @@ -188,7 +241,7 @@ class NumberSkeletonImpl { case STEM_UNIT_WIDTH_HIDDEN: return UnitWidth.HIDDEN; default: - return null; + return null; // for objects, throw; for enums, return null } } @@ -209,7 +262,7 @@ class NumberSkeletonImpl { case STEM_SIGN_ACCOUNTING_EXCEPT_ZERO: return SignDisplay.ACCOUNTING_EXCEPT_ZERO; default: - return null; + return null; // for objects, throw; for enums, return null } } @@ -220,7 +273,7 @@ class NumberSkeletonImpl { case STEM_DECIMAL_ALWAYS: return DecimalSeparatorDisplay.ALWAYS; default: - return null; + return null; // for objects, throw; for enums, return null } } } @@ -317,58 +370,6 @@ class NumberSkeletonImpl { } } - /** A data structure for mapping from stem strings to the stem enum. Built at startup. */ - static final String SERIALIZED_STEM_TRIE = buildStemTrie(); - - static String buildStemTrie() { - CharsTrieBuilder b = new CharsTrieBuilder(); - - // Section 1: - b.add("compact-short", StemEnum.STEM_COMPACT_SHORT.ordinal()); - b.add("compact-long", StemEnum.STEM_COMPACT_LONG.ordinal()); - b.add("scientific", StemEnum.STEM_SCIENTIFIC.ordinal()); - b.add("engineering", StemEnum.STEM_ENGINEERING.ordinal()); - b.add("notation-simple", StemEnum.STEM_NOTATION_SIMPLE.ordinal()); - b.add("base-unit", StemEnum.STEM_BASE_UNIT.ordinal()); - b.add("percent", StemEnum.STEM_PERCENT.ordinal()); - b.add("permille", StemEnum.STEM_PERMILLE.ordinal()); - b.add("round-integer", StemEnum.STEM_ROUND_INTEGER.ordinal()); - b.add("round-unlimited", StemEnum.STEM_ROUND_UNLIMITED.ordinal()); - b.add("round-currency-standard", StemEnum.STEM_ROUND_CURRENCY_STANDARD.ordinal()); - b.add("round-currency-cash", StemEnum.STEM_ROUND_CURRENCY_CASH.ordinal()); - b.add("group-off", StemEnum.STEM_GROUP_OFF.ordinal()); - b.add("group-min2", StemEnum.STEM_GROUP_MIN2.ordinal()); - b.add("group-auto", StemEnum.STEM_GROUP_AUTO.ordinal()); - b.add("group-on-aligned", StemEnum.STEM_GROUP_ON_ALIGNED.ordinal()); - b.add("group-thousands", StemEnum.STEM_GROUP_THOUSANDS.ordinal()); - b.add("latin", StemEnum.STEM_LATIN.ordinal()); - b.add("unit-width-narrow", StemEnum.STEM_UNIT_WIDTH_NARROW.ordinal()); - b.add("unit-width-short", StemEnum.STEM_UNIT_WIDTH_SHORT.ordinal()); - b.add("unit-width-full-name", StemEnum.STEM_UNIT_WIDTH_FULL_NAME.ordinal()); - b.add("unit-width-iso-code", StemEnum.STEM_UNIT_WIDTH_ISO_CODE.ordinal()); - b.add("unit-width-hidden", StemEnum.STEM_UNIT_WIDTH_HIDDEN.ordinal()); - b.add("sign-auto", StemEnum.STEM_SIGN_AUTO.ordinal()); - b.add("sign-always", StemEnum.STEM_SIGN_ALWAYS.ordinal()); - b.add("sign-never", StemEnum.STEM_SIGN_NEVER.ordinal()); - b.add("sign-accounting", StemEnum.STEM_SIGN_ACCOUNTING.ordinal()); - 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("decimal-auto", StemEnum.STEM_DECIMAL_AUTO.ordinal()); - b.add("decimal-always", StemEnum.STEM_DECIMAL_ALWAYS.ordinal()); - - // Section 2: - b.add("round-increment", StemEnum.STEM_ROUND_INCREMENT.ordinal()); - b.add("measure-unit", StemEnum.STEM_MEASURE_UNIT.ordinal()); - b.add("per-measure-unit", StemEnum.STEM_PER_MEASURE_UNIT.ordinal()); - b.add("currency", StemEnum.STEM_CURRENCY.ordinal()); - b.add("integer-width", StemEnum.STEM_INTEGER_WIDTH.ordinal()); - b.add("numbering-system", StemEnum.STEM_NUMBERING_SYSTEM.ordinal()); - - // TODO: Use SLOW or FAST here? - return b.buildCharSequence(StringTrieBuilder.Option.FAST).toString(); - } - /** Kebab case versions of the rounding mode enum. */ static final String[] ROUNDING_MODE_STRINGS = { "up", @@ -443,6 +444,7 @@ class NumberSkeletonImpl { CharsTrie stemTrie = new CharsTrie(SERIALIZED_STEM_TRIE, 0); ParseState stem = ParseState.STATE_NULL; int offset = 0; + // Primary skeleton parse loop: while (offset < segment.length()) { int cp = segment.codePointAt(offset); @@ -722,6 +724,10 @@ class NumberSkeletonImpl { ///// MAIN SKELETON GENERATION FUNCTION ///// + /** + * Main skeleton generator function. Appends the normalized skeleton for the MacroProps to the given + * StringBuilder. + */ private static void generateSkeleton(MacroProps macros, StringBuilder sb) { // Supported options if (macros.notation != null && GeneratorHelpers.notation(macros, sb)) { @@ -779,9 +785,14 @@ class NumberSkeletonImpl { } } - ///// BLUEPRINT HELPER FUNCTIONS (stem and options that cannot be interpreted literally) ///// + ///// BLUEPRINT HELPER FUNCTIONS ///// + /** + * Utility class for methods for processing stems and options that cannot be interpreted literally. + */ static final class BlueprintHelpers { + + /** @return Whether we successfully found and parsed an exponent width option. */ private static boolean parseExponentWidthOption(StringSegment segment, MacroProps macros) { if (segment.charAt(0) != '+') { return false; @@ -808,6 +819,7 @@ class NumberSkeletonImpl { appendMultiple(sb, 'e', minExponentDigits); } + /** @return Whether we successfully found and parsed an exponent sign option. */ private static boolean parseExponentSignOption(StringSegment segment, MacroProps macros) { // Get the sign display type out of the CharsTrie data structure. // TODO: Make this more efficient (avoid object allocation)? It shouldn't be very hot code. @@ -977,6 +989,7 @@ class NumberSkeletonImpl { } } + /** @return Whether we successfully found and parsed a frac-sig option. */ private static boolean parseFracSigOption(StringSegment segment, MacroProps macros) { if (segment.charAt(0) != '@') { return false; @@ -1047,6 +1060,7 @@ class NumberSkeletonImpl { sb.append(increment.toPlainString()); } + /** @return Whether we successfully found and parsed a rounding mode. */ private static boolean parseRoundingModeOption(StringSegment segment, MacroProps macros) { for (int rm = 0; rm < ROUNDING_MODE_STRINGS.length; rm++) { if (segment.equals(ROUNDING_MODE_STRINGS[rm])) { @@ -1127,6 +1141,10 @@ class NumberSkeletonImpl { ///// STEM GENERATION HELPER FUNCTIONS ///// + /** + * Utility class for methods for generating a token corresponding to each macro-prop. Each method + * returns whether or not a token was written to the string builder. + */ static final class GeneratorHelpers { private static boolean notation(MacroProps macros, StringBuilder sb) { From 52c665a2bda32091ec1d3e769d51e5c8bd47e811 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Fri, 23 Mar 2018 07:51:52 +0000 Subject: [PATCH 057/129] ICU-8610 More general progress in C++; generation code is largely implemented. Probably not building yet. X-SVN-Rev: 41143 --- icu4c/source/i18n/number_formatimpl.cpp | 18 +- icu4c/source/i18n/number_grouping.cpp | 12 +- icu4c/source/i18n/number_integerwidth.cpp | 10 + icu4c/source/i18n/number_skeletons.cpp | 331 +++++++++++++++++- icu4c/source/i18n/number_skeletons.h | 47 +-- icu4c/source/i18n/number_utils.h | 16 + icu4c/source/i18n/unicode/numberformatter.h | 41 ++- .../ibm/icu/number/NumberSkeletonImpl.java | 15 +- 8 files changed, 438 insertions(+), 52 deletions(-) diff --git a/icu4c/source/i18n/number_formatimpl.cpp b/icu4c/source/i18n/number_formatimpl.cpp index 102a786a22..b3e843206f 100644 --- a/icu4c/source/i18n/number_formatimpl.cpp +++ b/icu4c/source/i18n/number_formatimpl.cpp @@ -125,22 +125,6 @@ getCurrencyFormatInfo(const Locale& locale, const char* isoCode, UErrorCode& sta return result; } -inline bool unitIsCurrency(const MeasureUnit& unit) { - return uprv_strcmp("currency", unit.getType()) == 0; -} - -inline bool unitIsNoUnit(const MeasureUnit& unit) { - return uprv_strcmp("none", unit.getType()) == 0; -} - -inline bool unitIsPercent(const MeasureUnit& unit) { - return uprv_strcmp("percent", unit.getSubtype()) == 0; -} - -inline bool unitIsPermille(const MeasureUnit& unit) { - return uprv_strcmp("permille", unit.getSubtype()) == 0; -} - } // namespace NumberFormatterImpl* NumberFormatterImpl::fromMacros(const MacroProps& macros, UErrorCode& status) { @@ -325,7 +309,7 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe, if (!macros.integerWidth.isBogus()) { fMicros.integerWidth = macros.integerWidth; } else { - fMicros.integerWidth = IntegerWidth::zeroFillTo(1); + fMicros.integerWidth = IntegerWidth::standard(); } // Sign display diff --git a/icu4c/source/i18n/number_grouping.cpp b/icu4c/source/i18n/number_grouping.cpp index a77e505a22..90bcc4fb5b 100644 --- a/icu4c/source/i18n/number_grouping.cpp +++ b/icu4c/source/i18n/number_grouping.cpp @@ -37,15 +37,15 @@ int16_t getMinGroupingForLocale(const Locale& locale) { Grouper Grouper::forStrategy(UGroupingStrategy grouping) { switch (grouping) { case UNUM_GROUPING_OFF: - return {-1, -1, -2}; + return {-1, -1, -2, grouping}; case UNUM_GROUPING_AUTO: - return {-2, -2, -2}; + return {-2, -2, -2, grouping}; case UNUM_GROUPING_MIN2: - return {-2, -2, -3}; + return {-2, -2, -3, grouping}; case UNUM_GROUPING_ON_ALIGNED: - return {-4, -4, 1}; + return {-4, -4, 1, grouping}; case UNUM_GROUPING_THOUSANDS: - return {3, 3, 1}; + return {3, 3, 1, grouping}; default: U_ASSERT(FALSE); } @@ -57,7 +57,7 @@ Grouper Grouper::forProperties(const DecimalFormatProperties& properties) { auto minGrouping = static_cast(properties.minimumGroupingDigits); grouping1 = grouping1 > 0 ? grouping1 : grouping2 > 0 ? grouping2 : grouping1; grouping2 = grouping2 > 0 ? grouping2 : grouping1; - return {grouping1, grouping2, minGrouping}; + return {grouping1, grouping2, minGrouping, UNUM_GROUPING_COUNT}; } void Grouper::setLocaleData(const impl::ParsedPatternInfo &patternInfo, const Locale& locale) { diff --git a/icu4c/source/i18n/number_integerwidth.cpp b/icu4c/source/i18n/number_integerwidth.cpp index 4a612273f5..464c2230ff 100644 --- a/icu4c/source/i18n/number_integerwidth.cpp +++ b/icu4c/source/i18n/number_integerwidth.cpp @@ -1,6 +1,7 @@ // © 2017 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html +#include #include "unicode/utypes.h" #if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT @@ -48,4 +49,13 @@ void IntegerWidth::apply(impl::DecimalQuantity &quantity, UErrorCode &status) co } } +bool IntegerWidth::operator==(const IntegerWidth& other) const { + if (fHasError) { + return other.fHasError && fUnion.errorCode == other.fUnion.errorCode; + } else { + return !other.fHasError && fUnion.minMaxInt.fMinInt == other.fUnion.minMaxInt.fMinInt && + fUnion.minMaxInt.fMaxInt == other.fUnion.minMaxInt.fMaxInt; + } +} + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/number_skeletons.cpp b/icu4c/source/i18n/number_skeletons.cpp index 1431459a4a..dbb9485228 100644 --- a/icu4c/source/i18n/number_skeletons.cpp +++ b/icu4c/source/i18n/number_skeletons.cpp @@ -1,6 +1,7 @@ // © 2018 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html +#include #include "unicode/utypes.h" #if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT @@ -15,6 +16,7 @@ #include "hash.h" #include "patternprops.h" #include "unicode/ucharstriebuilder.h" +#include "number_utils.h" using namespace icu; using namespace icu::number; @@ -327,7 +329,7 @@ UnicodeString skeleton::generate(const MacroProps& macros, UErrorCode& status) { if (U_FAILURE(status)) { return {}; } UnicodeString sb; - generateSkeleton(macros, sb, status); + GeneratorHelpers::generateSkeleton(macros, sb, status); return sb; } @@ -559,5 +561,332 @@ skeleton::parseStem(const StringSegment& segment, const UCharsTrie& stemTrie, Se } } +ParseState skeleton::parseOption(ParseState stem, const StringSegment& segment, MacroProps& macros, + UErrorCode& status) { + + ///// Required options: ///// + + switch (stem) { + case STATE_CURRENCY_UNIT: + blueprint_helpers::parseCurrencyOption(segment, macros, status); + return STATE_NULL; + case STATE_MEASURE_UNIT: + blueprint_helpers::parseMeasureUnitOption(segment, macros, status); + return STATE_NULL; + case STATE_PER_MEASURE_UNIT: + blueprint_helpers::parseMeasurePerUnitOption(segment, macros, status); + return STATE_NULL; + case STATE_INCREMENT_ROUNDER: + blueprint_helpers::parseIncrementOption(segment, macros, status); + return STATE_ROUNDER; + case STATE_INTEGER_WIDTH: + blueprint_helpers::parseIntegerWidthOption(segment, macros, status); + return STATE_NULL; + case STATE_NUMBERING_SYSTEM: + blueprint_helpers::parseNumberingSystemOption(segment, macros, status); + return STATE_NULL; + default: + break; + } + + ///// Non-required options: ///// + + // Scientific options + switch (stem) { + case STATE_SCIENTIFIC: + if (blueprint_helpers::parseExponentWidthOption(segment, macros, status)) { + return STATE_SCIENTIFIC; + } + if (blueprint_helpers::parseExponentSignOption(segment, macros, status)) { + return STATE_SCIENTIFIC; + } + break; + default: + break; + } + + // Frac-sig option + switch (stem) { + case STATE_FRACTION_ROUNDER: + if (blueprint_helpers::parseFracSigOption(segment, macros, status)) { + return STATE_ROUNDER; + } + break; + default: + break; + } + + // Rounding mode option + switch (stem) { + case STATE_ROUNDER: + case STATE_FRACTION_ROUNDER: + if (blueprint_helpers::parseRoundingModeOption(segment, macros, status)) { + return STATE_ROUNDER; + } + break; + default: + break; + } + + // Unknown option + // throw new SkeletonSyntaxException("Invalid option", segment); + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return STATE_NULL; +} + +void GeneratorHelpers::generateSkeleton(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { + // Supported options + if (GeneratorHelpers::notation(macros, sb, status)) { + sb.append(u' '); + } + if (U_FAILURE(status)) { return; } + if (GeneratorHelpers::unit(macros, sb, status)) { + sb.append(u' '); + } + if (U_FAILURE(status)) { return; } + if (GeneratorHelpers::perUnit(macros, sb, status)) { + sb.append(u' '); + } + if (U_FAILURE(status)) { return; } + if (GeneratorHelpers::rounding(macros, sb, status)) { + sb.append(u' '); + } + if (U_FAILURE(status)) { return; } + if (GeneratorHelpers::grouping(macros, sb, status)) { + sb.append(u' '); + } + if (U_FAILURE(status)) { return; } + if (GeneratorHelpers::integerWidth(macros, sb, status)) { + sb.append(u' '); + } + if (U_FAILURE(status)) { return; } + if (GeneratorHelpers::symbols(macros, sb, status)) { + sb.append(u' '); + } + if (U_FAILURE(status)) { return; } + if (GeneratorHelpers::unitWidth(macros, sb, status)) { + sb.append(u' '); + } + if (U_FAILURE(status)) { return; } + if (GeneratorHelpers::sign(macros, sb, status)) { + sb.append(u' '); + } + if (U_FAILURE(status)) { return; } + if (GeneratorHelpers::decimal(macros, sb, status)) { + sb.append(u' '); + } + if (U_FAILURE(status)) { return; } + + // Unsupported options + if (!macros.padder.isBogus()) { + status = U_UNSUPPORTED_ERROR; + return; + } + if (macros.affixProvider != nullptr) { + status = U_UNSUPPORTED_ERROR; + return; + } + if (macros.multiplier.isValid()) { + status = U_UNSUPPORTED_ERROR; + return; + } + if (macros.rules != nullptr) { + status = U_UNSUPPORTED_ERROR; + return; + } + if (macros.currencySymbols != nullptr) { + status = U_UNSUPPORTED_ERROR; + return; + } + + // Remove the trailing space + if (sb.length() > 0) { + sb.truncate(sb.length() - 1); + } +} + + +bool GeneratorHelpers::notation(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { + if (macros.notation.fType == Notation::NTN_COMPACT) { + UNumberCompactStyle style = macros.notation.fUnion.compactStyle; + if (style == UNumberCompactStyle::UNUM_LONG) { + sb.append(u"compact-long", -1); + return true; + } else if (style == UNumberCompactStyle::UNUM_SHORT) { + sb.append(u"compact-short", -1); + return true; + } else { + // Compact notation generated from custom data (not supported in skeleton) + // The other compact notations are literals + status = U_UNSUPPORTED_ERROR; + return false; + } + } else if (macros.notation.fType == Notation::NTN_SCIENTIFIC) { + const Notation::ScientificSettings& impl = macros.notation.fUnion.scientific; + if (impl.fEngineeringInterval == 3) { + sb.append(u"engineering", -1); + } else { + sb.append(u"scientific", -1); + } + if (impl.fMinExponentDigits > 1) { + sb.append(u'/'); + blueprint_helpers::generateExponentWidthOption(impl.fMinExponentDigits, sb, status); + } + if (impl.fExponentSignDisplay != UNUM_SIGN_AUTO) { + sb.append(u'/'); + enum_to_stem_string::signDisplay(impl.fExponentSignDisplay, sb); + } + return true; + } else { + // Default value is not shown in normalized form + return false; + } +} + +bool GeneratorHelpers::unit(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { + if (unitIsCurrency(macros.unit)) { + sb.append(u"currency/", -1); + blueprint_helpers::generateCurrencyOption({macros.unit, status}, sb, status); + return true; + } else if (unitIsNoUnit(macros.unit)) { + if (unitIsPercent(macros.unit)) { + sb.append(u"percent", -1); + return true; + } else if (unitIsPermille(macros.unit)) { + sb.append(u"permille", -1); + return true; + } else { + // Default value is not shown in normalized form + return false; + } + } else { + sb.append(u"measure-unit/", -1); + blueprint_helpers::generateMeasureUnitOption(macros.unit, sb, status); + return true; + } +} + +bool GeneratorHelpers::perUnit(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { + // Per-units are currently expected to be only MeasureUnits. + if (unitIsCurrency(macros.perUnit) || unitIsNoUnit(macros.perUnit)) { + status = U_UNSUPPORTED_ERROR; + } else { + sb.append(u"per-measure-unit/", -1); + blueprint_helpers::generateMeasureUnitOption(macros.perUnit, sb, status); + return true; + } +} + +bool GeneratorHelpers::rounding(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { + if (macros.rounder.fType == Rounder::RND_NONE) { + sb.append(u"round-unlimited", -1); + } else if (macros.rounder.fType == Rounder::RND_FRACTION) { + const Rounder::FractionSignificantSettings& impl = macros.rounder.fUnion.fracSig; + blueprint_helpers::generateFractionStem(impl.fMinFrac, impl.fMaxFrac, sb, status); + } else if (macros.rounder.fType == Rounder::RND_SIGNIFICANT) { + const Rounder::FractionSignificantSettings& impl = macros.rounder.fUnion.fracSig; + blueprint_helpers::generateDigitsStem(impl.fMinSig, impl.fMaxSig, sb, status); + } else if (macros.rounder.fType == Rounder::RND_FRACTION_SIGNIFICANT) { + const Rounder::FractionSignificantSettings& impl = macros.rounder.fUnion.fracSig; + blueprint_helpers::generateFractionStem(impl.fMinFrac, impl.fMaxFrac, sb, status); + sb.append(u'/'); + if (impl.fMinSig == -1) { + blueprint_helpers::generateDigitsStem(1, impl.fMaxSig, sb, status); + } else { + blueprint_helpers::generateDigitsStem(impl.fMinSig, -1, sb, status); + } + } else if (macros.rounder.fType == Rounder::RND_INCREMENT) { + const Rounder::IncrementSettings& impl = macros.rounder.fUnion.increment; + sb.append(u"round-increment/", -1); + blueprint_helpers::generateIncrementOption(impl.fIncrement, sb, status); + } else if (macros.rounder.fType == Rounder::RND_CURRENCY) { + UCurrencyUsage usage = macros.rounder.fUnion.currencyUsage; + if (usage == UCURR_USAGE_STANDARD) { + sb.append(u"round-currency-standard", -1); + } else { + sb.append(u"round-currency-cash", -1); + } + } else { + // Bogus or Error + return false; + } + + // Generate the options + if (macros.rounder.fRoundingMode != kDefaultMode) { + sb.append(u'/'); + blueprint_helpers::generateRoundingModeOption(macros.rounder.fRoundingMode, sb, status); + } + + // NOTE: Always return true for rounding because the default value depends on other options. + return true; +} + +bool GeneratorHelpers::grouping(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { + if (macros.grouper.isBogus() || macros.grouper.fStrategy == UNUM_GROUPING_COUNT) { + status = U_UNSUPPORTED_ERROR; + } else if (macros.grouper.fStrategy == UNUM_GROUPING_AUTO) { + return false; // Default value + } else { + enum_to_stem_string::groupingStrategy(macros.grouper.fStrategy, sb); + } +} + +bool GeneratorHelpers::integerWidth(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { + if (macros.integerWidth.fHasError || macros.integerWidth == IntegerWidth::standard()) { + // Error or Default + return false; + } + sb.append(u"integer-width/", -1); + blueprint_helpers::generateIntegerWidthOption( + macros.integerWidth.fUnion.minMaxInt.fMinInt, + macros.integerWidth.fUnion.minMaxInt.fMaxInt, + sb, + status); + return true; +} + +bool GeneratorHelpers::symbols(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { + if (macros.symbols.isNumberingSystem()) { + const NumberingSystem& ns = *macros.symbols.getNumberingSystem(); + if (uprv_strcmp(ns.getName(), "latn") == 0) { + sb.append(u"latin", -1); + } else { + sb.append(u"numbering-system/", -1); + blueprint_helpers::generateNumberingSystemOption(ns, sb, status); + } + return true; + } else if (macros.symbols.isDecimalFormatSymbols()) { + status = U_UNSUPPORTED_ERROR; + return false; + } else { + // No custom symbols + return false; + } +} + +bool GeneratorHelpers::unitWidth(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { + if (macros.unitWidth == UNUM_UNIT_WIDTH_SHORT || macros.unitWidth == UNUM_UNIT_WIDTH_COUNT) { + return false; // Default or Bogus + } + enum_to_stem_string::unitWidth(macros.unitWidth, sb); + return true; +} + +bool GeneratorHelpers::sign(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { + if (macros.sign == UNUM_SIGN_AUTO || macros.sign == UNUM_SIGN_COUNT) { + return false; // Default or Bogus + } + enum_to_stem_string::signDisplay(macros.sign, sb); + return true; +} + +bool GeneratorHelpers::decimal(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { + if (macros.decimal == UNUM_DECIMAL_SEPARATOR_AUTO || macros.decimal == UNUM_DECIMAL_SEPARATOR_COUNT) { + return false; // Default or Bogus + } + enum_to_stem_string::decimalSeparatorDisplay(macros.decimal, sb); + return true; +} + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/number_skeletons.h b/icu4c/source/i18n/number_skeletons.h index 74f2fd193f..843f1390af 100644 --- a/icu4c/source/i18n/number_skeletons.h +++ b/icu4c/source/i18n/number_skeletons.h @@ -143,14 +143,6 @@ ParseState parseStem(const StringSegment& segment, const UCharsTrie& stemTrie, S ParseState parseOption(ParseState stem, const StringSegment& segment, MacroProps& macros, UErrorCode& status); -/** - * Main skeleton generator function. Appends the normalized skeleton for the MacroProps to the given - * StringBuilder. - * - * Internal: use the create() endpoint instead of this function. - */ -void generateSkeleton(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); - } // namespace skeleton @@ -241,37 +233,48 @@ void generateIntegerWidthOption(int32_t minInt, int32_t maxInt, UnicodeString& s void parseNumberingSystemOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status); -void generateNumberingSystemOption(NumberingSystem, UnicodeString& sb, UErrorCode& status); +void generateNumberingSystemOption(const NumberingSystem& ns, UnicodeString& sb, UErrorCode& status); } // namespace blueprint_helpers /** - * Namespace for utility methods for generating a token corresponding to each macro-prop. Each method + * Class for utility methods for generating a token corresponding to each macro-prop. Each method * returns whether or not a token was written to the string builder. + * + * This needs to be a class, not a namespace, so it can be friended. */ -namespace generator_helpers { +class GeneratorHelpers { + public: + /** + * Main skeleton generator function. Appends the normalized skeleton for the MacroProps to the given + * StringBuilder. + * + * Internal: use the create() endpoint instead of this function. + */ + static void generateSkeleton(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); -bool notation(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + private: + static bool notation(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); -bool unit(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + static bool unit(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); -bool perUnit(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + static bool perUnit(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); -bool rounding(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + static bool rounding(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); -bool grouping(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + static bool grouping(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); -bool integerWidth(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + static bool integerWidth(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); -bool symbols(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + static bool symbols(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); -bool unitWidth(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + static bool unitWidth(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); -bool sign(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + static bool sign(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); -bool decimal(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + static bool decimal(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); -} // namespace generator_helpers +}; /** * Struct for null-checking. diff --git a/icu4c/source/i18n/number_utils.h b/icu4c/source/i18n/number_utils.h index 1e5494dc81..6ca205dc2b 100644 --- a/icu4c/source/i18n/number_utils.h +++ b/icu4c/source/i18n/number_utils.h @@ -122,6 +122,22 @@ inline int32_t insertDigitFromSymbols(NumberStringBuilder& output, int32_t index return output.insert(index, symbols.getConstDigitSymbol(digit), field, status); } +inline bool unitIsCurrency(const MeasureUnit& unit) { + return uprv_strcmp("currency", unit.getType()) == 0; +} + +inline bool unitIsNoUnit(const MeasureUnit& unit) { + return uprv_strcmp("none", unit.getType()) == 0; +} + +inline bool unitIsPercent(const MeasureUnit& unit) { + return uprv_strcmp("percent", unit.getSubtype()) == 0; +} + +inline bool unitIsPermille(const MeasureUnit& unit) { + return uprv_strcmp("permille", unit.getSubtype()) == 0; +} + } // namespace impl } // namespace number U_NAMESPACE_END diff --git a/icu4c/source/i18n/unicode/numberformatter.h b/icu4c/source/i18n/unicode/numberformatter.h index 87b1c6f196..fdc6bdc285 100644 --- a/icu4c/source/i18n/unicode/numberformatter.h +++ b/icu4c/source/i18n/unicode/numberformatter.h @@ -454,6 +454,7 @@ class NumberPropertyMapper; struct DecimalFormatProperties; class MultiplierChain; class CurrencySymbols; +class GeneratorHelpers; } // namespace impl @@ -658,6 +659,9 @@ class U_I18N_API Notation : public UMemory { friend class impl::NumberFormatterImpl; friend class impl::ScientificModifier; friend class impl::ScientificHandler; + + // To allow access to the skeleton generation code: + friend class impl::GeneratorHelpers; }; /** @@ -1045,6 +1049,9 @@ class U_I18N_API Rounder : public UMemory { friend class FractionRounder; friend class CurrencyRounder; friend class IncrementRounder; + + // To allow access to the skeleton generation code: + friend class impl::GeneratorHelpers; }; /** @@ -1237,6 +1244,11 @@ class U_I18N_API IntegerWidth : public UMemory { fUnion.minMaxInt.fMinInt = -1; } + /** Returns the default instance. */ + static IntegerWidth standard() { + return IntegerWidth::zeroFillTo(1); + } + bool isBogus() const { return !fHasError && fUnion.minMaxInt.fMinInt == -1; } @@ -1251,12 +1263,17 @@ class U_I18N_API IntegerWidth : public UMemory { void apply(impl::DecimalQuantity &quantity, UErrorCode &status) const; + bool operator==(const IntegerWidth& other) const; + // To allow MacroProps/MicroProps to initialize empty instances: friend struct impl::MacroProps; friend struct impl::MicroProps; // To allow NumberFormatterImpl to access isBogus() and perform other operations: friend class impl::NumberFormatterImpl; + + // To allow access to the skeleton generation code: + friend class impl::GeneratorHelpers; }; namespace impl { @@ -1362,8 +1379,11 @@ class U_I18N_API Grouper : public UMemory { // Future: static Grouper forProperties(DecimalFormatProperties& properties); /** @internal */ - Grouper(int16_t grouping1, int16_t grouping2, int16_t minGrouping) - : fGrouping1(grouping1), fGrouping2(grouping2), fMinGrouping(minGrouping) {} + Grouper(int16_t grouping1, int16_t grouping2, int16_t minGrouping, UGroupingStrategy strategy) + : fGrouping1(grouping1), + fGrouping2(grouping2), + fMinGrouping(minGrouping), + fStrategy(strategy) {} /** @internal */ int16_t getPrimary() const; @@ -1384,7 +1404,7 @@ class U_I18N_API Grouper : public UMemory { int16_t fGrouping2; /** - * The minimum gropuing size, with the following special values: + * The minimum grouping size, with the following special values: *

    *
  • -2 = needs locale data *
  • -3 = no less than 2 @@ -1392,6 +1412,12 @@ class U_I18N_API Grouper : public UMemory { */ int16_t fMinGrouping; + /** + * The UGroupingStrategy that was used to create this Grouper, or UNUM_GROUPING_COUNT if this + * was not created from a UGroupingStrategy. + */ + UGroupingStrategy fStrategy; + Grouper() : fGrouping1(-3) {}; bool isBogus() const { @@ -1412,6 +1438,9 @@ class U_I18N_API Grouper : public UMemory { // To allow NumberParserImpl to perform setLocaleData(): friend class ::icu::numparse::impl::NumberParserImpl; + + // To allow access to the skeleton generation code: + friend class impl::GeneratorHelpers; }; /** @internal */ @@ -1472,6 +1501,9 @@ class U_I18N_API Padder : public UMemory { // To allow NumberFormatterImpl to access isBogus() and perform other operations: friend class impl::NumberFormatterImpl; + + // To allow access to the skeleton generation code: + friend class impl::GeneratorHelpers; }; /** @internal */ @@ -1504,6 +1536,9 @@ class U_I18N_API Multiplier : public UMemory { // To allow the helper class MultiplierChain access to private fields: friend class impl::MultiplierChain; + + // To allow access to the skeleton generation code: + friend class impl::GeneratorHelpers; }; /** @internal */ 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 19ea62f68b..dc831d2dd5 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 @@ -686,6 +686,8 @@ class NumberSkeletonImpl { case STATE_NUMBERING_SYSTEM: BlueprintHelpers.parseNumberingSystemOption(segment, macros); return ParseState.STATE_NULL; + default: + break; } ///// Non-required options: ///// @@ -699,6 +701,9 @@ class NumberSkeletonImpl { if (BlueprintHelpers.parseExponentSignOption(segment, macros)) { return ParseState.STATE_SCIENTIFIC; } + break; + default: + break; } // Frac-sig option @@ -707,6 +712,9 @@ class NumberSkeletonImpl { if (BlueprintHelpers.parseFracSigOption(segment, macros)) { return ParseState.STATE_ROUNDER; } + break; + default: + break; } // Rounding mode option @@ -716,6 +724,9 @@ class NumberSkeletonImpl { if (BlueprintHelpers.parseRoundingModeOption(segment, macros)) { return ParseState.STATE_ROUNDER; } + break; + default: + break; } // Unknown option @@ -1210,7 +1221,7 @@ class NumberSkeletonImpl { private static boolean perUnit(MacroProps macros, StringBuilder sb) { // Per-units are currently expected to be only MeasureUnits. - if (macros.unit instanceof Currency || macros.unit instanceof NoUnit) { + if (macros.perUnit instanceof Currency || macros.perUnit instanceof NoUnit) { throw new UnsupportedOperationException( "Cannot generate number skeleton with per-unit that is not a standard measure unit"); } else { @@ -1242,8 +1253,6 @@ class NumberSkeletonImpl { Rounder.IncrementRounderImpl impl = (Rounder.IncrementRounderImpl) macros.rounder; sb.append("round-increment/"); BlueprintHelpers.generateIncrementOption(impl.increment, sb); - } else if (macros.rounder instanceof Rounder.InfiniteRounderImpl) { - sb.append("round-unlimited"); } else { assert macros.rounder instanceof Rounder.CurrencyRounderImpl; Rounder.CurrencyRounderImpl impl = (Rounder.CurrencyRounderImpl) macros.rounder; From 7da9e75441c1c7b68c6750b8a6859da952fece2b Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Fri, 23 Mar 2018 10:07:38 +0000 Subject: [PATCH 058/129] ICU-8610 C++ number skeleton code is building. Testing is next. X-SVN-Rev: 41144 --- icu4c/source/i18n/Makefile.in | 2 +- icu4c/source/i18n/number_skeletons.cpp | 490 +++++++++++++++++- icu4c/source/i18n/number_skeletons.h | 4 +- icu4c/source/i18n/numparse_stringsegment.cpp | 3 +- .../ibm/icu/number/NumberSkeletonImpl.java | 10 +- 5 files changed, 494 insertions(+), 15 deletions(-) diff --git a/icu4c/source/i18n/Makefile.in b/icu4c/source/i18n/Makefile.in index e249995b47..0e11817b1a 100644 --- a/icu4c/source/i18n/Makefile.in +++ b/icu4c/source/i18n/Makefile.in @@ -108,7 +108,7 @@ double-conversion-cached-powers.o double-conversion-diy-fp.o double-conversion-f numparse_stringsegment.o numparse_unisets.o numparse_parsednumber.o \ numparse_impl.o numparse_symbols.o numparse_decimal.o numparse_scientific.o \ numparse_currency.o numparse_affixes.o numparse_compositions.o numparse_validators.o \ -number_mapper.o number_multiplier.o number_currencysymbols.o +number_mapper.o number_multiplier.o number_currencysymbols.o number_skeletons.o ## Header files to install diff --git a/icu4c/source/i18n/number_skeletons.cpp b/icu4c/source/i18n/number_skeletons.cpp index dbb9485228..3efae97f20 100644 --- a/icu4c/source/i18n/number_skeletons.cpp +++ b/icu4c/source/i18n/number_skeletons.cpp @@ -1,7 +1,6 @@ // © 2018 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html -#include #include "unicode/utypes.h" #if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT @@ -13,10 +12,11 @@ #include "number_skeletons.h" #include "umutex.h" #include "ucln_in.h" -#include "hash.h" #include "patternprops.h" #include "unicode/ucharstriebuilder.h" #include "number_utils.h" +#include "number_decimalquantity.h" +#include "unicode/numberformatter.h" using namespace icu; using namespace icu::number; @@ -34,6 +34,7 @@ char16_t* kSerializedStemTrie = nullptr; UBool U_CALLCONV cleanupNumberSkeletons() { uprv_free(kSerializedStemTrie); kSerializedStemTrie = nullptr; + return TRUE; } void U_CALLCONV initNumberSkeletons(UErrorCode& status) { @@ -99,7 +100,14 @@ void U_CALLCONV initNumberSkeletons(UErrorCode& status) { } -#define CHECK_NULL(seen, field, status) void; /* for auto-format line wraping */ \ +inline void appendMultiple(UnicodeString& sb, UChar32 cp, int32_t count) { + for (int i = 0; i < count; i++) { + sb.append(cp); + } +} + + +#define CHECK_NULL(seen, field, status) (void)(seen); /* for auto-format line wraping */ \ { \ if ((seen).field) { \ (status) = U_NUMBER_SKELETON_SYNTAX_ERROR; \ @@ -109,6 +117,15 @@ void U_CALLCONV initNumberSkeletons(UErrorCode& status) { } +const char16_t* const kRoundingModeStrings[] = { + u"up", u"down", u"ceiling", u"floor", u"half-up", u"half-down", u"half-even", u"unnecessary"}; + +constexpr int32_t kRoundingModeCount = 8; +static_assert( + sizeof(kRoundingModeStrings) / sizeof(*kRoundingModeStrings) == kRoundingModeCount, + "kRoundingModeCount should be the number of rounding modes"); + + } // anonymous namespace @@ -376,6 +393,7 @@ MacroProps skeleton::parseSkeleton(const UnicodeString& skeletonString, UErrorCo stem = parseOption(stem, segment, macros, status); } segment.resetLength(); + if (U_FAILURE(status)) { return macros; } // Consume the segment: segment.adjustOffset(offset); @@ -439,6 +457,8 @@ skeleton::parseStem(const StringSegment& segment, const UCharsTrie& stemTrie, Se CHECK_NULL(seen, rounder, status); blueprint_helpers::parseDigitsStem(segment, macros, status); return STATE_NULL; + default: + break; } // Now look at the stemsTrie, which is already be pointing at our stem. @@ -706,6 +726,461 @@ void GeneratorHelpers::generateSkeleton(const MacroProps& macros, UnicodeString& } +bool blueprint_helpers::parseExponentWidthOption(const StringSegment& segment, MacroProps& macros, + UErrorCode&) { + if (segment.charAt(0) != u'+') { + return false; + } + int32_t offset = 1; + int32_t minExp = 0; + for (; offset < segment.length(); offset++) { + if (segment.charAt(offset) == u'e') { + minExp++; + } else { + break; + } + } + if (offset < segment.length()) { + return false; + } + // Use the public APIs to enforce bounds checking + macros.notation = static_cast(macros.notation).withMinExponentDigits(minExp); + return true; +} + +void +blueprint_helpers::generateExponentWidthOption(int32_t minExponentDigits, UnicodeString& sb, UErrorCode&) { + sb.append(u'+'); + appendMultiple(sb, u'e', minExponentDigits); +} + +bool +blueprint_helpers::parseExponentSignOption(const StringSegment& segment, MacroProps& macros, UErrorCode&) { + // Get the sign display type out of the CharsTrie data structure. + UCharsTrie tempStemTrie(kSerializedStemTrie); + UStringTrieResult result = tempStemTrie.next(segment.toUnicodeString().getBuffer(), segment.length()); + if (result != USTRINGTRIE_INTERMEDIATE_VALUE && result != USTRINGTRIE_FINAL_VALUE) { + return false; + } + auto sign = stem_to_object::signDisplay(static_cast(tempStemTrie.getValue())); + if (sign == UNUM_SIGN_COUNT) { + return false; + } + macros.notation = static_cast(macros.notation).withExponentSignDisplay(sign); + return true; +} + +void blueprint_helpers::parseCurrencyOption(const StringSegment& segment, MacroProps& macros, + UErrorCode& status) { + if (segment.length() != 3) { + // throw new SkeletonSyntaxException("Invalid currency", segment); + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return; + } + const UChar* currencyCode = segment.toUnicodeString().getTerminatedBuffer(); + // Check that the currency code is valid: + int32_t numericCode = ucurr_getNumericCode(currencyCode); + if (numericCode == 0) { + // throw new SkeletonSyntaxException("Invalid currency", segment); + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return; + } + // Slicing is OK + macros.unit = CurrencyUnit(currencyCode, status); // NOLINT +} + +void +blueprint_helpers::generateCurrencyOption(const CurrencyUnit& currency, UnicodeString& sb, UErrorCode&) { + sb.append(currency.getISOCurrency(), -1); +} + +void blueprint_helpers::parseMeasureUnitOption(const StringSegment& segment, MacroProps& macros, + UErrorCode& status) { + // NOTE: The category (type) of the unit is guaranteed to be a valid subtag (alphanumeric) + // http://unicode.org/reports/tr35/#Validity_Data + int firstHyphen = 0; + while (firstHyphen < segment.length() && segment.charAt(firstHyphen) != '-') { + firstHyphen++; + } + if (firstHyphen == segment.length()) { + // throw new SkeletonSyntaxException("Invalid measure unit option", segment); + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return; + } + + // MeasureUnit is in char space; we need to convert. + // Note: the longest type/subtype as of this writing (March 2018) is 24 chars. + static constexpr int32_t CAPACITY = 30; + char type[CAPACITY]; + char subType[CAPACITY]; + const int32_t typeLen = firstHyphen; + const int32_t subTypeLen = segment.length() - firstHyphen - 1; + if (typeLen + 1 > CAPACITY || subTypeLen + 1 > CAPACITY) { + // Type or subtype longer than 30? + // The capacity should be increased if this is a problem with a real CLDR unit. + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return; + } + u_UCharsToChars(segment.toUnicodeString().getBuffer(), type, typeLen); + u_UCharsToChars(segment.toUnicodeString().getBuffer() + firstHyphen + 1, subType, subTypeLen); + type[typeLen] = 0; + subType[subTypeLen] = 0; + + // Note: the largest type as of this writing (March 2018) is "volume", which has 24 units. + MeasureUnit units[30]; + UErrorCode localStatus = U_ZERO_ERROR; + int32_t numUnits = MeasureUnit::getAvailable(type, units, 30, localStatus); + if (U_FAILURE(localStatus)) { + // More than 30 units in this type? + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return; + } + for (int32_t i = 0; i < numUnits; i++) { + auto& unit = units[i]; + if (uprv_strcmp(subType, unit.getSubtype()) == 0) { + macros.unit = unit; + return; + } + } + + // throw new SkeletonSyntaxException("Unknown measure unit", segment); + status = U_NUMBER_SKELETON_SYNTAX_ERROR; +} + +void blueprint_helpers::generateMeasureUnitOption(const MeasureUnit& measureUnit, UnicodeString& sb, + UErrorCode& status) { + // We need to convert from char* to UChar*... + // See comments in the previous function about the capacity setting. + static constexpr int32_t CAPACITY = 30; + char16_t type16[CAPACITY]; + char16_t subType16[CAPACITY]; + const auto typeLen = static_cast(uprv_strlen(measureUnit.getType())); + const auto subTypeLen = static_cast(uprv_strlen(measureUnit.getSubtype())); + if (typeLen + 1 > CAPACITY || subTypeLen + 1 > CAPACITY) { + // Type or subtype longer than 30? + // The capacity should be increased if this is a problem with a real CLDR unit. + status = U_UNSUPPORTED_ERROR; + return; + } + u_charsToUChars(measureUnit.getType(), type16, typeLen); + u_charsToUChars(measureUnit.getSubtype(), subType16, subTypeLen); + + sb.append(type16, typeLen); + sb.append(u'-'); + sb.append(subType16, subTypeLen); +} + +void blueprint_helpers::parseMeasurePerUnitOption(const StringSegment& segment, MacroProps& macros, + UErrorCode& status) { + // A little bit of a hack: safe the current unit (numerator), call the main measure unit + // parsing code, put back the numerator unit, and put the new unit into per-unit. + MeasureUnit numerator = macros.unit; + parseMeasureUnitOption(segment, macros, status); + macros.perUnit = macros.unit; + macros.unit = numerator; +} + +void blueprint_helpers::parseFractionStem(const StringSegment& segment, MacroProps& macros, + UErrorCode& status) { + U_ASSERT(segment.charAt(0) == u'.'); + int32_t offset = 1; + int32_t minFrac = 0; + int32_t maxFrac; + for (; offset < segment.length(); offset++) { + if (segment.charAt(offset) == u'0') { + minFrac++; + } else { + break; + } + } + if (offset < segment.length()) { + if (segment.charAt(offset) == u'+') { + maxFrac = -1; + offset++; + } else { + maxFrac = minFrac; + for (; offset < segment.length(); offset++) { + if (segment.charAt(offset) == u'#') { + maxFrac++; + } else { + break; + } + } + } + } else { + maxFrac = minFrac; + } + if (offset < segment.length()) { + // throw new SkeletonSyntaxException("Invalid fraction stem", segment); + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return; + } + // Use the public APIs to enforce bounds checking + if (maxFrac == -1) { + macros.rounder = Rounder::minFraction(minFrac); + } else { + macros.rounder = Rounder::minMaxFraction(minFrac, maxFrac); + } +} + +void +blueprint_helpers::generateFractionStem(int32_t minFrac, int32_t maxFrac, UnicodeString& sb, UErrorCode&) { + if (minFrac == 0 && maxFrac == 0) { + sb.append(u"round-integer", -1); + return; + } + sb.append(u'.'); + appendMultiple(sb, u'0', minFrac); + if (maxFrac == -1) { + sb.append(u'+'); + } else { + appendMultiple(sb, u'#', maxFrac - minFrac); + } +} + +void +blueprint_helpers::parseDigitsStem(const StringSegment& segment, MacroProps& macros, UErrorCode& status) { + U_ASSERT(segment.charAt(0) == u'@'); + int offset = 0; + int minSig = 0; + int maxSig; + for (; offset < segment.length(); offset++) { + if (segment.charAt(offset) == u'@') { + minSig++; + } else { + break; + } + } + if (offset < segment.length()) { + if (segment.charAt(offset) == u'+') { + maxSig = -1; + offset++; + } else { + maxSig = minSig; + for (; offset < segment.length(); offset++) { + if (segment.charAt(offset) == u'#') { + maxSig++; + } else { + break; + } + } + } + } else { + maxSig = minSig; + } + if (offset < segment.length()) { + // throw new SkeletonSyntaxException("Invalid significant digits stem", segment); + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + } + // Use the public APIs to enforce bounds checking + if (maxSig == -1) { + macros.rounder = Rounder::minDigits(minSig); + } else { + macros.rounder = Rounder::minMaxDigits(minSig, maxSig); + } +} + +void +blueprint_helpers::generateDigitsStem(int32_t minSig, int32_t maxSig, UnicodeString& sb, UErrorCode&) { + appendMultiple(sb, u'@', minSig); + if (maxSig == -1) { + sb.append(u'+'); + } else { + appendMultiple(sb, u'#', maxSig - minSig); + } +} + +bool blueprint_helpers::parseFracSigOption(const StringSegment& segment, MacroProps& macros, + UErrorCode& status) { + if (segment.charAt(0) != u'@') { + return false; + } + int offset = 0; + int minSig = 0; + int maxSig; + for (; offset < segment.length(); offset++) { + if (segment.charAt(offset) == u'@') { + minSig++; + } else { + break; + } + } + // For the frac-sig option, there must be minSig or maxSig but not both. + // Valid: @+, @@+, @@@+ + // Valid: @#, @##, @### + // Invalid: @, @@, @@@ + // Invalid: @@#, @@##, @@@# + if (offset < segment.length()) { + if (segment.charAt(offset) == u'+') { + maxSig = -1; + offset++; + } else if (minSig > 1) { + // @@#, @@##, @@@# + // throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", segment); + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return false; + } else { + maxSig = minSig; + for (; offset < segment.length(); offset++) { + if (segment.charAt(offset) == u'#') { + maxSig++; + } else { + break; + } + } + } + } else { + // @, @@, @@@ + // throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", segment); + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return false; + } + if (offset < segment.length()) { + // throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", segment); + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return false; + } + + auto& oldRounder = static_cast(macros.rounder); + if (maxSig == -1) { + macros.rounder = oldRounder.withMinDigits(minSig); + } else { + macros.rounder = oldRounder.withMaxDigits(maxSig); + } + return true; +} + +void blueprint_helpers::parseIncrementOption(const StringSegment& segment, MacroProps& macros, + UErrorCode& status) { + // Utilize DecimalQuantity/decNumber to parse this for us. + static constexpr int32_t CAPACITY = 30; + char buffer[CAPACITY]; + if (segment.length() > CAPACITY) { + // No support for numbers this long; they won't fit in a double anyway. + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return; + } + u_UCharsToChars(segment.toUnicodeString().getBuffer(), buffer, segment.length()); + DecimalQuantity dq; + dq.setToDecNumber({buffer, segment.length()}); + double increment = dq.toDouble(); + macros.rounder = Rounder::increment(increment); +} + +void blueprint_helpers::generateIncrementOption(double increment, UnicodeString& sb, UErrorCode&) { + // Utilize DecimalQuantity/double_conversion to format this for us. + DecimalQuantity dq; + dq.setToDouble(increment); + dq.roundToInfinity(); + sb.append(dq.toPlainString()); +} + +bool +blueprint_helpers::parseRoundingModeOption(const StringSegment& segment, MacroProps& macros, UErrorCode&) { + for (int rm = 0; rm < kRoundingModeCount; rm++) { + if (segment == UnicodeString(kRoundingModeStrings[rm], -1)) { + macros.rounder = macros.rounder.withMode(static_cast(rm)); + return true; + } + } + return false; +} + +void blueprint_helpers::generateRoundingModeOption(RoundingMode mode, UnicodeString& sb, UErrorCode&) { + sb.append(kRoundingModeStrings[mode], -1); +} + +void blueprint_helpers::parseIntegerWidthOption(const StringSegment& segment, MacroProps& macros, + UErrorCode& status) { + int32_t offset = 0; + int32_t minInt = 0; + int32_t maxInt; + if (segment.charAt(0) == u'+') { + maxInt = -1; + offset++; + } else { + maxInt = 0; + } + for (; offset < segment.length(); offset++) { + if (segment.charAt(offset) == u'#') { + maxInt++; + } else { + break; + } + } + if (offset < segment.length()) { + for (; offset < segment.length(); offset++) { + if (segment.charAt(offset) == u'0') { + minInt++; + } else { + break; + } + } + } + if (maxInt != -1) { + maxInt += minInt; + } + if (offset < segment.length()) { + // throw new SkeletonSyntaxException("Invalid integer width stem", segment); + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return; + } + // Use the public APIs to enforce bounds checking + if (maxInt == -1) { + macros.integerWidth = IntegerWidth::zeroFillTo(minInt); + } else { + macros.integerWidth = IntegerWidth::zeroFillTo(minInt).truncateAt(maxInt); + } +} + +void blueprint_helpers::generateIntegerWidthOption(int32_t minInt, int32_t maxInt, UnicodeString& sb, + UErrorCode&) { + if (maxInt == -1) { + sb.append(u'+'); + } else { + appendMultiple(sb, u'#', maxInt - minInt); + } + appendMultiple(sb, u'0', minInt); +} + +void blueprint_helpers::parseNumberingSystemOption(const StringSegment& segment, MacroProps& macros, + UErrorCode& status) { + // Need to do char <-> UChar conversion... + static constexpr int32_t CAPACITY = 30; + char buffer[CAPACITY]; + if (segment.length() + 1 > CAPACITY) { + // No support for numbers this long; they won't fit in a double anyway. + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return; + } + u_UCharsToChars(segment.toUnicodeString().getBuffer(), buffer, segment.length()); + buffer[segment.length()] = 0; + + NumberingSystem* ns = NumberingSystem::createInstanceByName(buffer, status); + if (ns == nullptr) { + // throw new SkeletonSyntaxException("Unknown numbering system", segment); + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return; + } + macros.symbols.setTo(ns); +} + +void blueprint_helpers::generateNumberingSystemOption(const NumberingSystem& ns, UnicodeString& sb, + UErrorCode& status) { + // Need to do char <-> UChar conversion... + static constexpr int32_t CAPACITY = 30; + char16_t buffer16[CAPACITY]; + const auto len = static_cast(uprv_strlen(ns.getName())); + if (len > CAPACITY) { + // No support for numbers this long; they won't fit in a double anyway. + status = U_UNSUPPORTED_ERROR; + return; + } + u_charsToUChars(ns.getName(), buffer16, len); + sb.append(buffer16, len); +} + + bool GeneratorHelpers::notation(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { if (macros.notation.fType == Notation::NTN_COMPACT) { UNumberCompactStyle style = macros.notation.fUnion.compactStyle; @@ -770,6 +1245,7 @@ bool GeneratorHelpers::perUnit(const MacroProps& macros, UnicodeString& sb, UErr // Per-units are currently expected to be only MeasureUnits. if (unitIsCurrency(macros.perUnit) || unitIsNoUnit(macros.perUnit)) { status = U_UNSUPPORTED_ERROR; + return false; } else { sb.append(u"per-measure-unit/", -1); blueprint_helpers::generateMeasureUnitOption(macros.perUnit, sb, status); @@ -824,10 +1300,12 @@ bool GeneratorHelpers::rounding(const MacroProps& macros, UnicodeString& sb, UEr bool GeneratorHelpers::grouping(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { if (macros.grouper.isBogus() || macros.grouper.fStrategy == UNUM_GROUPING_COUNT) { status = U_UNSUPPORTED_ERROR; + return false; } else if (macros.grouper.fStrategy == UNUM_GROUPING_AUTO) { return false; // Default value } else { enum_to_stem_string::groupingStrategy(macros.grouper.fStrategy, sb); + return true; } } @@ -864,7 +1342,7 @@ bool GeneratorHelpers::symbols(const MacroProps& macros, UnicodeString& sb, UErr } } -bool GeneratorHelpers::unitWidth(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { +bool GeneratorHelpers::unitWidth(const MacroProps& macros, UnicodeString& sb, UErrorCode&) { if (macros.unitWidth == UNUM_UNIT_WIDTH_SHORT || macros.unitWidth == UNUM_UNIT_WIDTH_COUNT) { return false; // Default or Bogus } @@ -872,7 +1350,7 @@ bool GeneratorHelpers::unitWidth(const MacroProps& macros, UnicodeString& sb, UE return true; } -bool GeneratorHelpers::sign(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { +bool GeneratorHelpers::sign(const MacroProps& macros, UnicodeString& sb, UErrorCode&) { if (macros.sign == UNUM_SIGN_AUTO || macros.sign == UNUM_SIGN_COUNT) { return false; // Default or Bogus } @@ -880,7 +1358,7 @@ bool GeneratorHelpers::sign(const MacroProps& macros, UnicodeString& sb, UErrorC return true; } -bool GeneratorHelpers::decimal(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { +bool GeneratorHelpers::decimal(const MacroProps& macros, UnicodeString& sb, UErrorCode&) { if (macros.decimal == UNUM_DECIMAL_SEPARATOR_AUTO || macros.decimal == UNUM_DECIMAL_SEPARATOR_COUNT) { return false; // Default or Bogus } diff --git a/icu4c/source/i18n/number_skeletons.h b/icu4c/source/i18n/number_skeletons.h index 843f1390af..e874d6acc3 100644 --- a/icu4c/source/i18n/number_skeletons.h +++ b/icu4c/source/i18n/number_skeletons.h @@ -192,7 +192,7 @@ namespace blueprint_helpers { /** @return Whether we successfully found and parsed an exponent width option. */ bool parseExponentWidthOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status); -void generateExponentWidthOption(int32_t xyz, UnicodeString& sb, UErrorCode& status); +void generateExponentWidthOption(int32_t minExponentDigits, UnicodeString& sb, UErrorCode& status); /** @return Whether we successfully found and parsed an exponent sign option. */ bool parseExponentSignOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status); @@ -225,7 +225,7 @@ void generateIncrementOption(double increment, UnicodeString& sb, UErrorCode& st /** @return Whether we successfully found and parsed a rounding mode. */ bool parseRoundingModeOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status); -void generateRoundingModeOption(RoundingMode, UnicodeString& sb, UErrorCode& status); +void generateRoundingModeOption(RoundingMode mode, UnicodeString& sb, UErrorCode& status); void parseIntegerWidthOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status); diff --git a/icu4c/source/i18n/numparse_stringsegment.cpp b/icu4c/source/i18n/numparse_stringsegment.cpp index e3bdc68c25..aa8e62beef 100644 --- a/icu4c/source/i18n/numparse_stringsegment.cpp +++ b/icu4c/source/i18n/numparse_stringsegment.cpp @@ -61,7 +61,8 @@ UChar32 StringSegment::codePointAt(int32_t index) const { } UnicodeString StringSegment::toUnicodeString() const { - return UnicodeString(fStr, fStart, fEnd - fStart); + // Use the readonly-aliasing constructor for efficiency. + return UnicodeString(FALSE, fStr.getBuffer() + fStart, fEnd - fStart); } UChar32 StringSegment::getCodePoint() const { 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 dc831d2dd5..6b8d77c9ac 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 @@ -884,13 +884,14 @@ class NumberSkeletonImpl { } private static void generateMeasureUnitOption(MeasureUnit unit, StringBuilder sb) { - sb.append(unit.getType() + "-" + unit.getSubtype()); + sb.append(unit.getType()); + sb.append("-"); + sb.append(unit.getSubtype()); } private static void parseMeasurePerUnitOption(StringSegment segment, MacroProps macros) { // A little bit of a hack: safe the current unit (numerator), call the main measure unit - // parsing - // code, put back the numerator unit, and put the new unit into per-unit. + // parsing code, put back the numerator unit, and put the new unit into per-unit. MeasureUnit numerator = macros.unit; parseMeasureUnitOption(segment, macros); macros.perUnit = macros.unit; @@ -1083,8 +1084,7 @@ class NumberSkeletonImpl { } private static void generateRoundingModeOption(RoundingMode mode, StringBuilder sb) { - String option = ROUNDING_MODE_STRINGS[mode.ordinal()]; - sb.append(option); + sb.append(ROUNDING_MODE_STRINGS[mode.ordinal()]); } private static void parseIntegerWidthOption(StringSegment segment, MacroProps macros) { From 4c07b01a461ddf8eae53b21a63319b6d1279a1c4 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Sat, 24 Mar 2018 05:41:10 +0000 Subject: [PATCH 059/129] ICU-13661 Adding "scope" option to IcuTestErrorCode. X-SVN-Rev: 41151 --- icu4c/source/tools/ctestfw/tstdtmod.cpp | 20 ++++++++++++++++++++ icu4c/source/tools/ctestfw/unicode/testlog.h | 10 ++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/icu4c/source/tools/ctestfw/tstdtmod.cpp b/icu4c/source/tools/ctestfw/tstdtmod.cpp index bb1008da6a..260987a537 100644 --- a/icu4c/source/tools/ctestfw/tstdtmod.cpp +++ b/icu4c/source/tools/ctestfw/tstdtmod.cpp @@ -13,6 +13,8 @@ #include "unicode/tstdtmod.h" #include "cmemory.h" #include +#include "cstr.h" +#include "cstring.h" TestLog::~TestLog() {} @@ -59,11 +61,29 @@ UBool IcuTestErrorCode::logDataIfFailureAndReset(const char *fmt, ...) { } } +void IcuTestErrorCode::setScope(const char* message) { + scopeMessage = message; +} + +static char kScopeMessageBuf[256]; + +void IcuTestErrorCode::setScope(const UnicodeString& message) { + CStr cstr(message); + const char* str = cstr(); + uprv_strncpy(kScopeMessageBuf, str, 256); + kScopeMessageBuf[255] = 0; // ensure NUL-terminated + scopeMessage = kScopeMessageBuf; +} + void IcuTestErrorCode::handleFailure() const { // testClass.errln("%s failure - %s", testName, errorName()); UnicodeString msg(testName, -1, US_INV); msg.append(UNICODE_STRING_SIMPLE(" failure: ")).append(UnicodeString(errorName(), -1, US_INV)); + if (scopeMessage != nullptr) { + msg.append(UNICODE_STRING_SIMPLE(" scope: ")).append(UnicodeString(scopeMessage, -1, US_INV)); + } + if (get() == U_MISSING_RESOURCE_ERROR || get() == U_FILE_ACCESS_ERROR) { testClass.dataerrln(msg); } else { diff --git a/icu4c/source/tools/ctestfw/unicode/testlog.h b/icu4c/source/tools/ctestfw/unicode/testlog.h index 811f62fba1..32edc6f0d9 100644 --- a/icu4c/source/tools/ctestfw/unicode/testlog.h +++ b/icu4c/source/tools/ctestfw/unicode/testlog.h @@ -32,17 +32,23 @@ public: class T_CTEST_EXPORT_API IcuTestErrorCode : public ErrorCode { public: - IcuTestErrorCode(TestLog &callingTestClass, const char *callingTestName) : - testClass(callingTestClass), testName(callingTestName) {} + IcuTestErrorCode(TestLog& callingTestClass, const char* callingTestName) + : testClass(callingTestClass), testName(callingTestName), scopeMessage(nullptr) {} virtual ~IcuTestErrorCode(); // Returns TRUE if isFailure(). UBool logIfFailureAndReset(const char *fmt, ...); UBool logDataIfFailureAndReset(const char *fmt, ...); + + /** Sets an additional message string to be appended to failure output. */ + void setScope(const char* message); + void setScope(const UnicodeString& message); + protected: virtual void handleFailure() const; private: TestLog &testClass; const char *const testName; + const char* scopeMessage; }; #endif From a8f2471248462cbcc71a3268e1eb05b8e925e814 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Sat, 24 Mar 2018 05:46:28 +0000 Subject: [PATCH 060/129] ICU-8610 Adding tests for number skeletons in C++. Adding error code handling to the setToDecNumber setter on DecimalQuantity. Refactoring char-to-uchar conversion in skeleton implementation code. X-SVN-Rev: 41152 --- icu4c/source/i18n/fmtable.cpp | 2 +- icu4c/source/i18n/number_decimalquantity.cpp | 28 +- icu4c/source/i18n/number_decimalquantity.h | 2 +- icu4c/source/i18n/number_fluent.cpp | 13 +- icu4c/source/i18n/number_integerwidth.cpp | 15 +- icu4c/source/i18n/number_skeletons.cpp | 164 ++++++------ icu4c/source/i18n/number_skeletons.h | 2 + icu4c/source/i18n/numparse_stringsegment.cpp | 4 + icu4c/source/i18n/plurrule.cpp | 2 +- icu4c/source/i18n/unicode/numberformatter.h | 33 ++- icu4c/source/test/intltest/Makefile.in | 2 +- icu4c/source/test/intltest/numbertest.h | 18 ++ .../test/intltest/numbertest_skeletons.cpp | 245 ++++++++++++++++++ icu4c/source/test/intltest/numfmtst.cpp | 6 +- icu4c/source/test/intltest/plurults.cpp | 2 +- .../ibm/icu/number/NumberSkeletonImpl.java | 5 +- .../dev/test/number/NumberSkeletonTest.java | 48 +++- 17 files changed, 463 insertions(+), 128 deletions(-) create mode 100644 icu4c/source/test/intltest/numbertest_skeletons.cpp diff --git a/icu4c/source/i18n/fmtable.cpp b/icu4c/source/i18n/fmtable.cpp index 051eccf33d..c65c5c2e09 100644 --- a/icu4c/source/i18n/fmtable.cpp +++ b/icu4c/source/i18n/fmtable.cpp @@ -795,7 +795,7 @@ Formattable::setDecimalNumber(StringPiece numberString, UErrorCode &status) { dispose(); DecimalQuantity* dq = new DecimalQuantity(); - dq->setToDecNumber(numberString); + dq->setToDecNumber(numberString, status); adoptDecimalQuantity(dq); // Note that we do not hang on to the caller's input string. diff --git a/icu4c/source/i18n/number_decimalquantity.cpp b/icu4c/source/i18n/number_decimalquantity.cpp index c00d1a126a..cd5df22f48 100644 --- a/icu4c/source/i18n/number_decimalquantity.cpp +++ b/icu4c/source/i18n/number_decimalquantity.cpp @@ -16,6 +16,7 @@ #include "number_roundingutils.h" #include "double-conversion.h" #include "unicode/plurrule.h" +#include "charstr.h" using namespace icu; using namespace icu::number; @@ -33,19 +34,29 @@ static constexpr int32_t DEFAULT_DIGITS = 34; typedef MaybeStackHeaderAndArray DecNumberWithStorage; /** Helper function to convert a decNumber-compatible string into a decNumber. */ -void stringToDecNumber(StringPiece n, DecNumberWithStorage &dn) { +void stringToDecNumber(StringPiece n, DecNumberWithStorage &dn, UErrorCode& status) { decContext set; uprv_decContextDefault(&set, DEC_INIT_BASE); uprv_decContextSetRounding(&set, DEC_ROUND_HALF_EVEN); - set.traps = 0; // no traps, thank you + set.traps = 0; // no traps, thank you (what does this mean?) if (n.length() > DEFAULT_DIGITS) { dn.resize(n.length(), 0); set.digits = n.length(); } else { set.digits = DEFAULT_DIGITS; } - uprv_decNumberFromString(dn.getAlias(), n.data(), &set); - U_ASSERT(DECDPUN == 1); + + // Make sure that the string is NUL-terminated; CharString guarantees this, but not StringPiece. + CharString cs(n, status); + if (U_FAILURE(status)) { return; } + + static_assert(DECDPUN == 1, "Assumes that DECDPUN is set to 1"); + uprv_decNumberFromString(dn.getAlias(), cs.data(), &set); + + // Check for invalid syntax and set the corresponding error code. + if ((set.status & DEC_Conversion_syntax) != 0) { + status = U_DECIMAL_NUMBER_SYNTAX_ERROR; + } } /** Helper function for safe subtraction (no overflow). */ @@ -329,7 +340,9 @@ void DecimalQuantity::_setToLong(int64_t n) { if (n == INT64_MIN) { static const char *int64minStr = "9.223372036854775808E+18"; DecNumberWithStorage dn; - stringToDecNumber(int64minStr, dn); + UErrorCode localStatus = U_ZERO_ERROR; + stringToDecNumber(int64minStr, dn, localStatus); + if (U_FAILURE(localStatus)) { return; } // unexpected readDecNumberToBcd(dn.getAlias()); } else if (n <= INT32_MAX) { readIntToBcd(static_cast(n)); @@ -429,12 +442,13 @@ void DecimalQuantity::convertToAccurateDouble() { explicitExactDouble = true; } -DecimalQuantity &DecimalQuantity::setToDecNumber(StringPiece n) { +DecimalQuantity &DecimalQuantity::setToDecNumber(StringPiece n, UErrorCode& status) { setBcdToZero(); flags = 0; DecNumberWithStorage dn; - stringToDecNumber(n, dn); + stringToDecNumber(n, dn, status); + if (U_FAILURE(status)) { return *this; } // The code path for decNumber is modeled after BigDecimal in Java. if (decNumberIsNegative(dn.getAlias())) { diff --git a/icu4c/source/i18n/number_decimalquantity.h b/icu4c/source/i18n/number_decimalquantity.h index 495ba80ec1..10f2e669b8 100644 --- a/icu4c/source/i18n/number_decimalquantity.h +++ b/icu4c/source/i18n/number_decimalquantity.h @@ -145,7 +145,7 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { /** decNumber is similar to BigDecimal in Java. */ - DecimalQuantity &setToDecNumber(StringPiece n); + DecimalQuantity &setToDecNumber(StringPiece n, UErrorCode& status); /** * Appends a digit, optionally with one or more leading zeros, to the end of the value represented diff --git a/icu4c/source/i18n/number_fluent.cpp b/icu4c/source/i18n/number_fluent.cpp index 372e6f18d8..e0ba258e3c 100644 --- a/icu4c/source/i18n/number_fluent.cpp +++ b/icu4c/source/i18n/number_fluent.cpp @@ -11,6 +11,7 @@ #include "number_decimalquantity.h" #include "number_formatimpl.h" #include "umutex.h" +#include "number_skeletons.h" using namespace icu; using namespace icu::number; @@ -287,6 +288,11 @@ Derived NumberFormatterSettings::macros(impl::MacroProps&& macros) && { return move; } +template +UnicodeString NumberFormatterSettings::toSkeleton(UErrorCode& status) const { + return skeleton::generate(fMacros, status); +} + // Declare all classes that implement NumberFormatterSettings // See https://stackoverflow.com/a/495056/1407170 template @@ -304,6 +310,11 @@ LocalizedNumberFormatter NumberFormatter::withLocale(const Locale& locale) { return with().locale(locale); } +UnlocalizedNumberFormatter +NumberFormatter::fromSkeleton(const UnicodeString& skeleton, UErrorCode& status) { + return skeleton::create(skeleton, status); +} + template using NFS = NumberFormatterSettings; @@ -563,7 +574,7 @@ FormattedNumber LocalizedNumberFormatter::formatDecimal(StringPiece value, UErro status = U_MEMORY_ALLOCATION_ERROR; return FormattedNumber(status); } - results->quantity.setToDecNumber(value); + results->quantity.setToDecNumber(value, status); return formatImpl(results, status); } diff --git a/icu4c/source/i18n/number_integerwidth.cpp b/icu4c/source/i18n/number_integerwidth.cpp index 464c2230ff..87e543622c 100644 --- a/icu4c/source/i18n/number_integerwidth.cpp +++ b/icu4c/source/i18n/number_integerwidth.cpp @@ -39,7 +39,7 @@ IntegerWidth IntegerWidth::truncateAt(int32_t maxInt) { } } -void IntegerWidth::apply(impl::DecimalQuantity &quantity, UErrorCode &status) const { +void IntegerWidth::apply(impl::DecimalQuantity& quantity, UErrorCode& status) const { if (fHasError) { status = U_ILLEGAL_ARGUMENT_ERROR; } else if (fUnion.minMaxInt.fMaxInt == -1) { @@ -50,12 +50,13 @@ void IntegerWidth::apply(impl::DecimalQuantity &quantity, UErrorCode &status) co } bool IntegerWidth::operator==(const IntegerWidth& other) const { - if (fHasError) { - return other.fHasError && fUnion.errorCode == other.fUnion.errorCode; - } else { - return !other.fHasError && fUnion.minMaxInt.fMinInt == other.fUnion.minMaxInt.fMinInt && - fUnion.minMaxInt.fMaxInt == other.fUnion.minMaxInt.fMaxInt; - } + // Private operator==; do error and bogus checking first! + U_ASSERT(!fHasError); + U_ASSERT(!other.fHasError); + U_ASSERT(!isBogus()); + U_ASSERT(!other.isBogus()); + return fUnion.minMaxInt.fMinInt == other.fUnion.minMaxInt.fMinInt && + fUnion.minMaxInt.fMaxInt == other.fUnion.minMaxInt.fMaxInt; } #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/number_skeletons.cpp b/icu4c/source/i18n/number_skeletons.cpp index 3efae97f20..357c443455 100644 --- a/icu4c/source/i18n/number_skeletons.cpp +++ b/icu4c/source/i18n/number_skeletons.cpp @@ -17,14 +17,14 @@ #include "number_utils.h" #include "number_decimalquantity.h" #include "unicode/numberformatter.h" +#include "uinvchar.h" +#include "charstr.h" using namespace icu; using namespace icu::number; using namespace icu::number::impl; using namespace icu::number::impl::skeleton; -static constexpr UErrorCode U_NUMBER_SKELETON_SYNTAX_ERROR = U_ILLEGAL_ARGUMENT_ERROR; - namespace { icu::UInitOnce gNumberSkeletonsInitOnce = U_INITONCE_INITIALIZER; @@ -107,7 +107,7 @@ inline void appendMultiple(UnicodeString& sb, UChar32 cp, int32_t count) { } -#define CHECK_NULL(seen, field, status) (void)(seen); /* for auto-format line wraping */ \ +#define CHECK_NULL(seen, field, status) (void)(seen); /* for auto-format line wrapping */ \ { \ if ((seen).field) { \ (status) = U_NUMBER_SKELETON_SYNTAX_ERROR; \ @@ -117,8 +117,24 @@ inline void appendMultiple(UnicodeString& sb, UChar32 cp, int32_t count) { } +#define SKELETON_UCHAR_TO_CHAR(dest, src, start, end, status) (void)(dest); \ +{ \ + UErrorCode conversionStatus = U_ZERO_ERROR; \ + (dest).appendInvariantChars({FALSE, (src).getBuffer() + (start), (end) - (start)}, conversionStatus); \ + if (conversionStatus == U_INVARIANT_CONVERSION_ERROR) { \ + /* Don't propagate the invariant conversion error; it is a skeleton syntax error */ \ + (status) = U_NUMBER_SKELETON_SYNTAX_ERROR; \ + return; \ + } else if (U_FAILURE(conversionStatus)) { \ + (status) = conversionStatus; \ + return; \ + } \ +} + + +// NOTE: The order of these strings must be consistent with UNumberFormatRoundingMode const char16_t* const kRoundingModeStrings[] = { - u"up", u"down", u"ceiling", u"floor", u"half-up", u"half-down", u"half-even", u"unnecessary"}; + u"ceiling", u"floor", u"down", u"up", u"half-even", u"half-down", u"half-up", u"unnecessary"}; constexpr int32_t kRoundingModeCount = 8; static_assert( @@ -357,14 +373,14 @@ MacroProps skeleton::parseSkeleton(const UnicodeString& skeletonString, UErrorCo SeenMacroProps seen; MacroProps macros; - StringSegment segment(skeletonString, false); + StringSegment segment(tempSkeletonString, false); UCharsTrie stemTrie(kSerializedStemTrie); ParseState stem = STATE_NULL; - int offset = 0; + int32_t offset = 0; // Primary skeleton parse loop: while (offset < segment.length()) { - int cp = segment.codePointAt(offset); + UChar32 cp = segment.codePointAt(offset); bool isTokenSeparator = PatternProps::isWhiteSpace(cp); bool isOptionSeparator = (cp == u'/'); @@ -772,21 +788,17 @@ blueprint_helpers::parseExponentSignOption(const StringSegment& segment, MacroPr void blueprint_helpers::parseCurrencyOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status) { - if (segment.length() != 3) { - // throw new SkeletonSyntaxException("Invalid currency", segment); - status = U_NUMBER_SKELETON_SYNTAX_ERROR; - return; - } const UChar* currencyCode = segment.toUnicodeString().getTerminatedBuffer(); - // Check that the currency code is valid: - int32_t numericCode = ucurr_getNumericCode(currencyCode); - if (numericCode == 0) { + UErrorCode localStatus = U_ZERO_ERROR; + CurrencyUnit currency(currencyCode, localStatus); + if (U_FAILURE(localStatus)) { + // Not 3 ascii chars // throw new SkeletonSyntaxException("Invalid currency", segment); status = U_NUMBER_SKELETON_SYNTAX_ERROR; return; } // Slicing is OK - macros.unit = CurrencyUnit(currencyCode, status); // NOLINT + macros.unit = currency; // NOLINT } void @@ -796,48 +808,40 @@ blueprint_helpers::generateCurrencyOption(const CurrencyUnit& currency, UnicodeS void blueprint_helpers::parseMeasureUnitOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status) { + UnicodeString stemString = segment.toUnicodeString(); + // NOTE: The category (type) of the unit is guaranteed to be a valid subtag (alphanumeric) // http://unicode.org/reports/tr35/#Validity_Data int firstHyphen = 0; - while (firstHyphen < segment.length() && segment.charAt(firstHyphen) != '-') { + while (firstHyphen < stemString.length() && stemString.charAt(firstHyphen) != '-') { firstHyphen++; } - if (firstHyphen == segment.length()) { + if (firstHyphen == stemString.length()) { // throw new SkeletonSyntaxException("Invalid measure unit option", segment); status = U_NUMBER_SKELETON_SYNTAX_ERROR; return; } - // MeasureUnit is in char space; we need to convert. - // Note: the longest type/subtype as of this writing (March 2018) is 24 chars. - static constexpr int32_t CAPACITY = 30; - char type[CAPACITY]; - char subType[CAPACITY]; - const int32_t typeLen = firstHyphen; - const int32_t subTypeLen = segment.length() - firstHyphen - 1; - if (typeLen + 1 > CAPACITY || subTypeLen + 1 > CAPACITY) { - // Type or subtype longer than 30? - // The capacity should be increased if this is a problem with a real CLDR unit. - status = U_NUMBER_SKELETON_SYNTAX_ERROR; - return; - } - u_UCharsToChars(segment.toUnicodeString().getBuffer(), type, typeLen); - u_UCharsToChars(segment.toUnicodeString().getBuffer() + firstHyphen + 1, subType, subTypeLen); - type[typeLen] = 0; - subType[subTypeLen] = 0; + // Need to do char <-> UChar conversion... + if (U_FAILURE(status)) { return; } + CharString type; + SKELETON_UCHAR_TO_CHAR(type, stemString, 0, firstHyphen, status); + CharString subType; + SKELETON_UCHAR_TO_CHAR(subType, stemString, firstHyphen + 1, stemString.length(), status); // Note: the largest type as of this writing (March 2018) is "volume", which has 24 units. - MeasureUnit units[30]; + static constexpr int32_t CAPACITY = 30; + MeasureUnit units[CAPACITY]; UErrorCode localStatus = U_ZERO_ERROR; - int32_t numUnits = MeasureUnit::getAvailable(type, units, 30, localStatus); + int32_t numUnits = MeasureUnit::getAvailable(type.data(), units, CAPACITY, localStatus); if (U_FAILURE(localStatus)) { // More than 30 units in this type? - status = U_NUMBER_SKELETON_SYNTAX_ERROR; + status = U_INTERNAL_PROGRAM_ERROR; return; } for (int32_t i = 0; i < numUnits; i++) { auto& unit = units[i]; - if (uprv_strcmp(subType, unit.getSubtype()) == 0) { + if (uprv_strcmp(subType.data(), unit.getSubtype()) == 0) { macros.unit = unit; return; } @@ -848,26 +852,11 @@ void blueprint_helpers::parseMeasureUnitOption(const StringSegment& segment, Mac } void blueprint_helpers::generateMeasureUnitOption(const MeasureUnit& measureUnit, UnicodeString& sb, - UErrorCode& status) { - // We need to convert from char* to UChar*... - // See comments in the previous function about the capacity setting. - static constexpr int32_t CAPACITY = 30; - char16_t type16[CAPACITY]; - char16_t subType16[CAPACITY]; - const auto typeLen = static_cast(uprv_strlen(measureUnit.getType())); - const auto subTypeLen = static_cast(uprv_strlen(measureUnit.getSubtype())); - if (typeLen + 1 > CAPACITY || subTypeLen + 1 > CAPACITY) { - // Type or subtype longer than 30? - // The capacity should be increased if this is a problem with a real CLDR unit. - status = U_UNSUPPORTED_ERROR; - return; - } - u_charsToUChars(measureUnit.getType(), type16, typeLen); - u_charsToUChars(measureUnit.getSubtype(), subType16, subTypeLen); - - sb.append(type16, typeLen); + UErrorCode&) { + // Need to do char <-> UChar conversion... + sb.append(UnicodeString(measureUnit.getType(), -1, US_INV)); sb.append(u'-'); - sb.append(subType16, subTypeLen); + sb.append(UnicodeString(measureUnit.getSubtype(), -1, US_INV)); } void blueprint_helpers::parseMeasurePerUnitOption(const StringSegment& segment, MacroProps& macros, @@ -1052,17 +1041,19 @@ bool blueprint_helpers::parseFracSigOption(const StringSegment& segment, MacroPr void blueprint_helpers::parseIncrementOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status) { + // Need to do char <-> UChar conversion... + CharString buffer; + SKELETON_UCHAR_TO_CHAR(buffer, segment.toUnicodeString(), 0, segment.length(), status); + // Utilize DecimalQuantity/decNumber to parse this for us. - static constexpr int32_t CAPACITY = 30; - char buffer[CAPACITY]; - if (segment.length() > CAPACITY) { - // No support for numbers this long; they won't fit in a double anyway. + DecimalQuantity dq; + UErrorCode localStatus = U_ZERO_ERROR; + dq.setToDecNumber({buffer.data(), buffer.length()}, localStatus); + if (U_FAILURE(localStatus)) { + // throw new SkeletonSyntaxException("Invalid rounding increment", segment, e); status = U_NUMBER_SKELETON_SYNTAX_ERROR; return; } - u_UCharsToChars(segment.toUnicodeString().getBuffer(), buffer, segment.length()); - DecimalQuantity dq; - dq.setToDecNumber({buffer, segment.length()}); double increment = dq.toDouble(); macros.rounder = Rounder::increment(increment); } @@ -1146,17 +1137,10 @@ void blueprint_helpers::generateIntegerWidthOption(int32_t minInt, int32_t maxIn void blueprint_helpers::parseNumberingSystemOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status) { // Need to do char <-> UChar conversion... - static constexpr int32_t CAPACITY = 30; - char buffer[CAPACITY]; - if (segment.length() + 1 > CAPACITY) { - // No support for numbers this long; they won't fit in a double anyway. - status = U_NUMBER_SKELETON_SYNTAX_ERROR; - return; - } - u_UCharsToChars(segment.toUnicodeString().getBuffer(), buffer, segment.length()); - buffer[segment.length()] = 0; + CharString buffer; + SKELETON_UCHAR_TO_CHAR(buffer, segment.toUnicodeString(), 0, segment.length(), status); - NumberingSystem* ns = NumberingSystem::createInstanceByName(buffer, status); + NumberingSystem* ns = NumberingSystem::createInstanceByName(buffer.data(), status); if (ns == nullptr) { // throw new SkeletonSyntaxException("Unknown numbering system", segment); status = U_NUMBER_SKELETON_SYNTAX_ERROR; @@ -1166,18 +1150,9 @@ void blueprint_helpers::parseNumberingSystemOption(const StringSegment& segment, } void blueprint_helpers::generateNumberingSystemOption(const NumberingSystem& ns, UnicodeString& sb, - UErrorCode& status) { + UErrorCode&) { // Need to do char <-> UChar conversion... - static constexpr int32_t CAPACITY = 30; - char16_t buffer16[CAPACITY]; - const auto len = static_cast(uprv_strlen(ns.getName())); - if (len > CAPACITY) { - // No support for numbers this long; they won't fit in a double anyway. - status = U_UNSUPPORTED_ERROR; - return; - } - u_charsToUChars(ns.getName(), buffer16, len); - sb.append(buffer16, len); + sb.append(UnicodeString(ns.getName(), -1, US_INV)); } @@ -1243,7 +1218,15 @@ bool GeneratorHelpers::unit(const MacroProps& macros, UnicodeString& sb, UErrorC bool GeneratorHelpers::perUnit(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { // Per-units are currently expected to be only MeasureUnits. - if (unitIsCurrency(macros.perUnit) || unitIsNoUnit(macros.perUnit)) { + if (unitIsNoUnit(macros.perUnit)) { + if (unitIsPercent(macros.perUnit) || unitIsPermille(macros.perUnit)) { + status = U_UNSUPPORTED_ERROR; + return false; + } else { + // Default value: ok to ignore + return false; + } + } else if (unitIsCurrency(macros.perUnit)) { status = U_UNSUPPORTED_ERROR; return false; } else { @@ -1298,7 +1281,9 @@ bool GeneratorHelpers::rounding(const MacroProps& macros, UnicodeString& sb, UEr } bool GeneratorHelpers::grouping(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { - if (macros.grouper.isBogus() || macros.grouper.fStrategy == UNUM_GROUPING_COUNT) { + if (macros.grouper.isBogus()) { + return false; // No value + } else if (macros.grouper.fStrategy == UNUM_GROUPING_COUNT) { status = U_UNSUPPORTED_ERROR; return false; } else if (macros.grouper.fStrategy == UNUM_GROUPING_AUTO) { @@ -1310,7 +1295,8 @@ bool GeneratorHelpers::grouping(const MacroProps& macros, UnicodeString& sb, UEr } bool GeneratorHelpers::integerWidth(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { - if (macros.integerWidth.fHasError || macros.integerWidth == IntegerWidth::standard()) { + if (macros.integerWidth.fHasError || macros.integerWidth.isBogus() || + macros.integerWidth == IntegerWidth::standard()) { // Error or Default return false; } diff --git a/icu4c/source/i18n/number_skeletons.h b/icu4c/source/i18n/number_skeletons.h index e874d6acc3..6a15d9efb4 100644 --- a/icu4c/source/i18n/number_skeletons.h +++ b/icu4c/source/i18n/number_skeletons.h @@ -16,6 +16,8 @@ using icu::numparse::impl::StringSegment; U_NAMESPACE_BEGIN namespace number { namespace impl { +static constexpr UErrorCode U_NUMBER_SKELETON_SYNTAX_ERROR = U_ILLEGAL_ARGUMENT_ERROR; + // Forward-declaration struct SeenMacroProps; diff --git a/icu4c/source/i18n/numparse_stringsegment.cpp b/icu4c/source/i18n/numparse_stringsegment.cpp index aa8e62beef..0a6e4fd104 100644 --- a/icu4c/source/i18n/numparse_stringsegment.cpp +++ b/icu4c/source/i18n/numparse_stringsegment.cpp @@ -133,5 +133,9 @@ bool StringSegment::codePointsEqual(UChar32 cp1, UChar32 cp2, bool foldCase) { return cp1 == cp2; } +bool StringSegment::operator==(const UnicodeString& other) const { + return toUnicodeString() == other; +} + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/plurrule.cpp b/icu4c/source/i18n/plurrule.cpp index eb77905b0c..1b0089f9c9 100644 --- a/icu4c/source/i18n/plurrule.cpp +++ b/icu4c/source/i18n/plurrule.cpp @@ -1458,7 +1458,7 @@ FixedDecimal::FixedDecimal(const UnicodeString &num, UErrorCode &status) { CharString cs; cs.appendInvariantChars(num, status); DecimalQuantity dl; - dl.setToDecNumber(cs.toStringPiece()); + dl.setToDecNumber(cs.toStringPiece(), status); if (U_FAILURE(status)) { init(0, 0, 0); return; diff --git a/icu4c/source/i18n/unicode/numberformatter.h b/icu4c/source/i18n/unicode/numberformatter.h index fdc6bdc285..c0131eaa71 100644 --- a/icu4c/source/i18n/unicode/numberformatter.h +++ b/icu4c/source/i18n/unicode/numberformatter.h @@ -2179,6 +2179,25 @@ class U_I18N_API NumberFormatterSettings { #endif /* U_HIDE_INTERNAL_API */ + /** + * Creates a skeleton string representation of this number formatter. A skeleton string is a + * locale-agnostic serialized form of a number formatter. + *

    + * Not all options are capable of being represented in the skeleton string; for example, a + * DecimalFormatSymbols object. If any such option is encountered, an + * {@link UnsupportedOperationException} is thrown. + *

    + * The returned skeleton is in normalized form, such that two number formatters with equivalent + * behavior should produce the same skeleton. + *

    + * Sets an error code if the number formatter has an option that cannot be represented in a skeleton + * string. + * + * @return A number skeleton string with behavior corresponding to this number formatter. + * @draft ICU 62 + */ + UnicodeString toSkeleton(UErrorCode& status) const; + /** * Sets the UErrorCode if an error occurred in the fluent chain. * Preserves older error codes in the outErrorCode. @@ -2192,7 +2211,7 @@ class U_I18N_API NumberFormatterSettings { } fMacros.copyErrorTo(outErrorCode); return U_FAILURE(outErrorCode); - } + }; // NOTE: Uses default copy and move constructors. @@ -2588,6 +2607,18 @@ class U_I18N_API NumberFormatter final { */ static LocalizedNumberFormatter withLocale(const Locale &locale); + /** + * Call this method at the beginning of a NumberFormatter fluent chain to create an instance based + * on a given number skeleton string. + * + * @param skeleton + * The skeleton string off of which to base this NumberFormatter. + * @return An UnlocalizedNumberFormatter, to be used for chaining. + * @throws SkeletonSyntaxException If the given string is not a valid number formatting skeleton. + * @draft ICU 62 + */ + static UnlocalizedNumberFormatter fromSkeleton(const UnicodeString& skeleton, UErrorCode& status); + /** * Use factory methods instead of the constructor to create a NumberFormatter. * @draft ICU 60 diff --git a/icu4c/source/test/intltest/Makefile.in b/icu4c/source/test/intltest/Makefile.in index 6699d319fe..55d1fb2142 100644 --- a/icu4c/source/test/intltest/Makefile.in +++ b/icu4c/source/test/intltest/Makefile.in @@ -65,7 +65,7 @@ numberformattesttuple.o pluralmaptest.o \ numbertest_affixutils.o numbertest_api.o numbertest_decimalquantity.o \ numbertest_modifiers.o numbertest_patternmodifier.o numbertest_patternstring.o \ numbertest_stringbuilder.o numbertest_stringsegment.o numbertest_unisets.o \ -numbertest_parse.o numbertest_doubleconversion.o +numbertest_parse.o numbertest_doubleconversion.o numbertest_skeletons.o DEPS = $(OBJECTS:.o=.d) diff --git a/icu4c/source/test/intltest/numbertest.h b/icu4c/source/test/intltest/numbertest.h index 410d9ba316..595a954e28 100644 --- a/icu4c/source/test/intltest/numbertest.h +++ b/icu4c/source/test/intltest/numbertest.h @@ -229,6 +229,23 @@ class NumberParserTest : public IntlTest { void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0); }; +class NumberSkeletonTest : public IntlTest { + public: + void validTokens(); + void invalidTokens(); + void unknownTokens(); + void unexpectedTokens(); + void duplicateValues(); + void stemsRequiringOption(); + void defaultTokens(); + void flexibleSeparators(); + + void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0); + + private: + void expectedErrorSkeleton(const char16_t** cases, int32_t casesLen); +}; + // NOTE: This macro is identical to the one in itformat.cpp #define TESTCLASS(id, TestClass) \ @@ -261,6 +278,7 @@ class NumberTest : public IntlTest { TESTCLASS(8, StringSegmentTest); TESTCLASS(9, UniSetsTest); TESTCLASS(10, NumberParserTest); + TESTCLASS(11, NumberSkeletonTest); default: name = ""; break; // needed to end loop } } diff --git a/icu4c/source/test/intltest/numbertest_skeletons.cpp b/icu4c/source/test/intltest/numbertest_skeletons.cpp new file mode 100644 index 0000000000..80196f9fc8 --- /dev/null +++ b/icu4c/source/test/intltest/numbertest_skeletons.cpp @@ -0,0 +1,245 @@ +// © 2017 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT + +#include "putilimp.h" +#include "unicode/dcfmtsym.h" +#include "numbertest.h" +#include "number_utils.h" +#include "number_skeletons.h" + +using namespace icu::number::impl; + + +void NumberSkeletonTest::runIndexedTest(int32_t index, UBool exec, const char*& name, char*) { + if (exec) { + logln("TestSuite AffixUtilsTest: "); + } + TESTCASE_AUTO_BEGIN; + TESTCASE_AUTO(validTokens); + TESTCASE_AUTO(invalidTokens); + TESTCASE_AUTO(unknownTokens); + TESTCASE_AUTO(unexpectedTokens); + TESTCASE_AUTO(duplicateValues); + TESTCASE_AUTO(stemsRequiringOption); + TESTCASE_AUTO(defaultTokens); + TESTCASE_AUTO(flexibleSeparators); + TESTCASE_AUTO_END; +} + +void NumberSkeletonTest::validTokens() { + // This tests only if the tokens are valid, not their behavior. + // Most of these are from the design doc. + static const char16_t* cases[] = { + u"round-integer", + u"round-unlimited", + u"@@@##", + u"@@+", + u".000##", + u".00+", + u".", + u".+", + u".######", + u".00/@@+", + u".00/@##", + u"round-increment/3.14", + u"round-currency-standard", + u"round-integer/half-up", + u".00#/ceiling", + u".00/@@+/floor", + u"scientific", + u"scientific/+ee", + u"scientific/sign-always", + u"scientific/+ee/sign-always", + u"scientific/sign-always/+ee", + u"scientific/sign-except-zero", + u"engineering", + u"engineering/+eee", + u"compact-short", + u"compact-long", + u"notation-simple", + u"percent", + u"permille", + u"measure-unit/length-meter", + u"measure-unit/area-square-meter", + u"measure-unit/energy-joule per-measure-unit/length-meter", + u"currency/XXX", + u"currency/ZZZ", + u"group-off", + u"group-min2", + u"group-auto", + u"group-on-aligned", + u"group-thousands", + u"integer-width/00", + u"integer-width/#0", + u"integer-width/+00", + u"sign-always", + u"sign-auto", + u"sign-never", + u"sign-accounting", + u"sign-accounting-always", + u"sign-except-zero", + u"sign-accounting-except-zero", + u"unit-width-narrow", + u"unit-width-short", + u"unit-width-iso-code", + u"unit-width-full-name", + u"unit-width-hidden", + u"decimal-auto", + u"decimal-always", + u"latin", + u"numbering-system/arab", + u"numbering-system/latn", + u"round-integer/@##", + u"round-integer/ceiling", + u"round-currency-cash/ceiling"}; + + for (auto& cas : cases) { + UnicodeString skeletonString(cas); + UErrorCode status = U_ZERO_ERROR; + NumberFormatter::fromSkeleton(skeletonString, status); + assertSuccess(skeletonString, status); + } +} + +void NumberSkeletonTest::invalidTokens() { + static const char16_t* cases[] = { + u".00x", + u".00##0", + u".##+", + u".00##+", + u".0#+", + u"@@x", + u"@@##0", + u"@#+", + u".00/@", + u".00/@@", + u".00/@@x", + u".00/@@#", + u".00/@@#+", + u".00/floor/@@+", // wrong order + u"round-currency-cash/XXX", + u"scientific/ee", + u"round-increment/xxx", + u"round-increment/0.1.2", + u"currency/dummy", + u"measure-unit/foo", + u"integer-width/xxx", + u"integer-width/0+", + u"integer-width/+0#", + u"scientific/foo"}; + + expectedErrorSkeleton(cases, sizeof(cases) / sizeof(*cases)); +} + +void NumberSkeletonTest::unknownTokens() { + static const char16_t* cases[] = { + u"maesure-unit", + u"measure-unit/foo-bar", + u"numbering-system/dummy", + u"français", + u"measure-unit/français-français", // non-invariant characters for C++ + u"numbering-system/français", // non-invariant characters for C++ + u"round-increment/français", // non-invariant characters for C++ + u"currency-USD"}; + + expectedErrorSkeleton(cases, sizeof(cases) / sizeof(*cases)); +} + +void NumberSkeletonTest::unexpectedTokens() { + static const char16_t* cases[] = { + u"group-thousands/foo", + u"round-integer//ceiling group-off", + u"round-integer//ceiling group-off", + u"round-integer/ group-off", + u"round-integer// group-off"}; + + expectedErrorSkeleton(cases, sizeof(cases) / sizeof(*cases)); +} + +void NumberSkeletonTest::duplicateValues() { + static const char16_t* cases[] = { + u"round-integer round-integer", + u"round-integer .00+", + u"round-integer round-unlimited", + u"round-integer @@@", + u"scientific engineering", + u"engineering compact-long", + u"sign-auto sign-always"}; + + expectedErrorSkeleton(cases, sizeof(cases) / sizeof(*cases)); +} + +void NumberSkeletonTest::stemsRequiringOption() { + static const char16_t* stems[] = {u"round-increment", u"currency", u"measure-unit", u"integer-width",}; + static const char16_t* suffixes[] = {u"", u"/ceiling", u" scientific", u"/ceiling scientific"}; + + for (auto& stem : stems) { + for (auto& suffix : suffixes) { + UnicodeString skeletonString = UnicodeString(stem) + suffix; + UErrorCode status = U_ZERO_ERROR; + NumberFormatter::fromSkeleton(skeletonString, status); + assertEquals(skeletonString, U_NUMBER_SKELETON_SYNTAX_ERROR, status); + } + } +} + +void NumberSkeletonTest::defaultTokens() { + IcuTestErrorCode status(*this, "defaultTokens"); + + static const char16_t* cases[] = { + u"notation-simple", + u"base-unit", + u"group-auto", + u"integer-width/+0", + u"sign-auto", + u"unit-width-short", + u"decimal-auto"}; + + for (auto& cas : cases) { + UnicodeString skeletonString(cas); + status.setScope(skeletonString); + UnicodeString normalized = NumberFormatter::fromSkeleton( + skeletonString, status).toSkeleton(status); + // Skeleton should become empty when normalized + assertEquals(skeletonString, u"", normalized); + } +} + +void NumberSkeletonTest::flexibleSeparators() { + IcuTestErrorCode status(*this, "flexibleSeparators"); + + static struct TestCase { + const char16_t* skeleton; + const char16_t* expected; + } cases[] = {{u"round-integer group-off", u"5142"}, + {u"round-integer group-off", u"5142"}, + {u"round-integer/ceiling group-off", u"5143"}, + {u"round-integer/ceiling group-off", u"5143"}}; + + for (auto& cas : cases) { + UnicodeString skeletonString(cas.skeleton); + UnicodeString expected(cas.expected); + status.setScope(skeletonString); + UnicodeString actual = NumberFormatter::fromSkeleton(skeletonString, status).locale("en") + .formatDouble(5142.3, status) + .toString(); + assertEquals(skeletonString, expected, actual); + } +} + +// In C++, there is no distinguishing between "invalid", "unknown", and "unexpected" tokens. +void NumberSkeletonTest::expectedErrorSkeleton(const char16_t** cases, int32_t casesLen) { + for (int32_t i = 0; i < casesLen; i++) { + UnicodeString skeletonString(cases[i]); + UErrorCode status = U_ZERO_ERROR; + NumberFormatter::fromSkeleton(skeletonString, status); + assertEquals(skeletonString, U_NUMBER_SKELETON_SYNTAX_ERROR, status); + } +} + + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/test/intltest/numfmtst.cpp b/icu4c/source/test/intltest/numfmtst.cpp index 35825b62cf..3487d432f7 100644 --- a/icu4c/source/test/intltest/numfmtst.cpp +++ b/icu4c/source/test/intltest/numfmtst.cpp @@ -105,7 +105,7 @@ static DecimalQuantity &strToDigitList( } CharString formatValue; formatValue.appendInvariantChars(str, status); - digitList.setToDecNumber(StringPiece(formatValue.data())); + digitList.setToDecNumber(StringPiece(formatValue.data()), status); return digitList; } @@ -7027,7 +7027,7 @@ void NumberFormatTest::TestDecimal() { UnicodeString formattedResult; DecimalQuantity dl; StringPiece num("123.4566666666666666666666666666666666621E+40"); - dl.setToDecNumber(num); + dl.setToDecNumber(num, status); ASSERT_SUCCESS(status); fmtr->format(dl, formattedResult, NULL, status); ASSERT_SUCCESS(status); @@ -7035,7 +7035,7 @@ void NumberFormatTest::TestDecimal() { status = U_ZERO_ERROR; num.set("666.666"); - dl.setToDecNumber(num); + dl.setToDecNumber(num, status); FieldPosition pos(NumberFormat::FRACTION_FIELD); ASSERT_SUCCESS(status); formattedResult.remove(); diff --git a/icu4c/source/test/intltest/plurults.cpp b/icu4c/source/test/intltest/plurults.cpp index 5722126303..c6b1a046a0 100644 --- a/icu4c/source/test/intltest/plurults.cpp +++ b/icu4c/source/test/intltest/plurults.cpp @@ -636,7 +636,7 @@ void PluralRulesTest::checkSelect(const LocalPointer &rules, UError // DigitList is a convenient way to parse the decimal number string and get a double. DecimalQuantity dl; - dl.setToDecNumber(StringPiece(num)); + dl.setToDecNumber(StringPiece(num), status); if (U_FAILURE(status)) { errln("file %s, line %d, ICU error status: %s.", __FILE__, line, u_errorName(status)); status = U_ZERO_ERROR; 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 6b8d77c9ac..e6138e280d 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 @@ -850,11 +850,14 @@ class NumberSkeletonImpl { private static void parseCurrencyOption(StringSegment segment, MacroProps macros) { String currencyCode = segment.subSequence(0, segment.length()).toString(); + Currency currency; try { - macros.unit = Currency.getInstance(currencyCode); + currency = Currency.getInstance(currencyCode); } catch (IllegalArgumentException e) { + // Not 3 ascii chars throw new SkeletonSyntaxException("Invalid currency", segment, e); } + macros.unit = currency; } private static void generateCurrencyOption(Currency currency, StringBuilder sb) { 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 4d5509f4f1..37815f6915 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 @@ -21,16 +21,6 @@ import com.ibm.icu.util.ULocale; */ public class NumberSkeletonTest { - @Test - public void duplicateValues() { - try { - NumberFormatter.fromSkeleton("round-integer round-integer"); - fail(); - } catch (SkeletonSyntaxException expected) { - assertTrue(expected.getMessage(), expected.getMessage().contains("Duplicated setting")); - } - } - @Test public void validTokens() { // This tests only if the tokens are valid, not their behavior. @@ -69,6 +59,7 @@ public class NumberSkeletonTest { "measure-unit/area-square-meter", "measure-unit/energy-joule per-measure-unit/length-meter", "currency/XXX", + "currency/ZZZ", "group-off", "group-min2", "group-auto", @@ -138,7 +129,7 @@ public class NumberSkeletonTest { for (String cas : cases) { try { NumberFormatter.fromSkeleton(cas); - fail("Skeleton parses, but it should have failed: " + cas); + fail(cas); } catch (SkeletonSyntaxException expected) { assertTrue(expected.getMessage(), expected.getMessage().contains("Invalid")); } @@ -147,12 +138,20 @@ public class NumberSkeletonTest { @Test public void unknownTokens() { - String[] cases = { "maesure-unit", "measure-unit/foo-bar", "numbering-system/dummy" }; + String[] cases = { + "maesure-unit", + "measure-unit/foo-bar", + "numbering-system/dummy", + "français", + "measure-unit/français-français", // non-invariant characters for C++ + "numbering-system/français", // non-invariant characters for C++ + "round-increment/français", // non-invariant characters for C++ + "currency-USD" }; for (String cas : cases) { try { NumberFormatter.fromSkeleton(cas); - fail(); + fail(cas); } catch (SkeletonSyntaxException expected) { assertTrue(expected.getMessage(), expected.getMessage().contains("Unknown")); } @@ -171,13 +170,34 @@ public class NumberSkeletonTest { for (String cas : cases) { try { NumberFormatter.fromSkeleton(cas); - fail(); + fail(cas); } catch (SkeletonSyntaxException expected) { assertTrue(expected.getMessage(), expected.getMessage().contains("Unexpected")); } } } + @Test + public void duplicateValues() { + String[] cases = { + "round-integer round-integer", + "round-integer .00+", + "round-integer round-unlimited", + "round-integer @@@", + "scientific engineering", + "engineering compact-long", + "sign-auto sign-always" }; + + for (String cas : cases) { + try { + NumberFormatter.fromSkeleton(cas); + fail(cas); + } catch (SkeletonSyntaxException expected) { + assertTrue(expected.getMessage(), expected.getMessage().contains("Duplicated")); + } + } + } + @Test public void stemsRequiringOption() { String[] stems = { "round-increment", "currency", "measure-unit", "integer-width", }; From fabc913f4725c490f8ad8226e9865d8258eafaa1 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Sat, 24 Mar 2018 07:36:18 +0000 Subject: [PATCH 061/129] ICU-8610 Adding skeletons to NumberFormatterApiTest. The test currently runs with a handful of failures that need investigation. X-SVN-Rev: 41153 --- icu4c/source/test/intltest/numbertest.h | 15 +- icu4c/source/test/intltest/numbertest_api.cpp | 349 ++++++++++++++---- 2 files changed, 287 insertions(+), 77 deletions(-) diff --git a/icu4c/source/test/intltest/numbertest.h b/icu4c/source/test/intltest/numbertest.h index 595a954e28..134665b785 100644 --- a/icu4c/source/test/intltest/numbertest.h +++ b/icu4c/source/test/intltest/numbertest.h @@ -98,14 +98,17 @@ class NumberFormatterApiTest : public IntlTest { DecimalFormatSymbols SWISS_SYMBOLS; DecimalFormatSymbols MYANMAR_SYMBOLS; - void assertFormatDescending(const UnicodeString &message, const UnlocalizedNumberFormatter &f, - Locale locale, ...); + void assertFormatDescending(const char16_t* message, const char16_t* skeleton, + const UnlocalizedNumberFormatter& f, Locale locale, ...); - void assertFormatDescendingBig(const UnicodeString &message, const UnlocalizedNumberFormatter &f, - Locale locale, ...); + void assertFormatDescendingBig(const char16_t* message, const char16_t* skeleton, + const UnlocalizedNumberFormatter& f, Locale locale, ...); - void assertFormatSingle(const UnicodeString &message, const UnlocalizedNumberFormatter &f, - Locale locale, double input, const UnicodeString &expected); + void assertFormatSingle(const char16_t* message, const char16_t* skeleton, + const UnlocalizedNumberFormatter& f, Locale locale, double input, + const UnicodeString& expected); + + void assertUndefinedSkeleton(const UnlocalizedNumberFormatter& f); }; class DecimalQuantityTest : public IntlTest { diff --git a/icu4c/source/test/intltest/numbertest_api.cpp b/icu4c/source/test/intltest/numbertest_api.cpp index e158e7a8c7..fd9872dd99 100644 --- a/icu4c/source/test/intltest/numbertest_api.cpp +++ b/icu4c/source/test/intltest/numbertest_api.cpp @@ -20,13 +20,16 @@ NumberFormatterApiTest::NumberFormatterApiTest() : NumberFormatterApiTest(globalNumberFormatterApiTestStatus) { } -NumberFormatterApiTest::NumberFormatterApiTest(UErrorCode &status) - : USD(u"USD", status), GBP(u"GBP", status), - CZK(u"CZK", status), CAD(u"CAD", status), - ESP(u"ESP", status), PTE(u"PTE", status), - FRENCH_SYMBOLS(Locale::getFrench(), status), - SWISS_SYMBOLS(Locale("de-CH"), status), - MYANMAR_SYMBOLS(Locale("my"), status) { +NumberFormatterApiTest::NumberFormatterApiTest(UErrorCode& status) + : USD(u"USD", status), + GBP(u"GBP", status), + CZK(u"CZK", status), + CAD(u"CAD", status), + ESP(u"ESP", status), + PTE(u"PTE", status), + FRENCH_SYMBOLS(Locale::getFrench(), status), + SWISS_SYMBOLS(Locale("de-CH"), status), + MYANMAR_SYMBOLS(Locale("my"), status) { // Check for error on the first MeasureUnit in case there is no data LocalPointer unit(MeasureUnit::createMeter(status)); @@ -50,7 +53,7 @@ NumberFormatterApiTest::NumberFormatterApiTest(UErrorCode &status) LATN = *LocalPointer(NumberingSystem::createInstanceByName("latn", status)); } -void NumberFormatterApiTest::runIndexedTest(int32_t index, UBool exec, const char *&name, char *) { +void NumberFormatterApiTest::runIndexedTest(int32_t index, UBool exec, const char*& name, char*) { if (exec) { logln("TestSuite NumberFormatterApiTest: "); } @@ -85,6 +88,7 @@ void NumberFormatterApiTest::runIndexedTest(int32_t index, UBool exec, const cha void NumberFormatterApiTest::notationSimple() { assertFormatDescending( u"Basic", + u"", NumberFormatter::with(), Locale::getEnglish(), u"87,650", @@ -97,8 +101,24 @@ void NumberFormatterApiTest::notationSimple() { u"0.008765", u"0"); + assertFormatDescendingBig( + u"Big Simple", + u"notation-simple", + NumberFormatter::with().notation(Notation::simple()), + Locale::getEnglish(), + u"87,650,000", + u"8,765,000", + u"876,500", + u"87,650", + u"8,765", + u"876.5", + u"87.65", + u"8.765", + u"0"); + assertFormatSingle( u"Basic with Negative Sign", + u"", NumberFormatter::with(), Locale::getEnglish(), -9876543.21, @@ -109,6 +129,7 @@ void NumberFormatterApiTest::notationSimple() { void NumberFormatterApiTest::notationScientific() { assertFormatDescending( u"Scientific", + u"scientific", NumberFormatter::with().notation(Notation::scientific()), Locale::getEnglish(), u"8.765E4", @@ -123,6 +144,7 @@ void NumberFormatterApiTest::notationScientific() { assertFormatDescending( u"Engineering", + u"engineering", NumberFormatter::with().notation(Notation::engineering()), Locale::getEnglish(), u"87.65E3", @@ -137,6 +159,7 @@ void NumberFormatterApiTest::notationScientific() { assertFormatDescending( u"Scientific sign always shown", + u"scientific/sign-always", NumberFormatter::with().notation( Notation::scientific().withExponentSignDisplay(UNumberSignDisplay::UNUM_SIGN_ALWAYS)), Locale::getEnglish(), @@ -152,6 +175,7 @@ void NumberFormatterApiTest::notationScientific() { assertFormatDescending( u"Scientific min exponent digits", + u"scientific/+ee", NumberFormatter::with().notation(Notation::scientific().withMinExponentDigits(2)), Locale::getEnglish(), u"8.765E04", @@ -166,6 +190,7 @@ void NumberFormatterApiTest::notationScientific() { assertFormatSingle( u"Scientific Negative", + u"scientific", NumberFormatter::with().notation(Notation::scientific()), Locale::getEnglish(), -1000000, @@ -175,6 +200,7 @@ void NumberFormatterApiTest::notationScientific() { void NumberFormatterApiTest::notationCompact() { assertFormatDescending( u"Compact Short", + u"compact-short", NumberFormatter::with().notation(Notation::compactShort()), Locale::getEnglish(), u"88K", @@ -189,6 +215,7 @@ void NumberFormatterApiTest::notationCompact() { assertFormatDescending( u"Compact Long", + u"compact-long", NumberFormatter::with().notation(Notation::compactLong()), Locale::getEnglish(), u"88 thousand", @@ -203,6 +230,7 @@ void NumberFormatterApiTest::notationCompact() { assertFormatDescending( u"Compact Short Currency", + u"compact-short currency/USD", NumberFormatter::with().notation(Notation::compactShort()).unit(USD), Locale::getEnglish(), u"$88K", @@ -217,6 +245,7 @@ void NumberFormatterApiTest::notationCompact() { assertFormatDescending( u"Compact Short with ISO Currency", + u"compact-short currency/USD unit-width-iso-code", NumberFormatter::with().notation(Notation::compactShort()) .unit(USD) .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_ISO_CODE), @@ -233,6 +262,7 @@ void NumberFormatterApiTest::notationCompact() { assertFormatDescending( u"Compact Short with Long Name Currency", + u"compact-short currency/USD unit-width-full-name", NumberFormatter::with().notation(Notation::compactShort()) .unit(USD) .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME), @@ -251,6 +281,7 @@ void NumberFormatterApiTest::notationCompact() { // This test case should be fixed when proper compact long currency patterns are added. assertFormatDescending( u"Compact Long Currency", + u"compact-long currency/USD", NumberFormatter::with().notation(Notation::compactLong()).unit(USD), Locale::getEnglish(), u"$88K", // should be something like "$88 thousand" @@ -267,6 +298,7 @@ void NumberFormatterApiTest::notationCompact() { // This test case should be fixed when proper compact long currency patterns are added. assertFormatDescending( u"Compact Long with ISO Currency", + u"compact-long currency/USD unit-width-iso-code", NumberFormatter::with().notation(Notation::compactLong()) .unit(USD) .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_ISO_CODE), @@ -284,6 +316,7 @@ void NumberFormatterApiTest::notationCompact() { // TODO: This behavior could be improved and should be revisited. assertFormatDescending( u"Compact Long with Long Name Currency", + u"compact-long currency/USD unit-width-full-name", NumberFormatter::with().notation(Notation::compactLong()) .unit(USD) .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME), @@ -300,6 +333,7 @@ void NumberFormatterApiTest::notationCompact() { assertFormatSingle( u"Compact Plural One", + u"compact-long", NumberFormatter::with().notation(Notation::compactLong()), Locale::createFromName("es"), 1000000, @@ -307,6 +341,7 @@ void NumberFormatterApiTest::notationCompact() { assertFormatSingle( u"Compact Plural Other", + u"compact-long", NumberFormatter::with().notation(Notation::compactLong()), Locale::createFromName("es"), 2000000, @@ -314,6 +349,7 @@ void NumberFormatterApiTest::notationCompact() { assertFormatSingle( u"Compact with Negative Sign", + u"compact-short", NumberFormatter::with().notation(Notation::compactShort()), Locale::getEnglish(), -9876543.21, @@ -321,6 +357,7 @@ void NumberFormatterApiTest::notationCompact() { assertFormatSingle( u"Compact Rounding", + u"compact-short", NumberFormatter::with().notation(Notation::compactShort()), Locale::getEnglish(), 990000, @@ -328,6 +365,7 @@ void NumberFormatterApiTest::notationCompact() { assertFormatSingle( u"Compact Rounding", + u"compact-short", NumberFormatter::with().notation(Notation::compactShort()), Locale::getEnglish(), 999000, @@ -335,6 +373,7 @@ void NumberFormatterApiTest::notationCompact() { assertFormatSingle( u"Compact Rounding", + u"compact-short", NumberFormatter::with().notation(Notation::compactShort()), Locale::getEnglish(), 999900, @@ -342,6 +381,7 @@ void NumberFormatterApiTest::notationCompact() { assertFormatSingle( u"Compact Rounding", + u"compact-short", NumberFormatter::with().notation(Notation::compactShort()), Locale::getEnglish(), 9900000, @@ -349,6 +389,7 @@ void NumberFormatterApiTest::notationCompact() { assertFormatSingle( u"Compact Rounding", + u"compact-short", NumberFormatter::with().notation(Notation::compactShort()), Locale::getEnglish(), 9990000, @@ -361,6 +402,7 @@ void NumberFormatterApiTest::notationCompact() { void NumberFormatterApiTest::unitMeasure() { assertFormatDescending( u"Meters Short and unit() method", + u"measure-unit/length-meter", NumberFormatter::with().unit(METER), Locale::getEnglish(), u"87,650 m", @@ -375,6 +417,7 @@ void NumberFormatterApiTest::unitMeasure() { assertFormatDescending( u"Meters Long and adoptUnit() method", + u"measure-unit/length-meter unit-width-full-name", NumberFormatter::with().adoptUnit(new MeasureUnit(METER)) .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME), Locale::getEnglish(), @@ -390,6 +433,7 @@ void NumberFormatterApiTest::unitMeasure() { assertFormatDescending( u"Compact Meters Long", + u"compact-long measure-unit/length-meter unit-width-full-name", NumberFormatter::with().notation(Notation::compactLong()) .unit(METER) .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME), @@ -422,6 +466,7 @@ void NumberFormatterApiTest::unitMeasure() { assertFormatSingle( u"Meters with Negative Sign", + u"measure-unit/length-meter", NumberFormatter::with().unit(METER), Locale::getEnglish(), -9876543.21, @@ -430,8 +475,8 @@ void NumberFormatterApiTest::unitMeasure() { // The locale string "सान" appears only in brx.txt: assertFormatSingle( u"Interesting Data Fallback 1", - NumberFormatter::with().unit(DAY) - .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME), + u"measure-unit/duration-day unit-width-full-name", + NumberFormatter::with().unit(DAY).unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME), Locale::createFromName("brx"), 5.43, u"5.43 सान"); @@ -439,8 +484,8 @@ void NumberFormatterApiTest::unitMeasure() { // Requires following the alias from unitsNarrow to unitsShort: assertFormatSingle( u"Interesting Data Fallback 2", - NumberFormatter::with().unit(DAY) - .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW), + u"measure-unit/duration-day unit-width-narrow", + NumberFormatter::with().unit(DAY).unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW), Locale::createFromName("brx"), 5.43, u"5.43 d"); @@ -449,8 +494,8 @@ void NumberFormatterApiTest::unitMeasure() { // requiring fallback to the root. assertFormatSingle( u"Interesting Data Fallback 3", - NumberFormatter::with().unit(SQUARE_METER) - .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW), + u"measure-unit/area-square-meter unit-width-narrow", + NumberFormatter::with().unit(SQUARE_METER).unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW), Locale::createFromName("en-GB"), 5.43, u"5.43 m²"); @@ -459,33 +504,32 @@ void NumberFormatterApiTest::unitMeasure() { // NOTE: This example is in the documentation. assertFormatSingle( u"Difference between Narrow and Short (Narrow Version)", - NumberFormatter::with().unit(FAHRENHEIT) - .unitWidth(UNUM_UNIT_WIDTH_NARROW), + u"measure-unit/temperature-fahrenheit unit-width-narrow", + NumberFormatter::with().unit(FAHRENHEIT).unitWidth(UNUM_UNIT_WIDTH_NARROW), Locale("es-US"), 5.43, u"5.43°"); assertFormatSingle( u"Difference between Narrow and Short (Short Version)", - NumberFormatter::with().unit(FAHRENHEIT) - .unitWidth(UNUM_UNIT_WIDTH_SHORT), + u"measure-unit/temperature-fahrenheit unit-width-short", + NumberFormatter::with().unit(FAHRENHEIT).unitWidth(UNUM_UNIT_WIDTH_SHORT), Locale("es-US"), 5.43, u"5.43 °F"); assertFormatSingle( u"MeasureUnit form without {0} in CLDR pattern", - NumberFormatter::with() - .unit(KELVIN) - .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME), + u"measure-unit/temperature-kelvin unit-width-full-name", + NumberFormatter::with().unit(KELVIN).unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME), Locale("es-MX"), 1, u"kelvin"); assertFormatSingle( u"MeasureUnit form without {0} in CLDR pattern and wide base form", - NumberFormatter::with() - .rounding(Rounder::fixedFraction(20)) + u"measure-unit/temperature-kelvin .0000000000 unit-width-full-name", + NumberFormatter::with().rounding(Rounder::fixedFraction(20)) .unit(KELVIN) .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME), Locale("es-MX"), @@ -496,6 +540,7 @@ void NumberFormatterApiTest::unitMeasure() { void NumberFormatterApiTest::unitCompoundMeasure() { assertFormatDescending( u"Meters Per Second Short (unit that simplifies) and perUnit method", + u"measure-unit/length-meter per-measure-unit/duration-second", NumberFormatter::with().unit(METER).perUnit(SECOND), Locale::getEnglish(), u"87,650 m/s", @@ -510,6 +555,7 @@ void NumberFormatterApiTest::unitCompoundMeasure() { assertFormatDescending( u"Pounds Per Square Mile Short (secondary unit has per-format) and adoptPerUnit method", + u"measure-unit/mass-pound per-measure-unit/area-square-mile", NumberFormatter::with().unit(POUND).adoptPerUnit(new MeasureUnit(SQUARE_MILE)), Locale::getEnglish(), u"87,650 lb/mi²", @@ -524,6 +570,7 @@ void NumberFormatterApiTest::unitCompoundMeasure() { assertFormatDescending( u"Joules Per Furlong Short (unit with no simplifications or special patterns)", + u"measure-unit/energy-joule per-measure-unit/length-furlong", NumberFormatter::with().unit(JOULE).perUnit(FURLONG), Locale::getEnglish(), u"87,650 J/fur", @@ -540,6 +587,7 @@ void NumberFormatterApiTest::unitCompoundMeasure() { void NumberFormatterApiTest::unitCurrency() { assertFormatDescending( u"Currency", + u"currency/GBP", NumberFormatter::with().unit(GBP), Locale::getEnglish(), u"£87,650.00", @@ -554,6 +602,7 @@ void NumberFormatterApiTest::unitCurrency() { assertFormatDescending( u"Currency ISO", + u"currency/GBP unit-width-iso-code", NumberFormatter::with().unit(GBP).unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_ISO_CODE), Locale::getEnglish(), u"GBP 87,650.00", @@ -568,6 +617,7 @@ void NumberFormatterApiTest::unitCurrency() { assertFormatDescending( u"Currency Long Name", + u"currency/GBP unit-width-full-name", NumberFormatter::with().unit(GBP).unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME), Locale::getEnglish(), u"87,650.00 British pounds", @@ -582,6 +632,7 @@ void NumberFormatterApiTest::unitCurrency() { assertFormatDescending( u"Currency Hidden", + u"currency/GBP unit-width-hidden", NumberFormatter::with().unit(GBP).unitWidth(UNUM_UNIT_WIDTH_HIDDEN), Locale::getEnglish(), u"87,650.00", @@ -611,6 +662,7 @@ void NumberFormatterApiTest::unitCurrency() { assertFormatSingle( u"Currency with Negative Sign", + u"currency/GBP", NumberFormatter::with().unit(GBP), Locale::getEnglish(), -9876543.21, @@ -620,6 +672,7 @@ void NumberFormatterApiTest::unitCurrency() { // NOTE: This example is in the documentation. assertFormatSingle( u"Currency Difference between Narrow and Short (Narrow Version)", + u"currency/USD unit-width-narrow", NumberFormatter::with().unit(USD).unitWidth(UNUM_UNIT_WIDTH_NARROW), Locale("en-CA"), 5.43, @@ -627,6 +680,7 @@ void NumberFormatterApiTest::unitCurrency() { assertFormatSingle( u"Currency Difference between Narrow and Short (Short Version)", + u"currency/USD unit-width-short", NumberFormatter::with().unit(USD).unitWidth(UNUM_UNIT_WIDTH_SHORT), Locale("en-CA"), 5.43, @@ -634,6 +688,7 @@ void NumberFormatterApiTest::unitCurrency() { assertFormatSingle( u"Currency-dependent format (Control)", + u"currency/USD unit-width-short", NumberFormatter::with().unit(USD).unitWidth(UNUM_UNIT_WIDTH_SHORT), Locale("ca"), 444444.55, @@ -641,6 +696,7 @@ void NumberFormatterApiTest::unitCurrency() { assertFormatSingle( u"Currency-dependent format (Test)", + u"currency/ESP unit-width-short", NumberFormatter::with().unit(ESP).unitWidth(UNUM_UNIT_WIDTH_SHORT), Locale("ca"), 444444.55, @@ -648,6 +704,7 @@ void NumberFormatterApiTest::unitCurrency() { assertFormatSingle( u"Currency-dependent symbols (Control)", + u"currency/USD unit-width-short", NumberFormatter::with().unit(USD).unitWidth(UNUM_UNIT_WIDTH_SHORT), Locale("pt-PT"), 444444.55, @@ -657,6 +714,7 @@ void NumberFormatterApiTest::unitCurrency() { // width space), and they set the decimal separator to the $ symbol. assertFormatSingle( u"Currency-dependent symbols (Test Short)", + u"currency/PTE unit-width-short", NumberFormatter::with().unit(PTE).unitWidth(UNUM_UNIT_WIDTH_SHORT), Locale("pt-PT"), 444444.55, @@ -664,6 +722,7 @@ void NumberFormatterApiTest::unitCurrency() { assertFormatSingle( u"Currency-dependent symbols (Test Narrow)", + u"currency/PTE unit-width-narrow", NumberFormatter::with().unit(PTE).unitWidth(UNUM_UNIT_WIDTH_NARROW), Locale("pt-PT"), 444444.55, @@ -671,6 +730,7 @@ void NumberFormatterApiTest::unitCurrency() { assertFormatSingle( u"Currency-dependent symbols (Test ISO Code)", + u"currency/PTE unit-width-iso-code", NumberFormatter::with().unit(PTE).unitWidth(UNUM_UNIT_WIDTH_ISO_CODE), Locale("pt-PT"), 444444.55, @@ -680,6 +740,7 @@ void NumberFormatterApiTest::unitCurrency() { void NumberFormatterApiTest::unitPercent() { assertFormatDescending( u"Percent", + u"percent", NumberFormatter::with().unit(NoUnit::percent()), Locale::getEnglish(), u"87,650%", @@ -694,6 +755,7 @@ void NumberFormatterApiTest::unitPercent() { assertFormatDescending( u"Permille", + u"permille", NumberFormatter::with().unit(NoUnit::permille()), Locale::getEnglish(), u"87,650‰", @@ -708,6 +770,7 @@ void NumberFormatterApiTest::unitPercent() { assertFormatSingle( u"NoUnit Base", + u"base-unit", NumberFormatter::with().unit(NoUnit::base()), Locale::getEnglish(), 51423, @@ -715,6 +778,7 @@ void NumberFormatterApiTest::unitPercent() { assertFormatSingle( u"Percent with Negative Sign", + u"percent", NumberFormatter::with().unit(NoUnit::percent()), Locale::getEnglish(), -98.7654321, @@ -724,6 +788,7 @@ void NumberFormatterApiTest::unitPercent() { void NumberFormatterApiTest::roundingFraction() { assertFormatDescending( u"Integer", + u"round-integer", NumberFormatter::with().rounding(Rounder::integer()), Locale::getEnglish(), u"87,650", @@ -738,6 +803,7 @@ void NumberFormatterApiTest::roundingFraction() { assertFormatDescending( u"Fixed Fraction", + u".000", NumberFormatter::with().rounding(Rounder::fixedFraction(3)), Locale::getEnglish(), u"87,650.000", @@ -752,6 +818,7 @@ void NumberFormatterApiTest::roundingFraction() { assertFormatDescending( u"Min Fraction", + u".0+", NumberFormatter::with().rounding(Rounder::minFraction(1)), Locale::getEnglish(), u"87,650.0", @@ -766,6 +833,7 @@ void NumberFormatterApiTest::roundingFraction() { assertFormatDescending( u"Max Fraction", + u".#", NumberFormatter::with().rounding(Rounder::maxFraction(1)), Locale::getEnglish(), u"87,650", @@ -780,6 +848,7 @@ void NumberFormatterApiTest::roundingFraction() { assertFormatDescending( u"Min/Max Fraction", + u".0##", NumberFormatter::with().rounding(Rounder::minMaxFraction(1, 3)), Locale::getEnglish(), u"87,650.0", @@ -796,6 +865,7 @@ void NumberFormatterApiTest::roundingFraction() { void NumberFormatterApiTest::roundingFigures() { assertFormatSingle( u"Fixed Significant", + u"@@@", NumberFormatter::with().rounding(Rounder::fixedDigits(3)), Locale::getEnglish(), -98, @@ -803,6 +873,7 @@ void NumberFormatterApiTest::roundingFigures() { assertFormatSingle( u"Fixed Significant Rounding", + u"@@@", NumberFormatter::with().rounding(Rounder::fixedDigits(3)), Locale::getEnglish(), -98.7654321, @@ -810,6 +881,7 @@ void NumberFormatterApiTest::roundingFigures() { assertFormatSingle( u"Fixed Significant Zero", + u"@@@", NumberFormatter::with().rounding(Rounder::fixedDigits(3)), Locale::getEnglish(), 0, @@ -817,6 +889,7 @@ void NumberFormatterApiTest::roundingFigures() { assertFormatSingle( u"Min Significant", + u"@@+", NumberFormatter::with().rounding(Rounder::minDigits(2)), Locale::getEnglish(), -9, @@ -824,6 +897,7 @@ void NumberFormatterApiTest::roundingFigures() { assertFormatSingle( u"Max Significant", + u"@###", NumberFormatter::with().rounding(Rounder::maxDigits(4)), Locale::getEnglish(), 98.7654321, @@ -831,6 +905,7 @@ void NumberFormatterApiTest::roundingFigures() { assertFormatSingle( u"Min/Max Significant", + u"@@@#", NumberFormatter::with().rounding(Rounder::minMaxDigits(3, 4)), Locale::getEnglish(), 9.99999, @@ -840,6 +915,7 @@ void NumberFormatterApiTest::roundingFigures() { void NumberFormatterApiTest::roundingFractionFigures() { assertFormatDescending( u"Basic Significant", // for comparison + u"@#", NumberFormatter::with().rounding(Rounder::maxDigits(2)), Locale::getEnglish(), u"88,000", @@ -854,6 +930,7 @@ void NumberFormatterApiTest::roundingFractionFigures() { assertFormatDescending( u"FracSig minMaxFrac minSig", + u".0#/@@@+", NumberFormatter::with().rounding(Rounder::minMaxFraction(1, 2).withMinDigits(3)), Locale::getEnglish(), u"87,650.0", @@ -868,6 +945,7 @@ void NumberFormatterApiTest::roundingFractionFigures() { assertFormatDescending( u"FracSig minMaxFrac maxSig A", + u".0##/@#", NumberFormatter::with().rounding(Rounder::minMaxFraction(1, 3).withMaxDigits(2)), Locale::getEnglish(), u"88,000.0", // maxSig beats maxFrac @@ -882,6 +960,7 @@ void NumberFormatterApiTest::roundingFractionFigures() { assertFormatDescending( u"FracSig minMaxFrac maxSig B", + u".00/@#", NumberFormatter::with().rounding(Rounder::fixedFraction(2).withMaxDigits(2)), Locale::getEnglish(), u"88,000.00", // maxSig beats maxFrac @@ -895,14 +974,16 @@ void NumberFormatterApiTest::roundingFractionFigures() { u"0.00"); assertFormatSingle( - "FracSig with trailing zeros A", + u"FracSig with trailing zeros A", + u".00/@@@+", NumberFormatter::with().rounding(Rounder::fixedFraction(2).withMinDigits(3)), Locale::getEnglish(), 0.1, u"0.10"); assertFormatSingle( - "FracSig with trailing zeros B", + u"FracSig with trailing zeros B", + u".00/@@@+", NumberFormatter::with().rounding(Rounder::fixedFraction(2).withMinDigits(3)), Locale::getEnglish(), 0.0999999, @@ -912,6 +993,7 @@ void NumberFormatterApiTest::roundingFractionFigures() { void NumberFormatterApiTest::roundingOther() { assertFormatDescending( u"Rounding None", + u"round-unlimited", NumberFormatter::with().rounding(Rounder::unlimited()), Locale::getEnglish(), u"87,650", @@ -926,6 +1008,7 @@ void NumberFormatterApiTest::roundingOther() { assertFormatDescending( u"Increment", + u"round-increment/0.5", NumberFormatter::with().rounding(Rounder::increment(0.5).withMinFraction(1)), Locale::getEnglish(), u"87,650.0", @@ -940,6 +1023,7 @@ void NumberFormatterApiTest::roundingOther() { assertFormatDescending( u"Increment with Min Fraction", + u"round-increment/0.50", NumberFormatter::with().rounding(Rounder::increment(0.5).withMinFraction(2)), Locale::getEnglish(), u"87,650.00", @@ -954,6 +1038,7 @@ void NumberFormatterApiTest::roundingOther() { assertFormatDescending( u"Currency Standard", + u"currency/CZK round-currency-standard", NumberFormatter::with().rounding(Rounder::currency(UCurrencyUsage::UCURR_USAGE_STANDARD)) .unit(CZK), Locale::getEnglish(), @@ -969,6 +1054,7 @@ void NumberFormatterApiTest::roundingOther() { assertFormatDescending( u"Currency Cash", + u"currency/CZK round-currency-cash", NumberFormatter::with().rounding(Rounder::currency(UCurrencyUsage::UCURR_USAGE_CASH)) .unit(CZK), Locale::getEnglish(), @@ -984,6 +1070,7 @@ void NumberFormatterApiTest::roundingOther() { assertFormatDescending( u"Currency Cash with Nickel Rounding", + u"currency/CAD round-currency-cash", NumberFormatter::with().rounding(Rounder::currency(UCurrencyUsage::UCURR_USAGE_CASH)) .unit(CAD), Locale::getEnglish(), @@ -999,6 +1086,7 @@ void NumberFormatterApiTest::roundingOther() { assertFormatDescending( u"Currency not in top-level fluent chain", + u"round-integer", // calling .withCurrency() applies currency rounding rules immediately NumberFormatter::with().rounding( Rounder::currency(UCurrencyUsage::UCURR_USAGE_CASH).withCurrency(CZK)), Locale::getEnglish(), @@ -1015,6 +1103,7 @@ void NumberFormatterApiTest::roundingOther() { // NOTE: Other tests cover the behavior of the other rounding modes. assertFormatDescending( u"Rounding Mode CEILING", + u"round-integer/ceiling", NumberFormatter::with().rounding(Rounder::integer().withMode(UNumberFormatRoundingMode::UNUM_ROUND_CEILING)), Locale::getEnglish(), u"87,650", @@ -1031,6 +1120,7 @@ void NumberFormatterApiTest::roundingOther() { void NumberFormatterApiTest::grouping() { assertFormatDescendingBig( u"Western Grouping", + u"group-auto", NumberFormatter::with().grouping(UNUM_GROUPING_AUTO), Locale::getEnglish(), u"87,650,000", @@ -1045,6 +1135,7 @@ void NumberFormatterApiTest::grouping() { assertFormatDescendingBig( u"Indic Grouping", + u"group-auto", NumberFormatter::with().grouping(UNUM_GROUPING_AUTO), Locale("en-IN"), u"8,76,50,000", @@ -1058,7 +1149,8 @@ void NumberFormatterApiTest::grouping() { u"0"); assertFormatDescendingBig( - u"Western Grouping, Wide", + u"Western Grouping, Min 2", + u"group-min2", NumberFormatter::with().grouping(UNUM_GROUPING_MIN2), Locale::getEnglish(), u"87,650,000", @@ -1072,7 +1164,8 @@ void NumberFormatterApiTest::grouping() { u"0"); assertFormatDescendingBig( - u"Indic Grouping, Wide", + u"Indic Grouping, Min 2", + u"group-min2", NumberFormatter::with().grouping(UNUM_GROUPING_MIN2), Locale("en-IN"), u"8,76,50,000", @@ -1087,6 +1180,7 @@ void NumberFormatterApiTest::grouping() { assertFormatDescendingBig( u"No Grouping", + u"group-off", NumberFormatter::with().grouping(UNUM_GROUPING_OFF), Locale("en-IN"), u"87650000", @@ -1101,6 +1195,7 @@ void NumberFormatterApiTest::grouping() { assertFormatDescendingBig( u"Indic locale with THOUSANDS grouping", + u"group-thousands", NumberFormatter::with().grouping(UNUM_GROUPING_THOUSANDS), Locale("en-IN"), u"87,650,000", @@ -1117,6 +1212,7 @@ void NumberFormatterApiTest::grouping() { // If this test breaks due to data changes, find another locale that has minimumGroupingDigits. assertFormatDescendingBig( u"Hungarian Grouping", + u"group-auto", NumberFormatter::with().grouping(UNUM_GROUPING_AUTO), Locale("hu"), u"87 650 000", @@ -1131,6 +1227,7 @@ void NumberFormatterApiTest::grouping() { assertFormatDescendingBig( u"Hungarian Grouping, Min 2", + u"group-min2", NumberFormatter::with().grouping(UNUM_GROUPING_MIN2), Locale("hu"), u"87 650 000", @@ -1145,6 +1242,7 @@ void NumberFormatterApiTest::grouping() { assertFormatDescendingBig( u"Hungarian Grouping, Always", + u"group-on-aligned", NumberFormatter::with().grouping(UNUM_GROUPING_ON_ALIGNED), Locale("hu"), u"87 650 000", @@ -1161,6 +1259,7 @@ void NumberFormatterApiTest::grouping() { // If this test breaks due to data changes, find another locale that has no default grouping. assertFormatDescendingBig( u"Bulgarian Currency Grouping", + u"currency/USD group-auto", NumberFormatter::with().grouping(UNUM_GROUPING_AUTO).unit(USD), Locale("bg"), u"87650000,00 щ.д.", @@ -1175,6 +1274,7 @@ void NumberFormatterApiTest::grouping() { assertFormatDescendingBig( u"Bulgarian Currency Grouping, Always", + u"currency/USD group-on-aligned", NumberFormatter::with().grouping(UNUM_GROUPING_ON_ALIGNED).unit(USD), Locale("bg"), u"87 650 000,00 щ.д.", @@ -1187,27 +1287,28 @@ void NumberFormatterApiTest::grouping() { u"8,76 щ.д.", u"0,00 щ.д."); - // TODO: Enable this test when macro-setter is available in C++ - // MacroProps macros; - // macros.grouping = Grouper(4, 1, 3); - // assertFormatDescendingBig( - // u"Custom Grouping via Internal API", - // NumberFormatter::with().macros(macros), - // Locale::getEnglish(), - // u"8,7,6,5,0000", - // u"8,7,6,5000", - // u"876500", - // u"87650", - // u"8765", - // u"876.5", - // u"87.65", - // u"8.765", - // u"0"); + MacroProps macros; + macros.grouper = Grouper(4, 1, 3, UNUM_GROUPING_COUNT); + assertFormatDescendingBig( + u"Custom Grouping via Internal API", + nullptr, + NumberFormatter::with().macros(macros), + Locale::getEnglish(), + u"8,7,6,5,0000", + u"8,7,6,5000", + u"876500", + u"87650", + u"8765", + u"876.5", + u"87.65", + u"8.765", + u"0"); } void NumberFormatterApiTest::padding() { assertFormatDescending( u"Padding", + nullptr, NumberFormatter::with().padding(Padder::none()), Locale::getEnglish(), u"87,650", @@ -1222,6 +1323,7 @@ void NumberFormatterApiTest::padding() { assertFormatDescending( u"Padding", + nullptr, NumberFormatter::with().padding( Padder::codePoints( '*', 8, PadPosition::UNUM_PAD_AFTER_PREFIX)), @@ -1238,6 +1340,7 @@ void NumberFormatterApiTest::padding() { assertFormatDescending( u"Padding with code points", + nullptr, NumberFormatter::with().padding( Padder::codePoints( 0x101E4, 8, PadPosition::UNUM_PAD_AFTER_PREFIX)), @@ -1254,6 +1357,7 @@ void NumberFormatterApiTest::padding() { assertFormatDescending( u"Padding with wide digits", + nullptr, NumberFormatter::with().padding( Padder::codePoints( '*', 8, PadPosition::UNUM_PAD_AFTER_PREFIX)) @@ -1271,6 +1375,7 @@ void NumberFormatterApiTest::padding() { assertFormatDescending( u"Padding with currency spacing", + nullptr, NumberFormatter::with().padding( Padder::codePoints( '*', 10, PadPosition::UNUM_PAD_AFTER_PREFIX)) @@ -1289,6 +1394,7 @@ void NumberFormatterApiTest::padding() { assertFormatSingle( u"Pad Before Prefix", + nullptr, NumberFormatter::with().padding( Padder::codePoints( '*', 8, PadPosition::UNUM_PAD_BEFORE_PREFIX)), @@ -1298,6 +1404,7 @@ void NumberFormatterApiTest::padding() { assertFormatSingle( u"Pad After Prefix", + nullptr, NumberFormatter::with().padding( Padder::codePoints( '*', 8, PadPosition::UNUM_PAD_AFTER_PREFIX)), @@ -1307,6 +1414,7 @@ void NumberFormatterApiTest::padding() { assertFormatSingle( u"Pad Before Suffix", + nullptr, NumberFormatter::with().padding( Padder::codePoints( '*', 8, PadPosition::UNUM_PAD_BEFORE_SUFFIX)).unit(NoUnit::percent()), @@ -1316,6 +1424,7 @@ void NumberFormatterApiTest::padding() { assertFormatSingle( u"Pad After Suffix", + nullptr, NumberFormatter::with().padding( Padder::codePoints( '*', 8, PadPosition::UNUM_PAD_AFTER_SUFFIX)).unit(NoUnit::percent()), @@ -1325,6 +1434,7 @@ void NumberFormatterApiTest::padding() { assertFormatSingle( u"Currency Spacing with Zero Digit Padding Broken", + nullptr, NumberFormatter::with().padding( Padder::codePoints( '0', 12, PadPosition::UNUM_PAD_AFTER_PREFIX)) @@ -1338,6 +1448,7 @@ void NumberFormatterApiTest::padding() { void NumberFormatterApiTest::integerWidth() { assertFormatDescending( u"Integer Width Default", + u"integer-width/+0", NumberFormatter::with().integerWidth(IntegerWidth::zeroFillTo(1)), Locale::getEnglish(), u"87,650", @@ -1352,6 +1463,7 @@ void NumberFormatterApiTest::integerWidth() { assertFormatDescending( u"Integer Width Zero Fill 0", + u"integer-width/+", NumberFormatter::with().integerWidth(IntegerWidth::zeroFillTo(0)), Locale::getEnglish(), u"87,650", @@ -1366,6 +1478,7 @@ void NumberFormatterApiTest::integerWidth() { assertFormatDescending( u"Integer Width Zero Fill 3", + u"integer-width/+000", NumberFormatter::with().integerWidth(IntegerWidth::zeroFillTo(3)), Locale::getEnglish(), u"87,650", @@ -1380,6 +1493,7 @@ void NumberFormatterApiTest::integerWidth() { assertFormatDescending( u"Integer Width Max 3", + u"integer-width/##0", NumberFormatter::with().integerWidth(IntegerWidth::zeroFillTo(1).truncateAt(3)), Locale::getEnglish(), u"650", @@ -1394,6 +1508,7 @@ void NumberFormatterApiTest::integerWidth() { assertFormatDescending( u"Integer Width Fixed 2", + u"integer-width/00", NumberFormatter::with().integerWidth(IntegerWidth::zeroFillTo(2).truncateAt(2)), Locale::getEnglish(), u"50", @@ -1410,6 +1525,7 @@ void NumberFormatterApiTest::integerWidth() { void NumberFormatterApiTest::symbols() { assertFormatDescending( u"French Symbols with Japanese Data 1", + nullptr, NumberFormatter::with().symbols(FRENCH_SYMBOLS), Locale::getJapan(), u"87 650", @@ -1424,6 +1540,7 @@ void NumberFormatterApiTest::symbols() { assertFormatSingle( u"French Symbols with Japanese Data 2", + nullptr, NumberFormatter::with().notation(Notation::compactShort()).symbols(FRENCH_SYMBOLS), Locale::getJapan(), 12345, @@ -1431,6 +1548,7 @@ void NumberFormatterApiTest::symbols() { assertFormatDescending( u"Latin Numbering System with Arabic Data", + u"currency/USD latin", NumberFormatter::with().adoptSymbols(new NumberingSystem(LATN)).unit(USD), Locale("ar"), u"US$ 87,650.00", @@ -1445,6 +1563,7 @@ void NumberFormatterApiTest::symbols() { assertFormatDescending( u"Math Numbering System with French Data", + u"numbering-system/mathsanb", NumberFormatter::with().adoptSymbols(new NumberingSystem(MATHSANB)), Locale::getFrench(), u"𝟴𝟳 𝟲𝟱𝟬", @@ -1459,6 +1578,7 @@ void NumberFormatterApiTest::symbols() { assertFormatSingle( u"Swiss Symbols (used in documentation)", + nullptr, NumberFormatter::with().symbols(SWISS_SYMBOLS), Locale::getEnglish(), 12345.67, @@ -1466,6 +1586,7 @@ void NumberFormatterApiTest::symbols() { assertFormatSingle( u"Myanmar Symbols (used in documentation)", + nullptr, NumberFormatter::with().symbols(MYANMAR_SYMBOLS), Locale::getEnglish(), 12345.67, @@ -1475,6 +1596,7 @@ void NumberFormatterApiTest::symbols() { assertFormatSingle( u"Currency symbol should precede number in ar with NS latn", + u"currency/USD latin", NumberFormatter::with().adoptSymbols(new NumberingSystem(LATN)).unit(USD), Locale("ar"), 12345.67, @@ -1482,6 +1604,7 @@ void NumberFormatterApiTest::symbols() { assertFormatSingle( u"Currency symbol should precede number in ar@numbers=latn", + u"currency/USD", NumberFormatter::with().unit(USD), Locale("ar@numbers=latn"), 12345.67, @@ -1489,6 +1612,7 @@ void NumberFormatterApiTest::symbols() { assertFormatSingle( u"Currency symbol should follow number in ar-EG with NS arab", + u"currency/USD", NumberFormatter::with().unit(USD), Locale("ar-EG"), 12345.67, @@ -1496,6 +1620,7 @@ void NumberFormatterApiTest::symbols() { assertFormatSingle( u"Currency symbol should follow number in ar@numbers=arab", + u"currency/USD", NumberFormatter::with().unit(USD), Locale("ar@numbers=arab"), 12345.67, @@ -1503,28 +1628,30 @@ void NumberFormatterApiTest::symbols() { assertFormatSingle( u"NumberingSystem in API should win over @numbers keyword", + u"currency/USD latin", NumberFormatter::with().adoptSymbols(new NumberingSystem(LATN)).unit(USD), Locale("ar@numbers=arab"), 12345.67, u"US$ 12,345.67"); UErrorCode status = U_ZERO_ERROR; - assertEquals("NumberingSystem in API should win over @numbers keyword in reverse order", + assertEquals( + "NumberingSystem in API should win over @numbers keyword in reverse order", u"US$ 12,345.67", - NumberFormatter::withLocale(Locale("ar@numbers=arab")) - .adoptSymbols(new NumberingSystem(LATN)) - .unit(USD) - .formatDouble(12345.67, status) - .toString()); + NumberFormatter::withLocale(Locale("ar@numbers=arab")).adoptSymbols(new NumberingSystem(LATN)) + .unit(USD) + .formatDouble(12345.67, status) + .toString()); DecimalFormatSymbols symbols = SWISS_SYMBOLS; UnlocalizedNumberFormatter f = NumberFormatter::with().symbols(symbols); symbols.setSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kGroupingSeparatorSymbol, u"!", status); assertFormatSingle( - u"Symbols object should be copied", f, Locale::getEnglish(), 12345.67, u"12’345.67"); + u"Symbols object should be copied", nullptr, f, Locale::getEnglish(), 12345.67, u"12’345.67"); assertFormatSingle( u"The last symbols setter wins", + u"latin", NumberFormatter::with().symbols(symbols).adoptSymbols(new NumberingSystem(LATN)), Locale::getEnglish(), 12345.67, @@ -1532,6 +1659,7 @@ void NumberFormatterApiTest::symbols() { assertFormatSingle( u"The last symbols setter wins", + nullptr, NumberFormatter::with().adoptSymbols(new NumberingSystem(LATN)).symbols(symbols), Locale::getEnglish(), 12345.67, @@ -1554,6 +1682,7 @@ void NumberFormatterApiTest::symbols() { void NumberFormatterApiTest::sign() { assertFormatSingle( u"Sign Auto Positive", + u"sign-auto", NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_AUTO), Locale::getEnglish(), 444444, @@ -1561,6 +1690,7 @@ void NumberFormatterApiTest::sign() { assertFormatSingle( u"Sign Auto Negative", + u"sign-auto", NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_AUTO), Locale::getEnglish(), -444444, @@ -1568,6 +1698,7 @@ void NumberFormatterApiTest::sign() { assertFormatSingle( u"Sign Auto Zero", + u"sign-auto", NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_AUTO), Locale::getEnglish(), 0, @@ -1575,6 +1706,7 @@ void NumberFormatterApiTest::sign() { assertFormatSingle( u"Sign Always Positive", + u"sign-always", NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ALWAYS), Locale::getEnglish(), 444444, @@ -1582,6 +1714,7 @@ void NumberFormatterApiTest::sign() { assertFormatSingle( u"Sign Always Negative", + u"sign-always", NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ALWAYS), Locale::getEnglish(), -444444, @@ -1589,6 +1722,7 @@ void NumberFormatterApiTest::sign() { assertFormatSingle( u"Sign Always Zero", + u"sign-always", NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ALWAYS), Locale::getEnglish(), 0, @@ -1596,6 +1730,7 @@ void NumberFormatterApiTest::sign() { assertFormatSingle( u"Sign Never Positive", + u"sign-never", NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_NEVER), Locale::getEnglish(), 444444, @@ -1603,6 +1738,7 @@ void NumberFormatterApiTest::sign() { assertFormatSingle( u"Sign Never Negative", + u"sign-never", NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_NEVER), Locale::getEnglish(), -444444, @@ -1610,6 +1746,7 @@ void NumberFormatterApiTest::sign() { assertFormatSingle( u"Sign Never Zero", + u"sign-never", NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_NEVER), Locale::getEnglish(), 0, @@ -1617,12 +1754,14 @@ void NumberFormatterApiTest::sign() { assertFormatSingle( u"Sign Accounting Positive", + u"currency/USD sign-accounting", NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING).unit(USD), Locale::getEnglish(), 444444, u"$444,444.00"); assertFormatSingle( + u"currency/USD sign-accounting", u"Sign Accounting Negative", NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING).unit(USD), Locale::getEnglish(), @@ -1631,6 +1770,7 @@ void NumberFormatterApiTest::sign() { assertFormatSingle( u"Sign Accounting Zero", + u"currency/USD sign-accounting", NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING).unit(USD), Locale::getEnglish(), 0, @@ -1638,6 +1778,7 @@ void NumberFormatterApiTest::sign() { assertFormatSingle( u"Sign Accounting-Always Positive", + u"currency/USD sign-accounting-always", NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_ALWAYS).unit(USD), Locale::getEnglish(), 444444, @@ -1645,6 +1786,7 @@ void NumberFormatterApiTest::sign() { assertFormatSingle( u"Sign Accounting-Always Negative", + u"currency/USD sign-accounting-always", NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_ALWAYS).unit(USD), Locale::getEnglish(), -444444, @@ -1652,6 +1794,7 @@ void NumberFormatterApiTest::sign() { assertFormatSingle( u"Sign Accounting-Always Zero", + u"currency/USD sign-accounting-always", NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_ALWAYS).unit(USD), Locale::getEnglish(), 0, @@ -1659,6 +1802,7 @@ void NumberFormatterApiTest::sign() { assertFormatSingle( u"Sign Except-Zero Positive", + u"sign-except-zero", NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_EXCEPT_ZERO), Locale::getEnglish(), 444444, @@ -1666,6 +1810,7 @@ void NumberFormatterApiTest::sign() { assertFormatSingle( u"Sign Except-Zero Negative", + u"sign-except-zero", NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_EXCEPT_ZERO), Locale::getEnglish(), -444444, @@ -1673,6 +1818,7 @@ void NumberFormatterApiTest::sign() { assertFormatSingle( u"Sign Except-Zero Zero", + u"sign-except-zero", NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_EXCEPT_ZERO), Locale::getEnglish(), 0, @@ -1680,6 +1826,7 @@ void NumberFormatterApiTest::sign() { assertFormatSingle( u"Sign Accounting-Except-Zero Positive", + u"currency/USD sign-accounting-except-zero", NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO).unit(USD), Locale::getEnglish(), 444444, @@ -1687,6 +1834,7 @@ void NumberFormatterApiTest::sign() { assertFormatSingle( u"Sign Accounting-Except-Zero Negative", + u"currency/USD sign-accounting-except-zero", NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO).unit(USD), Locale::getEnglish(), -444444, @@ -1694,6 +1842,7 @@ void NumberFormatterApiTest::sign() { assertFormatSingle( u"Sign Accounting-Except-Zero Zero", + u"currency/USD sign-accounting-except-zero", NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO).unit(USD), Locale::getEnglish(), 0, @@ -1701,6 +1850,7 @@ void NumberFormatterApiTest::sign() { assertFormatSingle( u"Sign Accounting Negative Hidden", + u"currency/USD unit-width-hidden sign-accounting", NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING) .unit(USD) .unitWidth(UNUM_UNIT_WIDTH_HIDDEN), @@ -1712,6 +1862,7 @@ void NumberFormatterApiTest::sign() { void NumberFormatterApiTest::decimal() { assertFormatDescending( u"Decimal Default", + u"decimal-auto", NumberFormatter::with().decimal(UNumberDecimalSeparatorDisplay::UNUM_DECIMAL_SEPARATOR_AUTO), Locale::getEnglish(), u"87,650", @@ -1726,6 +1877,7 @@ void NumberFormatterApiTest::decimal() { assertFormatDescending( u"Decimal Always Shown", + u"decimal-always", NumberFormatter::with().decimal(UNumberDecimalSeparatorDisplay::UNUM_DECIMAL_SEPARATOR_ALWAYS), Locale::getEnglish(), u"87,650.", @@ -1765,24 +1917,18 @@ void NumberFormatterApiTest::errors() { UErrorCode status2 = U_ZERO_ERROR; FormattedNumber fn = lnf.formatInt(1, status1); assertEquals( - "Should fail since rounder is not legal", - U_NUMBER_ARG_OUTOFBOUNDS_ERROR, - status1); + "Should fail since rounder is not legal", U_NUMBER_ARG_OUTOFBOUNDS_ERROR, status1); FieldPosition fp; fn.populateFieldPosition(fp, status2); assertEquals( - "Should fail on terminal method", - U_NUMBER_ARG_OUTOFBOUNDS_ERROR, - status2); + "Should fail on terminal method", U_NUMBER_ARG_OUTOFBOUNDS_ERROR, status2); } { UErrorCode status = U_ZERO_ERROR; lnf.copyErrorTo(status); assertEquals( - "Should fail since rounder is not legal", - U_NUMBER_ARG_OUTOFBOUNDS_ERROR, - status); + "Should fail since rounder is not legal", U_NUMBER_ARG_OUTOFBOUNDS_ERROR, status); } } @@ -1917,19 +2063,23 @@ void NumberFormatterApiTest::copyMove() { } -void NumberFormatterApiTest::assertFormatDescending(const UnicodeString &message, - const UnlocalizedNumberFormatter &f, - Locale locale, ...) { +void NumberFormatterApiTest::assertFormatDescending(const char16_t* umessage, const char16_t* uskeleton, + const UnlocalizedNumberFormatter& f, Locale locale, + ...) { va_list args; va_start(args, locale); + UnicodeString message(TRUE, umessage, -1); static double inputs[] = {87650, 8765, 876.5, 87.65, 8.765, 0.8765, 0.08765, 0.008765, 0}; const LocalizedNumberFormatter l1 = f.threshold(0).locale(locale); // no self-regulation const LocalizedNumberFormatter l2 = f.threshold(1).locale(locale); // all self-regulation - UErrorCode status = U_ZERO_ERROR; + IcuTestErrorCode status(*this, "assertFormatDescending"); + status.setScope(message); + UnicodeString expecteds[10]; for (int16_t i = 0; i < 9; i++) { char16_t caseNumber = u'0' + i; double d = inputs[i]; UnicodeString expected = va_arg(args, const char16_t*); + expecteds[i] = expected; UnicodeString actual1 = l1.formatDouble(d, status).toString(); assertSuccess(message + u": Unsafe Path: " + caseNumber, status); assertEquals(message + u": Unsafe Path: " + caseNumber, expected, actual1); @@ -1937,21 +2087,40 @@ void NumberFormatterApiTest::assertFormatDescending(const UnicodeString &message assertSuccess(message + u": Safe Path: " + caseNumber, status); assertEquals(message + u": Safe Path: " + caseNumber, expected, actual2); } + if (uskeleton != nullptr) { // if null, skeleton is declared as undefined. + UnicodeString skeleton(TRUE, uskeleton, -1); + // Only compare normalized skeletons: the tests need not provide the normalized forms. + // Use the normalized form to construct the testing formatter to guarantee no loss of info. + UnicodeString normalized = NumberFormatter::fromSkeleton(skeleton, status).toSkeleton(status); + assertEquals(message + ": Skeleton:", normalized, f.toSkeleton(status)); + LocalizedNumberFormatter l3 = NumberFormatter::fromSkeleton(normalized, status).locale(locale); + for (int32_t i = 0; i < 9; i++) { + double d = inputs[i]; + UnicodeString actual3 = l3.formatDouble(d, status).toString(); + assertEquals(message + ": Skeleton Path: " + d, expecteds[i], actual3); + } + } else { + assertUndefinedSkeleton(f); + } } -void NumberFormatterApiTest::assertFormatDescendingBig(const UnicodeString &message, - const UnlocalizedNumberFormatter &f, - Locale locale, ...) { +void NumberFormatterApiTest::assertFormatDescendingBig(const char16_t* umessage, const char16_t* uskeleton, + const UnlocalizedNumberFormatter& f, Locale locale, + ...) { va_list args; va_start(args, locale); + UnicodeString message(TRUE, umessage, -1); static double inputs[] = {87650000, 8765000, 876500, 87650, 8765, 876.5, 87.65, 8.765, 0}; const LocalizedNumberFormatter l1 = f.threshold(0).locale(locale); // no self-regulation const LocalizedNumberFormatter l2 = f.threshold(1).locale(locale); // all self-regulation - UErrorCode status = U_ZERO_ERROR; + IcuTestErrorCode status(*this, "assertFormatDescendingBig"); + status.setScope(message); + UnicodeString expecteds[10]; for (int16_t i = 0; i < 9; i++) { char16_t caseNumber = u'0' + i; double d = inputs[i]; UnicodeString expected = va_arg(args, const char16_t*); + expecteds[i] = expected; UnicodeString actual1 = l1.formatDouble(d, status).toString(); assertSuccess(message + u": Unsafe Path: " + caseNumber, status); assertEquals(message + u": Unsafe Path: " + caseNumber, expected, actual1); @@ -1959,20 +2128,58 @@ void NumberFormatterApiTest::assertFormatDescendingBig(const UnicodeString &mess assertSuccess(message + u": Safe Path: " + caseNumber, status); assertEquals(message + u": Safe Path: " + caseNumber, expected, actual2); } + if (uskeleton != nullptr) { // if null, skeleton is declared as undefined. + UnicodeString skeleton(TRUE, uskeleton, -1); + // Only compare normalized skeletons: the tests need not provide the normalized forms. + // Use the normalized form to construct the testing formatter to guarantee no loss of info. + UnicodeString normalized = NumberFormatter::fromSkeleton(skeleton, status).toSkeleton(status); + assertEquals(message + ": Skeleton:", normalized, f.toSkeleton(status)); + LocalizedNumberFormatter l3 = NumberFormatter::fromSkeleton(normalized, status).locale(locale); + for (int32_t i = 0; i < 9; i++) { + double d = inputs[i]; + UnicodeString actual3 = l3.formatDouble(d, status).toString(); + assertEquals(message + ": Skeleton Path: " + d, expecteds[i], actual3); + } + } else { + assertUndefinedSkeleton(f); + } } -void NumberFormatterApiTest::assertFormatSingle(const UnicodeString &message, - const UnlocalizedNumberFormatter &f, Locale locale, - double input, const UnicodeString &expected) { +void NumberFormatterApiTest::assertFormatSingle(const char16_t* umessage, const char16_t* uskeleton, + const UnlocalizedNumberFormatter& f, Locale locale, + double input, const UnicodeString& expected) { + UnicodeString message(TRUE, umessage, -1); const LocalizedNumberFormatter l1 = f.threshold(0).locale(locale); // no self-regulation const LocalizedNumberFormatter l2 = f.threshold(1).locale(locale); // all self-regulation - UErrorCode status = U_ZERO_ERROR; + IcuTestErrorCode status(*this, "assertFormatSingle"); + status.setScope(message); UnicodeString actual1 = l1.formatDouble(input, status).toString(); assertSuccess(message + u": Unsafe Path", status); assertEquals(message + u": Unsafe Path", expected, actual1); UnicodeString actual2 = l2.formatDouble(input, status).toString(); assertSuccess(message + u": Safe Path", status); assertEquals(message + u": Safe Path", expected, actual2); + if (uskeleton != nullptr) { // if null, skeleton is declared as undefined. + UnicodeString skeleton(TRUE, uskeleton, -1); + // Only compare normalized skeletons: the tests need not provide the normalized forms. + // Use the normalized form to construct the testing formatter to ensure no loss of info. + UnicodeString normalized = NumberFormatter::fromSkeleton(skeleton, status).toSkeleton(status); + assertEquals(message + ": Skeleton:", normalized, f.toSkeleton(status)); + LocalizedNumberFormatter l3 = NumberFormatter::fromSkeleton(normalized, status).locale(locale); + UnicodeString actual3 = l3.formatDouble(input, status).toString(); + assertEquals(message + ": Skeleton Path: " + input, expected, actual3); + } else { + assertUndefinedSkeleton(f); + } +} + +void NumberFormatterApiTest::assertUndefinedSkeleton(const UnlocalizedNumberFormatter& f) { + UErrorCode status = U_ZERO_ERROR; + UnicodeString skeleton = f.toSkeleton(status); + assertEquals( + u"Expect toSkeleton to fail, but passed, producing: " + skeleton, + U_UNSUPPORTED_ERROR, + status); } #endif /* #if !UCONFIG_NO_FORMATTING */ From 3574a63853748b1789082a7f3406825a38437f37 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Sat, 24 Mar 2018 08:06:34 +0000 Subject: [PATCH 062/129] ICU-8610 API test is passing with the skeletons in C++. :) X-SVN-Rev: 41154 --- icu4c/source/i18n/number_rounding.cpp | 1 + icu4c/source/i18n/number_skeletons.cpp | 27 ++++++++++++++++--- icu4c/source/i18n/number_skeletons.h | 3 ++- icu4c/source/test/intltest/numbertest_api.cpp | 4 +-- .../test/number/NumberFormatterApiTest.java | 6 ++--- 5 files changed, 32 insertions(+), 9 deletions(-) diff --git a/icu4c/source/i18n/number_rounding.cpp b/icu4c/source/i18n/number_rounding.cpp index 5f794228db..80120261e1 100644 --- a/icu4c/source/i18n/number_rounding.cpp +++ b/icu4c/source/i18n/number_rounding.cpp @@ -251,6 +251,7 @@ IncrementRounder Rounder::constructIncrement(double increment, int32_t minFrac) settings.fIncrement = increment; settings.fMinFrac = static_cast(minFrac); // One of the few pre-computed quantities: + // Note: it is possible for minFrac to be more than maxFrac... (misleading) settings.fMaxFrac = roundingutils::doubleFractionLength(increment); RounderUnion union_; union_.increment = settings; diff --git a/icu4c/source/i18n/number_skeletons.cpp b/icu4c/source/i18n/number_skeletons.cpp index 357c443455..0958d95c55 100644 --- a/icu4c/source/i18n/number_skeletons.cpp +++ b/icu4c/source/i18n/number_skeletons.cpp @@ -1055,15 +1055,32 @@ void blueprint_helpers::parseIncrementOption(const StringSegment& segment, Macro return; } double increment = dq.toDouble(); - macros.rounder = Rounder::increment(increment); + + // We also need to figure out how many digits. Do a brute force string operation. + int decimalOffset = 0; + while (decimalOffset < segment.length() && segment.charAt(decimalOffset) != '.') { + decimalOffset++; + } + if (decimalOffset == segment.length()) { + macros.rounder = Rounder::increment(increment); + } else { + int32_t fractionLength = segment.length() - decimalOffset - 1; + macros.rounder = Rounder::increment(increment).withMinFraction(fractionLength); + } } -void blueprint_helpers::generateIncrementOption(double increment, UnicodeString& sb, UErrorCode&) { +void blueprint_helpers::generateIncrementOption(double increment, int32_t trailingZeros, UnicodeString& sb, + UErrorCode&) { // Utilize DecimalQuantity/double_conversion to format this for us. DecimalQuantity dq; dq.setToDouble(increment); dq.roundToInfinity(); sb.append(dq.toPlainString()); + + // We might need to append extra trailing zeros for min fraction... + if (trailingZeros > 0) { + appendMultiple(sb, u'0', trailingZeros); + } } bool @@ -1257,7 +1274,11 @@ bool GeneratorHelpers::rounding(const MacroProps& macros, UnicodeString& sb, UEr } else if (macros.rounder.fType == Rounder::RND_INCREMENT) { const Rounder::IncrementSettings& impl = macros.rounder.fUnion.increment; sb.append(u"round-increment/", -1); - blueprint_helpers::generateIncrementOption(impl.fIncrement, sb, status); + blueprint_helpers::generateIncrementOption( + impl.fIncrement, + impl.fMinFrac - impl.fMaxFrac, + sb, + status); } else if (macros.rounder.fType == Rounder::RND_CURRENCY) { UCurrencyUsage usage = macros.rounder.fUnion.currencyUsage; if (usage == UCURR_USAGE_STANDARD) { diff --git a/icu4c/source/i18n/number_skeletons.h b/icu4c/source/i18n/number_skeletons.h index 6a15d9efb4..aeae63f4d9 100644 --- a/icu4c/source/i18n/number_skeletons.h +++ b/icu4c/source/i18n/number_skeletons.h @@ -222,7 +222,8 @@ bool parseFracSigOption(const StringSegment& segment, MacroProps& macros, UError void parseIncrementOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status); -void generateIncrementOption(double increment, UnicodeString& sb, UErrorCode& status); +void +generateIncrementOption(double increment, int32_t trailingZeros, UnicodeString& sb, UErrorCode& status); /** @return Whether we successfully found and parsed a rounding mode. */ bool parseRoundingModeOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status); diff --git a/icu4c/source/test/intltest/numbertest_api.cpp b/icu4c/source/test/intltest/numbertest_api.cpp index fd9872dd99..9fe790cd4a 100644 --- a/icu4c/source/test/intltest/numbertest_api.cpp +++ b/icu4c/source/test/intltest/numbertest_api.cpp @@ -528,7 +528,7 @@ void NumberFormatterApiTest::unitMeasure() { assertFormatSingle( u"MeasureUnit form without {0} in CLDR pattern and wide base form", - u"measure-unit/temperature-kelvin .0000000000 unit-width-full-name", + u"measure-unit/temperature-kelvin .00000000000000000000 unit-width-full-name", NumberFormatter::with().rounding(Rounder::fixedFraction(20)) .unit(KELVIN) .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME), @@ -1761,8 +1761,8 @@ void NumberFormatterApiTest::sign() { u"$444,444.00"); assertFormatSingle( - u"currency/USD sign-accounting", u"Sign Accounting Negative", + u"currency/USD sign-accounting", NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING).unit(USD), Locale::getEnglish(), -444444, 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 968f7a13a5..fad64604d9 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 @@ -502,9 +502,9 @@ public class NumberFormatterApiTest { assertFormatSingle( "MeasureUnit form without {0} in CLDR pattern and wide base form", - "measure-unit/temperature-kelvin .0000000000 unit-width-full-name", + "measure-unit/temperature-kelvin .00000000000000000000 unit-width-full-name", NumberFormatter.with() - .rounding(Rounder.fixedFraction(10)) + .rounding(Rounder.fixedFraction(20)) .unit(MeasureUnit.KELVIN) .unitWidth(UnitWidth.FULL_NAME), ULocale.forLanguageTag("es-MX"), @@ -515,7 +515,7 @@ public class NumberFormatterApiTest { @Test public void unitCompoundMeasure() { assertFormatDescending( - "Meters Per Second Short (unit that simplifies)", + "Meters Per Second Short (unit that simplifies) and perUnit method", "measure-unit/length-meter per-measure-unit/duration-second", NumberFormatter.with().unit(MeasureUnit.METER).perUnit(MeasureUnit.SECOND), ULocale.ENGLISH, From 3a55650b8cfc274921c74687bdd1bcab229652bb Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Tue, 27 Mar 2018 01:58:26 +0000 Subject: [PATCH 063/129] ICU-13597 Adding initial C API for NumberFormatter. Not yet fully featured. X-SVN-Rev: 41156 --- icu4c/source/i18n/Makefile.in | 2 +- icu4c/source/i18n/number_capi.cpp | 197 ++++++ icu4c/source/i18n/number_fluent.cpp | 135 +++-- icu4c/source/i18n/number_stringbuilder.cpp | 3 +- icu4c/source/i18n/number_utils.h | 11 - icu4c/source/i18n/number_utypes.h | 79 +++ icu4c/source/i18n/unicode/numberformatter.h | 572 +++++------------- icu4c/source/i18n/unicode/unumberformatter.h | 517 ++++++++++++++++ icu4c/source/test/cintltst/Makefile.in | 3 +- icu4c/source/test/cintltst/calltest.c | 2 + icu4c/source/test/cintltst/cintltst.c | 20 + icu4c/source/test/cintltst/cintltst.h | 7 + .../test/cintltst/unumberformattertst.c | 65 ++ 13 files changed, 1109 insertions(+), 504 deletions(-) create mode 100644 icu4c/source/i18n/number_capi.cpp create mode 100644 icu4c/source/i18n/number_utypes.h create mode 100644 icu4c/source/i18n/unicode/unumberformatter.h create mode 100644 icu4c/source/test/cintltst/unumberformattertst.c diff --git a/icu4c/source/i18n/Makefile.in b/icu4c/source/i18n/Makefile.in index 0e11817b1a..95ec314cdf 100644 --- a/icu4c/source/i18n/Makefile.in +++ b/icu4c/source/i18n/Makefile.in @@ -108,7 +108,7 @@ double-conversion-cached-powers.o double-conversion-diy-fp.o double-conversion-f numparse_stringsegment.o numparse_unisets.o numparse_parsednumber.o \ numparse_impl.o numparse_symbols.o numparse_decimal.o numparse_scientific.o \ numparse_currency.o numparse_affixes.o numparse_compositions.o numparse_validators.o \ -number_mapper.o number_multiplier.o number_currencysymbols.o number_skeletons.o +number_mapper.o number_multiplier.o number_currencysymbols.o number_skeletons.o number_capi.o \ ## Header files to install diff --git a/icu4c/source/i18n/number_capi.cpp b/icu4c/source/i18n/number_capi.cpp new file mode 100644 index 0000000000..bb2d47b48f --- /dev/null +++ b/icu4c/source/i18n/number_capi.cpp @@ -0,0 +1,197 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT + +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + +#include "numparse_types.h" +#include "number_utypes.h" +#include "unicode/numberformatter.h" +#include "unicode/unumberformatter.h" + +using namespace icu; +using namespace icu::number; +using namespace icu::number::impl; + + +////////////////////////////////// +/// C API CONVERSION FUNCTIONS /// +////////////////////////////////// + +UNumberFormatterData* UNumberFormatterData::validate(UNumberFormatter* input, UErrorCode& status) { + auto* constInput = static_cast(input); + auto* validated = validate(constInput, status); + return const_cast(validated); +} + +const UNumberFormatterData* +UNumberFormatterData::validate(const UNumberFormatter* input, UErrorCode& status) { + if (U_FAILURE(status)) { + return nullptr; + } + if (input == nullptr) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return nullptr; + } + auto* impl = reinterpret_cast(input); + if (impl->fMagic != UNumberFormatterData::kMagic) { + status = U_INVALID_FORMAT_ERROR; + return nullptr; + } + return impl; +} + +UNumberFormatter* UNumberFormatterData::exportForC() { + return reinterpret_cast(this); +} + +UFormattedNumberData* UFormattedNumberData::validate(UFormattedNumber* input, UErrorCode& status) { + auto* constInput = static_cast(input); + auto* validated = validate(constInput, status); + return const_cast(validated); +} + +const UFormattedNumberData* +UFormattedNumberData::validate(const UFormattedNumber* input, UErrorCode& status) { + if (U_FAILURE(status)) { + return nullptr; + } + if (input == nullptr) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return nullptr; + } + auto* impl = reinterpret_cast(input); + if (impl->fMagic != UFormattedNumberData::kMagic) { + status = U_INVALID_FORMAT_ERROR; + return nullptr; + } + return impl; +} + +UFormattedNumber* UFormattedNumberData::exportForC() { + return reinterpret_cast(this); +} + +///////////////////////////////////// +/// END CAPI CONVERSION FUNCTIONS /// +///////////////////////////////////// + + +U_CAPI UNumberFormatter* U_EXPORT2 +unumf_openFromSkeletonAndLocale(const UChar* skeleton, int32_t skeletonLen, const char* locale, + UErrorCode* ec) { + auto* impl = new UNumberFormatterData(); + if (impl == nullptr) { + *ec = U_MEMORY_ALLOCATION_ERROR; + return nullptr; + } + // Readonly-alias constructor (first argument is whether we are NUL-terminated) + UnicodeString skeletonString(skeletonLen == -1, skeleton, skeletonLen); + impl->fFormatter = NumberFormatter::fromSkeleton(skeletonString, *ec).locale(locale); + return impl->exportForC(); +} + +U_CAPI UFormattedNumber* U_EXPORT2 +unumf_openResult(UErrorCode* ec) { + auto* impl = new UFormattedNumberData(); + if (impl == nullptr) { + *ec = U_MEMORY_ALLOCATION_ERROR; + return nullptr; + } + return impl->exportForC(); +} + +U_CAPI void U_EXPORT2 +unumf_formatInt(const UNumberFormatter* uformatter, int64_t value, UFormattedNumber* uresult, + UErrorCode* ec) { + const UNumberFormatterData* formatter = UNumberFormatterData::validate(uformatter, *ec); + UFormattedNumberData* result = UFormattedNumberData::validate(uresult, *ec); + if (U_FAILURE(*ec)) { return; } + + result->string.clear(); + result->quantity.setToLong(value); + formatter->fFormatter.formatImpl(result, *ec); +} + +U_CAPI void U_EXPORT2 +unumf_formatDouble(const UNumberFormatter* uformatter, double value, UFormattedNumber* uresult, + UErrorCode* ec) { + const UNumberFormatterData* formatter = UNumberFormatterData::validate(uformatter, *ec); + UFormattedNumberData* result = UFormattedNumberData::validate(uresult, *ec); + if (U_FAILURE(*ec)) { return; } + + result->string.clear(); + result->quantity.setToDouble(value); + formatter->fFormatter.formatImpl(result, *ec); +} + +U_CAPI void U_EXPORT2 +unumf_formatDecimal(const UNumberFormatter* uformatter, const char* value, int32_t valueLen, + UFormattedNumber* uresult, UErrorCode* ec) { + const UNumberFormatterData* formatter = UNumberFormatterData::validate(uformatter, *ec); + UFormattedNumberData* result = UFormattedNumberData::validate(uresult, *ec); + if (U_FAILURE(*ec)) { return; } + + result->string.clear(); + result->quantity.setToDecNumber({value, valueLen}, *ec); + if (U_FAILURE(*ec)) { return; } + formatter->fFormatter.formatImpl(result, *ec); +} + +U_CAPI int32_t U_EXPORT2 +unumf_resultToString(const UFormattedNumber* uresult, UChar* buffer, int32_t bufferCapacity, + UErrorCode* ec) { + const UFormattedNumberData* result = UFormattedNumberData::validate(uresult, *ec); + if (U_FAILURE(*ec)) { return 0; } + + return result->string.toUnicodeString().extract(buffer, bufferCapacity, *ec); +} + +U_CAPI void U_EXPORT2 +unumf_closeResult(const UFormattedNumber* uresult, UErrorCode* ec) { + const UFormattedNumberData* impl = UFormattedNumberData::validate(uresult, *ec); + if (U_FAILURE(*ec)) { return; } + delete impl; +} + +U_CAPI void U_EXPORT2 +unumf_close(UNumberFormatter* f, UErrorCode* ec) { + const UNumberFormatterData* impl = UNumberFormatterData::validate(f, *ec); + if (U_FAILURE(*ec)) { return; } + delete impl; +} + + +#endif /* #if !UCONFIG_NO_FORMATTING */ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icu4c/source/i18n/number_fluent.cpp b/icu4c/source/i18n/number_fluent.cpp index e0ba258e3c..c5feb5e438 100644 --- a/icu4c/source/i18n/number_fluent.cpp +++ b/icu4c/source/i18n/number_fluent.cpp @@ -12,13 +12,14 @@ #include "number_formatimpl.h" #include "umutex.h" #include "number_skeletons.h" +#include "number_utypes.h" using namespace icu; using namespace icu::number; using namespace icu::number::impl; template -Derived NumberFormatterSettings::notation(const Notation& notation) const & { +Derived NumberFormatterSettings::notation(const Notation& notation) const& { Derived copy(*this); // NOTE: Slicing is OK. copy.fMacros.notation = notation; @@ -26,7 +27,7 @@ Derived NumberFormatterSettings::notation(const Notation& notation) con } template -Derived NumberFormatterSettings::notation(const Notation& notation) && { +Derived NumberFormatterSettings::notation(const Notation& notation)&& { Derived move(std::move(*this)); // NOTE: Slicing is OK. move.fMacros.notation = notation; @@ -34,7 +35,7 @@ Derived NumberFormatterSettings::notation(const Notation& notation) && } template -Derived NumberFormatterSettings::unit(const icu::MeasureUnit& unit) const & { +Derived NumberFormatterSettings::unit(const icu::MeasureUnit& unit) const& { Derived copy(*this); // NOTE: Slicing occurs here. However, CurrencyUnit can be restored from MeasureUnit. // TimeUnit may be affected, but TimeUnit is not as relevant to number formatting. @@ -43,7 +44,7 @@ Derived NumberFormatterSettings::unit(const icu::MeasureUnit& unit) con } template -Derived NumberFormatterSettings::unit(const icu::MeasureUnit& unit) && { +Derived NumberFormatterSettings::unit(const icu::MeasureUnit& unit)&& { Derived move(std::move(*this)); // See comments above about slicing. move.fMacros.unit = unit; @@ -51,7 +52,7 @@ Derived NumberFormatterSettings::unit(const icu::MeasureUnit& unit) && } template -Derived NumberFormatterSettings::adoptUnit(icu::MeasureUnit* unit) const & { +Derived NumberFormatterSettings::adoptUnit(icu::MeasureUnit* unit) const& { Derived copy(*this); // Just move the unit into the MacroProps by value, and delete it since we have ownership. // NOTE: Slicing occurs here. However, CurrencyUnit can be restored from MeasureUnit. @@ -65,7 +66,7 @@ Derived NumberFormatterSettings::adoptUnit(icu::MeasureUnit* unit) cons } template -Derived NumberFormatterSettings::adoptUnit(icu::MeasureUnit* unit) && { +Derived NumberFormatterSettings::adoptUnit(icu::MeasureUnit* unit)&& { Derived move(std::move(*this)); // See comments above about slicing and ownership. if (unit != nullptr) { @@ -77,7 +78,7 @@ Derived NumberFormatterSettings::adoptUnit(icu::MeasureUnit* unit) && { } template -Derived NumberFormatterSettings::perUnit(const icu::MeasureUnit& perUnit) const & { +Derived NumberFormatterSettings::perUnit(const icu::MeasureUnit& perUnit) const& { Derived copy(*this); // See comments above about slicing. copy.fMacros.perUnit = perUnit; @@ -85,7 +86,7 @@ Derived NumberFormatterSettings::perUnit(const icu::MeasureUnit& perUni } template -Derived NumberFormatterSettings::perUnit(const icu::MeasureUnit& perUnit) && { +Derived NumberFormatterSettings::perUnit(const icu::MeasureUnit& perUnit)&& { Derived copy(*this); // See comments above about slicing. copy.fMacros.perUnit = perUnit; @@ -93,7 +94,7 @@ Derived NumberFormatterSettings::perUnit(const icu::MeasureUnit& perUni } template -Derived NumberFormatterSettings::adoptPerUnit(icu::MeasureUnit* perUnit) const & { +Derived NumberFormatterSettings::adoptPerUnit(icu::MeasureUnit* perUnit) const& { Derived move(std::move(*this)); // See comments above about slicing and ownership. if (perUnit != nullptr) { @@ -105,7 +106,7 @@ Derived NumberFormatterSettings::adoptPerUnit(icu::MeasureUnit* perUnit } template -Derived NumberFormatterSettings::adoptPerUnit(icu::MeasureUnit* perUnit) && { +Derived NumberFormatterSettings::adoptPerUnit(icu::MeasureUnit* perUnit)&& { Derived copy(*this); // See comments above about slicing and ownership. if (perUnit != nullptr) { @@ -117,7 +118,7 @@ Derived NumberFormatterSettings::adoptPerUnit(icu::MeasureUnit* perUnit } template -Derived NumberFormatterSettings::rounding(const Rounder& rounder) const & { +Derived NumberFormatterSettings::rounding(const Rounder& rounder) const& { Derived copy(*this); // NOTE: Slicing is OK. copy.fMacros.rounder = rounder; @@ -125,7 +126,7 @@ Derived NumberFormatterSettings::rounding(const Rounder& rounder) const } template -Derived NumberFormatterSettings::rounding(const Rounder& rounder) && { +Derived NumberFormatterSettings::rounding(const Rounder& rounder)&& { Derived move(std::move(*this)); // NOTE: Slicing is OK. move.fMacros.rounder = rounder; @@ -133,7 +134,7 @@ Derived NumberFormatterSettings::rounding(const Rounder& rounder) && { } template -Derived NumberFormatterSettings::grouping(const UGroupingStrategy& strategy) const & { +Derived NumberFormatterSettings::grouping(const UGroupingStrategy& strategy) const& { Derived copy(*this); // NOTE: This is slightly different than how the setting is stored in Java // because we want to put it on the stack. @@ -142,147 +143,147 @@ Derived NumberFormatterSettings::grouping(const UGroupingStrategy& stra } template -Derived NumberFormatterSettings::grouping(const UGroupingStrategy& strategy) && { +Derived NumberFormatterSettings::grouping(const UGroupingStrategy& strategy)&& { Derived move(std::move(*this)); move.fMacros.grouper = Grouper::forStrategy(strategy); return move; } template -Derived NumberFormatterSettings::integerWidth(const IntegerWidth& style) const & { +Derived NumberFormatterSettings::integerWidth(const IntegerWidth& style) const& { Derived copy(*this); copy.fMacros.integerWidth = style; return copy; } template -Derived NumberFormatterSettings::integerWidth(const IntegerWidth& style) && { +Derived NumberFormatterSettings::integerWidth(const IntegerWidth& style)&& { Derived move(std::move(*this)); move.fMacros.integerWidth = style; return move; } template -Derived NumberFormatterSettings::symbols(const DecimalFormatSymbols& symbols) const & { +Derived NumberFormatterSettings::symbols(const DecimalFormatSymbols& symbols) const& { Derived copy(*this); copy.fMacros.symbols.setTo(symbols); return copy; } template -Derived NumberFormatterSettings::symbols(const DecimalFormatSymbols& symbols) && { +Derived NumberFormatterSettings::symbols(const DecimalFormatSymbols& symbols)&& { Derived move(std::move(*this)); move.fMacros.symbols.setTo(symbols); return move; } template -Derived NumberFormatterSettings::adoptSymbols(NumberingSystem* ns) const & { +Derived NumberFormatterSettings::adoptSymbols(NumberingSystem* ns) const& { Derived copy(*this); copy.fMacros.symbols.setTo(ns); return copy; } template -Derived NumberFormatterSettings::adoptSymbols(NumberingSystem* ns) && { +Derived NumberFormatterSettings::adoptSymbols(NumberingSystem* ns)&& { Derived move(std::move(*this)); move.fMacros.symbols.setTo(ns); return move; } template -Derived NumberFormatterSettings::unitWidth(const UNumberUnitWidth& width) const & { +Derived NumberFormatterSettings::unitWidth(const UNumberUnitWidth& width) const& { Derived copy(*this); copy.fMacros.unitWidth = width; return copy; } template -Derived NumberFormatterSettings::unitWidth(const UNumberUnitWidth& width) && { +Derived NumberFormatterSettings::unitWidth(const UNumberUnitWidth& width)&& { Derived move(std::move(*this)); move.fMacros.unitWidth = width; return move; } template -Derived NumberFormatterSettings::sign(const UNumberSignDisplay& style) const & { +Derived NumberFormatterSettings::sign(const UNumberSignDisplay& style) const& { Derived copy(*this); copy.fMacros.sign = style; return copy; } template -Derived NumberFormatterSettings::sign(const UNumberSignDisplay& style) && { +Derived NumberFormatterSettings::sign(const UNumberSignDisplay& style)&& { Derived move(std::move(*this)); move.fMacros.sign = style; return move; } template -Derived NumberFormatterSettings::decimal(const UNumberDecimalSeparatorDisplay& style) const & { +Derived NumberFormatterSettings::decimal(const UNumberDecimalSeparatorDisplay& style) const& { Derived copy(*this); copy.fMacros.decimal = style; return copy; } template -Derived NumberFormatterSettings::decimal(const UNumberDecimalSeparatorDisplay& style) && { +Derived NumberFormatterSettings::decimal(const UNumberDecimalSeparatorDisplay& style)&& { Derived move(std::move(*this)); move.fMacros.decimal = style; return move; } template -Derived NumberFormatterSettings::padding(const Padder& padder) const & { +Derived NumberFormatterSettings::padding(const Padder& padder) const& { Derived copy(*this); copy.fMacros.padder = padder; return copy; } template -Derived NumberFormatterSettings::padding(const Padder& padder) && { +Derived NumberFormatterSettings::padding(const Padder& padder)&& { Derived move(std::move(*this)); move.fMacros.padder = padder; return move; } template -Derived NumberFormatterSettings::threshold(int32_t threshold) const & { +Derived NumberFormatterSettings::threshold(int32_t threshold) const& { Derived copy(*this); copy.fMacros.threshold = threshold; return copy; } template -Derived NumberFormatterSettings::threshold(int32_t threshold) && { +Derived NumberFormatterSettings::threshold(int32_t threshold)&& { Derived move(std::move(*this)); move.fMacros.threshold = threshold; return move; } template -Derived NumberFormatterSettings::macros(const impl::MacroProps& macros) const & { +Derived NumberFormatterSettings::macros(const impl::MacroProps& macros) const& { Derived copy(*this); copy.fMacros = macros; return copy; } template -Derived NumberFormatterSettings::macros(const impl::MacroProps& macros) && { +Derived NumberFormatterSettings::macros(const impl::MacroProps& macros)&& { Derived move(std::move(*this)); move.fMacros = macros; return move; } template -Derived NumberFormatterSettings::macros(impl::MacroProps&& macros) const & { +Derived NumberFormatterSettings::macros(impl::MacroProps&& macros) const& { Derived copy(*this); copy.fMacros = std::move(macros); return copy; } template -Derived NumberFormatterSettings::macros(impl::MacroProps&& macros) && { +Derived NumberFormatterSettings::macros(impl::MacroProps&& macros)&& { Derived move(std::move(*this)); move.fMacros = std::move(macros); return move; @@ -316,8 +317,7 @@ NumberFormatter::fromSkeleton(const UnicodeString& skeleton, UErrorCode& status) } -template -using NFS = NumberFormatterSettings; +template using NFS = NumberFormatterSettings; using LNF = LocalizedNumberFormatter; using UNF = UnlocalizedNumberFormatter; @@ -403,11 +403,11 @@ LocalizedNumberFormatter::LocalizedNumberFormatter(MacroProps&& macros, const Lo fMacros.locale = locale; } -LocalizedNumberFormatter UnlocalizedNumberFormatter::locale(const Locale& locale) const & { +LocalizedNumberFormatter UnlocalizedNumberFormatter::locale(const Locale& locale) const& { return LocalizedNumberFormatter(fMacros, locale); } -LocalizedNumberFormatter UnlocalizedNumberFormatter::locale(const Locale& locale) && { +LocalizedNumberFormatter UnlocalizedNumberFormatter::locale(const Locale& locale)&& { return LocalizedNumberFormatter(std::move(fMacros), locale); } @@ -547,51 +547,82 @@ FormattedNumber& FormattedNumber::operator=(FormattedNumber&& src) U_NOEXCEPT { FormattedNumber LocalizedNumberFormatter::formatInt(int64_t value, UErrorCode& status) const { if (U_FAILURE(status)) { return FormattedNumber(U_ILLEGAL_ARGUMENT_ERROR); } - auto results = new NumberFormatterResults(); + auto results = new UFormattedNumberData(); if (results == nullptr) { status = U_MEMORY_ALLOCATION_ERROR; return FormattedNumber(status); } results->quantity.setToLong(value); - return formatImpl(results, status); + formatImpl(results, status); + + // Do not save the results object if we encountered a failure. + if (U_SUCCESS(status)) { + return FormattedNumber(results); + } else { + delete results; + return FormattedNumber(status); + } } FormattedNumber LocalizedNumberFormatter::formatDouble(double value, UErrorCode& status) const { if (U_FAILURE(status)) { return FormattedNumber(U_ILLEGAL_ARGUMENT_ERROR); } - auto results = new NumberFormatterResults(); + auto results = new UFormattedNumberData(); if (results == nullptr) { status = U_MEMORY_ALLOCATION_ERROR; return FormattedNumber(status); } results->quantity.setToDouble(value); - return formatImpl(results, status); + formatImpl(results, status); + + // Do not save the results object if we encountered a failure. + if (U_SUCCESS(status)) { + return FormattedNumber(results); + } else { + delete results; + return FormattedNumber(status); + } } FormattedNumber LocalizedNumberFormatter::formatDecimal(StringPiece value, UErrorCode& status) const { if (U_FAILURE(status)) { return FormattedNumber(U_ILLEGAL_ARGUMENT_ERROR); } - auto results = new NumberFormatterResults(); + auto results = new UFormattedNumberData(); if (results == nullptr) { status = U_MEMORY_ALLOCATION_ERROR; return FormattedNumber(status); } results->quantity.setToDecNumber(value, status); - return formatImpl(results, status); + formatImpl(results, status); + + // Do not save the results object if we encountered a failure. + if (U_SUCCESS(status)) { + return FormattedNumber(results); + } else { + delete results; + return FormattedNumber(status); + } } FormattedNumber LocalizedNumberFormatter::formatDecimalQuantity(const DecimalQuantity& dq, UErrorCode& status) const { if (U_FAILURE(status)) { return FormattedNumber(U_ILLEGAL_ARGUMENT_ERROR); } - auto results = new NumberFormatterResults(); + auto results = new UFormattedNumberData(); if (results == nullptr) { status = U_MEMORY_ALLOCATION_ERROR; return FormattedNumber(status); } results->quantity = dq; - return formatImpl(results, status); + formatImpl(results, status); + + // Do not save the results object if we encountered a failure. + if (U_SUCCESS(status)) { + return FormattedNumber(results); + } else { + delete results; + return FormattedNumber(status); + } } -FormattedNumber -LocalizedNumberFormatter::formatImpl(impl::NumberFormatterResults* results, UErrorCode& status) const { +void LocalizedNumberFormatter::formatImpl(impl::UFormattedNumberData* results, UErrorCode& status) const { // fUnsafeCallCount contains memory to be interpreted as an atomic int, most commonly // std::atomic. Since the type of atomic int is platform-dependent, we cast the // bytes in fUnsafeCallCount to u_atomic_int32_t, a typedef for the platform-dependent @@ -627,14 +658,6 @@ LocalizedNumberFormatter::formatImpl(impl::NumberFormatterResults* results, UErr // Format the number without building the data structure (slow path). NumberFormatterImpl::applyStatic(fMacros, results->quantity, results->string, status); } - - // Do not save the results object if we encountered a failure. - if (U_SUCCESS(status)) { - return FormattedNumber(results); - } else { - delete results; - return FormattedNumber(status); - } } const impl::NumberFormatterImpl* LocalizedNumberFormatter::getCompiled() const { diff --git a/icu4c/source/i18n/number_stringbuilder.cpp b/icu4c/source/i18n/number_stringbuilder.cpp index 37159d7e53..2759ed4568 100644 --- a/icu4c/source/i18n/number_stringbuilder.cpp +++ b/icu4c/source/i18n/number_stringbuilder.cpp @@ -334,7 +334,8 @@ int32_t NumberStringBuilder::remove(int32_t index, int32_t count) { } UnicodeString NumberStringBuilder::toUnicodeString() const { - return UnicodeString(getCharPtr() + fZero, fLength); + // Readonly-alias constructor: + return UnicodeString(FALSE, getCharPtr() + fZero, fLength); } UnicodeString NumberStringBuilder::toDebugString() const { diff --git a/icu4c/source/i18n/number_utils.h b/icu4c/source/i18n/number_utils.h index 6ca205dc2b..60879db085 100644 --- a/icu4c/source/i18n/number_utils.h +++ b/icu4c/source/i18n/number_utils.h @@ -102,17 +102,6 @@ struct MicroProps : public MicroPropsGenerator { bool exhausted = false; }; -/** - * This struct provides the result of the number formatting pipeline to FormattedNumber. - * - * The DecimalQuantity is not currently being used by FormattedNumber, but at some point it could be used - * to add a toDecNumber() or similar method. - */ -struct NumberFormatterResults : public UMemory { - DecimalQuantity quantity; - NumberStringBuilder string; -}; - inline int32_t insertDigitFromSymbols(NumberStringBuilder& output, int32_t index, int8_t digit, const DecimalFormatSymbols& symbols, Field field, UErrorCode& status) { diff --git a/icu4c/source/i18n/number_utypes.h b/icu4c/source/i18n/number_utypes.h new file mode 100644 index 0000000000..bff097eff1 --- /dev/null +++ b/icu4c/source/i18n/number_utypes.h @@ -0,0 +1,79 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#ifndef __SOURCE_NUMBER_UTYPES_H__ +#define __SOURCE_NUMBER_UTYPES_H__ + +#include "unicode/numberformatter.h" +#include "number_types.h" +#include "number_decimalquantity.h" +#include "number_stringbuilder.h" + +U_NAMESPACE_BEGIN namespace number { +namespace impl { + + +/** + * Implementation class for UNumberFormatter with a magic number for safety. + * + * Wraps a LocalizedNumberFormatter by value. + */ +struct UNumberFormatterData : public UMemory { + // The magic number to identify incoming objects. + // Reads in ASCII as "NFR" (NumberFormatteR with room at the end) + static constexpr int32_t kMagic = 0x4E465200; + + // Data members: + int32_t fMagic = kMagic; + LocalizedNumberFormatter fFormatter; + + /** Convert from UNumberFormatter -> UNumberFormatterData. */ + static UNumberFormatterData* validate(UNumberFormatter* input, UErrorCode& status); + + /** Convert from UNumberFormatter -> UNumberFormatterData (const version). */ + static const UNumberFormatterData* validate(const UNumberFormatter* input, UErrorCode& status); + + /** Convert from UNumberFormatterData -> UNumberFormatter. */ + UNumberFormatter* exportForC(); +}; + + +/** + * Implementation class for UFormattedNumber with magic number for safety. + * + * This struct is also held internally by the C++ version FormattedNumber since the member types are not + * declared in the public header file. + * + * The DecimalQuantity is not currently being used by FormattedNumber, but at some point it could be used + * to add a toDecNumber() or similar method. + */ +struct UFormattedNumberData : public UMemory { + // The magic number to identify incoming objects. + // Reads in ASCII as "FDN" (FormatteDNumber with room at the end) + static constexpr int32_t kMagic = 0x46444E00; + + // Data members: + int32_t fMagic = kMagic; + DecimalQuantity quantity; + NumberStringBuilder string; + + /** Convert from UFormattedNumber -> UFormattedNumberData. */ + static UFormattedNumberData* validate(UFormattedNumber* input, UErrorCode& status); + + /** Convert from UFormattedNumber -> UFormattedNumberData (const version). */ + static const UFormattedNumberData* validate(const UFormattedNumber* input, UErrorCode& status); + + /** Convert from UFormattedNumberData -> UFormattedNumber. */ + UFormattedNumber* exportForC(); +}; + + +} // namespace impl +} // namespace number +U_NAMESPACE_END + +#endif //__SOURCE_NUMBER_UTYPES_H__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/unicode/numberformatter.h b/icu4c/source/i18n/unicode/numberformatter.h index c0131eaa71..f1bb5316f1 100644 --- a/icu4c/source/i18n/unicode/numberformatter.h +++ b/icu4c/source/i18n/unicode/numberformatter.h @@ -13,6 +13,7 @@ #include "unicode/fieldpos.h" #include "unicode/fpositer.h" #include "unicode/measunit.h" +#include "unicode/unumberformatter.h" #include "unicode/nounit.h" #include "unicode/plurrule.h" #include "unicode/ucurr.h" @@ -75,319 +76,6 @@ * @author Shane Carr */ -/** - * An enum declaring how to render units, including currencies. Example outputs when formatting 123 USD and 123 - * meters in en-CA: - * - *

    - *

      - *
    • NARROW*: "$123.00" and "123 m" - *
    • SHORT: "US$ 123.00" and "123 m" - *
    • FULL_NAME: "123.00 US dollars" and "123 meters" - *
    • ISO_CODE: "USD 123.00" and undefined behavior - *
    • HIDDEN: "123.00" and "123" - *
    - * - *

    - * This enum is similar to {@link com.ibm.icu.text.MeasureFormat.FormatWidth}. - * - * @draft ICU 60 - */ -typedef enum UNumberUnitWidth { - /** - * Print an abbreviated version of the unit name. Similar to SHORT, but always use the shortest available - * abbreviation or symbol. This option can be used when the context hints at the identity of the unit. For more - * information on the difference between NARROW and SHORT, see SHORT. - * - *

    - * In CLDR, this option corresponds to the "Narrow" format for measure units and the "¤¤¤¤¤" placeholder for - * currencies. - * - * @draft ICU 60 - */ - UNUM_UNIT_WIDTH_NARROW, - - /** - * Print an abbreviated version of the unit name. Similar to NARROW, but use a slightly wider abbreviation or - * symbol when there may be ambiguity. This is the default behavior. - * - *

    - * For example, in es-US, the SHORT form for Fahrenheit is "{0} °F", but the NARROW form is "{0}°", - * since Fahrenheit is the customary unit for temperature in that locale. - * - *

    - * In CLDR, this option corresponds to the "Short" format for measure units and the "¤" placeholder for - * currencies. - * - * @draft ICU 60 - */ - UNUM_UNIT_WIDTH_SHORT, - - /** - * Print the full name of the unit, without any abbreviations. - * - *

    - * In CLDR, this option corresponds to the default format for measure units and the "¤¤¤" placeholder for - * currencies. - * - * @draft ICU 60 - */ - UNUM_UNIT_WIDTH_FULL_NAME, - - /** - * Use the three-digit ISO XXX code in place of the symbol for displaying currencies. The behavior of this - * option is currently undefined for use with measure units. - * - *

    - * In CLDR, this option corresponds to the "¤¤" placeholder for currencies. - * - * @draft ICU 60 - */ - UNUM_UNIT_WIDTH_ISO_CODE, - - /** - * Format the number according to the specified unit, but do not display the unit. For currencies, apply - * monetary symbols and formats as with SHORT, but omit the currency symbol. For measure units, the behavior is - * equivalent to not specifying the unit at all. - * - * @draft ICU 60 - */ - UNUM_UNIT_WIDTH_HIDDEN, - - /** - * One more than the highest UNumberUnitWidth value. - * - * @internal ICU 60: The numeric value may change over time; see ICU ticket #12420. - */ - UNUM_UNIT_WIDTH_COUNT -} UNumberUnitWidth; - -/** - * An enum declaring the strategy for when and how to display grouping separators (i.e., the - * separator, often a comma or period, after every 2-3 powers of ten). The choices are several - * pre-built strategies for different use cases that employ locale data whenever possible. Example - * outputs for 1234 and 1234567 in en-IN: - * - *

      - *
    • OFF: 1234 and 12345 - *
    • MIN2: 1234 and 12,34,567 - *
    • AUTO: 1,234 and 12,34,567 - *
    • ON_ALIGNED: 1,234 and 12,34,567 - *
    • THOUSANDS: 1,234 and 1,234,567 - *
    - * - *

    - * The default is AUTO, which displays grouping separators unless the locale data says that grouping - * is not customary. To force grouping for all numbers greater than 1000 consistently across locales, - * use ON_ALIGNED. On the other hand, to display grouping less frequently than the default, use MIN2 - * or OFF. See the docs of each option for details. - * - *

    - * Note: This enum specifies the strategy for grouping sizes. To set which character to use as the - * grouping separator, use the "symbols" setter. - * - * @draft ICU 61 -- TODO: This should be renamed to UNumberGroupingStrategy before promoting to stable, - * for consistency with the other enums. - */ -typedef enum UGroupingStrategy { - /** - * Do not display grouping separators in any locale. - * - * @draft ICU 61 - */ - UNUM_GROUPING_OFF, - - /** - * Display grouping using locale defaults, except do not show grouping on values smaller than - * 10000 (such that there is a minimum of two digits before the first separator). - * - *

    - * Note that locales may restrict grouping separators to be displayed only on 1 million or - * greater (for example, ee and hu) or disable grouping altogether (for example, bg currency). - * - *

    - * Locale data is used to determine whether to separate larger numbers into groups of 2 - * (customary in South Asia) or groups of 3 (customary in Europe and the Americas). - * - * @draft ICU 61 - */ - UNUM_GROUPING_MIN2, - - /** - * Display grouping using the default strategy for all locales. This is the default behavior. - * - *

    - * Note that locales may restrict grouping separators to be displayed only on 1 million or - * greater (for example, ee and hu) or disable grouping altogether (for example, bg currency). - * - *

    - * Locale data is used to determine whether to separate larger numbers into groups of 2 - * (customary in South Asia) or groups of 3 (customary in Europe and the Americas). - * - * @draft ICU 61 - */ - UNUM_GROUPING_AUTO, - - /** - * Always display the grouping separator on values of at least 1000. - * - *

    - * This option ignores the locale data that restricts or disables grouping, described in MIN2 and - * AUTO. This option may be useful to normalize the alignment of numbers, such as in a - * spreadsheet. - * - *

    - * Locale data is used to determine whether to separate larger numbers into groups of 2 - * (customary in South Asia) or groups of 3 (customary in Europe and the Americas). - * - * @draft ICU 61 - */ - UNUM_GROUPING_ON_ALIGNED, - - /** - * Use the Western defaults: groups of 3 and enabled for all numbers 1000 or greater. Do not use - * locale data for determining the grouping strategy. - * - * @draft ICU 61 - */ - UNUM_GROUPING_THOUSANDS, - - /** - * One more than the highest UNumberSignDisplay value. - * - * @internal ICU 62: The numeric value may change over time; see ICU ticket #12420. - */ - UNUM_GROUPING_COUNT - -} UGroupingStrategy; - -/** - * An enum declaring how to denote positive and negative numbers. Example outputs when formatting - * 123, 0, and -123 in en-US: - * - *

      - *
    • AUTO: "123", "0", and "-123" - *
    • ALWAYS: "+123", "+0", and "-123" - *
    • NEVER: "123", "0", and "123" - *
    • ACCOUNTING: "$123", "$0", and "($123)" - *
    • ACCOUNTING_ALWAYS: "+$123", "+$0", and "($123)" - *
    • EXCEPT_ZERO: "+123", "0", and "-123" - *
    • ACCOUNTING_EXCEPT_ZERO: "+$123", "$0", and "($123)" - *
    - * - *

    - * The exact format, including the position and the code point of the sign, differ by locale. - * - * @draft ICU 60 - */ -typedef enum UNumberSignDisplay { - /** - * Show the minus sign on negative numbers, and do not show the sign on positive numbers. This is the default - * behavior. - * - * @draft ICU 60 - */ - UNUM_SIGN_AUTO, - - /** - * 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, - - /** - * Do not show the sign on positive or negative numbers. - * - * @draft ICU 60 - */ - UNUM_SIGN_NEVER, - - /** - * Use the locale-dependent accounting format on negative numbers, and do not show the sign on positive numbers. - * - *

    - * The accounting format is defined in CLDR and varies by locale; in many Western locales, the format is a pair - * of parentheses around the number. - * - *

    - * Note: Since CLDR defines the accounting format in the monetary context only, this option falls back to the - * AUTO sign display strategy when formatting without a currency unit. This limitation may be lifted in the - * future. - * - * @draft ICU 60 - */ - UNUM_SIGN_ACCOUNTING, - - /** - * 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, - - /** - * 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 -} UNumberSignDisplay; - -/** - * An enum declaring how to render the decimal separator. - * - *

    - *

      - *
    • UNUM_DECIMAL_SEPARATOR_AUTO: "1", "1.1" - *
    • UNUM_DECIMAL_SEPARATOR_ALWAYS: "1.", "1.1" - *
    - */ -typedef enum UNumberDecimalSeparatorDisplay { - /** - * Show the decimal separator when there are one or more digits to display after the separator, and do not show - * it otherwise. This is the default behavior. - * - * @draft ICU 60 - */ - UNUM_DECIMAL_SEPARATOR_AUTO, - - /** - * Always show the decimal separator, even if there are no digits to display after the separator. - * - * @draft ICU 60 - */ - UNUM_DECIMAL_SEPARATOR_ALWAYS, - - /** - * One more than the highest UNumberDecimalSeparatorDisplay value. - * - * @internal ICU 60: The numeric value may change over time; see ICU ticket #12420. - */ - UNUM_DECIMAL_SEPARATOR_COUNT -} UNumberDecimalSeparatorDisplay; - U_NAMESPACE_BEGIN // Forward declarations: @@ -438,7 +126,7 @@ class Padder; struct MacroProps; struct MicroProps; class DecimalQuantity; -struct NumberFormatterResults; +struct UFormattedNumberData; class NumberFormatterImpl; struct ParsedPatternInfo; class ScientificModifier; @@ -635,15 +323,18 @@ class U_I18N_API Notation : public UMemory { typedef NotationUnion::ScientificSettings ScientificSettings; - Notation(const NotationType &type, const NotationUnion &union_) : fType(type), fUnion(union_) {} + Notation(const NotationType& type, const NotationUnion& union_) + : fType(type), fUnion(union_) {} - Notation(UErrorCode errorCode) : fType(NTN_ERROR) { + Notation(UErrorCode errorCode) + : fType(NTN_ERROR) { fUnion.errorCode = errorCode; } - Notation() : fType(NTN_SIMPLE), fUnion() {} + Notation() + : fType(NTN_SIMPLE), fUnion() {} - UBool copyErrorTo(UErrorCode &status) const { + UBool copyErrorTo(UErrorCode& status) const { if (fType == NTN_ERROR) { status = fUnion.errorCode; return TRUE; @@ -966,20 +657,22 @@ class U_I18N_API Rounder : public UMemory { UNumberFormatRoundingMode fRoundingMode; - Rounder(const RounderType &type, const RounderUnion &union_, UNumberFormatRoundingMode roundingMode) + Rounder(const RounderType& type, const RounderUnion& union_, UNumberFormatRoundingMode roundingMode) : fType(type), fUnion(union_), fRoundingMode(roundingMode) {} - Rounder(UErrorCode errorCode) : fType(RND_ERROR) { + Rounder(UErrorCode errorCode) + : fType(RND_ERROR) { fUnion.errorCode = errorCode; } - Rounder() : fType(RND_BOGUS) {} + Rounder() + : fType(RND_BOGUS) {} bool isBogus() const { return fType == RND_BOGUS; } - UBool copyErrorTo(UErrorCode &status) const { + UBool copyErrorTo(UErrorCode& status) const { if (fType == RND_ERROR) { status = fUnion.errorCode; return TRUE; @@ -988,15 +681,15 @@ class U_I18N_API Rounder : public UMemory { } // On the parent type so that this method can be called internally on Rounder instances. - Rounder withCurrency(const CurrencyUnit ¤cy, UErrorCode &status) const; + Rounder withCurrency(const CurrencyUnit& currency, UErrorCode& status) const; /** NON-CONST: mutates the current instance. */ - void setLocaleData(const CurrencyUnit ¤cy, UErrorCode &status); + void setLocaleData(const CurrencyUnit& currency, UErrorCode& status); - void apply(impl::DecimalQuantity &value, UErrorCode &status) const; + void apply(impl::DecimalQuantity& value, UErrorCode& status) const; /** Version of {@link #apply} that obeys minInt constraints. Used for scientific notation compatibility mode. */ - void apply(impl::DecimalQuantity &value, int32_t minInt, UErrorCode status); + void apply(impl::DecimalQuantity& value, int32_t minInt, UErrorCode status); /** * Rounding endpoint used by Engineering and Compact notation. Chooses the most appropriate multiplier (magnitude @@ -1012,16 +705,15 @@ class U_I18N_API Rounder : public UMemory { * @param producer Function to call to return a multiplier based on a magnitude. * @return The number of orders of magnitude the input was adjusted by this method. */ - int32_t - chooseMultiplierAndApply(impl::DecimalQuantity &input, const impl::MultiplierProducer &producer, - UErrorCode &status); + int32_t chooseMultiplierAndApply(impl::DecimalQuantity& input, + const impl::MultiplierProducer& producer, UErrorCode& status); static FractionRounder constructFraction(int32_t minFrac, int32_t maxFrac); static Rounder constructSignificant(int32_t minSig, int32_t maxSig); - static Rounder - constructFractionSignificant(const FractionRounder &base, int32_t minSig, int32_t maxSig); + static Rounder constructFractionSignificant(const FractionRounder& base, int32_t minSig, + int32_t maxSig); static IncrementRounder constructIncrement(double increment, int32_t minFrac); @@ -1138,7 +830,7 @@ class U_I18N_API CurrencyRounder : public Rounder { * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. * @draft ICU 60 */ - Rounder withCurrency(const CurrencyUnit ¤cy) const; + Rounder withCurrency(const CurrencyUnit& currency) const; private: // Inherit constructor @@ -1253,7 +945,7 @@ class U_I18N_API IntegerWidth : public UMemory { return !fHasError && fUnion.minMaxInt.fMinInt == -1; } - UBool copyErrorTo(UErrorCode &status) const { + UBool copyErrorTo(UErrorCode& status) const { if (fHasError) { status = fUnion.errorCode; return TRUE; @@ -1261,7 +953,7 @@ class U_I18N_API IntegerWidth : public UMemory { return FALSE; } - void apply(impl::DecimalQuantity &quantity, UErrorCode &status) const; + void apply(impl::DecimalQuantity& quantity, UErrorCode& status) const; bool operator==(const IntegerWidth& other) const; @@ -1282,10 +974,11 @@ namespace impl { class U_I18N_API SymbolsWrapper : public UMemory { public: /** @internal */ - SymbolsWrapper() : fType(SYMPTR_NONE), fPtr{nullptr} {} + SymbolsWrapper() + : fType(SYMPTR_NONE), fPtr{nullptr} {} /** @internal */ - SymbolsWrapper(const SymbolsWrapper &other); + SymbolsWrapper(const SymbolsWrapper& other); /** @internal */ SymbolsWrapper(SymbolsWrapper&& src) U_NOEXCEPT; @@ -1294,22 +987,22 @@ class U_I18N_API SymbolsWrapper : public UMemory { ~SymbolsWrapper(); /** @internal */ - SymbolsWrapper &operator=(const SymbolsWrapper &other); + SymbolsWrapper& operator=(const SymbolsWrapper& other); /** @internal */ - SymbolsWrapper &operator=(SymbolsWrapper&& src) U_NOEXCEPT; + SymbolsWrapper& operator=(SymbolsWrapper&& src) U_NOEXCEPT; /** * The provided object is copied, but we do not adopt it. * @internal */ - void setTo(const DecimalFormatSymbols &dfs); + void setTo(const DecimalFormatSymbols& dfs); /** * Adopt the provided object. * @internal */ - void setTo(const NumberingSystem *ns); + void setTo(const NumberingSystem* ns); /** * Whether the object is currently holding a DecimalFormatSymbols. @@ -1327,16 +1020,16 @@ class U_I18N_API SymbolsWrapper : public UMemory { * Get the DecimalFormatSymbols pointer. No ownership change. * @internal */ - const DecimalFormatSymbols *getDecimalFormatSymbols() const; + const DecimalFormatSymbols* getDecimalFormatSymbols() const; /** * Get the NumberingSystem pointer. No ownership change. * @internal */ - const NumberingSystem *getNumberingSystem() const; + const NumberingSystem* getNumberingSystem() const; /** @internal */ - UBool copyErrorTo(UErrorCode &status) const { + UBool copyErrorTo(UErrorCode& status) const { if (fType == SYMPTR_DFS && fPtr.dfs == nullptr) { status = U_MEMORY_ALLOCATION_ERROR; return TRUE; @@ -1353,11 +1046,11 @@ class U_I18N_API SymbolsWrapper : public UMemory { } fType; union { - const DecimalFormatSymbols *dfs; - const NumberingSystem *ns; + const DecimalFormatSymbols* dfs; + const NumberingSystem* ns; } fPtr; - void doCopyFrom(const SymbolsWrapper &other); + void doCopyFrom(const SymbolsWrapper& other); void doMoveFrom(SymbolsWrapper&& src); @@ -1418,16 +1111,17 @@ class U_I18N_API Grouper : public UMemory { */ UGroupingStrategy fStrategy; - Grouper() : fGrouping1(-3) {}; + Grouper() + : fGrouping1(-3) {}; bool isBogus() const { return fGrouping1 == -3; } /** NON-CONST: mutates the current instance. */ - void setLocaleData(const impl::ParsedPatternInfo &patternInfo, const Locale& locale); + void setLocaleData(const impl::ParsedPatternInfo& patternInfo, const Locale& locale); - bool groupAtPosition(int32_t position, const impl::DecimalQuantity &value) const; + bool groupAtPosition(int32_t position, const impl::DecimalQuantity& value) const; // To allow MacroProps/MicroProps to initialize empty instances: friend struct MacroProps; @@ -1469,17 +1163,19 @@ class U_I18N_API Padder : public UMemory { Padder(int32_t width); - Padder(UErrorCode errorCode) : fWidth(-3) { // NOLINT + Padder(UErrorCode errorCode) + : fWidth(-3) { // NOLINT fUnion.errorCode = errorCode; } - Padder() : fWidth(-2) {} // NOLINT + Padder() + : fWidth(-2) {} // NOLINT bool isBogus() const { return fWidth == -2; } - UBool copyErrorTo(UErrorCode &status) const { + UBool copyErrorTo(UErrorCode& status) const { if (fWidth == -3) { status = fUnion.errorCode; return TRUE; @@ -1491,9 +1187,9 @@ class U_I18N_API Padder : public UMemory { return fWidth > 0; } - int32_t padAndApply(const impl::Modifier &mod1, const impl::Modifier &mod2, - impl::NumberStringBuilder &string, int32_t leftIndex, int32_t rightIndex, - UErrorCode &status) const; + int32_t padAndApply(const impl::Modifier& mod1, const impl::Modifier& mod2, + impl::NumberStringBuilder& string, int32_t leftIndex, int32_t rightIndex, + UErrorCode& status) const; // To allow MacroProps/MicroProps to initialize empty instances: friend struct MacroProps; @@ -1521,7 +1217,8 @@ class U_I18N_API Multiplier : public UMemory { Multiplier(int32_t magnitudeMultiplier, int32_t multiplier); - Multiplier() : magnitudeMultiplier(0), multiplier(1) {} + Multiplier() + : magnitudeMultiplier(0), multiplier(1) {} bool isValid() const { return magnitudeMultiplier != 0 || multiplier != 1; @@ -1583,10 +1280,10 @@ struct U_I18N_API MacroProps : public UMemory { AffixPatternProvider* affixProvider = nullptr; // no ownership /** @internal */ - PluralRules *rules = nullptr; // no ownership + PluralRules* rules = nullptr; // no ownership /** @internal */ - CurrencySymbols *currencySymbols = nullptr; // no ownership + CurrencySymbols* currencySymbols = nullptr; // no ownership /** @internal */ int32_t threshold = DEFAULT_THRESHOLD; @@ -1600,10 +1297,9 @@ struct U_I18N_API MacroProps : public UMemory { * Check all members for errors. * @internal */ - bool copyErrorTo(UErrorCode &status) const { - return notation.copyErrorTo(status) || rounder.copyErrorTo(status) || - padder.copyErrorTo(status) || integerWidth.copyErrorTo(status) || - symbols.copyErrorTo(status); + bool copyErrorTo(UErrorCode& status) const { + return notation.copyErrorTo(status) || rounder.copyErrorTo(status) || padder.copyErrorTo(status) || + integerWidth.copyErrorTo(status) || symbols.copyErrorTo(status); } }; @@ -1644,7 +1340,7 @@ class U_I18N_API NumberFormatterSettings { * @see Notation * @draft ICU 60 */ - Derived notation(const Notation ¬ation) const &; + Derived notation(const Notation& notation) const& ; /** * Overload of notation() for use on an rvalue reference. @@ -1655,7 +1351,7 @@ class U_I18N_API NumberFormatterSettings { * @see #notation * @draft ICU 62 */ - Derived notation(const Notation ¬ation) &&; + Derived notation(const Notation& notation)&& ; /** * Specifies the unit (unit of measure, currency, or percent) to associate with rendered numbers. @@ -1701,7 +1397,7 @@ class U_I18N_API NumberFormatterSettings { * @see #perUnit * @draft ICU 60 */ - Derived unit(const icu::MeasureUnit &unit) const &; + Derived unit(const icu::MeasureUnit& unit) const& ; /** * Overload of unit() for use on an rvalue reference. @@ -1712,7 +1408,7 @@ class U_I18N_API NumberFormatterSettings { * @see #unit * @draft ICU 62 */ - Derived unit(const icu::MeasureUnit &unit) &&; + Derived unit(const icu::MeasureUnit& unit)&& ; /** * Like unit(), but takes ownership of a pointer. Convenient for use with the MeasureFormat factory @@ -1729,7 +1425,7 @@ class U_I18N_API NumberFormatterSettings { * @see MeasureUnit * @draft ICU 60 */ - Derived adoptUnit(icu::MeasureUnit *unit) const &; + Derived adoptUnit(icu::MeasureUnit* unit) const& ; /** * Overload of adoptUnit() for use on an rvalue reference. @@ -1740,7 +1436,7 @@ class U_I18N_API NumberFormatterSettings { * @see #adoptUnit * @draft ICU 62 */ - Derived adoptUnit(icu::MeasureUnit *unit) &&; + Derived adoptUnit(icu::MeasureUnit* unit)&& ; /** * Sets a unit to be used in the denominator. For example, to format "3 m/s", pass METER to the unit and SECOND to @@ -1759,7 +1455,7 @@ class U_I18N_API NumberFormatterSettings { * @see #unit * @draft ICU 61 */ - Derived perUnit(const icu::MeasureUnit &perUnit) const &; + Derived perUnit(const icu::MeasureUnit& perUnit) const& ; /** * Overload of perUnit() for use on an rvalue reference. @@ -1770,7 +1466,7 @@ class U_I18N_API NumberFormatterSettings { * @see #perUnit * @draft ICU 62 */ - Derived perUnit(const icu::MeasureUnit &perUnit) &&; + Derived perUnit(const icu::MeasureUnit& perUnit)&& ; /** * Like perUnit(), but takes ownership of a pointer. Convenient for use with the MeasureFormat factory @@ -1789,7 +1485,7 @@ class U_I18N_API NumberFormatterSettings { * @see MeasureUnit * @draft ICU 61 */ - Derived adoptPerUnit(icu::MeasureUnit *perUnit) const &; + Derived adoptPerUnit(icu::MeasureUnit* perUnit) const& ; /** * Overload of adoptPerUnit() for use on an rvalue reference. @@ -1800,7 +1496,7 @@ class U_I18N_API NumberFormatterSettings { * @see #adoptPerUnit * @draft ICU 62 */ - Derived adoptPerUnit(icu::MeasureUnit *perUnit) &&; + Derived adoptPerUnit(icu::MeasureUnit* perUnit)&& ; /** * Specifies the rounding strategy to use when formatting numbers. @@ -1832,7 +1528,7 @@ class U_I18N_API NumberFormatterSettings { * @see Rounder * @draft ICU 60 */ - Derived rounding(const Rounder &rounder) const &; + Derived rounding(const Rounder& rounder) const& ; /** * Overload of rounding() for use on an rvalue reference. @@ -1843,7 +1539,7 @@ class U_I18N_API NumberFormatterSettings { * @see #rounding * @draft ICU 62 */ - Derived rounding(const Rounder& rounder) &&; + Derived rounding(const Rounder& rounder)&& ; /** * Specifies the grouping strategy to use when formatting numbers. @@ -1872,7 +1568,7 @@ class U_I18N_API NumberFormatterSettings { * @return The fluent chain. * @draft ICU 61 */ - Derived grouping(const UGroupingStrategy &strategy) const &; + Derived grouping(const UGroupingStrategy& strategy) const& ; /** * Overload of grouping() for use on an rvalue reference. @@ -1884,7 +1580,7 @@ class U_I18N_API NumberFormatterSettings { * @provisional This API might change or be removed in a future release. * @draft ICU 62 */ - Derived grouping(const UGroupingStrategy& rounder) &&; + Derived grouping(const UGroupingStrategy& rounder)&& ; /** * Specifies the minimum and maximum number of digits to render before the decimal mark. @@ -1910,7 +1606,7 @@ class U_I18N_API NumberFormatterSettings { * @see IntegerWidth * @draft ICU 60 */ - Derived integerWidth(const IntegerWidth &style) const &; + Derived integerWidth(const IntegerWidth& style) const& ; /** * Overload of integerWidth() for use on an rvalue reference. @@ -1921,7 +1617,7 @@ class U_I18N_API NumberFormatterSettings { * @see #integerWidth * @draft ICU 62 */ - Derived integerWidth(const IntegerWidth &style) &&; + Derived integerWidth(const IntegerWidth& style)&& ; /** * Specifies the symbols (decimal separator, grouping separator, percent sign, numerals, etc.) to use when rendering @@ -1963,7 +1659,7 @@ class U_I18N_API NumberFormatterSettings { * @see DecimalFormatSymbols * @draft ICU 60 */ - Derived symbols(const DecimalFormatSymbols &symbols) const &; + Derived symbols(const DecimalFormatSymbols& symbols) const& ; /** * Overload of symbols() for use on an rvalue reference. @@ -1974,7 +1670,7 @@ class U_I18N_API NumberFormatterSettings { * @see #symbols * @draft ICU 62 */ - Derived symbols(const DecimalFormatSymbols &symbols) &&; + Derived symbols(const DecimalFormatSymbols& symbols)&& ; /** * Specifies that the given numbering system should be used when fetching symbols. @@ -2009,7 +1705,7 @@ class U_I18N_API NumberFormatterSettings { * @see NumberingSystem * @draft ICU 60 */ - Derived adoptSymbols(NumberingSystem *symbols) const &; + Derived adoptSymbols(NumberingSystem* symbols) const& ; /** * Overload of adoptSymbols() for use on an rvalue reference. @@ -2020,7 +1716,7 @@ class U_I18N_API NumberFormatterSettings { * @see #adoptSymbols * @draft ICU 62 */ - Derived adoptSymbols(NumberingSystem *symbols) &&; + Derived adoptSymbols(NumberingSystem* symbols)&& ; /** * Sets the width of the unit (measure unit or currency). Most common values: @@ -2047,7 +1743,7 @@ class U_I18N_API NumberFormatterSettings { * @see UNumberUnitWidth * @draft ICU 60 */ - Derived unitWidth(const UNumberUnitWidth &width) const &; + Derived unitWidth(const UNumberUnitWidth& width) const& ; /** * Overload of unitWidth() for use on an rvalue reference. @@ -2058,7 +1754,7 @@ class U_I18N_API NumberFormatterSettings { * @see #unitWidth * @draft ICU 62 */ - Derived unitWidth(const UNumberUnitWidth &width) &&; + Derived unitWidth(const UNumberUnitWidth& width)&& ; /** * Sets the plus/minus sign display strategy. Most common values: @@ -2086,7 +1782,7 @@ class U_I18N_API NumberFormatterSettings { * @provisional This API might change or be removed in a future release. * @draft ICU 60 */ - Derived sign(const UNumberSignDisplay &style) const &; + Derived sign(const UNumberSignDisplay& style) const& ; /** * Overload of sign() for use on an rvalue reference. @@ -2097,7 +1793,7 @@ class U_I18N_API NumberFormatterSettings { * @see #sign * @draft ICU 62 */ - Derived sign(const UNumberSignDisplay &style) &&; + Derived sign(const UNumberSignDisplay& style)&& ; /** * Sets the decimal separator display strategy. This affects integer numbers with no fraction part. Most common @@ -2125,7 +1821,7 @@ class U_I18N_API NumberFormatterSettings { * @provisional This API might change or be removed in a future release. * @draft ICU 60 */ - Derived decimal(const UNumberDecimalSeparatorDisplay &style) const &; + Derived decimal(const UNumberDecimalSeparatorDisplay& style) const& ; /** * Overload of decimal() for use on an rvalue reference. @@ -2136,7 +1832,7 @@ class U_I18N_API NumberFormatterSettings { * @see #sign * @draft ICU 62 */ - Derived decimal(const UNumberDecimalSeparatorDisplay &style) &&; + Derived decimal(const UNumberDecimalSeparatorDisplay& style)&& ; #ifndef U_HIDE_INTERNAL_API @@ -2145,10 +1841,10 @@ class U_I18N_API NumberFormatterSettings { * * @internal ICU 60: This API is ICU internal only. */ - Derived padding(const impl::Padder &padder) const &; + Derived padding(const impl::Padder& padder) const& ; /** @internal */ - Derived padding(const impl::Padder &padder) &&; + Derived padding(const impl::Padder& padder)&& ; /** * Internal fluent setter to support a custom regulation threshold. A threshold of 1 causes the data structures to @@ -2156,26 +1852,26 @@ class U_I18N_API NumberFormatterSettings { * * @internal ICU 60: This API is ICU internal only. */ - Derived threshold(int32_t threshold) const &; + Derived threshold(int32_t threshold) const& ; /** @internal */ - Derived threshold(int32_t threshold) &&; + Derived threshold(int32_t threshold)&& ; /** * Internal fluent setter to overwrite the entire macros object. * * @internal ICU 60: This API is ICU internal only. */ - Derived macros(const impl::MacroProps& macros) const &; + Derived macros(const impl::MacroProps& macros) const& ; /** @internal */ - Derived macros(const impl::MacroProps& macros) &&; + Derived macros(const impl::MacroProps& macros)&& ; /** @internal */ - Derived macros(impl::MacroProps&& macros) const &; + Derived macros(impl::MacroProps&& macros) const& ; /** @internal */ - Derived macros(impl::MacroProps&& macros) &&; + Derived macros(impl::MacroProps&& macros)&& ; #endif /* U_HIDE_INTERNAL_API */ @@ -2204,7 +1900,7 @@ class U_I18N_API NumberFormatterSettings { * @return TRUE if U_FAILURE(outErrorCode) * @draft ICU 60 */ - UBool copyErrorTo(UErrorCode &outErrorCode) const { + UBool copyErrorTo(UErrorCode& outErrorCode) const { if (U_FAILURE(outErrorCode)) { // Do not overwrite the older error code return TRUE; @@ -2252,7 +1948,7 @@ class U_I18N_API UnlocalizedNumberFormatter * @return The fluent chain. * @draft ICU 60 */ - LocalizedNumberFormatter locale(const icu::Locale &locale) const &; + LocalizedNumberFormatter locale(const icu::Locale& locale) const& ; /** * Overload of locale() for use on an rvalue reference. @@ -2263,7 +1959,7 @@ class U_I18N_API UnlocalizedNumberFormatter * @see #locale * @draft ICU 62 */ - LocalizedNumberFormatter locale(const icu::Locale &locale) &&; + LocalizedNumberFormatter locale(const icu::Locale& locale)&& ; /** * Default constructor: puts the formatter into a valid but undefined state. @@ -2277,7 +1973,7 @@ class U_I18N_API UnlocalizedNumberFormatter * Returns a copy of this UnlocalizedNumberFormatter. * @draft ICU 60 */ - UnlocalizedNumberFormatter(const UnlocalizedNumberFormatter &other); + UnlocalizedNumberFormatter(const UnlocalizedNumberFormatter& other); /** * Move constructor: @@ -2332,7 +2028,7 @@ class U_I18N_API LocalizedNumberFormatter * @return A FormattedNumber object; call .toString() to get the string. * @draft ICU 60 */ - FormattedNumber formatInt(int64_t value, UErrorCode &status) const; + FormattedNumber formatInt(int64_t value, UErrorCode& status) const; /** * Format the given float or double to a string using the settings specified in the NumberFormatter fluent setting @@ -2345,7 +2041,7 @@ class U_I18N_API LocalizedNumberFormatter * @return A FormattedNumber object; call .toString() to get the string. * @draft ICU 60 */ - FormattedNumber formatDouble(double value, UErrorCode &status) const; + FormattedNumber formatDouble(double value, UErrorCode& status) const; /** * Format the given decimal number to a string using the settings @@ -2396,7 +2092,7 @@ class U_I18N_API LocalizedNumberFormatter * Returns a copy of this LocalizedNumberFormatter. * @draft ICU 60 */ - LocalizedNumberFormatter(const LocalizedNumberFormatter &other); + LocalizedNumberFormatter(const LocalizedNumberFormatter& other); /** * Move constructor: @@ -2418,25 +2114,7 @@ class U_I18N_API LocalizedNumberFormatter */ LocalizedNumberFormatter& operator=(LocalizedNumberFormatter&& src) U_NOEXCEPT; - /** - * Destruct this LocalizedNumberFormatter, cleaning up any memory it might own. - * @draft ICU 60 - */ - ~LocalizedNumberFormatter(); - - private: - // Note: fCompiled can't be a LocalPointer because impl::NumberFormatterImpl is defined in an internal - // header, and LocalPointer needs the full class definition in order to delete the instance. - const impl::NumberFormatterImpl* fCompiled {nullptr}; - char fUnsafeCallCount[8] {}; // internally cast to u_atomic_int32_t - - explicit LocalizedNumberFormatter(const NumberFormatterSettings& other); - - explicit LocalizedNumberFormatter(NumberFormatterSettings&& src) U_NOEXCEPT; - - LocalizedNumberFormatter(const impl::MacroProps ¯os, const Locale &locale); - - LocalizedNumberFormatter(impl::MacroProps &¯os, const Locale &locale); +#ifndef U_HIDE_INTERNAL_API /** * This is the core entrypoint to the number formatting pipeline. It performs self-regulation: a static code path @@ -2446,10 +2124,31 @@ class U_I18N_API LocalizedNumberFormatter * This function is very hot, being called in every call to the number formatting pipeline. * * @param results - * The results object. This method takes ownership. - * @return The formatted number result. + * The results object. This method will mutate it to save the results. */ - FormattedNumber formatImpl(impl::NumberFormatterResults *results, UErrorCode &status) const; + void formatImpl(impl::UFormattedNumberData* results, UErrorCode& status) const; + +#endif + + /** + * Destruct this LocalizedNumberFormatter, cleaning up any memory it might own. + * @draft ICU 60 + */ + ~LocalizedNumberFormatter(); + + private: + // Note: fCompiled can't be a LocalPointer because impl::NumberFormatterImpl is defined in an internal + // header, and LocalPointer needs the full class definition in order to delete the instance. + const impl::NumberFormatterImpl* fCompiled{nullptr}; + char fUnsafeCallCount[8]{}; // internally cast to u_atomic_int32_t + + explicit LocalizedNumberFormatter(const NumberFormatterSettings& other); + + explicit LocalizedNumberFormatter(NumberFormatterSettings&& src) U_NOEXCEPT; + + LocalizedNumberFormatter(const impl::MacroProps& macros, const Locale& locale); + + LocalizedNumberFormatter(impl::MacroProps&& macros, const Locale& locale); // To give the fluent setters access to this class's constructor: friend class NumberFormatterSettings; @@ -2484,7 +2183,7 @@ class U_I18N_API FormattedNumber : public UMemory { * @draft ICU 60 * @see Appendable */ - Appendable &appendTo(Appendable &appendable); + Appendable& appendTo(Appendable& appendable); /** * Determine the start and end indices of the first occurrence of the given field in the output string. @@ -2505,7 +2204,7 @@ class U_I18N_API FormattedNumber : public UMemory { * @draft ICU 60 * @see UNumberFormatFields */ - void populateFieldPosition(FieldPosition &fieldPosition, UErrorCode &status); + void populateFieldPosition(FieldPosition& fieldPosition, UErrorCode& status); /** * Export the formatted number to a FieldPositionIterator. This allows you to determine which characters in @@ -2521,7 +2220,7 @@ class U_I18N_API FormattedNumber : public UMemory { * @draft ICU 60 * @see UNumberFormatFields */ - void populateFieldPositionIterator(FieldPositionIterator &iterator, UErrorCode &status); + void populateFieldPositionIterator(FieldPositionIterator& iterator, UErrorCode& status); #ifndef U_HIDE_INTERNAL_API @@ -2541,6 +2240,7 @@ class U_I18N_API FormattedNumber : public UMemory { // Don't allow copying of FormattedNumber, but moving is okay. FormattedNumber(const FormattedNumber&) = delete; + FormattedNumber& operator=(const FormattedNumber&) = delete; /** @@ -2564,17 +2264,21 @@ class U_I18N_API FormattedNumber : public UMemory { ~FormattedNumber(); private: - // Can't use LocalPointer because NumberFormatterResults is forward-declared - const impl::NumberFormatterResults *fResults; + // Can't use LocalPointer because UFormattedNumberData is forward-declared + const impl::UFormattedNumberData* fResults; // Error code for the terminal methods UErrorCode fErrorCode; - explicit FormattedNumber(impl::NumberFormatterResults *results) - : fResults(results), fErrorCode(U_ZERO_ERROR) {}; + /** + * Internal constructor from data type. Adopts the data pointer. + * @internal + */ + explicit FormattedNumber(const impl::UFormattedNumberData* results) + : fResults(results), fErrorCode(U_ZERO_ERROR) {}; explicit FormattedNumber(UErrorCode errorCode) - : fResults(nullptr), fErrorCode(errorCode) {}; + : fResults(nullptr), fErrorCode(errorCode) {}; // To give LocalizedNumberFormatter format methods access to this class's constructor: friend class LocalizedNumberFormatter; @@ -2605,7 +2309,7 @@ class U_I18N_API NumberFormatter final { * @return A {@link LocalizedNumberFormatter}, to be used for chaining. * @draft ICU 60 */ - static LocalizedNumberFormatter withLocale(const Locale &locale); + static LocalizedNumberFormatter withLocale(const Locale& locale); /** * Call this method at the beginning of a NumberFormatter fluent chain to create an instance based diff --git a/icu4c/source/i18n/unicode/unumberformatter.h b/icu4c/source/i18n/unicode/unumberformatter.h new file mode 100644 index 0000000000..ce46fb34da --- /dev/null +++ b/icu4c/source/i18n/unicode/unumberformatter.h @@ -0,0 +1,517 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#ifndef __UNUMBERFORMATTER_H__ +#define __UNUMBERFORMATTER_H__ + + +/** + * \file + * \brief C-compatible API for localized number formatting; not recommended for C++. + * + * This is the C-compatible version of the NumberFormatter API introduced in ICU 60. C++ users should + * include unicode/numberformatter.h and use the proper C++ APIs. + */ + + +/** + * An enum declaring how to render units, including currencies. Example outputs when formatting 123 USD and 123 + * meters in en-CA: + * + *

    + *

      + *
    • NARROW*: "$123.00" and "123 m" + *
    • SHORT: "US$ 123.00" and "123 m" + *
    • FULL_NAME: "123.00 US dollars" and "123 meters" + *
    • ISO_CODE: "USD 123.00" and undefined behavior + *
    • HIDDEN: "123.00" and "123" + *
    + * + *

    + * This enum is similar to {@link com.ibm.icu.text.MeasureFormat.FormatWidth}. + * + * @draft ICU 60 + */ +typedef enum UNumberUnitWidth { + /** + * Print an abbreviated version of the unit name. Similar to SHORT, but always use the shortest available + * abbreviation or symbol. This option can be used when the context hints at the identity of the unit. For more + * information on the difference between NARROW and SHORT, see SHORT. + * + *

    + * In CLDR, this option corresponds to the "Narrow" format for measure units and the "¤¤¤¤¤" placeholder for + * currencies. + * + * @draft ICU 60 + */ + UNUM_UNIT_WIDTH_NARROW, + + /** + * Print an abbreviated version of the unit name. Similar to NARROW, but use a slightly wider abbreviation or + * symbol when there may be ambiguity. This is the default behavior. + * + *

    + * For example, in es-US, the SHORT form for Fahrenheit is "{0} °F", but the NARROW form is "{0}°", + * since Fahrenheit is the customary unit for temperature in that locale. + * + *

    + * In CLDR, this option corresponds to the "Short" format for measure units and the "¤" placeholder for + * currencies. + * + * @draft ICU 60 + */ + UNUM_UNIT_WIDTH_SHORT, + + /** + * Print the full name of the unit, without any abbreviations. + * + *

    + * In CLDR, this option corresponds to the default format for measure units and the "¤¤¤" placeholder for + * currencies. + * + * @draft ICU 60 + */ + UNUM_UNIT_WIDTH_FULL_NAME, + + /** + * Use the three-digit ISO XXX code in place of the symbol for displaying currencies. The behavior of this + * option is currently undefined for use with measure units. + * + *

    + * In CLDR, this option corresponds to the "¤¤" placeholder for currencies. + * + * @draft ICU 60 + */ + UNUM_UNIT_WIDTH_ISO_CODE, + + /** + * Format the number according to the specified unit, but do not display the unit. For currencies, apply + * monetary symbols and formats as with SHORT, but omit the currency symbol. For measure units, the behavior is + * equivalent to not specifying the unit at all. + * + * @draft ICU 60 + */ + UNUM_UNIT_WIDTH_HIDDEN, + + /** + * One more than the highest UNumberUnitWidth value. + * + * @internal ICU 60: The numeric value may change over time; see ICU ticket #12420. + */ + UNUM_UNIT_WIDTH_COUNT +} UNumberUnitWidth; + +/** + * An enum declaring the strategy for when and how to display grouping separators (i.e., the + * separator, often a comma or period, after every 2-3 powers of ten). The choices are several + * pre-built strategies for different use cases that employ locale data whenever possible. Example + * outputs for 1234 and 1234567 in en-IN: + * + *

      + *
    • OFF: 1234 and 12345 + *
    • MIN2: 1234 and 12,34,567 + *
    • AUTO: 1,234 and 12,34,567 + *
    • ON_ALIGNED: 1,234 and 12,34,567 + *
    • THOUSANDS: 1,234 and 1,234,567 + *
    + * + *

    + * The default is AUTO, which displays grouping separators unless the locale data says that grouping + * is not customary. To force grouping for all numbers greater than 1000 consistently across locales, + * use ON_ALIGNED. On the other hand, to display grouping less frequently than the default, use MIN2 + * or OFF. See the docs of each option for details. + * + *

    + * Note: This enum specifies the strategy for grouping sizes. To set which character to use as the + * grouping separator, use the "symbols" setter. + * + * @draft ICU 61 -- TODO: This should be renamed to UNumberGroupingStrategy before promoting to stable, + * for consistency with the other enums. + */ +typedef enum UGroupingStrategy { + /** + * Do not display grouping separators in any locale. + * + * @draft ICU 61 + */ + UNUM_GROUPING_OFF, + + /** + * Display grouping using locale defaults, except do not show grouping on values smaller than + * 10000 (such that there is a minimum of two digits before the first separator). + * + *

    + * Note that locales may restrict grouping separators to be displayed only on 1 million or + * greater (for example, ee and hu) or disable grouping altogether (for example, bg currency). + * + *

    + * Locale data is used to determine whether to separate larger numbers into groups of 2 + * (customary in South Asia) or groups of 3 (customary in Europe and the Americas). + * + * @draft ICU 61 + */ + UNUM_GROUPING_MIN2, + + /** + * Display grouping using the default strategy for all locales. This is the default behavior. + * + *

    + * Note that locales may restrict grouping separators to be displayed only on 1 million or + * greater (for example, ee and hu) or disable grouping altogether (for example, bg currency). + * + *

    + * Locale data is used to determine whether to separate larger numbers into groups of 2 + * (customary in South Asia) or groups of 3 (customary in Europe and the Americas). + * + * @draft ICU 61 + */ + UNUM_GROUPING_AUTO, + + /** + * Always display the grouping separator on values of at least 1000. + * + *

    + * This option ignores the locale data that restricts or disables grouping, described in MIN2 and + * AUTO. This option may be useful to normalize the alignment of numbers, such as in a + * spreadsheet. + * + *

    + * Locale data is used to determine whether to separate larger numbers into groups of 2 + * (customary in South Asia) or groups of 3 (customary in Europe and the Americas). + * + * @draft ICU 61 + */ + UNUM_GROUPING_ON_ALIGNED, + + /** + * Use the Western defaults: groups of 3 and enabled for all numbers 1000 or greater. Do not use + * locale data for determining the grouping strategy. + * + * @draft ICU 61 + */ + UNUM_GROUPING_THOUSANDS, + + /** + * One more than the highest UNumberSignDisplay value. + * + * @internal ICU 62: The numeric value may change over time; see ICU ticket #12420. + */ + UNUM_GROUPING_COUNT + +} UGroupingStrategy; + +/** + * An enum declaring how to denote positive and negative numbers. Example outputs when formatting + * 123, 0, and -123 in en-US: + * + *

      + *
    • AUTO: "123", "0", and "-123" + *
    • ALWAYS: "+123", "+0", and "-123" + *
    • NEVER: "123", "0", and "123" + *
    • ACCOUNTING: "$123", "$0", and "($123)" + *
    • ACCOUNTING_ALWAYS: "+$123", "+$0", and "($123)" + *
    • EXCEPT_ZERO: "+123", "0", and "-123" + *
    • ACCOUNTING_EXCEPT_ZERO: "+$123", "$0", and "($123)" + *
    + * + *

    + * The exact format, including the position and the code point of the sign, differ by locale. + * + * @draft ICU 60 + */ +typedef enum UNumberSignDisplay { + /** + * Show the minus sign on negative numbers, and do not show the sign on positive numbers. This is the default + * behavior. + * + * @draft ICU 60 + */ + UNUM_SIGN_AUTO, + + /** + * 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, + + /** + * Do not show the sign on positive or negative numbers. + * + * @draft ICU 60 + */ + UNUM_SIGN_NEVER, + + /** + * Use the locale-dependent accounting format on negative numbers, and do not show the sign on positive numbers. + * + *

    + * The accounting format is defined in CLDR and varies by locale; in many Western locales, the format is a pair + * of parentheses around the number. + * + *

    + * Note: Since CLDR defines the accounting format in the monetary context only, this option falls back to the + * AUTO sign display strategy when formatting without a currency unit. This limitation may be lifted in the + * future. + * + * @draft ICU 60 + */ + UNUM_SIGN_ACCOUNTING, + + /** + * 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, + + /** + * 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 +} UNumberSignDisplay; + +/** + * An enum declaring how to render the decimal separator. + * + *

    + *

      + *
    • UNUM_DECIMAL_SEPARATOR_AUTO: "1", "1.1" + *
    • UNUM_DECIMAL_SEPARATOR_ALWAYS: "1.", "1.1" + *
    + */ +typedef enum UNumberDecimalSeparatorDisplay { + /** + * Show the decimal separator when there are one or more digits to display after the separator, and do not show + * it otherwise. This is the default behavior. + * + * @draft ICU 60 + */ + UNUM_DECIMAL_SEPARATOR_AUTO, + + /** + * Always show the decimal separator, even if there are no digits to display after the separator. + * + * @draft ICU 60 + */ + UNUM_DECIMAL_SEPARATOR_ALWAYS, + + /** + * One more than the highest UNumberDecimalSeparatorDisplay value. + * + * @internal ICU 60: The numeric value may change over time; see ICU ticket #12420. + */ + UNUM_DECIMAL_SEPARATOR_COUNT +} UNumberDecimalSeparatorDisplay; + + +/** + * C-compatible version of icu::number::LocalizedNumberFormatter. + * + * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. + * + * @draft ICU 62 + */ +typedef struct UNumberFormatter UNumberFormatter; + + +/** + * C-compatible version of icu::number::FormattedNumber. + * + * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. + * + * @draft ICU 62 + */ +typedef struct UFormattedNumber UFormattedNumber; + + +/** + * Creates a new UNumberFormatter from the given skeleton string and locale. + * + * For more details on skeleton strings, see the documentation in numberformatter.h. For more details on + * the usage of this API, see the documentation at the top of unumberformatter.h. + * + * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. + * + * @draft ICU 62 + */ +U_DRAFT UNumberFormatter* U_EXPORT2 +unumf_openFromSkeletonAndLocale(const UChar* skeleton, int32_t skeletonLen, const char* locale, + UErrorCode* ec); + + +/** + * Creates a new UFormattedNumber for holding the result of a number formatting operation. + * + * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. + * + * @draft ICU 62 + */ +U_DRAFT UFormattedNumber* U_EXPORT2 +unumf_openResult(UErrorCode* ec); + + +/** + * Uses a UNumberFormatter to format a double to a UFormattedNumber. A string, field position, and other + * information can be retrieved from the UFormattedNumber. + * + * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. + * + * @draft ICU 62 + */ +U_DRAFT void U_EXPORT2 +unumf_formatInt(const UNumberFormatter* uformatter, int64_t value, UFormattedNumber* uresult, + UErrorCode* ec); + + +/** + * Uses a UNumberFormatter to format a double to a UFormattedNumber. A string, field position, and other + * information can be retrieved from the UFormattedNumber. + * + * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. + * + * @draft ICU 62 + */ +U_DRAFT void U_EXPORT2 +unumf_formatDouble(const UNumberFormatter* uformatter, double value, UFormattedNumber* uresult, + UErrorCode* ec); + + +/** + * Uses a UNumberFormatter to format a decimal number to a UFormattedNumber. A string, field position, and + * other information can be retrieved from the UFormattedNumber. + * + * The syntax of the unformatted number is a "numeric string" as defined in the Decimal Arithmetic + * Specification, available at http://speleotrove.com/decimal + * + * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. + * + * @draft ICU 62 + */ +U_DRAFT void U_EXPORT2 +unumf_formatDecimal(const UNumberFormatter* uformatter, const char* value, int32_t valueLen, + UFormattedNumber* uresult, UErrorCode* ec); + + +/** + * Extracts the result number string out of a UFormattedNumber to a UChar buffer. The usual ICU pattern + * is used for writing to the buffer: + * + * - If the string is shorter than the buffer, it will be written to the buffer and will be NUL-terminated. + * - If the string is exactly the length of the buffer, it will be written to the buffer, but it will not + * be NUL-terminated, and a warning will be set. + * - If the string is longer than the buffer, nothing will be written to the buffer, and an error will be + * set. + * + * In all cases, the actual length of the string is returned, whether or not it was written to the buffer. + * + * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. + * + * @draft ICU 62 + */ +U_DRAFT int32_t U_EXPORT2 +unumf_resultToString(const UFormattedNumber* uresult, UChar* buffer, int32_t bufferCapacity, + UErrorCode* ec); + + +/** + * Releases the UFormattedNumber returned by unumf_formatDouble and friends. + * + * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. + * + * @draft ICU 62 + */ +U_DRAFT void U_EXPORT2 +unumf_closeResult(const UFormattedNumber* uresult, UErrorCode* ec); + + +/** + * Releases the UNumberFormatter created by unumf_openFromSkeletonAndLocale. + * + * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. + * + * @draft ICU 62 + */ +U_DRAFT void U_EXPORT2 +unumf_close(UNumberFormatter* uformatter, UErrorCode* ec); + + +#endif //__UNUMBERFORMATTER_H__ +#endif /* #if !UCONFIG_NO_FORMATTING */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icu4c/source/test/cintltst/Makefile.in b/icu4c/source/test/cintltst/Makefile.in index f60bb66db8..e7bf69e18d 100644 --- a/icu4c/source/test/cintltst/Makefile.in +++ b/icu4c/source/test/cintltst/Makefile.in @@ -54,7 +54,8 @@ idnatest.o nfsprep.o spreptst.o sprpdata.o \ hpmufn.o tracetst.o reapits.o uregiontest.o ulistfmttest.o\ utexttst.o ucsdetst.o spooftest.o \ cbiditransformtst.o \ -cgendtst.o +cgendtst.o \ +unumberformattertst.o DEPS = $(OBJECTS:.o=.d) diff --git a/icu4c/source/test/cintltst/calltest.c b/icu4c/source/test/cintltst/calltest.c index 6c5af0614c..96ea400a4e 100644 --- a/icu4c/source/test/cintltst/calltest.c +++ b/icu4c/source/test/cintltst/calltest.c @@ -45,6 +45,7 @@ void addUSpoofTest(TestNode** root); #if !UCONFIG_NO_FORMATTING void addGendInfoForTest(TestNode** root); #endif +void addUNumberFormatterTest(TestNode** root); void addAllTests(TestNode** root) { @@ -88,5 +89,6 @@ void addAllTests(TestNode** root) addPUtilTest(root); #if !UCONFIG_NO_FORMATTING addGendInfoForTest(root); + addUNumberFormatterTest(root); #endif } diff --git a/icu4c/source/test/cintltst/cintltst.c b/icu4c/source/test/cintltst/cintltst.c index 40a0b94672..2df1053733 100644 --- a/icu4c/source/test/cintltst/cintltst.c +++ b/icu4c/source/test/cintltst/cintltst.c @@ -709,4 +709,24 @@ U_CFUNC UBool assertEquals(const char* message, const char* expected, return TRUE; } +U_CFUNC UBool assertUEquals(const char* message, const UChar* expected, + const UChar* actual) { + for (int32_t i=0;; i++) { + if (expected[i] != actual[i]) { + log_err("FAIL: %s; got \"%s\"; expected \"%s\"\n", + message, actual, expected); + return FALSE; + } + UChar curr = expected[i]; + U_ASSERT(curr == actual[i]); + if (curr == 0) { + break; + } + } +#ifdef VERBOSE_ASSERTIONS + log_verbose("Ok: %s; got \"%s\"\n", message, actual); +#endif + return TRUE; +} + #endif diff --git a/icu4c/source/test/cintltst/cintltst.h b/icu4c/source/test/cintltst/cintltst.h index d038f36308..7540aa1bc0 100644 --- a/icu4c/source/test/cintltst/cintltst.h +++ b/icu4c/source/test/cintltst/cintltst.h @@ -135,6 +135,13 @@ U_CFUNC UBool assertTrue(const char* msg, int condition); U_CFUNC UBool assertEquals(const char* msg, const char* expectedString, const char* actualString); +/** + * Assert that the actualString equals the expectedString, and return + * TRUE if it does. + */ +U_CFUNC UBool assertUEquals(const char* msg, const UChar* expectedString, + const UChar* actualString); + /* * note - isICUVersionBefore and isICUVersionAtLeast have been removed. * use log_knownIssue() instead. diff --git a/icu4c/source/test/cintltst/unumberformattertst.c b/icu4c/source/test/cintltst/unumberformattertst.c new file mode 100644 index 0000000000..03be113b56 --- /dev/null +++ b/icu4c/source/test/cintltst/unumberformattertst.c @@ -0,0 +1,65 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT + +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + +#include "unicode/unumberformatter.h" +#include "cintltst.h" + +static void TestSkeletonFormatToString(); + +void addUNumberFormatterTest(TestNode** root); + +void addUNumberFormatterTest(TestNode** root) { + addTest(root, &TestSkeletonFormatToString, "unumberformatter/TestSkeletonFormatToString"); +} + + +static void TestSkeletonFormatToString() { + UErrorCode ec = U_ZERO_ERROR; + static const int32_t CAPACITY = 30; + UChar buffer[CAPACITY]; + + // SETUP: + UNumberFormatter* f = unumf_openFromSkeletonAndLocale( + u"round-integer currency/USD sign-accounting", -1, "en", &ec); + assertSuccess("Should create without error", &ec); + UFormattedNumber* result = unumf_openResult(&ec); + assertSuccess("Should create result without error", &ec); + + // INT TEST: + unumf_formatInt(f, -444444, result, &ec); + assertSuccess("Should format integer without error", &ec); + unumf_resultToString(result, buffer, CAPACITY, &ec); + assertSuccess("Should print string to buffer without error", &ec); + assertUEquals("Should produce expected string result", u"($444,444)", buffer); + + // DOUBLE TEST: + unumf_formatDouble(f, -5142.3, result, &ec); + assertSuccess("Should format double without error", &ec); + unumf_resultToString(result, buffer, CAPACITY, &ec); + assertSuccess("Should print string to buffer without error", &ec); + assertUEquals("Should produce expected string result", u"($5,142)", buffer); + + // DECIMAL TEST: + unumf_formatDecimal(f, "9.876E2", -1, result, &ec); + assertSuccess("Should format decimal without error", &ec); + unumf_resultToString(result, buffer, CAPACITY, &ec); + assertSuccess("Should print string to buffer without error", &ec); + assertUEquals("Should produce expected string result", u"$988", buffer); + + // CLEANUP: + unumf_closeResult(result, &ec); + assertSuccess("Should close without error", &ec); + unumf_close(f, &ec); + assertSuccess("Should close without error", &ec); +} + + +#endif /* #if !UCONFIG_NO_FORMATTING */ From 46c4709a943b86d61d2b754986bf4bb04f2e2d2a Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Tue, 27 Mar 2018 05:34:22 +0000 Subject: [PATCH 064/129] ICU-13597 Bug fix: fraction digits were being marked as the INTEGER field in ICU4C. X-SVN-Rev: 41158 --- icu4c/source/i18n/number_formatimpl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/icu4c/source/i18n/number_formatimpl.cpp b/icu4c/source/i18n/number_formatimpl.cpp index b3e843206f..7b88dc3ec8 100644 --- a/icu4c/source/i18n/number_formatimpl.cpp +++ b/icu4c/source/i18n/number_formatimpl.cpp @@ -518,7 +518,7 @@ int32_t NumberFormatterImpl::writeFractionDigits(const MicroProps& micros, Decim // Get and append the next digit value int8_t nextDigit = quantity.getDigit(-i - 1); length += insertDigitFromSymbols( - string, string.length(), nextDigit, *micros.symbols, UNUM_INTEGER_FIELD, status); + string, string.length(), nextDigit, *micros.symbols, UNUM_FRACTION_FIELD, status); } return length; } From 395463d7a70245ca4fe697995060c1724eb1b057 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Tue, 27 Mar 2018 05:36:04 +0000 Subject: [PATCH 065/129] ICU-13597 Adding support for field positions to the NumberFormatter C API. X-SVN-Rev: 41159 --- icu4c/source/i18n/fieldposutil.h | 50 +++++++++++ icu4c/source/i18n/number_capi.cpp | 42 +++++++-- icu4c/source/i18n/unicode/unumberformatter.h | 52 +++++++++-- icu4c/source/test/cintltst/cintltst.c | 14 +++ icu4c/source/test/cintltst/cintltst.h | 5 ++ .../test/cintltst/unumberformattertst.c | 86 +++++++++++++++++-- 6 files changed, 229 insertions(+), 20 deletions(-) create mode 100644 icu4c/source/i18n/fieldposutil.h diff --git a/icu4c/source/i18n/fieldposutil.h b/icu4c/source/i18n/fieldposutil.h new file mode 100644 index 0000000000..8a2053640c --- /dev/null +++ b/icu4c/source/i18n/fieldposutil.h @@ -0,0 +1,50 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING +#ifndef __SOURCE_FIELDPOSUTIL_H__ +#define __SOURCE_FIELDPOSUTIL_H__ + +U_NAMESPACE_BEGIN + +/** + * Wraps a UFieldPosition and makes it usable as a FieldPosition. Example: + * + *
    + * UFieldPositionWrapper wrapper(myUFPos);
    + * u_favorite_function_taking_ufpos(wrapper);
    + * // when destructed, the wrapper saves the data back into myUFPos
    + * 
    + */ +class UFieldPositionWrapper : public UMemory { + public: + explicit UFieldPositionWrapper(UFieldPosition& ufpos) + : _ufpos(ufpos) { + _fpos.setField(_ufpos.field); + _fpos.setBeginIndex(_ufpos.beginIndex); + _fpos.setEndIndex(_ufpos.endIndex); + } + + /** When destructed, copies the information from the fpos into the ufpos. */ + ~UFieldPositionWrapper() { + _ufpos.field = _fpos.getField(); + _ufpos.beginIndex = _fpos.getBeginIndex(); + _ufpos.endIndex = _fpos.getEndIndex(); + } + + /** Conversion operator to FieldPosition */ + operator FieldPosition&() { + return _fpos; + } + + private: + FieldPosition _fpos; + UFieldPosition& _ufpos; +}; + +U_NAMESPACE_END + +#endif //__SOURCE_FIELDPOSUTIL_H__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/number_capi.cpp b/icu4c/source/i18n/number_capi.cpp index bb2d47b48f..1dc2955faa 100644 --- a/icu4c/source/i18n/number_capi.cpp +++ b/icu4c/source/i18n/number_capi.cpp @@ -13,6 +13,7 @@ #include "number_utypes.h" #include "unicode/numberformatter.h" #include "unicode/unumberformatter.h" +#include "fieldposutil.h" using namespace icu; using namespace icu::number; @@ -153,16 +154,47 @@ unumf_resultToString(const UFormattedNumber* uresult, UChar* buffer, int32_t buf } U_CAPI void U_EXPORT2 -unumf_closeResult(const UFormattedNumber* uresult, UErrorCode* ec) { - const UFormattedNumberData* impl = UFormattedNumberData::validate(uresult, *ec); +unumf_resultGetField(const UFormattedNumber* uresult, UFieldPosition* ufpos, UErrorCode* ec) { + if (ufpos == nullptr) { + *ec = U_ILLEGAL_ARGUMENT_ERROR; + return; + } + + const UFormattedNumberData* result = UFormattedNumberData::validate(uresult, *ec); if (U_FAILURE(*ec)) { return; } + + UFieldPositionWrapper helper(*ufpos); + result->string.populateFieldPosition(helper, 0, *ec); +} + +U_CAPI void U_EXPORT2 +unumf_resultGetAllFields(const UFormattedNumber* uresult, UFieldPositionIterator* ufpositer, + UErrorCode* ec) { + if (ufpositer == nullptr) { + *ec = U_ILLEGAL_ARGUMENT_ERROR; + return; + } + + const UFormattedNumberData* result = UFormattedNumberData::validate(uresult, *ec); + if (U_FAILURE(*ec)) { return; } + + auto* helper = reinterpret_cast(ufpositer); + result->string.populateFieldPositionIterator(*helper, *ec); +} + +U_CAPI void U_EXPORT2 +unumf_closeResult(const UFormattedNumber* uresult) { + UErrorCode localStatus = U_ZERO_ERROR; + const UFormattedNumberData* impl = UFormattedNumberData::validate(uresult, localStatus); + if (U_FAILURE(localStatus)) { return; } delete impl; } U_CAPI void U_EXPORT2 -unumf_close(UNumberFormatter* f, UErrorCode* ec) { - const UNumberFormatterData* impl = UNumberFormatterData::validate(f, *ec); - if (U_FAILURE(*ec)) { return; } +unumf_close(UNumberFormatter* f) { + UErrorCode localStatus = U_ZERO_ERROR; + const UNumberFormatterData* impl = UNumberFormatterData::validate(f, localStatus); + if (U_FAILURE(localStatus)) { return; } delete impl; } diff --git a/icu4c/source/i18n/unicode/unumberformatter.h b/icu4c/source/i18n/unicode/unumberformatter.h index ce46fb34da..059cbb6491 100644 --- a/icu4c/source/i18n/unicode/unumberformatter.h +++ b/icu4c/source/i18n/unicode/unumberformatter.h @@ -7,6 +7,9 @@ #ifndef __UNUMBERFORMATTER_H__ #define __UNUMBERFORMATTER_H__ +#include "unicode/ufieldpositer.h" +#include "unicode/umisc.h" + /** * \file @@ -441,25 +444,62 @@ unumf_resultToString(const UFormattedNumber* uresult, UChar* buffer, int32_t buf /** - * Releases the UFormattedNumber returned by unumf_formatDouble and friends. + * Determines the start and end indices of the first occurrence of the given field in the output string. + * This allows you to determine the locations of the integer part, fraction part, and sign. * - * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. + * If a field occurs multiple times in an output string, such as a grouping separator, this method will + * only ever return the first occurrence. Use unumf_resultGetAllFields() to access all occurrences of an + * attribute. * - * @draft ICU 62 + * @param fpos + * A pointer to a UFieldPosition. On input, position->field is read. On output, + * position->beginIndex and position->endIndex indicate the beginning and ending indices of field + * number position->field, if such a field exists. */ U_DRAFT void U_EXPORT2 -unumf_closeResult(const UFormattedNumber* uresult, UErrorCode* ec); +unumf_resultGetField(const UFormattedNumber* uresult, UFieldPosition* ufpos, UErrorCode* ec); /** - * Releases the UNumberFormatter created by unumf_openFromSkeletonAndLocale. + * Populates the given iterator with all fields in the formatted output string. This allows you to + * determine the locations of the integer part, fraction part, and sign. + * + * If you need information on only one field, consider using unumf_resultGetField(). + * + * @param fpositer + * A pointer to a UFieldPositionIterator created by {@link #ufieldpositer_open}. Iteration + * information already present in the UFieldPositionIterator is deleted, and the iterator is reset + * to apply to the fields in the formatted string created by this function call. The field values + * and indexes returned by {@link #ufieldpositer_next} represent fields denoted by + * the UNumberFormatFields enum. Fields are not returned in a guaranteed order. Fields cannot + * overlap, but they may nest. For example, 1234 could format as "1,234" which might consist of a + * grouping separator field for ',' and an integer field encompassing the entire string. + */ +U_DRAFT void U_EXPORT2 +unumf_resultGetAllFields(const UFormattedNumber* uresult, UFieldPositionIterator* ufpositer, + UErrorCode* ec); + + +/** + * Releases the UNumberFormatter created by unumf_openFromSkeletonAndLocale(). * * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. * * @draft ICU 62 */ U_DRAFT void U_EXPORT2 -unumf_close(UNumberFormatter* uformatter, UErrorCode* ec); +unumf_close(UNumberFormatter* uformatter); + + +/** + * Releases the UFormattedNumber created by unumf_openResult(). + * + * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. + * + * @draft ICU 62 + */ +U_DRAFT void U_EXPORT2 +unumf_closeResult(const UFormattedNumber* uresult); #endif //__UNUMBERFORMATTER_H__ diff --git a/icu4c/source/test/cintltst/cintltst.c b/icu4c/source/test/cintltst/cintltst.c index 2df1053733..20ee1d8295 100644 --- a/icu4c/source/test/cintltst/cintltst.c +++ b/icu4c/source/test/cintltst/cintltst.c @@ -729,4 +729,18 @@ U_CFUNC UBool assertUEquals(const char* message, const UChar* expected, return TRUE; } +U_CFUNC UBool assertIntEquals(const char* message, int64_t expected, int64_t actual) { + if (expected != actual) { + log_err("FAIL: %s; got \"%d\"; expected \"%d\"\n", + message, actual, expected); + return FALSE; + } +#ifdef VERBOSE_ASSERTIONS + else { + log_verbose("Ok: %s; got \"%d\"\n", message, actual); + } +#endif + return TRUE; +} + #endif diff --git a/icu4c/source/test/cintltst/cintltst.h b/icu4c/source/test/cintltst/cintltst.h index 7540aa1bc0..edb60eb58e 100644 --- a/icu4c/source/test/cintltst/cintltst.h +++ b/icu4c/source/test/cintltst/cintltst.h @@ -142,6 +142,11 @@ U_CFUNC UBool assertEquals(const char* msg, const char* expectedString, U_CFUNC UBool assertUEquals(const char* msg, const UChar* expectedString, const UChar* actualString); +/** + * Assert that two 64-bit integers are equal, returning TRUE if they do. + */ +U_CFUNC UBool assertIntEquals(const char* msg, int64_t expected, int64_t actual); + /* * note - isICUVersionBefore and isICUVersionAtLeast have been removed. * use log_knownIssue() instead. diff --git a/icu4c/source/test/cintltst/unumberformattertst.c b/icu4c/source/test/cintltst/unumberformattertst.c index 03be113b56..e8ae8b5013 100644 --- a/icu4c/source/test/cintltst/unumberformattertst.c +++ b/icu4c/source/test/cintltst/unumberformattertst.c @@ -10,14 +10,19 @@ #define UNISTR_FROM_STRING_EXPLICIT #include "unicode/unumberformatter.h" +#include "unicode/umisc.h" +#include "unicode/unum.h" #include "cintltst.h" static void TestSkeletonFormatToString(); +static void TestSkeletonFormatToFields(); + void addUNumberFormatterTest(TestNode** root); void addUNumberFormatterTest(TestNode** root) { addTest(root, &TestSkeletonFormatToString, "unumberformatter/TestSkeletonFormatToString"); + addTest(root, &TestSkeletonFormatToFields, "unumberformatter/TestSkeletonFormatToFields"); } @@ -26,39 +31,102 @@ static void TestSkeletonFormatToString() { static const int32_t CAPACITY = 30; UChar buffer[CAPACITY]; - // SETUP: + // setup: UNumberFormatter* f = unumf_openFromSkeletonAndLocale( u"round-integer currency/USD sign-accounting", -1, "en", &ec); assertSuccess("Should create without error", &ec); UFormattedNumber* result = unumf_openResult(&ec); assertSuccess("Should create result without error", &ec); - // INT TEST: + // int64 test: unumf_formatInt(f, -444444, result, &ec); assertSuccess("Should format integer without error", &ec); unumf_resultToString(result, buffer, CAPACITY, &ec); assertSuccess("Should print string to buffer without error", &ec); assertUEquals("Should produce expected string result", u"($444,444)", buffer); - // DOUBLE TEST: + // double test: unumf_formatDouble(f, -5142.3, result, &ec); assertSuccess("Should format double without error", &ec); unumf_resultToString(result, buffer, CAPACITY, &ec); assertSuccess("Should print string to buffer without error", &ec); assertUEquals("Should produce expected string result", u"($5,142)", buffer); - // DECIMAL TEST: + // decnumber test: unumf_formatDecimal(f, "9.876E2", -1, result, &ec); assertSuccess("Should format decimal without error", &ec); unumf_resultToString(result, buffer, CAPACITY, &ec); assertSuccess("Should print string to buffer without error", &ec); assertUEquals("Should produce expected string result", u"$988", buffer); - // CLEANUP: - unumf_closeResult(result, &ec); - assertSuccess("Should close without error", &ec); - unumf_close(f, &ec); - assertSuccess("Should close without error", &ec); + // cleanup: + unumf_closeResult(result); + unumf_close(f); +} + + +static void TestSkeletonFormatToFields() { + UErrorCode ec = U_ZERO_ERROR; + + // setup: + UNumberFormatter* uformatter = unumf_openFromSkeletonAndLocale( + u".00 measure-unit/length-meter sign-always", -1, "en", &ec); + assertSuccess("Should create without error", &ec); + UFormattedNumber* uresult = unumf_openResult(&ec); + assertSuccess("Should create result without error", &ec); + unumf_formatInt(uformatter, 9876543210L, uresult, &ec); // "+9,876,543,210.00 m" + + // field position test: + UFieldPosition ufpos = {UNUM_DECIMAL_SEPARATOR_FIELD}; + unumf_resultGetField(uresult, &ufpos, &ec); + assertIntEquals("Field position should be correct", 14, ufpos.beginIndex); + assertIntEquals("Field position should be correct", 15, ufpos.endIndex); + + // field position iterator test: + UFieldPositionIterator* ufpositer = ufieldpositer_open(&ec); + assertSuccess("Should create iterator without error", &ec); + unumf_resultGetAllFields(uresult, ufpositer, &ec); + static const UFieldPosition expectedFields[] = { + // Field, begin index, end index + {UNUM_SIGN_FIELD, 0, 1}, + {UNUM_GROUPING_SEPARATOR_FIELD, 2, 3}, + {UNUM_GROUPING_SEPARATOR_FIELD, 6, 7}, + {UNUM_GROUPING_SEPARATOR_FIELD, 10, 11}, + {UNUM_INTEGER_FIELD, 1, 14}, + {UNUM_DECIMAL_SEPARATOR_FIELD, 14, 15}, + {UNUM_FRACTION_FIELD, 15, 17}}; + UFieldPosition actual; + for (int32_t i = 0; i < sizeof(expectedFields) / sizeof(*expectedFields); i++) { + // Iterate using the UFieldPosition to hold state... + UFieldPosition expected = expectedFields[i]; + actual.field = ufieldpositer_next(ufpositer, &actual.beginIndex, &actual.endIndex); + assertTrue("Should not return a negative index yet", actual.field >= 0); + if (expected.field != actual.field) { + log_err( + "FAIL: iteration %d; expected field %d; got %d\n", i, expected.field, actual.field); + } + if (expected.beginIndex != actual.beginIndex) { + log_err( + "FAIL: iteration %d; expected beginIndex %d; got %d\n", + i, + expected.beginIndex, + actual.beginIndex); + } + if (expected.endIndex != actual.endIndex) { + log_err( + "FAIL: iteration %d; expected endIndex %d; got %d\n", + i, + expected.endIndex, + actual.endIndex); + } + } + actual.field = ufieldpositer_next(ufpositer, &actual.beginIndex, &actual.endIndex); + assertTrue("No more fields; should return a negative index", actual.field < 0); + + // cleanup: + unumf_closeResult(uresult); + unumf_close(uformatter); + ufieldpositer_close(ufpositer); } From 7d4b54dfc359a834ae3ca06880d134e454c17b6d Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Tue, 27 Mar 2018 06:07:17 +0000 Subject: [PATCH 066/129] ICU-13597 Small API tweaks. X-SVN-Rev: 41160 --- icu4c/source/i18n/number_capi.cpp | 5 ++ icu4c/source/i18n/unicode/unumberformatter.h | 52 +++++++++++++++---- .../test/cintltst/unumberformattertst.c | 31 +++++++++++ 3 files changed, 77 insertions(+), 11 deletions(-) diff --git a/icu4c/source/i18n/number_capi.cpp b/icu4c/source/i18n/number_capi.cpp index 1dc2955faa..7095fe0fd6 100644 --- a/icu4c/source/i18n/number_capi.cpp +++ b/icu4c/source/i18n/number_capi.cpp @@ -150,6 +150,11 @@ unumf_resultToString(const UFormattedNumber* uresult, UChar* buffer, int32_t buf const UFormattedNumberData* result = UFormattedNumberData::validate(uresult, *ec); if (U_FAILURE(*ec)) { return 0; } + if (buffer == nullptr) { + // Return the length without setting an error. + return result->string.length(); + } + return result->string.toUnicodeString().extract(buffer, bufferCapacity, *ec); } diff --git a/icu4c/source/i18n/unicode/unumberformatter.h b/icu4c/source/i18n/unicode/unumberformatter.h index 059cbb6491..d0bb64c05f 100644 --- a/icu4c/source/i18n/unicode/unumberformatter.h +++ b/icu4c/source/i18n/unicode/unumberformatter.h @@ -17,6 +17,38 @@ * * This is the C-compatible version of the NumberFormatter API introduced in ICU 60. C++ users should * include unicode/numberformatter.h and use the proper C++ APIs. + * + * The C API accepts a number skeleton string for specifying the settings for formatting, which covers a + * very large subset of all possible number formatting features. For more information on number skeleton + * strings, see unicode/numberformatter.h. + * + * When using UNumberFormatter, which is treated as immutable, the results are exported to a mutable + * UFormattedNumber object, which you subsequently use for populating your string buffer or iterating over + * the fields. + * + * Example code: + *
    + * // Setup:
    + * UErrorCode ec = U_ZERO_ERROR;
    + * UNumberFormatter* uformatter = unumf_openFromSkeletonAndLocale(u"round-integer", -1, "en", &ec);
    + * UFormattedNumber* uresult = unumf_openResult(&ec);
    + * if (U_FAILURE(ec)) { return; }
    + *
    + * // Format a double:
    + * unumf_formatDouble(uformatter, 5142.3, uresult, &ec);
    + * if (U_FAILURE(ec)) { return; }
    + *
    + * // Export the string:
    + * int32_t len = unumf_resultToString(uresult, NULL, 0, &ec);
    + * UChar* buffer = (UChar*) malloc((len+1)*sizeof(UChar));
    + * unumf_resultToString(uresult, buffer, len+1, &ec);
    + * if (U_FAILURE(ec)) { return; }
    + *
    + * // Cleanup:
    + * unumf_close(uformatter);
    + * unumf_closeResult(uresult);
    + * free(buffer);
    + * 
    */ @@ -355,7 +387,8 @@ typedef struct UFormattedNumber UFormattedNumber; /** - * Creates a new UNumberFormatter from the given skeleton string and locale. + * Creates a new UNumberFormatter from the given skeleton string and locale. This is currently the only + * method for creating a new UNumberFormatter. * * For more details on skeleton strings, see the documentation in numberformatter.h. For more details on * the usage of this API, see the documentation at the top of unumberformatter.h. @@ -381,7 +414,7 @@ unumf_openResult(UErrorCode* ec); /** - * Uses a UNumberFormatter to format a double to a UFormattedNumber. A string, field position, and other + * Uses a UNumberFormatter to format an integer to a UFormattedNumber. A string, field position, and other * information can be retrieved from the UFormattedNumber. * * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. @@ -423,19 +456,16 @@ unumf_formatDecimal(const UNumberFormatter* uformatter, const char* value, int32 /** - * Extracts the result number string out of a UFormattedNumber to a UChar buffer. The usual ICU pattern - * is used for writing to the buffer: + * Extracts the result number string out of a UFormattedNumber to a UChar buffer if possible. + * If bufferCapacity is greater than the required length, a terminating NUL is written. + * If bufferCapacity is less than the required length, an error code is set. * - * - If the string is shorter than the buffer, it will be written to the buffer and will be NUL-terminated. - * - If the string is exactly the length of the buffer, it will be written to the buffer, but it will not - * be NUL-terminated, and a warning will be set. - * - If the string is longer than the buffer, nothing will be written to the buffer, and an error will be - * set. - * - * In all cases, the actual length of the string is returned, whether or not it was written to the buffer. + * If NULL is passed as the buffer argument, the required length is returned without setting an error. * * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. * + * @return The required length. + * * @draft ICU 62 */ U_DRAFT int32_t U_EXPORT2 diff --git a/icu4c/source/test/cintltst/unumberformattertst.c b/icu4c/source/test/cintltst/unumberformattertst.c index e8ae8b5013..0e6aa54e19 100644 --- a/icu4c/source/test/cintltst/unumberformattertst.c +++ b/icu4c/source/test/cintltst/unumberformattertst.c @@ -13,16 +13,20 @@ #include "unicode/umisc.h" #include "unicode/unum.h" #include "cintltst.h" +#include "cmemory.h" static void TestSkeletonFormatToString(); static void TestSkeletonFormatToFields(); +static void TestExampleCode(); + void addUNumberFormatterTest(TestNode** root); void addUNumberFormatterTest(TestNode** root) { addTest(root, &TestSkeletonFormatToString, "unumberformatter/TestSkeletonFormatToString"); addTest(root, &TestSkeletonFormatToFields, "unumberformatter/TestSkeletonFormatToFields"); + addTest(root, &TestExampleCode, "unumberformatter/TestExampleCode"); } @@ -130,4 +134,31 @@ static void TestSkeletonFormatToFields() { } +static void TestExampleCode() { + // This is the example code given in unumberformatter.h. + + // Setup: + UErrorCode ec = U_ZERO_ERROR; + UNumberFormatter* uformatter = unumf_openFromSkeletonAndLocale(u"round-integer", -1, "en", &ec); + UFormattedNumber* uresult = unumf_openResult(&ec); + assertSuccess("There should not be a failure in the example code", &ec); + + // Format a double: + unumf_formatDouble(uformatter, 5142.3, uresult, &ec); + assertSuccess("There should not be a failure in the example code", &ec); + + // Export the string: + int32_t len = unumf_resultToString(uresult, NULL, 0, &ec); + UChar* buffer = (UChar*) uprv_malloc((len+1)*sizeof(UChar)); + unumf_resultToString(uresult, buffer, len+1, &ec); + assertSuccess("There should not be a failure in the example code", &ec); + assertUEquals("Should produce expected string result", u"5,142", buffer); + + // Cleanup: + unumf_close(uformatter); + unumf_closeResult(uresult); + uprv_free(buffer); +} + + #endif /* #if !UCONFIG_NO_FORMATTING */ From aa88b7a590343699bd1bc558ef48d8d5b7c5de24 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Wed, 28 Mar 2018 00:39:28 +0000 Subject: [PATCH 067/129] ICU-13597 Reverting formatting changes to unicode/numberformatter.h introduced by r41156 X-SVN-Rev: 41161 --- icu4c/source/i18n/unicode/numberformatter.h | 214 ++++++++++---------- 1 file changed, 103 insertions(+), 111 deletions(-) diff --git a/icu4c/source/i18n/unicode/numberformatter.h b/icu4c/source/i18n/unicode/numberformatter.h index f1bb5316f1..9c79c92492 100644 --- a/icu4c/source/i18n/unicode/numberformatter.h +++ b/icu4c/source/i18n/unicode/numberformatter.h @@ -323,18 +323,15 @@ class U_I18N_API Notation : public UMemory { typedef NotationUnion::ScientificSettings ScientificSettings; - Notation(const NotationType& type, const NotationUnion& union_) - : fType(type), fUnion(union_) {} + Notation(const NotationType &type, const NotationUnion &union_) : fType(type), fUnion(union_) {} - Notation(UErrorCode errorCode) - : fType(NTN_ERROR) { + Notation(UErrorCode errorCode) : fType(NTN_ERROR) { fUnion.errorCode = errorCode; } - Notation() - : fType(NTN_SIMPLE), fUnion() {} + Notation() : fType(NTN_SIMPLE), fUnion() {} - UBool copyErrorTo(UErrorCode& status) const { + UBool copyErrorTo(UErrorCode &status) const { if (fType == NTN_ERROR) { status = fUnion.errorCode; return TRUE; @@ -657,22 +654,20 @@ class U_I18N_API Rounder : public UMemory { UNumberFormatRoundingMode fRoundingMode; - Rounder(const RounderType& type, const RounderUnion& union_, UNumberFormatRoundingMode roundingMode) + Rounder(const RounderType &type, const RounderUnion &union_, UNumberFormatRoundingMode roundingMode) : fType(type), fUnion(union_), fRoundingMode(roundingMode) {} - Rounder(UErrorCode errorCode) - : fType(RND_ERROR) { + Rounder(UErrorCode errorCode) : fType(RND_ERROR) { fUnion.errorCode = errorCode; } - Rounder() - : fType(RND_BOGUS) {} + Rounder() : fType(RND_BOGUS) {} bool isBogus() const { return fType == RND_BOGUS; } - UBool copyErrorTo(UErrorCode& status) const { + UBool copyErrorTo(UErrorCode &status) const { if (fType == RND_ERROR) { status = fUnion.errorCode; return TRUE; @@ -681,15 +676,15 @@ class U_I18N_API Rounder : public UMemory { } // On the parent type so that this method can be called internally on Rounder instances. - Rounder withCurrency(const CurrencyUnit& currency, UErrorCode& status) const; + Rounder withCurrency(const CurrencyUnit ¤cy, UErrorCode &status) const; /** NON-CONST: mutates the current instance. */ - void setLocaleData(const CurrencyUnit& currency, UErrorCode& status); + void setLocaleData(const CurrencyUnit ¤cy, UErrorCode &status); - void apply(impl::DecimalQuantity& value, UErrorCode& status) const; + void apply(impl::DecimalQuantity &value, UErrorCode &status) const; /** Version of {@link #apply} that obeys minInt constraints. Used for scientific notation compatibility mode. */ - void apply(impl::DecimalQuantity& value, int32_t minInt, UErrorCode status); + void apply(impl::DecimalQuantity &value, int32_t minInt, UErrorCode status); /** * Rounding endpoint used by Engineering and Compact notation. Chooses the most appropriate multiplier (magnitude @@ -705,15 +700,16 @@ class U_I18N_API Rounder : public UMemory { * @param producer Function to call to return a multiplier based on a magnitude. * @return The number of orders of magnitude the input was adjusted by this method. */ - int32_t chooseMultiplierAndApply(impl::DecimalQuantity& input, - const impl::MultiplierProducer& producer, UErrorCode& status); + int32_t + chooseMultiplierAndApply(impl::DecimalQuantity &input, const impl::MultiplierProducer &producer, + UErrorCode &status); static FractionRounder constructFraction(int32_t minFrac, int32_t maxFrac); static Rounder constructSignificant(int32_t minSig, int32_t maxSig); - static Rounder constructFractionSignificant(const FractionRounder& base, int32_t minSig, - int32_t maxSig); + static Rounder + constructFractionSignificant(const FractionRounder &base, int32_t minSig, int32_t maxSig); static IncrementRounder constructIncrement(double increment, int32_t minFrac); @@ -830,7 +826,7 @@ class U_I18N_API CurrencyRounder : public Rounder { * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. * @draft ICU 60 */ - Rounder withCurrency(const CurrencyUnit& currency) const; + Rounder withCurrency(const CurrencyUnit ¤cy) const; private: // Inherit constructor @@ -945,7 +941,7 @@ class U_I18N_API IntegerWidth : public UMemory { return !fHasError && fUnion.minMaxInt.fMinInt == -1; } - UBool copyErrorTo(UErrorCode& status) const { + UBool copyErrorTo(UErrorCode &status) const { if (fHasError) { status = fUnion.errorCode; return TRUE; @@ -953,7 +949,7 @@ class U_I18N_API IntegerWidth : public UMemory { return FALSE; } - void apply(impl::DecimalQuantity& quantity, UErrorCode& status) const; + void apply(impl::DecimalQuantity &quantity, UErrorCode &status) const; bool operator==(const IntegerWidth& other) const; @@ -974,11 +970,10 @@ namespace impl { class U_I18N_API SymbolsWrapper : public UMemory { public: /** @internal */ - SymbolsWrapper() - : fType(SYMPTR_NONE), fPtr{nullptr} {} + SymbolsWrapper() : fType(SYMPTR_NONE), fPtr{nullptr} {} /** @internal */ - SymbolsWrapper(const SymbolsWrapper& other); + SymbolsWrapper(const SymbolsWrapper &other); /** @internal */ SymbolsWrapper(SymbolsWrapper&& src) U_NOEXCEPT; @@ -987,22 +982,22 @@ class U_I18N_API SymbolsWrapper : public UMemory { ~SymbolsWrapper(); /** @internal */ - SymbolsWrapper& operator=(const SymbolsWrapper& other); + SymbolsWrapper &operator=(const SymbolsWrapper &other); /** @internal */ - SymbolsWrapper& operator=(SymbolsWrapper&& src) U_NOEXCEPT; + SymbolsWrapper &operator=(SymbolsWrapper&& src) U_NOEXCEPT; /** * The provided object is copied, but we do not adopt it. * @internal */ - void setTo(const DecimalFormatSymbols& dfs); + void setTo(const DecimalFormatSymbols &dfs); /** * Adopt the provided object. * @internal */ - void setTo(const NumberingSystem* ns); + void setTo(const NumberingSystem *ns); /** * Whether the object is currently holding a DecimalFormatSymbols. @@ -1020,16 +1015,16 @@ class U_I18N_API SymbolsWrapper : public UMemory { * Get the DecimalFormatSymbols pointer. No ownership change. * @internal */ - const DecimalFormatSymbols* getDecimalFormatSymbols() const; + const DecimalFormatSymbols *getDecimalFormatSymbols() const; /** * Get the NumberingSystem pointer. No ownership change. * @internal */ - const NumberingSystem* getNumberingSystem() const; + const NumberingSystem *getNumberingSystem() const; /** @internal */ - UBool copyErrorTo(UErrorCode& status) const { + UBool copyErrorTo(UErrorCode &status) const { if (fType == SYMPTR_DFS && fPtr.dfs == nullptr) { status = U_MEMORY_ALLOCATION_ERROR; return TRUE; @@ -1046,11 +1041,11 @@ class U_I18N_API SymbolsWrapper : public UMemory { } fType; union { - const DecimalFormatSymbols* dfs; - const NumberingSystem* ns; + const DecimalFormatSymbols *dfs; + const NumberingSystem *ns; } fPtr; - void doCopyFrom(const SymbolsWrapper& other); + void doCopyFrom(const SymbolsWrapper &other); void doMoveFrom(SymbolsWrapper&& src); @@ -1111,17 +1106,16 @@ class U_I18N_API Grouper : public UMemory { */ UGroupingStrategy fStrategy; - Grouper() - : fGrouping1(-3) {}; + Grouper() : fGrouping1(-3) {}; bool isBogus() const { return fGrouping1 == -3; } /** NON-CONST: mutates the current instance. */ - void setLocaleData(const impl::ParsedPatternInfo& patternInfo, const Locale& locale); + void setLocaleData(const impl::ParsedPatternInfo &patternInfo, const Locale& locale); - bool groupAtPosition(int32_t position, const impl::DecimalQuantity& value) const; + bool groupAtPosition(int32_t position, const impl::DecimalQuantity &value) const; // To allow MacroProps/MicroProps to initialize empty instances: friend struct MacroProps; @@ -1163,19 +1157,17 @@ class U_I18N_API Padder : public UMemory { Padder(int32_t width); - Padder(UErrorCode errorCode) - : fWidth(-3) { // NOLINT + Padder(UErrorCode errorCode) : fWidth(-3) { // NOLINT fUnion.errorCode = errorCode; } - Padder() - : fWidth(-2) {} // NOLINT + Padder() : fWidth(-2) {} // NOLINT bool isBogus() const { return fWidth == -2; } - UBool copyErrorTo(UErrorCode& status) const { + UBool copyErrorTo(UErrorCode &status) const { if (fWidth == -3) { status = fUnion.errorCode; return TRUE; @@ -1187,9 +1179,9 @@ class U_I18N_API Padder : public UMemory { return fWidth > 0; } - int32_t padAndApply(const impl::Modifier& mod1, const impl::Modifier& mod2, - impl::NumberStringBuilder& string, int32_t leftIndex, int32_t rightIndex, - UErrorCode& status) const; + int32_t padAndApply(const impl::Modifier &mod1, const impl::Modifier &mod2, + impl::NumberStringBuilder &string, int32_t leftIndex, int32_t rightIndex, + UErrorCode &status) const; // To allow MacroProps/MicroProps to initialize empty instances: friend struct MacroProps; @@ -1217,8 +1209,7 @@ class U_I18N_API Multiplier : public UMemory { Multiplier(int32_t magnitudeMultiplier, int32_t multiplier); - Multiplier() - : magnitudeMultiplier(0), multiplier(1) {} + Multiplier() : magnitudeMultiplier(0), multiplier(1) {} bool isValid() const { return magnitudeMultiplier != 0 || multiplier != 1; @@ -1280,10 +1271,10 @@ struct U_I18N_API MacroProps : public UMemory { AffixPatternProvider* affixProvider = nullptr; // no ownership /** @internal */ - PluralRules* rules = nullptr; // no ownership + PluralRules *rules = nullptr; // no ownership /** @internal */ - CurrencySymbols* currencySymbols = nullptr; // no ownership + CurrencySymbols *currencySymbols = nullptr; // no ownership /** @internal */ int32_t threshold = DEFAULT_THRESHOLD; @@ -1297,9 +1288,10 @@ struct U_I18N_API MacroProps : public UMemory { * Check all members for errors. * @internal */ - bool copyErrorTo(UErrorCode& status) const { - return notation.copyErrorTo(status) || rounder.copyErrorTo(status) || padder.copyErrorTo(status) || - integerWidth.copyErrorTo(status) || symbols.copyErrorTo(status); + bool copyErrorTo(UErrorCode &status) const { + return notation.copyErrorTo(status) || rounder.copyErrorTo(status) || + padder.copyErrorTo(status) || integerWidth.copyErrorTo(status) || + symbols.copyErrorTo(status); } }; @@ -1340,7 +1332,7 @@ class U_I18N_API NumberFormatterSettings { * @see Notation * @draft ICU 60 */ - Derived notation(const Notation& notation) const& ; + Derived notation(const Notation ¬ation) const &; /** * Overload of notation() for use on an rvalue reference. @@ -1351,7 +1343,7 @@ class U_I18N_API NumberFormatterSettings { * @see #notation * @draft ICU 62 */ - Derived notation(const Notation& notation)&& ; + Derived notation(const Notation ¬ation) &&; /** * Specifies the unit (unit of measure, currency, or percent) to associate with rendered numbers. @@ -1397,7 +1389,7 @@ class U_I18N_API NumberFormatterSettings { * @see #perUnit * @draft ICU 60 */ - Derived unit(const icu::MeasureUnit& unit) const& ; + Derived unit(const icu::MeasureUnit &unit) const &; /** * Overload of unit() for use on an rvalue reference. @@ -1408,7 +1400,7 @@ class U_I18N_API NumberFormatterSettings { * @see #unit * @draft ICU 62 */ - Derived unit(const icu::MeasureUnit& unit)&& ; + Derived unit(const icu::MeasureUnit &unit) &&; /** * Like unit(), but takes ownership of a pointer. Convenient for use with the MeasureFormat factory @@ -1425,7 +1417,7 @@ class U_I18N_API NumberFormatterSettings { * @see MeasureUnit * @draft ICU 60 */ - Derived adoptUnit(icu::MeasureUnit* unit) const& ; + Derived adoptUnit(icu::MeasureUnit *unit) const &; /** * Overload of adoptUnit() for use on an rvalue reference. @@ -1436,7 +1428,7 @@ class U_I18N_API NumberFormatterSettings { * @see #adoptUnit * @draft ICU 62 */ - Derived adoptUnit(icu::MeasureUnit* unit)&& ; + Derived adoptUnit(icu::MeasureUnit *unit) &&; /** * Sets a unit to be used in the denominator. For example, to format "3 m/s", pass METER to the unit and SECOND to @@ -1455,7 +1447,7 @@ class U_I18N_API NumberFormatterSettings { * @see #unit * @draft ICU 61 */ - Derived perUnit(const icu::MeasureUnit& perUnit) const& ; + Derived perUnit(const icu::MeasureUnit &perUnit) const &; /** * Overload of perUnit() for use on an rvalue reference. @@ -1466,7 +1458,7 @@ class U_I18N_API NumberFormatterSettings { * @see #perUnit * @draft ICU 62 */ - Derived perUnit(const icu::MeasureUnit& perUnit)&& ; + Derived perUnit(const icu::MeasureUnit &perUnit) &&; /** * Like perUnit(), but takes ownership of a pointer. Convenient for use with the MeasureFormat factory @@ -1485,7 +1477,7 @@ class U_I18N_API NumberFormatterSettings { * @see MeasureUnit * @draft ICU 61 */ - Derived adoptPerUnit(icu::MeasureUnit* perUnit) const& ; + Derived adoptPerUnit(icu::MeasureUnit *perUnit) const &; /** * Overload of adoptPerUnit() for use on an rvalue reference. @@ -1496,7 +1488,7 @@ class U_I18N_API NumberFormatterSettings { * @see #adoptPerUnit * @draft ICU 62 */ - Derived adoptPerUnit(icu::MeasureUnit* perUnit)&& ; + Derived adoptPerUnit(icu::MeasureUnit *perUnit) &&; /** * Specifies the rounding strategy to use when formatting numbers. @@ -1528,7 +1520,7 @@ class U_I18N_API NumberFormatterSettings { * @see Rounder * @draft ICU 60 */ - Derived rounding(const Rounder& rounder) const& ; + Derived rounding(const Rounder &rounder) const &; /** * Overload of rounding() for use on an rvalue reference. @@ -1539,7 +1531,7 @@ class U_I18N_API NumberFormatterSettings { * @see #rounding * @draft ICU 62 */ - Derived rounding(const Rounder& rounder)&& ; + Derived rounding(const Rounder& rounder) &&; /** * Specifies the grouping strategy to use when formatting numbers. @@ -1568,7 +1560,7 @@ class U_I18N_API NumberFormatterSettings { * @return The fluent chain. * @draft ICU 61 */ - Derived grouping(const UGroupingStrategy& strategy) const& ; + Derived grouping(const UGroupingStrategy &strategy) const &; /** * Overload of grouping() for use on an rvalue reference. @@ -1580,7 +1572,7 @@ class U_I18N_API NumberFormatterSettings { * @provisional This API might change or be removed in a future release. * @draft ICU 62 */ - Derived grouping(const UGroupingStrategy& rounder)&& ; + Derived grouping(const UGroupingStrategy& rounder) &&; /** * Specifies the minimum and maximum number of digits to render before the decimal mark. @@ -1606,7 +1598,7 @@ class U_I18N_API NumberFormatterSettings { * @see IntegerWidth * @draft ICU 60 */ - Derived integerWidth(const IntegerWidth& style) const& ; + Derived integerWidth(const IntegerWidth &style) const &; /** * Overload of integerWidth() for use on an rvalue reference. @@ -1617,7 +1609,7 @@ class U_I18N_API NumberFormatterSettings { * @see #integerWidth * @draft ICU 62 */ - Derived integerWidth(const IntegerWidth& style)&& ; + Derived integerWidth(const IntegerWidth &style) &&; /** * Specifies the symbols (decimal separator, grouping separator, percent sign, numerals, etc.) to use when rendering @@ -1659,7 +1651,7 @@ class U_I18N_API NumberFormatterSettings { * @see DecimalFormatSymbols * @draft ICU 60 */ - Derived symbols(const DecimalFormatSymbols& symbols) const& ; + Derived symbols(const DecimalFormatSymbols &symbols) const &; /** * Overload of symbols() for use on an rvalue reference. @@ -1670,7 +1662,7 @@ class U_I18N_API NumberFormatterSettings { * @see #symbols * @draft ICU 62 */ - Derived symbols(const DecimalFormatSymbols& symbols)&& ; + Derived symbols(const DecimalFormatSymbols &symbols) &&; /** * Specifies that the given numbering system should be used when fetching symbols. @@ -1705,7 +1697,7 @@ class U_I18N_API NumberFormatterSettings { * @see NumberingSystem * @draft ICU 60 */ - Derived adoptSymbols(NumberingSystem* symbols) const& ; + Derived adoptSymbols(NumberingSystem *symbols) const &; /** * Overload of adoptSymbols() for use on an rvalue reference. @@ -1716,7 +1708,7 @@ class U_I18N_API NumberFormatterSettings { * @see #adoptSymbols * @draft ICU 62 */ - Derived adoptSymbols(NumberingSystem* symbols)&& ; + Derived adoptSymbols(NumberingSystem *symbols) &&; /** * Sets the width of the unit (measure unit or currency). Most common values: @@ -1743,7 +1735,7 @@ class U_I18N_API NumberFormatterSettings { * @see UNumberUnitWidth * @draft ICU 60 */ - Derived unitWidth(const UNumberUnitWidth& width) const& ; + Derived unitWidth(const UNumberUnitWidth &width) const &; /** * Overload of unitWidth() for use on an rvalue reference. @@ -1754,7 +1746,7 @@ class U_I18N_API NumberFormatterSettings { * @see #unitWidth * @draft ICU 62 */ - Derived unitWidth(const UNumberUnitWidth& width)&& ; + Derived unitWidth(const UNumberUnitWidth &width) &&; /** * Sets the plus/minus sign display strategy. Most common values: @@ -1782,7 +1774,7 @@ class U_I18N_API NumberFormatterSettings { * @provisional This API might change or be removed in a future release. * @draft ICU 60 */ - Derived sign(const UNumberSignDisplay& style) const& ; + Derived sign(const UNumberSignDisplay &style) const &; /** * Overload of sign() for use on an rvalue reference. @@ -1793,7 +1785,7 @@ class U_I18N_API NumberFormatterSettings { * @see #sign * @draft ICU 62 */ - Derived sign(const UNumberSignDisplay& style)&& ; + Derived sign(const UNumberSignDisplay &style) &&; /** * Sets the decimal separator display strategy. This affects integer numbers with no fraction part. Most common @@ -1821,7 +1813,7 @@ class U_I18N_API NumberFormatterSettings { * @provisional This API might change or be removed in a future release. * @draft ICU 60 */ - Derived decimal(const UNumberDecimalSeparatorDisplay& style) const& ; + Derived decimal(const UNumberDecimalSeparatorDisplay &style) const &; /** * Overload of decimal() for use on an rvalue reference. @@ -1832,7 +1824,7 @@ class U_I18N_API NumberFormatterSettings { * @see #sign * @draft ICU 62 */ - Derived decimal(const UNumberDecimalSeparatorDisplay& style)&& ; + Derived decimal(const UNumberDecimalSeparatorDisplay &style) &&; #ifndef U_HIDE_INTERNAL_API @@ -1841,10 +1833,10 @@ class U_I18N_API NumberFormatterSettings { * * @internal ICU 60: This API is ICU internal only. */ - Derived padding(const impl::Padder& padder) const& ; + Derived padding(const impl::Padder &padder) const &; /** @internal */ - Derived padding(const impl::Padder& padder)&& ; + Derived padding(const impl::Padder &padder) &&; /** * Internal fluent setter to support a custom regulation threshold. A threshold of 1 causes the data structures to @@ -1852,26 +1844,26 @@ class U_I18N_API NumberFormatterSettings { * * @internal ICU 60: This API is ICU internal only. */ - Derived threshold(int32_t threshold) const& ; + Derived threshold(int32_t threshold) const &; /** @internal */ - Derived threshold(int32_t threshold)&& ; + Derived threshold(int32_t threshold) &&; /** * Internal fluent setter to overwrite the entire macros object. * * @internal ICU 60: This API is ICU internal only. */ - Derived macros(const impl::MacroProps& macros) const& ; + Derived macros(const impl::MacroProps& macros) const &; /** @internal */ - Derived macros(const impl::MacroProps& macros)&& ; + Derived macros(const impl::MacroProps& macros) &&; /** @internal */ - Derived macros(impl::MacroProps&& macros) const& ; + Derived macros(impl::MacroProps&& macros) const &; /** @internal */ - Derived macros(impl::MacroProps&& macros)&& ; + Derived macros(impl::MacroProps&& macros) &&; #endif /* U_HIDE_INTERNAL_API */ @@ -1900,7 +1892,7 @@ class U_I18N_API NumberFormatterSettings { * @return TRUE if U_FAILURE(outErrorCode) * @draft ICU 60 */ - UBool copyErrorTo(UErrorCode& outErrorCode) const { + UBool copyErrorTo(UErrorCode &outErrorCode) const { if (U_FAILURE(outErrorCode)) { // Do not overwrite the older error code return TRUE; @@ -1948,7 +1940,7 @@ class U_I18N_API UnlocalizedNumberFormatter * @return The fluent chain. * @draft ICU 60 */ - LocalizedNumberFormatter locale(const icu::Locale& locale) const& ; + LocalizedNumberFormatter locale(const icu::Locale &locale) const &; /** * Overload of locale() for use on an rvalue reference. @@ -1959,7 +1951,7 @@ class U_I18N_API UnlocalizedNumberFormatter * @see #locale * @draft ICU 62 */ - LocalizedNumberFormatter locale(const icu::Locale& locale)&& ; + LocalizedNumberFormatter locale(const icu::Locale &locale) &&; /** * Default constructor: puts the formatter into a valid but undefined state. @@ -1973,7 +1965,7 @@ class U_I18N_API UnlocalizedNumberFormatter * Returns a copy of this UnlocalizedNumberFormatter. * @draft ICU 60 */ - UnlocalizedNumberFormatter(const UnlocalizedNumberFormatter& other); + UnlocalizedNumberFormatter(const UnlocalizedNumberFormatter &other); /** * Move constructor: @@ -2028,7 +2020,7 @@ class U_I18N_API LocalizedNumberFormatter * @return A FormattedNumber object; call .toString() to get the string. * @draft ICU 60 */ - FormattedNumber formatInt(int64_t value, UErrorCode& status) const; + FormattedNumber formatInt(int64_t value, UErrorCode &status) const; /** * Format the given float or double to a string using the settings specified in the NumberFormatter fluent setting @@ -2041,7 +2033,7 @@ class U_I18N_API LocalizedNumberFormatter * @return A FormattedNumber object; call .toString() to get the string. * @draft ICU 60 */ - FormattedNumber formatDouble(double value, UErrorCode& status) const; + FormattedNumber formatDouble(double value, UErrorCode &status) const; /** * Format the given decimal number to a string using the settings @@ -2092,7 +2084,7 @@ class U_I18N_API LocalizedNumberFormatter * Returns a copy of this LocalizedNumberFormatter. * @draft ICU 60 */ - LocalizedNumberFormatter(const LocalizedNumberFormatter& other); + LocalizedNumberFormatter(const LocalizedNumberFormatter &other); /** * Move constructor: @@ -2126,7 +2118,7 @@ class U_I18N_API LocalizedNumberFormatter * @param results * The results object. This method will mutate it to save the results. */ - void formatImpl(impl::UFormattedNumberData* results, UErrorCode& status) const; + void formatImpl(impl::UFormattedNumberData *results, UErrorCode &status) const; #endif @@ -2139,16 +2131,16 @@ class U_I18N_API LocalizedNumberFormatter private: // Note: fCompiled can't be a LocalPointer because impl::NumberFormatterImpl is defined in an internal // header, and LocalPointer needs the full class definition in order to delete the instance. - const impl::NumberFormatterImpl* fCompiled{nullptr}; - char fUnsafeCallCount[8]{}; // internally cast to u_atomic_int32_t + const impl::NumberFormatterImpl* fCompiled {nullptr}; + char fUnsafeCallCount[8] {}; // internally cast to u_atomic_int32_t explicit LocalizedNumberFormatter(const NumberFormatterSettings& other); explicit LocalizedNumberFormatter(NumberFormatterSettings&& src) U_NOEXCEPT; - LocalizedNumberFormatter(const impl::MacroProps& macros, const Locale& locale); + LocalizedNumberFormatter(const impl::MacroProps ¯os, const Locale &locale); - LocalizedNumberFormatter(impl::MacroProps&& macros, const Locale& locale); + LocalizedNumberFormatter(impl::MacroProps &¯os, const Locale &locale); // To give the fluent setters access to this class's constructor: friend class NumberFormatterSettings; @@ -2183,7 +2175,7 @@ class U_I18N_API FormattedNumber : public UMemory { * @draft ICU 60 * @see Appendable */ - Appendable& appendTo(Appendable& appendable); + Appendable &appendTo(Appendable &appendable); /** * Determine the start and end indices of the first occurrence of the given field in the output string. @@ -2204,7 +2196,7 @@ class U_I18N_API FormattedNumber : public UMemory { * @draft ICU 60 * @see UNumberFormatFields */ - void populateFieldPosition(FieldPosition& fieldPosition, UErrorCode& status); + void populateFieldPosition(FieldPosition &fieldPosition, UErrorCode &status); /** * Export the formatted number to a FieldPositionIterator. This allows you to determine which characters in @@ -2220,7 +2212,7 @@ class U_I18N_API FormattedNumber : public UMemory { * @draft ICU 60 * @see UNumberFormatFields */ - void populateFieldPositionIterator(FieldPositionIterator& iterator, UErrorCode& status); + void populateFieldPositionIterator(FieldPositionIterator &iterator, UErrorCode &status); #ifndef U_HIDE_INTERNAL_API @@ -2240,7 +2232,6 @@ class U_I18N_API FormattedNumber : public UMemory { // Don't allow copying of FormattedNumber, but moving is okay. FormattedNumber(const FormattedNumber&) = delete; - FormattedNumber& operator=(const FormattedNumber&) = delete; /** @@ -2265,7 +2256,7 @@ class U_I18N_API FormattedNumber : public UMemory { private: // Can't use LocalPointer because UFormattedNumberData is forward-declared - const impl::UFormattedNumberData* fResults; + const impl::UFormattedNumberData *fResults; // Error code for the terminal methods UErrorCode fErrorCode; @@ -2274,11 +2265,11 @@ class U_I18N_API FormattedNumber : public UMemory { * Internal constructor from data type. Adopts the data pointer. * @internal */ - explicit FormattedNumber(const impl::UFormattedNumberData* results) - : fResults(results), fErrorCode(U_ZERO_ERROR) {}; + explicit FormattedNumber(impl::UFormattedNumberData *results) + : fResults(results), fErrorCode(U_ZERO_ERROR) {}; explicit FormattedNumber(UErrorCode errorCode) - : fResults(nullptr), fErrorCode(errorCode) {}; + : fResults(nullptr), fErrorCode(errorCode) {}; // To give LocalizedNumberFormatter format methods access to this class's constructor: friend class LocalizedNumberFormatter; @@ -2309,7 +2300,7 @@ class U_I18N_API NumberFormatter final { * @return A {@link LocalizedNumberFormatter}, to be used for chaining. * @draft ICU 60 */ - static LocalizedNumberFormatter withLocale(const Locale& locale); + static LocalizedNumberFormatter withLocale(const Locale &locale); /** * Call this method at the beginning of a NumberFormatter fluent chain to create an instance based @@ -2338,3 +2329,4 @@ U_NAMESPACE_END #endif // __NUMBERFORMATTER_H__ #endif /* #if !UCONFIG_NO_FORMATTING */ + From 2ede84ce4742c60a14a3fd7354f9cee8267f3bae Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Wed, 28 Mar 2018 03:42:12 +0000 Subject: [PATCH 068/129] ICU-13597 Fixing safety of toUnicodeString() readonly aliases by moving that behavior to a new method, toTempUnicodeString(). X-SVN-Rev: 41164 --- icu4c/source/common/unicode/utypes.h | 3 ++- icu4c/source/common/utypes.cpp | 3 ++- icu4c/source/i18n/number_capi.cpp | 2 +- icu4c/source/i18n/number_skeletons.cpp | 11 +++++++---- icu4c/source/i18n/number_skeletons.h | 2 -- icu4c/source/i18n/number_stringbuilder.cpp | 4 ++++ icu4c/source/i18n/number_stringbuilder.h | 9 +++++++++ icu4c/source/i18n/number_types.h | 9 +++++++++ icu4c/source/i18n/number_utils.h | 13 +++++++------ icu4c/source/i18n/numparse_currency.cpp | 5 ++--- icu4c/source/i18n/numparse_stringsegment.cpp | 6 +++++- icu4c/source/i18n/numparse_types.h | 2 ++ icu4c/source/i18n/unicode/unum.h | 11 ++++++++--- .../source/test/intltest/numbertest_affixutils.cpp | 1 + .../test/intltest/numbertest_stringsegment.cpp | 3 +++ 15 files changed, 62 insertions(+), 22 deletions(-) diff --git a/icu4c/source/common/unicode/utypes.h b/icu4c/source/common/unicode/utypes.h index dd89f39acb..af700d53f8 100644 --- a/icu4c/source/common/unicode/utypes.h +++ b/icu4c/source/common/unicode/utypes.h @@ -541,13 +541,14 @@ typedef enum UErrorCode { U_FORMAT_INEXACT_ERROR, /**< Cannot format a number exactly and rounding mode is ROUND_UNNECESSARY @stable ICU 4.8 */ #ifndef U_HIDE_DRAFT_API U_NUMBER_ARG_OUTOFBOUNDS_ERROR, /**< The argument to a NumberFormatter helper method was out of bounds; the bounds are usually 0 to 999. @draft ICU 61 */ + U_NUMBER_SKELETON_SYNTAX_ERROR, /**< The number skeleton passed to C++ NumberFormatter or C UNumberFormatter was invalid or contained a syntax error. @draft ICU 62 */ #endif // U_HIDE_DRAFT_API #ifndef U_HIDE_DEPRECATED_API /** * One more than the highest normal formatting API error code. * @deprecated ICU 58 The numeric value may change over time, see ICU ticket #12420. */ - U_FMT_PARSE_ERROR_LIMIT = 0x10113, + U_FMT_PARSE_ERROR_LIMIT = 0x10114, #endif // U_HIDE_DEPRECATED_API /* diff --git a/icu4c/source/common/utypes.cpp b/icu4c/source/common/utypes.cpp index 5d6a0504ba..7531e46568 100644 --- a/icu4c/source/common/utypes.cpp +++ b/icu4c/source/common/utypes.cpp @@ -126,7 +126,8 @@ _uFmtErrorName[U_FMT_PARSE_ERROR_LIMIT - U_FMT_PARSE_ERROR_START] = { "U_DEFAULT_KEYWORD_MISSING", "U_DECIMAL_NUMBER_SYNTAX_ERROR", "U_FORMAT_INEXACT_ERROR", - "U_NUMBER_ARG_OUTOFBOUNDS_ERROR" + "U_NUMBER_ARG_OUTOFBOUNDS_ERROR", + "U_NUMBER_SKELETON_SYNTAX_ERROR", }; static const char * const diff --git a/icu4c/source/i18n/number_capi.cpp b/icu4c/source/i18n/number_capi.cpp index 7095fe0fd6..f40b7dad5c 100644 --- a/icu4c/source/i18n/number_capi.cpp +++ b/icu4c/source/i18n/number_capi.cpp @@ -155,7 +155,7 @@ unumf_resultToString(const UFormattedNumber* uresult, UChar* buffer, int32_t buf return result->string.length(); } - return result->string.toUnicodeString().extract(buffer, bufferCapacity, *ec); + return result->string.toTempUnicodeString().extract(buffer, bufferCapacity, *ec); } U_CAPI void U_EXPORT2 diff --git a/icu4c/source/i18n/number_skeletons.cpp b/icu4c/source/i18n/number_skeletons.cpp index 0958d95c55..444471ea3b 100644 --- a/icu4c/source/i18n/number_skeletons.cpp +++ b/icu4c/source/i18n/number_skeletons.cpp @@ -774,7 +774,9 @@ bool blueprint_helpers::parseExponentSignOption(const StringSegment& segment, MacroProps& macros, UErrorCode&) { // Get the sign display type out of the CharsTrie data structure. UCharsTrie tempStemTrie(kSerializedStemTrie); - UStringTrieResult result = tempStemTrie.next(segment.toUnicodeString().getBuffer(), segment.length()); + UStringTrieResult result = tempStemTrie.next( + segment.toTempUnicodeString().getBuffer(), + segment.length()); if (result != USTRINGTRIE_INTERMEDIATE_VALUE && result != USTRINGTRIE_FINAL_VALUE) { return false; } @@ -788,6 +790,7 @@ blueprint_helpers::parseExponentSignOption(const StringSegment& segment, MacroPr void blueprint_helpers::parseCurrencyOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status) { + // Can't use toTempUnicodeString() because getTerminatedBuffer is non-const const UChar* currencyCode = segment.toUnicodeString().getTerminatedBuffer(); UErrorCode localStatus = U_ZERO_ERROR; CurrencyUnit currency(currencyCode, localStatus); @@ -808,7 +811,7 @@ blueprint_helpers::generateCurrencyOption(const CurrencyUnit& currency, UnicodeS void blueprint_helpers::parseMeasureUnitOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status) { - UnicodeString stemString = segment.toUnicodeString(); + const UnicodeString stemString = segment.toTempUnicodeString(); // NOTE: The category (type) of the unit is guaranteed to be a valid subtag (alphanumeric) // http://unicode.org/reports/tr35/#Validity_Data @@ -1043,7 +1046,7 @@ void blueprint_helpers::parseIncrementOption(const StringSegment& segment, Macro UErrorCode& status) { // Need to do char <-> UChar conversion... CharString buffer; - SKELETON_UCHAR_TO_CHAR(buffer, segment.toUnicodeString(), 0, segment.length(), status); + SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status); // Utilize DecimalQuantity/decNumber to parse this for us. DecimalQuantity dq; @@ -1155,7 +1158,7 @@ void blueprint_helpers::parseNumberingSystemOption(const StringSegment& segment, UErrorCode& status) { // Need to do char <-> UChar conversion... CharString buffer; - SKELETON_UCHAR_TO_CHAR(buffer, segment.toUnicodeString(), 0, segment.length(), status); + SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status); NumberingSystem* ns = NumberingSystem::createInstanceByName(buffer.data(), status); if (ns == nullptr) { diff --git a/icu4c/source/i18n/number_skeletons.h b/icu4c/source/i18n/number_skeletons.h index aeae63f4d9..14f1bdbe70 100644 --- a/icu4c/source/i18n/number_skeletons.h +++ b/icu4c/source/i18n/number_skeletons.h @@ -16,8 +16,6 @@ using icu::numparse::impl::StringSegment; U_NAMESPACE_BEGIN namespace number { namespace impl { -static constexpr UErrorCode U_NUMBER_SKELETON_SYNTAX_ERROR = U_ILLEGAL_ARGUMENT_ERROR; - // Forward-declaration struct SeenMacroProps; diff --git a/icu4c/source/i18n/number_stringbuilder.cpp b/icu4c/source/i18n/number_stringbuilder.cpp index 2759ed4568..a1900deab8 100644 --- a/icu4c/source/i18n/number_stringbuilder.cpp +++ b/icu4c/source/i18n/number_stringbuilder.cpp @@ -334,6 +334,10 @@ int32_t NumberStringBuilder::remove(int32_t index, int32_t count) { } UnicodeString NumberStringBuilder::toUnicodeString() const { + return UnicodeString(getCharPtr() + fZero, fLength); +} + +const UnicodeString NumberStringBuilder::toTempUnicodeString() const { // Readonly-alias constructor: return UnicodeString(FALSE, getCharPtr() + fZero, fLength); } diff --git a/icu4c/source/i18n/number_stringbuilder.h b/icu4c/source/i18n/number_stringbuilder.h index a97cc9ca02..f92547679c 100644 --- a/icu4c/source/i18n/number_stringbuilder.h +++ b/icu4c/source/i18n/number_stringbuilder.h @@ -84,8 +84,17 @@ class U_I18N_API NumberStringBuilder : public UMemory { int32_t insert(int32_t index, const NumberStringBuilder &other, UErrorCode &status); + /** + * Gets a "safe" UnicodeString that can be used even after the NumberStringBuilder is destructed. + * */ UnicodeString toUnicodeString() const; + /** + * Gets an "unsafe" UnicodeString that is valid only as long as the NumberStringBuilder is alive and + * unchanged. Slightly faster than toUnicodeString(). + */ + const UnicodeString toTempUnicodeString() const; + UnicodeString toDebugString() const; const char16_t *chars() const; diff --git a/icu4c/source/i18n/number_types.h b/icu4c/source/i18n/number_types.h index ac0188f498..1081919c4c 100644 --- a/icu4c/source/i18n/number_types.h +++ b/icu4c/source/i18n/number_types.h @@ -111,7 +111,16 @@ class U_I18N_API CharSequence { } } + /** + * Gets a "safe" UnicodeString that can be used even after the CharSequence is destructed. + * */ virtual UnicodeString toUnicodeString() const = 0; + + /** + * Gets an "unsafe" UnicodeString that is valid only as long as the CharSequence is alive and + * unchanged. Slightly faster than toUnicodeString(). + */ + virtual const UnicodeString toTempUnicodeString() const = 0; }; class U_I18N_API AffixPatternProvider { diff --git a/icu4c/source/i18n/number_utils.h b/icu4c/source/i18n/number_utils.h index 60879db085..8f74692cc6 100644 --- a/icu4c/source/i18n/number_utils.h +++ b/icu4c/source/i18n/number_utils.h @@ -39,12 +39,13 @@ class UnicodeStringCharSequence : public CharSequence { } UnicodeString toUnicodeString() const U_OVERRIDE { - // Allocate a UnicodeString of the correct length - UnicodeString output(length(), 0, -1); - for (int32_t i = 0; i < length(); i++) { - output.append(charAt(i)); - } - return output; + // Performs a copy: + return fStr; + } + + const UnicodeString toTempUnicodeString() const U_OVERRIDE { + // Readonly alias: + return UnicodeString().fastCopyFrom(fStr); } private: diff --git a/icu4c/source/i18n/numparse_currency.cpp b/icu4c/source/i18n/numparse_currency.cpp index 61f07acce2..504df74e36 100644 --- a/icu4c/source/i18n/numparse_currency.cpp +++ b/icu4c/source/i18n/numparse_currency.cpp @@ -33,9 +33,8 @@ bool CurrencyNamesMatcher::match(StringSegment& segment, ParsedNumber& result, U return false; } - // NOTE: This requires a new UnicodeString to be allocated, instead of using the StringSegment. - // This should be fixed with #13584. - UnicodeString segmentString = segment.toUnicodeString(); + // NOTE: This call site should be improved with #13584. + const UnicodeString segmentString = segment.toTempUnicodeString(); // Try to parse the currency ParsePosition ppos(0); diff --git a/icu4c/source/i18n/numparse_stringsegment.cpp b/icu4c/source/i18n/numparse_stringsegment.cpp index 0a6e4fd104..6f35deb7e7 100644 --- a/icu4c/source/i18n/numparse_stringsegment.cpp +++ b/icu4c/source/i18n/numparse_stringsegment.cpp @@ -61,6 +61,10 @@ UChar32 StringSegment::codePointAt(int32_t index) const { } UnicodeString StringSegment::toUnicodeString() const { + return UnicodeString(fStr.getBuffer() + fStart, fEnd - fStart); +} + +const UnicodeString StringSegment::toTempUnicodeString() const { // Use the readonly-aliasing constructor for efficiency. return UnicodeString(FALSE, fStr.getBuffer() + fStart, fEnd - fStart); } @@ -134,7 +138,7 @@ bool StringSegment::codePointsEqual(UChar32 cp1, UChar32 cp2, bool foldCase) { } bool StringSegment::operator==(const UnicodeString& other) const { - return toUnicodeString() == other; + return toTempUnicodeString() == other; } diff --git a/icu4c/source/i18n/numparse_types.h b/icu4c/source/i18n/numparse_types.h index b02bb249ce..420a5f88c5 100644 --- a/icu4c/source/i18n/numparse_types.h +++ b/icu4c/source/i18n/numparse_types.h @@ -203,6 +203,8 @@ class StringSegment : public UMemory, public ::icu::number::impl::CharSequence { UnicodeString toUnicodeString() const override; + const UnicodeString toTempUnicodeString() const override; + /** * Returns the first code point in the string segment, or -1 if the string starts with an invalid * code point. diff --git a/icu4c/source/i18n/unicode/unum.h b/icu4c/source/i18n/unicode/unum.h index 99864a169b..a8894a0052 100644 --- a/icu4c/source/i18n/unicode/unum.h +++ b/icu4c/source/i18n/unicode/unum.h @@ -29,12 +29,13 @@ /** * \file - * \brief C API: NumberFormat + * \brief C API: Compatibility APIs for number formatting. * *

    Number Format C API

    * - *

    IMPORTANT: New users with C++ capabilities are - * strongly encouraged to see if numberformatter.h fits their use case. + *

    IMPORTANT: New users with are strongly encouraged to + * see if unumberformatter.h fits their use case. Although not deprecated, + * this header is provided for backwards compatibility only. * * Number Format C API Provides functions for * formatting and parsing a number. Also provides methods for @@ -399,6 +400,10 @@ typedef enum UNumberFormatFields { * number format is opened using the given pattern, which must conform * to the syntax described in DecimalFormat or RuleBasedNumberFormat, * respectively. + * + *

    NOTE:: New users with are strongly encouraged to + * use unumf_openWithSkeletonAndLocale instead of unum_open. + * * @param pattern A pattern specifying the format to use. * This parameter is ignored unless the style is * UNUM_PATTERN_DECIMAL or UNUM_PATTERN_RULEBASED. diff --git a/icu4c/source/test/intltest/numbertest_affixutils.cpp b/icu4c/source/test/intltest/numbertest_affixutils.cpp index 63c155ca49..1d0dcc710b 100644 --- a/icu4c/source/test/intltest/numbertest_affixutils.cpp +++ b/icu4c/source/test/intltest/numbertest_affixutils.cpp @@ -226,6 +226,7 @@ void AffixUtilsTest::testUnescapeWithSymbolProvider() { AffixUtils::unescape(UnicodeStringCharSequence(input), sb, 0, provider, status); assertSuccess("Spot 1", status); assertEquals(input, expected, sb.toUnicodeString()); + assertEquals(input, expected, sb.toTempUnicodeString()); } // Test insertion position diff --git a/icu4c/source/test/intltest/numbertest_stringsegment.cpp b/icu4c/source/test/intltest/numbertest_stringsegment.cpp index 665bc7c52b..6c11df8f96 100644 --- a/icu4c/source/test/intltest/numbertest_stringsegment.cpp +++ b/icu4c/source/test/intltest/numbertest_stringsegment.cpp @@ -50,10 +50,13 @@ void StringSegmentTest::testLength() { void StringSegmentTest::testCharAt() { StringSegment segment(SAMPLE_STRING, 0); assertEquals("Initial", SAMPLE_STRING, segment.toUnicodeString()); + assertEquals("Initial", SAMPLE_STRING, segment.toTempUnicodeString()); segment.adjustOffset(3); assertEquals("After adjust-offset", UnicodeString(u"radio 📻"), segment.toUnicodeString()); + assertEquals("After adjust-offset", UnicodeString(u"radio 📻"), segment.toTempUnicodeString()); segment.setLength(5); assertEquals("After adjust-length", UnicodeString(u"radio"), segment.toUnicodeString()); + assertEquals("After adjust-length", UnicodeString(u"radio"), segment.toTempUnicodeString()); } void StringSegmentTest::testGetCodePoint() { From a5096d30753f46c2be9249a3e3736d64d69fe2fe Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Thu, 29 Mar 2018 00:47:26 +0000 Subject: [PATCH 069/129] ICU-8610 Small fixes to API docs for C++ number skeletons. X-SVN-Rev: 41169 --- icu4c/source/i18n/unicode/numberformatter.h | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/icu4c/source/i18n/unicode/numberformatter.h b/icu4c/source/i18n/unicode/numberformatter.h index 21e42c11b0..51ead000cf 100644 --- a/icu4c/source/i18n/unicode/numberformatter.h +++ b/icu4c/source/i18n/unicode/numberformatter.h @@ -1884,16 +1884,13 @@ class U_I18N_API NumberFormatterSettings { /** * Creates a skeleton string representation of this number formatter. A skeleton string is a * locale-agnostic serialized form of a number formatter. - *

    + * * Not all options are capable of being represented in the skeleton string; for example, a - * DecimalFormatSymbols object. If any such option is encountered, an - * {@link UnsupportedOperationException} is thrown. - *

    + * DecimalFormatSymbols object. If any such option is encountered, the error code is set to + * U_UNSUPPORTED_ERROR. + * * The returned skeleton is in normalized form, such that two number formatters with equivalent * behavior should produce the same skeleton. - *

    - * Sets an error code if the number formatter has an option that cannot be represented in a skeleton - * string. * * @return A number skeleton string with behavior corresponding to this number formatter. * @draft ICU 62 @@ -2322,8 +2319,9 @@ class U_I18N_API NumberFormatter final { * * @param skeleton * The skeleton string off of which to base this NumberFormatter. + * @param status + * Set to U_NUMBER_SKELETON_SYNTAX_ERROR if the skeleton was invalid. * @return An UnlocalizedNumberFormatter, to be used for chaining. - * @throws SkeletonSyntaxException If the given string is not a valid number formatting skeleton. * @draft ICU 62 */ static UnlocalizedNumberFormatter fromSkeleton(const UnicodeString& skeleton, UErrorCode& status); From e5bda1eb0e45dad5d56b4d793786999f188ce182 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Fri, 30 Mar 2018 04:28:53 +0000 Subject: [PATCH 070/129] ICU-13634 Various fixes to fix remaining compatibility issues in data-driven test. Includes fix for a memory sanitizer issue. X-SVN-Rev: 41174 --- icu4c/source/i18n/fmtable.cpp | 4 +- icu4c/source/i18n/number_currencysymbols.cpp | 9 ++- icu4c/source/i18n/number_decimalquantity.cpp | 26 ++++++- icu4c/source/i18n/numparse_currency.cpp | 3 +- icu4c/source/i18n/numparse_impl.cpp | 2 +- .../source/test/intltest/numbertest_parse.cpp | 6 +- .../intltest/numbertest_stringsegment.cpp | 10 +-- icu4c/source/test/intltest/numfmtst.cpp | 73 +++++++++++++------ .../numberformattestspecification.txt | 25 +++---- .../number/DecimalQuantity_AbstractBCD.java | 16 +++- .../icu/impl/number/parse/ParsedNumber.java | 1 + .../format/NumberFormatDataDrivenTest.java | 2 +- 12 files changed, 117 insertions(+), 60 deletions(-) diff --git a/icu4c/source/i18n/fmtable.cpp b/icu4c/source/i18n/fmtable.cpp index c65c5c2e09..862b5f912e 100644 --- a/icu4c/source/i18n/fmtable.cpp +++ b/icu4c/source/i18n/fmtable.cpp @@ -771,7 +771,7 @@ Formattable::adoptDecimalQuantity(DecimalQuantity *dq) { // Cannot use the set() functions because they would delete the fDecimalNum value, // TODO: fDecimalQuantity->fitsInInt() to kLong type. /* - if (fDecimalQuantity->fitsIntoLong(FALSE)) { + if (fDecimalQuantity->fitsInInt()) { fType = kLong; fValue.fInt64 = fDecimalNum->getLong(); } else @@ -794,7 +794,7 @@ Formattable::setDecimalNumber(StringPiece numberString, UErrorCode &status) { } dispose(); - DecimalQuantity* dq = new DecimalQuantity(); + auto* dq = new DecimalQuantity(); dq->setToDecNumber(numberString, status); adoptDecimalQuantity(dq); diff --git a/icu4c/source/i18n/number_currencysymbols.cpp b/icu4c/source/i18n/number_currencysymbols.cpp index 051212fb6b..8f05da78c4 100644 --- a/icu4c/source/i18n/number_currencysymbols.cpp +++ b/icu4c/source/i18n/number_currencysymbols.cpp @@ -61,7 +61,7 @@ UnicodeString CurrencySymbols::loadSymbol(UCurrNameStyle selector, UErrorCode& s &ignoredIsChoiceFormatFillIn, &symbolLen, &status); - // Readonly-aliasing char16_t* constructor: + // Readonly-aliasing char16_t* constructor, which points to a resource bundle: return UnicodeString(TRUE, symbol, symbolLen); } @@ -69,8 +69,9 @@ UnicodeString CurrencySymbols::getIntlCurrencySymbol(UErrorCode&) const { if (!fIntlCurrencySymbol.isBogus()) { return fIntlCurrencySymbol; } - // Readonly-aliasing char16_t* constructor: - return UnicodeString(TRUE, fCurrency.getISOCurrency(), 3); + // Note: Not safe to use readonly-aliasing constructor here because the buffer belongs to this object, + // which could be destructed or moved during the lifetime of the return value. + return UnicodeString(fCurrency.getISOCurrency(), 3); } UnicodeString CurrencySymbols::getPluralName(StandardPlural::Form plural, UErrorCode& status) const { @@ -83,7 +84,7 @@ UnicodeString CurrencySymbols::getPluralName(StandardPlural::Form plural, UError StandardPlural::getKeyword(plural), &symbolLen, &status); - // Readonly-aliasing char16_t* constructor: + // Readonly-aliasing char16_t* constructor, which points to a resource bundle: return UnicodeString(TRUE, symbol, symbolLen); } diff --git a/icu4c/source/i18n/number_decimalquantity.cpp b/icu4c/source/i18n/number_decimalquantity.cpp index 172dd23f42..538cf81f64 100644 --- a/icu4c/source/i18n/number_decimalquantity.cpp +++ b/icu4c/source/i18n/number_decimalquantity.cpp @@ -56,6 +56,9 @@ void stringToDecNumber(StringPiece n, DecNumberWithStorage &dn, UErrorCode& stat // Check for invalid syntax and set the corresponding error code. if ((set.status & DEC_Conversion_syntax) != 0) { status = U_DECIMAL_NUMBER_SYNTAX_ERROR; + } else if (set.status != 0) { + // Not a syntax error, but some other error, like an exponent that is too large. + status = U_UNSUPPORTED_ERROR; } } @@ -535,12 +538,26 @@ double DecimalQuantity::toDouble() const { if (_scale >= 0) { // 1e22 is the largest exact double. int32_t i = _scale; - for (; i >= 22; i -= 22) result *= 1e22; + for (; i >= 22; i -= 22) { + result *= 1e22; + if (uprv_isInfinite(result)) { + // Further multiplications will not be productive. + i = 0; + break; + } + } result *= DOUBLE_MULTIPLIERS[i]; } else { // 1e22 is the largest exact double. int32_t i = _scale; - for (; i <= -22; i += 22) result /= 1e22; + for (; i <= -22; i += 22) { + result /= 1e22; + if (result == 0.0) { + // Further divisions will not be productive. + i = 0; + break; + } + } result /= DOUBLE_MULTIPLIERS[-i]; } if (isNegative()) { result = -result; } @@ -1078,11 +1095,12 @@ UnicodeString DecimalQuantity::toString() const { } UnicodeString DecimalQuantity::toNumberString() const { - MaybeStackArray digits(precision + 11); + // 13 should hold both the largest and the smallest int32_t plus exponent separator and NUL + MaybeStackArray digits(precision + 13); for (int32_t i = 0; i < precision; i++) { digits[i] = getDigitPos(precision - i - 1) + '0'; } - snprintf(digits.getAlias() + precision, 11, "E%d", scale); + snprintf(digits.getAlias() + precision, 13, "E%d", scale); return UnicodeString(digits.getAlias(), -1, US_INV); } diff --git a/icu4c/source/i18n/numparse_currency.cpp b/icu4c/source/i18n/numparse_currency.cpp index 504df74e36..6dee3d28d4 100644 --- a/icu4c/source/i18n/numparse_currency.cpp +++ b/icu4c/source/i18n/numparse_currency.cpp @@ -99,7 +99,8 @@ bool CurrencyCustomMatcher::match(StringSegment& segment, ParsedNumber& result, } bool CurrencyCustomMatcher::smokeTest(const StringSegment& segment) const { - return segment.startsWith(fCurrency1) || segment.startsWith(fCurrency2); + return segment.startsWith(fCurrency1) + || segment.startsWith(fCurrency2); } UnicodeString CurrencyCustomMatcher::toString() const { diff --git a/icu4c/source/i18n/numparse_impl.cpp b/icu4c/source/i18n/numparse_impl.cpp index d8338ae0cf..c528d8efa8 100644 --- a/icu4c/source/i18n/numparse_impl.cpp +++ b/icu4c/source/i18n/numparse_impl.cpp @@ -223,7 +223,7 @@ void NumberParserImpl::parse(const UnicodeString& input, int32_t start, bool gre UErrorCode& status) const { U_ASSERT(fFrozen); // TODO: Check start >= 0 and start < input.length() - StringSegment segment(input, fParseFlags); + StringSegment segment(input, 0 != (fParseFlags & PARSE_FLAG_IGNORE_CASE)); segment.adjustOffset(start); if (greedy) { parseGreedyRecursive(segment, result, status); diff --git a/icu4c/source/test/intltest/numbertest_parse.cpp b/icu4c/source/test/intltest/numbertest_parse.cpp index d97141a489..11d6d64cdf 100644 --- a/icu4c/source/test/intltest/numbertest_parse.cpp +++ b/icu4c/source/test/intltest/numbertest_parse.cpp @@ -201,7 +201,7 @@ void NumberParserTest::testSeriesMatcher() { for (auto& cas : cases) { UnicodeString input(cas.input); - StringSegment segment(input, 0); + StringSegment segment(input, false); ParsedNumber result; bool actualMaybeMore = series.match(segment, result, status); int actualOffset = segment.getOffset(); @@ -242,7 +242,7 @@ void NumberParserTest::testCurrencyAnyMatcher() { for (auto& cas : cases) { UnicodeString input(cas.input); - StringSegment segment(input, 0); + StringSegment segment(input, false); ParsedNumber result; matcher.match(segment, result, status); assertEquals("Parsing " + input, cas.expectedCurrencyCode, result.currencyCode); @@ -293,7 +293,7 @@ void NumberParserTest::testAffixPatternMatcher() { assertEquals(affixPattern + " " + cas.exactMatch, cas.expectedMatcherLength, matcher.length()); // Check that the matcher works on a sample string - StringSegment segment(sampleParseableString, 0); + StringSegment segment(sampleParseableString, false); ParsedNumber result; matcher.match(segment, result, status); assertEquals(affixPattern + " " + cas.exactMatch, sampleParseableString.length(), result.charEnd); diff --git a/icu4c/source/test/intltest/numbertest_stringsegment.cpp b/icu4c/source/test/intltest/numbertest_stringsegment.cpp index 6c11df8f96..b174828e1f 100644 --- a/icu4c/source/test/intltest/numbertest_stringsegment.cpp +++ b/icu4c/source/test/intltest/numbertest_stringsegment.cpp @@ -24,7 +24,7 @@ void StringSegmentTest::runIndexedTest(int32_t index, UBool exec, const char*&na } void StringSegmentTest::testOffset() { - StringSegment segment(SAMPLE_STRING, 0); + StringSegment segment(SAMPLE_STRING, false); assertEquals("Initial Offset", 0, segment.getOffset()); segment.adjustOffset(3); assertEquals("Adjust A", 3, segment.getOffset()); @@ -35,7 +35,7 @@ void StringSegmentTest::testOffset() { } void StringSegmentTest::testLength() { - StringSegment segment(SAMPLE_STRING, 0); + StringSegment segment(SAMPLE_STRING, false); assertEquals("Initial length", 11, segment.length()); segment.adjustOffset(3); assertEquals("Adjust", 8, segment.length()); @@ -48,7 +48,7 @@ void StringSegmentTest::testLength() { } void StringSegmentTest::testCharAt() { - StringSegment segment(SAMPLE_STRING, 0); + StringSegment segment(SAMPLE_STRING, false); assertEquals("Initial", SAMPLE_STRING, segment.toUnicodeString()); assertEquals("Initial", SAMPLE_STRING, segment.toTempUnicodeString()); segment.adjustOffset(3); @@ -60,7 +60,7 @@ void StringSegmentTest::testCharAt() { } void StringSegmentTest::testGetCodePoint() { - StringSegment segment(SAMPLE_STRING, 0); + StringSegment segment(SAMPLE_STRING, false); assertEquals("Double-width code point", 0x1F4FB, segment.getCodePoint()); segment.setLength(1); assertEquals("Inalid A", -1, segment.getCodePoint()); @@ -72,7 +72,7 @@ void StringSegmentTest::testGetCodePoint() { } void StringSegmentTest::testCommonPrefixLength() { - StringSegment segment(SAMPLE_STRING, 0); + StringSegment segment(SAMPLE_STRING, false); assertEquals("", 11, segment.getCommonPrefixLength(SAMPLE_STRING)); assertEquals("", 4, segment.getCommonPrefixLength(u"📻 r")); assertEquals("", 3, segment.getCommonPrefixLength(u"📻 x")); diff --git a/icu4c/source/test/intltest/numfmtst.cpp b/icu4c/source/test/intltest/numfmtst.cpp index 29d9379173..ee638ea36a 100644 --- a/icu4c/source/test/intltest/numfmtst.cpp +++ b/icu4c/source/test/intltest/numfmtst.cpp @@ -105,7 +105,7 @@ static DecimalQuantity &strToDigitList( } CharString formatValue; formatValue.appendInvariantChars(str, status); - digitList.setToDecNumber(StringPiece(formatValue.data()), status); + digitList.setToDecNumber({formatValue.data(), formatValue.length()}, status); return digitList; } @@ -332,7 +332,9 @@ UBool NumberFormatTestDataDriven::isFormatPass( } } double doubleVal = digitList.toDouble(); - { + DecimalQuantity doubleCheck; + doubleCheck.setToDouble(doubleVal); + if (digitList == doubleCheck) { // skip cases where the double does not round-trip UnicodeString appendTo; format(*fmtPtr, doubleVal, appendTo, status); if (U_FAILURE(status)) { @@ -345,7 +347,7 @@ UBool NumberFormatTestDataDriven::isFormatPass( return FALSE; } } - if (!uprv_isNaN(doubleVal) && !uprv_isInfinite(doubleVal) && doubleVal == uprv_floor(doubleVal)) { + if (!uprv_isNaN(doubleVal) && !uprv_isInfinite(doubleVal) && digitList.fitsInLong()) { int64_t intVal = digitList.toLong(); { UnicodeString appendTo; @@ -423,40 +425,55 @@ UBool NumberFormatTestDataDriven::isParsePass( appendErrorMessage = appendErrorMessage + ppos.getErrorIndex(); return FALSE; } - UnicodeString resultStr(UnicodeString::fromUTF8(result.getDecimalNumber(status))); if (tuple.output == "fail") { - appendErrorMessage.append(UnicodeString("Parse succeeded: ") + resultStr + ", but was expected to fail."); + appendErrorMessage.append(UnicodeString("Parse succeeded: ") + result.getDouble() + ", but was expected to fail."); return TRUE; // TRUE because failure handling is in the test suite } if (tuple.output == "NaN") { if (!uprv_isNaN(result.getDouble())) { - appendErrorMessage.append("Expected NaN, but got: " + resultStr); + appendErrorMessage.append(UnicodeString("Expected NaN, but got: ") + result.getDouble()); return FALSE; } return TRUE; } else if (tuple.output == "Inf") { if (!uprv_isInfinite(result.getDouble()) || result.getDouble() < 0) { - appendErrorMessage.append("Expected Inf, but got: " + resultStr); + appendErrorMessage.append(UnicodeString("Expected Inf, but got: ") + result.getDouble()); return FALSE; } return TRUE; } else if (tuple.output == "-Inf") { if (!uprv_isInfinite(result.getDouble()) || result.getDouble() > 0) { - appendErrorMessage.append("Expected -Inf, but got: " + resultStr); + appendErrorMessage.append(UnicodeString("Expected -Inf, but got: ") + result.getDouble()); + return FALSE; + } + return TRUE; + } else if (tuple.output == "-0.0") { + if (std::signbit(result.getDouble()) == 0 || result.getDouble() != 0) { + appendErrorMessage.append(UnicodeString("Expected -0.0, but got: ") + result.getDouble()); return FALSE; } return TRUE; } - DecimalQuantity expected; - strToDigitList(tuple.output, expected, status); + // All other cases parse to a DecimalQuantity, not a double. + + DecimalQuantity expectedQuantity; + strToDigitList(tuple.output, expectedQuantity, status); + UnicodeString expectedString = expectedQuantity.toNumberString(); if (U_FAILURE(status)) { - appendErrorMessage.append("Error parsing."); - return FALSE; - } - if (expected != *result.getDecimalQuantity()) { - appendErrorMessage.append(UnicodeString("Expected: ") + tuple.output + ", but got: " + resultStr + " (" + ppos.getIndex() + ":" + ppos.getErrorIndex() + ")"); + appendErrorMessage.append("[Error parsing decnumber] "); + // If this happens, assume that tuple.output is exactly the same format as + // DecimalQuantity.toNumberString() + expectedString = tuple.output; + status = U_ZERO_ERROR; + } + UnicodeString actualString = result.getDecimalQuantity()->toNumberString(); + if (expectedString != actualString) { + appendErrorMessage.append( + UnicodeString("Expected: ") + tuple.output + " (i.e., " + expectedString + "), but got: " + + actualString + " (" + ppos.getIndex() + ":" + ppos.getErrorIndex() + ")"); return FALSE; } + return TRUE; } @@ -485,22 +502,30 @@ UBool NumberFormatTestDataDriven::isParseCurrencyPass( return FALSE; } UnicodeString currStr(currAmt->getISOCurrency()); - Formattable resultFormattable(currAmt->getNumber()); - UnicodeString resultStr(UnicodeString::fromUTF8(resultFormattable.getDecimalNumber(status))); + U_ASSERT(currAmt->getNumber().getDecimalQuantity() != nullptr); // no doubles in currency tests + UnicodeString resultStr = currAmt->getNumber().getDecimalQuantity()->toNumberString(); if (tuple.output == "fail") { appendErrorMessage.append(UnicodeString("Parse succeeded: ") + resultStr + ", but was expected to fail."); return TRUE; // TRUE because failure handling is in the test suite } - DecimalQuantity expected; - strToDigitList(tuple.output, expected, status); + + DecimalQuantity expectedQuantity; + strToDigitList(tuple.output, expectedQuantity, status); + UnicodeString expectedString = expectedQuantity.toNumberString(); if (U_FAILURE(status)) { - appendErrorMessage.append("Error parsing."); - return FALSE; - } - if (expected != *currAmt->getNumber().getDecimalQuantity()) { - appendErrorMessage.append(UnicodeString("Expected: ") + tuple.output + ", but got: " + resultStr + " (" + ppos.getIndex() + ":" + ppos.getErrorIndex() + ")"); + appendErrorMessage.append("Error parsing decnumber"); + // If this happens, assume that tuple.output is exactly the same format as + // DecimalQuantity.toNumberString() + expectedString = tuple.output; + status = U_ZERO_ERROR; + } + if (expectedString != resultStr) { + appendErrorMessage.append( + UnicodeString("Expected: ") + tuple.output + " (i.e., " + expectedString + "), but got: " + + resultStr + " (" + ppos.getIndex() + ":" + ppos.getErrorIndex() + ")"); return FALSE; } + if (currStr != tuple.outputCurrency) { appendErrorMessage.append(UnicodeString( "Expected currency: ") + tuple.outputCurrency + ", got: " + currStr + ". "); diff --git a/icu4c/source/test/testdata/numberformattestspecification.txt b/icu4c/source/test/testdata/numberformattestspecification.txt index 8aa1bb44ea..509da32c8d 100644 --- a/icu4c/source/test/testdata/numberformattestspecification.txt +++ b/icu4c/source/test/testdata/numberformattestspecification.txt @@ -548,7 +548,6 @@ set locale en begin currency currencyUsage toPattern breaks // These work in J, but it prepends an extra hash sign to the pattern. -// C does not print the currency rounding information in the pattern. // K does not support this feature. USD standard 0.00 JK CHF standard 0.00 JK @@ -1409,6 +1408,7 @@ nan 0 NaN JK test parse infinity and scientific notation overflow set locale en +set lenient 1 begin parse output breaks NaN NaN K @@ -1420,12 +1420,12 @@ NaN NaN K -1E-99999999999999 -0.0 1E2147483648 Inf K 1E2147483647 Inf K -1E2147483646 1E2147483646 C +1E2147483646 1E2147483646 1E-2147483649 0 1E-2147483648 0 -// P returns zero here -1E-2147483647 1E-2147483647 P -1E-2147483646 1E-2147483646 C +// C and P return zero here +1E-2147483647 1E-2147483647 CP +1E-2147483646 1E-2147483646 test format push limits set locale en @@ -1433,20 +1433,19 @@ set minFractionDigits 2 set roundingMode halfDown begin maxFractionDigits format output breaks -// C has trouble formatting too many digits (#11318) -100 987654321987654321 987654321987654321.00 C -100 987654321.987654321 987654321.987654321 C -100 9999999999999.9950000000001 9999999999999.9950000000001 C +100 987654321987654321 987654321987654321.00 +100 987654321.987654321 987654321.987654321 +100 9999999999999.9950000000001 9999999999999.9950000000001 2 9999999999999.9950000000001 10000000000000.00 2 9999999.99499999 9999999.99 // K doesn't support halfDown rounding mode? 2 9999999.995 9999999.99 K 2 9999999.99500001 10000000.00 -100 56565656565656565656565656565656565656565656565656565656565656 56565656565656565656565656565656565656565656565656565656565656.00 C -100 454545454545454545454545454545.454545454545454545454545454545 454545454545454545454545454545.454545454545454545454545454545 C +100 56565656565656565656565656565656565656565656565656565656565656 56565656565656565656565656565656565656565656565656565656565656.00 +100 454545454545454545454545454545.454545454545454545454545454545 454545454545454545454545454545.454545454545454545454545454545 100 0.0000000000000000000123 0.0000000000000000000123 -100 -78787878787878787878787878787878 -78787878787878787878787878787878.00 C -100 -8989898989898989898989.8989898989898989 -8989898989898989898989.8989898989898989 C +100 -78787878787878787878787878787878 -78787878787878787878787878787878.00 +100 -8989898989898989898989.8989898989898989 -8989898989898989898989.8989898989898989 test ticket 11230 set locale en 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 9bd291d9cf..ebf91c710f 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 @@ -646,14 +646,26 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { if (_scale >= 0) { // 1e22 is the largest exact double. int i = _scale; - for (; i >= 22; i -= 22) + for (; i >= 22; i -= 22) { result *= 1e22; + if (Double.isInfinite(result)) { + // Further multiplications will not be productive. + i = 0; + break; + } + } result *= DOUBLE_MULTIPLIERS[i]; } else { // 1e22 is the largest exact double. int i = _scale; - for (; i <= -22; i += 22) + for (; i <= -22; i += 22) { result /= 1e22; + if (result == 0.0) { + // Further divisions will not be productive. + i = 0; + break; + } + } result /= DOUBLE_MULTIPLIERS[-i]; } if (isNegative()) diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java index 7dd5ac1c69..9b472eed2d 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java @@ -161,6 +161,7 @@ public class ParsedNumber { d = d.negate(); } // Special case: MIN_LONG + // TODO: It is supported in quantity.toLong() if quantity had the negative flag. if (d.compareTo(BigDecimal.valueOf(Long.MIN_VALUE)) == 0 && !forceBigDecimal) { return Long.MIN_VALUE; } diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatDataDrivenTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatDataDrivenTest.java index 400ab06c4e..d8be411eb9 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatDataDrivenTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatDataDrivenTest.java @@ -11,11 +11,11 @@ import org.junit.Test; import com.ibm.icu.dev.test.TestUtil; import com.ibm.icu.dev.text.DecimalFormat_ICU58; import com.ibm.icu.impl.number.DecimalFormatProperties; +import com.ibm.icu.impl.number.DecimalFormatProperties.ParseMode; import com.ibm.icu.impl.number.Padder.PadPosition; import com.ibm.icu.impl.number.PatternStringParser; import com.ibm.icu.impl.number.PatternStringUtils; import com.ibm.icu.impl.number.parse.NumberParserImpl; -import com.ibm.icu.impl.number.parse.NumberParserImpl.ParseMode; import com.ibm.icu.number.LocalizedNumberFormatter; import com.ibm.icu.number.NumberFormatter; import com.ibm.icu.text.DecimalFormat; From 02669ad1bc7efe4564ac58e92ebccb693110ccdf Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Fri, 30 Mar 2018 07:22:24 +0000 Subject: [PATCH 071/129] ICU-13634 Fixes for NumberFormatTest/TestExponential. X-SVN-Rev: 41177 --- icu4c/source/i18n/fmtable.cpp | 15 ++++++--------- icu4c/source/i18n/number_decimalquantity.cpp | 4 ++++ icu4c/source/i18n/number_decimalquantity.h | 3 +++ icu4c/source/i18n/numparse_parsednumber.cpp | 2 +- .../test/intltest/numbertest_decimalquantity.cpp | 4 +++- icu4c/source/test/intltest/numfmtst.cpp | 7 ++++--- 6 files changed, 21 insertions(+), 14 deletions(-) diff --git a/icu4c/source/i18n/fmtable.cpp b/icu4c/source/i18n/fmtable.cpp index 862b5f912e..e4c119aea6 100644 --- a/icu4c/source/i18n/fmtable.cpp +++ b/icu4c/source/i18n/fmtable.cpp @@ -768,17 +768,14 @@ Formattable::adoptDecimalQuantity(DecimalQuantity *dq) { } // Set the value into the Union of simple type values. - // Cannot use the set() functions because they would delete the fDecimalNum value, - // TODO: fDecimalQuantity->fitsInInt() to kLong type. - /* - if (fDecimalQuantity->fitsInInt()) { - fType = kLong; - fValue.fInt64 = fDecimalNum->getLong(); - } else - */ + // Cannot use the set() functions because they would delete the fDecimalNum value. if (fDecimalQuantity->fitsInLong()) { - fType = kInt64; fValue.fInt64 = fDecimalQuantity->toLong(); + if (fValue.fInt64 <= INT32_MAX && fValue.fInt64 >= INT32_MIN) { + fType = kLong; + } else { + fType = kInt64; + } } else { fType = kDouble; fValue.fDouble = fDecimalQuantity->toDouble(); diff --git a/icu4c/source/i18n/number_decimalquantity.cpp b/icu4c/source/i18n/number_decimalquantity.cpp index 1d712b5ce4..a63044f981 100644 --- a/icu4c/source/i18n/number_decimalquantity.cpp +++ b/icu4c/source/i18n/number_decimalquantity.cpp @@ -202,6 +202,10 @@ void DecimalQuantity::multiplyBy(int32_t multiplicand) { setToDouble(temp); } +void DecimalQuantity::negate() { + flags ^= NEGATIVE_FLAG; +} + int32_t DecimalQuantity::getMagnitude() const { U_ASSERT(precision != 0); return scale + precision - 1; diff --git a/icu4c/source/i18n/number_decimalquantity.h b/icu4c/source/i18n/number_decimalquantity.h index 10f2e669b8..74c85248c4 100644 --- a/icu4c/source/i18n/number_decimalquantity.h +++ b/icu4c/source/i18n/number_decimalquantity.h @@ -95,6 +95,9 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { */ void multiplyBy(int32_t multiplicand); + /** Flips the sign from positive to negative and back. C++-only: not currently needed in Java. */ + void negate(); + /** * Scales the number by a power of ten. For example, if the value is currently "1234.56", calling * this method with delta=-3 will change the value to "1.23456". diff --git a/icu4c/source/i18n/numparse_parsednumber.cpp b/icu4c/source/i18n/numparse_parsednumber.cpp index b9cc54e627..16da923459 100644 --- a/icu4c/source/i18n/numparse_parsednumber.cpp +++ b/icu4c/source/i18n/numparse_parsednumber.cpp @@ -108,7 +108,7 @@ void ParsedNumber::populateFormattable(Formattable& output) const { // All other numbers LocalPointer actualQuantity(new DecimalQuantity(quantity)); if (0 != (flags & FLAG_NEGATIVE)) { - actualQuantity->multiplyBy(-1); + actualQuantity->negate(); } output.adoptDecimalQuantity(actualQuantity.orphan()); } diff --git a/icu4c/source/test/intltest/numbertest_decimalquantity.cpp b/icu4c/source/test/intltest/numbertest_decimalquantity.cpp index b260614dff..2b31380879 100644 --- a/icu4c/source/test/intltest/numbertest_decimalquantity.cpp +++ b/icu4c/source/test/intltest/numbertest_decimalquantity.cpp @@ -296,6 +296,7 @@ void DecimalQuantityTest::testHardDoubleConversion() { } void DecimalQuantityTest::testToDouble() { + IcuTestErrorCode status(*this, "testToDouble"); static const struct TestCase { const char* input; // char* for the decNumber constructor double expected; @@ -304,8 +305,9 @@ void DecimalQuantityTest::testToDouble() { { "-3.142E-271", -3.142e-271 } }; for (auto& cas : cases) { + status.setScope(cas.input); DecimalQuantity q; - q.setToDecNumber({cas.input, -1}); + q.setToDecNumber({cas.input, -1}, status); double actual = q.toDouble(); assertEquals("Doubles should exactly equal", cas.expected, actual); } diff --git a/icu4c/source/test/intltest/numfmtst.cpp b/icu4c/source/test/intltest/numfmtst.cpp index ee638ea36a..d042ccd7c8 100644 --- a/icu4c/source/test/intltest/numfmtst.cpp +++ b/icu4c/source/test/intltest/numfmtst.cpp @@ -927,7 +927,7 @@ NumberFormatTest::TestExponential(void) #endif } else { - errln((UnicodeString)"FAIL: Non-numeric Formattable returned"); + errln(UnicodeString("FAIL: Non-numeric Formattable returned: ") + pattern + " " + s); continue; } if (pos.getIndex() == s.length()) @@ -938,7 +938,8 @@ NumberFormatTest::TestExponential(void) (uprv_fabs(a - valParse[v+ival]) / a > (2*DBL_EPSILON))) || (!useEpsilon && a != valParse[v+ival])) { - errln((UnicodeString)"FAIL: Expected " + valParse[v+ival]); + errln((UnicodeString)"FAIL: Expected " + valParse[v+ival] + " but got " + a + + " on input " + s); } } else { @@ -965,7 +966,7 @@ NumberFormatTest::TestExponential(void) { logln((UnicodeString)" -parse-> " + a); if (a != lvalParse[v+ilval]) - errln((UnicodeString)"FAIL: Expected " + lvalParse[v+ilval]); + errln((UnicodeString)"FAIL: Expected " + lvalParse[v+ilval] + " but got " + a); } else errln((UnicodeString)"FAIL: Partial parse (" + pos.getIndex() + " chars) -> " + a); From 8d8f846755dc9dccdf5f2d8ab115d53137d56c0e Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Fri, 30 Mar 2018 08:21:06 +0000 Subject: [PATCH 072/129] ICU-13634 Fixing significant digit display on zero when minInt is zero. X-SVN-Rev: 41178 --- icu4c/source/i18n/number_decimalquantity.cpp | 6 ++++++ icu4c/source/i18n/number_rounding.cpp | 4 ++++ icu4c/source/test/intltest/numbertest_api.cpp | 18 ++++++++++++++++++ .../number/DecimalQuantity_AbstractBCD.java | 6 ++++++ .../core/src/com/ibm/icu/number/Rounder.java | 4 ++++ .../test/number/NumberFormatterApiTest.java | 16 ++++++++++++++++ 6 files changed, 54 insertions(+) diff --git a/icu4c/source/i18n/number_decimalquantity.cpp b/icu4c/source/i18n/number_decimalquantity.cpp index a63044f981..bb731938f1 100644 --- a/icu4c/source/i18n/number_decimalquantity.cpp +++ b/icu4c/source/i18n/number_decimalquantity.cpp @@ -149,6 +149,12 @@ void DecimalQuantity::setIntegerLength(int32_t minInt, int32_t maxInt) { U_ASSERT(minInt >= 0); U_ASSERT(maxInt >= minInt); + // Special behavior: do not set minInt to be less than what is already set. + // This is so significant digits rounding can set the integer length. + if (minInt < lReqPos) { + minInt = lReqPos; + } + // Save values into internal state // Negation is safe for minFrac/maxFrac because -Integer.MAX_VALUE > Integer.MIN_VALUE lOptPos = maxInt; diff --git a/icu4c/source/i18n/number_rounding.cpp b/icu4c/source/i18n/number_rounding.cpp index 80120261e1..b5e37194f8 100644 --- a/icu4c/source/i18n/number_rounding.cpp +++ b/icu4c/source/i18n/number_rounding.cpp @@ -342,6 +342,10 @@ void Rounder::apply(impl::DecimalQuantity &value, UErrorCode& status) const { value.setFractionLength( uprv_max(0, -getDisplayMagnitudeSignificant(value, fUnion.fracSig.fMinSig)), INT32_MAX); + // Make sure that digits are displayed on zero. + if (value.isZero() && fUnion.fracSig.fMinSig > 0) { + value.setIntegerLength(1, INT32_MAX); + } break; case RND_FRACTION_SIGNIFICANT: { diff --git a/icu4c/source/test/intltest/numbertest_api.cpp b/icu4c/source/test/intltest/numbertest_api.cpp index 9fe790cd4a..a6d1a3553c 100644 --- a/icu4c/source/test/intltest/numbertest_api.cpp +++ b/icu4c/source/test/intltest/numbertest_api.cpp @@ -910,6 +910,24 @@ void NumberFormatterApiTest::roundingFigures() { Locale::getEnglish(), 9.99999, u"10.0"); + + assertFormatSingle( + u"Fixed Significant on zero with lots of integer width", + u"@ integer-width/+000", + NumberFormatter::with().rounding(Rounder::fixedDigits(1)) + .integerWidth(IntegerWidth::zeroFillTo(3)), + Locale::getEnglish(), + 0, + "000"); + + assertFormatSingle( + u"Fixed Significant on zero with zero integer width", + u"@ integer-width/+", + NumberFormatter::with().rounding(Rounder::fixedDigits(1)) + .integerWidth(IntegerWidth::zeroFillTo(0)), + Locale::getEnglish(), + 0, + "0"); } void NumberFormatterApiTest::roundingFractionFigures() { 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 b5afb37e2e..d513ef3c16 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 @@ -142,6 +142,12 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { assert minInt >= 0; assert maxInt >= minInt; + // Special behavior: do not set minInt to be less than what is already set. + // This is so significant digits rounding can set the integer length. + if (minInt < lReqPos) { + minInt = lReqPos; + } + // Save values into internal state // Negation is safe for minFrac/maxFrac because -Integer.MAX_VALUE > Integer.MIN_VALUE lOptPos = maxInt; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/Rounder.java b/icu4j/main/classes/core/src/com/ibm/icu/number/Rounder.java index 9cec17b8af..47b6df7084 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/Rounder.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/Rounder.java @@ -609,6 +609,10 @@ public abstract class Rounder implements Cloneable { value.roundToMagnitude(getRoundingMagnitudeSignificant(value, maxSig), mathContext); value.setFractionLength(Math.max(0, -getDisplayMagnitudeSignificant(value, minSig)), Integer.MAX_VALUE); + // Make sure that digits are displayed on zero. + if (value.isZero() && minSig > 0) { + value.setIntegerLength(1, Integer.MAX_VALUE); + } } /** 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 fad64604d9..b9ca3976f3 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 @@ -894,6 +894,22 @@ public class NumberFormatterApiTest { ULocale.ENGLISH, 9.99999, "10.0"); + + assertFormatSingle( + "Fixed Significant on zero with zero integer width", + "@ integer-width/+", + NumberFormatter.with().rounding(Rounder.fixedDigits(1)).integerWidth(IntegerWidth.zeroFillTo(0)), + ULocale.ENGLISH, + 0, + "0"); + + assertFormatSingle( + "Fixed Significant on zero with lots of integer width", + "@ integer-width/+000", + NumberFormatter.with().rounding(Rounder.fixedDigits(1)).integerWidth(IntegerWidth.zeroFillTo(3)), + ULocale.ENGLISH, + 0, + "000"); } @Test From db9c74b3f4843a113db5e0b8375b298afdc45de5 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Fri, 30 Mar 2018 10:37:24 +0000 Subject: [PATCH 073/129] ICU-13634 More assorted number formatting/parsing fixes. X-SVN-Rev: 41179 --- icu4c/source/i18n/decimfmt.cpp | 2 +- .../test/intltest/numbertest_skeletons.cpp | 2 +- icu4c/source/test/intltest/numfmtst.cpp | 31 ++++++++++++------- .../dev/test/number/NumberSkeletonTest.java | 2 +- 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp index 5fd757daee..b15b962bf8 100644 --- a/icu4c/source/i18n/decimfmt.cpp +++ b/icu4c/source/i18n/decimfmt.cpp @@ -337,7 +337,7 @@ DecimalFormat::DecimalFormat(const UnicodeString& pattern, const DecimalFormatSy } DecimalFormat::DecimalFormat(const DecimalFormat& source) { - fProperties.adoptInstead(new DecimalFormatProperties()); + fProperties.adoptInstead(new DecimalFormatProperties(*source.fProperties)); fExportedProperties.adoptInstead(new DecimalFormatProperties()); fWarehouse.adoptInstead(new DecimalFormatWarehouse()); fSymbols.adoptInstead(new DecimalFormatSymbols(*source.fSymbols)); diff --git a/icu4c/source/test/intltest/numbertest_skeletons.cpp b/icu4c/source/test/intltest/numbertest_skeletons.cpp index 80196f9fc8..29d09f1a26 100644 --- a/icu4c/source/test/intltest/numbertest_skeletons.cpp +++ b/icu4c/source/test/intltest/numbertest_skeletons.cpp @@ -121,6 +121,7 @@ void NumberSkeletonTest::invalidTokens() { u".00/@@#", u".00/@@#+", u".00/floor/@@+", // wrong order + u"round-increment/français", // non-invariant characters for C++ u"round-currency-cash/XXX", u"scientific/ee", u"round-increment/xxx", @@ -143,7 +144,6 @@ void NumberSkeletonTest::unknownTokens() { u"français", u"measure-unit/français-français", // non-invariant characters for C++ u"numbering-system/français", // non-invariant characters for C++ - u"round-increment/français", // non-invariant characters for C++ u"currency-USD"}; expectedErrorSkeleton(cases, sizeof(cases) / sizeof(*cases)); diff --git a/icu4c/source/test/intltest/numfmtst.cpp b/icu4c/source/test/intltest/numfmtst.cpp index d042ccd7c8..e481acca22 100644 --- a/icu4c/source/test/intltest/numfmtst.cpp +++ b/icu4c/source/test/intltest/numfmtst.cpp @@ -753,7 +753,7 @@ NumberFormatTest::TestPatterns(void) const char* pat[] = { "#.#", "#.", ".#", "#" }; int32_t pat_length = UPRV_LENGTHOF(pat); - const char* newpat[] = { "#0.#", "#0.", "#.0", "#" }; + const char* newpat[] = { "0.#", "0.", "#.0", "0" }; const char* num[] = { "0", "0.", ".0", "0" }; for (int32_t i=0; i Date: Sat, 31 Mar 2018 03:10:44 +0000 Subject: [PATCH 074/129] ICU-13634 Fixing resolution of negative and percent signs in parsing; adding custom sign support to ScientificMatcher; and other minor fixes. X-SVN-Rev: 41180 --- icu4c/source/i18n/decimfmt.cpp | 8 +++ icu4c/source/i18n/number_decimalquantity.cpp | 15 ++++-- icu4c/source/i18n/number_decimalquantity.h | 2 +- icu4c/source/i18n/numparse_affixes.cpp | 8 ++- icu4c/source/i18n/numparse_impl.cpp | 1 + icu4c/source/i18n/numparse_parsednumber.cpp | 45 ++++++++-------- icu4c/source/i18n/numparse_scientific.cpp | 54 +++++++++++++++++-- icu4c/source/i18n/numparse_scientific.h | 2 + icu4c/source/i18n/numparse_symbols.cpp | 14 ----- icu4c/source/i18n/numparse_symbols.h | 4 -- icu4c/source/i18n/numparse_types.h | 3 ++ icu4c/source/test/intltest/numfmtst.cpp | 12 ++--- .../numberformattestspecification.txt | 9 ++++ .../ibm/icu/impl/number/DecimalQuantity.java | 3 ++ .../number/DecimalQuantity_AbstractBCD.java | 16 ++++-- .../DecimalQuantity_DualStorageBCD.java | 3 ++ .../icu/impl/number/parse/AffixMatcher.java | 8 ++- .../impl/number/parse/NumberParserImpl.java | 1 + .../icu/impl/number/parse/ParsedNumber.java | 39 +++++++------- .../icu/impl/number/parse/PercentMatcher.java | 8 --- .../impl/number/parse/PermilleMatcher.java | 8 --- .../impl/number/parse/ScientificMatcher.java | 43 +++++++++++++-- .../number/DecimalQuantity_SimpleStorage.java | 5 ++ .../icu/dev/test/format/NumberFormatTest.java | 19 +++++++ 24 files changed, 228 insertions(+), 102 deletions(-) diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp index b15b962bf8..569a2a8a7b 100644 --- a/icu4c/source/i18n/decimfmt.cpp +++ b/icu4c/source/i18n/decimfmt.cpp @@ -298,6 +298,7 @@ int32_t DecimalFormat::getAttribute(UNumberFormatAttribute attr, UErrorCode& sta } void DecimalFormat::setGroupingUsed(UBool enabled) { + NumberFormat::setGroupingUsed(enabled); // to set field for compatibility if (enabled) { // Set to a reasonable default value fProperties->groupingSize = 3; @@ -649,6 +650,7 @@ ERoundingMode DecimalFormat::getRoundingMode(void) const { } void DecimalFormat::setRoundingMode(ERoundingMode roundingMode) { + NumberFormat::setMaximumIntegerDigits(roundingMode); // to set field for compatibility fProperties->roundingMode = static_cast(roundingMode); refreshFormatterNoError(); } @@ -985,6 +987,12 @@ void DecimalFormat::refreshFormatter(UErrorCode& status) { fParserWithCurrency.adoptInsteadAndCheckErrorCode( NumberParserImpl::createParserFromProperties( *fProperties, *fSymbols, true, status), status); + + // In order for the getters to work, we need to populate some fields in NumberFormat. + NumberFormat::setMaximumIntegerDigits(fExportedProperties->maximumIntegerDigits); + NumberFormat::setMinimumIntegerDigits(fExportedProperties->minimumIntegerDigits); + NumberFormat::setMaximumFractionDigits(fExportedProperties->maximumFractionDigits); + NumberFormat::setMinimumFractionDigits(fExportedProperties->minimumFractionDigits); } void DecimalFormat::refreshFormatterNoError() { diff --git a/icu4c/source/i18n/number_decimalquantity.cpp b/icu4c/source/i18n/number_decimalquantity.cpp index bb731938f1..40ad848ff4 100644 --- a/icu4c/source/i18n/number_decimalquantity.cpp +++ b/icu4c/source/i18n/number_decimalquantity.cpp @@ -485,7 +485,9 @@ int64_t DecimalQuantity::toLong() const { for (int32_t magnitude = scale + precision - 1; magnitude >= 0; magnitude--) { result = result * 10 + getDigitPos(magnitude - scale); } - if (isNegative()) { result = -result; } + if (isNegative()) { + result = -result; + } return result; } @@ -544,7 +546,9 @@ double DecimalQuantity::toDouble() const { UnicodeString numberString = toNumberString(); int32_t count; double result = converter.StringToDouble(reinterpret_cast(numberString.getBuffer()), numberString.length(), &count); - if (isNegative()) { result = -result; } + if (isNegative()) { + result = -result; + } return result; } @@ -560,7 +564,9 @@ double DecimalQuantity::toDoubleFromOriginal() const { for (; delta <= -22; delta += 22) result /= 1e22; result /= DOUBLE_MULTIPLIERS[-delta]; } - if (isNegative()) { result *= -1; } + if (isNegative()) { + result = -result; + } return result; } @@ -1080,6 +1086,9 @@ UnicodeString DecimalQuantity::toString() const { UnicodeString DecimalQuantity::toNumberString() const { UnicodeString result; + if (precision == 0) { + result.append(u'0'); + } for (int32_t i = 0; i < precision; i++) { result.append(u'0' + getDigitPos(precision - i - 1)); } diff --git a/icu4c/source/i18n/number_decimalquantity.h b/icu4c/source/i18n/number_decimalquantity.h index 74c85248c4..b205778e19 100644 --- a/icu4c/source/i18n/number_decimalquantity.h +++ b/icu4c/source/i18n/number_decimalquantity.h @@ -95,7 +95,7 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { */ void multiplyBy(int32_t multiplicand); - /** Flips the sign from positive to negative and back. C++-only: not currently needed in Java. */ + /** Flips the sign from positive to negative and back. */ void negate(); /** diff --git a/icu4c/source/i18n/numparse_affixes.cpp b/icu4c/source/i18n/numparse_affixes.cpp index 159d70dcda..83ecdd144b 100644 --- a/icu4c/source/i18n/numparse_affixes.cpp +++ b/icu4c/source/i18n/numparse_affixes.cpp @@ -340,7 +340,7 @@ void AffixMatcherWarehouse::createAffixMatchers(const AffixPatternProvider& patt continue; } - // Flags for setting in the ParsedNumber + // Flags for setting in the ParsedNumber; the token matchers may add more. int flags = (signum == -1) ? FLAG_NEGATIVE : 0; // Note: it is indeed possible for posPrefix and posSuffix to both be null. @@ -438,6 +438,12 @@ void AffixMatcher::postProcess(ParsedNumber& result) const { result.suffix = UnicodeString(); } result.flags |= fFlags; + if (fPrefix != nullptr) { + fPrefix->postProcess(result); + } + if (fSuffix != nullptr) { + fSuffix->postProcess(result); + } } } diff --git a/icu4c/source/i18n/numparse_impl.cpp b/icu4c/source/i18n/numparse_impl.cpp index c528d8efa8..36ddc1f2f1 100644 --- a/icu4c/source/i18n/numparse_impl.cpp +++ b/icu4c/source/i18n/numparse_impl.cpp @@ -233,6 +233,7 @@ void NumberParserImpl::parse(const UnicodeString& input, int32_t start, bool gre for (int32_t i = 0; i < fNumMatchers; i++) { fMatchers[i]->postProcess(result); } + result.postProcess(); } void NumberParserImpl::parseGreedyRecursive(StringSegment& segment, ParsedNumber& result, diff --git a/icu4c/source/i18n/numparse_parsednumber.cpp b/icu4c/source/i18n/numparse_parsednumber.cpp index 16da923459..c658fc27d3 100644 --- a/icu4c/source/i18n/numparse_parsednumber.cpp +++ b/icu4c/source/i18n/numparse_parsednumber.cpp @@ -37,6 +37,18 @@ void ParsedNumber::setCharsConsumed(const StringSegment& segment) { charEnd = segment.getOffset(); } +void ParsedNumber::postProcess() { + if (!quantity.bogus && 0 != (flags & FLAG_NEGATIVE)) { + quantity.negate(); + } + if (!quantity.bogus && 0 != (flags & FLAG_PERCENT)) { + quantity.adjustMagnitude(-2); + } + if (!quantity.bogus && 0 != (flags & FLAG_PERMILLE)) { + quantity.adjustMagnitude(-3); + } +} + bool ParsedNumber::success() const { return charEnd > 0 && 0 == (flags & FLAG_FAIL); } @@ -46,7 +58,6 @@ bool ParsedNumber::seenNumber() const { } double ParsedNumber::getDouble() const { - bool sawNegative = 0 != (flags & FLAG_NEGATIVE); bool sawNaN = 0 != (flags & FLAG_NAN); bool sawInfinity = 0 != (flags & FLAG_INFINITY); @@ -55,34 +66,25 @@ double ParsedNumber::getDouble() const { return NAN; } if (sawInfinity) { - if (sawNegative) { + if (0 != (flags & FLAG_NEGATIVE)) { return -INFINITY; } else { return INFINITY; } } - if (quantity.isZero() && sawNegative) { + U_ASSERT(!quantity.bogus); + if (quantity.isZero() && quantity.isNegative()) { return -0.0; } if (quantity.fitsInLong()) { - long l = quantity.toLong(); - if (0 != (flags & FLAG_NEGATIVE)) { - l *= -1; - } - return l; + return quantity.toLong(); + } else { + return quantity.toDouble(); } - - // TODO: MIN_LONG. It is supported in quantity.toLong() if quantity had the negative flag. - double d = quantity.toDouble(); - if (0 != (flags & FLAG_NEGATIVE)) { - d *= -1; - } - return d; } void ParsedNumber::populateFormattable(Formattable& output) const { - bool sawNegative = 0 != (flags & FLAG_NEGATIVE); bool sawNaN = 0 != (flags & FLAG_NAN); bool sawInfinity = 0 != (flags & FLAG_INFINITY); @@ -92,7 +94,7 @@ void ParsedNumber::populateFormattable(Formattable& output) const { return; } if (sawInfinity) { - if (sawNegative) { + if (0 != (flags & FLAG_NEGATIVE)) { output.setDouble(-INFINITY); return; } else { @@ -100,17 +102,14 @@ void ParsedNumber::populateFormattable(Formattable& output) const { return; } } - if (quantity.isZero() && sawNegative) { + U_ASSERT(!quantity.bogus); + if (quantity.isZero() && quantity.isNegative()) { output.setDouble(-0.0); return; } // All other numbers - LocalPointer actualQuantity(new DecimalQuantity(quantity)); - if (0 != (flags & FLAG_NEGATIVE)) { - actualQuantity->negate(); - } - output.adoptDecimalQuantity(actualQuantity.orphan()); + output.adoptDecimalQuantity(new DecimalQuantity(quantity)); } bool ParsedNumber::isBetterThan(const ParsedNumber& other) { diff --git a/icu4c/source/i18n/numparse_scientific.cpp b/icu4c/source/i18n/numparse_scientific.cpp index 7e2dc52c94..849eab6837 100644 --- a/icu4c/source/i18n/numparse_scientific.cpp +++ b/icu4c/source/i18n/numparse_scientific.cpp @@ -18,9 +18,36 @@ using namespace icu::numparse; using namespace icu::numparse::impl; +namespace { + +inline const UnicodeSet& minusSignSet() { + return *unisets::get(unisets::MINUS_SIGN); +} + +inline const UnicodeSet& plusSignSet() { + return *unisets::get(unisets::PLUS_SIGN); +} + +} // namespace + + ScientificMatcher::ScientificMatcher(const DecimalFormatSymbols& dfs, const Grouper& grouper) : fExponentSeparatorString(dfs.getConstSymbol(DecimalFormatSymbols::kExponentialSymbol)), - fExponentMatcher(dfs, grouper, PARSE_FLAG_INTEGER_ONLY) { + fExponentMatcher(dfs, grouper, PARSE_FLAG_INTEGER_ONLY | PARSE_FLAG_GROUPING_DISABLED) { + + const UnicodeString& minusSign = dfs.getConstSymbol(DecimalFormatSymbols::kMinusSignSymbol); + if (minusSignSet().contains(minusSign)) { + fCustomMinusSign.setToBogus(); + } else { + fCustomMinusSign = minusSign; + } + + const UnicodeString& plusSign = dfs.getConstSymbol(DecimalFormatSymbols::kPlusSignSymbol); + if (plusSignSet().contains(plusSign)) { + fCustomPlusSign.setToBogus(); + } else { + fCustomPlusSign = plusSign; + } } bool ScientificMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const { @@ -37,18 +64,35 @@ bool ScientificMatcher::match(StringSegment& segment, ParsedNumber& result, UErr // Full exponent separator match. // First attempt to get a code point, returning true if we can't get one. - segment.adjustOffset(overlap1); - if (segment.length() == 0) { + if (segment.length() == overlap1) { return true; } + segment.adjustOffset(overlap1); // Allow a sign, and then try to match digits. int8_t exponentSign = 1; - if (segment.startsWith(*unisets::get(unisets::MINUS_SIGN))) { + if (segment.startsWith(minusSignSet())) { exponentSign = -1; segment.adjustOffsetByCodePoint(); - } else if (segment.startsWith(*unisets::get(unisets::PLUS_SIGN))) { + } else if (segment.startsWith(plusSignSet())) { segment.adjustOffsetByCodePoint(); + } else if (segment.startsWith(fCustomMinusSign)) { + int32_t overlap2 = segment.getCommonPrefixLength(fCustomMinusSign); + if (overlap2 != fCustomMinusSign.length()) { + // Partial custom sign match; un-match the exponent separator. + segment.adjustOffset(-overlap1); + return true; + } + exponentSign = -1; + segment.adjustOffset(overlap2); + } else if (segment.startsWith(fCustomPlusSign)) { + int32_t overlap2 = segment.getCommonPrefixLength(fCustomPlusSign); + if (overlap2 != fCustomPlusSign.length()) { + // Partial custom sign match; un-match the exponent separator. + segment.adjustOffset(-overlap1); + return true; + } + segment.adjustOffset(overlap2); } int digitsOffset = segment.getOffset(); diff --git a/icu4c/source/i18n/numparse_scientific.h b/icu4c/source/i18n/numparse_scientific.h index 4084fda1a7..0581b7e5ed 100644 --- a/icu4c/source/i18n/numparse_scientific.h +++ b/icu4c/source/i18n/numparse_scientific.h @@ -32,6 +32,8 @@ class ScientificMatcher : public NumberParseMatcher, public UMemory { private: UnicodeString fExponentSeparatorString; DecimalMatcher fExponentMatcher; + UnicodeString fCustomMinusSign; + UnicodeString fCustomPlusSign; }; diff --git a/icu4c/source/i18n/numparse_symbols.cpp b/icu4c/source/i18n/numparse_symbols.cpp index 51922752b6..d66e3e704b 100644 --- a/icu4c/source/i18n/numparse_symbols.cpp +++ b/icu4c/source/i18n/numparse_symbols.cpp @@ -152,13 +152,6 @@ PercentMatcher::PercentMatcher(const DecimalFormatSymbols& dfs) : SymbolMatcher(dfs.getConstSymbol(DecimalFormatSymbols::kPercentSymbol), unisets::PERCENT_SIGN) { } -void PercentMatcher::postProcess(ParsedNumber& result) const { - SymbolMatcher::postProcess(result); - if (0 != (result.flags & FLAG_PERCENT) && !result.quantity.bogus) { - result.quantity.adjustMagnitude(-2); - } -} - bool PercentMatcher::isDisabled(const ParsedNumber& result) const { return 0 != (result.flags & FLAG_PERCENT); } @@ -173,13 +166,6 @@ PermilleMatcher::PermilleMatcher(const DecimalFormatSymbols& dfs) : SymbolMatcher(dfs.getConstSymbol(DecimalFormatSymbols::kPerMillSymbol), unisets::PERMILLE_SIGN) { } -void PermilleMatcher::postProcess(ParsedNumber& result) const { - SymbolMatcher::postProcess(result); - if (0 != (result.flags & FLAG_PERMILLE) && !result.quantity.bogus) { - result.quantity.adjustMagnitude(-3); - } -} - bool PermilleMatcher::isDisabled(const ParsedNumber& result) const { return 0 != (result.flags & FLAG_PERMILLE); } diff --git a/icu4c/source/i18n/numparse_symbols.h b/icu4c/source/i18n/numparse_symbols.h index 7264734fc0..6f0edc7107 100644 --- a/icu4c/source/i18n/numparse_symbols.h +++ b/icu4c/source/i18n/numparse_symbols.h @@ -124,8 +124,6 @@ class PercentMatcher : public SymbolMatcher { PercentMatcher(const DecimalFormatSymbols& dfs); - void postProcess(ParsedNumber& result) const override; - protected: bool isDisabled(const ParsedNumber& result) const override; @@ -139,8 +137,6 @@ class PermilleMatcher : public SymbolMatcher { PermilleMatcher(const DecimalFormatSymbols& dfs); - void postProcess(ParsedNumber& result) const override; - protected: bool isDisabled(const ParsedNumber& result) const override; diff --git a/icu4c/source/i18n/numparse_types.h b/icu4c/source/i18n/numparse_types.h index 420a5f88c5..4e4456538b 100644 --- a/icu4c/source/i18n/numparse_types.h +++ b/icu4c/source/i18n/numparse_types.h @@ -146,6 +146,9 @@ class ParsedNumber { */ void setCharsConsumed(const StringSegment& segment); + /** Apply certain number-related flags to the DecimalQuantity. */ + void postProcess(); + /** * Returns whether this the parse was successful. To be successful, at least one char must have been * consumed, and the failure flag must not be set. diff --git a/icu4c/source/test/intltest/numfmtst.cpp b/icu4c/source/test/intltest/numfmtst.cpp index e481acca22..c1c194cfb1 100644 --- a/icu4c/source/test/intltest/numfmtst.cpp +++ b/icu4c/source/test/intltest/numfmtst.cpp @@ -1688,13 +1688,13 @@ void NumberFormatTest::TestSecondaryGrouping(void) { CHECK(status, "DecimalFormat ct"); expect2(f, (int32_t)123456789L, "12,34,56,789"); - expectPat(f, "#,##,###"); + expectPat(f, "#,##,##0"); f.applyPattern("#,###", status); CHECK(status, "applyPattern"); f.setSecondaryGroupingSize(4); expect2(f, (int32_t)123456789L, "12,3456,789"); - expectPat(f, "#,####,###"); + expectPat(f, "#,####,##0"); NumberFormat *g = NumberFormat::createInstance(Locale("hi", "IN"), status); CHECK_DATA(status, "createInstance(hi_IN)"); @@ -1816,7 +1816,7 @@ void NumberFormatTest::TestScientific(void) { int32_t PAT_length = UPRV_LENGTHOF(PAT); int32_t DIGITS[] = { // min int, max int, min frac, max frac - 0, 1, 0, 0, // "#E0" + 1, 1, 0, 0, // "#E0" 1, 1, 0, 4, // "0.####E0" 2, 2, 3, 3, // "00.000E00" 1, 3, 0, 4, // "##0.####E000" @@ -2159,7 +2159,7 @@ void NumberFormatTest::TestPatterns2(void) { fmt.setFormatWidth(16); // 12 34567890123456 - expectPat(fmt, "AA*^#,###,##0.00ZZ"); + expectPat(fmt, "AA*^#####,##0.00ZZ"); } void NumberFormatTest::TestSurrogateSupport(void) { @@ -2223,9 +2223,9 @@ void NumberFormatTest::TestSurrogateSupport(void) { int32_t(-20), expStr, status); custom.setSymbol(DecimalFormatSymbols::kPercentSymbol, "percent"); - patternStr = "'You''ve lost ' -0.00 %' of your money today'"; + patternStr = "'You''ve lost ' 0.00 %' of your money today'"; patternStr = patternStr.unescape(); - expStr = UnicodeString(" minus You've lost minus 2000decimal00 percent of your money today", ""); + expStr = UnicodeString(" minus You've lost 2000decimal00 percent of your money today", ""); status = U_ZERO_ERROR; expect2(new DecimalFormat(patternStr, custom, status), int32_t(-20), expStr, status); diff --git a/icu4c/source/test/testdata/numberformattestspecification.txt b/icu4c/source/test/testdata/numberformattestspecification.txt index 509da32c8d..4759745b21 100644 --- a/icu4c/source/test/testdata/numberformattestspecification.txt +++ b/icu4c/source/test/testdata/numberformattestspecification.txt @@ -1601,6 +1601,15 @@ lenient parse output breaks 0 0 fail JK 0 +0 0 JK +test parse with scientific-separator-affix overlap +set locale en +begin +pattern lenient parse output breaks +0E0','x 1 5E3,x 5000 +0E0','x 0 5E3,x 5000 +0E0'.'x 1 5E3.x 5000 +0E0'.'x 0 5E3.x 5000 + 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 1a5d6f73ad..ae2d469099 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 @@ -86,6 +86,9 @@ public interface DecimalQuantity extends PluralRules.IFixedDecimal { */ public void multiplyBy(BigDecimal multiplicand); + /** Flips the sign from positive to negative and back. */ + void negate(); + /** * Scales the number by a power of ten. For example, if the value is currently "1234.56", calling * this method with delta=-3 will change the value to "1.23456". 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 d513ef3c16..1366f0a411 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 @@ -199,6 +199,11 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { setToBigDecimal(temp); } + @Override + public void negate() { + flags ^= NEGATIVE_FLAG; + } + @Override public int getMagnitude() throws ArithmeticException { if (precision == 0) { @@ -573,6 +578,9 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { for (int magnitude = scale + precision - 1; magnitude >= 0; magnitude--) { result = result * 10 + getDigitPos(magnitude - scale); } + if (isNegative()) { + result = -result; + } return result; } @@ -676,8 +684,9 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { } result /= DOUBLE_MULTIPLIERS[-i]; } - if (isNegative()) + if (isNegative()) { result = -result; + } return result; } @@ -704,8 +713,9 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { result /= 1e22; result /= DOUBLE_MULTIPLIERS[-delta]; } - if (isNegative()) - result *= -1; + if (isNegative()) { + result = -result; + } return result; } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java index 67fb1e3066..72a923a216 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java @@ -428,6 +428,9 @@ public final class DecimalQuantity_DualStorageBCD extends DecimalQuantity_Abstra public String toNumberString() { StringBuilder sb = new StringBuilder(); if (usingBytes) { + if (precision == 0) { + sb.append('0'); + } for (int i = precision - 1; i >= 0; i--) { sb.append(bcdBytes[i]); } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixMatcher.java index 6f51158140..04bbadbc22 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixMatcher.java @@ -129,7 +129,7 @@ public class AffixMatcher implements NumberParseMatcher { continue; } - // Flags for setting in the ParsedNumber + // Flags for setting in the ParsedNumber; the token matchers may add more. int flags = (signum == -1) ? ParsedNumber.FLAG_NEGATIVE : 0; // Note: it is indeed possible for posPrefix and posSuffix to both be null. @@ -223,6 +223,12 @@ public class AffixMatcher implements NumberParseMatcher { result.suffix = ""; } result.flags |= flags; + if (prefix != null) { + prefix.postProcess(result); + } + if (suffix != null) { + suffix.postProcess(result); + } } } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java index 95e313651c..f91a61ebd4 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java @@ -302,6 +302,7 @@ public class NumberParserImpl { for (NumberParseMatcher matcher : matchers) { matcher.postProcess(result); } + result.postProcess(); } private void parseGreedyRecursive(StringSegment segment, ParsedNumber result) { diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java index 9b472eed2d..40277d7700 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java @@ -2,7 +2,6 @@ // License & terms of use: http://www.unicode.org/copyright.html#License package com.ibm.icu.impl.number.parse; -import java.math.BigDecimal; import java.util.Comparator; import com.ibm.icu.impl.StringSegment; @@ -112,6 +111,19 @@ public class ParsedNumber { charEnd = segment.getOffset(); } + /** Apply certain number-related flags to the DecimalQuantity. */ + public void postProcess() { + if (quantity != null && 0 != (flags & FLAG_NEGATIVE)) { + quantity.negate(); + } + if (quantity != null && 0 != (flags & FLAG_PERCENT)) { + quantity.adjustMagnitude(-2); + } + if (quantity != null && 0 != (flags & FLAG_PERMILLE)) { + quantity.adjustMagnitude(-3); + } + } + /** * Returns whether this the parse was successful. To be successful, at least one char must have been * consumed, and the failure flag must not be set. @@ -129,7 +141,6 @@ public class ParsedNumber { } public Number getNumber(boolean forceBigDecimal) { - boolean sawNegative = 0 != (flags & FLAG_NEGATIVE); boolean sawNaN = 0 != (flags & FLAG_NAN); boolean sawInfinity = 0 != (flags & FLAG_INFINITY); @@ -138,35 +149,23 @@ public class ParsedNumber { return Double.NaN; } if (sawInfinity) { - if (sawNegative) { + if (0 != (flags & FLAG_NEGATIVE)) { return Double.NEGATIVE_INFINITY; } else { return Double.POSITIVE_INFINITY; } } - if (quantity.isZero() && sawNegative) { + assert quantity != null; + if (quantity.isZero() && quantity.isNegative()) { return -0.0; } if (quantity.fitsInLong() && !forceBigDecimal) { - long l = quantity.toLong(); - if (0 != (flags & FLAG_NEGATIVE)) { - l *= -1; - } - return l; + return quantity.toLong(); + } else { + return quantity.toBigDecimal(); } - BigDecimal d = quantity.toBigDecimal(); - if (0 != (flags & FLAG_NEGATIVE)) { - d = d.negate(); - } - // Special case: MIN_LONG - // TODO: It is supported in quantity.toLong() if quantity had the negative flag. - if (d.compareTo(BigDecimal.valueOf(Long.MIN_VALUE)) == 0 && !forceBigDecimal) { - return Long.MIN_VALUE; - } - return d; - } boolean isBetterThan(ParsedNumber other) { diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/PercentMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/PercentMatcher.java index afac0a6f72..3944c31684 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/PercentMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/PercentMatcher.java @@ -41,14 +41,6 @@ public class PercentMatcher extends SymbolMatcher { result.setCharsConsumed(segment); } - @Override - public void postProcess(ParsedNumber result) { - super.postProcess(result); - if (0 != (result.flags & ParsedNumber.FLAG_PERCENT) && result.quantity != null) { - result.quantity.adjustMagnitude(-2); - } - } - @Override public String toString() { return ""; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/PermilleMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/PermilleMatcher.java index d28e96b8e4..3ad6e09eac 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/PermilleMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/PermilleMatcher.java @@ -41,14 +41,6 @@ public class PermilleMatcher extends SymbolMatcher { result.setCharsConsumed(segment); } - @Override - public void postProcess(ParsedNumber result) { - super.postProcess(result); - if (0 != (result.flags & ParsedNumber.FLAG_PERMILLE) && result.quantity != null) { - result.quantity.adjustMagnitude(-3); - } - } - @Override public String toString() { return ""; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ScientificMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ScientificMatcher.java index 8fdd5b5bd8..9e0f1fa71e 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ScientificMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ScientificMatcher.java @@ -5,6 +5,7 @@ package com.ibm.icu.impl.number.parse; import com.ibm.icu.impl.StringSegment; import com.ibm.icu.impl.number.Grouper; import com.ibm.icu.text.DecimalFormatSymbols; +import com.ibm.icu.text.UnicodeSet; /** * @author sffc @@ -14,6 +15,8 @@ public class ScientificMatcher implements NumberParseMatcher { private final String exponentSeparatorString; private final DecimalMatcher exponentMatcher; + private final String customMinusSign; + private final String customPlusSign; public static ScientificMatcher getInstance(DecimalFormatSymbols symbols, Grouper grouper) { // TODO: Static-initialize most common instances? @@ -24,7 +27,20 @@ public class ScientificMatcher implements NumberParseMatcher { exponentSeparatorString = symbols.getExponentSeparator(); exponentMatcher = DecimalMatcher.getInstance(symbols, grouper, - ParsingUtils.PARSE_FLAG_INTEGER_ONLY); + ParsingUtils.PARSE_FLAG_INTEGER_ONLY | ParsingUtils.PARSE_FLAG_GROUPING_DISABLED); + + String minusSign = symbols.getMinusSignString(); + customMinusSign = minusSignSet().contains(minusSign) ? null : minusSign; + String plusSign = symbols.getPlusSignString(); + customPlusSign = plusSignSet().contains(plusSign) ? null : plusSign; + } + + private static UnicodeSet minusSignSet() { + return UnicodeSetStaticCache.get(UnicodeSetStaticCache.Key.MINUS_SIGN); + } + + private static UnicodeSet plusSignSet() { + return UnicodeSetStaticCache.get(UnicodeSetStaticCache.Key.PLUS_SIGN); } @Override @@ -42,18 +58,35 @@ public class ScientificMatcher implements NumberParseMatcher { // Full exponent separator match. // First attempt to get a code point, returning true if we can't get one. - segment.adjustOffset(overlap1); - if (segment.length() == 0) { + if (segment.length() == overlap1) { return true; } + segment.adjustOffset(overlap1); // Allow a sign, and then try to match digits. int exponentSign = 1; - if (segment.startsWith(UnicodeSetStaticCache.get(UnicodeSetStaticCache.Key.MINUS_SIGN))) { + if (segment.startsWith(minusSignSet())) { exponentSign = -1; segment.adjustOffsetByCodePoint(); - } else if (segment.startsWith(UnicodeSetStaticCache.get(UnicodeSetStaticCache.Key.PLUS_SIGN))) { + } else if (segment.startsWith(plusSignSet())) { segment.adjustOffsetByCodePoint(); + } else if (segment.startsWith(customMinusSign)) { + int overlap2 = segment.getCommonPrefixLength(customMinusSign); + if (overlap2 != customMinusSign.length()) { + // Partial custom sign match; un-match the exponent separator. + segment.adjustOffset(-overlap1); + return true; + } + exponentSign = -1; + segment.adjustOffset(overlap2); + } else if (segment.startsWith(customPlusSign)) { + int overlap2 = segment.getCommonPrefixLength(customPlusSign); + if (overlap2 != customPlusSign.length()) { + // Partial custom sign match; un-match the exponent separator. + segment.adjustOffset(-overlap1); + return true; + } + segment.adjustOffset(overlap2); } int digitsOffset = segment.getOffset(); 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 b910a9ca73..64cb95552f 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 @@ -429,6 +429,11 @@ public class DecimalQuantity_SimpleStorage implements DecimalQuantity { } } + @Override + public void negate() { + flags ^= NEGATIVE_FLAG; + } + /** * Divide the internal number by the specified quotient. This method forces the internal * representation into a BigDecimal. If you are dividing by a power of 10, use {@link diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java index 370a843f51..5f1ca8bfdf 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java @@ -5955,4 +5955,23 @@ public class NumberFormatTest extends TestFmwk { result.doubleValue(), 0.0); } + + @Test + public void testScientificCustomSign() { + DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(ULocale.ENGLISH); + dfs.setMinusSignString("nnn"); + dfs.setPlusSignString("ppp"); + DecimalFormat df = new DecimalFormat("0E0", dfs); + df.setExponentSignAlwaysShown(true); + expect2(df, 0.5, "5Ennn1"); + expect2(df, 50, "5Eppp1"); + } + + @Test + public void testParsePercentInPattern() { + DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(ULocale.ENGLISH); + DecimalFormat df = new DecimalFormat("0x%", dfs); + df.setParseStrict(true); + expect2(df, 0.5, "50x%"); + } } From 921355c6f05f6b5cc1d7a2e3c4fa9d66ba85fac7 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Sat, 31 Mar 2018 05:18:51 +0000 Subject: [PATCH 075/129] ICU-13634 Refactoring the two separate currency matchers into a single unified CombinedCurrencyMatcher. Allows for easy implementation of currency spacing (included in this changeset) and possibly other currency-related parsing features in the future. X-SVN-Rev: 41181 --- icu4c/source/i18n/numparse_affixes.cpp | 2 +- icu4c/source/i18n/numparse_affixes.h | 2 +- icu4c/source/i18n/numparse_compositions.cpp | 38 ----- icu4c/source/i18n/numparse_compositions.h | 44 ++--- icu4c/source/i18n/numparse_currency.cpp | 158 ++++++++---------- icu4c/source/i18n/numparse_currency.h | 69 ++------ icu4c/source/i18n/numparse_impl.cpp | 5 +- icu4c/source/i18n/numparse_impl.h | 3 +- icu4c/source/test/intltest/numbertest.h | 3 +- .../source/test/intltest/numbertest_parse.cpp | 6 +- .../test/testdata/NumberFormatTestCases.txt | 10 +- .../parse/AffixTokenMatcherFactory.java | 8 +- .../ibm/icu/impl/number/parse/AnyMatcher.java | 92 ---------- .../number/parse/CombinedCurrencyMatcher.java | 157 +++++++++++++++++ .../number/parse/CurrencyCustomMatcher.java | 67 -------- .../number/parse/CurrencyNamesMatcher.java | 82 --------- .../impl/number/parse/NumberParserImpl.java | 8 +- .../icu/dev/test/format/NumberFormatTest.java | 10 ++ .../icu/dev/test/number/NumberParserTest.java | 7 +- 19 files changed, 304 insertions(+), 467 deletions(-) delete mode 100644 icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AnyMatcher.java create mode 100644 icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CombinedCurrencyMatcher.java delete mode 100644 icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyCustomMatcher.java delete mode 100644 icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyNamesMatcher.java diff --git a/icu4c/source/i18n/numparse_affixes.cpp b/icu4c/source/i18n/numparse_affixes.cpp index 83ecdd144b..013cc01ff8 100644 --- a/icu4c/source/i18n/numparse_affixes.cpp +++ b/icu4c/source/i18n/numparse_affixes.cpp @@ -190,7 +190,7 @@ NumberParseMatcher& AffixTokenMatcherWarehouse::permille() { } NumberParseMatcher& AffixTokenMatcherWarehouse::currency(UErrorCode& status) { - return fCurrency = {{fSetupData->locale, status}, {fSetupData->currencySymbols, status}}; + return fCurrency = {fSetupData->currencySymbols, fSetupData->dfs, status}; } IgnorablesMatcher& AffixTokenMatcherWarehouse::ignorables() { diff --git a/icu4c/source/i18n/numparse_affixes.h b/icu4c/source/i18n/numparse_affixes.h index 3b79457b6d..08a7d912c6 100644 --- a/icu4c/source/i18n/numparse_affixes.h +++ b/icu4c/source/i18n/numparse_affixes.h @@ -125,7 +125,7 @@ class AffixTokenMatcherWarehouse : public UMemory { PlusSignMatcher fPlusSign; PercentMatcher fPercent; PermilleMatcher fPermille; - CurrencyAnyMatcher fCurrency; + CombinedCurrencyMatcher fCurrency; // Use a child class for code point matchers, since it requires non-default operators. CodePointMatcherWarehouse fCodePoints; diff --git a/icu4c/source/i18n/numparse_compositions.cpp b/icu4c/source/i18n/numparse_compositions.cpp index d254c07349..06aa476a29 100644 --- a/icu4c/source/i18n/numparse_compositions.cpp +++ b/icu4c/source/i18n/numparse_compositions.cpp @@ -18,44 +18,6 @@ using namespace icu::numparse; using namespace icu::numparse::impl; -bool AnyMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const { - int32_t initialOffset = segment.getOffset(); - bool maybeMore = false; - - // NOTE: The range-based for loop calls the virtual begin() and end() methods. - for (auto& matcher : *this) { - maybeMore = maybeMore || matcher->match(segment, result, status); - if (segment.getOffset() != initialOffset) { - // Match succeeded. - // NOTE: Except for a couple edge cases, if a matcher accepted string A, then it will - // accept any string starting with A. Therefore, there is no possibility that matchers - // later in the list may be evaluated on longer strings, and we can exit the loop here. - break; - } - } - - // None of the matchers succeeded. - return maybeMore; -} - -bool AnyMatcher::smokeTest(const StringSegment& segment) const { - // NOTE: The range-based for loop calls the virtual begin() and end() methods. - for (auto& matcher : *this) { - if (matcher->smokeTest(segment)) { - return true; - } - } - return false; -} - -void AnyMatcher::postProcess(ParsedNumber& result) const { - // NOTE: The range-based for loop calls the virtual begin() and end() methods. - for (auto& matcher : *this) { - matcher->postProcess(result); - } -} - - bool SeriesMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const { ParsedNumber backup(result); diff --git a/icu4c/source/i18n/numparse_compositions.h b/icu4c/source/i18n/numparse_compositions.h index 8d61ab46d3..a0b20c3433 100644 --- a/icu4c/source/i18n/numparse_compositions.h +++ b/icu4c/source/i18n/numparse_compositions.h @@ -29,27 +29,29 @@ class CompositionMatcher : public NumberParseMatcher { }; -/** - * Composes a number of matchers, and succeeds if any of the matchers succeed. Always greedily chooses - * the first matcher in the list to succeed. - * - * NOTE: In C++, this is a base class, unlike ICU4J, which uses a factory-style interface. - * - * @author sffc - * @see SeriesMatcher - */ -class AnyMatcher : public CompositionMatcher { - public: - bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; - - bool smokeTest(const StringSegment& segment) const override; - - void postProcess(ParsedNumber& result) const override; - - protected: - // No construction except by subclasses! - AnyMatcher() = default; -}; +// NOTE: AnyMatcher is no longer being used. The previous definition is shown below. +// The implementation can be found in SVN source control, deleted around March 30, 2018. +///** +// * Composes a number of matchers, and succeeds if any of the matchers succeed. Always greedily chooses +// * the first matcher in the list to succeed. +// * +// * NOTE: In C++, this is a base class, unlike ICU4J, which uses a factory-style interface. +// * +// * @author sffc +// * @see SeriesMatcher +// */ +//class AnyMatcher : public CompositionMatcher { +// public: +// bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; +// +// bool smokeTest(const StringSegment& segment) const override; +// +// void postProcess(ParsedNumber& result) const override; +// +// protected: +// // No construction except by subclasses! +// AnyMatcher() = default; +//}; /** diff --git a/icu4c/source/i18n/numparse_currency.cpp b/icu4c/source/i18n/numparse_currency.cpp index 6dee3d28d4..5c14fd7429 100644 --- a/icu4c/source/i18n/numparse_currency.cpp +++ b/icu4c/source/i18n/numparse_currency.cpp @@ -20,19 +20,83 @@ using namespace icu::numparse; using namespace icu::numparse::impl; -CurrencyNamesMatcher::CurrencyNamesMatcher(const Locale& locale, UErrorCode& status) - : fLocaleName(locale.getName(), -1, status) { +CombinedCurrencyMatcher::CombinedCurrencyMatcher(const CurrencySymbols& currencySymbols, + const DecimalFormatSymbols& dfs, UErrorCode& status) + : fCurrency1(currencySymbols.getCurrencySymbol(status)), + fCurrency2(currencySymbols.getIntlCurrencySymbol(status)), + afterPrefixInsert(dfs.getPatternForCurrencySpacing(UNUM_CURRENCY_INSERT, false, status)), + beforeSuffixInsert(dfs.getPatternForCurrencySpacing(UNUM_CURRENCY_INSERT, true, status)), + fLocaleName(dfs.getLocale().getName(), -1, status) { + utils::copyCurrencyCode(fCurrencyCode, currencySymbols.getIsoCode()); + + // Compute the full set of characters that could be the first in a currency to allow for + // efficient smoke test. + fLeadCodePoints.add(fCurrency1.char32At(0)); + fLeadCodePoints.add(fCurrency2.char32At(0)); + fLeadCodePoints.add(beforeSuffixInsert.char32At(0)); uprv_currencyLeads(fLocaleName.data(), fLeadCodePoints, status); // Always apply case mapping closure for currencies fLeadCodePoints.closeOver(USET_ADD_CASE_MAPPINGS); fLeadCodePoints.freeze(); } -bool CurrencyNamesMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const { +bool CombinedCurrencyMatcher::match(StringSegment& segment, ParsedNumber& result, + UErrorCode& status) const { if (result.currencyCode[0] != 0) { return false; } + // Try to match a currency spacing separator. + int32_t initialOffset = segment.getOffset(); + bool maybeMore = false; + if (result.seenNumber()) { + int32_t overlap = segment.getCommonPrefixLength(beforeSuffixInsert); + if (overlap == beforeSuffixInsert.length()) { + segment.adjustOffset(overlap); + // Note: let currency spacing be a weak match. Don't update chars consumed. + } + maybeMore = maybeMore || overlap == segment.length(); + } + + // Match the currency string, and reset if we didn't find one. + maybeMore = maybeMore || matchCurrency(segment, result, status); + if (result.currencyCode[0] == 0) { + segment.setOffset(initialOffset); + return maybeMore; + } + + // Try to match a currency spacing separator. + if (!result.seenNumber()) { + int32_t overlap = segment.getCommonPrefixLength(afterPrefixInsert); + if (overlap == afterPrefixInsert.length()) { + segment.adjustOffset(overlap); + // Note: let currency spacing be a weak match. Don't update chars consumed. + } + maybeMore = maybeMore || overlap == segment.length(); + } + + return maybeMore; +} + +bool CombinedCurrencyMatcher::matchCurrency(StringSegment& segment, ParsedNumber& result, + UErrorCode& status) const { + + int32_t overlap1 = segment.getCommonPrefixLength(fCurrency1); + if (overlap1 == fCurrency1.length()) { + utils::copyCurrencyCode(result.currencyCode, fCurrencyCode); + segment.adjustOffset(overlap1); + result.setCharsConsumed(segment); + return segment.length() == 0; + } + + int32_t overlap2 = segment.getCommonPrefixLength(fCurrency2); + if (overlap2 == fCurrency2.length()) { + utils::copyCurrencyCode(result.currencyCode, fCurrencyCode); + segment.adjustOffset(overlap2); + result.setCharsConsumed(segment); + return segment.length() == 0; + } + // NOTE: This call site should be improved with #13584. const UnicodeString segmentString = segment.toTempUnicodeString(); @@ -48,9 +112,6 @@ bool CurrencyNamesMatcher::match(StringSegment& segment, ParsedNumber& result, U result.currencyCode, status); - // Possible partial match - bool partialMatch = partialMatchLen == segment.length(); - if (U_SUCCESS(status) && ppos.getIndex() != 0) { // Complete match. // NOTE: The currency code should already be saved in the ParsedNumber. @@ -58,91 +119,16 @@ bool CurrencyNamesMatcher::match(StringSegment& segment, ParsedNumber& result, U result.setCharsConsumed(segment); } - return partialMatch; + return overlap1 == segment.length() || overlap2 == segment.length() || + partialMatchLen == segment.length(); } -bool CurrencyNamesMatcher::smokeTest(const StringSegment& segment) const { +bool CombinedCurrencyMatcher::smokeTest(const StringSegment& segment) const { return segment.startsWith(fLeadCodePoints); } -UnicodeString CurrencyNamesMatcher::toString() const { - return u""; -} - - -CurrencyCustomMatcher::CurrencyCustomMatcher(const CurrencySymbols& currencySymbols, UErrorCode& status) - : fCurrency1(currencySymbols.getCurrencySymbol(status)), - fCurrency2(currencySymbols.getIntlCurrencySymbol(status)) { - utils::copyCurrencyCode(fCurrencyCode, currencySymbols.getIsoCode()); -} - -bool CurrencyCustomMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCode&) const { - if (result.currencyCode[0] != 0) { - return false; - } - - int overlap1 = segment.getCommonPrefixLength(fCurrency1); - if (overlap1 == fCurrency1.length()) { - utils::copyCurrencyCode(result.currencyCode, fCurrencyCode); - segment.adjustOffset(overlap1); - result.setCharsConsumed(segment); - } - - int overlap2 = segment.getCommonPrefixLength(fCurrency2); - if (overlap2 == fCurrency2.length()) { - utils::copyCurrencyCode(result.currencyCode, fCurrencyCode); - segment.adjustOffset(overlap2); - result.setCharsConsumed(segment); - } - - return overlap1 == segment.length() || overlap2 == segment.length(); -} - -bool CurrencyCustomMatcher::smokeTest(const StringSegment& segment) const { - return segment.startsWith(fCurrency1) - || segment.startsWith(fCurrency2); -} - -UnicodeString CurrencyCustomMatcher::toString() const { - return u""; -} - - -CurrencyAnyMatcher::CurrencyAnyMatcher() { - fMatcherArray[0] = &fNamesMatcher; - fMatcherArray[1] = &fCustomMatcher; -} - -CurrencyAnyMatcher::CurrencyAnyMatcher(CurrencyNamesMatcher namesMatcher, - CurrencyCustomMatcher customMatcher) - : fNamesMatcher(std::move(namesMatcher)), fCustomMatcher(std::move(customMatcher)) { - fMatcherArray[0] = &fNamesMatcher; - fMatcherArray[1] = &fCustomMatcher; -} - -CurrencyAnyMatcher::CurrencyAnyMatcher(CurrencyAnyMatcher&& src) U_NOEXCEPT - : fNamesMatcher(std::move(src.fNamesMatcher)), fCustomMatcher(std::move(src.fCustomMatcher)) { - fMatcherArray[0] = &fNamesMatcher; - fMatcherArray[1] = &fCustomMatcher; -} - -CurrencyAnyMatcher& CurrencyAnyMatcher::operator=(CurrencyAnyMatcher&& src) U_NOEXCEPT { - fNamesMatcher = std::move(src.fNamesMatcher); - fCustomMatcher = std::move(src.fCustomMatcher); - // Note: do NOT move fMatcherArray - return *this; -} - -const NumberParseMatcher* const* CurrencyAnyMatcher::begin() const { - return fMatcherArray; -} - -const NumberParseMatcher* const* CurrencyAnyMatcher::end() const { - return fMatcherArray + 2; -} - -UnicodeString CurrencyAnyMatcher::toString() const { - return u""; +UnicodeString CombinedCurrencyMatcher::toString() const { + return u""; } diff --git a/icu4c/source/i18n/numparse_currency.h b/icu4c/source/i18n/numparse_currency.h index 1c2a57d2c1..fa7d67b9bd 100644 --- a/icu4c/source/i18n/numparse_currency.h +++ b/icu4c/source/i18n/numparse_currency.h @@ -19,38 +19,21 @@ namespace impl { using ::icu::number::impl::CurrencySymbols; /** - * Matches currencies according to all available strings in locale data. + * Matches a currency, either a custom currency or one from the data bundle. The class is called + * "combined" to emphasize that the currency string may come from one of multiple sources. * - * The implementation of this class is different between J and C. See #13584 for a follow-up. + * Will match currency spacing either before or after the number depending on whether we are currently in + * the prefix or suffix. + * + * The implementation of this class is slightly different between J and C. See #13584 for a follow-up. * * @author sffc */ -class CurrencyNamesMatcher : public NumberParseMatcher, public UMemory { +class CombinedCurrencyMatcher : public NumberParseMatcher, public UMemory { public: - CurrencyNamesMatcher() = default; // WARNING: Leaves the object in an unusable state + CombinedCurrencyMatcher() = default; // WARNING: Leaves the object in an unusable state - CurrencyNamesMatcher(const Locale& locale, UErrorCode& status); - - bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; - - bool smokeTest(const StringSegment& segment) const override; - - UnicodeString toString() const override; - - private: - // We could use Locale instead of CharString here, but - // Locale has a non-trivial default constructor. - CharString fLocaleName; - - UnicodeSet fLeadCodePoints; -}; - - -class CurrencyCustomMatcher : public NumberParseMatcher, public UMemory { - public: - CurrencyCustomMatcher() = default; // WARNING: Leaves the object in an unusable state - - CurrencyCustomMatcher(const CurrencySymbols& currencySymbols, UErrorCode& status); + CombinedCurrencyMatcher(const CurrencySymbols& currencySymbols, const DecimalFormatSymbols& dfs, UErrorCode& status); bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; @@ -62,36 +45,18 @@ class CurrencyCustomMatcher : public NumberParseMatcher, public UMemory { UChar fCurrencyCode[4]; UnicodeString fCurrency1; UnicodeString fCurrency2; -}; + UnicodeString afterPrefixInsert; + UnicodeString beforeSuffixInsert; -/** - * An implementation of AnyMatcher, allowing for either currency data or locale currency matches. - */ -class CurrencyAnyMatcher : public AnyMatcher, public UMemory { - public: - CurrencyAnyMatcher(); // WARNING: Leaves the object in an unusable state + // We could use Locale instead of CharString here, but + // Locale has a non-trivial default constructor. + CharString fLocaleName; - CurrencyAnyMatcher(CurrencyNamesMatcher namesMatcher, CurrencyCustomMatcher customMatcher); + UnicodeSet fLeadCodePoints; - // Needs custom move constructor/operator since constructor is nontrivial - - CurrencyAnyMatcher(CurrencyAnyMatcher&& src) U_NOEXCEPT; - - CurrencyAnyMatcher& operator=(CurrencyAnyMatcher&& src) U_NOEXCEPT; - - UnicodeString toString() const override; - - protected: - const NumberParseMatcher* const* begin() const override; - - const NumberParseMatcher* const* end() const override; - - private: - CurrencyNamesMatcher fNamesMatcher; - CurrencyCustomMatcher fCustomMatcher; - - const NumberParseMatcher* fMatcherArray[2]; + /** Matches the currency string without concern for currency spacing. */ + bool matchCurrency(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const; }; diff --git a/icu4c/source/i18n/numparse_impl.cpp b/icu4c/source/i18n/numparse_impl.cpp index 36ddc1f2f1..89db7001a3 100644 --- a/icu4c/source/i18n/numparse_impl.cpp +++ b/icu4c/source/i18n/numparse_impl.cpp @@ -69,7 +69,7 @@ NumberParserImpl::createSimpleParser(const Locale& locale, const UnicodeString& parser->addMatcher(parser->fLocalMatchers.infinity = {symbols}); parser->addMatcher(parser->fLocalMatchers.padding = {u"@"}); parser->addMatcher(parser->fLocalMatchers.scientific = {symbols, grouper}); - parser->addMatcher(parser->fLocalMatchers.currencyNames = {locale, status}); + parser->addMatcher(parser->fLocalMatchers.currency = {currencySymbols, symbols, status}); // parser.addMatcher(new RequireNumberMatcher()); parser->freeze(); @@ -136,8 +136,7 @@ NumberParserImpl::createParserFromProperties(const number::impl::DecimalFormatPr //////////////////////// if (parseCurrency || patternInfo.hasCurrencySign()) { - parser->addMatcher(parser->fLocalMatchers.currencyCustom = {currencySymbols, status}); - parser->addMatcher(parser->fLocalMatchers.currencyNames = {locale, status}); + parser->addMatcher(parser->fLocalMatchers.currency = {currencySymbols, symbols, status}); } /////////////////////////////// diff --git a/icu4c/source/i18n/numparse_impl.h b/icu4c/source/i18n/numparse_impl.h index 96a259a55f..308a2ffcf8 100644 --- a/icu4c/source/i18n/numparse_impl.h +++ b/icu4c/source/i18n/numparse_impl.h @@ -68,8 +68,7 @@ class NumberParserImpl : public MutableMatcherCollection { PlusSignMatcher plusSign; DecimalMatcher decimal; ScientificMatcher scientific; - CurrencyNamesMatcher currencyNames; - CurrencyCustomMatcher currencyCustom; + CombinedCurrencyMatcher currency; AffixMatcherWarehouse affixMatcherWarehouse; AffixTokenMatcherWarehouse affixTokenMatcherWarehouse; } fLocalMatchers; diff --git a/icu4c/source/test/intltest/numbertest.h b/icu4c/source/test/intltest/numbertest.h index f8a16e8653..e1a84aab1e 100644 --- a/icu4c/source/test/intltest/numbertest.h +++ b/icu4c/source/test/intltest/numbertest.h @@ -226,9 +226,10 @@ class NumberParserTest : public IntlTest { void testBasic(); void testLocaleFi(); void testSeriesMatcher(); - void testCurrencyAnyMatcher(); + void testCombinedCurrencyMatcher(); void testAffixPatternMatcher(); void testGroupingDisabled(); + void testCaseFolding(); void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0); }; diff --git a/icu4c/source/test/intltest/numbertest_parse.cpp b/icu4c/source/test/intltest/numbertest_parse.cpp index 11d6d64cdf..ec8fee01b8 100644 --- a/icu4c/source/test/intltest/numbertest_parse.cpp +++ b/icu4c/source/test/intltest/numbertest_parse.cpp @@ -23,7 +23,7 @@ void NumberParserTest::runIndexedTest(int32_t index, UBool exec, const char*& na TESTCASE_AUTO_BEGIN; TESTCASE_AUTO(testBasic); TESTCASE_AUTO(testSeriesMatcher); - TESTCASE_AUTO(testCurrencyAnyMatcher); + TESTCASE_AUTO(testCombinedCurrencyMatcher); TESTCASE_AUTO(testAffixPatternMatcher); TESTCASE_AUTO_END; } @@ -211,8 +211,8 @@ void NumberParserTest::testSeriesMatcher() { } } -void NumberParserTest::testCurrencyAnyMatcher() { - IcuTestErrorCode status(*this, "testCurrencyAnyMatcher"); +void NumberParserTest::testCombinedCurrencyMatcher() { + IcuTestErrorCode status(*this, "testCombinedCurrencyMatcher"); IgnorablesMatcher ignorables(unisets::DEFAULT_IGNORABLES); Locale locale = Locale::getEnglish(); diff --git a/icu4c/source/test/testdata/NumberFormatTestCases.txt b/icu4c/source/test/testdata/NumberFormatTestCases.txt index b7c7beb866..f46810bf01 100644 --- a/icu4c/source/test/testdata/NumberFormatTestCases.txt +++ b/icu4c/source/test/testdata/NumberFormatTestCases.txt @@ -16,12 +16,12 @@ rt: "0.###" 1.0 "1" # Basics fp: "0.####" 0.10005 "0.1" 0.1 fp: - 0.10006 "0.1001" 0.1001 -pat: - "#0.####" +pat: - "0.####" fp: "#.####" 0.10005 "0.1" 0.1 -pat: - "#0.####" +pat: - "0.####" rt: "0" 1234 "1234" -pat: - "#0" +pat: - "0" # Significant digits fp: "@@@" 1.234567 "1.23" 1.23 @@ -79,12 +79,12 @@ fpc: - 1234.56/JPY "\u00A51,235" 1235/JPY # ISO codes that overlap display names (QQQ vs. Q) # recognize real ISO name in parsing, so, can not use fake name as QQQ #fpc: - 123/QQQ "QQQ123.00" 123/QQQ # QQQ is fake -fpc: - 123/GTQ "GTQ123.00" 123/GTQ +fpc: - 123/GTQ "GTQ 123.00" 123/GTQ # ChoiceFormat-based display names fpc: - 1/INR "\u20b91.00" 1/INR fpc: - 2/INR "\u20b92.00" 2/INR # Display names with shared prefix (YDD vs. Y) -fpc: - 100/YDD "YDD100.00" 100/YDD +fpc: - 100/YDD "YDD 100.00" 100/YDD fpc: - 100/CNY "CN\u00a5100.00" 100/CNY # Regression Tests bug#7914 diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixTokenMatcherFactory.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixTokenMatcherFactory.java index 4c051572aa..769749f7cb 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixTokenMatcherFactory.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixTokenMatcherFactory.java @@ -33,12 +33,8 @@ public class AffixTokenMatcherFactory { return PermilleMatcher.getInstance(symbols); } - public AnyMatcher currency() { - AnyMatcher any = new AnyMatcher(); - any.addMatcher(CurrencyCustomMatcher.getInstance(currency, locale)); - any.addMatcher(CurrencyNamesMatcher.getInstance(locale)); - any.freeze(); - return any; + public CombinedCurrencyMatcher currency() { + return CombinedCurrencyMatcher.getInstance(currency, symbols); } public IgnorablesMatcher ignorables() { diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AnyMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AnyMatcher.java deleted file mode 100644 index e5359ead47..0000000000 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AnyMatcher.java +++ /dev/null @@ -1,92 +0,0 @@ -// © 2018 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html#License -package com.ibm.icu.impl.number.parse; - -import java.util.ArrayList; -import java.util.List; - -import com.ibm.icu.impl.StringSegment; - -/** - * Composes a number of matchers, and succeeds if any of the matchers succeed. Always greedily chooses - * the first matcher in the list to succeed. - * - * @author sffc - * @see SeriesMatcher - */ -public class AnyMatcher implements NumberParseMatcher { - - protected List matchers = null; - protected boolean frozen = false; - - public void addMatcher(NumberParseMatcher matcher) { - assert !frozen; - if (matchers == null) { - matchers = new ArrayList(); - } - matchers.add(matcher); - } - - public void freeze() { - frozen = true; - } - - @Override - public boolean match(StringSegment segment, ParsedNumber result) { - assert frozen; - if (matchers == null) { - return false; - } - - int initialOffset = segment.getOffset(); - boolean maybeMore = false; - for (int i = 0; i < matchers.size(); i++) { - NumberParseMatcher matcher = matchers.get(i); - maybeMore = maybeMore || matcher.match(segment, result); - if (segment.getOffset() != initialOffset) { - // Match succeeded. - // NOTE: Except for a couple edge cases, if a matcher accepted string A, then it will - // accept any string starting with A. Therefore, there is no possibility that matchers - // later in the list may be evaluated on longer strings, and we can exit the loop here. - break; - } - } - - // None of the matchers succeeded. - return maybeMore; - } - - @Override - public boolean smokeTest(StringSegment segment) { - assert frozen; - if (matchers == null) { - return false; - } - - for (int i = 0; i < matchers.size(); i++) { - if (matchers.get(i).smokeTest(segment)) { - return true; - } - } - return false; - } - - @Override - public void postProcess(ParsedNumber result) { - assert frozen; - if (matchers == null) { - return; - } - - for (int i = 0; i < matchers.size(); i++) { - NumberParseMatcher matcher = matchers.get(i); - matcher.postProcess(result); - } - } - - @Override - public String toString() { - return ""; - } - -} diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CombinedCurrencyMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CombinedCurrencyMatcher.java new file mode 100644 index 0000000000..c1d14189b6 --- /dev/null +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CombinedCurrencyMatcher.java @@ -0,0 +1,157 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html#License +package com.ibm.icu.impl.number.parse; + +import java.util.Iterator; + +import com.ibm.icu.impl.StringSegment; +import com.ibm.icu.impl.TextTrieMap; +import com.ibm.icu.text.DecimalFormatSymbols; +import com.ibm.icu.text.UnicodeSet; +import com.ibm.icu.util.Currency; +import com.ibm.icu.util.Currency.CurrencyStringInfo; + +/** + * Matches a currency, either a custom currency or one from the data bundle. The class is called + * "combined" to emphasize that the currency string may come from one of multiple sources. + * + * Will match currency spacing either before or after the number depending on whether we are currently in + * the prefix or suffix. + * + * The implementation of this class is slightly different between J and C. See #13584 for a follow-up. + * + * @author sffc + */ +public class CombinedCurrencyMatcher implements NumberParseMatcher { + + private final String isoCode; + private final String currency1; + private final String currency2; + + private final String afterPrefixInsert; + private final String beforeSuffixInsert; + + private final TextTrieMap longNameTrie; + private final TextTrieMap symbolTrie; + + private final UnicodeSet leadCodePoints; + + public static CombinedCurrencyMatcher getInstance(Currency currency, DecimalFormatSymbols dfs) { + // TODO: Cache these instances. They are somewhat expensive. + return new CombinedCurrencyMatcher(currency, dfs); + } + + private CombinedCurrencyMatcher(Currency currency, DecimalFormatSymbols dfs) { + this.isoCode = currency.getSubtype(); + this.currency1 = currency.getSymbol(dfs.getULocale()); + this.currency2 = currency.getCurrencyCode(); + + afterPrefixInsert = dfs + .getPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_INSERT, false); + beforeSuffixInsert = dfs + .getPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_INSERT, true); + + // TODO: Currency trie does not currently have an option for case folding. It defaults to use + // case folding on long-names but not symbols. + longNameTrie = Currency.getParsingTrie(dfs.getULocale(), Currency.LONG_NAME); + symbolTrie = Currency.getParsingTrie(dfs.getULocale(), Currency.SYMBOL_NAME); + + // Compute the full set of characters that could be the first in a currency to allow for + // efficient smoke test. + leadCodePoints = new UnicodeSet(); + leadCodePoints.add(currency1.codePointAt(0)); + leadCodePoints.add(currency2.codePointAt(0)); + leadCodePoints.add(beforeSuffixInsert.codePointAt(0)); + longNameTrie.putLeadCodePoints(leadCodePoints); + symbolTrie.putLeadCodePoints(leadCodePoints); + // Always apply case mapping closure for currencies + leadCodePoints.closeOver(UnicodeSet.ADD_CASE_MAPPINGS); + leadCodePoints.freeze(); + } + + @Override + public boolean match(StringSegment segment, ParsedNumber result) { + if (result.currencyCode != null) { + return false; + } + + // Try to match a currency spacing separator. + int initialOffset = segment.getOffset(); + boolean maybeMore = false; + if (result.seenNumber()) { + int overlap = segment.getCommonPrefixLength(beforeSuffixInsert); + if (overlap == beforeSuffixInsert.length()) { + segment.adjustOffset(overlap); + // Note: let currency spacing be a weak match. Don't update chars consumed. + } + maybeMore = maybeMore || overlap == segment.length(); + } + + // Match the currency string, and reset if we didn't find one. + maybeMore = maybeMore || matchCurrency(segment, result); + if (result.currencyCode == null) { + segment.setOffset(initialOffset); + return maybeMore; + } + + // Try to match a currency spacing separator. + if (!result.seenNumber()) { + int overlap = segment.getCommonPrefixLength(afterPrefixInsert); + if (overlap == afterPrefixInsert.length()) { + segment.adjustOffset(overlap); + // Note: let currency spacing be a weak match. Don't update chars consumed. + } + maybeMore = maybeMore || overlap == segment.length(); + } + + return maybeMore; + } + + /** Matches the currency string without concern for currency spacing. */ + private boolean matchCurrency(StringSegment segment, ParsedNumber result) { + int overlap1 = segment.getCommonPrefixLength(currency1); + if (overlap1 == currency1.length()) { + result.currencyCode = isoCode; + segment.adjustOffset(overlap1); + result.setCharsConsumed(segment); + return segment.length() == 0; + } + + int overlap2 = segment.getCommonPrefixLength(currency2); + if (overlap2 == currency2.length()) { + result.currencyCode = isoCode; + segment.adjustOffset(overlap2); + result.setCharsConsumed(segment); + return segment.length() == 0; + } + + TextTrieMap.Output trieOutput = new TextTrieMap.Output(); + Iterator values = longNameTrie.get(segment, 0, trieOutput); + if (values == null) { + values = symbolTrie.get(segment, 0, trieOutput); + } + if (values != null) { + result.currencyCode = values.next().getISOCode(); + segment.adjustOffset(trieOutput.matchLength); + result.setCharsConsumed(segment); + } + + return overlap1 == segment.length() || overlap2 == segment.length() || trieOutput.partialMatch; + } + + @Override + public boolean smokeTest(StringSegment segment) { + return segment.startsWith(leadCodePoints); + } + + @Override + public void postProcess(ParsedNumber result) { + // No-op + } + + @Override + public String toString() { + return ""; + } + +} diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyCustomMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyCustomMatcher.java deleted file mode 100644 index 019af7504f..0000000000 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyCustomMatcher.java +++ /dev/null @@ -1,67 +0,0 @@ -// © 2017 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html#License -package com.ibm.icu.impl.number.parse; - -import com.ibm.icu.impl.StringSegment; -import com.ibm.icu.util.Currency; -import com.ibm.icu.util.ULocale; - -/** - * A matcher for a single currency instance (not the full trie). - */ -public class CurrencyCustomMatcher implements NumberParseMatcher { - - private final String isoCode; - private final String currency1; - private final String currency2; - - public static CurrencyCustomMatcher getInstance(Currency currency, ULocale loc) { - return new CurrencyCustomMatcher(currency.getSubtype(), - currency.getSymbol(loc), - currency.getCurrencyCode()); - } - - private CurrencyCustomMatcher(String isoCode, String currency1, String currency2) { - this.isoCode = isoCode; - this.currency1 = currency1; - this.currency2 = currency2; - } - - @Override - public boolean match(StringSegment segment, ParsedNumber result) { - if (result.currencyCode != null) { - return false; - } - - int overlap1 = segment.getCommonPrefixLength(currency1); - if (overlap1 == currency1.length()) { - result.currencyCode = isoCode; - segment.adjustOffset(overlap1); - result.setCharsConsumed(segment); - } - - int overlap2 = segment.getCommonPrefixLength(currency2); - if (overlap2 == currency2.length()) { - result.currencyCode = isoCode; - segment.adjustOffset(overlap2); - result.setCharsConsumed(segment); - } - - return overlap1 == segment.length() || overlap2 == segment.length(); - } - - @Override - public boolean smokeTest(StringSegment segment) { - return segment.startsWith(currency1) || segment.startsWith(currency2); - } - - @Override - public void postProcess(ParsedNumber result) { - // No-op - } - - @Override - public String toString() { - return ""; - } -} diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyNamesMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyNamesMatcher.java deleted file mode 100644 index be2acd42f9..0000000000 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyNamesMatcher.java +++ /dev/null @@ -1,82 +0,0 @@ -// © 2017 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html#License -package com.ibm.icu.impl.number.parse; - -import java.util.Iterator; - -import com.ibm.icu.impl.StringSegment; -import com.ibm.icu.impl.TextTrieMap; -import com.ibm.icu.text.UnicodeSet; -import com.ibm.icu.util.Currency; -import com.ibm.icu.util.Currency.CurrencyStringInfo; -import com.ibm.icu.util.ULocale; - -/** - * Matches currencies according to all available strings in locale data. - * - * The implementation of this class is different between J and C. See #13584 for a follow-up. - * - * @author sffc - */ -public class CurrencyNamesMatcher implements NumberParseMatcher { - - private final TextTrieMap longNameTrie; - private final TextTrieMap symbolTrie; - - private final UnicodeSet leadCodePoints; - - public static CurrencyNamesMatcher getInstance(ULocale locale) { - // TODO: Pre-compute some of the more popular locales? - return new CurrencyNamesMatcher(locale); - } - - private CurrencyNamesMatcher(ULocale locale) { - // TODO: Currency trie does not currently have an option for case folding. It defaults to use - // case folding on long-names but not symbols. - longNameTrie = Currency.getParsingTrie(locale, Currency.LONG_NAME); - symbolTrie = Currency.getParsingTrie(locale, Currency.SYMBOL_NAME); - - // Compute the full set of characters that could be the first in a currency to allow for - // efficient smoke test. - leadCodePoints = new UnicodeSet(); - longNameTrie.putLeadCodePoints(leadCodePoints); - symbolTrie.putLeadCodePoints(leadCodePoints); - // Always apply case mapping closure for currencies - leadCodePoints.closeOver(UnicodeSet.ADD_CASE_MAPPINGS); - leadCodePoints.freeze(); - } - - @Override - public boolean match(StringSegment segment, ParsedNumber result) { - if (result.currencyCode != null) { - return false; - } - - TextTrieMap.Output trieOutput = new TextTrieMap.Output(); - Iterator values = longNameTrie.get(segment, 0, trieOutput); - if (values == null) { - values = symbolTrie.get(segment, 0, trieOutput); - } - if (values != null) { - result.currencyCode = values.next().getISOCode(); - segment.adjustOffset(trieOutput.matchLength); - result.setCharsConsumed(segment); - } - return trieOutput.partialMatch; - } - - @Override - public boolean smokeTest(StringSegment segment) { - return segment.startsWith(leadCodePoints); - } - - @Override - public void postProcess(ParsedNumber result) { - // No-op - } - - @Override - public String toString() { - return ""; - } -} diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java index f91a61ebd4..9283523275 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java @@ -37,11 +37,12 @@ public class NumberParserImpl { public static NumberParserImpl createSimpleParser(ULocale locale, String pattern, int parseFlags) { NumberParserImpl parser = new NumberParserImpl(parseFlags); + Currency currency = Currency.getInstance("USD"); DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale); IgnorablesMatcher ignorables = IgnorablesMatcher.DEFAULT; AffixTokenMatcherFactory factory = new AffixTokenMatcherFactory(); - factory.currency = Currency.getInstance("USD"); + factory.currency = currency; factory.symbols = symbols; factory.ignorables = ignorables; factory.locale = locale; @@ -61,7 +62,7 @@ public class NumberParserImpl { parser.addMatcher(InfinityMatcher.getInstance(symbols)); parser.addMatcher(PaddingMatcher.getInstance("@")); parser.addMatcher(ScientificMatcher.getInstance(symbols, grouper)); - parser.addMatcher(CurrencyNamesMatcher.getInstance(locale)); + parser.addMatcher(CombinedCurrencyMatcher.getInstance(currency, symbols)); parser.addMatcher(new RequireNumberValidator()); parser.freeze(); @@ -185,8 +186,7 @@ public class NumberParserImpl { //////////////////////// if (parseCurrency || patternInfo.hasCurrencySign()) { - parser.addMatcher(CurrencyCustomMatcher.getInstance(currency, locale)); - parser.addMatcher(CurrencyNamesMatcher.getInstance(locale)); + parser.addMatcher(CombinedCurrencyMatcher.getInstance(currency, symbols)); } /////////////////////////////// diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java index 5f1ca8bfdf..06ece47211 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java @@ -5974,4 +5974,14 @@ public class NumberFormatTest extends TestFmwk { df.setParseStrict(true); expect2(df, 0.5, "50x%"); } + + @Test + public void testParseIsoStrict() { + DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(ULocale.ENGLISH); + DecimalFormat df = new DecimalFormat("¤¤0;-0¤¤", dfs); + df.setCurrency(Currency.getInstance("USD")); + df.setParseStrict(true); + expect2(df, 45, "USD 45.00"); + expect2(df, -45, "-45.00 USD"); + } } diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java index b35cd789c9..57aef1547c 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java @@ -13,7 +13,7 @@ import com.ibm.icu.impl.number.CustomSymbolCurrency; import com.ibm.icu.impl.number.DecimalFormatProperties; import com.ibm.icu.impl.number.parse.AffixPatternMatcher; import com.ibm.icu.impl.number.parse.AffixTokenMatcherFactory; -import com.ibm.icu.impl.number.parse.AnyMatcher; +import com.ibm.icu.impl.number.parse.CombinedCurrencyMatcher; import com.ibm.icu.impl.number.parse.IgnorablesMatcher; import com.ibm.icu.impl.number.parse.MinusSignMatcher; import com.ibm.icu.impl.number.parse.NumberParserImpl; @@ -229,12 +229,13 @@ public class NumberParserTest { } @Test - public void testCurrencyAnyMatcher() { + public void testCombinedCurrencyMatcher() { AffixTokenMatcherFactory factory = new AffixTokenMatcherFactory(); factory.locale = ULocale.ENGLISH; CustomSymbolCurrency currency = new CustomSymbolCurrency("ICU", "IU$", "ICU"); factory.currency = currency; - AnyMatcher matcher = factory.currency(); + factory.symbols = DecimalFormatSymbols.getInstance(ULocale.ENGLISH); + CombinedCurrencyMatcher matcher = factory.currency(); Object[][] cases = new Object[][] { { "", null }, From 8ea876aadb5a1f7718f9662611381fbc834a1e97 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Tue, 3 Apr 2018 04:38:16 +0000 Subject: [PATCH 076/129] ICU-13678 Cleaning up multiplier implementation and adding public API. X-SVN-Rev: 41188 --- icu4c/source/i18n/Makefile.in | 8 +- icu4c/source/i18n/number_decimalquantity.cpp | 10 +- icu4c/source/i18n/number_decimalquantity.h | 2 +- icu4c/source/i18n/number_fluent.cpp | 14 ++ icu4c/source/i18n/number_mapper.cpp | 6 +- icu4c/source/i18n/number_multiplier.cpp | 72 ++++++-- icu4c/source/i18n/number_multiplier.h | 20 +- icu4c/source/i18n/number_utils.h | 2 +- icu4c/source/i18n/numparse_impl.cpp | 12 +- icu4c/source/i18n/numparse_impl.h | 2 + icu4c/source/i18n/numparse_validators.h | 20 ++ icu4c/source/i18n/unicode/numberformatter.h | 171 ++++++++++++++---- icu4c/source/test/intltest/numbertest.h | 1 + icu4c/source/test/intltest/numbertest_api.cpp | 78 ++++++++ .../com/ibm/icu/impl/number/MacroProps.java | 3 +- .../impl/number/MultiplierFormatHandler.java | 25 +++ .../ibm/icu/impl/number/MultiplierImpl.java | 43 ----- .../ibm/icu/impl/number/RoundingUtils.java | 21 +++ .../impl/number/parse/MultiplierHandler.java | 39 ---- .../number/parse/MultiplierParseHandler.java | 30 +++ .../impl/number/parse/NumberParserImpl.java | 7 +- .../src/com/ibm/icu/number/Multiplier.java | 171 ++++++++++++++++++ .../ibm/icu/number/NumberFormatterImpl.java | 3 +- .../icu/number/NumberFormatterSettings.java | 42 ++++- .../ibm/icu/number/NumberPropertyMapper.java | 7 +- .../ibm/icu/number/NumberSkeletonImpl.java | 3 +- .../core/src/com/ibm/icu/number/Rounder.java | 5 +- .../test/number/NumberFormatterApiTest.java | 79 ++++++++ 28 files changed, 716 insertions(+), 180 deletions(-) create mode 100644 icu4j/main/classes/core/src/com/ibm/icu/impl/number/MultiplierFormatHandler.java delete mode 100644 icu4j/main/classes/core/src/com/ibm/icu/impl/number/MultiplierImpl.java delete mode 100644 icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/MultiplierHandler.java create mode 100644 icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/MultiplierParseHandler.java create mode 100644 icu4j/main/classes/core/src/com/ibm/icu/number/Multiplier.java diff --git a/icu4c/source/i18n/Makefile.in b/icu4c/source/i18n/Makefile.in index eaa5d0683e..44236b5047 100644 --- a/icu4c/source/i18n/Makefile.in +++ b/icu4c/source/i18n/Makefile.in @@ -103,13 +103,13 @@ number_decimfmtprops.o number_fluent.o number_formatimpl.o number_grouping.o \ number_integerwidth.o number_longnames.o number_modifiers.o number_notation.o \ number_padding.o number_patternmodifier.o number_patternstring.o \ number_rounding.o number_scientific.o number_stringbuilder.o \ +number_mapper.o number_multiplier.o number_currencysymbols.o number_skeletons.o number_capi.o \ double-conversion.o double-conversion-bignum-dtoa.o double-conversion-bignum.o \ double-conversion-cached-powers.o double-conversion-diy-fp.o \ double-conversion-fast-dtoa.o double-conversion-strtod.o \ -numparse_stringsegment.o numparse_unisets.o numparse_parsednumber.o \ -numparse_impl.o numparse_symbols.o numparse_decimal.o numparse_scientific.o \ -numparse_currency.o numparse_affixes.o numparse_compositions.o numparse_validators.o \ -number_mapper.o number_multiplier.o number_currencysymbols.o number_skeletons.o number_capi.o \ +numparse_stringsegment.o numparse_unisets.o numparse_parsednumber.o numparse_impl.o \ +numparse_symbols.o numparse_decimal.o numparse_scientific.o numparse_currency.o \ +numparse_affixes.o numparse_compositions.o numparse_validators.o \ ## Header files to install diff --git a/icu4c/source/i18n/number_decimalquantity.cpp b/icu4c/source/i18n/number_decimalquantity.cpp index 40ad848ff4..ca91c839a6 100644 --- a/icu4c/source/i18n/number_decimalquantity.cpp +++ b/icu4c/source/i18n/number_decimalquantity.cpp @@ -198,10 +198,18 @@ void DecimalQuantity::roundToIncrement(double roundingIncrement, RoundingMode ro roundToMagnitude(-maxFrac, roundingMode, status); } -void DecimalQuantity::multiplyBy(int32_t multiplicand) { +void DecimalQuantity::multiplyBy(double multiplicand) { if (isInfinite() || isZero() || isNaN()) { return; } + // Cover a few simple cases... + if (multiplicand == 1) { + return; + } else if (multiplicand == -1) { + negate(); + return; + } + // Do math for all other cases... // TODO: Should we convert to decNumber instead? double temp = toDouble(); temp *= multiplicand; diff --git a/icu4c/source/i18n/number_decimalquantity.h b/icu4c/source/i18n/number_decimalquantity.h index b205778e19..4d8bb270d7 100644 --- a/icu4c/source/i18n/number_decimalquantity.h +++ b/icu4c/source/i18n/number_decimalquantity.h @@ -93,7 +93,7 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { * * @param multiplicand The value by which to multiply. */ - void multiplyBy(int32_t multiplicand); + void multiplyBy(double multiplicand); /** Flips the sign from positive to negative and back. */ void negate(); diff --git a/icu4c/source/i18n/number_fluent.cpp b/icu4c/source/i18n/number_fluent.cpp index c5feb5e438..81cf90a847 100644 --- a/icu4c/source/i18n/number_fluent.cpp +++ b/icu4c/source/i18n/number_fluent.cpp @@ -233,6 +233,20 @@ Derived NumberFormatterSettings::decimal(const UNumberDecimalSeparatorD return move; } +template +Derived NumberFormatterSettings::multiplier(const Multiplier& multiplier) const& { + Derived copy(*this); + copy.fMacros.multiplier = multiplier; + return copy; +} + +template +Derived NumberFormatterSettings::multiplier(const Multiplier& multiplier)&& { + Derived move(std::move(*this)); + move.fMacros.multiplier = multiplier; + return move; +} + template Derived NumberFormatterSettings::padding(const Padder& padder) const& { Derived copy(*this); diff --git a/icu4c/source/i18n/number_mapper.cpp b/icu4c/source/i18n/number_mapper.cpp index 8477401101..9acf60382b 100644 --- a/icu4c/source/i18n/number_mapper.cpp +++ b/icu4c/source/i18n/number_mapper.cpp @@ -257,11 +257,7 @@ MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& propert // MULTIPLIERS // ///////////////// - if (properties.magnitudeMultiplier != 0) { - macros.multiplier = Multiplier::magnitude(properties.magnitudeMultiplier); - } else if (properties.multiplier != 1) { - macros.multiplier = Multiplier::integer(properties.multiplier); - } + macros.multiplier = multiplierFromProperties(properties); ////////////////////// // PROPERTY EXPORTS // diff --git a/icu4c/source/i18n/number_multiplier.cpp b/icu4c/source/i18n/number_multiplier.cpp index ca445ba4dd..85fbd9e6ac 100644 --- a/icu4c/source/i18n/number_multiplier.cpp +++ b/icu4c/source/i18n/number_multiplier.cpp @@ -11,37 +11,77 @@ #include "number_types.h" #include "number_multiplier.h" +#include "numparse_validators.h" using namespace icu; using namespace icu::number; using namespace icu::number::impl; +using namespace icu::numparse::impl; -Multiplier::Multiplier(int32_t magnitudeMultiplier, int32_t multiplier) - : magnitudeMultiplier(magnitudeMultiplier), multiplier(multiplier) {} +Multiplier::Multiplier(int32_t magnitude, double arbitrary) + : fMagnitude(magnitude), fArbitrary(arbitrary) {} -Multiplier Multiplier::magnitude(int32_t magnitudeMultiplier) { - return {magnitudeMultiplier, 1}; +Multiplier Multiplier::none() { + return {0, 1}; } -Multiplier Multiplier::integer(int32_t multiplier) { - return {0, multiplier}; +Multiplier Multiplier::powerOfTen(int32_t power) { + return {power, 1}; } - -void MultiplierChain::setAndChain(const Multiplier& multiplier, const MicroPropsGenerator* parent) { - this->multiplier = multiplier; - this->parent = parent; +Multiplier Multiplier::arbitraryDecimal(StringPiece multiplicand) { + // TODO: Fix this hack + UErrorCode localError = U_ZERO_ERROR; + DecimalQuantity dq; + dq.setToDecNumber(multiplicand, localError); + return {0, dq.toDouble()}; } -void -MultiplierChain::processQuantity(DecimalQuantity& quantity, MicroProps& micros, UErrorCode& status) const { - parent->processQuantity(quantity, micros, status); - quantity.adjustMagnitude(multiplier.magnitudeMultiplier); - if (multiplier.multiplier != 1) { - quantity.multiplyBy(multiplier.multiplier); +Multiplier Multiplier::arbitraryDouble(double multiplicand) { + return {0, multiplicand}; +} + +void Multiplier::applyTo(impl::DecimalQuantity& quantity) const { + quantity.adjustMagnitude(fMagnitude); + quantity.multiplyBy(fArbitrary); +} + +void Multiplier::applyReciprocalTo(impl::DecimalQuantity& quantity) const { + quantity.adjustMagnitude(-fMagnitude); + if (fArbitrary != 0) { + quantity.multiplyBy(1 / fArbitrary); } } +void +MultiplierFormatHandler::setAndChain(const Multiplier& multiplier, const MicroPropsGenerator* parent) { + this->multiplier = multiplier; + this->parent = parent; +} + +void MultiplierFormatHandler::processQuantity(DecimalQuantity& quantity, MicroProps& micros, + UErrorCode& status) const { + parent->processQuantity(quantity, micros, status); + multiplier.applyTo(quantity); +} + + +// NOTE: MultiplierParseHandler is declared in the header numparse_validators.h +MultiplierParseHandler::MultiplierParseHandler(::icu::number::Multiplier multiplier) + : fMultiplier(multiplier) {} + +void MultiplierParseHandler::postProcess(ParsedNumber& result) const { + if (!result.quantity.bogus) { + fMultiplier.applyReciprocalTo(result.quantity); + // NOTE: It is okay if the multiplier was negative. + } +} + +UnicodeString MultiplierParseHandler::toString() const { + return u""; +} + + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/number_multiplier.h b/icu4c/source/i18n/number_multiplier.h index 93d103dd84..6baa686591 100644 --- a/icu4c/source/i18n/number_multiplier.h +++ b/icu4c/source/i18n/number_multiplier.h @@ -8,14 +8,18 @@ #define __SOURCE_NUMBER_MULTIPLIER_H__ #include "numparse_types.h" +#include "number_decimfmtprops.h" U_NAMESPACE_BEGIN namespace number { namespace impl { -class MultiplierChain : public MicroPropsGenerator, public UMemory { +/** + * Wraps a {@link Multiplier} for use in the number formatting pipeline. + */ +class MultiplierFormatHandler : public MicroPropsGenerator, public UMemory { public: - void setAndChain(const Multiplier& other, const MicroPropsGenerator* parent); + void setAndChain(const Multiplier& multiplier, const MicroPropsGenerator* parent); void processQuantity(DecimalQuantity& quantity, MicroProps& micros, UErrorCode& status) const U_OVERRIDE; @@ -26,6 +30,18 @@ class MultiplierChain : public MicroPropsGenerator, public UMemory { }; +/** Gets a Multiplier from a DecimalFormatProperties. In Java, defined in RoundingUtils.java */ +static inline Multiplier multiplierFromProperties(const DecimalFormatProperties& properties) { + if (properties.magnitudeMultiplier != 0) { + return Multiplier::powerOfTen(properties.magnitudeMultiplier); + } else if (properties.multiplier != 1) { + return Multiplier::arbitraryDouble(properties.multiplier); + } else { + return Multiplier::none(); + } +} + + } // namespace impl } // namespace number U_NAMESPACE_END diff --git a/icu4c/source/i18n/number_utils.h b/icu4c/source/i18n/number_utils.h index 8f74692cc6..23c6fcf7ec 100644 --- a/icu4c/source/i18n/number_utils.h +++ b/icu4c/source/i18n/number_utils.h @@ -75,7 +75,7 @@ struct MicroProps : public MicroPropsGenerator { ScientificModifier scientificModifier; EmptyModifier emptyWeakModifier{false}; EmptyModifier emptyStrongModifier{true}; - MultiplierChain multiplier; + MultiplierFormatHandler multiplier; } helpers; diff --git a/icu4c/source/i18n/numparse_impl.cpp b/icu4c/source/i18n/numparse_impl.cpp index 89db7001a3..c917aad176 100644 --- a/icu4c/source/i18n/numparse_impl.cpp +++ b/icu4c/source/i18n/numparse_impl.cpp @@ -180,14 +180,10 @@ NumberParserImpl::createParserFromProperties(const number::impl::DecimalFormatPr properties.decimalSeparatorAlwaysShown || properties.maximumFractionDigits != 0; parser->addMatcher(parser->fLocalValidators.decimalSeparator = {patternHasDecimalSeparator}); } - - // TODO: MULTIPLIER -// if (properties.getMultiplier() != null) { -// // We need to use a math context in order to prevent non-terminating decimal expansions. -// // This is only used when dividing by the multiplier. -// parser.addMatcher(new MultiplierHandler(properties.getMultiplier(), -// RoundingUtils.getMathContextOr34Digits(properties))); -// } + // NOTE: Don't look at magnitude multiplier here. That is performed when percent sign is seen. + if (properties.multiplier != 1) { + parser->addMatcher(parser->fLocalValidators.multiplier = {multiplierFromProperties(properties)}); + } parser->freeze(); return parser.orphan(); diff --git a/icu4c/source/i18n/numparse_impl.h b/icu4c/source/i18n/numparse_impl.h index 308a2ffcf8..748b9415ec 100644 --- a/icu4c/source/i18n/numparse_impl.h +++ b/icu4c/source/i18n/numparse_impl.h @@ -17,6 +17,7 @@ #include "number_decimfmtprops.h" #include "unicode/localpointer.h" #include "numparse_validators.h" +#include "number_multiplier.h" U_NAMESPACE_BEGIN namespace numparse { namespace impl { @@ -78,6 +79,7 @@ class NumberParserImpl : public MutableMatcherCollection { RequireDecimalSeparatorValidator decimalSeparator; RequireExponentValidator exponent; RequireNumberValidator number; + MultiplierParseHandler multiplier; } fLocalValidators; explicit NumberParserImpl(parse_flags_t parseFlags); diff --git a/icu4c/source/i18n/numparse_validators.h b/icu4c/source/i18n/numparse_validators.h index 817ec9cb8d..d3bc63aceb 100644 --- a/icu4c/source/i18n/numparse_validators.h +++ b/icu4c/source/i18n/numparse_validators.h @@ -77,6 +77,26 @@ class RequireNumberValidator : public ValidationMatcher, public UMemory { }; +/** + * Wraps a {@link Multiplier} for use in the number parsing pipeline. + * + * NOTE: Implemented in number_multiplier.cpp + */ +class MultiplierParseHandler : public ValidationMatcher, public UMemory { + public: + MultiplierParseHandler() = default; // leaves instance in valid but undefined state + + MultiplierParseHandler(::icu::number::Multiplier multiplier); + + void postProcess(ParsedNumber& result) const U_OVERRIDE; + + UnicodeString toString() const U_OVERRIDE; + + private: + ::icu::number::Multiplier fMultiplier; +}; + + } // namespace impl } // namespace numparse U_NAMESPACE_END diff --git a/icu4c/source/i18n/unicode/numberformatter.h b/icu4c/source/i18n/unicode/numberformatter.h index 51ead000cf..54698d382e 100644 --- a/icu4c/source/i18n/unicode/numberformatter.h +++ b/icu4c/source/i18n/unicode/numberformatter.h @@ -86,6 +86,7 @@ namespace impl { // Forward declarations: class NumberParserImpl; +class MultiplierParseHandler; } } @@ -142,7 +143,7 @@ class NumberStringBuilder; class AffixPatternProvider; class NumberPropertyMapper; struct DecimalFormatProperties; -class MultiplierChain; +class MultiplierFormatHandler; class CurrencySymbols; class GeneratorHelpers; @@ -895,7 +896,6 @@ class U_I18N_API IntegerWidth : public UMemory { * The minimum number of places before the decimal separator. * @return An IntegerWidth for chaining or passing to the NumberFormatter integerWidth() setter. * @draft ICU 60 - * @see NumberFormatter */ static IntegerWidth zeroFillTo(int32_t minInt); @@ -909,7 +909,6 @@ class U_I18N_API IntegerWidth : public UMemory { * truncation. * @return An IntegerWidth for passing to the NumberFormatter integerWidth() setter. * @draft ICU 60 - * @see NumberFormatter */ IntegerWidth truncateAt(int32_t maxInt); @@ -966,6 +965,94 @@ class U_I18N_API IntegerWidth : public UMemory { friend class impl::GeneratorHelpers; }; +/** + * A class that defines a quantity by which a number should be multiplied when formatting. + * + *

    + * To create a Multiplier, use one of the factory methods. + * + * @draft ICU 62 + */ +class U_I18N_API Multiplier : public UMemory { + public: + /** + * Do not change the value of numbers when formatting or parsing. + * + * @return A Multiplier to prevent any multiplication. + * @draft ICU 62 + */ + static Multiplier none(); + + /** + * Multiply numbers by 100 before formatting. Useful for combining with a percent unit: + * + *

    +     * NumberFormatter::with().unit(NoUnit::percent()).multiplier(Multiplier::powerOfTen(2))
    +     * 
    + * + * @return A Multiplier for passing to the setter in NumberFormatter. + * @draft ICU 62 + */ + static Multiplier powerOfTen(int32_t power); + + /** + * Multiply numbers by an arbitrary value before formatting. Useful for unit conversions. + * + * This method takes a string in a decimal number format with syntax + * as defined in the Decimal Arithmetic Specification, available at + * http://speleotrove.com/decimal + * + * Also see the version of this method that takes a double. + * + * @return A Multiplier for passing to the setter in NumberFormatter. + * @draft ICU 62 + */ + static Multiplier arbitraryDecimal(StringPiece multiplicand); + + /** + * Multiply numbers by an arbitrary value before formatting. Useful for unit conversions. + * + * This method takes a double; also see the version of this method that takes an exact decimal. + * + * @return A Multiplier for passing to the setter in NumberFormatter. + * @draft ICU 62 + */ + static Multiplier arbitraryDouble(double multiplicand); + + private: + int32_t fMagnitude; + double fArbitrary; + + Multiplier(int32_t magnitude, double arbitrary); + + Multiplier() : fMagnitude(0), fArbitrary(1) {} + + bool isValid() const { + return fMagnitude != 0 || fArbitrary != 1; + } + + void applyTo(impl::DecimalQuantity& quantity) const; + + void applyReciprocalTo(impl::DecimalQuantity& quantity) const; + + // To allow MacroProps/MicroProps to initialize empty instances: + friend struct impl::MacroProps; + friend struct impl::MicroProps; + + // To allow NumberFormatterImpl to access isBogus() and perform other operations: + friend class impl::NumberFormatterImpl; + + // To allow the helper class MultiplierFormatHandler access to private fields: + friend class impl::MultiplierFormatHandler; + + // To allow access to the skeleton generation code: + friend class impl::GeneratorHelpers; + + // To allow access to parsing code: + friend class ::icu::numparse::impl::NumberParserImpl; + friend class ::icu::numparse::impl::MultiplierParseHandler; +}; + namespace impl { // Do not enclose entire SymbolsWrapper with #ifndef U_HIDE_INTERNAL_API, needed for a protected field @@ -1208,41 +1295,6 @@ class U_I18N_API Padder : public UMemory { }; // Do not enclose entire MacroProps with #ifndef U_HIDE_INTERNAL_API, needed for a protected field -/** @internal */ -class U_I18N_API Multiplier : public UMemory { - public: - /** @internal */ - static Multiplier magnitude(int32_t magnitudeMultiplier); - - /** @internal */ - static Multiplier integer(int32_t multiplier); - - private: - int32_t magnitudeMultiplier; - int32_t multiplier; - - Multiplier(int32_t magnitudeMultiplier, int32_t multiplier); - - Multiplier() : magnitudeMultiplier(0), multiplier(1) {} - - bool isValid() const { - return magnitudeMultiplier != 0 || multiplier != 1; - } - - // To allow MacroProps/MicroProps to initialize empty instances: - friend struct MacroProps; - friend struct MicroProps; - - // To allow NumberFormatterImpl to access isBogus() and perform other operations: - friend class impl::NumberFormatterImpl; - - // To allow the helper class MultiplierChain access to private fields: - friend class impl::MultiplierChain; - - // To allow access to the skeleton generation code: - friend class impl::GeneratorHelpers; -}; - /** @internal */ struct U_I18N_API MacroProps : public UMemory { /** @internal */ @@ -1280,8 +1332,10 @@ struct U_I18N_API MacroProps : public UMemory { /** @internal */ UNumberDecimalSeparatorDisplay decimal = UNUM_DECIMAL_SEPARATOR_COUNT; - Multiplier multiplier; // = Multiplier(); (bogus) + /** @internal */ + Multiplier multiplier; // = Multiplier(); (benign value) + /** @internal */ AffixPatternProvider* affixProvider = nullptr; // no ownership /** @internal */ @@ -1835,11 +1889,48 @@ class U_I18N_API NumberFormatterSettings { * @param style * The decimal separator display strategy to use when rendering numbers. * @return The fluent chain. - * @see #sign + * @see #decimal * @draft ICU 62 */ Derived decimal(const UNumberDecimalSeparatorDisplay &style) &&; + /** + * Sets a multiplier to be used to scale the number by an arbitrary amount before formatting. Most + * common values: + * + *
      + *
    • Multiply by 100: useful for percentages. + *
    • Multiply by an arbitrary value: useful for unit conversions. + *
    + * + *

    + * Pass an element from a {@link Multiplier} factory method to this setter. For example: + * + *

    +     * NumberFormatter::with().multiplier(Multiplier::powerOfTen(2))
    +     * 
    + * + *

    + * The default is to not apply any multiplier. + * + * @param style + * The decimal separator display strategy to use when rendering numbers. + * @return The fluent chain + * @draft ICU 60 + */ + Derived multiplier(const Multiplier &style) const &; + + /** + * Overload of multiplier() for use on an rvalue reference. + * + * @param style + * The multiplier separator display strategy to use when rendering numbers. + * @return The fluent chain. + * @see #multiplier + * @draft ICU 62 + */ + Derived multiplier(const Multiplier &style) &&; + #ifndef U_HIDE_INTERNAL_API /** diff --git a/icu4c/source/test/intltest/numbertest.h b/icu4c/source/test/intltest/numbertest.h index e1a84aab1e..2da93bcbb8 100644 --- a/icu4c/source/test/intltest/numbertest.h +++ b/icu4c/source/test/intltest/numbertest.h @@ -64,6 +64,7 @@ class NumberFormatterApiTest : public IntlTest { //void symbolsOverride(); void sign(); void decimal(); + void multiplier(); void locale(); void formatTypes(); void errors(); diff --git a/icu4c/source/test/intltest/numbertest_api.cpp b/icu4c/source/test/intltest/numbertest_api.cpp index a6d1a3553c..bf65008788 100644 --- a/icu4c/source/test/intltest/numbertest_api.cpp +++ b/icu4c/source/test/intltest/numbertest_api.cpp @@ -77,6 +77,7 @@ void NumberFormatterApiTest::runIndexedTest(int32_t index, UBool exec, const cha //TESTCASE_AUTO(symbolsOverride); TESTCASE_AUTO(sign); TESTCASE_AUTO(decimal); + TESTCASE_AUTO(multiplier); TESTCASE_AUTO(locale); TESTCASE_AUTO(formatTypes); TESTCASE_AUTO(errors); @@ -1909,6 +1910,83 @@ void NumberFormatterApiTest::decimal() { u"0."); } +void NumberFormatterApiTest::multiplier() { + assertFormatDescending( + u"Multiplier None", + u"", + NumberFormatter::with().multiplier(Multiplier::none()), + Locale::getEnglish(), + u"87,650", + u"8,765", + u"876.5", + u"87.65", + u"8.765", + u"0.8765", + u"0.08765", + u"0.008765", + u"0"); + + assertFormatDescending( + u"Multiplier Power of Ten", + nullptr, + NumberFormatter::with().multiplier(Multiplier::powerOfTen(6)), + Locale::getEnglish(), + u"87,650,000,000", + u"8,765,000,000", + u"876,500,000", + u"87,650,000", + u"8,765,000", + u"876,500", + u"87,650", + u"8,765", + u"0"); + + assertFormatDescending( + u"Multiplier Arbitrary Double", + nullptr, + NumberFormatter::with().multiplier(Multiplier::arbitraryDouble(5.2)), + Locale::getEnglish(), + u"455,780", + u"45,578", + u"4,557.8", + u"455.78", + u"45.578", + u"4.5578", + u"0.45578", + u"0.045578", + u"0"); + + assertFormatDescending( + u"Multiplier Arbitrary BigDecimal", + nullptr, + NumberFormatter::with().multiplier(Multiplier::arbitraryDecimal({"5.2", -1})), + Locale::getEnglish(), + u"455,780", + u"45,578", + u"4,557.8", + u"455.78", + u"45.578", + u"4.5578", + u"0.45578", + u"0.045578", + u"0"); + + assertFormatDescending( + u"Multiplier Zero", + nullptr, + NumberFormatter::with().multiplier(Multiplier::arbitraryDouble(0)), + Locale::getEnglish(), + u"0", + u"0", + u"0", + u"0", + u"0", + u"0", + u"0", + u"0", + u"0"); +} + void NumberFormatterApiTest::locale() { // Coverage for the locale setters. UErrorCode status = U_ZERO_ERROR; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MacroProps.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MacroProps.java index fa0f7648ca..47448c2bbd 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MacroProps.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MacroProps.java @@ -4,6 +4,7 @@ package com.ibm.icu.impl.number; import com.ibm.icu.impl.Utility; import com.ibm.icu.number.IntegerWidth; +import com.ibm.icu.number.Multiplier; import com.ibm.icu.number.Notation; import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay; import com.ibm.icu.number.NumberFormatter.SignDisplay; @@ -25,8 +26,8 @@ public class MacroProps implements Cloneable { public UnitWidth unitWidth; public SignDisplay sign; public DecimalSeparatorDisplay decimal; + public Multiplier multiplier; public AffixPatternProvider affixProvider; // not in API; for JDK compatibility mode only - public MultiplierImpl multiplier; // not in API; for JDK compatibility mode only public PluralRules rules; // not in API; could be made public in the future public Long threshold; // not in API; controls internal self-regulation threshold public ULocale loc; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MultiplierFormatHandler.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MultiplierFormatHandler.java new file mode 100644 index 0000000000..fb7389a631 --- /dev/null +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MultiplierFormatHandler.java @@ -0,0 +1,25 @@ +// © 2017 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html#License +package com.ibm.icu.impl.number; + +import com.ibm.icu.number.Multiplier; + +/** + * Wraps a {@link Multiplier} for use in the number formatting pipeline. + */ +public class MultiplierFormatHandler implements MicroPropsGenerator { + final Multiplier multiplier; + final MicroPropsGenerator parent; + + public MultiplierFormatHandler(Multiplier multiplier, MicroPropsGenerator parent) { + this.multiplier = multiplier; + this.parent = parent; + } + + @Override + public MicroProps processQuantity(DecimalQuantity quantity) { + MicroProps micros = parent.processQuantity(quantity); + multiplier.applyTo(quantity); + return micros; + } +} diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MultiplierImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MultiplierImpl.java deleted file mode 100644 index dd495d1a79..0000000000 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MultiplierImpl.java +++ /dev/null @@ -1,43 +0,0 @@ -// © 2017 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html#License -package com.ibm.icu.impl.number; - -import java.math.BigDecimal; - -public class MultiplierImpl implements MicroPropsGenerator { - final int magnitudeMultiplier; - final BigDecimal bigDecimalMultiplier; - final MicroPropsGenerator parent; - - public MultiplierImpl(int magnitudeMultiplier) { - this.magnitudeMultiplier = magnitudeMultiplier; - this.bigDecimalMultiplier = null; - parent = null; - } - - public MultiplierImpl(BigDecimal bigDecimalMultiplier) { - this.magnitudeMultiplier = 0; - this.bigDecimalMultiplier = bigDecimalMultiplier; - parent = null; - } - - private MultiplierImpl(MultiplierImpl base, MicroPropsGenerator parent) { - this.magnitudeMultiplier = base.magnitudeMultiplier; - this.bigDecimalMultiplier = base.bigDecimalMultiplier; - this.parent = parent; - } - - public MicroPropsGenerator copyAndChain(MicroPropsGenerator parent) { - return new MultiplierImpl(this, parent); - } - - @Override - public MicroProps processQuantity(DecimalQuantity quantity) { - MicroProps micros = parent.processQuantity(quantity); - quantity.adjustMagnitude(magnitudeMultiplier); - if (bigDecimalMultiplier != null) { - quantity.multiplyBy(bigDecimalMultiplier); - } - return micros; - } -} diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/RoundingUtils.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/RoundingUtils.java index b9a3cdb6da..7d9ca686c3 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/RoundingUtils.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/RoundingUtils.java @@ -6,6 +6,8 @@ import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; +import com.ibm.icu.number.Multiplier; + /** @author sffc */ public class RoundingUtils { @@ -147,6 +149,14 @@ public class RoundingUtils { } } + /** The default MathContext, unlimited-precision version. */ + public static final MathContext DEFAULT_MATH_CONTEXT_UNLIMITED + = MATH_CONTEXT_BY_ROUNDING_MODE_UNLIMITED[DEFAULT_ROUNDING_MODE.ordinal()]; + + /** The default MathContext, 34-digit version. */ + public static final MathContext DEFAULT_MATH_CONTEXT_34_DIGITS + = MATH_CONTEXT_BY_ROUNDING_MODE_34_DIGITS[DEFAULT_ROUNDING_MODE.ordinal()]; + /** * Gets the user-specified math context out of the property bag. If there is none, falls back to a * math context with unlimited precision and the user-specified rounding mode, which defaults to @@ -198,4 +208,15 @@ public class RoundingUtils { public static MathContext mathContextUnlimited(RoundingMode roundingMode) { return MATH_CONTEXT_BY_ROUNDING_MODE_UNLIMITED[roundingMode.ordinal()]; } + + public static Multiplier multiplierFromProperties(DecimalFormatProperties properties) { + MathContext mc = getMathContextOr34Digits(properties); + if (properties.getMagnitudeMultiplier() != 0) { + return Multiplier.powerOfTen(properties.getMagnitudeMultiplier()).withMathContext(mc); + } else if (properties.getMultiplier() != null) { + return Multiplier.arbitrary(properties.getMultiplier()).withMathContext(mc); + } else { + return null; + } + } } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/MultiplierHandler.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/MultiplierHandler.java deleted file mode 100644 index e2c225084c..0000000000 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/MultiplierHandler.java +++ /dev/null @@ -1,39 +0,0 @@ -// © 2017 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html#License -package com.ibm.icu.impl.number.parse; - -import java.math.BigDecimal; -import java.math.MathContext; - -/** - * @author sffc - * - */ -public class MultiplierHandler extends ValidationMatcher { - - private final BigDecimal multiplier; - private final MathContext mc; - private final boolean isNegative; - - public MultiplierHandler(BigDecimal multiplier, MathContext mc) { - this.multiplier = BigDecimal.ONE.divide(multiplier, mc).abs(); - this.mc = mc; - isNegative = multiplier.signum() < 0; - } - - @Override - public void postProcess(ParsedNumber result) { - if (result.quantity != null) { - result.quantity.multiplyBy(multiplier); - result.quantity.roundToMagnitude(result.quantity.getMagnitude() - mc.getPrecision(), mc); - if (isNegative) { - result.flags ^= ParsedNumber.FLAG_NEGATIVE; - } - } - } - - @Override - public String toString() { - return ""; - } -} diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/MultiplierParseHandler.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/MultiplierParseHandler.java new file mode 100644 index 0000000000..054cdf41c4 --- /dev/null +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/MultiplierParseHandler.java @@ -0,0 +1,30 @@ +// © 2017 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html#License +package com.ibm.icu.impl.number.parse; + +import com.ibm.icu.number.Multiplier; + +/** + * Wraps a {@link Multiplier} for use in the number parsing pipeline. + */ +public class MultiplierParseHandler extends ValidationMatcher { + + private final Multiplier multiplier; + + public MultiplierParseHandler(Multiplier multiplier) { + this.multiplier = multiplier; + } + + @Override + public void postProcess(ParsedNumber result) { + if (result.quantity != null) { + multiplier.applyReciprocalTo(result.quantity); + // NOTE: It is okay if the multiplier was negative. + } + } + + @Override + public String toString() { + return ""; + } +} diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java index 9283523275..b5fac76526 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java @@ -230,11 +230,10 @@ public class NumberParserImpl { || properties.getMaximumFractionDigits() != 0; parser.addMatcher(RequireDecimalSeparatorValidator.getInstance(patternHasDecimalSeparator)); } + // NOTE: Don't look at magnitude multiplier here. That is performed when percent sign is seen. if (properties.getMultiplier() != null) { - // We need to use a math context in order to prevent non-terminating decimal expansions. - // This is only used when dividing by the multiplier. - parser.addMatcher(new MultiplierHandler(properties.getMultiplier(), - RoundingUtils.getMathContextOr34Digits(properties))); + parser.addMatcher( + new MultiplierParseHandler(RoundingUtils.multiplierFromProperties(properties))); } parser.freeze(); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/Multiplier.java b/icu4j/main/classes/core/src/com/ibm/icu/number/Multiplier.java new file mode 100644 index 0000000000..d048221f5c --- /dev/null +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/Multiplier.java @@ -0,0 +1,171 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html#License +package com.ibm.icu.number; + +import java.math.BigDecimal; +import java.math.MathContext; + +import com.ibm.icu.impl.number.DecimalQuantity; +import com.ibm.icu.impl.number.RoundingUtils; + +/** + * A class that defines a quantity by which a number should be multiplied when formatting. + * + *

    + * To create a Multiplier, use one of the factory methods. + * + * @draft ICU 62 + * @provisional This API might change or be removed in a future release. + * @see NumberFormatter + */ +public class Multiplier { + + private static final Multiplier DEFAULT = new Multiplier(0, null); + private static final Multiplier HUNDRED = new Multiplier(2, null); + private static final Multiplier THOUSAND = new Multiplier(3, null); + + private static final BigDecimal BIG_DECIMAL_100 = BigDecimal.valueOf(100); + private static final BigDecimal BIG_DECIMAL_1000 = BigDecimal.valueOf(1000); + + final int magnitude; + final BigDecimal arbitrary; + final BigDecimal reciprocal; + final MathContext mc; + + private Multiplier(int magnitude, BigDecimal arbitrary) { + this(magnitude, arbitrary, RoundingUtils.DEFAULT_MATH_CONTEXT_34_DIGITS); + } + + private Multiplier(int magnitude, BigDecimal arbitrary, MathContext mc) { + this.magnitude = magnitude; + this.arbitrary = arbitrary; + this.mc = mc; + // We need to use a math context in order to prevent non-terminating decimal expansions. + // This is only used when dividing by the multiplier. + if (arbitrary != null && BigDecimal.ZERO.compareTo(arbitrary) != 0) { + this.reciprocal = BigDecimal.ONE.divide(arbitrary, mc); + } else { + this.reciprocal = null; + } + } + + /** + * Do not change the value of numbers when formatting or parsing. + * + * @return A Multiplier to prevent any multiplication. + * @draft ICU 62 + * @provisional This API might change or be removed in a future release. + * @see NumberFormatter + */ + public static Multiplier none() { + return DEFAULT; + } + + /** + * Multiply numbers by 100 before formatting. Useful for combining with a percent unit: + *

    + * + *

    +     * NumberFormatter.with().unit(NoUnit.PERCENT).multiplier(Multiplier.powerOfTen(2))
    +     * 
    + * + * @return A Multiplier for passing to the setter in NumberFormatter. + * @draft ICU 62 + * @provisional This API might change or be removed in a future release. + * @see NumberFormatter + */ + public static Multiplier powerOfTen(int power) { + if (power == 0) { + return DEFAULT; + } else if (power == 2) { + return HUNDRED; + } else if (power == 3) { + return THOUSAND; + } else { + return new Multiplier(power, null); + } + } + + /** + * Multiply numbers by an arbitrary value before formatting. Useful for unit conversions. + *

    + * This method takes a BigDecimal; also see the version that takes a double. + * + * @return A Multiplier for passing to the setter in NumberFormatter. + * @draft ICU 62 + * @provisional This API might change or be removed in a future release. + * @see NumberFormatter + */ + public static Multiplier arbitrary(BigDecimal multiplicand) { + if (multiplicand.compareTo(BigDecimal.ONE) == 0) { + return DEFAULT; + } else if (multiplicand.compareTo(BIG_DECIMAL_100) == 0) { + return HUNDRED; + } else if (multiplicand.compareTo(BIG_DECIMAL_1000) == 0) { + return THOUSAND; + } else { + return new Multiplier(0, multiplicand); + } + } + + /** + * Multiply numbers by an arbitrary value before formatting. Useful for unit conversions. + *

    + * This method takes a double; also see the version that takes a BigDecimal. + * + * @return A Multiplier for passing to the setter in NumberFormatter. + * @draft ICU 62 + * @provisional This API might change or be removed in a future release. + * @see NumberFormatter + */ + public static Multiplier arbitrary(double multiplicand) { + if (multiplicand == 1) { + return DEFAULT; + } else if (multiplicand == 100.0) { + return HUNDRED; + } else if (multiplicand == 1000.0) { + return THOUSAND; + } else { + return new Multiplier(0, BigDecimal.valueOf(multiplicand)); + } + } + + /** + * @internal + * @deprecated ICU 62 This API is ICU internal only. + */ + @Deprecated + public Multiplier withMathContext(MathContext mc) { + // TODO: Make this public? + if (this.mc.equals(mc)) { + return this; + } + return new Multiplier(magnitude, arbitrary, mc); + } + + /** + * @internal + * @deprecated ICU 62 This API is ICU internal only. + */ + @Deprecated + public void applyTo(DecimalQuantity quantity) { + quantity.adjustMagnitude(magnitude); + if (arbitrary != null) { + quantity.multiplyBy(arbitrary); + } + } + + /** + * @internal + * @deprecated ICU 62 This API is ICU internal only. + */ + @Deprecated + public void applyReciprocalTo(DecimalQuantity quantity) { + quantity.adjustMagnitude(-magnitude); + if (reciprocal != null) { + quantity.multiplyBy(reciprocal); + quantity.roundToMagnitude(quantity.getMagnitude() - mc.getPrecision(), mc); + } + } + +} 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 e8d8d238b1..0ecca0e813 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 @@ -12,6 +12,7 @@ import com.ibm.icu.impl.number.LongNameHandler; import com.ibm.icu.impl.number.MacroProps; import com.ibm.icu.impl.number.MicroProps; import com.ibm.icu.impl.number.MicroPropsGenerator; +import com.ibm.icu.impl.number.MultiplierFormatHandler; import com.ibm.icu.impl.number.MutablePatternModifier; import com.ibm.icu.impl.number.NumberStringBuilder; import com.ibm.icu.impl.number.Padder; @@ -183,7 +184,7 @@ class NumberFormatterImpl { // Multiplier (compatibility mode value). if (macros.multiplier != null) { - chain = macros.multiplier.copyAndChain(chain); + chain = new MultiplierFormatHandler(macros.multiplier, chain); } // Rounding strategy diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java index 88033d70a3..5fb86d5a10 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java @@ -39,9 +39,10 @@ public abstract class NumberFormatterSettings parent; final int key; @@ -441,6 +442,36 @@ public abstract class NumberFormatterSettings + *

  • Multiply by 100: useful for percentages. + *
  • Multiply by an arbitrary value: useful for unit conversions. + *
+ * + *

+ * Pass an element from a {@link Multiplier} factory method to this setter. For example: + * + *

+     * NumberFormatter.with().multiplier(Multiplier.powerOfTen(2))
+     * 
+ * + *

+ * The default is to not apply any multiplier. + * + * @param multiplier + * An amount to be multiplied against numbers before formatting. + * @return The fluent chain + * @see Multiplier + * @draft ICU 62 + * @provisional This API might change or be removed in a future release. + */ + public T multiplier(Multiplier multiplier) { + return create(KEY_MULTIPLIER, multiplier); + } + /** * Internal method to set a starting macros. * @@ -568,6 +599,11 @@ public abstract class NumberFormatterSettings Date: Wed, 4 Apr 2018 01:26:18 +0000 Subject: [PATCH 077/129] ICU-13678 Adding multiplier to skeleton string syntax. X-SVN-Rev: 41193 --- icu4c/source/i18n/number_decimalquantity.cpp | 3 + icu4c/source/i18n/number_skeletons.cpp | 59 +++++++++++++++++-- icu4c/source/i18n/number_skeletons.h | 9 +++ icu4c/source/test/intltest/numbertest_api.cpp | 26 ++++++-- .../test/intltest/numbertest_skeletons.cpp | 17 +++++- .../number/DecimalQuantity_AbstractBCD.java | 3 + .../src/com/ibm/icu/number/Multiplier.java | 18 ++++++ .../ibm/icu/number/NumberSkeletonImpl.java | 49 +++++++++++++-- .../test/number/NumberFormatterApiTest.java | 26 ++++++-- .../dev/test/number/NumberSkeletonTest.java | 17 +++++- 10 files changed, 207 insertions(+), 20 deletions(-) diff --git a/icu4c/source/i18n/number_decimalquantity.cpp b/icu4c/source/i18n/number_decimalquantity.cpp index ca91c839a6..f092bc10e4 100644 --- a/icu4c/source/i18n/number_decimalquantity.cpp +++ b/icu4c/source/i18n/number_decimalquantity.cpp @@ -757,6 +757,9 @@ UnicodeString DecimalQuantity::toPlainString() const { if (isNegative()) { sb.append(u'-'); } + if (precision == 0) { + sb.append(u'0'); + } for (int m = getUpperDisplayMagnitude(); m >= getLowerDisplayMagnitude(); m--) { if (m == -1) { sb.append(u'.'); } sb.append(getDigit(m) + u'0'); diff --git a/icu4c/source/i18n/number_skeletons.cpp b/icu4c/source/i18n/number_skeletons.cpp index 444471ea3b..0976cb5d5b 100644 --- a/icu4c/source/i18n/number_skeletons.cpp +++ b/icu4c/source/i18n/number_skeletons.cpp @@ -85,6 +85,7 @@ void U_CALLCONV initNumberSkeletons(UErrorCode& status) { b.add(u"currency", STEM_CURRENCY, status); b.add(u"integer-width", STEM_INTEGER_WIDTH, status); b.add(u"numbering-system", STEM_NUMBERING_SYSTEM, status); + b.add(u"multiply", STEM_MULTIPLY, status); if (U_FAILURE(status)) { return; } // Build the CharsTrie @@ -443,6 +444,7 @@ MacroProps skeleton::parseSkeleton(const UnicodeString& skeletonString, UErrorCo case STATE_CURRENCY_UNIT: case STATE_INTEGER_WIDTH: case STATE_NUMBERING_SYSTEM: + case STATE_MULTIPLY: // segment.setLength(U16_LENGTH(cp)); // for error message // throw new SkeletonSyntaxException("Stem requires an option", segment); status = U_NUMBER_SKELETON_SYNTAX_ERROR; @@ -592,6 +594,10 @@ skeleton::parseStem(const StringSegment& segment, const UCharsTrie& stemTrie, Se CHECK_NULL(seen, symbols, status); return STATE_NUMBERING_SYSTEM; + case STEM_MULTIPLY: + CHECK_NULL(seen, multiplier, status); + return STATE_MULTIPLY; + default: U_ASSERT(false); } @@ -621,6 +627,9 @@ ParseState skeleton::parseOption(ParseState stem, const StringSegment& segment, case STATE_NUMBERING_SYSTEM: blueprint_helpers::parseNumberingSystemOption(segment, macros, status); return STATE_NULL; + case STATE_MULTIPLY: + blueprint_helpers::parseMultiplierOption(segment, macros, status); + return STATE_NULL; default: break; } @@ -712,6 +721,10 @@ void GeneratorHelpers::generateSkeleton(const MacroProps& macros, UnicodeString& sb.append(u' '); } if (U_FAILURE(status)) { return; } + if (GeneratorHelpers::multiplier(macros, sb, status)) { + sb.append(u' '); + } + if (U_FAILURE(status)) { return; } // Unsupported options if (!macros.padder.isBogus()) { @@ -722,10 +735,6 @@ void GeneratorHelpers::generateSkeleton(const MacroProps& macros, UnicodeString& status = U_UNSUPPORTED_ERROR; return; } - if (macros.multiplier.isValid()) { - status = U_UNSUPPORTED_ERROR; - return; - } if (macros.rules != nullptr) { status = U_UNSUPPORTED_ERROR; return; @@ -1175,6 +1184,35 @@ void blueprint_helpers::generateNumberingSystemOption(const NumberingSystem& ns, sb.append(UnicodeString(ns.getName(), -1, US_INV)); } +void blueprint_helpers::parseMultiplierOption(const StringSegment& segment, MacroProps& macros, + UErrorCode& status) { + // Need to do char <-> UChar conversion... + CharString buffer; + SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status); + + // Utilize DecimalQuantity/decNumber to parse this for us. + // TODO: Parse to a DecNumber directly. + DecimalQuantity dq; + UErrorCode localStatus = U_ZERO_ERROR; + dq.setToDecNumber({buffer.data(), buffer.length()}, localStatus); + if (U_FAILURE(localStatus)) { + // throw new SkeletonSyntaxException("Invalid rounding increment", segment, e); + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return; + } + macros.multiplier = Multiplier::arbitraryDouble(dq.toDouble()); +} + +void blueprint_helpers::generateMultiplierOption(int32_t magnitude, double arbitrary, UnicodeString& sb, + UErrorCode&) { + // Utilize DecimalQuantity/double_conversion to format this for us. + DecimalQuantity dq; + dq.setToDouble(arbitrary); + dq.adjustMagnitude(magnitude); + dq.roundToInfinity(); + sb.append(dq.toPlainString()); +} + bool GeneratorHelpers::notation(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { if (macros.notation.fType == Notation::NTN_COMPACT) { @@ -1376,5 +1414,18 @@ bool GeneratorHelpers::decimal(const MacroProps& macros, UnicodeString& sb, UErr return true; } +bool GeneratorHelpers::multiplier(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { + if (!macros.multiplier.isValid()) { + return false; // Default or Bogus + } + sb.append(u"multiply/", -1); + blueprint_helpers::generateMultiplierOption( + macros.multiplier.fMagnitude, + macros.multiplier.fArbitrary, + sb, + status); + return true; +} + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/number_skeletons.h b/icu4c/source/i18n/number_skeletons.h index 14f1bdbe70..7954b99f2b 100644 --- a/icu4c/source/i18n/number_skeletons.h +++ b/icu4c/source/i18n/number_skeletons.h @@ -45,6 +45,7 @@ enum ParseState { STATE_CURRENCY_UNIT, STATE_INTEGER_WIDTH, STATE_NUMBERING_SYSTEM, + STATE_MULTIPLY, }; /** @@ -99,6 +100,7 @@ enum StemEnum { STEM_CURRENCY, STEM_INTEGER_WIDTH, STEM_NUMBERING_SYSTEM, + STEM_MULTIPLY, }; /** @@ -236,6 +238,10 @@ void parseNumberingSystemOption(const StringSegment& segment, MacroProps& macros void generateNumberingSystemOption(const NumberingSystem& ns, UnicodeString& sb, UErrorCode& status); +void parseMultiplierOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status); + +void generateMultiplierOption(int32_t magnitude, double arbitrary, UnicodeString& sb, UErrorCode& status); + } // namespace blueprint_helpers /** @@ -275,6 +281,8 @@ class GeneratorHelpers { static bool decimal(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + static bool multiplier(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + }; /** @@ -293,6 +301,7 @@ struct SeenMacroProps { bool unitWidth = false; bool sign = false; bool decimal = false; + bool multiplier = false; }; } // namespace impl diff --git a/icu4c/source/test/intltest/numbertest_api.cpp b/icu4c/source/test/intltest/numbertest_api.cpp index bf65008788..947e970d6d 100644 --- a/icu4c/source/test/intltest/numbertest_api.cpp +++ b/icu4c/source/test/intltest/numbertest_api.cpp @@ -1913,7 +1913,7 @@ void NumberFormatterApiTest::decimal() { void NumberFormatterApiTest::multiplier() { assertFormatDescending( u"Multiplier None", - u"", + u"multiply/1", NumberFormatter::with().multiplier(Multiplier::none()), Locale::getEnglish(), u"87,650", @@ -1928,7 +1928,7 @@ void NumberFormatterApiTest::multiplier() { assertFormatDescending( u"Multiplier Power of Ten", - nullptr, + u"multiply/1000000", NumberFormatter::with().multiplier(Multiplier::powerOfTen(6)), Locale::getEnglish(), u"87,650,000,000", @@ -1943,7 +1943,7 @@ void NumberFormatterApiTest::multiplier() { assertFormatDescending( u"Multiplier Arbitrary Double", - nullptr, + u"multiply/5.2", NumberFormatter::with().multiplier(Multiplier::arbitraryDouble(5.2)), Locale::getEnglish(), u"455,780", @@ -1958,7 +1958,7 @@ void NumberFormatterApiTest::multiplier() { assertFormatDescending( u"Multiplier Arbitrary BigDecimal", - nullptr, + u"multiply/5.2", NumberFormatter::with().multiplier(Multiplier::arbitraryDecimal({"5.2", -1})), Locale::getEnglish(), u"455,780", @@ -1973,7 +1973,7 @@ void NumberFormatterApiTest::multiplier() { assertFormatDescending( u"Multiplier Zero", - nullptr, + u"multiply/0", NumberFormatter::with().multiplier(Multiplier::arbitraryDouble(0)), Locale::getEnglish(), u"0", @@ -1985,6 +1985,22 @@ void NumberFormatterApiTest::multiplier() { u"0", u"0", u"0"); + + assertFormatSingle( + u"Multiplier Skeleton Scientific Notation and Percent", + u"percent multiply/1E2", + NumberFormatter::with().unit(NoUnit::percent()).multiplier(Multiplier::powerOfTen(2)), + Locale::getEnglish(), + 0.5, + u"50%"); + + assertFormatSingle( + u"Negative Multiplier", + u"multiply/-5.2", + NumberFormatter::with().multiplier(Multiplier::arbitraryDouble(-5.2)), + Locale::getEnglish(), + 2, + u"-10.4"); } void NumberFormatterApiTest::locale() { diff --git a/icu4c/source/test/intltest/numbertest_skeletons.cpp b/icu4c/source/test/intltest/numbertest_skeletons.cpp index 29d09f1a26..68138b9b08 100644 --- a/icu4c/source/test/intltest/numbertest_skeletons.cpp +++ b/icu4c/source/test/intltest/numbertest_skeletons.cpp @@ -90,6 +90,11 @@ void NumberSkeletonTest::validTokens() { u"unit-width-hidden", u"decimal-auto", u"decimal-always", + u"multiply/5.2", + u"multiply/-5.2", + u"multiply/100", + u"multiply/1E2", + u"multiply/1", u"latin", u"numbering-system/arab", u"numbering-system/latn", @@ -126,6 +131,9 @@ void NumberSkeletonTest::invalidTokens() { u"scientific/ee", u"round-increment/xxx", u"round-increment/0.1.2", + u"multiply/xxx", + u"multiply/0.1.2", + u"multiply/français", // non-invariant characters for C++ u"currency/dummy", u"measure-unit/foo", u"integer-width/xxx", @@ -174,7 +182,14 @@ void NumberSkeletonTest::duplicateValues() { } void NumberSkeletonTest::stemsRequiringOption() { - static const char16_t* stems[] = {u"round-increment", u"currency", u"measure-unit", u"integer-width",}; + static const char16_t* stems[] = { + u"round-increment", + u"measure-unit", + u"per-unit", + u"currency", + u"integer-width", + u"numbering-system", + u"multiply"}; static const char16_t* suffixes[] = {u"", u"/ceiling", u" scientific", u"/ceiling scientific"}; for (auto& stem : stems) { 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 1366f0a411..7a87d856e9 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 @@ -946,6 +946,9 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { if (isNegative()) { sb.append('-'); } + if (precision == 0) { + sb.append('0'); + } for (int m = getUpperDisplayMagnitude(); m >= getLowerDisplayMagnitude(); m--) { sb.append(getDigit(m)); if (m == 0) diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/Multiplier.java b/icu4j/main/classes/core/src/com/ibm/icu/number/Multiplier.java index d048221f5c..a40850ae19 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/Multiplier.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/Multiplier.java @@ -3,6 +3,7 @@ package com.ibm.icu.number; import java.math.BigDecimal; +import java.math.BigInteger; import java.math.MathContext; import com.ibm.icu.impl.number.DecimalQuantity; @@ -37,6 +38,16 @@ public class Multiplier { } private Multiplier(int magnitude, BigDecimal arbitrary, MathContext mc) { + if (arbitrary != null) { + // Attempt to convert the BigDecimal to a magnitude multiplier. + arbitrary = arbitrary.stripTrailingZeros(); + if (arbitrary.precision() == 1 && arbitrary.unscaledValue().equals(BigInteger.ONE)) { + // Success! + magnitude = -arbitrary.scale(); + arbitrary = null; + } + } + this.magnitude = magnitude; this.arbitrary = arbitrary; this.mc = mc; @@ -130,6 +141,13 @@ public class Multiplier { } } + /** + * Returns whether the multiplier will change the number. + */ + boolean isValid() { + return magnitude != 0 || arbitrary != null; + } + /** * @internal * @deprecated ICU 62 This API is ICU internal only. 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 055bcc8f82..70df1dbcd1 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 @@ -52,6 +52,7 @@ class NumberSkeletonImpl { STATE_CURRENCY_UNIT, STATE_INTEGER_WIDTH, STATE_NUMBERING_SYSTEM, + STATE_MULTIPLY, } /** @@ -103,6 +104,7 @@ class NumberSkeletonImpl { STEM_CURRENCY, STEM_INTEGER_WIDTH, STEM_NUMBERING_SYSTEM, + STEM_MULTIPLY, }; /** For mapping from ordinal back to StemEnum in Java. */ @@ -155,6 +157,7 @@ class NumberSkeletonImpl { b.add("currency", StemEnum.STEM_CURRENCY.ordinal()); b.add("integer-width", StemEnum.STEM_INTEGER_WIDTH.ordinal()); b.add("numbering-system", StemEnum.STEM_NUMBERING_SYSTEM.ordinal()); + b.add("multiply", StemEnum.STEM_MULTIPLY.ordinal()); // Build the CharsTrie // TODO: Use SLOW or FAST here? @@ -506,6 +509,7 @@ class NumberSkeletonImpl { case STATE_CURRENCY_UNIT: case STATE_INTEGER_WIDTH: case STATE_NUMBERING_SYSTEM: + case STATE_MULTIPLY: segment.setLength(Character.charCount(cp)); // for error message throw new SkeletonSyntaxException("Stem requires an option", segment); default: @@ -653,6 +657,10 @@ class NumberSkeletonImpl { checkNull(macros.symbols, segment); return ParseState.STATE_NUMBERING_SYSTEM; + case STEM_MULTIPLY: + checkNull(macros.multiplier, segment); + return ParseState.STATE_MULTIPLY; + default: throw new AssertionError(); } @@ -687,6 +695,9 @@ class NumberSkeletonImpl { case STATE_NUMBERING_SYSTEM: BlueprintHelpers.parseNumberingSystemOption(segment, macros); return ParseState.STATE_NULL; + case STATE_MULTIPLY: + BlueprintHelpers.parseMultiplierOption(segment, macros); + return ParseState.STATE_NULL; default: break; } @@ -772,6 +783,9 @@ class NumberSkeletonImpl { if (macros.decimal != null && GeneratorHelpers.decimal(macros, sb)) { sb.append(' '); } + if (macros.multiplier != null && GeneratorHelpers.multiplier(macros, sb)) { + sb.append(' '); + } // Unsupported options if (macros.padder != null) { @@ -782,10 +796,6 @@ class NumberSkeletonImpl { throw new UnsupportedOperationException( "Cannot generate number skeleton with custom affix provider"); } - if (macros.multiplier != null) { - throw new UnsupportedOperationException( - "Cannot generate number skeleton with custom multiplier"); - } if (macros.rules != null) { throw new UnsupportedOperationException( "Cannot generate number skeleton with custom plural rules"); @@ -1152,6 +1162,28 @@ class NumberSkeletonImpl { private static void generateNumberingSystemOption(NumberingSystem ns, StringBuilder sb) { sb.append(ns.getName()); } + + private static void parseMultiplierOption(StringSegment segment, MacroProps macros) { + // Call segment.subSequence() because segment.toString() doesn't create a clean string. + String str = segment.subSequence(0, segment.length()).toString(); + BigDecimal bd; + try { + bd = new BigDecimal(str); + } catch (NumberFormatException e) { + throw new SkeletonSyntaxException("Invalid multiplier", segment, e); + } + // NOTE: If bd is a power of ten, the Multiplier API optimizes it for us. + macros.multiplier = Multiplier.arbitrary(bd); + } + + private static void generateMultiplierOption(Multiplier multiplier, StringBuilder sb) { + BigDecimal bd = multiplier.arbitrary; + if (bd == null) { + bd = BigDecimal.ONE; + } + bd = bd.scaleByPowerOfTen(multiplier.magnitude); + sb.append(bd.toPlainString()); + } } ///// STEM GENERATION HELPER FUNCTIONS ///// @@ -1343,6 +1375,15 @@ class NumberSkeletonImpl { return true; } + private static boolean multiplier(MacroProps macros, StringBuilder sb) { + if (!macros.multiplier.isValid()) { + return false; // Default value + } + sb.append("multiply/"); + BlueprintHelpers.generateMultiplierOption(macros.multiplier, sb); + return true; + } + } ///// OTHER UTILITY FUNCTIONS ///// 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 77291c929e..ac0a00f6d1 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 @@ -1887,7 +1887,7 @@ public class NumberFormatterApiTest { public void multiplier() { assertFormatDescending( "Multiplier None", - null, + "multiply/1", NumberFormatter.with().multiplier(Multiplier.none()), ULocale.ENGLISH, "87,650", @@ -1902,7 +1902,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Multiplier Power of Ten", - null, + "multiply/1000000", NumberFormatter.with().multiplier(Multiplier.powerOfTen(6)), ULocale.ENGLISH, "87,650,000,000", @@ -1917,7 +1917,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Multiplier Arbitrary Double", - null, + "multiply/5.2", NumberFormatter.with().multiplier(Multiplier.arbitrary(5.2)), ULocale.ENGLISH, "455,780", @@ -1932,7 +1932,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Multiplier Arbitrary BigDecimal", - null, + "multiply/5.2", NumberFormatter.with().multiplier(Multiplier.arbitrary(new BigDecimal("5.2"))), ULocale.ENGLISH, "455,780", @@ -1947,7 +1947,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Multiplier Zero", - null, + "multiply/0", NumberFormatter.with().multiplier(Multiplier.arbitrary(0)), ULocale.ENGLISH, "0", @@ -1959,6 +1959,22 @@ public class NumberFormatterApiTest { "0", "0", "0"); + + assertFormatSingle( + "Multiplier Skeleton Scientific Notation and Percent", + "percent multiply/1E2", + NumberFormatter.with().unit(NoUnit.PERCENT).multiplier(Multiplier.powerOfTen(2)), + ULocale.ENGLISH, + 0.5, + "50%"); + + assertFormatSingle( + "Negative Multiplier", + "multiply/-5.2", + NumberFormatter.with().multiplier(Multiplier.arbitrary(-5.2)), + ULocale.ENGLISH, + 2, + "-10.4"); } @Test 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 cd260d54a8..5e88476532 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 @@ -82,6 +82,11 @@ public class NumberSkeletonTest { "unit-width-hidden", "decimal-auto", "decimal-always", + "multiply/5.2", + "multiply/-5.2", + "multiply/100", + "multiply/1E2", + "multiply/1", "latin", "numbering-system/arab", "numbering-system/latn", @@ -120,6 +125,9 @@ public class NumberSkeletonTest { "scientific/ee", "round-increment/xxx", "round-increment/0.1.2", + "multiply/xxx", + "multiply/0.1.2", + "multiply/français", // non-invariant characters for C++ "currency/dummy", "measure-unit/foo", "integer-width/xxx", @@ -200,7 +208,14 @@ public class NumberSkeletonTest { @Test public void stemsRequiringOption() { - String[] stems = { "round-increment", "currency", "measure-unit", "integer-width", }; + String[] stems = { + "round-increment", + "measure-unit", + "per-unit", + "currency", + "integer-width", + "numbering-system", + "multiply" }; String[] suffixes = { "", "/ceiling", " scientific", "/ceiling scientific" }; for (String stem : stems) { From fe0725cd2ae9cc5509be39191420af204205fc78 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Thu, 5 Apr 2018 21:54:04 +0000 Subject: [PATCH 078/129] ICU-13678 Changing Multiplier to use decNumber instead of double, in order to fix some unit tests. Refactored call sites to use a common DecNum wrapper class with constructors for string, double, and BCD. X-SVN-Rev: 41198 --- icu4c/source/i18n/Makefile.in | 2 +- icu4c/source/i18n/fmtable.cpp | 11 +- icu4c/source/i18n/nfsubs.cpp | 1 + icu4c/source/i18n/number_decimalquantity.cpp | 132 ++++++------- icu4c/source/i18n/number_decimalquantity.h | 26 ++- icu4c/source/i18n/number_fluent.cpp | 3 + icu4c/source/i18n/number_multiplier.cpp | 98 +++++++-- icu4c/source/i18n/number_rounding.cpp | 1 + icu4c/source/i18n/number_skeletons.cpp | 26 +-- icu4c/source/i18n/number_skeletons.h | 8 +- icu4c/source/i18n/number_utils.cpp | 186 ++++++++++++++++++ icu4c/source/i18n/number_utils.h | 46 +++++ icu4c/source/i18n/numparse_decimal.cpp | 1 + icu4c/source/i18n/unicode/numberformatter.h | 53 +++-- icu4c/source/test/intltest/numbertest_api.cpp | 15 +- .../test/intltest/numbertest_skeletons.cpp | 2 + .../ibm/icu/number/NumberSkeletonImpl.java | 5 + .../test/number/NumberFormatterApiTest.java | 8 + .../dev/test/number/NumberSkeletonTest.java | 2 + 19 files changed, 508 insertions(+), 118 deletions(-) create mode 100644 icu4c/source/i18n/number_utils.cpp diff --git a/icu4c/source/i18n/Makefile.in b/icu4c/source/i18n/Makefile.in index 44236b5047..b4fafdf72f 100644 --- a/icu4c/source/i18n/Makefile.in +++ b/icu4c/source/i18n/Makefile.in @@ -102,7 +102,7 @@ number_affixutils.o number_compact.o number_decimalquantity.o \ number_decimfmtprops.o number_fluent.o number_formatimpl.o number_grouping.o \ number_integerwidth.o number_longnames.o number_modifiers.o number_notation.o \ number_padding.o number_patternmodifier.o number_patternstring.o \ -number_rounding.o number_scientific.o number_stringbuilder.o \ +number_rounding.o number_scientific.o number_stringbuilder.o number_utils.o \ number_mapper.o number_multiplier.o number_currencysymbols.o number_skeletons.o number_capi.o \ double-conversion.o double-conversion-bignum-dtoa.o double-conversion-bignum.o \ double-conversion-cached-powers.o double-conversion-diy-fp.o \ diff --git a/icu4c/source/i18n/fmtable.cpp b/icu4c/source/i18n/fmtable.cpp index e4c119aea6..ac2411a20b 100644 --- a/icu4c/source/i18n/fmtable.cpp +++ b/icu4c/source/i18n/fmtable.cpp @@ -28,7 +28,6 @@ #include "charstr.h" #include "cmemory.h" #include "cstring.h" -#include "decNumber.h" #include "fmtableimp.h" #include "number_decimalquantity.h" @@ -463,12 +462,12 @@ Formattable::getInt64(UErrorCode& status) const status = U_INVALID_FORMAT_ERROR; return U_INT64_MIN; } else if (fabs(fValue.fDouble) > U_DOUBLE_MAX_EXACT_INT && fDecimalQuantity != NULL) { - int64_t val = fDecimalQuantity->toLong(); - if (val != 0) { - return val; + if (fDecimalQuantity->fitsInLong()) { + return fDecimalQuantity->toLong(); + } else if (fDecimalQuantity->isNegative()) { + return U_INT64_MIN; } else { - status = U_INVALID_FORMAT_ERROR; - return fValue.fDouble > 0 ? U_INT64_MAX : U_INT64_MIN; + return U_INT64_MAX; } } else { return (int64_t)fValue.fDouble; diff --git a/icu4c/source/i18n/nfsubs.cpp b/icu4c/source/i18n/nfsubs.cpp index 81aa2e5ffd..0911ac0887 100644 --- a/icu4c/source/i18n/nfsubs.cpp +++ b/icu4c/source/i18n/nfsubs.cpp @@ -20,6 +20,7 @@ #include "nfsubs.h" #include "fmtableimp.h" +#include "putilimp.h" #include "number_decimalquantity.h" #if U_HAVE_RBNF diff --git a/icu4c/source/i18n/number_decimalquantity.cpp b/icu4c/source/i18n/number_decimalquantity.cpp index f092bc10e4..c39a998f23 100644 --- a/icu4c/source/i18n/number_decimalquantity.cpp +++ b/icu4c/source/i18n/number_decimalquantity.cpp @@ -8,15 +8,14 @@ #include "uassert.h" #include #include "cmemory.h" -#include "decNumber.h" #include +#include "putilimp.h" #include "number_decimalquantity.h" -#include "decContext.h" -#include "decNumber.h" #include "number_roundingutils.h" #include "double-conversion.h" #include "unicode/plurrule.h" #include "charstr.h" +#include "number_utils.h" using namespace icu; using namespace icu::number; @@ -31,38 +30,6 @@ int8_t NEGATIVE_FLAG = 1; int8_t INFINITY_FLAG = 2; int8_t NAN_FLAG = 4; -static constexpr int32_t DEFAULT_DIGITS = 34; -typedef MaybeStackHeaderAndArray DecNumberWithStorage; - -/** Helper function to convert a decNumber-compatible string into a decNumber. */ -void stringToDecNumber(StringPiece n, DecNumberWithStorage &dn, UErrorCode& status) { - decContext set; - uprv_decContextDefault(&set, DEC_INIT_BASE); - uprv_decContextSetRounding(&set, DEC_ROUND_HALF_EVEN); - set.traps = 0; // no traps, thank you (what does this mean?) - if (n.length() > DEFAULT_DIGITS) { - dn.resize(n.length(), 0); - set.digits = n.length(); - } else { - set.digits = DEFAULT_DIGITS; - } - - // Make sure that the string is NUL-terminated; CharString guarantees this, but not StringPiece. - CharString cs(n, status); - if (U_FAILURE(status)) { return; } - - static_assert(DECDPUN == 1, "Assumes that DECDPUN is set to 1"); - uprv_decNumberFromString(dn.getAlias(), cs.data(), &set); - - // Check for invalid syntax and set the corresponding error code. - if ((set.status & DEC_Conversion_syntax) != 0) { - status = U_DECIMAL_NUMBER_SYNTAX_ERROR; - } else if (set.status != 0) { - // Not a syntax error, but some other error, like an exponent that is too large. - status = U_UNSUPPORTED_ERROR; - } -} - /** Helper function for safe subtraction (no overflow). */ inline int32_t safeSubtract(int32_t a, int32_t b) { // Note: In C++, signed integer subtraction is undefined behavior. @@ -198,22 +165,30 @@ void DecimalQuantity::roundToIncrement(double roundingIncrement, RoundingMode ro roundToMagnitude(-maxFrac, roundingMode, status); } -void DecimalQuantity::multiplyBy(double multiplicand) { +void DecimalQuantity::multiplyBy(const DecNum& multiplicand, UErrorCode& status) { if (isInfinite() || isZero() || isNaN()) { return; } - // Cover a few simple cases... - if (multiplicand == 1) { - return; - } else if (multiplicand == -1) { - negate(); + // Convert to DecNum, multiply, and convert back. + DecNum decnum; + toDecNum(decnum, status); + if (U_FAILURE(status)) { return; } + decnum.multiplyBy(multiplicand, status); + if (U_FAILURE(status)) { return; } + setToDecNum(decnum, status); +} + +void DecimalQuantity::divideBy(const DecNum& divisor, UErrorCode& status) { + if (isInfinite() || isZero() || isNaN()) { return; } - // Do math for all other cases... - // TODO: Should we convert to decNumber instead? - double temp = toDouble(); - temp *= multiplicand; - setToDouble(temp); + // Convert to DecNum, multiply, and convert back. + DecNum decnum; + toDecNum(decnum, status); + if (U_FAILURE(status)) { return; } + decnum.divideBy(divisor, status); + if (U_FAILURE(status)) { return; } + setToDecNum(decnum, status); } void DecimalQuantity::negate() { @@ -347,7 +322,7 @@ void DecimalQuantity::_setToInt(int32_t n) { DecimalQuantity &DecimalQuantity::setToLong(int64_t n) { setBcdToZero(); flags = 0; - if (n < 0) { + if (n < 0 && n > INT64_MIN) { flags |= NEGATIVE_FLAG; n = -n; } @@ -360,12 +335,12 @@ DecimalQuantity &DecimalQuantity::setToLong(int64_t n) { void DecimalQuantity::_setToLong(int64_t n) { if (n == INT64_MIN) { - static const char *int64minStr = "9.223372036854775808E+18"; - DecNumberWithStorage dn; + DecNum decnum; UErrorCode localStatus = U_ZERO_ERROR; - stringToDecNumber(int64minStr, dn, localStatus); + decnum.setTo("9.223372036854775808E+18", localStatus); if (U_FAILURE(localStatus)) { return; } // unexpected - readDecNumberToBcd(dn.getAlias()); + flags |= NEGATIVE_FLAG; + readDecNumberToBcd(decnum); } else if (n <= INT32_MAX) { readIntToBcd(static_cast(n)); } else { @@ -468,27 +443,36 @@ DecimalQuantity &DecimalQuantity::setToDecNumber(StringPiece n, UErrorCode& stat setBcdToZero(); flags = 0; - DecNumberWithStorage dn; - stringToDecNumber(n, dn, status); - if (U_FAILURE(status)) { return *this; } + // Compute the decNumber representation + DecNum decnum; + decnum.setTo(n, status); - // The code path for decNumber is modeled after BigDecimal in Java. - if (decNumberIsNegative(dn.getAlias())) { - flags |= NEGATIVE_FLAG; - } - if (!decNumberIsZero(dn.getAlias())) { - _setToDecNumber(dn.getAlias()); - } + _setToDecNum(decnum, status); return *this; } -void DecimalQuantity::_setToDecNumber(decNumber *n) { - // Java fastpaths for ints here. In C++, just always read directly from the decNumber. - readDecNumberToBcd(n); - compact(); +DecimalQuantity& DecimalQuantity::setToDecNum(const DecNum& decnum, UErrorCode& status) { + setBcdToZero(); + flags = 0; + + _setToDecNum(decnum, status); + return *this; +} + +void DecimalQuantity::_setToDecNum(const DecNum& decnum, UErrorCode& status) { + if (U_FAILURE(status)) { return; } + if (decnum.isNegative()) { + flags |= NEGATIVE_FLAG; + } + if (!decnum.isZero()) { + readDecNumberToBcd(decnum); + compact(); + } } int64_t DecimalQuantity::toLong() const { + // NOTE: Call sites should be guarded by fitsInLong(), like this: + // if (dq.fitsInLong()) { /* use dq.toLong() */ } else { /* use some fallback */ } int64_t result = 0L; for (int32_t magnitude = scale + precision - 1; magnitude >= 0; magnitude--) { result = result * 10 + getDigitPos(magnitude - scale); @@ -578,6 +562,21 @@ double DecimalQuantity::toDoubleFromOriginal() const { return result; } +void DecimalQuantity::toDecNum(DecNum& output, UErrorCode& status) const { + // Special handling for zero + if (precision == 0) { + output.setTo("0", status); + } + + // Use the BCD constructor. We need to do a little bit of work to convert, though. + // The decNumber constructor expects most-significant first, but we store least-significant first. + MaybeStackArray ubcd(precision); + for (int32_t m = 0; m < precision; m++) { + ubcd[precision - m - 1] = static_cast(getDigitPos(m)); + } + output.setTo(ubcd.getAlias(), precision, scale, isNegative(), status); +} + void DecimalQuantity::roundToMagnitude(int32_t magnitude, RoundingMode roundingMode, UErrorCode& status) { // The position in the BCD at which rounding will be performed; digits to the right of position // will be rounded away. @@ -886,7 +885,8 @@ void DecimalQuantity::readLongToBcd(int64_t n) { } } -void DecimalQuantity::readDecNumberToBcd(decNumber *dn) { +void DecimalQuantity::readDecNumberToBcd(const DecNum& decnum) { + const decNumber* dn = decnum.getRawDecNumber(); if (dn->digits > 16) { ensureCapacity(dn->digits); for (int32_t i = 0; i < dn->digits; i++) { diff --git a/icu4c/source/i18n/number_decimalquantity.h b/icu4c/source/i18n/number_decimalquantity.h index 4d8bb270d7..bcd3aa7cf6 100644 --- a/icu4c/source/i18n/number_decimalquantity.h +++ b/icu4c/source/i18n/number_decimalquantity.h @@ -9,7 +9,6 @@ #include #include "unicode/umachine.h" -#include "decNumber.h" #include "standardplural.h" #include "plurrule_impl.h" #include "number_types.h" @@ -17,6 +16,9 @@ U_NAMESPACE_BEGIN namespace number { namespace impl { +// Forward-declare (maybe don't want number_utils.h included here): +class DecNum; + /** * An class for representing a number to be processed by the decimal formatting pipeline. Includes * methods for rounding, plural rules, and decimal digit extraction. @@ -89,11 +91,18 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { void roundToInfinity(); /** - * Multiply the internal value. + * Multiply the internal value. Uses decNumber. * * @param multiplicand The value by which to multiply. */ - void multiplyBy(double multiplicand); + void multiplyBy(const DecNum& multiplicand, UErrorCode& status); + + /** + * Divide the internal value. Uses decNumber. + * + * @param multiplicand The value by which to multiply. + */ + void divideBy(const DecNum& divisor, UErrorCode& status); /** Flips the sign from positive to negative and back. */ void negate(); @@ -140,6 +149,9 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { /** @return The value contained in this {@link DecimalQuantity} approximated as a double. */ double toDouble() const; + /** Computes a DecNum representation of this DecimalQuantity, saving it to the output parameter. */ + void toDecNum(DecNum& output, UErrorCode& status) const; + DecimalQuantity &setToInt(int32_t n); DecimalQuantity &setToLong(int64_t n); @@ -147,9 +159,11 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { DecimalQuantity &setToDouble(double n); /** decNumber is similar to BigDecimal in Java. */ - DecimalQuantity &setToDecNumber(StringPiece n, UErrorCode& status); + /** Internal method if the caller already has a DecNum. */ + DecimalQuantity &setToDecNum(const DecNum& n, UErrorCode& status); + /** * Appends a digit, optionally with one or more leading zeros, to the end of the value represented * by this DecimalQuantity. @@ -416,7 +430,7 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { */ void readLongToBcd(int64_t n); - void readDecNumberToBcd(decNumber *dn); + void readDecNumberToBcd(const DecNum& dn); void readDoubleConversionToBcd(const char* buffer, int32_t length, int32_t point); @@ -438,7 +452,7 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { void _setToDoubleFast(double n); - void _setToDecNumber(decNumber *n); + void _setToDecNum(const DecNum& dn, UErrorCode& status); void convertToAccurateDouble(); diff --git a/icu4c/source/i18n/number_fluent.cpp b/icu4c/source/i18n/number_fluent.cpp index 81cf90a847..86a5a8cfa7 100644 --- a/icu4c/source/i18n/number_fluent.cpp +++ b/icu4c/source/i18n/number_fluent.cpp @@ -305,6 +305,9 @@ Derived NumberFormatterSettings::macros(impl::MacroProps&& macros)&& { template UnicodeString NumberFormatterSettings::toSkeleton(UErrorCode& status) const { + if (fMacros.copyErrorTo(status)) { + return {}; + } return skeleton::generate(fMacros, status); } diff --git a/icu4c/source/i18n/number_multiplier.cpp b/icu4c/source/i18n/number_multiplier.cpp index 85fbd9e6ac..10e5508b55 100644 --- a/icu4c/source/i18n/number_multiplier.cpp +++ b/icu4c/source/i18n/number_multiplier.cpp @@ -12,6 +12,8 @@ #include "number_types.h" #include "number_multiplier.h" #include "numparse_validators.h" +#include "number_utils.h" +#include "decNumber.h" using namespace icu; using namespace icu::number; @@ -19,38 +21,108 @@ using namespace icu::number::impl; using namespace icu::numparse::impl; -Multiplier::Multiplier(int32_t magnitude, double arbitrary) - : fMagnitude(magnitude), fArbitrary(arbitrary) {} +Multiplier::Multiplier(int32_t magnitude, DecNum* arbitraryToAdopt) + : fMagnitude(magnitude), fArbitrary(arbitraryToAdopt), fError(U_ZERO_ERROR) { + if (fArbitrary != nullptr) { + // Attempt to convert the DecNum to a magnitude multiplier. + fArbitrary->normalize(); + if (fArbitrary->getRawDecNumber()->digits == 1 && fArbitrary->getRawDecNumber()->lsu[0] == 1 && + !fArbitrary->isNegative()) { + // Success! + fMagnitude = fArbitrary->getRawDecNumber()->exponent; + delete fArbitrary; + fArbitrary = nullptr; + } + } +} + +Multiplier::Multiplier(const Multiplier& other) + : fMagnitude(other.fMagnitude), fArbitrary(nullptr), fError(other.fError) { + if (other.fArbitrary != nullptr) { + UErrorCode localStatus = U_ZERO_ERROR; + fArbitrary = new DecNum(*other.fArbitrary, localStatus); + } +} + +Multiplier& Multiplier::operator=(const Multiplier& other) { + fMagnitude = other.fMagnitude; + if (other.fArbitrary != nullptr) { + UErrorCode localStatus = U_ZERO_ERROR; + fArbitrary = new DecNum(*other.fArbitrary, localStatus); + } else { + fArbitrary = nullptr; + } + fError = other.fError; + return *this; +} + +Multiplier::Multiplier(Multiplier&& src) U_NOEXCEPT + : fMagnitude(src.fMagnitude), fArbitrary(src.fArbitrary), fError(src.fError) { + // Take ownership away from src if necessary + src.fArbitrary = nullptr; +} + +Multiplier& Multiplier::operator=(Multiplier&& src) U_NOEXCEPT { + fMagnitude = src.fMagnitude; + fArbitrary = src.fArbitrary; + fError = src.fError; + // Take ownership away from src if necessary + src.fArbitrary = nullptr; + return *this; +} + +Multiplier::~Multiplier() { + delete fArbitrary; +} + Multiplier Multiplier::none() { - return {0, 1}; + return {0, nullptr}; } Multiplier Multiplier::powerOfTen(int32_t power) { - return {power, 1}; + return {power, nullptr}; } Multiplier Multiplier::arbitraryDecimal(StringPiece multiplicand) { - // TODO: Fix this hack UErrorCode localError = U_ZERO_ERROR; - DecimalQuantity dq; - dq.setToDecNumber(multiplicand, localError); - return {0, dq.toDouble()}; + LocalPointer decnum(new DecNum(), localError); + if (U_FAILURE(localError)) { + return {localError}; + } + decnum->setTo(multiplicand, localError); + if (U_FAILURE(localError)) { + return {localError}; + } + return {0, decnum.orphan()}; } Multiplier Multiplier::arbitraryDouble(double multiplicand) { - return {0, multiplicand}; + UErrorCode localError = U_ZERO_ERROR; + LocalPointer decnum(new DecNum(), localError); + if (U_FAILURE(localError)) { + return {localError}; + } + decnum->setTo(multiplicand, localError); + if (U_FAILURE(localError)) { + return {localError}; + } + return {0, decnum.orphan()}; } void Multiplier::applyTo(impl::DecimalQuantity& quantity) const { quantity.adjustMagnitude(fMagnitude); - quantity.multiplyBy(fArbitrary); + if (fArbitrary != nullptr) { + UErrorCode localStatus = U_ZERO_ERROR; + quantity.multiplyBy(*fArbitrary, localStatus); + } } void Multiplier::applyReciprocalTo(impl::DecimalQuantity& quantity) const { quantity.adjustMagnitude(-fMagnitude); - if (fArbitrary != 0) { - quantity.multiplyBy(1 / fArbitrary); + if (fArbitrary != nullptr) { + UErrorCode localStatus = U_ZERO_ERROR; + quantity.divideBy(*fArbitrary, localStatus); } } @@ -70,7 +142,7 @@ void MultiplierFormatHandler::processQuantity(DecimalQuantity& quantity, MicroPr // NOTE: MultiplierParseHandler is declared in the header numparse_validators.h MultiplierParseHandler::MultiplierParseHandler(::icu::number::Multiplier multiplier) - : fMultiplier(multiplier) {} + : fMultiplier(std::move(multiplier)) {} void MultiplierParseHandler::postProcess(ParsedNumber& result) const { if (!result.quantity.bogus) { diff --git a/icu4c/source/i18n/number_rounding.cpp b/icu4c/source/i18n/number_rounding.cpp index b5e37194f8..ba77cadef0 100644 --- a/icu4c/source/i18n/number_rounding.cpp +++ b/icu4c/source/i18n/number_rounding.cpp @@ -11,6 +11,7 @@ #include "number_decimalquantity.h" #include "double-conversion.h" #include "number_roundingutils.h" +#include "putilimp.h" using namespace icu; using namespace icu::number; diff --git a/icu4c/source/i18n/number_skeletons.cpp b/icu4c/source/i18n/number_skeletons.cpp index 0976cb5d5b..81be03b15a 100644 --- a/icu4c/source/i18n/number_skeletons.cpp +++ b/icu4c/source/i18n/number_skeletons.cpp @@ -1190,24 +1190,28 @@ void blueprint_helpers::parseMultiplierOption(const StringSegment& segment, Macr CharString buffer; SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status); - // Utilize DecimalQuantity/decNumber to parse this for us. - // TODO: Parse to a DecNumber directly. - DecimalQuantity dq; - UErrorCode localStatus = U_ZERO_ERROR; - dq.setToDecNumber({buffer.data(), buffer.length()}, localStatus); - if (U_FAILURE(localStatus)) { - // throw new SkeletonSyntaxException("Invalid rounding increment", segment, e); + LocalPointer decnum(new DecNum(), status); + if (U_FAILURE(status)) { return; } + decnum->setTo({buffer.data(), buffer.length()}, status); + if (U_FAILURE(status)) { status = U_NUMBER_SKELETON_SYNTAX_ERROR; return; } - macros.multiplier = Multiplier::arbitraryDouble(dq.toDouble()); + + // NOTE: The constructor will optimize the decnum for us if possible. + macros.multiplier = {0, decnum.orphan()}; } -void blueprint_helpers::generateMultiplierOption(int32_t magnitude, double arbitrary, UnicodeString& sb, - UErrorCode&) { +void +blueprint_helpers::generateMultiplierOption(int32_t magnitude, const DecNum* arbitrary, UnicodeString& sb, + UErrorCode& status) { // Utilize DecimalQuantity/double_conversion to format this for us. DecimalQuantity dq; - dq.setToDouble(arbitrary); + if (arbitrary != nullptr) { + dq.setToDecNum(*arbitrary, status); + } else { + dq.setToInt(1); + } dq.adjustMagnitude(magnitude); dq.roundToInfinity(); sb.append(dq.toPlainString()); diff --git a/icu4c/source/i18n/number_skeletons.h b/icu4c/source/i18n/number_skeletons.h index 7954b99f2b..b3e7287f54 100644 --- a/icu4c/source/i18n/number_skeletons.h +++ b/icu4c/source/i18n/number_skeletons.h @@ -22,6 +22,11 @@ struct SeenMacroProps; // namespace for enums and entrypoint functions namespace skeleton { +/////////////////////////////////////////////////////////////////////////////////////// +// NOTE: For an example of how to add a new stem to the number skeleton parser, see: // +// http://bugs.icu-project.org/trac/changeset/41193 // +/////////////////////////////////////////////////////////////////////////////////////// + /** * While parsing a skeleton, this enum records what type of option we expect to find next. */ @@ -240,7 +245,8 @@ void generateNumberingSystemOption(const NumberingSystem& ns, UnicodeString& sb, void parseMultiplierOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status); -void generateMultiplierOption(int32_t magnitude, double arbitrary, UnicodeString& sb, UErrorCode& status); +void generateMultiplierOption(int32_t magnitude, const DecNum* arbitrary, UnicodeString& sb, + UErrorCode& status); } // namespace blueprint_helpers diff --git a/icu4c/source/i18n/number_utils.cpp b/icu4c/source/i18n/number_utils.cpp new file mode 100644 index 0000000000..c2f74e5bb6 --- /dev/null +++ b/icu4c/source/i18n/number_utils.cpp @@ -0,0 +1,186 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include +#include +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT + +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + +#include "number_types.h" +#include "number_utils.h" +#include "charstr.h" +#include "decContext.h" +#include "decNumber.h" +#include "double-conversion.h" + +using namespace icu; +using namespace icu::number; +using namespace icu::number::impl; + +using icu::double_conversion::DoubleToStringConverter; + + +DecNum::DecNum() { + uprv_decContextDefault(&fContext, DEC_INIT_BASE); + uprv_decContextSetRounding(&fContext, DEC_ROUND_HALF_EVEN); + fContext.traps = 0; // no traps, thank you (what does this even mean?) +} + +DecNum::DecNum(const DecNum& other, UErrorCode& status) + : fContext(other.fContext) { + // Allocate memory for the new DecNum. + U_ASSERT(fContext.digits == other.fData.getCapacity()); + if (fContext.digits > kDefaultDigits) { + void* p = fData.resize(fContext.digits, 0); + if (p == nullptr) { + status = U_MEMORY_ALLOCATION_ERROR; + return; + } + } + + // Copy the data from the old DecNum to the new one. + uprv_memcpy(fData.getAlias(), other.fData.getAlias(), sizeof(decNumber)); + uprv_memcpy(fData.getArrayStart(), + other.fData.getArrayStart(), + other.fData.getArrayLimit() - other.fData.getArrayStart()); +} + +void DecNum::setTo(StringPiece str, UErrorCode& status) { + // We need NUL-terminated for decNumber; CharString guarantees this, but not StringPiece. + CharString cstr(str, status); + if (U_FAILURE(status)) { return; } + _setTo(cstr.data(), str.length(), status); +} + +void DecNum::setTo(const char* str, UErrorCode& status) { + _setTo(str, static_cast(uprv_strlen(str)), status); +} + +void DecNum::setTo(double d, UErrorCode& status) { + // Need to check for NaN and Infinity before going into DoubleToStringConverter + if (std::isnan(d) != 0 || std::isfinite(d) == 0) { + status = U_UNSUPPORTED_ERROR; + return; + } + + // First convert from double to string, then string to DecNum. + // Allocate enough room for: all digits, "E-324", and NUL-terminator. + char buffer[DoubleToStringConverter::kBase10MaximalLength + 6]; + bool sign; // unused; always positive + int32_t length; + int32_t point; + DoubleToStringConverter::DoubleToAscii( + d, + DoubleToStringConverter::DtoaMode::SHORTEST, + 0, + buffer, + sizeof(buffer), + &sign, + &length, + &point + ); + + // Read initial result as a string. + _setTo(buffer, length, status); + + // Set exponent and bitmask. Note that DoubleToStringConverter does not do negatives. + fData.getAlias()->exponent += point - length; + fData.getAlias()->bits |= static_cast(std::signbit(d) ? DECNEG : 0); +} + +void DecNum::_setTo(const char* str, int32_t maxDigits, UErrorCode& status) { + if (maxDigits > kDefaultDigits) { + fData.resize(maxDigits, 0); + fContext.digits = maxDigits; + } else { + fContext.digits = kDefaultDigits; + } + + static_assert(DECDPUN == 1, "Assumes that DECDPUN is set to 1"); + uprv_decNumberFromString(fData.getAlias(), str, &fContext); + + // For consistency with Java BigDecimal, no support for DecNum that is NaN or Infinity! + if (decNumberIsSpecial(fData.getAlias())) { + status = U_UNSUPPORTED_ERROR; + return; + } + + // Check for invalid syntax and set the corresponding error code. + if ((fContext.status & DEC_Conversion_syntax) != 0) { + status = U_DECIMAL_NUMBER_SYNTAX_ERROR; + } else if (fContext.status != 0) { + // Not a syntax error, but some other error, like an exponent that is too large. + status = U_UNSUPPORTED_ERROR; + } +} + +void +DecNum::setTo(const uint8_t* bcd, int32_t length, int32_t scale, bool isNegative, UErrorCode& status) { + if (length > kDefaultDigits) { + fData.resize(length, 0); + fContext.digits = length; + } else { + fContext.digits = kDefaultDigits; + } + + // "digits is of type int32_t, and must have a value in the range 1 through 999,999,999." + if (length < 1 || length > 999999999) { + // Too large for decNumber + status = U_UNSUPPORTED_ERROR; + return; + } + // "The exponent field holds the exponent of the number. Its range is limited by the requirement that + // "the range of the adjusted exponent of the number be balanced and fit within a whole number of + // "decimal digits (in this implementation, be –999,999,999 through +999,999,999). The adjusted + // "exponent is the exponent that would result if the number were expressed with a single digit before + // "the decimal point, and is therefore given by exponent+digits-1." + if (scale > 999999999 - length + 1 || scale < -999999999 - length + 1) { + // Too large for decNumber + status = U_UNSUPPORTED_ERROR; + return; + } + + fData.getAlias()->digits = length; + fData.getAlias()->exponent = scale; + fData.getAlias()->bits = static_cast(isNegative ? DECNEG : 0); + uprv_decNumberSetBCD(fData, bcd, static_cast(length)); + if (fContext.status != 0) { + // Some error occured while constructing the decNumber. + status = U_INTERNAL_PROGRAM_ERROR; + } +} + +void DecNum::normalize() { + uprv_decNumberReduce(fData, fData, &fContext); +} + +void DecNum::multiplyBy(const DecNum& rhs, UErrorCode& status) { + uprv_decNumberMultiply(fData, fData, rhs.fData, &fContext); + if (fContext.status != 0) { + status = U_INTERNAL_PROGRAM_ERROR; + } +} + +void DecNum::divideBy(const DecNum& rhs, UErrorCode& status) { + uprv_decNumberDivide(fData, fData, rhs.fData, &fContext); + if (fContext.status != 0) { + status = U_INTERNAL_PROGRAM_ERROR; + } +} + +bool DecNum::isNegative() const { + return decNumberIsNegative(fData.getAlias()); +} + +bool DecNum::isZero() const { + return decNumberIsZero(fData.getAlias()); +} + + + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/number_utils.h b/icu4c/source/i18n/number_utils.h index 23c6fcf7ec..1afd608ae9 100644 --- a/icu4c/source/i18n/number_utils.h +++ b/icu4c/source/i18n/number_utils.h @@ -14,6 +14,8 @@ #include "number_patternstring.h" #include "number_modifiers.h" #include "number_multiplier.h" +#include "decNumber.h" +#include "charstr.h" U_NAMESPACE_BEGIN namespace number { namespace impl { @@ -128,6 +130,50 @@ inline bool unitIsPermille(const MeasureUnit& unit) { return uprv_strcmp("permille", unit.getSubtype()) == 0; } + +/** A very thin C++ wrapper around decNumber.h */ +class DecNum : public UMemory { + public: + DecNum(); // leaves object in valid but undefined state + + // Copy-like constructor; use the default move operators. + DecNum(const DecNum& other, UErrorCode& status); + + /** Sets the decNumber to the StringPiece. */ + void setTo(StringPiece str, UErrorCode& status); + + /** Sets the decNumber to the NUL-terminated char string. */ + void setTo(const char* str, UErrorCode& status); + + /** Uses double_conversion to set this decNumber to the given double. */ + void setTo(double d, UErrorCode& status); + + /** Sets the decNumber to the BCD representation. */ + void setTo(const uint8_t* bcd, int32_t length, int32_t scale, bool isNegative, UErrorCode& status); + + void normalize(); + + void multiplyBy(const DecNum& rhs, UErrorCode& status); + + void divideBy(const DecNum& rhs, UErrorCode& status); + + bool isNegative() const; + + bool isZero() const; + + inline const decNumber* getRawDecNumber() const { + return fData.getAlias(); + } + + private: + static constexpr int32_t kDefaultDigits = 34; + MaybeStackHeaderAndArray fData; + decContext fContext; + + void _setTo(const char* str, int32_t maxDigits, UErrorCode& status); +}; + + } // namespace impl } // namespace number U_NAMESPACE_END diff --git a/icu4c/source/i18n/numparse_decimal.cpp b/icu4c/source/i18n/numparse_decimal.cpp index 3853e5a9a9..28a67d85ba 100644 --- a/icu4c/source/i18n/numparse_decimal.cpp +++ b/icu4c/source/i18n/numparse_decimal.cpp @@ -14,6 +14,7 @@ #include "numparse_unisets.h" #include "numparse_utils.h" #include "unicode/uchar.h" +#include "putilimp.h" using namespace icu; using namespace icu::numparse; diff --git a/icu4c/source/i18n/unicode/numberformatter.h b/icu4c/source/i18n/unicode/numberformatter.h index 54698d382e..a95e1da2d1 100644 --- a/icu4c/source/i18n/unicode/numberformatter.h +++ b/icu4c/source/i18n/unicode/numberformatter.h @@ -146,6 +146,7 @@ struct DecimalFormatProperties; class MultiplierFormatHandler; class CurrencySymbols; class GeneratorHelpers; +class DecNum; } // namespace impl @@ -1019,16 +1020,46 @@ class U_I18N_API Multiplier : public UMemory { */ static Multiplier arbitraryDouble(double multiplicand); + // We need a custom destructor for the DecNum, which means we need to declare + // the copy/move constructor/assignment quartet. + + /** @draft ICU 62 */ + Multiplier(const Multiplier& other); + + /** @draft ICU 62 */ + Multiplier& operator=(const Multiplier& other); + + /** @draft ICU 62 */ + Multiplier(Multiplier&& src) U_NOEXCEPT; + + /** @draft ICU 62 */ + Multiplier& operator=(Multiplier&& src) U_NOEXCEPT; + + /** @draft ICU 62 */ + ~Multiplier(); + + /** @internal */ + Multiplier(int32_t magnitude, impl::DecNum* arbitraryToAdopt); + private: int32_t fMagnitude; - double fArbitrary; + impl::DecNum* fArbitrary; + UErrorCode fError; - Multiplier(int32_t magnitude, double arbitrary); + Multiplier(UErrorCode error) : fMagnitude(0), fArbitrary(nullptr), fError(error) {} - Multiplier() : fMagnitude(0), fArbitrary(1) {} + Multiplier() : fMagnitude(0), fArbitrary(nullptr), fError(U_ZERO_ERROR) {} bool isValid() const { - return fMagnitude != 0 || fArbitrary != 1; + return fMagnitude != 0 || fArbitrary != nullptr; + } + + UBool copyErrorTo(UErrorCode &status) const { + if (fError != U_ZERO_ERROR) { + status = fError; + return TRUE; + } + return FALSE; } void applyTo(impl::DecimalQuantity& quantity) const; @@ -1065,18 +1096,18 @@ class U_I18N_API SymbolsWrapper : public UMemory { /** @internal */ SymbolsWrapper(const SymbolsWrapper &other); - /** @internal */ - SymbolsWrapper(SymbolsWrapper&& src) U_NOEXCEPT; - - /** @internal */ - ~SymbolsWrapper(); - /** @internal */ SymbolsWrapper &operator=(const SymbolsWrapper &other); + /** @internal */ + SymbolsWrapper(SymbolsWrapper&& src) U_NOEXCEPT; + /** @internal */ SymbolsWrapper &operator=(SymbolsWrapper&& src) U_NOEXCEPT; + /** @internal */ + ~SymbolsWrapper(); + #ifndef U_HIDE_INTERNAL_API /** @@ -1359,7 +1390,7 @@ struct U_I18N_API MacroProps : public UMemory { bool copyErrorTo(UErrorCode &status) const { return notation.copyErrorTo(status) || rounder.copyErrorTo(status) || padder.copyErrorTo(status) || integerWidth.copyErrorTo(status) || - symbols.copyErrorTo(status); + symbols.copyErrorTo(status) || multiplier.copyErrorTo(status); } }; diff --git a/icu4c/source/test/intltest/numbertest_api.cpp b/icu4c/source/test/intltest/numbertest_api.cpp index 947e970d6d..ca75918311 100644 --- a/icu4c/source/test/intltest/numbertest_api.cpp +++ b/icu4c/source/test/intltest/numbertest_api.cpp @@ -7,6 +7,7 @@ #include "charstr.h" #include +#include #include "unicode/unum.h" #include "unicode/numberformatter.h" #include "number_types.h" @@ -2001,6 +2002,14 @@ void NumberFormatterApiTest::multiplier() { Locale::getEnglish(), 2, u"-10.4"); + + assertFormatSingle( + u"Negative One Multiplier", + u"multiply/-1", + NumberFormatter::with().multiplier(Multiplier::arbitraryDouble(-1)), + Locale::getEnglish(), + 444444, + u"-444,444"); } void NumberFormatterApiTest::locale() { @@ -2209,7 +2218,7 @@ void NumberFormatterApiTest::assertFormatDescending(const char16_t* umessage, co for (int32_t i = 0; i < 9; i++) { double d = inputs[i]; UnicodeString actual3 = l3.formatDouble(d, status).toString(); - assertEquals(message + ": Skeleton Path: " + d, expecteds[i], actual3); + assertEquals(message + ": Skeleton Path: '" + normalized + "': " + d, expecteds[i], actual3); } } else { assertUndefinedSkeleton(f); @@ -2250,7 +2259,7 @@ void NumberFormatterApiTest::assertFormatDescendingBig(const char16_t* umessage, for (int32_t i = 0; i < 9; i++) { double d = inputs[i]; UnicodeString actual3 = l3.formatDouble(d, status).toString(); - assertEquals(message + ": Skeleton Path: " + d, expecteds[i], actual3); + assertEquals(message + ": Skeleton Path: '" + normalized + "': " + d, expecteds[i], actual3); } } else { assertUndefinedSkeleton(f); @@ -2279,7 +2288,7 @@ void NumberFormatterApiTest::assertFormatSingle(const char16_t* umessage, const assertEquals(message + ": Skeleton:", normalized, f.toSkeleton(status)); LocalizedNumberFormatter l3 = NumberFormatter::fromSkeleton(normalized, status).locale(locale); UnicodeString actual3 = l3.formatDouble(input, status).toString(); - assertEquals(message + ": Skeleton Path: " + input, expected, actual3); + assertEquals(message + ": Skeleton Path: '" + normalized + "': " + input, expected, actual3); } else { assertUndefinedSkeleton(f); } diff --git a/icu4c/source/test/intltest/numbertest_skeletons.cpp b/icu4c/source/test/intltest/numbertest_skeletons.cpp index 68138b9b08..28700b3bf9 100644 --- a/icu4c/source/test/intltest/numbertest_skeletons.cpp +++ b/icu4c/source/test/intltest/numbertest_skeletons.cpp @@ -130,8 +130,10 @@ void NumberSkeletonTest::invalidTokens() { u"round-currency-cash/XXX", u"scientific/ee", u"round-increment/xxx", + u"round-increment/NaN", u"round-increment/0.1.2", u"multiply/xxx", + u"multiply/NaN", u"multiply/0.1.2", u"multiply/français", // non-invariant characters for C++ u"currency/dummy", 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 70df1dbcd1..1f855830f0 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 @@ -33,6 +33,11 @@ import com.ibm.icu.util.StringTrieBuilder; */ class NumberSkeletonImpl { + /////////////////////////////////////////////////////////////////////////////////////// + // NOTE: For an example of how to add a new stem to the number skeleton parser, see: // + // http://bugs.icu-project.org/trac/changeset/41193 // + /////////////////////////////////////////////////////////////////////////////////////// + /** * While parsing a skeleton, this enum records what type of option we expect to find next. */ 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 ac0a00f6d1..4505b21cb1 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 @@ -1975,6 +1975,14 @@ public class NumberFormatterApiTest { ULocale.ENGLISH, 2, "-10.4"); + + assertFormatSingle( + "Negative One Multiplier", + "multiply/-1", + NumberFormatter.with().multiplier(Multiplier.arbitrary(-1)), + ULocale.ENGLISH, + 444444, + "-444,444"); } @Test 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 5e88476532..216127923b 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 @@ -124,8 +124,10 @@ public class NumberSkeletonTest { "round-currency-cash/XXX", "scientific/ee", "round-increment/xxx", + "round-increment/NaN", "round-increment/0.1.2", "multiply/xxx", + "multiply/NaN", "multiply/0.1.2", "multiply/français", // non-invariant characters for C++ "currency/dummy", From afda6f137878057910bf4a86bc45a03a73a1e35a Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Thu, 5 Apr 2018 21:55:29 +0000 Subject: [PATCH 079/129] ICU-13634 Removing stray #include added in r41136. X-SVN-Rev: 41199 --- icu4c/source/common/putil.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/icu4c/source/common/putil.cpp b/icu4c/source/common/putil.cpp index 452e2fd79c..2d2de524db 100644 --- a/icu4c/source/common/putil.cpp +++ b/icu4c/source/common/putil.cpp @@ -533,8 +533,6 @@ uprv_fmin(double x, double y) return (x > y ? y : x); } -#include - U_CAPI UBool U_EXPORT2 uprv_add32_overflow(int32_t a, int32_t b, int32_t* res) { // NOTE: Some compilers (GCC, Clang) have primitives available, like __builtin_add_overflow. From 96bf6508efc2920cc0f1f24b3eba837d3aca7381 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Thu, 5 Apr 2018 22:00:10 +0000 Subject: [PATCH 080/129] ICU-13672 Replacing % and / with std::div in toNumberString function. X-SVN-Rev: 41200 --- icu4c/source/i18n/number_decimalquantity.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/icu4c/source/i18n/number_decimalquantity.cpp b/icu4c/source/i18n/number_decimalquantity.cpp index c39a998f23..1a3e258384 100644 --- a/icu4c/source/i18n/number_decimalquantity.cpp +++ b/icu4c/source/i18n/number_decimalquantity.cpp @@ -9,6 +9,7 @@ #include #include "cmemory.h" #include +#include #include "putilimp.h" #include "number_decimalquantity.h" #include "number_roundingutils.h" @@ -1114,8 +1115,9 @@ UnicodeString DecimalQuantity::toNumberString() const { } int32_t insertIndex = result.length(); while (_scale > 0) { - result.insert(insertIndex, u'0' + (_scale % 10)); - _scale /= 10; + std::div_t res = std::div(_scale, 10); + result.insert(insertIndex, u'0' + res.rem); + _scale = res.quot; } return result; } From b5fb39f3028290b923e6aafc83f7b5bc79a107be Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Thu, 5 Apr 2018 23:44:03 +0000 Subject: [PATCH 081/129] ICU-13672 Adding the toDouble test to Java with comments. X-SVN-Rev: 41201 --- .../intltest/numbertest_decimalquantity.cpp | 1 + .../number/DecimalQuantity_AbstractBCD.java | 1 + .../dev/test/number/DecimalQuantityTest.java | 20 +++++++++++++++++++ 3 files changed, 22 insertions(+) diff --git a/icu4c/source/test/intltest/numbertest_decimalquantity.cpp b/icu4c/source/test/intltest/numbertest_decimalquantity.cpp index 2b31380879..ba1a67c215 100644 --- a/icu4c/source/test/intltest/numbertest_decimalquantity.cpp +++ b/icu4c/source/test/intltest/numbertest_decimalquantity.cpp @@ -302,6 +302,7 @@ void DecimalQuantityTest::testToDouble() { double expected; } cases[] = { { "0", 0.0 }, + { "514.23", 514.23 }, { "-3.142E-271", -3.142e-271 } }; for (auto& cas : cases) { 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 7a87d856e9..772691004e 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 @@ -651,6 +651,7 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { } // TODO: Do like in C++ and use a library function to perform this conversion? + // This code is not as not in Java because .parse() returns a BigDecimal, not a double. long tempLong = 0L; int lostDigits = precision - Math.min(precision, 17); diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java index e33405d12b..85877343a2 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java @@ -478,6 +478,26 @@ public class DecimalQuantityTest extends TestFmwk { } } + @Test + public void testToDouble() { + Object[][] cases = new Object[][] { + { "0", 0.0 }, + { "514.23", 514.23 }, + // NOTE: This does not currently pass in Java. See DecimalFormat_AbstractBCD#toDecimal. + // { "-3.142E-271", -3.142e-271 } + }; + + for (Object[] cas : cases) { + String input = (String) cas[0]; + double expected = (Double) cas[1]; + + DecimalQuantity q = new DecimalQuantity_DualStorageBCD(); + q.setToBigDecimal(new BigDecimal(input)); + double actual = q.toDouble(); + assertEquals("Doubles should exactly equal", expected, actual); + } + } + static void assertDoubleEquals(String message, double d1, double d2) { boolean equal = (Math.abs(d1 - d2) < 1e-6) || (Math.abs((d1 - d2) / d1) < 1e-6); handleAssert(equal, message, d1, d2, null, false); From 73f93a457aa640d1f8e1874977adbcbf0365a81f Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Fri, 6 Apr 2018 09:35:16 +0000 Subject: [PATCH 082/129] ICU-13634 Fixing DecimalQuantity call sites, first written in r41063, r41064, and r41098. RNBF test is passing again. X-SVN-Rev: 41204 --- icu4c/source/i18n/fmtable.cpp | 8 ++----- icu4c/source/i18n/msgfmt.cpp | 2 +- icu4c/source/i18n/nfsubs.cpp | 6 ++++-- icu4c/source/i18n/number_decimalquantity.cpp | 3 +++ icu4c/source/i18n/numparse_impl.cpp | 2 +- icu4c/source/i18n/plurfmt.cpp | 21 ++++++------------- icu4c/source/i18n/rbnf.cpp | 8 +++---- icu4c/source/test/intltest/numfmtst.cpp | 1 + .../impl/number/parse/NumberParserImpl.java | 2 +- .../src/com/ibm/icu/text/NFSubstitution.java | 11 ++++------ 10 files changed, 27 insertions(+), 37 deletions(-) diff --git a/icu4c/source/i18n/fmtable.cpp b/icu4c/source/i18n/fmtable.cpp index ac2411a20b..68e06767a1 100644 --- a/icu4c/source/i18n/fmtable.cpp +++ b/icu4c/source/i18n/fmtable.cpp @@ -725,6 +725,7 @@ CharString *Formattable::internalGetCharString(UErrorCode &status) { switch (fType) { case kDouble: fDecimalQuantity->setToDouble(this->getDouble()); + fDecimalQuantity->roundToInfinity(); break; case kLong: fDecimalQuantity->setToInt(this->getLong()); @@ -745,12 +746,7 @@ CharString *Formattable::internalGetCharString(UErrorCode &status) { return NULL; } UnicodeString result = fDecimalQuantity->toNumberString(); - for (int32_t i=0; iappend(static_cast(result[i]), status); - if (U_FAILURE(status)) { - return NULL; - } - } + fDecimalStr->appendInvariantChars(result, status); } return fDecimalStr; } diff --git a/icu4c/source/i18n/msgfmt.cpp b/icu4c/source/i18n/msgfmt.cpp index a758f0d055..faf6c21c6e 100644 --- a/icu4c/source/i18n/msgfmt.cpp +++ b/icu4c/source/i18n/msgfmt.cpp @@ -1962,7 +1962,7 @@ UnicodeString MessageFormat::PluralSelectorProvider::select(void *ctx, double nu auto* decFmt = dynamic_cast(context.formatter); if(decFmt != NULL) { number::impl::DecimalQuantity dq; - decFmt->formatToDecimalQuantity(number, dq, ec); + decFmt->formatToDecimalQuantity(context.number, dq, ec); if (U_FAILURE(ec)) { return UnicodeString(FALSE, OTHER_STRING, 5); } diff --git a/icu4c/source/i18n/nfsubs.cpp b/icu4c/source/i18n/nfsubs.cpp index 0911ac0887..208543d1ac 100644 --- a/icu4c/source/i18n/nfsubs.cpp +++ b/icu4c/source/i18n/nfsubs.cpp @@ -1086,7 +1086,7 @@ FractionalPartSubstitution::doSubstitution(double number, UnicodeString& toInser } else { pad = TRUE; } - int64_t digit = didx>=0 ? dl.getDigit(didx) - '0' : 0; + int64_t digit = dl.getDigit(didx); getRuleSet()->format(digit, toInsertInto, _pos + getPos(), recursionCount, status); } @@ -1145,6 +1145,7 @@ FractionalPartSubstitution::doParse(const UnicodeString& text, // double p10 = 0.1; DecimalQuantity dl; + int32_t totalDigits = 0; NumberFormat* fmt = NULL; while (workText.length() > 0 && workPos.getIndex() != 0) { workPos.setIndex(0); @@ -1172,8 +1173,8 @@ FractionalPartSubstitution::doParse(const UnicodeString& text, } if (workPos.getIndex() != 0) { - // TODO(sffc): Make sure this is doing what it is supposed to do. dl.appendDigit(static_cast(digit), 0, true); + totalDigits++; // result += digit * p10; // p10 /= 10; parsePosition.setIndex(parsePosition.getIndex() + workPos.getIndex()); @@ -1186,6 +1187,7 @@ FractionalPartSubstitution::doParse(const UnicodeString& text, } delete fmt; + dl.adjustMagnitude(-totalDigits); result = dl.toDouble(); result = composeRuleValue(result, baseValue); resVal.setDouble(result); diff --git a/icu4c/source/i18n/number_decimalquantity.cpp b/icu4c/source/i18n/number_decimalquantity.cpp index 1a3e258384..8b369547ae 100644 --- a/icu4c/source/i18n/number_decimalquantity.cpp +++ b/icu4c/source/i18n/number_decimalquantity.cpp @@ -474,6 +474,7 @@ void DecimalQuantity::_setToDecNum(const DecNum& decnum, UErrorCode& status) { int64_t DecimalQuantity::toLong() const { // NOTE: Call sites should be guarded by fitsInLong(), like this: // if (dq.fitsInLong()) { /* use dq.toLong() */ } else { /* use some fallback */ } + U_ASSERT(fitsInLong()); int64_t result = 0L; for (int32_t magnitude = scale + precision - 1; magnitude >= 0; magnitude--) { result = result * 10 + getDigitPos(magnitude - scale); @@ -753,6 +754,7 @@ void DecimalQuantity::appendDigit(int8_t value, int32_t leadingZeros, bool appen } UnicodeString DecimalQuantity::toPlainString() const { + U_ASSERT(!isApproximate); UnicodeString sb; if (isNegative()) { sb.append(u'-'); @@ -1097,6 +1099,7 @@ UnicodeString DecimalQuantity::toString() const { } UnicodeString DecimalQuantity::toNumberString() const { + U_ASSERT(!isApproximate); UnicodeString result; if (precision == 0) { result.append(u'0'); diff --git a/icu4c/source/i18n/numparse_impl.cpp b/icu4c/source/i18n/numparse_impl.cpp index c917aad176..b8240e7f40 100644 --- a/icu4c/source/i18n/numparse_impl.cpp +++ b/icu4c/source/i18n/numparse_impl.cpp @@ -146,10 +146,10 @@ NumberParserImpl::createParserFromProperties(const number::impl::DecimalFormatPr if (!isStrict) { parser->addMatcher(parser->fLocalMatchers.plusSign = {symbols, false}); parser->addMatcher(parser->fLocalMatchers.minusSign = {symbols, false}); - parser->addMatcher(parser->fLocalMatchers.nan = {symbols}); parser->addMatcher(parser->fLocalMatchers.percent = {symbols}); parser->addMatcher(parser->fLocalMatchers.permille = {symbols}); } + parser->addMatcher(parser->fLocalMatchers.nan = {symbols}); parser->addMatcher(parser->fLocalMatchers.infinity = {symbols}); UnicodeString padString = properties.padString; if (!padString.isBogus() && !ignorables.getSet()->contains(padString)) { diff --git a/icu4c/source/i18n/plurfmt.cpp b/icu4c/source/i18n/plurfmt.cpp index a8fd24d176..eb8b5d88a8 100644 --- a/icu4c/source/i18n/plurfmt.cpp +++ b/icu4c/source/i18n/plurfmt.cpp @@ -264,35 +264,26 @@ PluralFormat::format(const Formattable& numberObject, double number, double numberMinusOffset = number - offset; UnicodeString numberString; DecimalQuantity quantity; - quantity.setToDouble(numberMinusOffset); FieldPosition ignorePos; if (offset == 0) { DecimalFormat *decFmt = dynamic_cast(numberFormat); if(decFmt != NULL) { -// decFmt->initVisibleDigitsWithExponent( -// numberObject, dec, status); -// if (U_FAILURE(status)) { -// return appendTo; -// } -// decFmt->format(dec, numberString, ignorePos, status); - decFmt->format(quantity, numberString, ignorePos, status); + decFmt->formatToDecimalQuantity(numberObject, quantity, status); } else { numberFormat->format( numberObject, numberString, ignorePos, status); // could be BigDecimal etc. + quantity.setToDouble(numberMinusOffset); + quantity.roundToInfinity(); } } else { DecimalFormat *decFmt = dynamic_cast(numberFormat); if(decFmt != NULL) { -// decFmt->initVisibleDigitsWithExponent( -// numberMinusOffset, dec, status); -// if (U_FAILURE(status)) { -// return appendTo; -// } -// decFmt->format(dec, numberString, ignorePos, status); - decFmt->format(quantity, numberString, ignorePos, status); + decFmt->formatToDecimalQuantity(numberMinusOffset, quantity, status); } else { numberFormat->format( numberMinusOffset, numberString, ignorePos, status); + quantity.setToDouble(numberMinusOffset); + quantity.roundToInfinity(); } } int32_t partIndex = findSubMessage(msgPattern, 0, pluralRulesWrapper, &quantity, number, status); diff --git a/icu4c/source/i18n/rbnf.cpp b/icu4c/source/i18n/rbnf.cpp index 3cc208585f..68bbf1ab17 100644 --- a/icu4c/source/i18n/rbnf.cpp +++ b/icu4c/source/i18n/rbnf.cpp @@ -1120,12 +1120,12 @@ RuleBasedNumberFormat::format(const DecimalQuantity &number, } DecimalQuantity copy(number); if (copy.fitsInLong()) { - format(((DecimalQuantity &)number).toLong(), appendTo, posIter, status); + format(number.toLong(), appendTo, posIter, status); } else { copy.roundToMagnitude(0, number::impl::RoundingMode::UNUM_ROUND_HALFEVEN, status); if (copy.fitsInLong()) { - format(number.toLong(), appendTo, posIter, status); + format(number.toDouble(), appendTo, posIter, status); } else { // We're outside of our normal range that this framework can handle. @@ -1153,12 +1153,12 @@ RuleBasedNumberFormat::format(const DecimalQuantity &number, } DecimalQuantity copy(number); if (copy.fitsInLong()) { - format(((DecimalQuantity &)number).toLong(), appendTo, pos, status); + format(number.toLong(), appendTo, pos, status); } else { copy.roundToMagnitude(0, number::impl::RoundingMode::UNUM_ROUND_HALFEVEN, status); if (copy.fitsInLong()) { - format(number.toLong(), appendTo, pos, status); + format(number.toDouble(), appendTo, pos, status); } else { // We're outside of our normal range that this framework can handle. diff --git a/icu4c/source/test/intltest/numfmtst.cpp b/icu4c/source/test/intltest/numfmtst.cpp index c1c194cfb1..8e75246e2e 100644 --- a/icu4c/source/test/intltest/numfmtst.cpp +++ b/icu4c/source/test/intltest/numfmtst.cpp @@ -3691,6 +3691,7 @@ NumberFormatTest::TestSpaceParsing() { foo->setLenient(DATA[i].lenient); Formattable result; foo->parse(stringToBeParsed, result, parsePosition); + logln("Parsing: " + stringToBeParsed); if (parsePosition.getIndex() != parsedPosition || parsePosition.getErrorIndex() != errorIndex) { errln("FAILED parse " + stringToBeParsed + "; lenient: " + DATA[i].lenient + "; wrong position, expected: (" + parsedPosition + ", " + errorIndex + "); got (" + parsePosition.getIndex() + ", " + parsePosition.getErrorIndex() + ")"); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java index b5fac76526..72ab98e4e3 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java @@ -196,10 +196,10 @@ public class NumberParserImpl { if (!isStrict) { parser.addMatcher(PlusSignMatcher.getInstance(symbols, false)); parser.addMatcher(MinusSignMatcher.getInstance(symbols, false)); - parser.addMatcher(NanMatcher.getInstance(symbols, parseFlags)); parser.addMatcher(PercentMatcher.getInstance(symbols)); parser.addMatcher(PermilleMatcher.getInstance(symbols)); } + parser.addMatcher(NanMatcher.getInstance(symbols, parseFlags)); parser.addMatcher(InfinityMatcher.getInstance(symbols)); String padString = properties.getPadString(); if (padString != null && !ignorables.getSet().contains(padString)) { diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/NFSubstitution.java b/icu4j/main/classes/core/src/com/ibm/icu/text/NFSubstitution.java index 15ca301ed8..e2a992bc45 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/NFSubstitution.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/NFSubstitution.java @@ -1317,7 +1317,7 @@ class FractionalPartSubstitution extends NFSubstitution { int digit; DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD(); - int leadingZeros = 0; + int totalDigits = 0; while (workText.length() > 0 && workPos.getIndex() != 0) { workPos.setIndex(0); digit = ruleSet.parse(workText, workPos, 10, nonNumericalExecutedRuleMask).intValue(); @@ -1329,12 +1329,8 @@ class FractionalPartSubstitution extends NFSubstitution { } if (workPos.getIndex() != 0) { - if (digit == 0) { - leadingZeros++; - } else { - fq.appendDigit((byte) digit, leadingZeros, false); - leadingZeros = 0; - } + fq.appendDigit((byte) digit, 0, true); + totalDigits++; parsePosition.setIndex(parsePosition.getIndex() + workPos.getIndex()); workText = workText.substring(workPos.getIndex()); @@ -1344,6 +1340,7 @@ class FractionalPartSubstitution extends NFSubstitution { } } } + fq.adjustMagnitude(-totalDigits); result = fq.toDouble(); result = composeRuleValue(result, baseValue); From e74395dce9be7b5a2b484f9d47fa71a9ce30f2fc Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Fri, 6 Apr 2018 21:46:18 +0000 Subject: [PATCH 083/129] ICU-13684 Making DecimalMatcher no longer consume trailing grouping separators, which is consistent with previous behavior. X-SVN-Rev: 41207 --- .../com/ibm/icu/impl/number/parse/DecimalMatcher.java | 6 ++++++ .../ibm/icu/dev/data/numberformattestspecification.txt | 6 +++--- .../com/ibm/icu/dev/test/format/NumberFormatTest.java | 10 ++++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/DecimalMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/DecimalMatcher.java index 5a622611da..6d97d3dea9 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/DecimalMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/DecimalMatcher.java @@ -278,6 +278,12 @@ public class DecimalMatcher implements NumberParseMatcher { break; } + // Back up if there was a trailing grouping separator + if (backupOffset != -1) { + segment.setOffset(backupOffset); + hasPartialPrefix = true; // redundant with `groupingOverlap == segment.length()` + } + // Check the final grouping for validity if (requireGroupingMatch && !seenDecimal diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt b/icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt index 1f11f8da7c..b38a420f2c 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt @@ -743,7 +743,7 @@ parse output breaks (34 25E-1) -342.5 K (34,,25E-1) -342.5 // J doesn't allow trailing separators before E but C does -(34,,25,E-1) -342.5 J +(34,,25,E-1) -342.5 JP (34 25 E-1) -342.5 JK (34,,25 E-1) -342.5 CJK // Spaces are not allowed after exponent symbol @@ -807,8 +807,8 @@ parse output breaks // C parses until trailing separators, but sees -1234 1,234,,,+ 1234 JKC 1,234- -1234 -// J bails because of trailing separators -1,234,- -1234 J +// J and P bail because of trailing separators +1,234,- -1234 JP // J bails here too 1234 - -1234 J diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java index 06ece47211..679031eea2 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java @@ -5984,4 +5984,14 @@ public class NumberFormatTest extends TestFmwk { expect2(df, 45, "USD 45.00"); expect2(df, -45, "-45.00 USD"); } + + @Test + public void test13684_FrenchPercentParsing() { + NumberFormat numberFormat = NumberFormat.getPercentInstance(ULocale.FRENCH); + numberFormat.setParseStrict(true); + ParsePosition ppos = new ParsePosition(0); + Number percentage = numberFormat.parse("8\u00A0%", ppos); + assertEquals("Should parse successfully", 0.08, percentage.doubleValue()); + assertEquals("Should consume whole string", 3, ppos.getIndex()); + } } From 06485f3b6bd8c5a9ff6d3da687cfd34eff57a946 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Sat, 7 Apr 2018 08:49:11 +0000 Subject: [PATCH 084/129] ICU-13634 Fixing CurrencyPluralInfo support in formatting, allowing for currency long names to be formatted. X-SVN-Rev: 41211 --- icu4c/source/i18n/decimfmt.cpp | 9 ++++ icu4c/source/i18n/number_decimalquantity.cpp | 34 ++++++++++++- icu4c/source/i18n/number_decimalquantity.h | 10 ++++ icu4c/source/i18n/number_formatimpl.cpp | 2 +- icu4c/source/i18n/number_mapper.cpp | 9 +++- icu4c/source/i18n/number_patternstring.cpp | 11 +++- icu4c/source/i18n/number_patternstring.h | 14 +++++- icu4c/source/test/intltest/numbertest.h | 1 + .../intltest/numbertest_decimalquantity.cpp | 50 ++++++++++++++++++- icu4c/source/test/intltest/numfmtst.cpp | 43 ++++++++-------- 10 files changed, 155 insertions(+), 28 deletions(-) diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp index 569a2a8a7b..bf975a31d9 100644 --- a/icu4c/source/i18n/decimfmt.cpp +++ b/icu4c/source/i18n/decimfmt.cpp @@ -62,6 +62,15 @@ DecimalFormat::DecimalFormat(const UnicodeString& pattern, DecimalFormatSymbols* } else { setPropertiesFromPattern(pattern, IGNORE_ROUNDING_IF_CURRENCY, status); } + // Note: in Java, CurrencyPluralInfo is set in NumberFormat.java, but in C++, it is not set there, + // so we have to set it here. + if (style == UNumberFormatStyle::UNUM_CURRENCY_PLURAL) { + LocalPointer cpi( + new CurrencyPluralInfo(fSymbols->getLocale(), status), + status); + if (U_FAILURE(status)) { return; } + fProperties->currencyPluralInfo.fPtr.adoptInstead(cpi.orphan()); + } refreshFormatter(status); } diff --git a/icu4c/source/i18n/number_decimalquantity.cpp b/icu4c/source/i18n/number_decimalquantity.cpp index 8b369547ae..9c16fe4c3e 100644 --- a/icu4c/source/i18n/number_decimalquantity.cpp +++ b/icu4c/source/i18n/number_decimalquantity.cpp @@ -84,11 +84,29 @@ DecimalQuantity::DecimalQuantity(const DecimalQuantity &other) { *this = other; } +DecimalQuantity::DecimalQuantity(DecimalQuantity&& src) U_NOEXCEPT { + *this = std::move(src); +} + DecimalQuantity &DecimalQuantity::operator=(const DecimalQuantity &other) { if (this == &other) { return *this; } copyBcdFrom(other); + copyFieldsFrom(other); + return *this; +} + +DecimalQuantity& DecimalQuantity::operator=(DecimalQuantity&& src) U_NOEXCEPT { + if (this == &src) { + return *this; + } + moveBcdFrom(src); + copyFieldsFrom(src); + return *this; +} + +void DecimalQuantity::copyFieldsFrom(const DecimalQuantity& other) { bogus = other.bogus; lOptPos = other.lOptPos; lReqPos = other.lReqPos; @@ -100,7 +118,6 @@ DecimalQuantity &DecimalQuantity::operator=(const DecimalQuantity &other) { origDouble = other.origDouble; origDelta = other.origDelta; isApproximate = other.isApproximate; - return *this; } void DecimalQuantity::clear() { @@ -474,7 +491,6 @@ void DecimalQuantity::_setToDecNum(const DecNum& decnum, UErrorCode& status) { int64_t DecimalQuantity::toLong() const { // NOTE: Call sites should be guarded by fitsInLong(), like this: // if (dq.fitsInLong()) { /* use dq.toLong() */ } else { /* use some fallback */ } - U_ASSERT(fitsInLong()); int64_t result = 0L; for (int32_t magnitude = scale + precision - 1; magnitude >= 0; magnitude--) { result = result * 10 + getDigitPos(magnitude - scale); @@ -1032,6 +1048,20 @@ void DecimalQuantity::copyBcdFrom(const DecimalQuantity &other) { } } +void DecimalQuantity::moveBcdFrom(DecimalQuantity &other) { + setBcdToZero(); + if (other.usingBytes) { + usingBytes = true; + fBCD.bcdBytes.ptr = other.fBCD.bcdBytes.ptr; + fBCD.bcdBytes.len = other.fBCD.bcdBytes.len; + // Take ownership away from the old instance: + other.fBCD.bcdBytes.ptr = nullptr; + other.usingBytes = false; + } else { + fBCD.bcdLong = other.fBCD.bcdLong; + } +} + const char16_t* DecimalQuantity::checkHealth() const { if (usingBytes) { if (precision == 0) { return u"Zero precision but we are in byte mode"; } diff --git a/icu4c/source/i18n/number_decimalquantity.h b/icu4c/source/i18n/number_decimalquantity.h index bcd3aa7cf6..268cdd9967 100644 --- a/icu4c/source/i18n/number_decimalquantity.h +++ b/icu4c/source/i18n/number_decimalquantity.h @@ -35,6 +35,9 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { /** Copy constructor. */ DecimalQuantity(const DecimalQuantity &other); + /** Move constructor. */ + DecimalQuantity(DecimalQuantity &&src) U_NOEXCEPT; + DecimalQuantity(); ~DecimalQuantity() override; @@ -46,6 +49,9 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { */ DecimalQuantity &operator=(const DecimalQuantity &other); + /** Move assignment */ + DecimalQuantity &operator=(DecimalQuantity&& src) U_NOEXCEPT; + /** * Sets the minimum and maximum integer digits that this {@link DecimalQuantity} should generate. * This method does not perform rounding. @@ -434,8 +440,12 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { void readDoubleConversionToBcd(const char* buffer, int32_t length, int32_t point); + void copyFieldsFrom(const DecimalQuantity& other); + void copyBcdFrom(const DecimalQuantity &other); + void moveBcdFrom(DecimalQuantity& src); + /** * Removes trailing zeros from the BCD (adjusting the scale as required) and then computes the * precision. The precision is the number of digits in the number up through the greatest nonzero diff --git a/icu4c/source/i18n/number_formatimpl.cpp b/icu4c/source/i18n/number_formatimpl.cpp index 7b88dc3ec8..62947be571 100644 --- a/icu4c/source/i18n/number_formatimpl.cpp +++ b/icu4c/source/i18n/number_formatimpl.cpp @@ -233,7 +233,7 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe, if (info.exists) { pattern = info.pattern; // It's clunky to clone an object here, but this code is not frequently executed. - DecimalFormatSymbols* symbols = new DecimalFormatSymbols(*fMicros.symbols); + auto* symbols = new DecimalFormatSymbols(*fMicros.symbols); fMicros.symbols = symbols; fSymbols.adoptInstead(symbols); symbols->setSymbol( diff --git a/icu4c/source/i18n/number_mapper.cpp b/icu4c/source/i18n/number_mapper.cpp index 9acf60382b..ef13267dcd 100644 --- a/icu4c/source/i18n/number_mapper.cpp +++ b/icu4c/source/i18n/number_mapper.cpp @@ -304,6 +304,8 @@ MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& propert void PropertiesAffixPatternProvider::setTo(const DecimalFormatProperties& properties, UErrorCode&) { + fBogus = false; + // There are two ways to set affixes in DecimalFormat: via the pattern string (applyPattern), and via the // explicit setters (setPositivePrefix and friends). The way to resolve the settings is as follows: // @@ -432,11 +434,16 @@ bool PropertiesAffixPatternProvider::hasBody() const { void CurrencyPluralInfoAffixProvider::setTo(const CurrencyPluralInfo& cpi, UErrorCode& status) { + fBogus = false; for (int32_t plural = 0; plural < StandardPlural::COUNT; plural++) { const char* keyword = StandardPlural::getKeyword(static_cast(plural)); UnicodeString patternString; patternString = cpi.getCurrencyPluralPattern(keyword, patternString); - PatternParser::parseToPatternInfo(patternString, affixesByPlural[plural], status); + // ParsedPatternInfo does not support being overwritten if it was written previously; + // instead, we need to write to a fresh instance and move it into place. + ParsedPatternInfo temp; + PatternParser::parseToPatternInfo(patternString, temp, status); + affixesByPlural[plural] = std::move(temp); } } diff --git a/icu4c/source/i18n/number_patternstring.cpp b/icu4c/source/i18n/number_patternstring.cpp index 8ea130fb4a..e32ad6b130 100644 --- a/icu4c/source/i18n/number_patternstring.cpp +++ b/icu4c/source/i18n/number_patternstring.cpp @@ -20,6 +20,7 @@ using namespace icu; using namespace icu::number; using namespace icu::number::impl; + void PatternParser::parseToPatternInfo(const UnicodeString& patternString, ParsedPatternInfo& patternInfo, UErrorCode& status) { patternInfo.consumePattern(patternString, status); @@ -44,6 +45,7 @@ PatternParser::parseToExistingProperties(const UnicodeString& pattern, DecimalFo parseToExistingPropertiesImpl(pattern, properties, ignoreRounding, status); } + char16_t ParsedPatternInfo::charAt(int32_t flags, int32_t index) const { const Endpoints& endpoints = getEndpoints(flags); if (index < 0 || index >= endpoints.end - endpoints.start) { @@ -134,6 +136,10 @@ void ParsedPatternInfo::consumePattern(const UnicodeString& patternString, UErro if (U_FAILURE(status)) { return; } this->pattern = patternString; + // This class is not intended for writing twice! + // Use move assignment to overwrite instead. + U_ASSERT(state.offset == 0); + // pattern := subpattern (';' subpattern)? currentSubpattern = &positive; consumeSubpattern(status); @@ -178,12 +184,13 @@ void ParsedPatternInfo::consumePadding(PadPosition paddingLocation, UErrorCode& if (state.peek() != u'*') { return; } - if (!currentSubpattern->paddingLocation.isNull()) { + if (currentSubpattern->hasPadding) { state.toParseException(u"Cannot have multiple pad specifiers"); status = U_MULTIPLE_PAD_SPECIFIERS; return; } currentSubpattern->paddingLocation = paddingLocation; + currentSubpattern->hasPadding = true; state.next(); // consume the '*' currentSubpattern->paddingEndpoints.start = state.offset; consumeLiteral(status); @@ -580,7 +587,7 @@ PatternParser::patternInfoToProperties(DecimalFormatProperties& properties, Pars UnicodeString posSuffix = patternInfo.getString(0); // Padding settings - if (!positive.paddingLocation.isNull()) { + if (positive.hasPadding) { // The width of the positive prefix and suffix templates are included in the padding int paddingWidth = positive.widthExceptAffixes + AffixUtils::estimateLength(UnicodeStringCharSequence(posPrefix), status) + diff --git a/icu4c/source/i18n/number_patternstring.h b/icu4c/source/i18n/number_patternstring.h index 742ee3547c..a87cc7415c 100644 --- a/icu4c/source/i18n/number_patternstring.h +++ b/icu4c/source/i18n/number_patternstring.h @@ -41,7 +41,9 @@ struct U_I18N_API ParsedSubpatternInfo { int32_t fractionTotal = 0; // for convenience bool hasDecimal = false; int32_t widthExceptAffixes = 0; - NullableValue paddingLocation; + // Note: NullableValue causes issues here with std::move. + bool hasPadding = false; + UNumberFormatPadPosition paddingLocation = UNUM_PAD_BEFORE_PREFIX; DecimalQuantity rounding; bool exponentHasPlusSign = false; int32_t exponentZeros = 0; @@ -67,6 +69,9 @@ struct U_I18N_API ParsedPatternInfo : public AffixPatternProvider, public UMemor ~ParsedPatternInfo() U_OVERRIDE = default; + // Need to declare this explicitly because of the destructor + ParsedPatternInfo& operator=(ParsedPatternInfo&& src) U_NOEXCEPT = default; + static int32_t getLengthFromEndpoints(const Endpoints& endpoints); char16_t charAt(int32_t flags, int32_t index) const U_OVERRIDE; @@ -95,6 +100,13 @@ struct U_I18N_API ParsedPatternInfo : public AffixPatternProvider, public UMemor explicit ParserState(const UnicodeString& _pattern) : pattern(_pattern) {}; + ParserState& operator=(ParserState&& src) U_NOEXCEPT { + // Leave pattern reference alone; it will continue to point to the same place in memory, + // which gets overwritten by ParsedPatternInfo's implicit move assignment. + offset = src.offset; + return *this; + } + UChar32 peek(); UChar32 next(); diff --git a/icu4c/source/test/intltest/numbertest.h b/icu4c/source/test/intltest/numbertest.h index 2da93bcbb8..a3678d5913 100644 --- a/icu4c/source/test/intltest/numbertest.h +++ b/icu4c/source/test/intltest/numbertest.h @@ -116,6 +116,7 @@ class DecimalQuantityTest : public IntlTest { public: void testDecimalQuantityBehaviorStandalone(); void testSwitchStorage(); + void testCopyMove(); void testAppend(); void testConvertToAccurateDouble(); void testUseApproximateDoubleWhenAble(); diff --git a/icu4c/source/test/intltest/numbertest_decimalquantity.cpp b/icu4c/source/test/intltest/numbertest_decimalquantity.cpp index ba1a67c215..320dbe0eab 100644 --- a/icu4c/source/test/intltest/numbertest_decimalquantity.cpp +++ b/icu4c/source/test/intltest/numbertest_decimalquantity.cpp @@ -16,7 +16,8 @@ void DecimalQuantityTest::runIndexedTest(int32_t index, UBool exec, const char * } TESTCASE_AUTO_BEGIN; TESTCASE_AUTO(testDecimalQuantityBehaviorStandalone); - TESTCASE_AUTO(testSwitchStorage); + TESTCASE_AUTO(testSwitchStorage);; + TESTCASE_AUTO(testCopyMove); TESTCASE_AUTO(testAppend); TESTCASE_AUTO(testConvertToAccurateDouble); TESTCASE_AUTO(testUseApproximateDoubleWhenAble); @@ -119,6 +120,53 @@ void DecimalQuantityTest::testSwitchStorage() { assertHealth(fq); } +void DecimalQuantityTest::testCopyMove() { + // Small numbers (fits in BCD long) + { + DecimalQuantity a; + a.setToLong(1234123412341234L); + DecimalQuantity b = a; // copy constructor + assertToStringAndHealth(a, u""); + assertToStringAndHealth(b, u""); + DecimalQuantity c(std::move(a)); // move constructor + assertToStringAndHealth(c, u""); + c.setToLong(54321L); + assertToStringAndHealth(c, u""); + c = b; // copy assignment + assertToStringAndHealth(b, u""); + assertToStringAndHealth(c, u""); + b.setToLong(45678); + c.setToLong(56789); + c = std::move(b); // move assignment + assertToStringAndHealth(c, u""); + a = std::move(c); // move assignment to a defunct object + assertToStringAndHealth(a, u""); + } + + // Large numbers (requires byte allocation) + { + IcuTestErrorCode status(*this, "testCopyMove"); + DecimalQuantity a; + a.setToDecNumber({"1234567890123456789", -1}, status); + DecimalQuantity b = a; // copy constructor + assertToStringAndHealth(a, u""); + assertToStringAndHealth(b, u""); + DecimalQuantity c(std::move(a)); // move constructor + assertToStringAndHealth(c, u""); + c.setToDecNumber({"9876543210987654321", -1}, status); + assertToStringAndHealth(c, u""); + c = b; // copy assignment + assertToStringAndHealth(b, u""); + assertToStringAndHealth(c, u""); + b.setToDecNumber({"876543210987654321", -1}, status); + c.setToDecNumber({"987654321098765432", -1}, status); + c = std::move(b); // move assignment + assertToStringAndHealth(c, u""); + a = std::move(c); // move assignment to a defunct object + assertToStringAndHealth(a, u""); + } +} + void DecimalQuantityTest::testAppend() { DecimalQuantity fq; fq.appendDigit(1, 0, true); diff --git a/icu4c/source/test/intltest/numfmtst.cpp b/icu4c/source/test/intltest/numfmtst.cpp index 8e75246e2e..54817637b4 100644 --- a/icu4c/source/test/intltest/numfmtst.cpp +++ b/icu4c/source/test/intltest/numfmtst.cpp @@ -3804,13 +3804,13 @@ NumberFormatTest::TestMultiCurrencySign() { // currency format using currency ISO name, such as "USD", // currency format using plural name, such as "US dollars". // for US locale - {"en_US", "\\u00A4#,##0.00;-\\u00A4#,##0.00", "1234.56", "$1,234.56", "USD1,234.56", "US dollars1,234.56"}, - {"en_US", "\\u00A4#,##0.00;-\\u00A4#,##0.00", "-1234.56", "-$1,234.56", "-USD1,234.56", "-US dollars1,234.56"}, - {"en_US", "\\u00A4#,##0.00;-\\u00A4#,##0.00", "1", "$1.00", "USD1.00", "US dollars1.00"}, + {"en_US", "\\u00A4#,##0.00;-\\u00A4#,##0.00", "1234.56", "$1,234.56", "USD\\u00A01,234.56", "US dollars\\u00A01,234.56"}, + {"en_US", "\\u00A4#,##0.00;-\\u00A4#,##0.00", "-1234.56", "-$1,234.56", "-USD\\u00A01,234.56", "-US dollars\\u00A01,234.56"}, + {"en_US", "\\u00A4#,##0.00;-\\u00A4#,##0.00", "1", "$1.00", "USD\\u00A01.00", "US dollars\\u00A01.00"}, // for CHINA locale - {"zh_CN", "\\u00A4#,##0.00;(\\u00A4#,##0.00)", "1234.56", "\\uFFE51,234.56", "CNY1,234.56", "\\u4EBA\\u6C11\\u5E011,234.56"}, - {"zh_CN", "\\u00A4#,##0.00;(\\u00A4#,##0.00)", "-1234.56", "(\\uFFE51,234.56)", "(CNY1,234.56)", "(\\u4EBA\\u6C11\\u5E011,234.56)"}, - {"zh_CN", "\\u00A4#,##0.00;(\\u00A4#,##0.00)", "1", "\\uFFE51.00", "CNY1.00", "\\u4EBA\\u6C11\\u5E011.00"} + {"zh_CN", "\\u00A4#,##0.00;(\\u00A4#,##0.00)", "1234.56", "\\uFFE51,234.56", "CNY\\u00A01,234.56", "\\u4EBA\\u6C11\\u5E01\\u00A01,234.56"}, + {"zh_CN", "\\u00A4#,##0.00;(\\u00A4#,##0.00)", "-1234.56", "(\\uFFE51,234.56)", "(CNY\\u00A01,234.56)", "(\\u4EBA\\u6C11\\u5E01\\u00A01,234.56)"}, + {"zh_CN", "\\u00A4#,##0.00;(\\u00A4#,##0.00)", "1", "\\uFFE51.00", "CNY\\u00A01.00", "\\u4EBA\\u6C11\\u5E01\\u00A01.00"} }; const UChar doubleCurrencySign[] = {0xA4, 0xA4, 0}; @@ -3896,7 +3896,7 @@ NumberFormatTest::TestCurrencyFormatForMixParsing() { "$1,234.56", // string to be parsed "USD1,234.56", "US dollars1,234.56", - "1,234.56 US dollars" + "1,234.56 US dollars" // NOTE: Fails in 62 because currency format is not compatible with pattern }; const CurrencyAmount* curramt = NULL; for (uint32_t i = 0; i < UPRV_LENGTHOF(formats); ++i) { @@ -3949,10 +3949,10 @@ NumberFormatTest::TestDecimalFormatCurrencyParse() { // string to be parsed, the parsed result (number) {"$1.00", "1"}, {"USD1.00", "1"}, - {"1.00 US dollar", "1"}, + {"1.00 US dollar", "1"}, // NOTE: Fails in 62 because currency format is not compatible with pattern {"$1,234.56", "1234.56"}, {"USD1,234.56", "1234.56"}, - {"1,234.56 US dollar", "1234.56"}, + {"1,234.56 US dollar", "1234.56"}, // NOTE: Fails in 62 because currency format is not compatible with pattern }; for (uint32_t i = 0; i < UPRV_LENGTHOF(DATA); ++i) { UnicodeString stringToBeParsed = ctou(DATA[i][0]); @@ -3960,6 +3960,7 @@ NumberFormatTest::TestDecimalFormatCurrencyParse() { UErrorCode status = U_ZERO_ERROR; Formattable result; fmt->parse(stringToBeParsed, result, status); + logln((UnicodeString)"Input: " + stringToBeParsed + "; output: " + result.getDouble(status)); if (U_FAILURE(status) || (result.getType() == Formattable::kDouble && result.getDouble() != parsedResult) || @@ -3983,13 +3984,13 @@ NumberFormatTest::TestCurrencyIsoPluralFormat() { // format result using ISOCURRENCYSTYLE, // format result using PLURALCURRENCYSTYLE, - {"en_US", "1", "USD", "$1.00", "USD1.00", "1.00 US dollars"}, - {"en_US", "1234.56", "USD", "$1,234.56", "USD1,234.56", "1,234.56 US dollars"}, - {"en_US", "-1234.56", "USD", "-$1,234.56", "-USD1,234.56", "-1,234.56 US dollars"}, - {"zh_CN", "1", "USD", "US$1.00", "USD1.00", "1.00\\u7F8E\\u5143"}, - {"zh_CN", "1234.56", "USD", "US$1,234.56", "USD1,234.56", "1,234.56\\u7F8E\\u5143"}, - {"zh_CN", "1", "CNY", "\\uFFE51.00", "CNY1.00", "1.00\\u4EBA\\u6C11\\u5E01"}, - {"zh_CN", "1234.56", "CNY", "\\uFFE51,234.56", "CNY1,234.56", "1,234.56\\u4EBA\\u6C11\\u5E01"}, + {"en_US", "1", "USD", "$1.00", "USD\\u00A01.00", "1.00 US dollars"}, + {"en_US", "1234.56", "USD", "$1,234.56", "USD\\u00A01,234.56", "1,234.56 US dollars"}, + {"en_US", "-1234.56", "USD", "-$1,234.56", "-USD\\u00A01,234.56", "-1,234.56 US dollars"}, + {"zh_CN", "1", "USD", "US$1.00", "USD\\u00A01.00", "1.00\\u7F8E\\u5143"}, + {"zh_CN", "1234.56", "USD", "US$1,234.56", "USD\\u00A01,234.56", "1,234.56\\u7F8E\\u5143"}, + {"zh_CN", "1", "CNY", "\\uFFE51.00", "CNY\\u00A01.00", "1.00\\u4EBA\\u6C11\\u5E01"}, + {"zh_CN", "1234.56", "CNY", "\\uFFE51,234.56", "CNY\\u00A01,234.56", "1,234.56\\u4EBA\\u6C11\\u5E01"}, {"ru_RU", "1", "RUB", "1,00\\u00A0\\u20BD", "1,00\\u00A0RUB", "1,00 \\u0440\\u043E\\u0441\\u0441\\u0438\\u0439\\u0441\\u043A\\u043E\\u0433\\u043E \\u0440\\u0443\\u0431\\u043B\\u044F"}, {"ru_RU", "2", "RUB", "2,00\\u00A0\\u20BD", "2,00\\u00A0RUB", "2,00 \\u0440\\u043E\\u0441\\u0441\\u0438\\u0439\\u0441\\u043A\\u043E\\u0433\\u043E \\u0440\\u0443\\u0431\\u043B\\u044F"}, {"ru_RU", "5", "RUB", "5,00\\u00A0\\u20BD", "5,00\\u00A0RUB", "5,00 \\u0440\\u043E\\u0441\\u0441\\u0438\\u0439\\u0441\\u043A\\u043E\\u0433\\u043E \\u0440\\u0443\\u0431\\u043B\\u044F"}, @@ -4005,12 +4006,14 @@ NumberFormatTest::TestCurrencyIsoPluralFormat() { }; for (int32_t i=0; i Date: Sat, 7 Apr 2018 11:10:08 +0000 Subject: [PATCH 085/129] ICU-13634 Fixing various issues in order to make currencies round-trip in strict mode. X-SVN-Rev: 41212 --- icu4c/source/i18n/numparse_affixes.cpp | 2 +- icu4c/source/i18n/numparse_compositions.cpp | 5 ++++ icu4c/source/i18n/numparse_impl.cpp | 19 +++++++++++---- icu4c/source/test/intltest/numfmtst.cpp | 12 ++++++---- .../icu/impl/number/parse/AffixMatcher.java | 2 +- .../impl/number/parse/NumberParserImpl.java | 14 +++++++---- .../icu/impl/number/parse/SeriesMatcher.java | 5 ++++ .../icu/dev/test/format/NumberFormatTest.java | 23 +++++++++++++++++++ 8 files changed, 67 insertions(+), 15 deletions(-) diff --git a/icu4c/source/i18n/numparse_affixes.cpp b/icu4c/source/i18n/numparse_affixes.cpp index 013cc01ff8..0ac1fe7c0e 100644 --- a/icu4c/source/i18n/numparse_affixes.cpp +++ b/icu4c/source/i18n/numparse_affixes.cpp @@ -304,7 +304,7 @@ void AffixMatcherWarehouse::createAffixMatchers(const AffixPatternProvider& patt UnicodeString sb; bool includeUnpaired = 0 != (parseFlags & PARSE_FLAG_INCLUDE_UNPAIRED_AFFIXES); UNumberSignDisplay signDisplay = (0 != (parseFlags & PARSE_FLAG_PLUS_SIGN_ALLOWED)) ? UNUM_SIGN_ALWAYS - : UNUM_SIGN_NEVER; + : UNUM_SIGN_AUTO; int32_t numAffixMatchers = 0; int32_t numAffixPatternMatchers = 0; diff --git a/icu4c/source/i18n/numparse_compositions.cpp b/icu4c/source/i18n/numparse_compositions.cpp index 06aa476a29..8c7b7d251c 100644 --- a/icu4c/source/i18n/numparse_compositions.cpp +++ b/icu4c/source/i18n/numparse_compositions.cpp @@ -40,6 +40,11 @@ bool SeriesMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCo } else if (success) { // Match succeeded, and this is NOT a flexible matcher. Proceed to the next matcher. it++; + // Small hack: if there is another matcher coming, do not accept trailing weak chars. + // Needed for proper handling of currency spacing. + if (it < end() && segment.getOffset() != result.charEnd && result.charEnd > matcherOffset) { + segment.setOffset(result.charEnd); + } } else if (isFlexible) { // Match failed, and this is a flexible matcher. Try again with the next matcher. it++; diff --git a/icu4c/source/i18n/numparse_impl.cpp b/icu4c/source/i18n/numparse_impl.cpp index b8240e7f40..54609151bc 100644 --- a/icu4c/source/i18n/numparse_impl.cpp +++ b/icu4c/source/i18n/numparse_impl.cpp @@ -81,12 +81,23 @@ NumberParserImpl::createParserFromProperties(const number::impl::DecimalFormatPr const DecimalFormatSymbols& symbols, bool parseCurrency, UErrorCode& status) { Locale locale = symbols.getLocale(); - PropertiesAffixPatternProvider patternInfo(properties, status); + PropertiesAffixPatternProvider localPAPP; + CurrencyPluralInfoAffixProvider localCPIAP; + AffixPatternProvider* affixProvider; + if (properties.currencyPluralInfo.fPtr.isNull()) { + localPAPP.setTo(properties, status); + affixProvider = &localPAPP; + } else { + localCPIAP.setTo(*properties.currencyPluralInfo.fPtr, status); + affixProvider = &localCPIAP; + } + if (affixProvider == nullptr || U_FAILURE(status)) { return nullptr; } CurrencyUnit currency = resolveCurrency(properties, locale, status); CurrencySymbols currencySymbols(currency, locale, symbols, status); bool isStrict = properties.parseMode.getOrDefault(PARSE_MODE_STRICT) == PARSE_MODE_STRICT; Grouper grouper = Grouper::forProperties(properties); int parseFlags = 0; + if (affixProvider == nullptr || U_FAILURE(status)) { return nullptr; } // Fraction grouping is disabled by default because it has never been supported in DecimalFormat parseFlags |= PARSE_FLAG_FRACTION_GROUPING_DISABLED; if (!properties.parseCaseSensitive) { @@ -109,7 +120,7 @@ NumberParserImpl::createParserFromProperties(const number::impl::DecimalFormatPr if (grouper.getPrimary() <= 0) { parseFlags |= PARSE_FLAG_GROUPING_DISABLED; } - if (parseCurrency || patternInfo.hasCurrencySign()) { + if (parseCurrency || affixProvider->hasCurrencySign()) { parseFlags |= PARSE_FLAG_MONETARY_SEPARATORS; } @@ -129,13 +140,13 @@ NumberParserImpl::createParserFromProperties(const number::impl::DecimalFormatPr parser->fLocalMatchers.affixTokenMatcherWarehouse = {&affixSetupData}; parser->fLocalMatchers.affixMatcherWarehouse = {&parser->fLocalMatchers.affixTokenMatcherWarehouse}; parser->fLocalMatchers.affixMatcherWarehouse.createAffixMatchers( - patternInfo, *parser, ignorables, parseFlags, status); + *affixProvider, *parser, ignorables, parseFlags, status); //////////////////////// /// CURRENCY MATCHER /// //////////////////////// - if (parseCurrency || patternInfo.hasCurrencySign()) { + if (parseCurrency || affixProvider->hasCurrencySign()) { parser->addMatcher(parser->fLocalMatchers.currency = {currencySymbols, symbols, status}); } diff --git a/icu4c/source/test/intltest/numfmtst.cpp b/icu4c/source/test/intltest/numfmtst.cpp index 54817637b4..bf070fa75a 100644 --- a/icu4c/source/test/intltest/numfmtst.cpp +++ b/icu4c/source/test/intltest/numfmtst.cpp @@ -3987,10 +3987,10 @@ NumberFormatTest::TestCurrencyIsoPluralFormat() { {"en_US", "1", "USD", "$1.00", "USD\\u00A01.00", "1.00 US dollars"}, {"en_US", "1234.56", "USD", "$1,234.56", "USD\\u00A01,234.56", "1,234.56 US dollars"}, {"en_US", "-1234.56", "USD", "-$1,234.56", "-USD\\u00A01,234.56", "-1,234.56 US dollars"}, - {"zh_CN", "1", "USD", "US$1.00", "USD\\u00A01.00", "1.00\\u7F8E\\u5143"}, - {"zh_CN", "1234.56", "USD", "US$1,234.56", "USD\\u00A01,234.56", "1,234.56\\u7F8E\\u5143"}, - {"zh_CN", "1", "CNY", "\\uFFE51.00", "CNY\\u00A01.00", "1.00\\u4EBA\\u6C11\\u5E01"}, - {"zh_CN", "1234.56", "CNY", "\\uFFE51,234.56", "CNY\\u00A01,234.56", "1,234.56\\u4EBA\\u6C11\\u5E01"}, + {"zh_CN", "1", "USD", "US$1.00", "USD\\u00A01.00", "1.00\\u00A0\\u7F8E\\u5143"}, + {"zh_CN", "1234.56", "USD", "US$1,234.56", "USD\\u00A01,234.56", "1,234.56\\u00A0\\u7F8E\\u5143"}, + {"zh_CN", "1", "CNY", "\\uFFE51.00", "CNY\\u00A01.00", "1.00\\u00A0\\u4EBA\\u6C11\\u5E01"}, + {"zh_CN", "1234.56", "CNY", "\\uFFE51,234.56", "CNY\\u00A01,234.56", "1,234.56\\u00A0\\u4EBA\\u6C11\\u5E01"}, {"ru_RU", "1", "RUB", "1,00\\u00A0\\u20BD", "1,00\\u00A0RUB", "1,00 \\u0440\\u043E\\u0441\\u0441\\u0438\\u0439\\u0441\\u043A\\u043E\\u0433\\u043E \\u0440\\u0443\\u0431\\u043B\\u044F"}, {"ru_RU", "2", "RUB", "2,00\\u00A0\\u20BD", "2,00\\u00A0RUB", "2,00 \\u0440\\u043E\\u0441\\u0441\\u0438\\u0439\\u0441\\u043A\\u043E\\u0433\\u043E \\u0440\\u0443\\u0431\\u043B\\u044F"}, {"ru_RU", "5", "RUB", "5,00\\u00A0\\u20BD", "5,00\\u00A0RUB", "5,00 \\u0440\\u043E\\u0441\\u0441\\u0438\\u0439\\u0441\\u043A\\u043E\\u0433\\u043E \\u0440\\u0443\\u0431\\u043B\\u044F"}, @@ -4040,7 +4040,9 @@ NumberFormatTest::TestCurrencyIsoPluralFormat() { errln("FAIL: Expected " + formatResult + " actual: " + strBuf); } // test parsing, and test parsing for all currency formats. - for (int j = 3; j < 6; ++j) { + // NOTE: ICU 62 requires that the currency format match the pattern in strict mode. + //for (int j = 3; j < 6; ++j) { + for (int j = 3 + kIndex; j <= 3 + kIndex; j++) { // DATA[i][3] is the currency format result using // CURRENCYSTYLE formatter. // DATA[i][4] is the currency format result using diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixMatcher.java index 04bbadbc22..f3c1f9410a 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixMatcher.java @@ -92,7 +92,7 @@ public class AffixMatcher implements NumberParseMatcher { boolean includeUnpaired = 0 != (parseFlags & ParsingUtils.PARSE_FLAG_INCLUDE_UNPAIRED_AFFIXES); SignDisplay signDisplay = (0 != (parseFlags & ParsingUtils.PARSE_FLAG_PLUS_SIGN_ALLOWED)) ? SignDisplay.ALWAYS - : SignDisplay.NEVER; + : SignDisplay.AUTO; AffixPatternMatcher posPrefix = null; AffixPatternMatcher posSuffix = null; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java index 72ab98e4e3..957a593345 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java @@ -9,6 +9,7 @@ import java.util.List; import com.ibm.icu.impl.StringSegment; import com.ibm.icu.impl.number.AffixPatternProvider; +import com.ibm.icu.impl.number.CurrencyPluralInfoAffixProvider; import com.ibm.icu.impl.number.CustomSymbolCurrency; import com.ibm.icu.impl.number.DecimalFormatProperties; import com.ibm.icu.impl.number.DecimalFormatProperties.ParseMode; @@ -134,7 +135,12 @@ public class NumberParserImpl { boolean parseCurrency) { ULocale locale = symbols.getULocale(); - AffixPatternProvider patternInfo = new PropertiesAffixPatternProvider(properties); + AffixPatternProvider affixProvider; + if (properties.getCurrencyPluralInfo() == null) { + affixProvider = new PropertiesAffixPatternProvider(properties); + } else { + affixProvider = new CurrencyPluralInfoAffixProvider(properties.getCurrencyPluralInfo()); + } Currency currency = CustomSymbolCurrency.resolve(properties.getCurrency(), locale, symbols); boolean isStrict = properties.getParseMode() == ParseMode.STRICT; Grouper grouper = Grouper.forProperties(properties); @@ -161,7 +167,7 @@ public class NumberParserImpl { if (grouper.getPrimary() <= 0) { parseFlags |= ParsingUtils.PARSE_FLAG_GROUPING_DISABLED; } - if (parseCurrency || patternInfo.hasCurrencySign()) { + if (parseCurrency || affixProvider.hasCurrencySign()) { parseFlags |= ParsingUtils.PARSE_FLAG_MONETARY_SEPARATORS; } IgnorablesMatcher ignorables = isStrict ? IgnorablesMatcher.STRICT : IgnorablesMatcher.DEFAULT; @@ -179,13 +185,13 @@ public class NumberParserImpl { ////////////////////// // Set up a pattern modifier with mostly defaults to generate AffixMatchers. - AffixMatcher.createMatchers(patternInfo, parser, factory, ignorables, parseFlags); + AffixMatcher.createMatchers(affixProvider, parser, factory, ignorables, parseFlags); //////////////////////// /// CURRENCY MATCHER /// //////////////////////// - if (parseCurrency || patternInfo.hasCurrencySign()) { + if (parseCurrency || affixProvider.hasCurrencySign()) { parser.addMatcher(CombinedCurrencyMatcher.getInstance(currency, symbols)); } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/SeriesMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/SeriesMatcher.java index f12a81fd35..466be0e648 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/SeriesMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/SeriesMatcher.java @@ -65,6 +65,11 @@ public class SeriesMatcher implements NumberParseMatcher { } else if (success) { // Match succeeded, and this is NOT a flexible matcher. Proceed to the next matcher. i++; + // Small hack: if there is another matcher coming, do not accept trailing weak chars. + // Needed for proper handling of currency spacing. + if (i < matchers.size() && segment.getOffset() != result.charEnd && result.charEnd > matcherOffset) { + segment.setOffset(result.charEnd); + } } else if (isFlexible) { // Match failed, and this is a flexible matcher. Try again with the next matcher. i++; diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java index 679031eea2..639744aed4 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java @@ -5994,4 +5994,27 @@ public class NumberFormatTest extends TestFmwk { assertEquals("Should parse successfully", 0.08, percentage.doubleValue()); assertEquals("Should consume whole string", 3, ppos.getIndex()); } + + @Test + public void testStrictParseCurrencyLongNames() { + DecimalFormat df = (DecimalFormat) DecimalFormat.getInstance(ULocale.ENGLISH, DecimalFormat.PLURALCURRENCYSTYLE); + df.setParseStrict(true); + df.setCurrency(Currency.getInstance("USD")); + double input = 514.23; + String formatted = df.format(input); + assertEquals("Should format as expected", "514.23 US dollars", formatted); + ParsePosition ppos = new ParsePosition(0); + CurrencyAmount ca = df.parseCurrency(formatted, ppos); + assertEquals("Should consume whole number", ppos.getIndex(), 17); + assertEquals("Number should round-trip", ca.getNumber().doubleValue(), input); + assertEquals("Should get correct currency", ca.getCurrency().getCurrencyCode(), "USD"); + } + + @Test + public void testStrictParseCurrencySpacing() { + DecimalFormat df = new DecimalFormat("¤ 0", DecimalFormatSymbols.getInstance(ULocale.ROOT)); + df.setCurrency(Currency.getInstance("USD")); + df.setParseStrict(true); + expect2(df, -51.42, "-US$ 51.42"); + } } From a901b5c04abf6e050da7570bc32500af48a507e5 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Wed, 11 Apr 2018 02:18:13 +0000 Subject: [PATCH 086/129] ICU-13634 Fixing more assorted currency parsing issues. X-SVN-Rev: 41214 --- icu4c/source/i18n/number_currencysymbols.cpp | 24 +++++++++--- icu4c/source/i18n/numparse_currency.cpp | 4 +- icu4c/source/test/intltest/numfmtst.cpp | 27 +++++++------ .../number/parse/CombinedCurrencyMatcher.java | 4 +- .../data/numberformattestspecification.txt | 38 ++++++++++++++----- .../format/NumberFormatDataDrivenTest.java | 3 ++ .../icu/dev/test/format/NumberFormatTest.java | 10 +++++ 7 files changed, 78 insertions(+), 32 deletions(-) diff --git a/icu4c/source/i18n/number_currencysymbols.cpp b/icu4c/source/i18n/number_currencysymbols.cpp index 8f05da78c4..51f37076e7 100644 --- a/icu4c/source/i18n/number_currencysymbols.cpp +++ b/icu4c/source/i18n/number_currencysymbols.cpp @@ -52,17 +52,23 @@ UnicodeString CurrencySymbols::getCurrencySymbol(UErrorCode& status) const { } UnicodeString CurrencySymbols::loadSymbol(UCurrNameStyle selector, UErrorCode& status) const { + const char16_t* isoCode = fCurrency.getISOCurrency(); UBool ignoredIsChoiceFormatFillIn = FALSE; int32_t symbolLen = 0; const char16_t* symbol = ucurr_getName( - fCurrency.getISOCurrency(), + isoCode, fLocaleName.data(), selector, &ignoredIsChoiceFormatFillIn, &symbolLen, &status); - // Readonly-aliasing char16_t* constructor, which points to a resource bundle: - return UnicodeString(TRUE, symbol, symbolLen); + // If given an unknown currency, ucurr_getName returns the input string, which we can't alias safely! + // Otherwise, symbol points to a resource bundle, and we can use readonly-aliasing constructor. + if (symbol == isoCode) { + return UnicodeString(isoCode, 3); + } else { + return UnicodeString(TRUE, symbol, symbolLen); + } } UnicodeString CurrencySymbols::getIntlCurrencySymbol(UErrorCode&) const { @@ -75,17 +81,23 @@ UnicodeString CurrencySymbols::getIntlCurrencySymbol(UErrorCode&) const { } UnicodeString CurrencySymbols::getPluralName(StandardPlural::Form plural, UErrorCode& status) const { + const char16_t* isoCode = fCurrency.getISOCurrency(); UBool isChoiceFormat = FALSE; int32_t symbolLen = 0; const char16_t* symbol = ucurr_getPluralName( - fCurrency.getISOCurrency(), + isoCode, fLocaleName.data(), &isChoiceFormat, StandardPlural::getKeyword(plural), &symbolLen, &status); - // Readonly-aliasing char16_t* constructor, which points to a resource bundle: - return UnicodeString(TRUE, symbol, symbolLen); + // If given an unknown currency, ucurr_getName returns the input string, which we can't alias safely! + // Otherwise, symbol points to a resource bundle, and we can use readonly-aliasing constructor. + if (symbol == isoCode) { + return UnicodeString(isoCode, 3); + } else { + return UnicodeString(TRUE, symbol, symbolLen); + } } diff --git a/icu4c/source/i18n/numparse_currency.cpp b/icu4c/source/i18n/numparse_currency.cpp index 5c14fd7429..0848375962 100644 --- a/icu4c/source/i18n/numparse_currency.cpp +++ b/icu4c/source/i18n/numparse_currency.cpp @@ -81,7 +81,7 @@ bool CombinedCurrencyMatcher::match(StringSegment& segment, ParsedNumber& result bool CombinedCurrencyMatcher::matchCurrency(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const { - int32_t overlap1 = segment.getCommonPrefixLength(fCurrency1); + int32_t overlap1 = segment.getCaseSensitivePrefixLength(fCurrency1); if (overlap1 == fCurrency1.length()) { utils::copyCurrencyCode(result.currencyCode, fCurrencyCode); segment.adjustOffset(overlap1); @@ -89,7 +89,7 @@ bool CombinedCurrencyMatcher::matchCurrency(StringSegment& segment, ParsedNumber return segment.length() == 0; } - int32_t overlap2 = segment.getCommonPrefixLength(fCurrency2); + int32_t overlap2 = segment.getCaseSensitivePrefixLength(fCurrency2); if (overlap2 == fCurrency2.length()) { utils::copyCurrencyCode(result.currencyCode, fCurrencyCode); segment.adjustOffset(overlap2); diff --git a/icu4c/source/test/intltest/numfmtst.cpp b/icu4c/source/test/intltest/numfmtst.cpp index bf070fa75a..cf9bd3bc8a 100644 --- a/icu4c/source/test/intltest/numfmtst.cpp +++ b/icu4c/source/test/intltest/numfmtst.cpp @@ -4082,24 +4082,24 @@ NumberFormatTest::TestCurrencyParsing() { // format result using CURRENCYSTYLE, // format result using ISOCURRENCYSTYLE, // format result using PLURALCURRENCYSTYLE, - {"en_US", "1", "USD", "$1.00", "USD1.00", "1.00 US dollar"}, + {"en_US", "1", "USD", "$1.00", "USD\\u00A01.00", "1.00 US dollar"}, {"pa_IN", "1", "USD", "US$\\u00A01.00", "USD\\u00A01.00", "1.00 \\u0a2f\\u0a42.\\u0a10\\u0a38. \\u0a21\\u0a3e\\u0a32\\u0a30"}, {"es_AR", "1", "USD", "US$\\u00A01,00", "USD\\u00A01,00", "1,00 d\\u00f3lar estadounidense"}, {"ar_EG", "1", "USD", "\\u0661\\u066b\\u0660\\u0660\\u00a0US$", "\\u0661\\u066b\\u0660\\u0660\\u00a0USD", "\\u0661\\u066b\\u0660\\u0660 \\u062f\\u0648\\u0644\\u0627\\u0631 \\u0623\\u0645\\u0631\\u064a\\u0643\\u064a"}, {"fa_CA", "1", "USD", "\\u200e$\\u06f1\\u066b\\u06f0\\u06f0", "\\u200eUSD\\u06f1\\u066b\\u06f0\\u06f0", "\\u06f1\\u066b\\u06f0\\u06f0 \\u062f\\u0644\\u0627\\u0631 \\u0627\\u0645\\u0631\\u06cc\\u06a9\\u0627"}, {"he_IL", "1", "USD", "\\u200f1.00\\u00a0$", "\\u200f1.00\\u00a0USD", "1.00 \\u05d3\\u05d5\\u05dc\\u05e8 \\u05d0\\u05de\\u05e8\\u05d9\\u05e7\\u05d0\\u05d9"}, {"hr_HR", "1", "USD", "1,00\\u00a0USD", "1,00\\u00a0USD", "1,00 Ameri\\u010dki dolar"}, - {"id_ID", "1", "USD", "US$1,00", "USD1,00", "1,00 Dolar Amerika Serikat"}, + {"id_ID", "1", "USD", "US$\\u00A01,00", "USD\\u00A01,00", "1,00 Dolar Amerika Serikat"}, {"it_IT", "1", "USD", "1,00\\u00a0USD", "1,00\\u00a0USD", "1,00 Dollaro Statunitense"}, - {"ko_KR", "1", "USD", "US$1.00", "USD1.00", "1.00 \\ubbf8\\uad6d \\ub2ec\\ub7ec"}, - {"ja_JP", "1", "USD", "$1.00", "USD1.00", "1.00\\u7c73\\u30c9\\u30eb"}, - {"zh_CN", "1", "CNY", "\\uFFE51.00", "CNY01.00", "1.00\\u4EBA\\u6C11\\u5E01"}, - {"zh_TW", "1", "CNY", "CN\\u00A51.00", "CNY1.00", "1.00 \\u4eba\\u6c11\\u5e63"}, - {"zh_Hant", "1", "CNY", "CN\\u00A51.00", "CNY1.00", "1.00 \\u4eba\\u6c11\\u5e63"}, - {"zh_Hant", "1", "JPY", "\\u00A51.00", "JPY1.00", "1.00 \\u65e5\\u5713"}, - {"ja_JP", "1", "JPY", "\\uFFE51.00", "JPY1.00", "1.00\\u65e5\\u672c\\u5186"}, - {"ja_JP", "1", "JPY", "\\u00A51.00", "JPY1.00", "1.00\\u65e5\\u672c\\u5186"}, - {"ru_RU", "1", "RUB", "1,00\\u00A0\\u20BD", "1,00\\u00A0RUB", "1,00 \\u0420\\u043E\\u0441\\u0441\\u0438\\u0439\\u0441\\u043A\\u0438\\u0439 \\u0440\\u0443\\u0431\\u043B\\u044C"} + {"ko_KR", "1", "USD", "US$\\u00A01.00", "USD\\u00A01.00", "1.00 \\ubbf8\\uad6d \\ub2ec\\ub7ec"}, + {"ja_JP", "1", "USD", "$1.00", "USD\\u00A01.00", "1.00\\u00A0\\u7c73\\u30c9\\u30eb"}, + {"zh_CN", "1", "CNY", "\\uFFE51.00", "CNY\\u00A001.00", "1.00\\u00A0\\u4EBA\\u6C11\\u5E01"}, + {"zh_TW", "1", "CNY", "CN\\u00A51.00", "CNY\\u00A01.00", "1.00 \\u4eba\\u6c11\\u5e63"}, + {"zh_Hant", "1", "CNY", "CN\\u00A51.00", "CNY\\u00A01.00", "1.00 \\u4eba\\u6c11\\u5e63"}, + {"zh_Hant", "1", "JPY", "\\u00A51.00", "JPY\\u00A01.00", "1.00 \\u65e5\\u5713"}, + {"ja_JP", "1", "JPY", "\\uFFE51.00", "JPY\\u00A01.00", "1.00\\u00A0\\u65e5\\u672c\\u5186"}, + {"ja_JP", "1", "JPY", "\\u00A51.00", "JPY\\u00A01.00", "1.00\\u00A0\\u65e5\\u672c\\u5186"}, + {"ru_RU", "1", "RUB", "1,00\\u00A0\\u00A0\\u20BD", "1,00\\u00A0\\u00A0RUB", "1,00 \\u0420\\u043E\\u0441\\u0441\\u0438\\u0439\\u0441\\u043A\\u0438\\u0439 \\u0440\\u0443\\u0431\\u043B\\u044C"} }; static const UNumberFormatStyle currencyStyles[] = { UNUM_CURRENCY, @@ -4158,7 +4158,9 @@ for (;;) { } */ // test parsing, and test parsing for all currency formats. - for (int j = 3; j < 6; ++j) { + // NOTE: ICU 62 requires that the currency format match the pattern in strict mode. + //for (int j = 3; j < 6; ++j) { + for (int j = 3 + kIndex; j <= 3 + kIndex; j++) { // DATA[i][3] is the currency format result using // CURRENCYSTYLE formatter. // DATA[i][4] is the currency format result using @@ -6744,6 +6746,7 @@ NumberFormatTest::TestParseCurrencyInUCurr() { UnicodeString formatted = ctou(DATA[i]); UErrorCode status = U_ZERO_ERROR; NumberFormat* numFmt = NumberFormat::createInstance(locale, UNUM_CURRENCY, status); + numFmt->setLenient(TRUE); // ICU 62 PATCH if (numFmt != NULL && U_SUCCESS(status)) { ParsePosition parsePos; LocalPointer currAmt(numFmt->parseCurrency(formatted, parsePos)); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CombinedCurrencyMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CombinedCurrencyMatcher.java index c1d14189b6..ee303092b3 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CombinedCurrencyMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CombinedCurrencyMatcher.java @@ -109,7 +109,7 @@ public class CombinedCurrencyMatcher implements NumberParseMatcher { /** Matches the currency string without concern for currency spacing. */ private boolean matchCurrency(StringSegment segment, ParsedNumber result) { - int overlap1 = segment.getCommonPrefixLength(currency1); + int overlap1 = segment.getCaseSensitivePrefixLength(currency1); if (overlap1 == currency1.length()) { result.currencyCode = isoCode; segment.adjustOffset(overlap1); @@ -117,7 +117,7 @@ public class CombinedCurrencyMatcher implements NumberParseMatcher { return segment.length() == 0; } - int overlap2 = segment.getCommonPrefixLength(currency2); + int overlap2 = segment.getCaseSensitivePrefixLength(currency2); if (overlap2 == currency2.length()) { result.currencyCode = isoCode; segment.adjustOffset(overlap2); diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt b/icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt index b38a420f2c..35731ff8b7 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt @@ -1023,7 +1023,7 @@ begin parse output outputCurrency breaks 53.45 fail GBP £53.45 53.45 GBP -$53.45 fail USD J +$53.45 fail USD JP 53.45 USD 53.45 USD 53.45 GBP 53.45 GBP USD 53.45 53.45 USD J @@ -1041,7 +1041,7 @@ USD(7.92) -7.92 USD CJ (8) USD -8 USD -8 USD -8 USD C 67 USD 67 USD -53.45$ fail USD +53.45$ fail USD P US Dollars 53.45 53.45 USD J 53.45 US Dollars 53.45 USD US Dollar 53.45 53.45 USD J @@ -1070,7 +1070,7 @@ begin parse output outputCurrency breaks 53.45 fail GBP £53.45 53.45 GBP -$53.45 fail USD J +$53.45 fail USD JP 53.45 USD 53.45 USD 53.45 GBP 53.45 GBP USD 53.45 53.45 USD J @@ -1084,7 +1084,7 @@ USD -7.926 -7.926 USD CJ USD-7.92 -7.92 USD CJ -8 USD -8 USD 67 USD 67 USD -53.45$ fail USD +53.45$ fail USD P US Dollars 53.45 53.45 USD J 53.45 US Dollars 53.45 USD US Dollar 53.45 53.45 USD J @@ -1104,7 +1104,7 @@ parse output outputCurrency breaks // J throws a NullPointerException on the first case 53.45 fail GBP £53.45 53.45 GBP -$53.45 fail USD J +$53.45 fail USD JP 53.45 USD 53.45 USD 53.45 GBP 53.45 GBP USD 53.45 53.45 USD J @@ -1123,7 +1123,7 @@ USD(7.92) -7.92 USD CJ -8 USD -8 USD C 67 USD 67 USD // J throws a NullPointerException on the next case -53.45$ fail USD +53.45$ fail USD P US Dollars 53.45 53.45 USD J 53.45 US Dollars 53.45 USD US Dollar 53.45 53.45 USD J @@ -1142,7 +1142,7 @@ begin parse output outputCurrency breaks 53.45 fail GBP £53.45 53.45 GBP -$53.45 fail USD J +$53.45 fail USD JP 53.45 USD 53.45 USD 53.45 GBP 53.45 GBP USD 53.45 53.45 USD J @@ -1160,7 +1160,7 @@ USD(7.92) -7.92 USD CJ (8) USD -8 USD -8 USD -8 USD C 67 USD 67 USD -53.45$ fail USD +53.45$ fail USD P US Dollars 53.45 53.45 USD J 53.45 US Dollars 53.45 USD US Dollar 53.45 53.45 USD J @@ -1179,7 +1179,7 @@ begin parse output outputCurrency breaks 53.45 fail GBP £53.45 53.45 GBP -$53.45 fail USD J +$53.45 fail USD JP 53.45 USD 53.45 USD C 53.45 GBP 53.45 GBP C USD 53.45 53.45 USD J @@ -1198,7 +1198,7 @@ USD(7.92) -7.92 USD CJP (8) USD -8 USD CJP -8 USD -8 USD C 67 USD 67 USD C -53.45$ fail USD +53.45$ fail USD P US Dollars 53.45 53.45 USD J 53.45 US Dollars 53.45 USD US Dollar 53.45 53.45 USD J @@ -1604,6 +1604,24 @@ lenient parse output breaks 0 0 fail CJK 0 +0 0 CJK +test parse lowercase currency +set locale en +set pattern ¤¤0 +set currency USD +begin +parse output outputCurrency breaks +USD123 123 USD +USD 123 123 USD J +usd123 123 USD +usd 123 123 USD J +Usd123 123 USD +Usd 123 123 USD J +US$123 123 USD +us$123 fail fail +Us$123 fail fail +123 US dollars 123 USD +123 US DOLLARS 123 USD +123 us dollars 123 USD diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatDataDrivenTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatDataDrivenTest.java index d8be411eb9..3a38ffde9c 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatDataDrivenTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatDataDrivenTest.java @@ -663,6 +663,9 @@ public class NumberFormatDataDrivenTest { if (ppos.getIndex() == 0 || actual.getCurrency().getCurrencyCode().equals("XXX")) { return "Parse failed; got " + actual + ", but expected " + tuple.output; } + if (tuple.output.equals("fail")) { + return null; + } BigDecimal expectedNumber = new BigDecimal(tuple.output); if (expectedNumber.compareTo(new BigDecimal(actual.getNumber().toString())) != 0) { return "Wrong number: Expected: " + expectedNumber + ", got: " + actual; diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java index 639744aed4..d1386fcb87 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java @@ -6017,4 +6017,14 @@ public class NumberFormatTest extends TestFmwk { df.setParseStrict(true); expect2(df, -51.42, "-US$ 51.42"); } + + @Test + public void testCaseSensitiveCustomIsoCurrency() { + DecimalFormat df = new DecimalFormat("¤¤0", DecimalFormatSymbols.getInstance(ULocale.ENGLISH)); + df.setCurrency(Currency.getInstance("ICU")); + ParsePosition ppos = new ParsePosition(0); + df.parseCurrency("icu123", ppos); + assertEquals("Should fail to parse", 0, ppos.getIndex()); + assertEquals("Should fail to parse", 0, ppos.getErrorIndex()); + } } From cd92fa2c88788780b2572d508f129cddb89c2e36 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Wed, 11 Apr 2018 05:52:58 +0000 Subject: [PATCH 087/129] ICU-13634 Changing DecimalQuantity#toNumberString() to be DecimalQuantity#toScientificString() with slightly friendlier output syntax for better compatibility. More currency tweaks. X-SVN-Rev: 41215 --- icu4c/source/i18n/currunit.cpp | 25 +++--- icu4c/source/i18n/decimfmt.cpp | 19 ++++- icu4c/source/i18n/fmtable.cpp | 10 ++- icu4c/source/i18n/number_decimalquantity.cpp | 77 +++++++++++-------- icu4c/source/i18n/number_decimalquantity.h | 6 +- icu4c/source/i18n/number_formatimpl.cpp | 2 +- icu4c/source/i18n/number_mapper.cpp | 1 + icu4c/source/i18n/number_stringbuilder.cpp | 2 +- icu4c/source/i18n/number_types.h | 3 - icu4c/source/i18n/number_utils.cpp | 16 ++-- .../intltest/numbertest_decimalquantity.cpp | 38 ++++----- icu4c/source/test/intltest/numfmtst.cpp | 36 ++++----- .../number/DecimalQuantity_AbstractBCD.java | 44 ++++++++++- .../DecimalQuantity_DualStorageBCD.java | 2 +- .../ibm/icu/number/NumberPropertyMapper.java | 1 + .../src/com/ibm/icu/text/DecimalFormat.java | 4 +- .../icu/dev/test/format/NumberFormatTest.java | 4 +- .../dev/test/number/DecimalQuantityTest.java | 36 ++++----- 18 files changed, 194 insertions(+), 132 deletions(-) diff --git a/icu4c/source/i18n/currunit.cpp b/icu4c/source/i18n/currunit.cpp index 1750b94fab..003ce61210 100644 --- a/icu4c/source/i18n/currunit.cpp +++ b/icu4c/source/i18n/currunit.cpp @@ -18,20 +18,23 @@ #include "unicode/ustring.h" #include "cstring.h" +static constexpr char16_t kDefaultCurrency[] = u"XXX"; + U_NAMESPACE_BEGIN CurrencyUnit::CurrencyUnit(ConstChar16Ptr _isoCode, UErrorCode& ec) { - *isoCode = 0; - if (U_SUCCESS(ec)) { - if (_isoCode != nullptr && u_strlen(_isoCode)==3) { - u_strcpy(isoCode, _isoCode); - char simpleIsoCode[4]; - u_UCharsToChars(isoCode, simpleIsoCode, 4); - initCurrency(simpleIsoCode); - } else { - ec = U_ILLEGAL_ARGUMENT_ERROR; - } + // The constructor always leaves the CurrencyUnit in a valid state (with a 3-character currency code). + if (U_FAILURE(ec) || _isoCode == nullptr) { + u_strcpy(isoCode, kDefaultCurrency); + } else if (u_strlen(_isoCode) != 3) { + u_strcpy(isoCode, kDefaultCurrency); + ec = U_ILLEGAL_ARGUMENT_ERROR; + } else { + u_strcpy(isoCode, _isoCode); } + char simpleIsoCode[4]; + u_UCharsToChars(isoCode, simpleIsoCode, 4); + initCurrency(simpleIsoCode); } CurrencyUnit::CurrencyUnit(const CurrencyUnit& other) : MeasureUnit(other) { @@ -52,7 +55,7 @@ CurrencyUnit::CurrencyUnit(const MeasureUnit& other, UErrorCode& ec) : MeasureUn } CurrencyUnit::CurrencyUnit() : MeasureUnit() { - u_strcpy(isoCode, u"XXX"); + u_strcpy(isoCode, kDefaultCurrency); char simpleIsoCode[4]; u_UCharsToChars(isoCode, simpleIsoCode, 4); initCurrency(simpleIsoCode); diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp index bf975a31d9..c88857c271 100644 --- a/icu4c/source/i18n/decimfmt.cpp +++ b/icu4c/source/i18n/decimfmt.cpp @@ -402,7 +402,9 @@ UnicodeString& DecimalFormat::format(double number, UnicodeString& appendTo, FieldPositionIterator* posIter, UErrorCode& status) const { FormattedNumber output = fFormatter->formatDouble(number, status); - output.populateFieldPositionIterator(*posIter, status); + if (posIter != nullptr) { + output.populateFieldPositionIterator(*posIter, status); + } auto appendable = UnicodeStringAppendable(appendTo); output.appendTo(appendable); return appendTo; @@ -445,7 +447,9 @@ UnicodeString& DecimalFormat::format(int64_t number, UnicodeString& appendTo, FieldPositionIterator* posIter, UErrorCode& status) const { FormattedNumber output = fFormatter->formatInt(number, status); - output.populateFieldPositionIterator(*posIter, status); + if (posIter != nullptr) { + output.populateFieldPositionIterator(*posIter, status); + } auto appendable = UnicodeStringAppendable(appendTo); output.appendTo(appendable); return appendTo; @@ -456,7 +460,9 @@ DecimalFormat::format(StringPiece number, UnicodeString& appendTo, FieldPosition UErrorCode& status) const { ErrorCode localStatus; FormattedNumber output = fFormatter->formatDecimal(number, localStatus); - output.populateFieldPositionIterator(*posIter, status); + if (posIter != nullptr) { + output.populateFieldPositionIterator(*posIter, status); + } auto appendable = UnicodeStringAppendable(appendTo); output.appendTo(appendable); return appendTo; @@ -465,7 +471,9 @@ DecimalFormat::format(StringPiece number, UnicodeString& appendTo, FieldPosition UnicodeString& DecimalFormat::format(const DecimalQuantity& number, UnicodeString& appendTo, FieldPositionIterator* posIter, UErrorCode& status) const { FormattedNumber output = fFormatter->formatDecimalQuantity(number, status); - output.populateFieldPositionIterator(*posIter, status); + if (posIter != nullptr) { + output.populateFieldPositionIterator(*posIter, status); + } auto appendable = UnicodeStringAppendable(appendTo); output.appendTo(appendable); return appendTo; @@ -916,6 +924,7 @@ void DecimalFormat::setSignificantDigitsUsed(UBool useSignificantDigits) { } void DecimalFormat::setCurrency(const char16_t* theCurrency, UErrorCode& ec) { + NumberFormat::setCurrency(theCurrency, ec); // to set field for compatibility fProperties->currency = CurrencyUnit(theCurrency, ec); // TODO: Set values in fSymbols, too? refreshFormatterNoError(); @@ -923,6 +932,7 @@ void DecimalFormat::setCurrency(const char16_t* theCurrency, UErrorCode& ec) { void DecimalFormat::setCurrency(const char16_t* theCurrency) { ErrorCode localStatus; + NumberFormat::setCurrency(theCurrency, localStatus); // to set field for compatibility setCurrency(theCurrency, localStatus); } @@ -998,6 +1008,7 @@ void DecimalFormat::refreshFormatter(UErrorCode& status) { *fProperties, *fSymbols, true, status), status); // In order for the getters to work, we need to populate some fields in NumberFormat. + NumberFormat::setCurrency(fExportedProperties->currency.get(status).getISOCurrency(), status); NumberFormat::setMaximumIntegerDigits(fExportedProperties->maximumIntegerDigits); NumberFormat::setMinimumIntegerDigits(fExportedProperties->minimumIntegerDigits); NumberFormat::setMaximumFractionDigits(fExportedProperties->maximumFractionDigits); diff --git a/icu4c/source/i18n/fmtable.cpp b/icu4c/source/i18n/fmtable.cpp index 68e06767a1..ecee7388da 100644 --- a/icu4c/source/i18n/fmtable.cpp +++ b/icu4c/source/i18n/fmtable.cpp @@ -745,8 +745,14 @@ CharString *Formattable::internalGetCharString(UErrorCode &status) { status = U_MEMORY_ALLOCATION_ERROR; return NULL; } - UnicodeString result = fDecimalQuantity->toNumberString(); - fDecimalStr->appendInvariantChars(result, status); + // Older ICUs called uprv_decNumberToString here, which is not exactly the same as + // DecimalQuantity::toScientificString(). The biggest difference is that uprv_decNumberToString does + // not print scientific notation for magnitudes greater than -5 and smaller than some amount (+5?). + if (std::abs(fDecimalQuantity->getMagnitude()) < 5) { + fDecimalStr->appendInvariantChars(fDecimalQuantity->toPlainString(), status); + } else { + fDecimalStr->appendInvariantChars(fDecimalQuantity->toScientificString(), status); + } } return fDecimalStr; } diff --git a/icu4c/source/i18n/number_decimalquantity.cpp b/icu4c/source/i18n/number_decimalquantity.cpp index 9c16fe4c3e..baae9b7632 100644 --- a/icu4c/source/i18n/number_decimalquantity.cpp +++ b/icu4c/source/i18n/number_decimalquantity.cpp @@ -553,13 +553,12 @@ double DecimalQuantity::toDouble() const { // We are processing well-formed input, so we don't need any special options to StringToDoubleConverter. StringToDoubleConverter converter(0, 0, 0, "", ""); - UnicodeString numberString = toNumberString(); + UnicodeString numberString = this->toScientificString(); int32_t count; - double result = converter.StringToDouble(reinterpret_cast(numberString.getBuffer()), numberString.length(), &count); - if (isNegative()) { - result = -result; - } - return result; + return converter.StringToDouble( + reinterpret_cast(numberString.getBuffer()), + numberString.length(), + &count); } double DecimalQuantity::toDoubleFromOriginal() const { @@ -775,7 +774,7 @@ UnicodeString DecimalQuantity::toPlainString() const { if (isNegative()) { sb.append(u'-'); } - if (precision == 0) { + if (precision == 0 || getMagnitude() < 0) { sb.append(u'0'); } for (int m = getUpperDisplayMagnitude(); m >= getLowerDisplayMagnitude(); m--) { @@ -785,6 +784,43 @@ UnicodeString DecimalQuantity::toPlainString() const { return sb; } +UnicodeString DecimalQuantity::toScientificString() const { + U_ASSERT(!isApproximate); + UnicodeString result; + if (isNegative()) { + result.append(u'-'); + } + if (precision == 0) { + result.append(u"0E+0", -1); + return result; + } + result.append(u'0' + getDigitPos(precision - 1)); + if (precision > 1) { + result.append(u'.'); + for (int32_t i = 1; i < precision; i++) { + result.append(u'0' + getDigitPos(precision - i - 1)); + } + } + result.append(u'E'); + int32_t _scale = scale + precision - 1; + if (_scale < 0) { + _scale *= -1; + result.append(u'-'); + } else { + result.append(u'+'); + } + if (_scale == 0) { + result.append(u'0'); + } + int32_t insertIndex = result.length(); + while (_scale > 0) { + std::div_t res = std::div(_scale, 10); + result.insert(insertIndex, u'0' + res.rem); + _scale = res.quot; + } + return result; +} + //////////////////////////////////////////////////// /// End of DecimalQuantity_AbstractBCD.java /// /// Start of DecimalQuantity_DualStorageBCD.java /// @@ -1128,31 +1164,4 @@ UnicodeString DecimalQuantity::toString() const { return UnicodeString(buffer8, -1, US_INV); } -UnicodeString DecimalQuantity::toNumberString() const { - U_ASSERT(!isApproximate); - UnicodeString result; - if (precision == 0) { - result.append(u'0'); - } - for (int32_t i = 0; i < precision; i++) { - result.append(u'0' + getDigitPos(precision - i - 1)); - } - result.append(u'E'); - int32_t _scale = scale; - if (_scale < 0) { - _scale *= -1; - result.append(u'-'); - } - if (_scale == 0) { - result.append(u'0'); - } - int32_t insertIndex = result.length(); - while (_scale > 0) { - std::div_t res = std::div(_scale, 10); - result.insert(insertIndex, u'0' + res.rem); - _scale = res.quot; - } - return result; -} - #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/number_decimalquantity.h b/icu4c/source/i18n/number_decimalquantity.h index 268cdd9967..78bcdfc0b3 100644 --- a/icu4c/source/i18n/number_decimalquantity.h +++ b/icu4c/source/i18n/number_decimalquantity.h @@ -252,10 +252,10 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { UnicodeString toString() const; - /* Returns the string in exponential notation. */ - UnicodeString toNumberString() const; + /** Returns the string in standard exponential notation. */ + UnicodeString toScientificString() const; - /* Returns the string without exponential notation. Slightly slower than toNumberString(). */ + /** Returns the string without exponential notation. Slightly slower than toScientificString(). */ UnicodeString toPlainString() const; /** Visible for testing */ diff --git a/icu4c/source/i18n/number_formatimpl.cpp b/icu4c/source/i18n/number_formatimpl.cpp index 62947be571..48a45c0044 100644 --- a/icu4c/source/i18n/number_formatimpl.cpp +++ b/icu4c/source/i18n/number_formatimpl.cpp @@ -185,7 +185,7 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe, bool isAccounting = macros.sign == UNUM_SIGN_ACCOUNTING || macros.sign == UNUM_SIGN_ACCOUNTING_ALWAYS || macros.sign == UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO; - CurrencyUnit currency(kDefaultCurrency, status); + CurrencyUnit currency(nullptr, status); if (isCurrency) { currency = CurrencyUnit(macros.unit, status); // Restore CurrencyUnit from MeasureUnit } diff --git a/icu4c/source/i18n/number_mapper.cpp b/icu4c/source/i18n/number_mapper.cpp index ef13267dcd..24a09e6f4b 100644 --- a/icu4c/source/i18n/number_mapper.cpp +++ b/icu4c/source/i18n/number_mapper.cpp @@ -265,6 +265,7 @@ MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& propert if (exportedProperties != nullptr) { + exportedProperties->currency = currency; exportedProperties->roundingMode = roundingMode; exportedProperties->minimumIntegerDigits = minInt; exportedProperties->maximumIntegerDigits = maxInt == -1 ? INT32_MAX : maxInt; diff --git a/icu4c/source/i18n/number_stringbuilder.cpp b/icu4c/source/i18n/number_stringbuilder.cpp index a1900deab8..dd189067a7 100644 --- a/icu4c/source/i18n/number_stringbuilder.cpp +++ b/icu4c/source/i18n/number_stringbuilder.cpp @@ -459,7 +459,7 @@ void NumberStringBuilder::populateFieldPosition(FieldPosition &fp, int32_t offse void NumberStringBuilder::populateFieldPositionIterator(FieldPositionIterator &fpi, UErrorCode &status) const { // TODO: Set an initial capacity on uvec? - LocalPointer uvec(new UVector32(status)); + LocalPointer uvec(new UVector32(status), status); if (U_FAILURE(status)) { return; } diff --git a/icu4c/source/i18n/number_types.h b/icu4c/source/i18n/number_types.h index 1081919c4c..ac7b095932 100644 --- a/icu4c/source/i18n/number_types.h +++ b/icu4c/source/i18n/number_types.h @@ -38,9 +38,6 @@ static constexpr RoundingMode kDefaultMode = RoundingMode::UNUM_FOUND_HALFEVEN; // ICU4J Equivalent: Padder.FALLBACK_PADDING_STRING static constexpr char16_t kFallbackPaddingString[] = u" "; -// ICU4J Equivalent: NumberFormatterImpl.DEFAULT_CURRENCY -static constexpr char16_t kDefaultCurrency[] = u"XXX"; - // Forward declarations: class Modifier; diff --git a/icu4c/source/i18n/number_utils.cpp b/icu4c/source/i18n/number_utils.cpp index c2f74e5bb6..8cf702dd33 100644 --- a/icu4c/source/i18n/number_utils.cpp +++ b/icu4c/source/i18n/number_utils.cpp @@ -104,18 +104,20 @@ void DecNum::_setTo(const char* str, int32_t maxDigits, UErrorCode& status) { static_assert(DECDPUN == 1, "Assumes that DECDPUN is set to 1"); uprv_decNumberFromString(fData.getAlias(), str, &fContext); - // For consistency with Java BigDecimal, no support for DecNum that is NaN or Infinity! - if (decNumberIsSpecial(fData.getAlias())) { + // Check for invalid syntax and set the corresponding error code. + if ((fContext.status & DEC_Conversion_syntax) != 0) { + status = U_DECIMAL_NUMBER_SYNTAX_ERROR; + return; + } else if (fContext.status != 0) { + // Not a syntax error, but some other error, like an exponent that is too large. status = U_UNSUPPORTED_ERROR; return; } - // Check for invalid syntax and set the corresponding error code. - if ((fContext.status & DEC_Conversion_syntax) != 0) { - status = U_DECIMAL_NUMBER_SYNTAX_ERROR; - } else if (fContext.status != 0) { - // Not a syntax error, but some other error, like an exponent that is too large. + // For consistency with Java BigDecimal, no support for DecNum that is NaN or Infinity! + if (decNumberIsSpecial(fData.getAlias())) { status = U_UNSUPPORTED_ERROR; + return; } } diff --git a/icu4c/source/test/intltest/numbertest_decimalquantity.cpp b/icu4c/source/test/intltest/numbertest_decimalquantity.cpp index 320dbe0eab..2a19b0b908 100644 --- a/icu4c/source/test/intltest/numbertest_decimalquantity.cpp +++ b/icu4c/source/test/intltest/numbertest_decimalquantity.cpp @@ -105,18 +105,18 @@ void DecimalQuantityTest::testSwitchStorage() { fq.setToLong(1234123412341234L); assertFalse("Should not be using byte array", fq.isUsingBytes()); - assertEquals("Failed on initialize", u"1234123412341234E0", fq.toNumberString()); + assertEquals("Failed on initialize", u"1.234123412341234E+15", fq.toScientificString()); assertHealth(fq); // Long -> Bytes fq.appendDigit(5, 0, true); assertTrue("Should be using byte array", fq.isUsingBytes()); - assertEquals("Failed on multiply", u"12341234123412345E0", fq.toNumberString()); + assertEquals("Failed on multiply", u"1.2341234123412345E+16", fq.toScientificString()); assertHealth(fq); // Bytes -> Long fq.roundToMagnitude(5, RoundingMode::UNUM_ROUND_HALFEVEN, status); assertSuccess("Rounding to magnitude", status); assertFalse("Should not be using byte array", fq.isUsingBytes()); - assertEquals("Failed on round", u"123412341234E5", fq.toNumberString()); + assertEquals("Failed on round", u"1.23412341234E+16", fq.toScientificString()); assertHealth(fq); } @@ -170,50 +170,46 @@ void DecimalQuantityTest::testCopyMove() { void DecimalQuantityTest::testAppend() { DecimalQuantity fq; fq.appendDigit(1, 0, true); - assertEquals("Failed on append", u"1E0", fq.toNumberString()); + assertEquals("Failed on append", u"1E+0", fq.toScientificString()); assertHealth(fq); fq.appendDigit(2, 0, true); - assertEquals("Failed on append", u"12E0", fq.toNumberString()); + assertEquals("Failed on append", u"1.2E+1", fq.toScientificString()); assertHealth(fq); fq.appendDigit(3, 1, true); - assertEquals("Failed on append", u"1203E0", fq.toNumberString()); + assertEquals("Failed on append", u"1.203E+3", fq.toScientificString()); assertHealth(fq); fq.appendDigit(0, 1, true); - assertEquals("Failed on append", u"1203E2", fq.toNumberString()); + assertEquals("Failed on append", u"1.203E+5", fq.toScientificString()); assertHealth(fq); fq.appendDigit(4, 0, true); - assertEquals("Failed on append", u"1203004E0", fq.toNumberString()); + assertEquals("Failed on append", u"1.203004E+6", fq.toScientificString()); assertHealth(fq); fq.appendDigit(0, 0, true); - assertEquals("Failed on append", u"1203004E1", fq.toNumberString()); + assertEquals("Failed on append", u"1.203004E+7", fq.toScientificString()); assertHealth(fq); fq.appendDigit(5, 0, false); - assertEquals("Failed on append", u"120300405E-1", fq.toNumberString()); + assertEquals("Failed on append", u"1.20300405E+7", fq.toScientificString()); assertHealth(fq); fq.appendDigit(6, 0, false); - assertEquals("Failed on append", u"1203004056E-2", fq.toNumberString()); + assertEquals("Failed on append", u"1.203004056E+7", fq.toScientificString()); assertHealth(fq); fq.appendDigit(7, 3, false); - assertEquals("Failed on append", u"12030040560007E-6", fq.toNumberString()); + assertEquals("Failed on append", u"1.2030040560007E+7", fq.toScientificString()); assertHealth(fq); - UnicodeString baseExpected(u"12030040560007"); + UnicodeString baseExpected(u"1.2030040560007"); for (int i = 0; i < 10; i++) { fq.appendDigit(8, 0, false); baseExpected.append(u'8'); UnicodeString expected(baseExpected); - expected.append(u"E-"); - if (i >= 3) { - expected.append(u'1'); - } - expected.append(((7 + i) % 10) + u'0'); - assertEquals("Failed on append", expected, fq.toNumberString()); + expected.append(u"E+7"); + assertEquals("Failed on append", expected, fq.toScientificString()); assertHealth(fq); } fq.appendDigit(9, 2, false); baseExpected.append(u"009"); UnicodeString expected(baseExpected); - expected.append(u"E-19"); - assertEquals("Failed on append", expected, fq.toNumberString()); + expected.append(u"E+7"); + assertEquals("Failed on append", expected, fq.toScientificString()); assertHealth(fq); } diff --git a/icu4c/source/test/intltest/numfmtst.cpp b/icu4c/source/test/intltest/numfmtst.cpp index cf9bd3bc8a..dc014a3c92 100644 --- a/icu4c/source/test/intltest/numfmtst.cpp +++ b/icu4c/source/test/intltest/numfmtst.cpp @@ -458,15 +458,15 @@ UBool NumberFormatTestDataDriven::isParsePass( DecimalQuantity expectedQuantity; strToDigitList(tuple.output, expectedQuantity, status); - UnicodeString expectedString = expectedQuantity.toNumberString(); + UnicodeString expectedString = expectedQuantity.toScientificString(); if (U_FAILURE(status)) { appendErrorMessage.append("[Error parsing decnumber] "); // If this happens, assume that tuple.output is exactly the same format as - // DecimalQuantity.toNumberString() + // DecimalQuantity.toScientificString() expectedString = tuple.output; status = U_ZERO_ERROR; } - UnicodeString actualString = result.getDecimalQuantity()->toNumberString(); + UnicodeString actualString = result.getDecimalQuantity()->toScientificString(); if (expectedString != actualString) { appendErrorMessage.append( UnicodeString("Expected: ") + tuple.output + " (i.e., " + expectedString + "), but got: " + @@ -503,7 +503,7 @@ UBool NumberFormatTestDataDriven::isParseCurrencyPass( } UnicodeString currStr(currAmt->getISOCurrency()); U_ASSERT(currAmt->getNumber().getDecimalQuantity() != nullptr); // no doubles in currency tests - UnicodeString resultStr = currAmt->getNumber().getDecimalQuantity()->toNumberString(); + UnicodeString resultStr = currAmt->getNumber().getDecimalQuantity()->toScientificString(); if (tuple.output == "fail") { appendErrorMessage.append(UnicodeString("Parse succeeded: ") + resultStr + ", but was expected to fail."); return TRUE; // TRUE because failure handling is in the test suite @@ -511,7 +511,7 @@ UBool NumberFormatTestDataDriven::isParseCurrencyPass( DecimalQuantity expectedQuantity; strToDigitList(tuple.output, expectedQuantity, status); - UnicodeString expectedString = expectedQuantity.toNumberString(); + UnicodeString expectedString = expectedQuantity.toScientificString(); if (U_FAILURE(status)) { appendErrorMessage.append("Error parsing decnumber"); // If this happens, assume that tuple.output is exactly the same format as @@ -6978,15 +6978,11 @@ const char* attrString(int32_t attrId) { // API test, not a comprehensive test. // See DecimalFormatTest/DataDrivenTests // -#define ASSERT_SUCCESS(status) {if (U_FAILURE(status)) errln("file %s, line %d: status: %s", \ - __FILE__, __LINE__, u_errorName(status));} -#define ASSERT_EQUALS(expected, actual) {if ((expected) != (actual)) \ - errln("file %s, line %d: %s != %s", __FILE__, __LINE__, #expected, #actual);} - -static UBool operator != (const char *s1, UnicodeString &s2) { - // This function lets ASSERT_EQUALS("literal", UnicodeString) work. - UnicodeString us1(s1); - return us1 != s2; +#define ASSERT_SUCCESS(status) { \ + assertSuccess(UnicodeString("file ") + __FILE__ + ", line " + __LINE__, (status)); \ +} +#define ASSERT_EQUALS(expected, actual) { \ + assertEquals(UnicodeString("file ") + __FILE__ + ", line " + __LINE__, (expected), (actual)); \ } void NumberFormatTest::TestDecimal() { @@ -6996,7 +6992,7 @@ void NumberFormatTest::TestDecimal() { ASSERT_SUCCESS(status); StringPiece s = f.getDecimalNumber(status); ASSERT_SUCCESS(status); - ASSERT_EQUALS("1.2345678999987654321E+667", s); + ASSERT_EQUALS("1.2345678999987654321E+667", s.data()); //printf("%s\n", s.data()); } @@ -7015,7 +7011,7 @@ void NumberFormatTest::TestDecimal() { ASSERT_EQUALS(123.45, f.getDouble()); ASSERT_EQUALS(123.45, f.getDouble(status)); ASSERT_SUCCESS(status); - ASSERT_EQUALS("123.45", f.getDecimalNumber(status)); + ASSERT_EQUALS("123.45", f.getDecimalNumber(status).data()); ASSERT_SUCCESS(status); f.setDecimalNumber("4.5678E7", status); @@ -7030,7 +7026,7 @@ void NumberFormatTest::TestDecimal() { ASSERT_EQUALS(-123, f.getLong()); ASSERT_EQUALS(-123, f.getLong(status)); ASSERT_SUCCESS(status); - ASSERT_EQUALS("-123", f.getDecimalNumber(status)); + ASSERT_EQUALS("-123", f.getDecimalNumber(status).data()); ASSERT_SUCCESS(status); status = U_ZERO_ERROR; @@ -7040,7 +7036,7 @@ void NumberFormatTest::TestDecimal() { ASSERT_EQUALS(1234567890123LL, f.getInt64()); ASSERT_EQUALS(1234567890123LL, f.getInt64(status)); ASSERT_SUCCESS(status); - ASSERT_EQUALS("1234567890123", f.getDecimalNumber(status)); + ASSERT_EQUALS("1.234567890123E+12", f.getDecimalNumber(status).data()); ASSERT_SUCCESS(status); } @@ -7103,7 +7099,7 @@ void NumberFormatTest::TestDecimal() { Formattable result; fmtr->parse(input, result, status); ASSERT_SUCCESS(status); - ASSERT_EQUALS(0, strcmp("0.0184", result.getDecimalNumber(status).data())); + ASSERT_EQUALS("0.0184", result.getDecimalNumber(status).data()); //std::cout << result.getDecimalNumber(status).data(); delete fmtr; } @@ -7156,8 +7152,8 @@ void NumberFormatTest::TestCurrencyFractionDigits() { errln((UnicodeString)"NumberFormat::format() should return the same result - text1=" + text1 + " text2=" + text2); } - delete fmt; } + delete fmt; } void NumberFormatTest::TestExponentParse() { 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 772691004e..160c4fd1f8 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 @@ -947,7 +947,7 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { if (isNegative()) { sb.append('-'); } - if (precision == 0) { + if (precision == 0 || getMagnitude() < 0) { sb.append('0'); } for (int m = getUpperDisplayMagnitude(); m >= getLowerDisplayMagnitude(); m--) { @@ -958,6 +958,48 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { return sb.toString(); } + public String toScientificString() { + StringBuilder sb = new StringBuilder(); + toScientificString(sb); + return sb.toString(); + } + + public void toScientificString(StringBuilder result) { + assert(!isApproximate); + if (isNegative()) { + result.append('-'); + } + if (precision == 0) { + result.append("0E+0"); + return; + } + result.append((char) ('0' + getDigitPos(precision - 1))); + if (precision > 1) { + result.append('.'); + for (int i = 1; i < precision; i++) { + result.append((char) ('0' + getDigitPos(precision - i - 1))); + } + } + result.append('E'); + int _scale = scale + precision - 1; + if (_scale < 0) { + _scale *= -1; + result.append('-'); + } else { + result.append('+'); + } + if (_scale == 0) { + result.append('0'); + } + int insertIndex = result.length(); + while (_scale > 0) { + int quot = _scale / 10; + int rem = _scale % 10; + result.insert(insertIndex, (char) ('0' + rem)); + _scale = quot; + } + } + /** * Returns a single digit from the BCD list. No internal state is changed by calling this method. * diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java index 72a923a216..8d8e791cca 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java @@ -425,7 +425,7 @@ public final class DecimalQuantity_DualStorageBCD extends DecimalQuantity_Abstra toNumberString()); } - public String toNumberString() { + private String toNumberString() { StringBuilder sb = new StringBuilder(); if (usingBytes) { if (precision == 0) { diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberPropertyMapper.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberPropertyMapper.java index 385944f677..b3051bc4f6 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberPropertyMapper.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberPropertyMapper.java @@ -306,6 +306,7 @@ final class NumberPropertyMapper { if (exportedProperties != null) { + exportedProperties.setCurrency(currency); exportedProperties.setMathContext(mathContext); exportedProperties.setRoundingMode(mathContext.getRoundingMode()); exportedProperties.setMinimumIntegerDigits(minInt); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java index f1ec364337..fa54739c49 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java @@ -2006,7 +2006,7 @@ public class DecimalFormat extends NumberFormat { } /** - * Returns the user-specified currency. May be null. + * Returns the currency used to display currency amounts. May be null. * * @see #setCurrency * @see DecimalFormatSymbols#getCurrency @@ -2015,7 +2015,7 @@ public class DecimalFormat extends NumberFormat { */ @Override public synchronized Currency getCurrency() { - return properties.getCurrency(); + return exportedProperties.getCurrency(); } /** diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java index d1386fcb87..6a784fdf4c 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java @@ -5450,8 +5450,8 @@ public class NumberFormatTest extends TestFmwk { @Test public void testGetSetCurrency() { - DecimalFormat df = new DecimalFormat("¤#"); - assertEquals("Currency should start out null", null, df.getCurrency()); + DecimalFormat df = new DecimalFormat("¤#", DecimalFormatSymbols.getInstance(ULocale.US)); + assertEquals("Currency should start out as the locale default", Currency.getInstance("USD"), df.getCurrency()); Currency curr = Currency.getInstance("EUR"); df.setCurrency(curr); assertEquals("Currency should equal EUR after set", curr, df.getCurrency()); diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java index 85877343a2..256f548e84 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java @@ -292,17 +292,17 @@ public class DecimalQuantityTest extends TestFmwk { fq.setToLong(1234123412341234L); assertFalse("Should not be using byte array", fq.isUsingBytes()); - assertEquals("Failed on initialize", "1234123412341234E0", fq.toNumberString()); + assertEquals("Failed on initialize", "1.234123412341234E+15", fq.toScientificString()); assertNull("Failed health check", fq.checkHealth()); // Long -> Bytes fq.appendDigit((byte) 5, 0, true); assertTrue("Should be using byte array", fq.isUsingBytes()); - assertEquals("Failed on multiply", "12341234123412345E0", fq.toNumberString()); + assertEquals("Failed on multiply", "1.2341234123412345E+16", fq.toScientificString()); assertNull("Failed health check", fq.checkHealth()); // Bytes -> Long fq.roundToMagnitude(5, MATH_CONTEXT_HALF_EVEN); assertFalse("Should not be using byte array", fq.isUsingBytes()); - assertEquals("Failed on round", "123412341234E5", fq.toNumberString()); + assertEquals("Failed on round", "1.23412341234E+16", fq.toScientificString()); assertNull("Failed health check", fq.checkHealth()); } @@ -310,48 +310,46 @@ public class DecimalQuantityTest extends TestFmwk { public void testAppend() { DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD(); fq.appendDigit((byte) 1, 0, true); - assertEquals("Failed on append", "1E0", fq.toNumberString()); + assertEquals("Failed on append", "1E+0", fq.toScientificString()); assertNull("Failed health check", fq.checkHealth()); fq.appendDigit((byte) 2, 0, true); - assertEquals("Failed on append", "12E0", fq.toNumberString()); + assertEquals("Failed on append", "1.2E+1", fq.toScientificString()); assertNull("Failed health check", fq.checkHealth()); fq.appendDigit((byte) 3, 1, true); - assertEquals("Failed on append", "1203E0", fq.toNumberString()); + assertEquals("Failed on append", "1.203E+3", fq.toScientificString()); assertNull("Failed health check", fq.checkHealth()); fq.appendDigit((byte) 0, 1, true); - assertEquals("Failed on append", "1203E2", fq.toNumberString()); + assertEquals("Failed on append", "1.203E+5", fq.toScientificString()); assertNull("Failed health check", fq.checkHealth()); fq.appendDigit((byte) 4, 0, true); - assertEquals("Failed on append", "1203004E0", fq.toNumberString()); + assertEquals("Failed on append", "1.203004E+6", fq.toScientificString()); assertNull("Failed health check", fq.checkHealth()); fq.appendDigit((byte) 0, 0, true); - assertEquals("Failed on append", "1203004E1", fq.toNumberString()); + assertEquals("Failed on append", "1.203004E+7", fq.toScientificString()); assertNull("Failed health check", fq.checkHealth()); fq.appendDigit((byte) 5, 0, false); - assertEquals("Failed on append", "120300405E-1", fq.toNumberString()); + assertEquals("Failed on append", "1.20300405E+7", fq.toScientificString()); assertNull("Failed health check", fq.checkHealth()); fq.appendDigit((byte) 6, 0, false); - assertEquals("Failed on append", "1203004056E-2", fq.toNumberString()); + assertEquals("Failed on append", "1.203004056E+7", fq.toScientificString()); assertNull("Failed health check", fq.checkHealth()); fq.appendDigit((byte) 7, 3, false); - assertEquals("Failed on append", "12030040560007E-6", fq.toNumberString()); + assertEquals("Failed on append", "1.2030040560007E+7", fq.toScientificString()); assertNull("Failed health check", fq.checkHealth()); - StringBuilder baseExpected = new StringBuilder("12030040560007"); + StringBuilder baseExpected = new StringBuilder("1.2030040560007"); for (int i = 0; i < 10; i++) { fq.appendDigit((byte) 8, 0, false); baseExpected.append('8'); StringBuilder expected = new StringBuilder(baseExpected); - expected.append("E"); - expected.append(-7 - i); - assertEquals("Failed on append", expected.toString(), fq.toNumberString()); + expected.append("E+7"); + assertEquals("Failed on append", expected.toString(), fq.toScientificString()); assertNull("Failed health check", fq.checkHealth()); } fq.appendDigit((byte) 9, 2, false); baseExpected.append("009"); StringBuilder expected = new StringBuilder(baseExpected); - expected.append('E'); - expected.append("-19"); - assertEquals("Failed on append", expected.toString(), fq.toNumberString()); + expected.append("E+7"); + assertEquals("Failed on append", expected.toString(), fq.toScientificString()); assertNull("Failed health check", fq.checkHealth()); } From 2c6bf0d77e504255d5ed241f928b4aa8bca1b7e8 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Wed, 11 Apr 2018 11:10:52 +0000 Subject: [PATCH 088/129] ICU-13634 Refactoring affix-getting code to avoid the need to save the micro-props. Other assorted minor changes. X-SVN-Rev: 41216 --- icu4c/source/i18n/decimfmt.cpp | 15 +++++--- icu4c/source/i18n/fmtable.cpp | 4 +- icu4c/source/i18n/number_fluent.cpp | 36 +++++++++--------- icu4c/source/i18n/number_formatimpl.cpp | 18 +++++++++ icu4c/source/i18n/number_formatimpl.h | 12 ++++++ icu4c/source/i18n/number_padding.cpp | 4 +- icu4c/source/i18n/unicode/fmtable.h | 2 +- icu4c/source/i18n/unicode/numberformatter.h | 12 +++--- icu4c/source/test/intltest/numfmtst.cpp | 37 +++++-------------- .../numberformattestspecification.txt | 2 +- .../com/ibm/icu/number/FormattedNumber.java | 37 ++----------------- .../icu/number/LocalizedNumberFormatter.java | 28 +++++++++++--- .../ibm/icu/number/NumberFormatterImpl.java | 23 ++++++++++-- .../src/com/ibm/icu/text/DecimalFormat.java | 11 +++--- 14 files changed, 129 insertions(+), 112 deletions(-) diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp index c88857c271..e84bb82238 100644 --- a/icu4c/source/i18n/decimfmt.cpp +++ b/icu4c/source/i18n/decimfmt.cpp @@ -564,7 +564,7 @@ void DecimalFormat::setCurrencyPluralInfo(const CurrencyPluralInfo& info) { UnicodeString& DecimalFormat::getPositivePrefix(UnicodeString& result) const { ErrorCode localStatus; - result = fFormatter->formatInt(1, localStatus).getPrefix(localStatus); + fFormatter->getAffix(true, false, result, localStatus); return result; } @@ -575,7 +575,7 @@ void DecimalFormat::setPositivePrefix(const UnicodeString& newValue) { UnicodeString& DecimalFormat::getNegativePrefix(UnicodeString& result) const { ErrorCode localStatus; - result = fFormatter->formatInt(-1, localStatus).getPrefix(localStatus); + fFormatter->getAffix(true, true, result, localStatus); return result; } @@ -586,7 +586,7 @@ void DecimalFormat::setNegativePrefix(const UnicodeString& newValue) { UnicodeString& DecimalFormat::getPositiveSuffix(UnicodeString& result) const { ErrorCode localStatus; - result = fFormatter->formatInt(1, localStatus).getSuffix(localStatus); + fFormatter->getAffix(false, false, result, localStatus); return result; } @@ -597,7 +597,7 @@ void DecimalFormat::setPositiveSuffix(const UnicodeString& newValue) { UnicodeString& DecimalFormat::getNegativeSuffix(UnicodeString& result) const { ErrorCode localStatus; - result = fFormatter->formatInt(-1, localStatus).getSuffix(localStatus); + fFormatter->getAffix(false, true, result, localStatus); return result; } @@ -682,7 +682,12 @@ void DecimalFormat::setFormatWidth(int32_t width) { } UnicodeString DecimalFormat::getPadCharacterString() const { - return fProperties->padString; + if (fProperties->padString.isBogus()) { + // Readonly-alias the static string kFallbackPaddingString + return {TRUE, kFallbackPaddingString, -1}; + } else { + return fProperties->padString; + } } void DecimalFormat::setPadCharacter(const UnicodeString& padChar) { diff --git a/icu4c/source/i18n/fmtable.cpp b/icu4c/source/i18n/fmtable.cpp index ecee7388da..299adfa244 100644 --- a/icu4c/source/i18n/fmtable.cpp +++ b/icu4c/source/i18n/fmtable.cpp @@ -748,7 +748,9 @@ CharString *Formattable::internalGetCharString(UErrorCode &status) { // Older ICUs called uprv_decNumberToString here, which is not exactly the same as // DecimalQuantity::toScientificString(). The biggest difference is that uprv_decNumberToString does // not print scientific notation for magnitudes greater than -5 and smaller than some amount (+5?). - if (std::abs(fDecimalQuantity->getMagnitude()) < 5) { + if (fDecimalQuantity->isZero()) { + fDecimalStr->append("0", -1, status); + } else if (std::abs(fDecimalQuantity->getMagnitude()) < 5) { fDecimalStr->appendInvariantChars(fDecimalQuantity->toPlainString(), status); } else { fDecimalStr->appendInvariantChars(fDecimalQuantity->toScientificString(), status); diff --git a/icu4c/source/i18n/number_fluent.cpp b/icu4c/source/i18n/number_fluent.cpp index 86a5a8cfa7..0a559b445e 100644 --- a/icu4c/source/i18n/number_fluent.cpp +++ b/icu4c/source/i18n/number_fluent.cpp @@ -677,6 +677,24 @@ void LocalizedNumberFormatter::formatImpl(impl::UFormattedNumberData* results, U } } +void LocalizedNumberFormatter::getAffix(bool isPrefix, bool isNegative, UnicodeString& result, + UErrorCode& status) const { + NumberStringBuilder nsb; + DecimalQuantity dq; + if (isNegative) { + dq.setToInt(-1); + } else { + dq.setToInt(1); + } + int prefixLength = NumberFormatterImpl::getPrefixSuffix(fMacros, dq, nsb, status); + result.remove(); + if (isPrefix) { + result.append(nsb.toTempUnicodeString().tempSubStringBetween(0, prefixLength)); + } else { + result.append(nsb.toTempUnicodeString().tempSubStringBetween(prefixLength, nsb.length())); + } +} + const impl::NumberFormatterImpl* LocalizedNumberFormatter::getCompiled() const { return fCompiled; } @@ -731,24 +749,6 @@ void FormattedNumber::getDecimalQuantity(DecimalQuantity& output, UErrorCode& st output = fResults->quantity; } -const UnicodeString FormattedNumber::getPrefix(UErrorCode& status) const { - if (fResults == nullptr) { - status = fErrorCode; - return {}; - } - // FIXME - return {}; -} - -const UnicodeString FormattedNumber::getSuffix(UErrorCode& status) const { - if (fResults == nullptr) { - status = fErrorCode; - return {}; - } - // FIXME - return {}; -} - FormattedNumber::~FormattedNumber() { delete fResults; } diff --git a/icu4c/source/i18n/number_formatimpl.cpp b/icu4c/source/i18n/number_formatimpl.cpp index 48a45c0044..c0dee2e615 100644 --- a/icu4c/source/i18n/number_formatimpl.cpp +++ b/icu4c/source/i18n/number_formatimpl.cpp @@ -137,6 +137,12 @@ void NumberFormatterImpl::applyStatic(const MacroProps& macros, DecimalQuantity& impl.applyUnsafe(inValue, outString, status); } +int32_t NumberFormatterImpl::getPrefixSuffix(const MacroProps& macros, DecimalQuantity& inValue, + NumberStringBuilder& outString, UErrorCode& status) { + NumberFormatterImpl impl(macros, false, status); + return impl.getPrefixSuffixUnsafe(inValue, outString, status); +} + // NOTE: C++ SPECIFIC DIFFERENCE FROM JAVA: // The "safe" apply method uses a new MicroProps. In the MicroPropsGenerator, fMicros is copied into the new instance. // The "unsafe" method simply re-uses fMicros, eliminating the extra copy operation. @@ -159,6 +165,18 @@ void NumberFormatterImpl::applyUnsafe(DecimalQuantity& inValue, NumberStringBuil microsToString(fMicros, inValue, outString, status); } +int32_t +NumberFormatterImpl::getPrefixSuffixUnsafe(DecimalQuantity& inValue, NumberStringBuilder& outString, + UErrorCode& status) { + if (U_FAILURE(status)) { return 0; } + fMicroPropsGenerator->processQuantity(inValue, fMicros, status); + if (U_FAILURE(status)) { return 0; } + // #13453: DecimalFormat wants the affixes from the pattern only (modMiddle). + fMicros.modMiddle->apply(outString, 0, 0, status); + if (U_FAILURE(status)) { return 0; } + return fMicros.modMiddle->getPrefixLength(status); +} + NumberFormatterImpl::NumberFormatterImpl(const MacroProps& macros, bool safe, UErrorCode& status) { fMicroPropsGenerator = macrosToMicroGenerator(macros, safe, status); } diff --git a/icu4c/source/i18n/number_formatimpl.h b/icu4c/source/i18n/number_formatimpl.h index 13c22aec75..ef8ee7064e 100644 --- a/icu4c/source/i18n/number_formatimpl.h +++ b/icu4c/source/i18n/number_formatimpl.h @@ -37,6 +37,15 @@ class NumberFormatterImpl : public UMemory { applyStatic(const MacroProps ¯os, DecimalQuantity &inValue, NumberStringBuilder &outString, UErrorCode &status); + /** + * Prints only the prefix and suffix; used for DecimalFormat getters. + * + * @return The index into the output at which the prefix ends and the suffix starts; in other words, + * the prefix length. + */ + static int32_t getPrefixSuffix(const MacroProps& macros, DecimalQuantity& inValue, + NumberStringBuilder& outString, UErrorCode& status); + /** * Evaluates the "safe" MicroPropsGenerator created by "fromMacros". */ @@ -70,6 +79,9 @@ class NumberFormatterImpl : public UMemory { void applyUnsafe(DecimalQuantity &inValue, NumberStringBuilder &outString, UErrorCode &status); + int32_t getPrefixSuffixUnsafe(DecimalQuantity& inValue, NumberStringBuilder& outString, + UErrorCode& status); + /** * If rulesPtr is non-null, return it. Otherwise, return a PluralRules owned by this object for the * specified locale, creating it if necessary. diff --git a/icu4c/source/i18n/number_padding.cpp b/icu4c/source/i18n/number_padding.cpp index 3c470df504..d2d0a87b15 100644 --- a/icu4c/source/i18n/number_padding.cpp +++ b/icu4c/source/i18n/number_padding.cpp @@ -16,8 +16,6 @@ using namespace icu::number::impl; namespace { -static UChar32 kFallbackPadChar = 0x0020; - int32_t addPaddingHelper(UChar32 paddingCp, int32_t requiredPadding, NumberStringBuilder &string, int32_t index, UErrorCode &status) { @@ -55,7 +53,7 @@ Padder Padder::forProperties(const DecimalFormatProperties& properties) { if (properties.padString.length() > 0) { padCp = properties.padString.char32At(0); } else { - padCp = kFallbackPadChar; + padCp = kFallbackPaddingString[0]; } return {padCp, properties.formatWidth, properties.padPosition.getOrDefault(UNUM_PAD_BEFORE_PREFIX)}; } diff --git a/icu4c/source/i18n/unicode/fmtable.h b/icu4c/source/i18n/unicode/fmtable.h index 6727803911..9f729c918a 100644 --- a/icu4c/source/i18n/unicode/fmtable.h +++ b/icu4c/source/i18n/unicode/fmtable.h @@ -46,7 +46,7 @@ class DecimalQuantity; #if U_PLATFORM == U_PF_OS400 #define UNUM_INTERNAL_STACKARRAY_SIZE 144 #else -#define UNUM_INTERNAL_STACKARRAY_SIZE 128 +#define UNUM_INTERNAL_STACKARRAY_SIZE 80 #endif /** diff --git a/icu4c/source/i18n/unicode/numberformatter.h b/icu4c/source/i18n/unicode/numberformatter.h index a95e1da2d1..bf4b1a7a15 100644 --- a/icu4c/source/i18n/unicode/numberformatter.h +++ b/icu4c/source/i18n/unicode/numberformatter.h @@ -2191,6 +2191,11 @@ class U_I18N_API LocalizedNumberFormatter */ FormattedNumber formatDecimalQuantity(const impl::DecimalQuantity& dq, UErrorCode& status) const; + /** Internal method for DecimalFormat compatibility. + * @internal + */ + void getAffix(bool isPrefix, bool isNegative, UnicodeString& result, UErrorCode& status) const; + /** * Internal method for testing. * @internal @@ -2250,6 +2255,7 @@ class U_I18N_API LocalizedNumberFormatter * * @param results * The results object. This method will mutate it to save the results. + * @internal */ void formatImpl(impl::UFormattedNumberData *results, UErrorCode &status) const; @@ -2355,12 +2361,6 @@ class U_I18N_API FormattedNumber : public UMemory { */ void getDecimalQuantity(impl::DecimalQuantity& output, UErrorCode& status) const; - /** @internal */ - const UnicodeString getPrefix(UErrorCode& status) const; - - /** @internal */ - const UnicodeString getSuffix(UErrorCode& status) const; - #endif // Don't allow copying of FormattedNumber, but moving is okay. diff --git a/icu4c/source/test/intltest/numfmtst.cpp b/icu4c/source/test/intltest/numfmtst.cpp index dc014a3c92..9ac2f98d3d 100644 --- a/icu4c/source/test/intltest/numfmtst.cpp +++ b/icu4c/source/test/intltest/numfmtst.cpp @@ -637,7 +637,6 @@ void NumberFormatTest::runIndexedTest( int32_t index, UBool exec, const char* &n TESTCASE_AUTO(TestNumberFormatTestTuple); TESTCASE_AUTO(TestDataDriven); TESTCASE_AUTO(TestDoubleLimit11439); - TESTCASE_AUTO(TestFastPathConsistent11524); TESTCASE_AUTO(TestGetAffixes); TESTCASE_AUTO(TestToPatternScientific11648); TESTCASE_AUTO(TestBenchmark); @@ -1586,7 +1585,8 @@ NumberFormatTest::TestLenientParse(void) if (U_FAILURE(status) ||n.getType() != Formattable::kDouble || n.getDouble() != 0.25) { errln((UnicodeString)"Lenient parse failed for \"" + (UnicodeString) lenientPercentTestCases[t] - + (UnicodeString) "\"; error code = " + u_errorName(status)); + + (UnicodeString) "\"; error code = " + u_errorName(status) + + "; got: " + n.getDouble(status)); status = U_ZERO_ERROR; } } @@ -1600,7 +1600,8 @@ NumberFormatTest::TestLenientParse(void) if (U_FAILURE(status) ||n.getType() != Formattable::kDouble || n.getDouble() != -0.25) { errln((UnicodeString)"Lenient parse failed for \"" + (UnicodeString) lenientNegativePercentTestCases[t] - + (UnicodeString) "\"; error code = " + u_errorName(status)); + + (UnicodeString) "\"; error code = " + u_errorName(status) + + "; got: " + n.getDouble(status)); status = U_ZERO_ERROR; } } @@ -7983,19 +7984,14 @@ void NumberFormatTest::Test10468ApplyPattern() { return; } - if (fmt.getPadCharacterString() != UnicodeString("a")) { - errln("Padding character should be 'a'."); - return; - } + assertEquals("Padding character should be 'a'.", u"a", fmt.getPadCharacterString()); // Padding char of fmt ought to be '*' since that is the default and no // explicit padding char is specified in the new pattern. fmt.applyPattern("AA#,##0.00ZZ", status); // Oops this still prints 'a' even though we changed the pattern. - if (fmt.getPadCharacterString() != UnicodeString(" ")) { - errln("applyPattern did not clear padding character."); - } + assertEquals("applyPattern did not clear padding character.", u" ", fmt.getPadCharacterString()); } void NumberFormatTest::TestRoundingScientific10542() { @@ -8275,7 +8271,7 @@ void NumberFormatTest::TestCurrencyUsage() { UnicodeString original; fmt->format(agent,original); - assertEquals("Test Currency Usage 1", UnicodeString("PKR124"), original); + assertEquals("Test Currency Usage 1", UnicodeString("PKR\u00A0124"), original); // test the getter here UCurrencyUsage curUsage = fmt->getCurrencyUsage(); @@ -8295,7 +8291,7 @@ void NumberFormatTest::TestCurrencyUsage() { UnicodeString cash_currency; fmt->format(agent,cash_currency); - assertEquals("Test Currency Usage 2", UnicodeString("PKR124"), cash_currency); + assertEquals("Test Currency Usage 2", UnicodeString("PKR\u00A0124"), cash_currency); delete fmt; } @@ -8355,7 +8351,7 @@ void NumberFormatTest::TestCurrencyUsage() { UnicodeString PKR_changed; fmt->format(agent, PKR_changed); - assertEquals("Test Currency Usage 6", UnicodeString("PKR124"), PKR_changed); + assertEquals("Test Currency Usage 6", UnicodeString("PKR\u00A0124"), PKR_changed); delete fmt; } } @@ -8460,21 +8456,6 @@ void NumberFormatTest::TestDoubleLimit11439() { } } -void NumberFormatTest::TestFastPathConsistent11524() { - UErrorCode status = U_ZERO_ERROR; - NumberFormat *fmt = NumberFormat::createInstance("en", status); - if (U_FAILURE(status) || fmt == NULL) { - dataerrln("Failed call to NumberFormat::createInstance() - %s", u_errorName(status)); - return; - } - fmt->setMaximumIntegerDigits(INT32_MIN); - UnicodeString appendTo; - assertEquals("", "0", fmt->format((int32_t)123, appendTo)); - appendTo.remove(); - assertEquals("", "0", fmt->format((int32_t)12345, appendTo)); - delete fmt; -} - void NumberFormatTest::TestGetAffixes() { UErrorCode status = U_ZERO_ERROR; DecimalFormatSymbols sym("en_US", status); diff --git a/icu4c/source/test/testdata/numberformattestspecification.txt b/icu4c/source/test/testdata/numberformattestspecification.txt index 4759745b21..900f77e6a1 100644 --- a/icu4c/source/test/testdata/numberformattestspecification.txt +++ b/icu4c/source/test/testdata/numberformattestspecification.txt @@ -1420,7 +1420,7 @@ NaN NaN K -1E-99999999999999 -0.0 1E2147483648 Inf K 1E2147483647 Inf K -1E2147483646 1E2147483646 +1E2147483646 1E+2147483646 1E-2147483649 0 1E-2147483648 0 // C and P return zero here diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/FormattedNumber.java b/icu4j/main/classes/core/src/com/ibm/icu/number/FormattedNumber.java index b267fd2be6..8c8a10aeac 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/FormattedNumber.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/FormattedNumber.java @@ -9,7 +9,6 @@ import java.text.FieldPosition; import java.util.Arrays; import com.ibm.icu.impl.number.DecimalQuantity; -import com.ibm.icu.impl.number.MicroProps; import com.ibm.icu.impl.number.NumberStringBuilder; import com.ibm.icu.text.PluralRules.IFixedDecimal; import com.ibm.icu.util.ICUUncheckedIOException; @@ -23,14 +22,12 @@ import com.ibm.icu.util.ICUUncheckedIOException; * @see NumberFormatter */ public class FormattedNumber { - NumberStringBuilder nsb; - DecimalQuantity fq; - MicroProps micros; + final NumberStringBuilder nsb; + final DecimalQuantity fq; - FormattedNumber(NumberStringBuilder nsb, DecimalQuantity fq, MicroProps micros) { + FormattedNumber(NumberStringBuilder nsb, DecimalQuantity fq) { this.nsb = nsb; this.fq = fq; - this.micros = micros; } /** @@ -141,34 +138,6 @@ public class FormattedNumber { return fq.toBigDecimal(); } - /** - * @internal - * @deprecated This API is ICU internal only. Use {@link #populateFieldPosition} or - * {@link #getFieldIterator} for similar functionality. - */ - @Deprecated - public String getPrefix() { - NumberStringBuilder temp = new NumberStringBuilder(); - // #13453: DecimalFormat wants the affixes from the pattern only (modMiddle). - micros.modMiddle.apply(temp, 0, 0); - int prefixLength = micros.modMiddle.getPrefixLength(); - return temp.subSequence(0, prefixLength).toString(); - } - - /** - * @internal - * @deprecated This API is ICU internal only. Use {@link #populateFieldPosition} or - * {@link #getFieldIterator} for similar functionality. - */ - @Deprecated - public String getSuffix() { - NumberStringBuilder temp = new NumberStringBuilder(); - // #13453: DecimalFormat wants the affixes from the pattern only (modMiddle). - int length = micros.modMiddle.apply(temp, 0, 0); - int prefixLength = micros.modMiddle.getPrefixLength(); - return temp.subSequence(prefixLength, length).toString(); - } - /** * @internal * @deprecated This API is ICU internal only. diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/LocalizedNumberFormatter.java b/icu4j/main/classes/core/src/com/ibm/icu/number/LocalizedNumberFormatter.java index f614d1d70d..9e1404c961 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/LocalizedNumberFormatter.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/LocalizedNumberFormatter.java @@ -9,7 +9,6 @@ import com.ibm.icu.impl.Utility; import com.ibm.icu.impl.number.DecimalQuantity; import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD; import com.ibm.icu.impl.number.MacroProps; -import com.ibm.icu.impl.number.MicroProps; import com.ibm.icu.impl.number.NumberStringBuilder; import com.ibm.icu.math.BigDecimal; import com.ibm.icu.util.CurrencyAmount; @@ -137,16 +136,33 @@ public class LocalizedNumberFormatter extends NumberFormatterSettings Date: Wed, 11 Apr 2018 23:14:06 +0000 Subject: [PATCH 089/129] ICU-13634 Fixing affix overrides when using CurrencyPluralInfo. X-SVN-Rev: 41217 --- icu4c/source/i18n/number_decimfmtprops.h | 4 ---- icu4c/source/i18n/number_mapper.cpp | 20 ++++++++++++------- icu4c/source/i18n/number_mapper.h | 5 +++-- icu4c/source/i18n/numparse_impl.cpp | 2 +- .../CurrencyPluralInfoAffixProvider.java | 16 +++++++++------ .../impl/number/parse/NumberParserImpl.java | 2 +- .../ibm/icu/number/NumberPropertyMapper.java | 2 +- .../icu/dev/test/format/NumberFormatTest.java | 10 ++++++++++ 8 files changed, 39 insertions(+), 22 deletions(-) diff --git a/icu4c/source/i18n/number_decimfmtprops.h b/icu4c/source/i18n/number_decimfmtprops.h index 6632dfc5a2..d66a621250 100644 --- a/icu4c/source/i18n/number_decimfmtprops.h +++ b/icu4c/source/i18n/number_decimfmtprops.h @@ -132,10 +132,6 @@ struct U_I18N_API DecimalFormatProperties { DecimalFormatProperties(); - //DecimalFormatProperties(const DecimalFormatProperties &other) = default; - - DecimalFormatProperties& operator=(const DecimalFormatProperties& other) = default; - bool operator==(const DecimalFormatProperties& other) const; void clear(); diff --git a/icu4c/source/i18n/number_mapper.cpp b/icu4c/source/i18n/number_mapper.cpp index 24a09e6f4b..51ebb3c087 100644 --- a/icu4c/source/i18n/number_mapper.cpp +++ b/icu4c/source/i18n/number_mapper.cpp @@ -67,7 +67,7 @@ MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& propert warehouse.propertiesAPP.setTo(properties, status); affixProvider = &warehouse.propertiesAPP; } else { - warehouse.currencyPluralInfoAPP.setTo(*properties.currencyPluralInfo.fPtr, status); + warehouse.currencyPluralInfoAPP.setTo(*properties.currencyPluralInfo.fPtr, properties, status); warehouse.propertiesAPP.setToBogus(); affixProvider = &warehouse.currencyPluralInfoAPP; } @@ -434,17 +434,23 @@ bool PropertiesAffixPatternProvider::hasBody() const { } -void CurrencyPluralInfoAffixProvider::setTo(const CurrencyPluralInfo& cpi, UErrorCode& status) { +void CurrencyPluralInfoAffixProvider::setTo(const CurrencyPluralInfo& cpi, + const DecimalFormatProperties& properties, + UErrorCode& status) { + // We need to use a PropertiesAffixPatternProvider, not the simpler version ParsedPatternInfo, + // because user-specified affix overrides still need to work. fBogus = false; + DecimalFormatProperties pluralProperties(properties); for (int32_t plural = 0; plural < StandardPlural::COUNT; plural++) { const char* keyword = StandardPlural::getKeyword(static_cast(plural)); UnicodeString patternString; patternString = cpi.getCurrencyPluralPattern(keyword, patternString); - // ParsedPatternInfo does not support being overwritten if it was written previously; - // instead, we need to write to a fresh instance and move it into place. - ParsedPatternInfo temp; - PatternParser::parseToPatternInfo(patternString, temp, status); - affixesByPlural[plural] = std::move(temp); + PatternParser::parseToExistingProperties( + patternString, + pluralProperties, + IGNORE_ROUNDING_NEVER, + status); + affixesByPlural[plural].setTo(pluralProperties, status); } } diff --git a/icu4c/source/i18n/number_mapper.h b/icu4c/source/i18n/number_mapper.h index 5183b1195f..ef9de18f77 100644 --- a/icu4c/source/i18n/number_mapper.h +++ b/icu4c/source/i18n/number_mapper.h @@ -77,7 +77,8 @@ class CurrencyPluralInfoAffixProvider : public AffixPatternProvider, public UMem fBogus = true; } - void setTo(const CurrencyPluralInfo& cpi, UErrorCode& status); + void setTo(const CurrencyPluralInfo& cpi, const DecimalFormatProperties& properties, + UErrorCode& status); // AffixPatternProvider Methods: @@ -100,7 +101,7 @@ class CurrencyPluralInfoAffixProvider : public AffixPatternProvider, public UMem bool hasBody() const U_OVERRIDE; private: - ParsedPatternInfo affixesByPlural[StandardPlural::COUNT]; + PropertiesAffixPatternProvider affixesByPlural[StandardPlural::COUNT]; bool fBogus{true}; }; diff --git a/icu4c/source/i18n/numparse_impl.cpp b/icu4c/source/i18n/numparse_impl.cpp index 54609151bc..db1586bca4 100644 --- a/icu4c/source/i18n/numparse_impl.cpp +++ b/icu4c/source/i18n/numparse_impl.cpp @@ -88,7 +88,7 @@ NumberParserImpl::createParserFromProperties(const number::impl::DecimalFormatPr localPAPP.setTo(properties, status); affixProvider = &localPAPP; } else { - localCPIAP.setTo(*properties.currencyPluralInfo.fPtr, status); + localCPIAP.setTo(*properties.currencyPluralInfo.fPtr, properties, status); affixProvider = &localCPIAP; } if (affixProvider == nullptr || U_FAILURE(status)) { return nullptr; } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CurrencyPluralInfoAffixProvider.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CurrencyPluralInfoAffixProvider.java index 2540899cda..a3aae565af 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CurrencyPluralInfoAffixProvider.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CurrencyPluralInfoAffixProvider.java @@ -3,17 +3,21 @@ package com.ibm.icu.impl.number; import com.ibm.icu.impl.StandardPlural; -import com.ibm.icu.impl.number.PatternStringParser.ParsedPatternInfo; import com.ibm.icu.text.CurrencyPluralInfo; public class CurrencyPluralInfoAffixProvider implements AffixPatternProvider { - private final AffixPatternProvider[] affixesByPlural; + private final PropertiesAffixPatternProvider[] affixesByPlural; - public CurrencyPluralInfoAffixProvider(CurrencyPluralInfo cpi) { - affixesByPlural = new ParsedPatternInfo[StandardPlural.COUNT]; + public CurrencyPluralInfoAffixProvider(CurrencyPluralInfo cpi, DecimalFormatProperties properties) { + // We need to use a PropertiesAffixPatternProvider, not the simpler version ParsedPatternInfo, + // because user-specified affix overrides still need to work. + affixesByPlural = new PropertiesAffixPatternProvider[StandardPlural.COUNT]; + DecimalFormatProperties pluralProperties = new DecimalFormatProperties(); + pluralProperties.copyFrom(properties); for (StandardPlural plural : StandardPlural.VALUES) { - affixesByPlural[plural.ordinal()] = PatternStringParser - .parseToPatternInfo(cpi.getCurrencyPluralPattern(plural.getKeyword())); + String pattern = cpi.getCurrencyPluralPattern(plural.getKeyword()); + PatternStringParser.parseToExistingProperties(pattern, pluralProperties); + affixesByPlural[plural.ordinal()] = new PropertiesAffixPatternProvider(pluralProperties); } } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java index 957a593345..bff15dce37 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java @@ -139,7 +139,7 @@ public class NumberParserImpl { if (properties.getCurrencyPluralInfo() == null) { affixProvider = new PropertiesAffixPatternProvider(properties); } else { - affixProvider = new CurrencyPluralInfoAffixProvider(properties.getCurrencyPluralInfo()); + affixProvider = new CurrencyPluralInfoAffixProvider(properties.getCurrencyPluralInfo(), properties); } Currency currency = CustomSymbolCurrency.resolve(properties.getCurrency(), locale, symbols); boolean isStrict = properties.getParseMode() == ParseMode.STRICT; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberPropertyMapper.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberPropertyMapper.java index b3051bc4f6..0cbdf3c5ef 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberPropertyMapper.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberPropertyMapper.java @@ -102,7 +102,7 @@ final class NumberPropertyMapper { if (properties.getCurrencyPluralInfo() == null) { affixProvider = new PropertiesAffixPatternProvider(properties); } else { - affixProvider = new CurrencyPluralInfoAffixProvider(properties.getCurrencyPluralInfo()); + affixProvider = new CurrencyPluralInfoAffixProvider(properties.getCurrencyPluralInfo(), properties); } macros.affixProvider = affixProvider; diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java index 6a784fdf4c..728c538197 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java @@ -6027,4 +6027,14 @@ public class NumberFormatTest extends TestFmwk { assertEquals("Should fail to parse", 0, ppos.getIndex()); assertEquals("Should fail to parse", 0, ppos.getErrorIndex()); } + + @Test + public void testCurrencyPluralAffixOverrides() { + // The affix setters should override CurrencyPluralInfo, used in the plural currency constructor. + DecimalFormat df = (DecimalFormat) NumberFormat.getInstance(ULocale.ENGLISH, NumberFormat.PLURALCURRENCYSTYLE); + df.setCurrency(Currency.getInstance("USD")); + df.setPositiveSuffix("lala"); + assertEquals("Custom suffix should round-trip", "lala", df.getPositiveSuffix()); + assertEquals("Custom suffix should be used in formatting", "123.00lala", df.format(123)); + } } From 5f57f0446603003fb1640ded0fefefd8b8f139e7 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Thu, 12 Apr 2018 04:04:23 +0000 Subject: [PATCH 090/129] ICU-13634 Updating test expectations for the default currency long name. X-SVN-Rev: 41218 --- icu4c/source/i18n/numparse_affixes.h | 4 ---- icu4c/source/test/intltest/numfmtst.cpp | 9 ++++----- icu4c/source/test/intltest/numfmtst.h | 2 +- .../com/ibm/icu/impl/number/CustomSymbolCurrency.java | 6 +++--- .../com/ibm/icu/dev/test/format/NumberFormatTest.java | 2 ++ 5 files changed, 10 insertions(+), 13 deletions(-) diff --git a/icu4c/source/i18n/numparse_affixes.h b/icu4c/source/i18n/numparse_affixes.h index 08a7d912c6..fe27cad03e 100644 --- a/icu4c/source/i18n/numparse_affixes.h +++ b/icu4c/source/i18n/numparse_affixes.h @@ -79,10 +79,6 @@ struct AffixTokenMatcherSetupData { const DecimalFormatSymbols& dfs; IgnorablesMatcher& ignorables; const Locale& locale; - -// const UChar* currencyCode, const UnicodeString* currency1, -// const UnicodeString* currency2, const DecimalFormatSymbols* dfs, -// IgnorablesMatcher* ignorables, const Locale* locale }; diff --git a/icu4c/source/test/intltest/numfmtst.cpp b/icu4c/source/test/intltest/numfmtst.cpp index 9ac2f98d3d..8afd143a39 100644 --- a/icu4c/source/test/intltest/numfmtst.cpp +++ b/icu4c/source/test/intltest/numfmtst.cpp @@ -651,6 +651,7 @@ void NumberFormatTest::runIndexedTest( int32_t index, UBool exec, const char* &n TESTCASE_AUTO(Test11649_toPatternWithMultiCurrency); TESTCASE_AUTO(Test13327_numberingSystemBufferOverflow); TESTCASE_AUTO(Test13391_chakmaParsing); + TESTCASE_AUTO(Test11735_ExceptionIssue); TESTCASE_AUTO(Test11035_FormatCurrencyAmount); TESTCASE_AUTO(Test11318_DoubleConversion); TESTCASE_AUTO_END; @@ -8724,9 +8725,7 @@ void NumberFormatTest::Test11376_getAndSetPositivePrefix() { } DecimalFormat *dfmt = (DecimalFormat *) fmt.getAlias(); UnicodeString result; - UnicodeString tripleIntlCurrency(" \\u00a4\\u00a4\\u00a4"); - tripleIntlCurrency = tripleIntlCurrency.unescape(); - assertEquals("", tripleIntlCurrency, dfmt->getPositiveSuffix(result)); + assertEquals("", u" (unknown currency)", dfmt->getPositiveSuffix(result)); dfmt->setCurrency(USD); // getPositiveSuffix() always returns the suffix for the @@ -8919,8 +8918,8 @@ void NumberFormatTest::verifyFieldPositionIterator( } } -void NumberFormatTest::checkExceptionIssue11735() { - UErrorCode status; +void NumberFormatTest::Test11735_ExceptionIssue() { + UErrorCode status = U_ZERO_ERROR; Locale enLocale("en"); DecimalFormatSymbols symbols(enLocale, status); diff --git a/icu4c/source/test/intltest/numfmtst.h b/icu4c/source/test/intltest/numfmtst.h index c212bea393..d318cad93f 100644 --- a/icu4c/source/test/intltest/numfmtst.h +++ b/icu4c/source/test/intltest/numfmtst.h @@ -218,7 +218,7 @@ class NumberFormatTest: public CalendarTimeZoneTest { void Test13327_numberingSystemBufferOverflow(); void Test13391_chakmaParsing(); - void checkExceptionIssue11735(); + void Test11735_ExceptionIssue(); void Test11035_FormatCurrencyAmount(); void Test11318_DoubleConversion(); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CustomSymbolCurrency.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CustomSymbolCurrency.java index b794aebe53..a04170b625 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CustomSymbolCurrency.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CustomSymbolCurrency.java @@ -17,14 +17,14 @@ public class CustomSymbolCurrency extends Currency { if (currency == null) { currency = symbols.getCurrency(); } - String currency1Sym = symbols.getCurrencySymbol(); - String currency2Sym = symbols.getInternationalCurrencySymbol(); if (currency == null) { - return new CustomSymbolCurrency("XXX", currency1Sym, currency2Sym); + return Currency.getInstance("XXX"); } if (!currency.equals(symbols.getCurrency())) { return currency; } + String currency1Sym = symbols.getCurrencySymbol(); + String currency2Sym = symbols.getInternationalCurrencySymbol(); String currency1 = currency.getName(symbols.getULocale(), Currency.SYMBOL_NAME, null); String currency2 = currency.getCurrencyCode(); if (!currency1.equals(currency1Sym) || !currency2.equals(currency2Sym)) { diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java index 728c538197..443dc1dbd5 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java @@ -6032,7 +6032,9 @@ public class NumberFormatTest extends TestFmwk { public void testCurrencyPluralAffixOverrides() { // The affix setters should override CurrencyPluralInfo, used in the plural currency constructor. DecimalFormat df = (DecimalFormat) NumberFormat.getInstance(ULocale.ENGLISH, NumberFormat.PLURALCURRENCYSTYLE); + assertEquals("Defaults to unknown currency", " (unknown currency)", df.getPositiveSuffix()); df.setCurrency(Currency.getInstance("USD")); + assertEquals("Should resolve to CurrencyPluralInfo", " US dollars", df.getPositiveSuffix()); df.setPositiveSuffix("lala"); assertEquals("Custom suffix should round-trip", "lala", df.getPositiveSuffix()); assertEquals("Custom suffix should be used in formatting", "123.00lala", df.format(123)); From 6c1714870fa3799760dc0c8c9de2b20870616cf2 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Thu, 12 Apr 2018 05:15:19 +0000 Subject: [PATCH 091/129] ICU-13634 Updating test expectations for strict currency position in strict mode. Other minor changes. All but one test is now passing in numfmtst.cpp. X-SVN-Rev: 41220 --- icu4c/source/i18n/decimfmt.cpp | 20 +++ icu4c/source/i18n/numparse_symbols.cpp | 4 +- icu4c/source/test/intltest/numfmtst.cpp | 125 ++++++++++++------ icu4c/source/test/intltest/numfmtst.h | 1 + .../numberformattestspecification.txt | 3 +- .../impl/number/parse/MinusSignMatcher.java | 3 +- .../impl/number/parse/PlusSignMatcher.java | 2 +- .../icu/dev/test/format/NumberFormatTest.java | 36 +++++ 8 files changed, 148 insertions(+), 46 deletions(-) diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp index e84bb82238..b3bc2455d4 100644 --- a/icu4c/source/i18n/decimfmt.cpp +++ b/icu4c/source/i18n/decimfmt.cpp @@ -867,21 +867,41 @@ void DecimalFormat::applyLocalizedPattern(const UnicodeString& localizedPattern, } void DecimalFormat::setMaximumIntegerDigits(int32_t newValue) { + // For backwards compatibility, conflicting min/max need to keep the most recent setting. + int32_t min = fProperties->minimumIntegerDigits; + if (min >= 0 && min > newValue) { + fProperties->minimumIntegerDigits = newValue; + } fProperties->maximumIntegerDigits = newValue; refreshFormatterNoError(); } void DecimalFormat::setMinimumIntegerDigits(int32_t newValue) { + // For backwards compatibility, conflicting min/max need to keep the most recent setting. + int32_t max = fProperties->maximumIntegerDigits; + if (max >= 0 && max < newValue) { + fProperties->maximumIntegerDigits = newValue; + } fProperties->minimumIntegerDigits = newValue; refreshFormatterNoError(); } void DecimalFormat::setMaximumFractionDigits(int32_t newValue) { + // For backwards compatibility, conflicting min/max need to keep the most recent setting. + int32_t min = fProperties->minimumFractionDigits; + if (min >= 0 && min > newValue) { + fProperties->minimumFractionDigits = newValue; + } fProperties->maximumFractionDigits = newValue; refreshFormatterNoError(); } void DecimalFormat::setMinimumFractionDigits(int32_t newValue) { + // For backwards compatibility, conflicting min/max need to keep the most recent setting. + int32_t max = fProperties->maximumFractionDigits; + if (max >= 0 && max < newValue) { + fProperties->maximumFractionDigits = newValue; + } fProperties->minimumFractionDigits = newValue; refreshFormatterNoError(); } diff --git a/icu4c/source/i18n/numparse_symbols.cpp b/icu4c/source/i18n/numparse_symbols.cpp index d66e3e704b..9882fb06ce 100644 --- a/icu4c/source/i18n/numparse_symbols.cpp +++ b/icu4c/source/i18n/numparse_symbols.cpp @@ -109,7 +109,7 @@ MinusSignMatcher::MinusSignMatcher(const DecimalFormatSymbols& dfs, bool allowTr } bool MinusSignMatcher::isDisabled(const ParsedNumber& result) const { - return 0 != (result.flags & FLAG_NEGATIVE) || (fAllowTrailing ? false : result.seenNumber()); + return !fAllowTrailing && result.seenNumber(); } void MinusSignMatcher::accept(StringSegment& segment, ParsedNumber& result) const { @@ -182,7 +182,7 @@ PlusSignMatcher::PlusSignMatcher(const DecimalFormatSymbols& dfs, bool allowTrai } bool PlusSignMatcher::isDisabled(const ParsedNumber& result) const { - return fAllowTrailing ? false : result.seenNumber(); + return !fAllowTrailing && result.seenNumber(); } void PlusSignMatcher::accept(StringSegment& segment, ParsedNumber& result) const { diff --git a/icu4c/source/test/intltest/numfmtst.cpp b/icu4c/source/test/intltest/numfmtst.cpp index 8afd143a39..3d26e1a6b6 100644 --- a/icu4c/source/test/intltest/numfmtst.cpp +++ b/icu4c/source/test/intltest/numfmtst.cpp @@ -602,6 +602,7 @@ void NumberFormatTest::runIndexedTest( int32_t index, UBool exec, const char* &n TESTCASE_AUTO(TestSpaceParsing); TESTCASE_AUTO(TestMultiCurrencySign); TESTCASE_AUTO(TestCurrencyFormatForMixParsing); + TESTCASE_AUTO(TestMismatchedCurrencyFormatFail); TESTCASE_AUTO(TestDecimalFormatCurrencyParse); TESTCASE_AUTO(TestCurrencyIsoPluralFormat); TESTCASE_AUTO(TestCurrencyParsing); @@ -644,7 +645,7 @@ void NumberFormatTest::runIndexedTest( int32_t index, UBool exec, const char* &n TESTCASE_AUTO(TestFractionalDigitsForCurrency); TESTCASE_AUTO(TestFormatCurrencyPlural); TESTCASE_AUTO(Test11868); -// TESTCASE_AUTO(Test10727_RoundingZero); + TESTCASE_AUTO(Test10727_RoundingZero); TESTCASE_AUTO(Test11376_getAndSetPositivePrefix); TESTCASE_AUTO(Test11475_signRecognition); TESTCASE_AUTO(Test11640_getAffixes); @@ -671,10 +672,10 @@ NumberFormatTest::TestAPI(void) } if(test != NULL) { test->setMinimumIntegerDigits(10); - test->setMaximumIntegerDigits(2); + test->setMaximumIntegerDigits(1); test->setMinimumFractionDigits(10); - test->setMaximumFractionDigits(2); + test->setMaximumFractionDigits(1); UnicodeString result; FieldPosition pos; @@ -689,9 +690,14 @@ NumberFormatTest::TestAPI(void) result.remove(); int64_t ll = 12; test->format(ll, result); - if (result != "12.00"){ - errln("format int64_t error"); - } + assertEquals("format int64_t error", u"2.0", result); + + test->setMinimumIntegerDigits(4); + test->setMinimumFractionDigits(4); + + result.remove(); + test->format(ll, result); + assertEquals("format int64_t error", u"0,012.0000", result); ParsePosition ppos; LocalPointer currAmt(test->parseCurrency("",ppos)); @@ -1748,6 +1754,8 @@ void NumberFormatTest::TestWhiteSpaceParsing(void) { errcheckln(ec, "FAIL: Constructor - %s", u_errorName(ec)); return; } + // From ICU 62, flexible whitespace needs lenient mode + fmt.setLenient(TRUE); int32_t n = 1234; expect(fmt, "a b1234c ", n); expect(fmt, "a b1234c ", n); @@ -2225,9 +2233,9 @@ void NumberFormatTest::TestSurrogateSupport(void) { int32_t(-20), expStr, status); custom.setSymbol(DecimalFormatSymbols::kPercentSymbol, "percent"); - patternStr = "'You''ve lost ' 0.00 %' of your money today'"; + patternStr = "'You''ve lost ' -0.00 %' of your money today'"; patternStr = patternStr.unescape(); - expStr = UnicodeString(" minus You've lost 2000decimal00 percent of your money today", ""); + expStr = UnicodeString(" minus You've lost minus 2000decimal00 percent of your money today", ""); status = U_ZERO_ERROR; expect2(new DecimalFormat(patternStr, custom, status), int32_t(-20), expStr, status); @@ -3654,28 +3662,24 @@ NumberFormatTest::TestSpaceParsing() { // the data are: // the string to be parsed, parsed position, parsed error index const TestSpaceParsingItem DATA[] = { - // TOTO: Update the following TODOs, some may be handled now {"$124", 4, -1, FALSE}, {"$124 $124", 4, -1, FALSE}, {"$124 ", 4, -1, FALSE}, - //{"$ 124 ", 5, -1, FALSE}, // TODO: need to handle space correctly - //{"$\\u00A0124 ", 5, -1, FALSE}, // TODO: need to handle space correctly - {"$ 124 ", 0, 1, FALSE}, // errorIndex used to be 0, now 1 (better) - {"$\\u00A0124 ", 0, 1, FALSE}, // errorIndex used to be 0, now 1 (better) - {" $ 124 ", 0, 0, FALSE}, // TODO: need to handle space correctly - {"124$", 0, 3, FALSE}, // TODO: need to handle space correctly - // {"124 $", 5, -1, FALSE}, // TODO: OK or not, need currency spacing rule - {"124 $", 0, 3, FALSE}, + {"$ 124 ", 0, 1, FALSE}, + {"$\\u00A0124 ", 5, -1, FALSE}, + {" $ 124 ", 0, 0, FALSE}, + {"124$", 0, 4, FALSE}, + {"124 $", 0, 5, FALSE}, {"$124", 4, -1, TRUE}, {"$124 $124", 4, -1, TRUE}, {"$124 ", 4, -1, TRUE}, {"$ 124 ", 5, -1, TRUE}, {"$\\u00A0124 ", 5, -1, TRUE}, {" $ 124 ", 6, -1, TRUE}, - //{"124$", 4, -1, TRUE}, // TODO: need to handle trailing currency correctly - {"124$", 3, -1, TRUE}, - //{"124 $", 5, -1, TRUE}, // TODO: OK or not, need currency spacing rule - {"124 $", 4, -1, TRUE}, + {"124$", 4, -1, TRUE}, + {"124$", 4, -1, TRUE}, + {"124 $", 5, -1, TRUE}, + {"124 $", 5, -1, TRUE}, }; UErrorCode status = U_ZERO_ERROR; Locale locale("en_US"); @@ -3898,7 +3902,7 @@ NumberFormatTest::TestCurrencyFormatForMixParsing() { "$1,234.56", // string to be parsed "USD1,234.56", "US dollars1,234.56", - "1,234.56 US dollars" // NOTE: Fails in 62 because currency format is not compatible with pattern + // "1,234.56 US dollars" // Fails in 62 because currency format is not compatible with pattern. }; const CurrencyAmount* curramt = NULL; for (uint32_t i = 0; i < UPRV_LENGTHOF(formats); ++i) { @@ -3927,6 +3931,39 @@ NumberFormatTest::TestCurrencyFormatForMixParsing() { } +/** Starting in ICU 62, strict mode is actually strict with currency formats. */ +void NumberFormatTest::TestMismatchedCurrencyFormatFail() { + IcuTestErrorCode status(*this, "TestMismatchedCurrencyFormatFail"); + LocalPointer df( + dynamic_cast(DecimalFormat::createCurrencyInstance("en", status)), status); + UnicodeString pattern; + assertEquals("Test assumes that currency sign is at the beginning", + u"\u00A4#,##0.00", + df->toPattern(pattern)); + // Should round-trip on the correct currency format: + expect2(*df, 1.23, u"XXX\u00A01.23"); + df->setCurrency(u"EUR", status); + expect2(*df, 1.23, u"\u20AC1.23"); + // Should parse with currency in the wrong place in lenient mode + df->setLenient(TRUE); + expect(*df, u"1.23\u20AC", 1.23); + expectParseCurrency(*df, u"EUR", 1.23, "1.23\\u20AC"); + // Should NOT parse with currency in the wrong place in STRICT mode + df->setLenient(FALSE); + { + Formattable result; + ErrorCode failStatus; + df->parse(u"1.23\u20AC", result, failStatus); + assertEquals("Should fail to parse", U_INVALID_FORMAT_ERROR, failStatus); + } + { + ParsePosition ppos; + df->parseCurrency(u"1.23\u20AC", ppos); + assertEquals("Should fail to parse currency", 0, ppos.getIndex()); + } +} + + void NumberFormatTest::TestDecimalFormatCurrencyParse() { // Locale.US @@ -3951,11 +3988,13 @@ NumberFormatTest::TestDecimalFormatCurrencyParse() { // string to be parsed, the parsed result (number) {"$1.00", "1"}, {"USD1.00", "1"}, - {"1.00 US dollar", "1"}, // NOTE: Fails in 62 because currency format is not compatible with pattern + {"1.00 US dollar", "1"}, {"$1,234.56", "1234.56"}, {"USD1,234.56", "1234.56"}, - {"1,234.56 US dollar", "1234.56"}, // NOTE: Fails in 62 because currency format is not compatible with pattern + {"1,234.56 US dollar", "1234.56"}, }; + // NOTE: ICU 62 requires that the currency format match the pattern in strict mode. + fmt->setLenient(TRUE); for (uint32_t i = 0; i < UPRV_LENGTHOF(DATA); ++i) { UnicodeString stringToBeParsed = ctou(DATA[i][0]); double parsedResult = atof(DATA[i][1]); @@ -4043,8 +4082,8 @@ NumberFormatTest::TestCurrencyIsoPluralFormat() { } // test parsing, and test parsing for all currency formats. // NOTE: ICU 62 requires that the currency format match the pattern in strict mode. - //for (int j = 3; j < 6; ++j) { - for (int j = 3 + kIndex; j <= 3 + kIndex; j++) { + numFmt->setLenient(TRUE); + for (int j = 3; j < 6; ++j) { // DATA[i][3] is the currency format result using // CURRENCYSTYLE formatter. // DATA[i][4] is the currency format result using @@ -4084,24 +4123,24 @@ NumberFormatTest::TestCurrencyParsing() { // format result using CURRENCYSTYLE, // format result using ISOCURRENCYSTYLE, // format result using PLURALCURRENCYSTYLE, - {"en_US", "1", "USD", "$1.00", "USD\\u00A01.00", "1.00 US dollar"}, + {"en_US", "1", "USD", "$1.00", "USD\\u00A01.00", "1.00 US dollars"}, {"pa_IN", "1", "USD", "US$\\u00A01.00", "USD\\u00A01.00", "1.00 \\u0a2f\\u0a42.\\u0a10\\u0a38. \\u0a21\\u0a3e\\u0a32\\u0a30"}, {"es_AR", "1", "USD", "US$\\u00A01,00", "USD\\u00A01,00", "1,00 d\\u00f3lar estadounidense"}, {"ar_EG", "1", "USD", "\\u0661\\u066b\\u0660\\u0660\\u00a0US$", "\\u0661\\u066b\\u0660\\u0660\\u00a0USD", "\\u0661\\u066b\\u0660\\u0660 \\u062f\\u0648\\u0644\\u0627\\u0631 \\u0623\\u0645\\u0631\\u064a\\u0643\\u064a"}, {"fa_CA", "1", "USD", "\\u200e$\\u06f1\\u066b\\u06f0\\u06f0", "\\u200eUSD\\u06f1\\u066b\\u06f0\\u06f0", "\\u06f1\\u066b\\u06f0\\u06f0 \\u062f\\u0644\\u0627\\u0631 \\u0627\\u0645\\u0631\\u06cc\\u06a9\\u0627"}, {"he_IL", "1", "USD", "\\u200f1.00\\u00a0$", "\\u200f1.00\\u00a0USD", "1.00 \\u05d3\\u05d5\\u05dc\\u05e8 \\u05d0\\u05de\\u05e8\\u05d9\\u05e7\\u05d0\\u05d9"}, - {"hr_HR", "1", "USD", "1,00\\u00a0USD", "1,00\\u00a0USD", "1,00 Ameri\\u010dki dolar"}, + {"hr_HR", "1", "USD", "1,00\\u00a0USD", "1,00\\u00a0USD", "1,00 ameri\\u010Dkih dolara"}, {"id_ID", "1", "USD", "US$\\u00A01,00", "USD\\u00A01,00", "1,00 Dolar Amerika Serikat"}, - {"it_IT", "1", "USD", "1,00\\u00a0USD", "1,00\\u00a0USD", "1,00 Dollaro Statunitense"}, + {"it_IT", "1", "USD", "1,00\\u00a0USD", "1,00\\u00a0USD", "1,00 dollari statunitensi"}, {"ko_KR", "1", "USD", "US$\\u00A01.00", "USD\\u00A01.00", "1.00 \\ubbf8\\uad6d \\ub2ec\\ub7ec"}, {"ja_JP", "1", "USD", "$1.00", "USD\\u00A01.00", "1.00\\u00A0\\u7c73\\u30c9\\u30eb"}, {"zh_CN", "1", "CNY", "\\uFFE51.00", "CNY\\u00A001.00", "1.00\\u00A0\\u4EBA\\u6C11\\u5E01"}, {"zh_TW", "1", "CNY", "CN\\u00A51.00", "CNY\\u00A01.00", "1.00 \\u4eba\\u6c11\\u5e63"}, {"zh_Hant", "1", "CNY", "CN\\u00A51.00", "CNY\\u00A01.00", "1.00 \\u4eba\\u6c11\\u5e63"}, - {"zh_Hant", "1", "JPY", "\\u00A51.00", "JPY\\u00A01.00", "1.00 \\u65e5\\u5713"}, - {"ja_JP", "1", "JPY", "\\uFFE51.00", "JPY\\u00A01.00", "1.00\\u00A0\\u65e5\\u672c\\u5186"}, - {"ja_JP", "1", "JPY", "\\u00A51.00", "JPY\\u00A01.00", "1.00\\u00A0\\u65e5\\u672c\\u5186"}, - {"ru_RU", "1", "RUB", "1,00\\u00A0\\u00A0\\u20BD", "1,00\\u00A0\\u00A0RUB", "1,00 \\u0420\\u043E\\u0441\\u0441\\u0438\\u0439\\u0441\\u043A\\u0438\\u0439 \\u0440\\u0443\\u0431\\u043B\\u044C"} + {"zh_Hant", "1", "JPY", "\\u00A51.00", "JPY\\u00A01.00", "1 \\u65E5\\u5713"}, + {"ja_JP", "1", "JPY", "\\uFFE51.00", "JPY\\u00A01.00", "1\\u00A0\\u5186"}, + {"ja_JP", "1", "JPY", "\\u00A51.00", "JPY\\u00A01.00", "1\\u00A0\\u5186"}, + {"ru_RU", "1", "RUB", "1,00\\u00A0\\u00A0\\u20BD", "1,00\\u00A0\\u00A0RUB", "1,00 \\u0440\\u043E\\u0441\\u0441\\u0438\\u0439\\u0441\\u043A\\u043E\\u0433\\u043E \\u0440\\u0443\\u0431\\u043B\\u044F"} }; static const UNumberFormatStyle currencyStyles[] = { UNUM_CURRENCY, @@ -4148,9 +4187,6 @@ for (;;) { UnicodeString strBuf; numFmt->format(numberToBeFormat, strBuf); - // TODO: Re-enable the following test block. It has been disabled since - // the code was first checked-in (r25497) - /* int resultDataIndex = 3 + kIndex; // DATA[i][resultDataIndex] is the currency format result // using 'k' currency style. @@ -4158,11 +4194,10 @@ for (;;) { if (strBuf.compare(formatResult)) { errln("FAIL: Expected " + formatResult + " actual: " + strBuf); } - */ // test parsing, and test parsing for all currency formats. // NOTE: ICU 62 requires that the currency format match the pattern in strict mode. - //for (int j = 3; j < 6; ++j) { - for (int j = 3 + kIndex; j <= 3 + kIndex; j++) { + numFmt->setLenient(TRUE); + for (int j = 3; j < 6; ++j) { // DATA[i][3] is the currency format result using // CURRENCYSTYLE formatter. // DATA[i][4] is the currency format result using @@ -6748,7 +6783,8 @@ NumberFormatTest::TestParseCurrencyInUCurr() { UnicodeString formatted = ctou(DATA[i]); UErrorCode status = U_ZERO_ERROR; NumberFormat* numFmt = NumberFormat::createInstance(locale, UNUM_CURRENCY, status); - numFmt->setLenient(TRUE); // ICU 62 PATCH + // NOTE: ICU 62 requires that the currency format match the pattern in strict mode. + numFmt->setLenient(TRUE); if (numFmt != NULL && U_SUCCESS(status)) { ParsePosition parsePos; LocalPointer currAmt(numFmt->parseCurrency(formatted, parsePos)); @@ -8694,6 +8730,15 @@ void NumberFormatTest::Test11868() { } } +void NumberFormatTest::Test10727_RoundingZero() { + IcuTestErrorCode status(*this, "Test10727_RoundingZero"); + DecimalQuantity dq; + dq.setToDouble(-0.0); + assertTrue("", dq.isNegative()); + dq.roundToMagnitude(0, UNUM_ROUND_HALFEVEN, status); + assertTrue("", dq.isNegative()); +} + void NumberFormatTest::Test11376_getAndSetPositivePrefix() { { const UChar USD[] = {0x55, 0x53, 0x44, 0x0}; diff --git a/icu4c/source/test/intltest/numfmtst.h b/icu4c/source/test/intltest/numfmtst.h index d318cad93f..d71b8c4880 100644 --- a/icu4c/source/test/intltest/numfmtst.h +++ b/icu4c/source/test/intltest/numfmtst.h @@ -155,6 +155,7 @@ class NumberFormatTest: public CalendarTimeZoneTest { void TestSpaceParsing(); void TestMultiCurrencySign(); void TestCurrencyFormatForMixParsing(); + void TestMismatchedCurrencyFormatFail(); void TestDecimalFormatCurrencyParse(); void TestCurrencyIsoPluralFormat(); void TestCurrencyParsing(); diff --git a/icu4c/source/test/testdata/numberformattestspecification.txt b/icu4c/source/test/testdata/numberformattestspecification.txt index 900f77e6a1..629073890c 100644 --- a/icu4c/source/test/testdata/numberformattestspecification.txt +++ b/icu4c/source/test/testdata/numberformattestspecification.txt @@ -381,7 +381,8 @@ set pattern #,##0.### begin format maxIntegerDigits output breaks 123 1 3 -0 0 0 +// C obeys maxIntegerDigits and prints after the decimal place +0 0 .0 Q // C and Q ignore max integer if it is less than zero and prints "123" 123 -2147483648 0 CQ 12345 1 5 diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/MinusSignMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/MinusSignMatcher.java index 08e9d1a9ec..616015c441 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/MinusSignMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/MinusSignMatcher.java @@ -37,8 +37,7 @@ public class MinusSignMatcher extends SymbolMatcher { @Override protected boolean isDisabled(ParsedNumber result) { - return 0 != (result.flags & ParsedNumber.FLAG_NEGATIVE) - || (allowTrailing ? false : result.seenNumber()); + return !allowTrailing && result.seenNumber(); } @Override diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/PlusSignMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/PlusSignMatcher.java index d7b9411061..dccb684631 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/PlusSignMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/PlusSignMatcher.java @@ -37,7 +37,7 @@ public class PlusSignMatcher extends SymbolMatcher { @Override protected boolean isDisabled(ParsedNumber result) { - return allowTrailing ? false : result.seenNumber(); + return !allowTrailing && result.seenNumber(); } @Override diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java index 443dc1dbd5..dbbdc13210 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java @@ -593,6 +593,36 @@ public class NumberFormatTest extends TestFmwk { } } + + /** Starting in ICU 62, strict mode is actually strict with currency formats. */ + @Test + public void TestMismatchedCurrencyFormatFail() { + DecimalFormat df = (DecimalFormat) NumberFormat.getCurrencyInstance(ULocale.ENGLISH); + assertEquals("Test assumes that currency sign is at the beginning", + "\u00A4#,##0.00", + df.toPattern()); + // Should round-trip on the correct currency format: + expect2(df, 1.23, "XXX\u00A01.23"); + df.setCurrency(Currency.getInstance("EUR")); + expect2(df, 1.23, "\u20AC1.23"); + // Should parse with currency in the wrong place in lenient mode + df.setParseStrict(false); + expect(df, "1.23\u20AC", 1.23); + expectParseCurrency(df, Currency.getInstance("EUR"), "1.23\u20AC"); + // Should NOT parse with currency in the wrong place in STRICT mode + df.setParseStrict(true); + { + ParsePosition ppos = new ParsePosition(0); + df.parse("1.23\u20AC", ppos); + assertEquals("Should fail to parse", 0, ppos.getIndex()); + } + { + ParsePosition ppos = new ParsePosition(0); + df.parseCurrency("1.23\u20AC", ppos); + assertEquals("Should fail to parse currency", 0, ppos.getIndex()); + } + } + @Test public void TestDecimalFormatCurrencyParse() { // Locale.US @@ -6039,4 +6069,10 @@ public class NumberFormatTest extends TestFmwk { assertEquals("Custom suffix should round-trip", "lala", df.getPositiveSuffix()); assertEquals("Custom suffix should be used in formatting", "123.00lala", df.format(123)); } + + @Test + public void testParseDoubleMinus() { + DecimalFormat df = new DecimalFormat("-0", DecimalFormatSymbols.getInstance(ULocale.ENGLISH)); + expect2(df, -5, "--5"); + } } From af0f8e62e4ecde8c6517bca3d2a1068cd0d04708 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Thu, 12 Apr 2018 06:49:24 +0000 Subject: [PATCH 092/129] ICU-13634 In accordance with ICU-TC meeting, changing percent parsing behavior to be closer to that of ICU 60. X-SVN-Rev: 41222 --- icu4c/source/i18n/numparse_impl.cpp | 19 +++++++++-- icu4c/source/i18n/numparse_impl.h | 2 ++ icu4c/source/i18n/numparse_validators.cpp | 12 +++++++ icu4c/source/i18n/numparse_validators.h | 18 +++++++++++ icu4c/source/test/intltest/numfmtst.cpp | 32 +++++++++++++++++++ icu4c/source/test/intltest/numfmtst.h | 1 + .../numberformattestspecification.txt | 4 +-- .../icu/impl/number/parse/FlagHandler.java | 28 ++++++++++++++++ .../impl/number/parse/NumberParserImpl.java | 20 ++++++++++-- .../data/numberformattestspecification.txt | 4 +-- .../icu/dev/test/format/NumberFormatTest.java | 30 ++++++++++++++++- 11 files changed, 161 insertions(+), 9 deletions(-) create mode 100644 icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/FlagHandler.java diff --git a/icu4c/source/i18n/numparse_impl.cpp b/icu4c/source/i18n/numparse_impl.cpp index db1586bca4..acc68394a1 100644 --- a/icu4c/source/i18n/numparse_impl.cpp +++ b/icu4c/source/i18n/numparse_impl.cpp @@ -150,6 +150,23 @@ NumberParserImpl::createParserFromProperties(const number::impl::DecimalFormatPr parser->addMatcher(parser->fLocalMatchers.currency = {currencySymbols, symbols, status}); } + /////////////// + /// PERCENT /// + /////////////// + + // ICU-TC meeting, April 11, 2018: accept percent/permille only if it is in the pattern, + // and to maintain regressive behavior, divide by 100 even if no percent sign is present. + if (affixProvider->containsSymbolType(AffixPatternType::TYPE_PERCENT, status)) { + parser->addMatcher(parser->fLocalMatchers.percent = {symbols}); + // causes number to be always scaled by 100: + parser->addMatcher(parser->fLocalValidators.percentFlags = {ResultFlags::FLAG_PERCENT}); + } + if (affixProvider->containsSymbolType(AffixPatternType::TYPE_PERMILLE, status)) { + parser->addMatcher(parser->fLocalMatchers.permille = {symbols}); + // causes number to be always scaled by 1000: + parser->addMatcher(parser->fLocalValidators.permilleFlags = {ResultFlags::FLAG_PERMILLE}); + } + /////////////////////////////// /// OTHER STANDARD MATCHERS /// /////////////////////////////// @@ -157,8 +174,6 @@ NumberParserImpl::createParserFromProperties(const number::impl::DecimalFormatPr if (!isStrict) { parser->addMatcher(parser->fLocalMatchers.plusSign = {symbols, false}); parser->addMatcher(parser->fLocalMatchers.minusSign = {symbols, false}); - parser->addMatcher(parser->fLocalMatchers.percent = {symbols}); - parser->addMatcher(parser->fLocalMatchers.permille = {symbols}); } parser->addMatcher(parser->fLocalMatchers.nan = {symbols}); parser->addMatcher(parser->fLocalMatchers.infinity = {symbols}); diff --git a/icu4c/source/i18n/numparse_impl.h b/icu4c/source/i18n/numparse_impl.h index 748b9415ec..4677ae14d8 100644 --- a/icu4c/source/i18n/numparse_impl.h +++ b/icu4c/source/i18n/numparse_impl.h @@ -80,6 +80,8 @@ class NumberParserImpl : public MutableMatcherCollection { RequireExponentValidator exponent; RequireNumberValidator number; MultiplierParseHandler multiplier; + FlagHandler percentFlags; + FlagHandler permilleFlags; } fLocalValidators; explicit NumberParserImpl(parse_flags_t parseFlags); diff --git a/icu4c/source/i18n/numparse_validators.cpp b/icu4c/source/i18n/numparse_validators.cpp index 724b0cf031..a36a15f512 100644 --- a/icu4c/source/i18n/numparse_validators.cpp +++ b/icu4c/source/i18n/numparse_validators.cpp @@ -80,4 +80,16 @@ UnicodeString RequireNumberValidator::toString() const { } +FlagHandler::FlagHandler(result_flags_t flags) + : fFlags(flags) {} + +void FlagHandler::postProcess(ParsedNumber& result) const { + result.flags |= fFlags; +} + +UnicodeString FlagHandler::toString() const { + return u""; +} + + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/numparse_validators.h b/icu4c/source/i18n/numparse_validators.h index d3bc63aceb..c27890ae11 100644 --- a/icu4c/source/i18n/numparse_validators.h +++ b/icu4c/source/i18n/numparse_validators.h @@ -97,6 +97,24 @@ class MultiplierParseHandler : public ValidationMatcher, public UMemory { }; +/** + * Unconditionally applies a given set of flags to the ParsedNumber in the post-processing step. + */ +class FlagHandler : public ValidationMatcher, public UMemory { + public: + FlagHandler() = default; + + FlagHandler(result_flags_t flags); + + void postProcess(ParsedNumber& result) const U_OVERRIDE; + + UnicodeString toString() const U_OVERRIDE; + + private: + result_flags_t fFlags; +}; + + } // namespace impl } // namespace numparse U_NAMESPACE_END diff --git a/icu4c/source/test/intltest/numfmtst.cpp b/icu4c/source/test/intltest/numfmtst.cpp index 3d26e1a6b6..0d07750a4e 100644 --- a/icu4c/source/test/intltest/numfmtst.cpp +++ b/icu4c/source/test/intltest/numfmtst.cpp @@ -655,6 +655,7 @@ void NumberFormatTest::runIndexedTest( int32_t index, UBool exec, const char* &n TESTCASE_AUTO(Test11735_ExceptionIssue); TESTCASE_AUTO(Test11035_FormatCurrencyAmount); TESTCASE_AUTO(Test11318_DoubleConversion); + TESTCASE_AUTO(TestParsePercentRegression); TESTCASE_AUTO_END; } @@ -9029,4 +9030,35 @@ void NumberFormatTest::Test11318_DoubleConversion() { assertEquals("Should render all digits", u"999,999,999,999,999.9", appendTo); } +void NumberFormatTest::TestParsePercentRegression() { + IcuTestErrorCode status(*this, "TestParsePercentRegression"); + LocalPointer df1((DecimalFormat*) NumberFormat::createInstance("en", status)); + LocalPointer df2((DecimalFormat*) NumberFormat::createPercentInstance("en", status)); + df1->setLenient(TRUE); + df2->setLenient(TRUE); + + { + ParsePosition ppos; + Formattable result; + df1->parse("50%", result, ppos); + assertEquals("df1 should accept a number but not the percent sign", 2, ppos.getIndex()); + assertEquals("df1 should return the number as 50", 50.0, result.getDouble(status)); + } + { + ParsePosition ppos; + Formattable result; + df2->parse("50%", result, ppos); + assertEquals("df2 should accept the percent sign", 3, ppos.getIndex()); + assertEquals("df2 should return the number as 0.5", 0.5, result.getDouble(status)); + } + { + ParsePosition ppos; + Formattable result; + df2->parse("50", result, ppos); + assertEquals("df2 should return the number as 0.5 even though the percent sign is missing", + 0.5, + result.getDouble(status)); + } +} + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/test/intltest/numfmtst.h b/icu4c/source/test/intltest/numfmtst.h index d71b8c4880..e17b825d03 100644 --- a/icu4c/source/test/intltest/numfmtst.h +++ b/icu4c/source/test/intltest/numfmtst.h @@ -222,6 +222,7 @@ class NumberFormatTest: public CalendarTimeZoneTest { void Test11735_ExceptionIssue(); void Test11035_FormatCurrencyAmount(); void Test11318_DoubleConversion(); + void TestParsePercentRegression(); private: UBool testFormattableAsUFormattable(const char *file, int line, Formattable &f); diff --git a/icu4c/source/test/testdata/numberformattestspecification.txt b/icu4c/source/test/testdata/numberformattestspecification.txt index 629073890c..7b375222ca 100644 --- a/icu4c/source/test/testdata/numberformattestspecification.txt +++ b/icu4c/source/test/testdata/numberformattestspecification.txt @@ -1543,8 +1543,8 @@ begin parse output breaks 55% 0.55 // J and K get null -// P requires the symbol to be present and gets 55 -55 0.55 CJKP +// C and P scale by 100 even if the percent sign is not present +55 0.55 JK test trailing grouping separators in pattern // This test is for #13115 diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/FlagHandler.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/FlagHandler.java new file mode 100644 index 0000000000..37d39113ad --- /dev/null +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/FlagHandler.java @@ -0,0 +1,28 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html#License +package com.ibm.icu.impl.number.parse; + +/** + * Unconditionally applies a given set of flags to the ParsedNumber in the post-processing step. + */ +public class FlagHandler extends ValidationMatcher { + + public static final FlagHandler PERCENT = new FlagHandler(ParsedNumber.FLAG_PERCENT); + public static final FlagHandler PERMILLE = new FlagHandler(ParsedNumber.FLAG_PERMILLE); + + private final int flags; + + private FlagHandler(int flags) { + this.flags = flags; + } + + @Override + public void postProcess(ParsedNumber result) { + result.flags |= flags; + } + + @Override + public String toString() { + return ""; + } +} diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java index bff15dce37..3d41c37ce7 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java @@ -9,6 +9,7 @@ import java.util.List; import com.ibm.icu.impl.StringSegment; import com.ibm.icu.impl.number.AffixPatternProvider; +import com.ibm.icu.impl.number.AffixUtils; import com.ibm.icu.impl.number.CurrencyPluralInfoAffixProvider; import com.ibm.icu.impl.number.CustomSymbolCurrency; import com.ibm.icu.impl.number.DecimalFormatProperties; @@ -195,6 +196,23 @@ public class NumberParserImpl { parser.addMatcher(CombinedCurrencyMatcher.getInstance(currency, symbols)); } + /////////////// + /// PERCENT /// + /////////////// + + // ICU-TC meeting, April 11, 2018: accept percent/permille only if it is in the pattern, + // and to maintain regressive behavior, divide by 100 even if no percent sign is present. + if (affixProvider.containsSymbolType(AffixUtils.TYPE_PERCENT)) { + parser.addMatcher(PercentMatcher.getInstance(symbols)); + // causes number to be always scaled by 100: + parser.addMatcher(FlagHandler.PERCENT); + } + if (affixProvider.containsSymbolType(AffixUtils.TYPE_PERMILLE)) { + parser.addMatcher(PermilleMatcher.getInstance(symbols)); + // causes number to be always scaled by 1000: + parser.addMatcher(FlagHandler.PERMILLE); + } + /////////////////////////////// /// OTHER STANDARD MATCHERS /// /////////////////////////////// @@ -202,8 +220,6 @@ public class NumberParserImpl { if (!isStrict) { parser.addMatcher(PlusSignMatcher.getInstance(symbols, false)); parser.addMatcher(MinusSignMatcher.getInstance(symbols, false)); - parser.addMatcher(PercentMatcher.getInstance(symbols)); - parser.addMatcher(PermilleMatcher.getInstance(symbols)); } parser.addMatcher(NanMatcher.getInstance(symbols, parseFlags)); parser.addMatcher(InfinityMatcher.getInstance(symbols)); diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt b/icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt index 35731ff8b7..d1e71388d3 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt @@ -1545,8 +1545,8 @@ begin parse output breaks 55% 0.55 // J and K get null -// P requires the symbol to be present and gets 55 -55 0.55 JKP +// C and P scale by 100 even if the percent sign is not present +55 0.55 JK test trailing grouping separators in pattern // This test is for #13115 diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java index dbbdc13210..b442d3b302 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java @@ -5637,7 +5637,7 @@ public class NumberFormatTest extends TestFmwk { assertEquals("Should consume the trailing bidi since it is in the symbol", 5, ppos.getIndex()); ppos.setIndex(0); result = df.parse("-42a\u200E ", ppos); - assertEquals("Should not parse as percent", new Long(-42), result); + assertEquals("Should parse as percent", -0.42, result.doubleValue()); assertEquals("Should not consume the trailing bidi or whitespace", 4, ppos.getIndex()); // A few more cases based on the docstring: @@ -6075,4 +6075,32 @@ public class NumberFormatTest extends TestFmwk { DecimalFormat df = new DecimalFormat("-0", DecimalFormatSymbols.getInstance(ULocale.ENGLISH)); expect2(df, -5, "--5"); } + + @Test + public void testParsePercentRegression() { + DecimalFormat df1 = (DecimalFormat) NumberFormat.getInstance(ULocale.ENGLISH); + DecimalFormat df2 = (DecimalFormat) NumberFormat.getPercentInstance(ULocale.ENGLISH); + df1.setParseStrict(false); + df2.setParseStrict(false); + + { + ParsePosition ppos = new ParsePosition(0); + Number result = df1.parse("50%", ppos); + assertEquals("df1 should accept a number but not the percent sign", 2, ppos.getIndex()); + assertEquals("df1 should return the number as 50", 50.0, result.doubleValue()); + } + { + ParsePosition ppos = new ParsePosition(0); + Number result = df2.parse("50%", ppos); + assertEquals("df2 should accept the percent sign", 3, ppos.getIndex()); + assertEquals("df2 should return the number as 0.5", 0.5, result.doubleValue()); + } + { + ParsePosition ppos = new ParsePosition(0); + Number result = df2.parse("50", ppos); + assertEquals("df2 should return the number as 0.5 even though the percent sign is missing", + 0.5, + result.doubleValue()); + } + } } From 354afa4e79ff81499e4e108cad7cc24d5b1db2d5 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Thu, 12 Apr 2018 10:59:37 +0000 Subject: [PATCH 093/129] ICU-13697 Adding data-loading logic for parseLenients sets in CLDR. Ties the sets in with number and currency parsing in ICU4C and ICU4J. X-SVN-Rev: 41223 --- icu4c/source/common/Makefile.in | 3 +- icu4c/source/common/numparse_unisets.cpp | 200 ++++++++++++++ .../{i18n => common}/numparse_unisets.h | 37 ++- icu4c/source/common/ucln_cmn.h | 1 + icu4c/source/common/ucurr.cpp | 35 ++- icu4c/source/i18n/Makefile.in | 4 +- icu4c/source/i18n/decfmtst.cpp | 251 ------------------ icu4c/source/i18n/decfmtst.h | 69 ----- icu4c/source/i18n/numparse_unisets.cpp | 127 --------- .../source/i18n/scientificnumberformatter.cpp | 17 +- icu4c/source/i18n/ucln_in.h | 1 - .../i18n/unicode/scientificnumberformatter.h | 5 - icu4c/source/test/intltest/numfmtst.cpp | 2 +- .../number/parse/UnicodeSetStaticCache.java | 140 ++++++++-- .../core/src/com/ibm/icu/util/Currency.java | 50 +--- .../icu/dev/test/format/NumberFormatTest.java | 2 +- 16 files changed, 390 insertions(+), 554 deletions(-) create mode 100644 icu4c/source/common/numparse_unisets.cpp rename icu4c/source/{i18n => common}/numparse_unisets.h (71%) delete mode 100644 icu4c/source/i18n/decfmtst.cpp delete mode 100644 icu4c/source/i18n/decfmtst.h delete mode 100644 icu4c/source/i18n/numparse_unisets.cpp diff --git a/icu4c/source/common/Makefile.in b/icu4c/source/common/Makefile.in index cf0799aed1..2025b85ee1 100644 --- a/icu4c/source/common/Makefile.in +++ b/icu4c/source/common/Makefile.in @@ -111,7 +111,8 @@ util.o util_props.o parsepos.o locbased.o cwchar.o wintz.o dtintrv.o ucnvsel.o p ulist.o uloc_tag.o icudataver.o icuplug.o listformatter.o ulistformatter.o \ sharedobject.o simpleformatter.o unifiedcache.o uloc_keytype.o \ ubiditransform.o \ -pluralmap.o +pluralmap.o \ +numparse_unisets.o ## Header files to install HEADERS = $(srcdir)/unicode/*.h diff --git a/icu4c/source/common/numparse_unisets.cpp b/icu4c/source/common/numparse_unisets.cpp new file mode 100644 index 0000000000..3aa5b5b1e5 --- /dev/null +++ b/icu4c/source/common/numparse_unisets.cpp @@ -0,0 +1,200 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT + +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + +#include "numparse_unisets.h" +#include "umutex.h" +#include "ucln_cmn.h" +#include "unicode/uniset.h" +#include "uresimp.h" +#include "cstring.h" +#include "uassert.h" + +using namespace icu; +using namespace icu::numparse; +using namespace icu::numparse::impl; +using namespace icu::numparse::impl::unisets; + + +namespace { + +static UnicodeSet* gUnicodeSets[COUNT] = {}; + +UnicodeSet* computeUnion(Key k1, Key k2) { + UnicodeSet* result = new UnicodeSet(); + if (result == nullptr) { + return nullptr; + } + result->addAll(*gUnicodeSets[k1]); + result->addAll(*gUnicodeSets[k2]); + result->freeze(); + return result; +} + +UnicodeSet* computeUnion(Key k1, Key k2, Key k3) { + UnicodeSet* result = new UnicodeSet(); + if (result == nullptr) { + return nullptr; + } + result->addAll(*gUnicodeSets[k1]); + result->addAll(*gUnicodeSets[k2]); + result->addAll(*gUnicodeSets[k3]); + result->freeze(); + return result; +} + + +void saveSet(Key key, const UnicodeString& unicodeSetPattern, UErrorCode& status) { + // assert unicodeSets.get(key) == null; + gUnicodeSets[key] = new UnicodeSet(unicodeSetPattern, status); +} + +class ParseDataSink : public ResourceSink { + public: + void put(const char* key, ResourceValue& value, UBool /*noFallback*/, UErrorCode& status) U_OVERRIDE { + ResourceTable contextsTable = value.getTable(status); + if (U_FAILURE(status)) { return; } + for (int i = 0; contextsTable.getKeyAndValue(i, key, value); i++) { + if (uprv_strcmp(key, "date") == 0) { + // ignore + } else { + ResourceTable strictnessTable = value.getTable(status); + if (U_FAILURE(status)) { return; } + for (int j = 0; strictnessTable.getKeyAndValue(j, key, value); j++) { + bool isLenient = (uprv_strcmp(key, "lenient") == 0); + ResourceArray array = value.getArray(status); + if (U_FAILURE(status)) { return; } + for (int k = 0; k < array.getSize(); k++) { + array.getValue(k, value); + UnicodeString str = value.getUnicodeString(status); + if (U_FAILURE(status)) { return; } + // There is both lenient and strict data for comma/period, + // but not for any of the other symbols. + if (str.indexOf(u'.') != -1) { + saveSet(isLenient ? PERIOD : STRICT_PERIOD, str, status); + } else if (str.indexOf(u',') != -1) { + saveSet(isLenient ? COMMA : STRICT_COMMA, str, status); + } else if (str.indexOf(u'+') != -1) { + saveSet(PLUS_SIGN, str, status); + } else if (str.indexOf(u'‒') != -1) { + saveSet(MINUS_SIGN, str, status); + } else if (str.indexOf(u'$') != -1) { + saveSet(DOLLAR_SIGN, str, status); + } else if (str.indexOf(u'£') != -1) { + saveSet(POUND_SIGN, str, status); + } else if (str.indexOf(u'₨') != -1) { + saveSet(RUPEE_SIGN, str, status); + } + if (U_FAILURE(status)) { return; } + } + } + } + } + } +}; + + +icu::UInitOnce gNumberParseUniSetsInitOnce = U_INITONCE_INITIALIZER; + +UBool U_CALLCONV cleanupNumberParseUniSets() { + for (int32_t i = 0; i < COUNT; i++) { + delete gUnicodeSets[i]; + gUnicodeSets[i] = nullptr; + } + return TRUE; +} + +void U_CALLCONV initNumberParseUniSets(UErrorCode& status) { + ucln_common_registerCleanup(UCLN_COMMON_NUMPARSE_UNISETS, cleanupNumberParseUniSets); + + gUnicodeSets[EMPTY] = new UnicodeSet(); + + // These sets were decided after discussion with icu-design@. See tickets #13084 and #13309. + // Zs+TAB is "horizontal whitespace" according to UTS #18 (blank property). + gUnicodeSets[DEFAULT_IGNORABLES] = new UnicodeSet( + u"[[:Zs:][\\u0009][:Bidi_Control:][:Variation_Selector:]]", status); + gUnicodeSets[STRICT_IGNORABLES] = new UnicodeSet(u"[[:Bidi_Control:]]", status); + + LocalUResourceBundlePointer rb(ures_open(nullptr, "root", &status)); + if (U_FAILURE(status)) { return; } + ParseDataSink sink; + ures_getAllItemsWithFallback(rb.getAlias(), "parse", sink, status); + if (U_FAILURE(status)) { return; } + + // TODO: Should there be fallback behavior if for some reason these sets didn't get populated? + U_ASSERT(gUnicodeSets[COMMA] != nullptr); + U_ASSERT(gUnicodeSets[STRICT_COMMA] != nullptr); + U_ASSERT(gUnicodeSets[PERIOD] != nullptr); + U_ASSERT(gUnicodeSets[STRICT_PERIOD] != nullptr); + + gUnicodeSets[OTHER_GROUPING_SEPARATORS] = new UnicodeSet( + u"['٬‘’'\\u0020\\u00A0\\u2000-\\u200A\\u202F\\u205F\\u3000]", status); + gUnicodeSets[ALL_SEPARATORS] = computeUnion(COMMA, PERIOD, OTHER_GROUPING_SEPARATORS); + gUnicodeSets[STRICT_ALL_SEPARATORS] = computeUnion( + STRICT_COMMA, STRICT_PERIOD, OTHER_GROUPING_SEPARATORS); + + U_ASSERT(gUnicodeSets[MINUS_SIGN] != nullptr); + U_ASSERT(gUnicodeSets[PLUS_SIGN] != nullptr); + + gUnicodeSets[PERCENT_SIGN] = new UnicodeSet(u"[%٪]", status); + gUnicodeSets[PERMILLE_SIGN] = new UnicodeSet(u"[‰؉]", status); + gUnicodeSets[INFINITY_KEY] = new UnicodeSet(u"[∞]", status); + + U_ASSERT(gUnicodeSets[DOLLAR_SIGN] != nullptr); + U_ASSERT(gUnicodeSets[POUND_SIGN] != nullptr); + U_ASSERT(gUnicodeSets[RUPEE_SIGN] != nullptr); + gUnicodeSets[YEN_SIGN] = new UnicodeSet(u"[¥\\uffe5]", status); + + gUnicodeSets[DIGITS] = new UnicodeSet(u"[:digit:]", status); + + gUnicodeSets[DIGITS_OR_ALL_SEPARATORS] = computeUnion(DIGITS, ALL_SEPARATORS); + gUnicodeSets[DIGITS_OR_STRICT_ALL_SEPARATORS] = computeUnion(DIGITS, STRICT_ALL_SEPARATORS); + + for (int32_t i = 0; i < COUNT; i++) { + gUnicodeSets[i]->freeze(); + } +} + +} + +const UnicodeSet* unisets::get(Key key) { + UErrorCode localStatus = U_ZERO_ERROR; + umtx_initOnce(gNumberParseUniSetsInitOnce, &initNumberParseUniSets, localStatus); + if (U_FAILURE(localStatus)) { + // TODO: This returns non-null in Java, and callers assume that. + return nullptr; + } + return gUnicodeSets[key]; +} + +Key unisets::chooseFrom(UnicodeString str, Key key1) { + return get(key1)->contains(str) ? key1 : COUNT; +} + +Key unisets::chooseFrom(UnicodeString str, Key key1, Key key2) { + return get(key1)->contains(str) ? key1 : chooseFrom(str, key2); +} + +//Key unisets::chooseCurrency(UnicodeString str) { +// if (get(DOLLAR_SIGN)->contains(str)) { +// return DOLLAR_SIGN; +// } else if (get(POUND_SIGN)->contains(str)) { +// return POUND_SIGN; +// } else if (get(RUPEE_SIGN)->contains(str)) { +// return RUPEE_SIGN; +// } else if (get(YEN_SIGN)->contains(str)) { +// return YEN_SIGN; +// } else { +// return COUNT; +// } +//} + + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/numparse_unisets.h b/icu4c/source/common/numparse_unisets.h similarity index 71% rename from icu4c/source/i18n/numparse_unisets.h rename to icu4c/source/common/numparse_unisets.h index 97a44ea860..7cf3f6aeb1 100644 --- a/icu4c/source/i18n/numparse_unisets.h +++ b/icu4c/source/common/numparse_unisets.h @@ -1,14 +1,16 @@ // © 2018 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html +// This file is in common instead of i18n because it is needed by ucurr.cpp. + #include "unicode/utypes.h" #if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT #ifndef __NUMPARSE_UNISETS_H__ #define __NUMPARSE_UNISETS_H__ -#include "numparse_types.h" #include "unicode/uniset.h" +#include "unicode/unistr.h" U_NAMESPACE_BEGIN namespace numparse { namespace impl { @@ -18,8 +20,6 @@ enum Key { EMPTY, // Ignorables - BIDI, - WHITESPACE, DEFAULT_IGNORABLES, STRICT_IGNORABLES, @@ -29,7 +29,7 @@ enum Key { // - PERIOD is a superset of SCRICT_PERIOD // - ALL_SEPARATORS is the union of COMMA, PERIOD, and OTHER_GROUPING_SEPARATORS // - STRICT_ALL_SEPARATORS is the union of STRICT_COMMA, STRICT_PERIOD, and OTHER_GRP_SEPARATORS - COMMA, + COMMA, PERIOD, STRICT_COMMA, STRICT_PERIOD, @@ -38,23 +38,27 @@ enum Key { STRICT_ALL_SEPARATORS, // Symbols - // TODO: NaN? - MINUS_SIGN, + MINUS_SIGN, PLUS_SIGN, PERCENT_SIGN, PERMILLE_SIGN, INFINITY_KEY, // INFINITY is defined in cmath + // Currency Symbols + DOLLAR_SIGN, + POUND_SIGN, + RUPEE_SIGN, + YEN_SIGN, // not in CLDR data, but Currency.java wants it + // Other - DIGITS, - CWCF, + DIGITS, // Combined Separators with Digits (for lead code points) - DIGITS_OR_ALL_SEPARATORS, + DIGITS_OR_ALL_SEPARATORS, DIGITS_OR_STRICT_ALL_SEPARATORS, // The number of elements in the enum. Also used to indicate null. - COUNT + COUNT }; const UnicodeSet* get(Key key); @@ -63,6 +67,19 @@ Key chooseFrom(UnicodeString str, Key key1); Key chooseFrom(UnicodeString str, Key key1, Key key2); +// Unused in C++: +// Key chooseCurrency(UnicodeString str); +// Used instead: +static const struct { + Key key; + UChar32 exemplar; +} kCurrencyEntries[] = { + {DOLLAR_SIGN, u'$'}, + {POUND_SIGN, u'£'}, + {RUPEE_SIGN, u'₨'}, + {YEN_SIGN, u'¥'}, +}; + } // namespace unisets } // namespace impl } // namespace numparse diff --git a/icu4c/source/common/ucln_cmn.h b/icu4c/source/common/ucln_cmn.h index 5db9494517..9b6c205813 100644 --- a/icu4c/source/common/ucln_cmn.h +++ b/icu4c/source/common/ucln_cmn.h @@ -33,6 +33,7 @@ Please keep the order of enums declared in same order as the cleanup functions are suppose to be called. */ typedef enum ECleanupCommonType { UCLN_COMMON_START = -1, + UCLN_COMMON_NUMPARSE_UNISETS, UCLN_COMMON_USPREP, UCLN_COMMON_BREAKITERATOR, UCLN_COMMON_RBBI, diff --git a/icu4c/source/common/ucurr.cpp b/icu4c/source/common/ucurr.cpp index 6ce53c2d5e..1fd02ec30b 100644 --- a/icu4c/source/common/ucurr.cpp +++ b/icu4c/source/common/ucurr.cpp @@ -17,11 +17,13 @@ #include "unicode/ustring.h" #include "unicode/parsepos.h" #include "unicode/uniset.h" +#include "unicode/usetiter.h" #include "unicode/utf16.h" #include "ustr_imp.h" #include "charstr.h" #include "cmemory.h" #include "cstring.h" +#include "numparse_unisets.h" #include "uassert.h" #include "umutex.h" #include "ucln_cmn.h" @@ -67,14 +69,6 @@ static const int32_t POW10[] = { 1, 10, 100, 1000, 10000, 100000, static const int32_t MAX_POW10 = UPRV_LENGTHOF(POW10) - 1; -// Defines equivalent currency symbols. -static const char *EQUIV_CURRENCY_SYMBOLS[][2] = { - {"\\u00a5", "\\uffe5"}, - {"$", "\\ufe69"}, - {"$", "\\uff04"}, - {"\\u20a8", "\\u20b9"}, - {"\\u00a3", "\\u20a4"}}; - #define ISO_CURRENCY_CODE_LENGTH 3 //------------------------------------------------------------ @@ -2207,16 +2201,21 @@ static void U_CALLCONV initIsoCodes(UErrorCode &status) { } static void populateCurrSymbolsEquiv(icu::Hashtable *hash, UErrorCode &status) { - if (U_FAILURE(status)) { - return; - } - int32_t length = UPRV_LENGTHOF(EQUIV_CURRENCY_SYMBOLS); - for (int32_t i = 0; i < length; ++i) { - icu::UnicodeString lhs(EQUIV_CURRENCY_SYMBOLS[i][0], -1, US_INV); - icu::UnicodeString rhs(EQUIV_CURRENCY_SYMBOLS[i][1], -1, US_INV); - makeEquivalent(lhs.unescape(), rhs.unescape(), hash, status); - if (U_FAILURE(status)) { - return; + using namespace icu::numparse::impl; + if (U_FAILURE(status)) { return; } + for (auto& entry : unisets::kCurrencyEntries) { + UnicodeString exemplar(entry.exemplar); + const UnicodeSet* set = unisets::get(entry.key); + if (set == nullptr) { return; } + UnicodeSetIterator it(*set); + while (it.next()) { + UnicodeString value = it.getString(); + if (value == exemplar) { + // No need to mark the exemplar character as an equivalent + continue; + } + makeEquivalent(exemplar, value, hash, status); + if (U_FAILURE(status)) { return; } } } } diff --git a/icu4c/source/i18n/Makefile.in b/icu4c/source/i18n/Makefile.in index b4fafdf72f..a66b65a874 100644 --- a/icu4c/source/i18n/Makefile.in +++ b/icu4c/source/i18n/Makefile.in @@ -92,7 +92,7 @@ csdetect.o csmatch.o csr2022.o csrecog.o csrmbcs.o csrsbcs.o csrucode.o csrutf8. wintzimpl.o windtfmt.o winnmfmt.o basictz.o dtrule.o rbtz.o tzrule.o tztrans.o vtzone.o zonemeta.o \ standardplural.o upluralrules.o plurrule.o plurfmt.o selfmt.o dtitvfmt.o dtitvinf.o udateintervalformat.o \ tmunit.o tmutamt.o tmutfmt.o currpinf.o \ -uspoof.o uspoof_impl.o uspoof_build.o uspoof_conf.o decfmtst.o smpdtfst.o \ +uspoof.o uspoof_impl.o uspoof_build.o uspoof_conf.o smpdtfst.o \ ztrans.o zrule.o vzone.o fphdlimp.o fpositer.o ufieldpositer.o \ decNumber.o decContext.o alphaindex.o tznames.o tznames_impl.o tzgnames.o \ tzfmt.o compactdecimalformat.o gender.o region.o scriptset.o \ @@ -107,7 +107,7 @@ number_mapper.o number_multiplier.o number_currencysymbols.o number_skeletons.o double-conversion.o double-conversion-bignum-dtoa.o double-conversion-bignum.o \ double-conversion-cached-powers.o double-conversion-diy-fp.o \ double-conversion-fast-dtoa.o double-conversion-strtod.o \ -numparse_stringsegment.o numparse_unisets.o numparse_parsednumber.o numparse_impl.o \ +numparse_stringsegment.o numparse_parsednumber.o numparse_impl.o \ numparse_symbols.o numparse_decimal.o numparse_scientific.o numparse_currency.o \ numparse_affixes.o numparse_compositions.o numparse_validators.o \ diff --git a/icu4c/source/i18n/decfmtst.cpp b/icu4c/source/i18n/decfmtst.cpp deleted file mode 100644 index e939ab474a..0000000000 --- a/icu4c/source/i18n/decfmtst.cpp +++ /dev/null @@ -1,251 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* Copyright (C) 2009-2016, International Business Machines Corporation and -* others. All Rights Reserved. -******************************************************************************* -* -* This file contains the class DecimalFormatStaticSets -* -* DecimalFormatStaticSets holds the UnicodeSets that are needed for lenient -* parsing of decimal and group separators. -******************************************************************************** -*/ - -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING - -#include "unicode/unistr.h" -#include "unicode/uniset.h" -#include "unicode/uchar.h" -#include "cmemory.h" -#include "cstring.h" -#include "uassert.h" -#include "ucln_in.h" -#include "umutex.h" - -#include "decfmtst.h" - -U_NAMESPACE_BEGIN - - -//------------------------------------------------------------------------------ -// -// Unicode Set pattern strings for all of the required constant sets. -// Initialized with hex values for portability to EBCDIC based machines. -// Really ugly, but there's no good way to avoid it. -// -//------------------------------------------------------------------------------ - -static const UChar gDotEquivalentsPattern[] = { - // [ . \u2024 \u3002 \uFE12 \uFE52 \uFF0E \uFF61 ] - 0x005B, 0x002E, 0x2024, 0x3002, 0xFE12, 0xFE52, 0xFF0E, 0xFF61, 0x005D, 0x0000}; - -static const UChar gCommaEquivalentsPattern[] = { - // [ , \u060C \u066B \u3001 \uFE10 \uFE11 \uFE50 \uFE51 \uFF0C \uFF64 ] - 0x005B, 0x002C, 0x060C, 0x066B, 0x3001, 0xFE10, 0xFE11, 0xFE50, 0xFE51, 0xFF0C, 0xFF64, 0x005D, 0x0000}; - -static const UChar gOtherGroupingSeparatorsPattern[] = { - // [ \ SPACE ' NBSP \u066C \u2000 - \u200A \u2018 \u2019 \u202F \u205F \u3000 \uFF07 ] - 0x005B, 0x005C, 0x0020, 0x0027, 0x00A0, 0x066C, 0x2000, 0x002D, 0x200A, 0x2018, 0x2019, 0x202F, 0x205F, 0x3000, 0xFF07, 0x005D, 0x0000}; - -static const UChar gDashEquivalentsPattern[] = { - // [ \ - HYPHEN F_DASH N_DASH MINUS ] - 0x005B, 0x005C, 0x002D, 0x2010, 0x2012, 0x2013, 0x2212, 0x005D, 0x0000}; - -static const UChar gStrictDotEquivalentsPattern[] = { - // [ . \u2024 \uFE52 \uFF0E \uFF61 ] - 0x005B, 0x002E, 0x2024, 0xFE52, 0xFF0E, 0xFF61, 0x005D, 0x0000}; - -static const UChar gStrictCommaEquivalentsPattern[] = { - // [ , \u066B \uFE10 \uFE50 \uFF0C ] - 0x005B, 0x002C, 0x066B, 0xFE10, 0xFE50, 0xFF0C, 0x005D, 0x0000}; - -static const UChar gStrictOtherGroupingSeparatorsPattern[] = { - // [ \ SPACE ' NBSP \u066C \u2000 - \u200A \u2018 \u2019 \u202F \u205F \u3000 \uFF07 ] - 0x005B, 0x005C, 0x0020, 0x0027, 0x00A0, 0x066C, 0x2000, 0x002D, 0x200A, 0x2018, 0x2019, 0x202F, 0x205F, 0x3000, 0xFF07, 0x005D, 0x0000}; - -static const UChar gStrictDashEquivalentsPattern[] = { - // [ \ - MINUS ] - 0x005B, 0x005C, 0x002D, 0x2212, 0x005D, 0x0000}; - -static const UChar32 gMinusSigns[] = { - 0x002D, - 0x207B, - 0x208B, - 0x2212, - 0x2796, - 0xFE63, - 0xFF0D}; - -static const UChar32 gPlusSigns[] = { - 0x002B, - 0x207A, - 0x208A, - 0x2795, - 0xfB29, - 0xFE62, - 0xFF0B}; - -static void initUnicodeSet(const UChar32 *raw, int32_t len, UnicodeSet *s) { - for (int32_t i = 0; i < len; ++i) { - s->add(raw[i]); - } -} - -DecimalFormatStaticSets::DecimalFormatStaticSets(UErrorCode &status) -: fDotEquivalents(NULL), - fCommaEquivalents(NULL), - fOtherGroupingSeparators(NULL), - fDashEquivalents(NULL), - fStrictDotEquivalents(NULL), - fStrictCommaEquivalents(NULL), - fStrictOtherGroupingSeparators(NULL), - fStrictDashEquivalents(NULL), - fDefaultGroupingSeparators(NULL), - fStrictDefaultGroupingSeparators(NULL), - fMinusSigns(NULL), - fPlusSigns(NULL) -{ - fDotEquivalents = new UnicodeSet(UnicodeString(TRUE, gDotEquivalentsPattern, -1), status); - fCommaEquivalents = new UnicodeSet(UnicodeString(TRUE, gCommaEquivalentsPattern, -1), status); - fOtherGroupingSeparators = new UnicodeSet(UnicodeString(TRUE, gOtherGroupingSeparatorsPattern, -1), status); - fDashEquivalents = new UnicodeSet(UnicodeString(TRUE, gDashEquivalentsPattern, -1), status); - - fStrictDotEquivalents = new UnicodeSet(UnicodeString(TRUE, gStrictDotEquivalentsPattern, -1), status); - fStrictCommaEquivalents = new UnicodeSet(UnicodeString(TRUE, gStrictCommaEquivalentsPattern, -1), status); - fStrictOtherGroupingSeparators = new UnicodeSet(UnicodeString(TRUE, gStrictOtherGroupingSeparatorsPattern, -1), status); - fStrictDashEquivalents = new UnicodeSet(UnicodeString(TRUE, gStrictDashEquivalentsPattern, -1), status); - - - fDefaultGroupingSeparators = new UnicodeSet(*fDotEquivalents); - fDefaultGroupingSeparators->addAll(*fCommaEquivalents); - fDefaultGroupingSeparators->addAll(*fOtherGroupingSeparators); - - fStrictDefaultGroupingSeparators = new UnicodeSet(*fStrictDotEquivalents); - fStrictDefaultGroupingSeparators->addAll(*fStrictCommaEquivalents); - fStrictDefaultGroupingSeparators->addAll(*fStrictOtherGroupingSeparators); - - fMinusSigns = new UnicodeSet(); - fPlusSigns = new UnicodeSet(); - - // Check for null pointers - if (fDotEquivalents == NULL || fCommaEquivalents == NULL || fOtherGroupingSeparators == NULL || fDashEquivalents == NULL || - fStrictDotEquivalents == NULL || fStrictCommaEquivalents == NULL || fStrictOtherGroupingSeparators == NULL || fStrictDashEquivalents == NULL || - fDefaultGroupingSeparators == NULL || fStrictOtherGroupingSeparators == NULL || - fMinusSigns == NULL || fPlusSigns == NULL) { - cleanup(); - status = U_MEMORY_ALLOCATION_ERROR; - return; - } - - initUnicodeSet( - gMinusSigns, - UPRV_LENGTHOF(gMinusSigns), - fMinusSigns); - initUnicodeSet( - gPlusSigns, - UPRV_LENGTHOF(gPlusSigns), - fPlusSigns); - - // Freeze all the sets - fDotEquivalents->freeze(); - fCommaEquivalents->freeze(); - fOtherGroupingSeparators->freeze(); - fDashEquivalents->freeze(); - fStrictDotEquivalents->freeze(); - fStrictCommaEquivalents->freeze(); - fStrictOtherGroupingSeparators->freeze(); - fStrictDashEquivalents->freeze(); - fDefaultGroupingSeparators->freeze(); - fStrictDefaultGroupingSeparators->freeze(); - fMinusSigns->freeze(); - fPlusSigns->freeze(); -} - -DecimalFormatStaticSets::~DecimalFormatStaticSets() { - cleanup(); -} - -void DecimalFormatStaticSets::cleanup() { // Be sure to clean up newly added fields! - delete fDotEquivalents; fDotEquivalents = NULL; - delete fCommaEquivalents; fCommaEquivalents = NULL; - delete fOtherGroupingSeparators; fOtherGroupingSeparators = NULL; - delete fDashEquivalents; fDashEquivalents = NULL; - delete fStrictDotEquivalents; fStrictDotEquivalents = NULL; - delete fStrictCommaEquivalents; fStrictCommaEquivalents = NULL; - delete fStrictOtherGroupingSeparators; fStrictOtherGroupingSeparators = NULL; - delete fStrictDashEquivalents; fStrictDashEquivalents = NULL; - delete fDefaultGroupingSeparators; fDefaultGroupingSeparators = NULL; - delete fStrictDefaultGroupingSeparators; fStrictDefaultGroupingSeparators = NULL; - delete fStrictOtherGroupingSeparators; fStrictOtherGroupingSeparators = NULL; - delete fMinusSigns; fMinusSigns = NULL; - delete fPlusSigns; fPlusSigns = NULL; -} - -static DecimalFormatStaticSets *gStaticSets; -static icu::UInitOnce gStaticSetsInitOnce = U_INITONCE_INITIALIZER; - - -//------------------------------------------------------------------------------ -// -// decfmt_cleanup Memory cleanup function, free/delete all -// cached memory. Called by ICU's u_cleanup() function. -// -//------------------------------------------------------------------------------ -U_CDECL_BEGIN -static UBool U_CALLCONV -decimfmt_cleanup(void) -{ - delete gStaticSets; - gStaticSets = NULL; - gStaticSetsInitOnce.reset(); - return TRUE; -} - -static void U_CALLCONV initSets(UErrorCode &status) { - U_ASSERT(gStaticSets == NULL); - ucln_i18n_registerCleanup(UCLN_I18N_DECFMT, decimfmt_cleanup); - gStaticSets = new DecimalFormatStaticSets(status); - if (U_FAILURE(status)) { - delete gStaticSets; - gStaticSets = NULL; - return; - } - if (gStaticSets == NULL) { - status = U_MEMORY_ALLOCATION_ERROR; - } -} -U_CDECL_END - -const DecimalFormatStaticSets *DecimalFormatStaticSets::getStaticSets(UErrorCode &status) { - umtx_initOnce(gStaticSetsInitOnce, initSets, status); - return gStaticSets; -} - - -const UnicodeSet *DecimalFormatStaticSets::getSimilarDecimals(UChar32 decimal, UBool strictParse) -{ - UErrorCode status = U_ZERO_ERROR; - umtx_initOnce(gStaticSetsInitOnce, initSets, status); - if (U_FAILURE(status)) { - return NULL; - } - - if (gStaticSets->fDotEquivalents->contains(decimal)) { - return strictParse ? gStaticSets->fStrictDotEquivalents : gStaticSets->fDotEquivalents; - } - - if (gStaticSets->fCommaEquivalents->contains(decimal)) { - return strictParse ? gStaticSets->fStrictCommaEquivalents : gStaticSets->fCommaEquivalents; - } - - // if there is no match, return NULL - return NULL; -} - - -U_NAMESPACE_END -#endif // !UCONFIG_NO_FORMATTING diff --git a/icu4c/source/i18n/decfmtst.h b/icu4c/source/i18n/decfmtst.h deleted file mode 100644 index 63ae50c6df..0000000000 --- a/icu4c/source/i18n/decfmtst.h +++ /dev/null @@ -1,69 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* Copyright (C) 2009-2016, International Business Machines Corporation and -* others. All Rights Reserved. -******************************************************************************* -* -* This file contains declarations for the class DecimalFormatStaticSets -* -* DecimalFormatStaticSets holds the UnicodeSets that are needed for lenient -* parsing of decimal and group separators. -******************************************************************************** -*/ - -#ifndef DECFMTST_H -#define DECFMTST_H - -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING - -#include "unicode/uobject.h" - -U_NAMESPACE_BEGIN - -class UnicodeSet; - - -class DecimalFormatStaticSets : public UMemory -{ -public: - // Constructor and Destructor not for general use. - // Public to permit access from plain C implementation functions. - DecimalFormatStaticSets(UErrorCode &status); - ~DecimalFormatStaticSets(); - - /** - * Return a pointer to a lazy-initialized singleton instance of this class. - */ - static const DecimalFormatStaticSets *getStaticSets(UErrorCode &status); - - static const UnicodeSet *getSimilarDecimals(UChar32 decimal, UBool strictParse); - - UnicodeSet *fDotEquivalents; - UnicodeSet *fCommaEquivalents; - UnicodeSet *fOtherGroupingSeparators; - UnicodeSet *fDashEquivalents; - - UnicodeSet *fStrictDotEquivalents; - UnicodeSet *fStrictCommaEquivalents; - UnicodeSet *fStrictOtherGroupingSeparators; - UnicodeSet *fStrictDashEquivalents; - - UnicodeSet *fDefaultGroupingSeparators; - UnicodeSet *fStrictDefaultGroupingSeparators; - - UnicodeSet *fMinusSigns; - UnicodeSet *fPlusSigns; -private: - void cleanup(); - -}; - - -U_NAMESPACE_END - -#endif // !UCONFIG_NO_FORMATTING -#endif // DECFMTST_H diff --git a/icu4c/source/i18n/numparse_unisets.cpp b/icu4c/source/i18n/numparse_unisets.cpp deleted file mode 100644 index eb2f6c1e9d..0000000000 --- a/icu4c/source/i18n/numparse_unisets.cpp +++ /dev/null @@ -1,127 +0,0 @@ -// © 2018 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html - -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT - -// Allow implicit conversion from char16_t* to UnicodeString for this file: -// Helpful in toString methods and elsewhere. -#define UNISTR_FROM_STRING_EXPLICIT - -#include "numparse_unisets.h" -#include "numparse_types.h" -#include "umutex.h" -#include "ucln_in.h" -#include "unicode/uniset.h" - -using namespace icu; -using namespace icu::numparse; -using namespace icu::numparse::impl; -using namespace icu::numparse::impl::unisets; - - -namespace { - -static UnicodeSet* gUnicodeSets[COUNT] = {}; - -UnicodeSet* computeUnion(Key k1, Key k2) { - UnicodeSet* result = new UnicodeSet(); - if (result == nullptr) { - return nullptr; - } - result->addAll(*gUnicodeSets[k1]); - result->addAll(*gUnicodeSets[k2]); - result->freeze(); - return result; -} - -UnicodeSet* computeUnion(Key k1, Key k2, Key k3) { - UnicodeSet* result = new UnicodeSet(); - if (result == nullptr) { - return nullptr; - } - result->addAll(*gUnicodeSets[k1]); - result->addAll(*gUnicodeSets[k2]); - result->addAll(*gUnicodeSets[k3]); - result->freeze(); - return result; -} - -icu::UInitOnce gNumberParseUniSetsInitOnce = U_INITONCE_INITIALIZER; - -UBool U_CALLCONV cleanupNumberParseUniSets() { - for (int32_t i = 0; i < COUNT; i++) { - delete gUnicodeSets[i]; - gUnicodeSets[i] = nullptr; - } - return TRUE; -} - -void U_CALLCONV initNumberParseUniSets(UErrorCode& status) { - ucln_i18n_registerCleanup(UCLN_I18N_NUMPARSE_UNISETS, cleanupNumberParseUniSets); - - gUnicodeSets[EMPTY] = new UnicodeSet(); - - // These characters are skipped over and ignored at any point in the string, even in strict mode. - // See ticket #13084. - gUnicodeSets[BIDI] = new UnicodeSet(u"[[:DI:]]", status); - - // This set was decided after discussion with icu-design@. See ticket #13309. - // Zs+TAB is "horizontal whitespace" according to UTS #18 (blank property). - gUnicodeSets[WHITESPACE] = new UnicodeSet(u"[[:Zs:][\\u0009]]", status); - - gUnicodeSets[DEFAULT_IGNORABLES] = computeUnion(BIDI, WHITESPACE); - gUnicodeSets[STRICT_IGNORABLES] = new UnicodeSet(*gUnicodeSets[BIDI]); - - // TODO: Re-generate these sets from the UCD. They probably haven't been updated in a while. - gUnicodeSets[COMMA] = new UnicodeSet(u"[,،٫、︐︑﹐﹑,、]", status); - gUnicodeSets[STRICT_COMMA] = new UnicodeSet(u"[,٫︐﹐,]", status); - gUnicodeSets[PERIOD] = new UnicodeSet(u"[.․。︒﹒.。]", status); - gUnicodeSets[STRICT_PERIOD] = new UnicodeSet(u"[.․﹒.。]", status); - gUnicodeSets[OTHER_GROUPING_SEPARATORS] = new UnicodeSet( - u"['٬‘’'\\u0020\\u00A0\\u2000-\\u200A\\u202F\\u205F\\u3000]", status); - gUnicodeSets[ALL_SEPARATORS] = computeUnion(COMMA, PERIOD, OTHER_GROUPING_SEPARATORS); - gUnicodeSets[STRICT_ALL_SEPARATORS] = computeUnion( - STRICT_COMMA, STRICT_PERIOD, OTHER_GROUPING_SEPARATORS); - - gUnicodeSets[MINUS_SIGN] = new UnicodeSet(u"[-⁻₋−➖﹣-]", status); - gUnicodeSets[PLUS_SIGN] = new UnicodeSet(u"[+⁺₊➕﬩﹢+]", status); - - gUnicodeSets[PERCENT_SIGN] = new UnicodeSet(u"[%٪]", status); - gUnicodeSets[PERMILLE_SIGN] = new UnicodeSet(u"[‰؉]", status); - gUnicodeSets[INFINITY_KEY] = new UnicodeSet(u"[∞]", status); - - gUnicodeSets[DIGITS] = new UnicodeSet(u"[:digit:]", status); - gUnicodeSets[CWCF] = new UnicodeSet(u"[:CWCF:]", status); - - gUnicodeSets[DIGITS_OR_ALL_SEPARATORS] = computeUnion(DIGITS, ALL_SEPARATORS); - gUnicodeSets[DIGITS_OR_STRICT_ALL_SEPARATORS] = computeUnion(DIGITS, STRICT_ALL_SEPARATORS); - - for (int32_t i = 0; i < COUNT; i++) { - gUnicodeSets[i]->freeze(); - } -} - -} - -const UnicodeSet* unisets::get(Key key) { - UErrorCode localStatus = U_ZERO_ERROR; - umtx_initOnce(gNumberParseUniSetsInitOnce, &initNumberParseUniSets, localStatus); - if (U_FAILURE(localStatus)) { - // TODO: This returns non-null in Java, and callers assume that. - return nullptr; - } - return gUnicodeSets[key]; -} - -Key unisets::chooseFrom(UnicodeString str, Key key1) { - return get(key1)->contains(str) ? key1 : COUNT; -} - -Key unisets::chooseFrom(UnicodeString str, Key key1, Key key2) { - return get(key1)->contains(str) ? key1 : chooseFrom(str, key2); -} - - -#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/scientificnumberformatter.cpp b/icu4c/source/i18n/scientificnumberformatter.cpp index adf032d989..a63f15f6fb 100644 --- a/icu4c/source/i18n/scientificnumberformatter.cpp +++ b/icu4c/source/i18n/scientificnumberformatter.cpp @@ -15,8 +15,8 @@ #include "unicode/fpositer.h" #include "unicode/utf16.h" #include "unicode/uniset.h" -#include "decfmtst.h" #include "unicode/decimfmt.h" +#include "numparse_unisets.h" U_NAMESPACE_BEGIN @@ -129,7 +129,6 @@ UnicodeString &ScientificNumberFormatter::SuperscriptStyle::format( const UnicodeString &original, FieldPositionIterator &fpi, const UnicodeString &preExponent, - const DecimalFormatStaticSets &staticSets, UnicodeString &appendTo, UErrorCode &status) const { if (U_FAILURE(status)) { @@ -149,16 +148,17 @@ UnicodeString &ScientificNumberFormatter::SuperscriptStyle::format( break; case UNUM_EXPONENT_SIGN_FIELD: { + using namespace icu::numparse::impl; int32_t beginIndex = fp.getBeginIndex(); int32_t endIndex = fp.getEndIndex(); UChar32 aChar = original.char32At(beginIndex); - if (staticSets.fMinusSigns->contains(aChar)) { + if (unisets::get(unisets::MINUS_SIGN)->contains(aChar)) { appendTo.append( original, copyFromOffset, beginIndex - copyFromOffset); appendTo.append(kSuperscriptMinusSign); - } else if (staticSets.fPlusSigns->contains(aChar)) { + } else if (unisets::get(unisets::PLUS_SIGN)->contains(aChar)) { appendTo.append( original, copyFromOffset, @@ -203,7 +203,6 @@ UnicodeString &ScientificNumberFormatter::MarkupStyle::format( const UnicodeString &original, FieldPositionIterator &fpi, const UnicodeString &preExponent, - const DecimalFormatStaticSets & /*unusedDecimalFormatSets*/, UnicodeString &appendTo, UErrorCode &status) const { if (U_FAILURE(status)) { @@ -243,8 +242,7 @@ ScientificNumberFormatter::ScientificNumberFormatter( DecimalFormat *fmtToAdopt, Style *styleToAdopt, UErrorCode &status) : fPreExponent(), fDecimalFormat(fmtToAdopt), - fStyle(styleToAdopt), - fStaticSets(NULL) { + fStyle(styleToAdopt) { if (U_FAILURE(status)) { return; } @@ -258,7 +256,6 @@ ScientificNumberFormatter::ScientificNumberFormatter( return; } getPreExponent(*sym, fPreExponent); - fStaticSets = DecimalFormatStaticSets::getStaticSets(status); } ScientificNumberFormatter::ScientificNumberFormatter( @@ -266,8 +263,7 @@ ScientificNumberFormatter::ScientificNumberFormatter( : UObject(other), fPreExponent(other.fPreExponent), fDecimalFormat(NULL), - fStyle(NULL), - fStaticSets(other.fStaticSets) { + fStyle(NULL) { fDecimalFormat = static_cast( other.fDecimalFormat->clone()); fStyle = other.fStyle->clone(); @@ -292,7 +288,6 @@ UnicodeString &ScientificNumberFormatter::format( original, fpi, fPreExponent, - *fStaticSets, appendTo, status); } diff --git a/icu4c/source/i18n/ucln_in.h b/icu4c/source/i18n/ucln_in.h index dc447ca898..318eafc143 100644 --- a/icu4c/source/i18n/ucln_in.h +++ b/icu4c/source/i18n/ucln_in.h @@ -27,7 +27,6 @@ It's usually best to have child dependencies called first. */ typedef enum ECleanupI18NType { UCLN_I18N_START = -1, UCLN_I18N_NUMBER_SKELETONS, - UCLN_I18N_NUMPARSE_UNISETS, UCLN_I18N_CURRENCY_SPACING, UCLN_I18N_SPOOF, UCLN_I18N_SPOOFDATA, diff --git a/icu4c/source/i18n/unicode/scientificnumberformatter.h b/icu4c/source/i18n/unicode/scientificnumberformatter.h index 15023d5141..6c34d2ce29 100644 --- a/icu4c/source/i18n/unicode/scientificnumberformatter.h +++ b/icu4c/source/i18n/unicode/scientificnumberformatter.h @@ -24,7 +24,6 @@ U_NAMESPACE_BEGIN class FieldPositionIterator; -class DecimalFormatStaticSets; class DecimalFormatSymbols; class DecimalFormat; class Formattable; @@ -150,7 +149,6 @@ public: const UnicodeString &original, FieldPositionIterator &fpi, const UnicodeString &preExponent, - const DecimalFormatStaticSets &decimalFormatSets, UnicodeString &appendTo, UErrorCode &status) const = 0; private: @@ -165,7 +163,6 @@ public: const UnicodeString &original, FieldPositionIterator &fpi, const UnicodeString &preExponent, - const DecimalFormatStaticSets &decimalFormatSets, UnicodeString &appendTo, UErrorCode &status) const; }; @@ -184,7 +181,6 @@ public: const UnicodeString &original, FieldPositionIterator &fpi, const UnicodeString &preExponent, - const DecimalFormatStaticSets &decimalFormatSets, UnicodeString &appendTo, UErrorCode &status) const; private: @@ -211,7 +207,6 @@ public: UnicodeString fPreExponent; DecimalFormat *fDecimalFormat; Style *fStyle; - const DecimalFormatStaticSets *fStaticSets; }; diff --git a/icu4c/source/test/intltest/numfmtst.cpp b/icu4c/source/test/intltest/numfmtst.cpp index 0d07750a4e..0e259e0d29 100644 --- a/icu4c/source/test/intltest/numfmtst.cpp +++ b/icu4c/source/test/intltest/numfmtst.cpp @@ -1412,7 +1412,7 @@ static const char *lenientAffixTestCases[] = { static const char *lenientMinusTestCases[] = { "-5", "\\u22125", - "\\u20105" + "\\u27965" }; static const char *lenientCurrencyTestCases[] = { diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/UnicodeSetStaticCache.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/UnicodeSetStaticCache.java index cba2dc9384..0148b36347 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/UnicodeSetStaticCache.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/UnicodeSetStaticCache.java @@ -5,7 +5,13 @@ package com.ibm.icu.impl.number.parse; import java.util.EnumMap; import java.util.Map; +import com.ibm.icu.impl.ICUData; +import com.ibm.icu.impl.ICUResourceBundle; +import com.ibm.icu.impl.UResource; +import com.ibm.icu.impl.UResource.Value; import com.ibm.icu.text.UnicodeSet; +import com.ibm.icu.util.ULocale; +import com.ibm.icu.util.UResourceBundle; /** * This class statically initializes UnicodeSets useful for number parsing. Microbenchmarks show this to @@ -20,8 +26,6 @@ import com.ibm.icu.text.UnicodeSet; public class UnicodeSetStaticCache { public static enum Key { // Ignorables - BIDI, - WHITESPACE, DEFAULT_IGNORABLES, STRICT_IGNORABLES, @@ -47,9 +51,14 @@ public class UnicodeSetStaticCache { PERMILLE_SIGN, INFINITY, + // Currency Symbols + DOLLAR_SIGN, + POUND_SIGN, + RUPEE_SIGN, + YEN_SIGN, // not in CLDR data, but Currency.java wants it + // Other DIGITS, - CWCF, // TODO: Check if this is being used and remove it if not. // Combined Separators with Digits (for lead code points) DIGITS_OR_ALL_SEPARATORS, @@ -70,6 +79,20 @@ public class UnicodeSetStaticCache { return get(key1).contains(str) ? key1 : chooseFrom(str, key2); } + public static Key chooseCurrency(String str) { + if (get(Key.DOLLAR_SIGN).contains(str)) { + return Key.DOLLAR_SIGN; + } else if (get(Key.POUND_SIGN).contains(str)) { + return Key.POUND_SIGN; + } else if (get(Key.RUPEE_SIGN).contains(str)) { + return Key.RUPEE_SIGN; + } else if (get(Key.YEN_SIGN).contains(str)) { + return Key.YEN_SIGN; + } else { + return null; + } + } + private static UnicodeSet computeUnion(Key k1, Key k2) { return new UnicodeSet().addAll(get(k1)).addAll(get(k2)).freeze(); } @@ -78,23 +101,98 @@ public class UnicodeSetStaticCache { return new UnicodeSet().addAll(get(k1)).addAll(get(k2)).addAll(get(k3)).freeze(); } + private static void saveSet(Key key, String unicodeSetPattern) { + assert unicodeSets.get(key) == null; + unicodeSets.put(key, new UnicodeSet(unicodeSetPattern).freeze()); + } + + /* + parse{ + date{ + lenient{ + "[\\--/]", + "[\\:∶]", + } + } + general{ + lenient{ + "[.․。︒﹒.。]", + "[\$﹩$$]", + "[£₤]", + "[₨₹{Rp}{Rs}]", + } + } + number{ + lenient{ + "[\\-‒⁻₋−➖﹣-]", + "[,،٫、︐︑﹐﹑,、]", + "[+⁺₊➕﬩﹢+]", + } + stricter{ + "[,٫︐﹐,]", + "[.․﹒.。]", + } + } + } + */ + static class ParseDataSink extends UResource.Sink { + @Override + public void put(com.ibm.icu.impl.UResource.Key key, Value value, boolean noFallback) { + UResource.Table contextsTable = value.getTable(); + for (int i = 0; contextsTable.getKeyAndValue(i, key, value); i++) { + if (key.contentEquals("date")) { + // ignore + } else { + assert key.contentEquals("general") || key.contentEquals("number"); + UResource.Table strictnessTable = value.getTable(); + for (int j = 0; strictnessTable.getKeyAndValue(j, key, value); j++) { + boolean isLenient = key.contentEquals("lenient"); + UResource.Array array = value.getArray(); + for (int k = 0; k < array.getSize(); k++) { + array.getValue(k, value); + String str = value.toString(); + // There is both lenient and strict data for comma/period, + // but not for any of the other symbols. + if (str.indexOf('.') != -1) { + saveSet(isLenient ? Key.PERIOD : Key.STRICT_PERIOD, str); + } else if (str.indexOf(',') != -1) { + saveSet(isLenient ? Key.COMMA : Key.STRICT_COMMA, str); + } else if (str.indexOf('+') != -1) { + saveSet(Key.PLUS_SIGN, str); + } else if (str.indexOf('‒') != -1) { + saveSet(Key.MINUS_SIGN, str); + } else if (str.indexOf('$') != -1) { + saveSet(Key.DOLLAR_SIGN, str); + } else if (str.indexOf('£') != -1) { + saveSet(Key.POUND_SIGN, str); + } else if (str.indexOf('₨') != -1) { + saveSet(Key.RUPEE_SIGN, str); + } + } + } + } + } + } + } + static { - // These characters are skipped over and ignored at any point in the string, even in strict mode. - // See ticket #13084. - unicodeSets.put(Key.BIDI, new UnicodeSet("[[:DI:]]").freeze()); - - // This set was decided after discussion with icu-design@. See ticket #13309. + // These sets were decided after discussion with icu-design@. See tickets #13084 and #13309. // Zs+TAB is "horizontal whitespace" according to UTS #18 (blank property). - unicodeSets.put(Key.WHITESPACE, new UnicodeSet("[[:Zs:][\\u0009]]").freeze()); + unicodeSets.put(Key.DEFAULT_IGNORABLES, + new UnicodeSet("[[:Zs:][\\u0009][:Bidi_Control:][:Variation_Selector:]]").freeze()); + unicodeSets.put(Key.STRICT_IGNORABLES, new UnicodeSet("[[:Bidi_Control:]]").freeze()); - unicodeSets.put(Key.DEFAULT_IGNORABLES, computeUnion(Key.BIDI, Key.WHITESPACE)); - unicodeSets.put(Key.STRICT_IGNORABLES, get(Key.BIDI)); + // CLDR provides data for comma, period, minus sign, and plus sign. + ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle + .getBundleInstance(ICUData.ICU_BASE_NAME, ULocale.ROOT); + rb.getAllItemsWithFallback("parse", new ParseDataSink()); + + // TODO: Should there be fallback behavior if for some reason these sets didn't get populated? + assert unicodeSets.containsKey(Key.COMMA); + assert unicodeSets.containsKey(Key.STRICT_COMMA); + assert unicodeSets.containsKey(Key.PERIOD); + assert unicodeSets.containsKey(Key.STRICT_PERIOD); - // TODO: Re-generate these sets from the UCD. They probably haven't been updated in a while. - unicodeSets.put(Key.COMMA, new UnicodeSet("[,،٫、︐︑﹐﹑,、]").freeze()); - unicodeSets.put(Key.STRICT_COMMA, new UnicodeSet("[,٫︐﹐,]").freeze()); - unicodeSets.put(Key.PERIOD, new UnicodeSet("[.․。︒﹒.。]").freeze()); - unicodeSets.put(Key.STRICT_PERIOD, new UnicodeSet("[.․﹒.。]").freeze()); unicodeSets.put(Key.OTHER_GROUPING_SEPARATORS, new UnicodeSet("['٬‘’'\\u0020\\u00A0\\u2000-\\u200A\\u202F\\u205F\\u3000]").freeze()); unicodeSets.put(Key.ALL_SEPARATORS, @@ -102,15 +200,19 @@ public class UnicodeSetStaticCache { unicodeSets.put(Key.STRICT_ALL_SEPARATORS, computeUnion(Key.STRICT_COMMA, Key.STRICT_PERIOD, Key.OTHER_GROUPING_SEPARATORS)); - unicodeSets.put(Key.MINUS_SIGN, new UnicodeSet("[-⁻₋−➖﹣-]").freeze()); - unicodeSets.put(Key.PLUS_SIGN, new UnicodeSet("[+⁺₊➕﬩﹢+]").freeze()); + assert unicodeSets.containsKey(Key.MINUS_SIGN); + assert unicodeSets.containsKey(Key.PLUS_SIGN); unicodeSets.put(Key.PERCENT_SIGN, new UnicodeSet("[%٪]").freeze()); unicodeSets.put(Key.PERMILLE_SIGN, new UnicodeSet("[‰؉]").freeze()); unicodeSets.put(Key.INFINITY, new UnicodeSet("[∞]").freeze()); + assert unicodeSets.containsKey(Key.DOLLAR_SIGN); + assert unicodeSets.containsKey(Key.POUND_SIGN); + assert unicodeSets.containsKey(Key.RUPEE_SIGN); + unicodeSets.put(Key.YEN_SIGN, new UnicodeSet("[¥\\uffe5]").freeze()); + unicodeSets.put(Key.DIGITS, new UnicodeSet("[:digit:]").freeze()); - unicodeSets.put(Key.CWCF, new UnicodeSet("[:CWCF:]").freeze()); unicodeSets.put(Key.DIGITS_OR_ALL_SEPARATORS, computeUnion(Key.DIGITS, Key.ALL_SEPARATORS)); unicodeSets.put(Key.DIGITS_OR_STRICT_ALL_SEPARATORS, diff --git a/icu4j/main/classes/core/src/com/ibm/icu/util/Currency.java b/icu4j/main/classes/core/src/com/ibm/icu/util/Currency.java index a05cebd804..03febbe143 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/util/Currency.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/util/Currency.java @@ -14,7 +14,6 @@ import java.text.ParsePosition; import java.util.ArrayList; import java.util.Collections; import java.util.Date; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -31,10 +30,12 @@ import com.ibm.icu.impl.ICUResourceBundle; import com.ibm.icu.impl.SimpleCache; import com.ibm.icu.impl.SoftCache; import com.ibm.icu.impl.TextTrieMap; +import com.ibm.icu.impl.number.parse.UnicodeSetStaticCache; import com.ibm.icu.text.CurrencyDisplayNames; import com.ibm.icu.text.CurrencyMetaInfo; import com.ibm.icu.text.CurrencyMetaInfo.CurrencyDigits; import com.ibm.icu.text.CurrencyMetaInfo.CurrencyFilter; +import com.ibm.icu.text.UnicodeSet; import com.ibm.icu.util.ULocale.Category; /** @@ -98,13 +99,6 @@ public class Currency extends MeasureUnit { */ public static final int NARROW_SYMBOL_NAME = 3; - private static final EquivalenceRelation EQUIVALENT_CURRENCY_SYMBOLS = - new EquivalenceRelation() - .add("\u00a5", "\uffe5") - .add("$", "\ufe69", "\uff04") - .add("\u20a8", "\u20b9") - .add("\u00a3", "\u20a4"); - /** * Currency Usage used for Decimal Format * @stable ICU 54 @@ -778,8 +772,16 @@ public class Currency extends MeasureUnit { String isoCode = e.getValue(); // Register under not just symbol, but under every equivalent symbol as well // e.g short width yen and long width yen. - for (String equivalentSymbol : EQUIVALENT_CURRENCY_SYMBOLS.get(symbol)) { - symTrie.put(equivalentSymbol, new CurrencyStringInfo(isoCode, symbol)); + UnicodeSetStaticCache.Key key = UnicodeSetStaticCache.chooseCurrency(symbol); + CurrencyStringInfo value = new CurrencyStringInfo(isoCode, symbol); + if (key != null) { + UnicodeSet equivalents = UnicodeSetStaticCache.get(key); + // The symbol itself is included in the UnicodeSet + for (String equivalentSymbol : equivalents) { + symTrie.put(equivalentSymbol, value); + } + } else { + symTrie.put(symbol, value); } } for (Map.Entry e : names.nameMap().entrySet()) { @@ -1039,34 +1041,6 @@ public class Currency extends MeasureUnit { return info.currencies(filter.withTender()); } - private static final class EquivalenceRelation { - - private Map> data = new HashMap>(); - - @SuppressWarnings("unchecked") // See ticket #11395, this is safe. - public EquivalenceRelation add(T... items) { - Set group = new HashSet(); - for (T item : items) { - if (data.containsKey(item)) { - throw new IllegalArgumentException("All groups passed to add must be disjoint."); - } - group.add(item); - } - for (T item : items) { - data.put(item, group); - } - return this; - } - - public Set get(T item) { - Set result = data.get(item); - if (result == null) { - return Collections.singleton(item); - } - return Collections.unmodifiableSet(result); - } - } - private Object writeReplace() throws ObjectStreamException { return new MeasureUnitProxy(type, subType); } diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java index b442d3b302..de6c392777 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java @@ -1764,7 +1764,7 @@ public class NumberFormatTest extends TestFmwk { } // Test default ignorable characters. These should work in both lenient and strict. - UnicodeSet defaultIgnorables = new UnicodeSet("[[:Default_Ignorable_Code_Point:]]").freeze(); + UnicodeSet defaultIgnorables = new UnicodeSet("[[:Bidi_Control:]]").freeze(); fmt.setParseStrict(false); for (String ignorable : defaultIgnorables) { String str = "a b " + ignorable + "1234c "; From 79f4944ecd2eb2e9aa2859fd5c794280bf36fe4c Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Sat, 14 Apr 2018 05:54:53 +0000 Subject: [PATCH 094/129] ICU-13634 Refactoring new percentage parsing code. X-SVN-Rev: 41227 --- icu4c/source/i18n/numparse_impl.cpp | 11 +++----- icu4c/source/i18n/numparse_impl.h | 2 -- icu4c/source/i18n/numparse_parsednumber.cpp | 6 ---- icu4c/source/i18n/numparse_validators.cpp | 12 -------- icu4c/source/i18n/numparse_validators.h | 18 ------------ .../source/test/intltest/numbertest_parse.cpp | 12 ++++---- .../icu/impl/number/parse/FlagHandler.java | 28 ------------------- .../impl/number/parse/NumberParserImpl.java | 13 ++++----- .../icu/impl/number/parse/ParsedNumber.java | 6 ---- .../icu/dev/test/number/NumberParserTest.java | 12 ++++---- 10 files changed, 21 insertions(+), 99 deletions(-) delete mode 100644 icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/FlagHandler.java diff --git a/icu4c/source/i18n/numparse_impl.cpp b/icu4c/source/i18n/numparse_impl.cpp index acc68394a1..25be60726d 100644 --- a/icu4c/source/i18n/numparse_impl.cpp +++ b/icu4c/source/i18n/numparse_impl.cpp @@ -158,13 +158,9 @@ NumberParserImpl::createParserFromProperties(const number::impl::DecimalFormatPr // and to maintain regressive behavior, divide by 100 even if no percent sign is present. if (affixProvider->containsSymbolType(AffixPatternType::TYPE_PERCENT, status)) { parser->addMatcher(parser->fLocalMatchers.percent = {symbols}); - // causes number to be always scaled by 100: - parser->addMatcher(parser->fLocalValidators.percentFlags = {ResultFlags::FLAG_PERCENT}); } if (affixProvider->containsSymbolType(AffixPatternType::TYPE_PERMILLE, status)) { parser->addMatcher(parser->fLocalMatchers.permille = {symbols}); - // causes number to be always scaled by 1000: - parser->addMatcher(parser->fLocalValidators.permilleFlags = {ResultFlags::FLAG_PERMILLE}); } /////////////////////////////// @@ -206,9 +202,10 @@ NumberParserImpl::createParserFromProperties(const number::impl::DecimalFormatPr properties.decimalSeparatorAlwaysShown || properties.maximumFractionDigits != 0; parser->addMatcher(parser->fLocalValidators.decimalSeparator = {patternHasDecimalSeparator}); } - // NOTE: Don't look at magnitude multiplier here. That is performed when percent sign is seen. - if (properties.multiplier != 1) { - parser->addMatcher(parser->fLocalValidators.multiplier = {multiplierFromProperties(properties)}); + // The multiplier takes care of scaling percentages. + Multiplier multiplier = multiplierFromProperties(properties); + if (multiplier.isValid()) { + parser->addMatcher(parser->fLocalValidators.multiplier = {multiplier}); } parser->freeze(); diff --git a/icu4c/source/i18n/numparse_impl.h b/icu4c/source/i18n/numparse_impl.h index 4677ae14d8..748b9415ec 100644 --- a/icu4c/source/i18n/numparse_impl.h +++ b/icu4c/source/i18n/numparse_impl.h @@ -80,8 +80,6 @@ class NumberParserImpl : public MutableMatcherCollection { RequireExponentValidator exponent; RequireNumberValidator number; MultiplierParseHandler multiplier; - FlagHandler percentFlags; - FlagHandler permilleFlags; } fLocalValidators; explicit NumberParserImpl(parse_flags_t parseFlags); diff --git a/icu4c/source/i18n/numparse_parsednumber.cpp b/icu4c/source/i18n/numparse_parsednumber.cpp index c658fc27d3..f97219eed8 100644 --- a/icu4c/source/i18n/numparse_parsednumber.cpp +++ b/icu4c/source/i18n/numparse_parsednumber.cpp @@ -41,12 +41,6 @@ void ParsedNumber::postProcess() { if (!quantity.bogus && 0 != (flags & FLAG_NEGATIVE)) { quantity.negate(); } - if (!quantity.bogus && 0 != (flags & FLAG_PERCENT)) { - quantity.adjustMagnitude(-2); - } - if (!quantity.bogus && 0 != (flags & FLAG_PERMILLE)) { - quantity.adjustMagnitude(-3); - } } bool ParsedNumber::success() const { diff --git a/icu4c/source/i18n/numparse_validators.cpp b/icu4c/source/i18n/numparse_validators.cpp index a36a15f512..724b0cf031 100644 --- a/icu4c/source/i18n/numparse_validators.cpp +++ b/icu4c/source/i18n/numparse_validators.cpp @@ -80,16 +80,4 @@ UnicodeString RequireNumberValidator::toString() const { } -FlagHandler::FlagHandler(result_flags_t flags) - : fFlags(flags) {} - -void FlagHandler::postProcess(ParsedNumber& result) const { - result.flags |= fFlags; -} - -UnicodeString FlagHandler::toString() const { - return u""; -} - - #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/numparse_validators.h b/icu4c/source/i18n/numparse_validators.h index c27890ae11..d3bc63aceb 100644 --- a/icu4c/source/i18n/numparse_validators.h +++ b/icu4c/source/i18n/numparse_validators.h @@ -97,24 +97,6 @@ class MultiplierParseHandler : public ValidationMatcher, public UMemory { }; -/** - * Unconditionally applies a given set of flags to the ParsedNumber in the post-processing step. - */ -class FlagHandler : public ValidationMatcher, public UMemory { - public: - FlagHandler() = default; - - FlagHandler(result_flags_t flags); - - void postProcess(ParsedNumber& result) const U_OVERRIDE; - - UnicodeString toString() const U_OVERRIDE; - - private: - result_flags_t fFlags; -}; - - } // namespace impl } // namespace numparse U_NAMESPACE_END diff --git a/icu4c/source/test/intltest/numbertest_parse.cpp b/icu4c/source/test/intltest/numbertest_parse.cpp index ec8fee01b8..e5a10c2c3f 100644 --- a/icu4c/source/test/intltest/numbertest_parse.cpp +++ b/icu4c/source/test/intltest/numbertest_parse.cpp @@ -59,12 +59,12 @@ void NumberParserTest::testBasic() { {3, u"51423-", u"0", 5, 51423.}, // plus and minus sign by default do NOT match after {3, u"+51423", u"0", 6, 51423.}, {3, u"51423+", u"0", 5, 51423.}, // plus and minus sign by default do NOT match after - {3, u"%51423", u"0", 6, 514.23}, - {3, u"51423%", u"0", 6, 514.23}, - {3, u"51423%%", u"0", 6, 514.23}, - {3, u"‰51423", u"0", 6, 51.423}, - {3, u"51423‰", u"0", 6, 51.423}, - {3, u"51423‰‰", u"0", 6, 51.423}, + {3, u"%51423", u"0", 6, 51423.}, + {3, u"51423%", u"0", 6, 51423.}, + {3, u"51423%%", u"0", 6, 51423.}, + {3, u"‰51423", u"0", 6, 51423.}, + {3, u"51423‰", u"0", 6, 51423.}, + {3, u"51423‰‰", u"0", 6, 51423.}, {3, u"∞", u"0", 1, INFINITY}, {3, u"-∞", u"0", 2, -INFINITY}, {3, u"@@@123 @@", u"0", 6, 123.}, // TODO: Should padding be strong instead of weak? diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/FlagHandler.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/FlagHandler.java deleted file mode 100644 index 37d39113ad..0000000000 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/FlagHandler.java +++ /dev/null @@ -1,28 +0,0 @@ -// © 2018 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html#License -package com.ibm.icu.impl.number.parse; - -/** - * Unconditionally applies a given set of flags to the ParsedNumber in the post-processing step. - */ -public class FlagHandler extends ValidationMatcher { - - public static final FlagHandler PERCENT = new FlagHandler(ParsedNumber.FLAG_PERCENT); - public static final FlagHandler PERMILLE = new FlagHandler(ParsedNumber.FLAG_PERMILLE); - - private final int flags; - - private FlagHandler(int flags) { - this.flags = flags; - } - - @Override - public void postProcess(ParsedNumber result) { - result.flags |= flags; - } - - @Override - public String toString() { - return ""; - } -} diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java index 3d41c37ce7..0e964ef576 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java @@ -19,6 +19,7 @@ import com.ibm.icu.impl.number.PatternStringParser; import com.ibm.icu.impl.number.PatternStringParser.ParsedPatternInfo; import com.ibm.icu.impl.number.PropertiesAffixPatternProvider; import com.ibm.icu.impl.number.RoundingUtils; +import com.ibm.icu.number.Multiplier; import com.ibm.icu.number.NumberFormatter.GroupingStrategy; import com.ibm.icu.text.DecimalFormatSymbols; import com.ibm.icu.util.Currency; @@ -204,13 +205,9 @@ public class NumberParserImpl { // and to maintain regressive behavior, divide by 100 even if no percent sign is present. if (affixProvider.containsSymbolType(AffixUtils.TYPE_PERCENT)) { parser.addMatcher(PercentMatcher.getInstance(symbols)); - // causes number to be always scaled by 100: - parser.addMatcher(FlagHandler.PERCENT); } if (affixProvider.containsSymbolType(AffixUtils.TYPE_PERMILLE)) { parser.addMatcher(PermilleMatcher.getInstance(symbols)); - // causes number to be always scaled by 1000: - parser.addMatcher(FlagHandler.PERMILLE); } /////////////////////////////// @@ -252,10 +249,10 @@ public class NumberParserImpl { || properties.getMaximumFractionDigits() != 0; parser.addMatcher(RequireDecimalSeparatorValidator.getInstance(patternHasDecimalSeparator)); } - // NOTE: Don't look at magnitude multiplier here. That is performed when percent sign is seen. - if (properties.getMultiplier() != null) { - parser.addMatcher( - new MultiplierParseHandler(RoundingUtils.multiplierFromProperties(properties))); + // The multiplier takes care of scaling percentages. + Multiplier multiplier = RoundingUtils.multiplierFromProperties(properties); + if (multiplier != null) { + parser.addMatcher(new MultiplierParseHandler(multiplier)); } parser.freeze(); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java index 40277d7700..c6e686e64e 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java @@ -116,12 +116,6 @@ public class ParsedNumber { if (quantity != null && 0 != (flags & FLAG_NEGATIVE)) { quantity.negate(); } - if (quantity != null && 0 != (flags & FLAG_PERCENT)) { - quantity.adjustMagnitude(-2); - } - if (quantity != null && 0 != (flags & FLAG_PERMILLE)) { - quantity.adjustMagnitude(-3); - } } /** diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java index 57aef1547c..cf6ed3dc5b 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java @@ -65,12 +65,12 @@ public class NumberParserTest { { 3, "51423-", "0", 5, 51423. }, // plus and minus sign by default do NOT match after { 3, "+51423", "0", 6, 51423. }, { 3, "51423+", "0", 5, 51423. }, // plus and minus sign by default do NOT match after - { 3, "%51423", "0", 6, 514.23 }, - { 3, "51423%", "0", 6, 514.23 }, - { 3, "51423%%", "0", 6, 514.23 }, - { 3, "‰51423", "0", 6, 51.423 }, - { 3, "51423‰", "0", 6, 51.423 }, - { 3, "51423‰‰", "0", 6, 51.423 }, + { 3, "%51423", "0", 6, 51423. }, + { 3, "51423%", "0", 6, 51423. }, + { 3, "51423%%", "0", 6, 51423. }, + { 3, "‰51423", "0", 6, 51423. }, + { 3, "51423‰", "0", 6, 51423. }, + { 3, "51423‰‰", "0", 6, 51423. }, { 3, "∞", "0", 1, Double.POSITIVE_INFINITY }, { 3, "-∞", "0", 2, Double.NEGATIVE_INFINITY }, { 3, "@@@123 @@", "0", 6, 123. }, // TODO: Should padding be strong instead of weak? From cd3b2c7d418917982f13a20b0668cd8696e12cab Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Sat, 14 Apr 2018 06:17:39 +0000 Subject: [PATCH 095/129] ICU-13634 Updating Java test expectations with new behavior for fallback currency display. Other minor ICU4J test updates. All ICU4J tests are passing. X-SVN-Rev: 41228 --- icu4c/source/i18n/number_decimalquantity.cpp | 3 ++- .../icu/impl/number/CustomSymbolCurrency.java | 4 ---- .../number/DecimalQuantity_AbstractBCD.java | 5 +++-- .../icu/number/SkeletonSyntaxException.java | 18 +++++++++++++++++- .../format/NumberFormatRegressionTest.java | 4 ++-- .../dev/test/format/NumberRegressionTests.java | 17 ++++++++++++----- .../icu/dev/test/format/TestMessageFormat.java | 9 +++++---- .../test/serializable/ExceptionHandler.java | 12 ++++++++++++ .../serializable/SerializableTestUtility.java | 1 + 9 files changed, 54 insertions(+), 19 deletions(-) diff --git a/icu4c/source/i18n/number_decimalquantity.cpp b/icu4c/source/i18n/number_decimalquantity.cpp index baae9b7632..c61cd82e32 100644 --- a/icu4c/source/i18n/number_decimalquantity.cpp +++ b/icu4c/source/i18n/number_decimalquantity.cpp @@ -245,7 +245,8 @@ double DecimalQuantity::getPluralOperand(PluralOperand operand) const { switch (operand) { case PLURAL_OPERAND_I: - return static_cast(toLong()); + // Invert the negative sign if necessary + return static_cast(isNegative() ? -toLong() : toLong()); case PLURAL_OPERAND_F: return static_cast(toFractionLong(true)); case PLURAL_OPERAND_T: diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CustomSymbolCurrency.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CustomSymbolCurrency.java index a04170b625..bf43ccfff4 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CustomSymbolCurrency.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CustomSymbolCurrency.java @@ -49,10 +49,6 @@ public class CustomSymbolCurrency extends Currency { @Override public String getName(ULocale locale, int nameStyle, String pluralCount, boolean[] isChoiceFormat) { - if (nameStyle == PLURAL_LONG_NAME && subType.equals("XXX")) { - // Plural in absence of a currency should return the symbol - return symbol1; - } return super.getName(locale, nameStyle, pluralCount, isChoiceFormat); } 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 160c4fd1f8..7cb9bb1b15 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 @@ -241,7 +241,8 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { switch (operand) { case i: - return toLong(); + // Invert the negative sign if necessary + return isNegative() ? -toLong() : toLong(); case f: return toFractionLong(true); case t: @@ -571,7 +572,7 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { * Returns a long approximating the internal BCD. A long can only represent the integral part of the * number. * - * @return A double representation of the internal BCD. + * @return A 64-bit integer representation of the internal BCD. */ public long toLong() { long result = 0L; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/SkeletonSyntaxException.java b/icu4j/main/classes/core/src/com/ibm/icu/number/SkeletonSyntaxException.java index 92ba548eb4..d749c65943 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/SkeletonSyntaxException.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/SkeletonSyntaxException.java @@ -5,15 +5,31 @@ package com.ibm.icu.number; /** * Exception used for illegal number skeleton strings. * - * @author sffc + * @draft ICU 62 + * @provisional This API might change or be removed in a future release. + * @see NumberFormatter */ public class SkeletonSyntaxException extends IllegalArgumentException { private static final long serialVersionUID = 7733971331648360554L; + /** + * Construct a new SkeletonSyntaxException with information about the token at the point of failure. + * + * @draft ICU 62 + * @provisional This API might change or be removed in a future release. + * @see NumberFormatter + */ public SkeletonSyntaxException(String message, CharSequence token) { super("Syntax error in skeleton string: " + message + ": " + token); } + /** + * Construct a new SkeletonSyntaxException with information about the token at the point of failure. + * + * @draft ICU 62 + * @provisional This API might change or be removed in a future release. + * @see NumberFormatter + */ public SkeletonSyntaxException(String message, CharSequence token, Throwable cause) { super("Syntax error in skeleton string: " + message + ": " + token, cause); } diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatRegressionTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatRegressionTest.java index 466a8124d5..c07161b10a 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatRegressionTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatRegressionTest.java @@ -393,8 +393,8 @@ public class NumberFormatRegressionTest extends TestFmwk { ULocale locale = new ULocale("en"); DecimalFormat nf = (DecimalFormat) NumberFormat.getInstance(locale, NumberFormat.PLURALCURRENCYSTYLE); assertEquals( - "Positive suffix should contain the single currency sign when no currency is set", - " \u00A4", + "Positive suffix should contain the localized display name for currency XXX", + " (unknown currency)", nf.getPositiveSuffix()); } } diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberRegressionTests.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberRegressionTests.java index 0b163c8a7b..af4eb3e54c 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberRegressionTests.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberRegressionTests.java @@ -38,6 +38,7 @@ import java.text.ParsePosition; import java.util.Date; import java.util.Locale; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -49,6 +50,7 @@ import com.ibm.icu.text.DateFormat; import com.ibm.icu.text.DecimalFormat; import com.ibm.icu.text.DecimalFormatSymbols; import com.ibm.icu.text.NumberFormat; +import com.ibm.icu.util.Currency; import com.ibm.icu.util.GregorianCalendar; import com.ibm.icu.util.ULocale; import com.ibm.icu.util.VersionInfo; @@ -410,9 +412,7 @@ public class NumberRegressionTests extends TestFmwk { logln("Long.MIN_VALUE : " + df.parse(str, new ParsePosition(0)).toString()); df.setMultiplier(100); Number num = df.parse(str, new ParsePosition(0)); - if (num.doubleValue() != -9.223372036854776E16) { - errln("Bug 4092561 test failed when multiplier is set to not 1."); - } + assertEquals("Bug 4092561 test failed when multiplier is set to not 1.", -9.223372036854776E16, num.doubleValue()); Locale.setDefault(savedLocale); } @@ -998,8 +998,12 @@ public class NumberRegressionTests extends TestFmwk { * 1) Make sure that all currency formats use the generic currency symbol. * 2) Make sure we get the same results using the generic symbol or a * hard-coded one. + * + * ICU 62: DecimalFormatSymbols currency symbol has long been deprecated. + * In the absence of a user-specified currency, XXX is used instead. */ @Test + @Ignore public void Test4122840() { Locale[] locales = NumberFormat.getAvailableLocales(); @@ -1555,10 +1559,13 @@ public class NumberRegressionTests extends TestFmwk { String pat = df.toPattern(); DecimalFormatSymbols symb = new DecimalFormatSymbols(avail[i]); DecimalFormat f2 = new DecimalFormat(pat, symb); - f2.setCurrency(df.getCurrency()); // Currency does not travel with the pattern string + if (df.getCurrency() != Currency.getInstance("XXX") && j == 1) { + // Currency does not travel with the pattern string + f2.setCurrency(df.getCurrency()); + } if (!df.equals(f2)) { errln("FAIL: " + avail[i] + " #" + j + " -> \"" + pat + - "\" -> \"" + f2.toPattern() + '"'); + "\" -> \"" + f2.toPattern() + "\" for case " + j); } // Test toLocalizedPattern/applyLocalizedPattern round trip diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/TestMessageFormat.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/TestMessageFormat.java index 9c3b109904..83dc828937 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/TestMessageFormat.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/TestMessageFormat.java @@ -443,16 +443,17 @@ public class TestMessageFormat extends TestFmwk { String formatStr = "At

- * To create a Multiplier, use one of the factory methods. + * To create a Scale, use one of the factory methods. * * @draft ICU 62 */ -class U_I18N_API Multiplier : public UMemory { +class U_I18N_API Scale : public UMemory { public: /** * Do not change the value of numbers when formatting or parsing. * - * @return A Multiplier to prevent any multiplication. + * @return A Scale to prevent any multiplication. * @draft ICU 62 */ - static Multiplier none(); + static Scale none(); /** - * Multiply numbers by 100 before formatting. Useful for combining with a percent unit: + * Multiply numbers by a power of ten before formatting. Useful for combining with a percent unit: * *

-     * NumberFormatter::with().unit(NoUnit::percent()).multiplier(Multiplier::powerOfTen(2))
+     * NumberFormatter::with().unit(NoUnit::percent()).multiplier(Scale::powerOfTen(2))
      * 
* - * @return A Multiplier for passing to the setter in NumberFormatter. + * @return A Scale for passing to the setter in NumberFormatter. * @draft ICU 62 */ - static Multiplier powerOfTen(int32_t power); + static Scale powerOfTen(int32_t power); /** * Multiply numbers by an arbitrary value before formatting. Useful for unit conversions. @@ -1005,50 +1005,58 @@ class U_I18N_API Multiplier : public UMemory { * * Also see the version of this method that takes a double. * - * @return A Multiplier for passing to the setter in NumberFormatter. + * @return A Scale for passing to the setter in NumberFormatter. * @draft ICU 62 */ - static Multiplier arbitraryDecimal(StringPiece multiplicand); + static Scale byDecimal(StringPiece multiplicand); /** * Multiply numbers by an arbitrary value before formatting. Useful for unit conversions. * * This method takes a double; also see the version of this method that takes an exact decimal. * - * @return A Multiplier for passing to the setter in NumberFormatter. + * @return A Scale for passing to the setter in NumberFormatter. * @draft ICU 62 */ - static Multiplier arbitraryDouble(double multiplicand); + static Scale byDouble(double multiplicand); + + /** + * Multiply a number by both a power of ten and by an arbitrary double value. + * + * @return A Scale for passing to the setter in NumberFormatter. + * @draft ICU 62 + */ + static Scale byDoubleAndPowerOfTen(double multiplicand, int32_t power); // We need a custom destructor for the DecNum, which means we need to declare // the copy/move constructor/assignment quartet. /** @draft ICU 62 */ - Multiplier(const Multiplier& other); + Scale(const Scale& other); /** @draft ICU 62 */ - Multiplier& operator=(const Multiplier& other); + Scale& operator=(const Scale& other); /** @draft ICU 62 */ - Multiplier(Multiplier&& src) U_NOEXCEPT; + Scale(Scale&& src) U_NOEXCEPT; /** @draft ICU 62 */ - Multiplier& operator=(Multiplier&& src) U_NOEXCEPT; + Scale& operator=(Scale&& src) U_NOEXCEPT; /** @draft ICU 62 */ - ~Multiplier(); + ~Scale(); /** @internal */ - Multiplier(int32_t magnitude, impl::DecNum* arbitraryToAdopt); + Scale(int32_t magnitude, impl::DecNum* arbitraryToAdopt); private: int32_t fMagnitude; impl::DecNum* fArbitrary; UErrorCode fError; - Multiplier(UErrorCode error) : fMagnitude(0), fArbitrary(nullptr), fError(error) {} + Scale(UErrorCode error) : fMagnitude(0), fArbitrary(nullptr), fError(error) {} - Multiplier() : fMagnitude(0), fArbitrary(nullptr), fError(U_ZERO_ERROR) {} + Scale() : fMagnitude(0), fArbitrary(nullptr), fError(U_ZERO_ERROR) {} bool isValid() const { return fMagnitude != 0 || fArbitrary != nullptr; @@ -1364,7 +1372,7 @@ struct U_I18N_API MacroProps : public UMemory { UNumberDecimalSeparatorDisplay decimal = UNUM_DECIMAL_SEPARATOR_COUNT; /** @internal */ - Multiplier multiplier; // = Multiplier(); (benign value) + Scale scale; // = Scale(); (benign value) /** @internal */ AffixPatternProvider* affixProvider = nullptr; // no ownership @@ -1390,7 +1398,7 @@ struct U_I18N_API MacroProps : public UMemory { bool copyErrorTo(UErrorCode &status) const { return notation.copyErrorTo(status) || rounder.copyErrorTo(status) || padder.copyErrorTo(status) || integerWidth.copyErrorTo(status) || - symbols.copyErrorTo(status) || multiplier.copyErrorTo(status); + symbols.copyErrorTo(status) || scale.copyErrorTo(status); } }; @@ -1926,8 +1934,8 @@ class U_I18N_API NumberFormatterSettings { Derived decimal(const UNumberDecimalSeparatorDisplay &style) &&; /** - * Sets a multiplier to be used to scale the number by an arbitrary amount before formatting. Most - * common values: + * Sets a scale (multiplier) to be used to scale the number by an arbitrary amount before formatting. + * Most common values: * *
    *
  • Multiply by 100: useful for percentages. @@ -1935,32 +1943,32 @@ class U_I18N_API NumberFormatterSettings { *
* *

- * Pass an element from a {@link Multiplier} factory method to this setter. For example: + * Pass an element from a {@link Scale} factory method to this setter. For example: * *

-     * NumberFormatter::with().multiplier(Multiplier::powerOfTen(2))
+     * NumberFormatter::with().scale(Scale::powerOfTen(2))
      * 
* *

* The default is to not apply any multiplier. * - * @param style - * The decimal separator display strategy to use when rendering numbers. + * @param scale + * The scale to apply when rendering numbers. * @return The fluent chain * @draft ICU 60 */ - Derived multiplier(const Multiplier &style) const &; + Derived scale(const Scale &scale) const &; /** - * Overload of multiplier() for use on an rvalue reference. + * Overload of scale() for use on an rvalue reference. * - * @param style - * The multiplier separator display strategy to use when rendering numbers. + * @param scale + * The scale to apply when rendering numbers. * @return The fluent chain. - * @see #multiplier + * @see #scale * @draft ICU 62 */ - Derived multiplier(const Multiplier &style) &&; + Derived scale(const Scale &scale) &&; #ifndef U_HIDE_INTERNAL_API diff --git a/icu4c/source/test/intltest/numbertest.h b/icu4c/source/test/intltest/numbertest.h index a3678d5913..fdb892a0fc 100644 --- a/icu4c/source/test/intltest/numbertest.h +++ b/icu4c/source/test/intltest/numbertest.h @@ -64,7 +64,7 @@ class NumberFormatterApiTest : public IntlTest { //void symbolsOverride(); void sign(); void decimal(); - void multiplier(); + void scale(); void locale(); void formatTypes(); void errors(); diff --git a/icu4c/source/test/intltest/numbertest_api.cpp b/icu4c/source/test/intltest/numbertest_api.cpp index ca75918311..d81a1cf133 100644 --- a/icu4c/source/test/intltest/numbertest_api.cpp +++ b/icu4c/source/test/intltest/numbertest_api.cpp @@ -78,7 +78,7 @@ void NumberFormatterApiTest::runIndexedTest(int32_t index, UBool exec, const cha //TESTCASE_AUTO(symbolsOverride); TESTCASE_AUTO(sign); TESTCASE_AUTO(decimal); - TESTCASE_AUTO(multiplier); + TESTCASE_AUTO(scale); TESTCASE_AUTO(locale); TESTCASE_AUTO(formatTypes); TESTCASE_AUTO(errors); @@ -1911,11 +1911,11 @@ void NumberFormatterApiTest::decimal() { u"0."); } -void NumberFormatterApiTest::multiplier() { +void NumberFormatterApiTest::scale() { assertFormatDescending( u"Multiplier None", - u"multiply/1", - NumberFormatter::with().multiplier(Multiplier::none()), + u"scale/1", + NumberFormatter::with().scale(Scale::none()), Locale::getEnglish(), u"87,650", u"8,765", @@ -1929,8 +1929,8 @@ void NumberFormatterApiTest::multiplier() { assertFormatDescending( u"Multiplier Power of Ten", - u"multiply/1000000", - NumberFormatter::with().multiplier(Multiplier::powerOfTen(6)), + u"scale/1000000", + NumberFormatter::with().scale(Scale::powerOfTen(6)), Locale::getEnglish(), u"87,650,000,000", u"8,765,000,000", @@ -1944,8 +1944,8 @@ void NumberFormatterApiTest::multiplier() { assertFormatDescending( u"Multiplier Arbitrary Double", - u"multiply/5.2", - NumberFormatter::with().multiplier(Multiplier::arbitraryDouble(5.2)), + u"scale/5.2", + NumberFormatter::with().scale(Scale::byDouble(5.2)), Locale::getEnglish(), u"455,780", u"45,578", @@ -1959,8 +1959,8 @@ void NumberFormatterApiTest::multiplier() { assertFormatDescending( u"Multiplier Arbitrary BigDecimal", - u"multiply/5.2", - NumberFormatter::with().multiplier(Multiplier::arbitraryDecimal({"5.2", -1})), + u"scale/5.2", + NumberFormatter::with().scale(Scale::byDecimal({"5.2", -1})), Locale::getEnglish(), u"455,780", u"45,578", @@ -1972,10 +1972,25 @@ void NumberFormatterApiTest::multiplier() { u"0.045578", u"0"); + assertFormatDescending( + u"Multiplier Arbitrary Double And Power Of Ten", + u"scale/5200", + NumberFormatter::with().scale(Scale::byDoubleAndPowerOfTen(5.2, 3)), + Locale::getEnglish(), + u"455,780,000", + u"45,578,000", + u"4,557,800", + u"455,780", + u"45,578", + u"4,557.8", + u"455.78", + u"45.578", + u"0"); + assertFormatDescending( u"Multiplier Zero", - u"multiply/0", - NumberFormatter::with().multiplier(Multiplier::arbitraryDouble(0)), + u"scale/0", + NumberFormatter::with().scale(Scale::byDouble(0)), Locale::getEnglish(), u"0", u"0", @@ -1989,27 +2004,35 @@ void NumberFormatterApiTest::multiplier() { assertFormatSingle( u"Multiplier Skeleton Scientific Notation and Percent", - u"percent multiply/1E2", - NumberFormatter::with().unit(NoUnit::percent()).multiplier(Multiplier::powerOfTen(2)), + u"percent scale/1E2", + NumberFormatter::with().unit(NoUnit::percent()).scale(Scale::powerOfTen(2)), Locale::getEnglish(), 0.5, u"50%"); assertFormatSingle( u"Negative Multiplier", - u"multiply/-5.2", - NumberFormatter::with().multiplier(Multiplier::arbitraryDouble(-5.2)), + u"scale/-5.2", + NumberFormatter::with().scale(Scale::byDouble(-5.2)), Locale::getEnglish(), 2, u"-10.4"); assertFormatSingle( u"Negative One Multiplier", - u"multiply/-1", - NumberFormatter::with().multiplier(Multiplier::arbitraryDouble(-1)), + u"scale/-1", + NumberFormatter::with().scale(Scale::byDouble(-1)), Locale::getEnglish(), 444444, u"-444,444"); + + assertFormatSingle( + u"Two-Type Multiplier with Overlap", + u"scale/10000", + NumberFormatter::with().scale(Scale::byDoubleAndPowerOfTen(100, 2)), + Locale::getEnglish(), + 2, + u"20,000"); } void NumberFormatterApiTest::locale() { diff --git a/icu4c/source/test/intltest/numbertest_skeletons.cpp b/icu4c/source/test/intltest/numbertest_skeletons.cpp index 28700b3bf9..33de5c311b 100644 --- a/icu4c/source/test/intltest/numbertest_skeletons.cpp +++ b/icu4c/source/test/intltest/numbertest_skeletons.cpp @@ -90,11 +90,11 @@ void NumberSkeletonTest::validTokens() { u"unit-width-hidden", u"decimal-auto", u"decimal-always", - u"multiply/5.2", - u"multiply/-5.2", - u"multiply/100", - u"multiply/1E2", - u"multiply/1", + u"scale/5.2", + u"scale/-5.2", + u"scale/100", + u"scale/1E2", + u"scale/1", u"latin", u"numbering-system/arab", u"numbering-system/latn", @@ -132,10 +132,10 @@ void NumberSkeletonTest::invalidTokens() { u"round-increment/xxx", u"round-increment/NaN", u"round-increment/0.1.2", - u"multiply/xxx", - u"multiply/NaN", - u"multiply/0.1.2", - u"multiply/français", // non-invariant characters for C++ + u"scale/xxx", + u"scale/NaN", + u"scale/0.1.2", + u"scale/français", // non-invariant characters for C++ u"currency/dummy", u"measure-unit/foo", u"integer-width/xxx", @@ -191,7 +191,7 @@ void NumberSkeletonTest::stemsRequiringOption() { u"currency", u"integer-width", u"numbering-system", - u"multiply"}; + u"scale"}; static const char16_t* suffixes[] = {u"", u"/ceiling", u" scientific", u"/ceiling scientific"}; for (auto& stem : stems) { diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MacroProps.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MacroProps.java index 47448c2bbd..9cb89879e9 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MacroProps.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MacroProps.java @@ -4,7 +4,7 @@ package com.ibm.icu.impl.number; import com.ibm.icu.impl.Utility; import com.ibm.icu.number.IntegerWidth; -import com.ibm.icu.number.Multiplier; +import com.ibm.icu.number.Scale; import com.ibm.icu.number.Notation; import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay; import com.ibm.icu.number.NumberFormatter.SignDisplay; @@ -26,7 +26,7 @@ public class MacroProps implements Cloneable { public UnitWidth unitWidth; public SignDisplay sign; public DecimalSeparatorDisplay decimal; - public Multiplier multiplier; + public Scale scale; public AffixPatternProvider affixProvider; // not in API; for JDK compatibility mode only public PluralRules rules; // not in API; could be made public in the future public Long threshold; // not in API; controls internal self-regulation threshold @@ -63,8 +63,8 @@ public class MacroProps implements Cloneable { decimal = fallback.decimal; if (affixProvider == null) affixProvider = fallback.affixProvider; - if (multiplier == null) - multiplier = fallback.multiplier; + if (scale == null) + scale = fallback.scale; if (rules == null) rules = fallback.rules; if (loc == null) @@ -85,7 +85,7 @@ public class MacroProps implements Cloneable { sign, decimal, affixProvider, - multiplier, + scale, rules, loc); } @@ -111,7 +111,7 @@ public class MacroProps implements Cloneable { && Utility.equals(sign, other.sign) && Utility.equals(decimal, other.decimal) && Utility.equals(affixProvider, other.affixProvider) - && Utility.equals(multiplier, other.multiplier) + && Utility.equals(scale, other.scale) && Utility.equals(rules, other.rules) && Utility.equals(loc, other.loc); } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MultiplierFormatHandler.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MultiplierFormatHandler.java index fb7389a631..d75cbab45f 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MultiplierFormatHandler.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MultiplierFormatHandler.java @@ -2,16 +2,16 @@ // License & terms of use: http://www.unicode.org/copyright.html#License package com.ibm.icu.impl.number; -import com.ibm.icu.number.Multiplier; +import com.ibm.icu.number.Scale; /** - * Wraps a {@link Multiplier} for use in the number formatting pipeline. + * Wraps a {@link Scale} for use in the number formatting pipeline. */ public class MultiplierFormatHandler implements MicroPropsGenerator { - final Multiplier multiplier; + final Scale multiplier; final MicroPropsGenerator parent; - public MultiplierFormatHandler(Multiplier multiplier, MicroPropsGenerator parent) { + public MultiplierFormatHandler(Scale multiplier, MicroPropsGenerator parent) { this.multiplier = multiplier; this.parent = parent; } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/RoundingUtils.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/RoundingUtils.java index 7d9ca686c3..5921f60ff2 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/RoundingUtils.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/RoundingUtils.java @@ -6,7 +6,7 @@ import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; -import com.ibm.icu.number.Multiplier; +import com.ibm.icu.number.Scale; /** @author sffc */ public class RoundingUtils { @@ -209,12 +209,12 @@ public class RoundingUtils { return MATH_CONTEXT_BY_ROUNDING_MODE_UNLIMITED[roundingMode.ordinal()]; } - public static Multiplier multiplierFromProperties(DecimalFormatProperties properties) { + public static Scale scaleFromProperties(DecimalFormatProperties properties) { MathContext mc = getMathContextOr34Digits(properties); if (properties.getMagnitudeMultiplier() != 0) { - return Multiplier.powerOfTen(properties.getMagnitudeMultiplier()).withMathContext(mc); + return Scale.powerOfTen(properties.getMagnitudeMultiplier()).withMathContext(mc); } else if (properties.getMultiplier() != null) { - return Multiplier.arbitrary(properties.getMultiplier()).withMathContext(mc); + return Scale.byBigDecimal(properties.getMultiplier()).withMathContext(mc); } else { return null; } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/MultiplierParseHandler.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/MultiplierParseHandler.java index 054cdf41c4..f0a98aea51 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/MultiplierParseHandler.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/MultiplierParseHandler.java @@ -2,16 +2,16 @@ // License & terms of use: http://www.unicode.org/copyright.html#License package com.ibm.icu.impl.number.parse; -import com.ibm.icu.number.Multiplier; +import com.ibm.icu.number.Scale; /** - * Wraps a {@link Multiplier} for use in the number parsing pipeline. + * Wraps a {@link Scale} for use in the number parsing pipeline. */ public class MultiplierParseHandler extends ValidationMatcher { - private final Multiplier multiplier; + private final Scale multiplier; - public MultiplierParseHandler(Multiplier multiplier) { + public MultiplierParseHandler(Scale multiplier) { this.multiplier = multiplier; } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java index 0e964ef576..19dd535fa2 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java @@ -19,7 +19,7 @@ import com.ibm.icu.impl.number.PatternStringParser; import com.ibm.icu.impl.number.PatternStringParser.ParsedPatternInfo; import com.ibm.icu.impl.number.PropertiesAffixPatternProvider; import com.ibm.icu.impl.number.RoundingUtils; -import com.ibm.icu.number.Multiplier; +import com.ibm.icu.number.Scale; import com.ibm.icu.number.NumberFormatter.GroupingStrategy; import com.ibm.icu.text.DecimalFormatSymbols; import com.ibm.icu.util.Currency; @@ -250,7 +250,7 @@ public class NumberParserImpl { parser.addMatcher(RequireDecimalSeparatorValidator.getInstance(patternHasDecimalSeparator)); } // The multiplier takes care of scaling percentages. - Multiplier multiplier = RoundingUtils.multiplierFromProperties(properties); + Scale multiplier = RoundingUtils.scaleFromProperties(properties); if (multiplier != null) { parser.addMatcher(new MultiplierParseHandler(multiplier)); } 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 91536e9beb..7fbce25a77 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 @@ -197,9 +197,9 @@ class NumberFormatterImpl { /// START POPULATING THE DEFAULT MICROPROPS AND BUILDING THE MICROPROPS GENERATOR /// ///////////////////////////////////////////////////////////////////////////////////// - // Multiplier (compatibility mode value). - if (macros.multiplier != null) { - chain = new MultiplierFormatHandler(macros.multiplier, chain); + // Multiplier + if (macros.scale != null) { + chain = new MultiplierFormatHandler(macros.scale, chain); } // Rounding strategy diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java index 5fb86d5a10..70cf69389c 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java @@ -39,7 +39,7 @@ public abstract class NumberFormatterSettings *

  • Multiply by 100: useful for percentages. @@ -452,24 +452,24 @@ public abstract class NumberFormatterSettings * *

    - * Pass an element from a {@link Multiplier} factory method to this setter. For example: + * Pass an element from a {@link Scale} factory method to this setter. For example: * *

    -     * NumberFormatter.with().multiplier(Multiplier.powerOfTen(2))
    +     * NumberFormatter.with().scale(Scale.powerOfTen(2))
          * 
    * *

    * The default is to not apply any multiplier. * - * @param multiplier + * @param scale * An amount to be multiplied against numbers before formatting. * @return The fluent chain - * @see Multiplier + * @see Scale * @draft ICU 62 * @provisional This API might change or be removed in a future release. */ - public T multiplier(Multiplier multiplier) { - return create(KEY_MULTIPLIER, multiplier); + public T scale(Scale scale) { + return create(KEY_SCALE, scale); } /** @@ -599,9 +599,9 @@ public abstract class NumberFormatterSettings Date: Sat, 14 Apr 2018 09:49:12 +0000 Subject: [PATCH 099/129] ICU-13700 Adding DecimalFormat "scale" getter/setter and implementation in the new number formatting pipeline. X-SVN-Rev: 41232 --- icu4c/source/i18n/decimfmt.cpp | 18 ++++++++++++++++-- icu4c/source/i18n/number_decimfmtprops.cpp | 2 ++ icu4c/source/i18n/number_decimfmtprops.h | 1 + icu4c/source/i18n/unicode/decimfmt.h | 22 ++++++++++++++++++++++ 4 files changed, 41 insertions(+), 2 deletions(-) diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp index 8ca75eb10a..f95b811231 100644 --- a/icu4c/source/i18n/decimfmt.cpp +++ b/icu4c/source/i18n/decimfmt.cpp @@ -166,6 +166,10 @@ DecimalFormat::setAttribute(UNumberFormatAttribute attr, int32_t newValue, UErro setMultiplier(newValue); break; + case UNUM_SCALE: + setScale(newValue); + break; + case UNUM_GROUPING_SIZE: setGroupingSize(newValue); break; @@ -221,7 +225,6 @@ DecimalFormat::setAttribute(UNumberFormatAttribute attr, int32_t newValue, UErro status = U_UNSUPPORTED_ERROR; break; } - // TODO: UNUM_SCALE? // TODO: UNUM_FORMAT_FAIL_IF_MORE_THAN_MAX_DIGITS? return *this; } @@ -273,6 +276,9 @@ int32_t DecimalFormat::getAttribute(UNumberFormatAttribute attr, UErrorCode& sta case UNUM_MULTIPLIER: return getMultiplier(); + case UNUM_SCALE: + return getScale(); + case UNUM_GROUPING_SIZE: return getGroupingSize(); @@ -311,7 +317,6 @@ int32_t DecimalFormat::getAttribute(UNumberFormatAttribute attr, UErrorCode& sta break; } // TODO: UNUM_FORMAT_FAIL_IF_MORE_THAN_MAX_DIGITS? - // TODO: UNUM_SCALE? return -1; /* undefined */ } @@ -657,6 +662,15 @@ void DecimalFormat::setMultiplier(int32_t multiplier) { refreshFormatterNoError(); } +int32_t DecimalFormat::getScale() const { + return fProperties->scaleMultiplier; +} + +void DecimalFormat::setScale(int32_t newValue) { + fProperties->scaleMultiplier = newValue; + refreshFormatterNoError(); +} + double DecimalFormat::getRoundingIncrement(void) const { return fExportedProperties->roundingIncrement; } diff --git a/icu4c/source/i18n/number_decimfmtprops.cpp b/icu4c/source/i18n/number_decimfmtprops.cpp index f7d486d699..412a04d8f3 100644 --- a/icu4c/source/i18n/number_decimfmtprops.cpp +++ b/icu4c/source/i18n/number_decimfmtprops.cpp @@ -54,6 +54,7 @@ void DecimalFormatProperties::clear() { positiveSuffixPattern.setToBogus(); roundingIncrement = 0.0; roundingMode.nullify(); + scaleMultiplier = 0; secondaryGroupingSize = -1; signAlwaysShown = false; } @@ -98,6 +99,7 @@ bool DecimalFormatProperties::operator==(const DecimalFormatProperties &other) c eq = eq && positiveSuffixPattern == other.positiveSuffixPattern; eq = eq && roundingIncrement == other.roundingIncrement; eq = eq && roundingMode == other.roundingMode; + eq = eq && scaleMultiplier == other.scaleMultiplier; eq = eq && secondaryGroupingSize == other.secondaryGroupingSize; eq = eq && signAlwaysShown == other.signAlwaysShown; return eq; diff --git a/icu4c/source/i18n/number_decimfmtprops.h b/icu4c/source/i18n/number_decimfmtprops.h index 4ad3225ba3..2cac011c7d 100644 --- a/icu4c/source/i18n/number_decimfmtprops.h +++ b/icu4c/source/i18n/number_decimfmtprops.h @@ -128,6 +128,7 @@ struct U_I18N_API DecimalFormatProperties { UnicodeString positiveSuffixPattern; double roundingIncrement; NullableValue roundingMode; + int32_t scaleMultiplier; int32_t secondaryGroupingSize; bool signAlwaysShown; diff --git a/icu4c/source/i18n/unicode/decimfmt.h b/icu4c/source/i18n/unicode/decimfmt.h index ee6db91d1c..1ce1ea9398 100644 --- a/icu4c/source/i18n/unicode/decimfmt.h +++ b/icu4c/source/i18n/unicode/decimfmt.h @@ -1320,6 +1320,7 @@ class U_I18N_API DecimalFormat : public NumberFormat { * For a percentage, set the suffixes to have "%" and the multiplier to be 100. * (For Arabic, use arabic percent symbol). * For a permill, set the suffixes to have "\\u2031" and the multiplier to be 1000. + * It is possible to set both via setMultiplier() as via setScale() simultaneously. * * @param newValue the new value of the multiplier for use in percent, permill, etc. * Examples: with 100, 1.23 -> "123", and "123" -> 1.23 @@ -1327,6 +1328,27 @@ class U_I18N_API DecimalFormat : public NumberFormat { */ virtual void setMultiplier(int32_t newValue); + /** + * Gets a multiplier for the given power of ten. + * For example, scale of 2 corresponds to a multiplier of 100. + * + * @return the multiplier for use in percent, permill, etc. + * Examples: with 100, 1.23 -> "123", and "123" -> 1.23 + * @draft ICU 62 + */ + int32_t getScale(void) const; + + /** + * Sets a multiplier for the given power of ten. + * For example, scale of 2 corresponds to a multiplier of 100. + * It is possible to set both via setMultiplier() as via setScale() simultaneously. + * + * @param newValue the new value of the multiplier for use in percent, permill, etc. + * Examples: with 100, 1.23 -> "123", and "123" -> 1.23 + * @draft ICU 62 + */ + virtual void setScale(int32_t newValue); + /** * Get the rounding increment. * @return A positive rounding increment, or 0.0 if a custom rounding From f84f0b726e667967be09f591d71a6f02458e5cb7 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Sat, 14 Apr 2018 10:38:59 +0000 Subject: [PATCH 100/129] ICU-13634 Cleanup of dcfmapts.cpp and minor code behavior changes. X-SVN-Rev: 41233 --- icu4c/source/test/intltest/dcfmapts.cpp | 26 +++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/icu4c/source/test/intltest/dcfmapts.cpp b/icu4c/source/test/intltest/dcfmapts.cpp index b1bde0121e..48f6a9e2e9 100644 --- a/icu4c/source/test/intltest/dcfmapts.cpp +++ b/icu4c/source/test/intltest/dcfmapts.cpp @@ -367,6 +367,7 @@ void IntlTestDecimalFormatAPI::testAPI(/*char *par*/) // ======= Test applyPattern() logln((UnicodeString)"Testing applyPattern()"); + pat = DecimalFormat(status); // reset UnicodeString p1("#,##0.0#;(#,##0.0#)"); logln((UnicodeString)"Applying pattern " + p1); @@ -378,9 +379,7 @@ void IntlTestDecimalFormatAPI::testAPI(/*char *par*/) UnicodeString s2; s2 = pat.toPattern(s2); logln((UnicodeString)"Extracted pattern is " + s2); - if(s2 != p1) { - errln((UnicodeString)"ERROR: toPattern() result did not match pattern applied"); - } + assertEquals("toPattern() result did not match pattern applied", p1, s2); if(pat.getSecondaryGroupingSize() != 0) { errln("FAIL: Secondary Grouping Size should be 0, not %d\n", pat.getSecondaryGroupingSize()); @@ -400,9 +399,7 @@ void IntlTestDecimalFormatAPI::testAPI(/*char *par*/) UnicodeString s3; s3 = pat.toLocalizedPattern(s3); logln((UnicodeString)"Extracted pattern is " + s3); - if(s3 != p2) { - errln((UnicodeString)"ERROR: toLocalizedPattern() result did not match pattern applied"); - } + assertEquals("toLocalizedPattern() result did not match pattern applied", p2, s3); status = U_ZERO_ERROR; UParseError pe; @@ -413,9 +410,7 @@ void IntlTestDecimalFormatAPI::testAPI(/*char *par*/) UnicodeString s4; s4 = pat.toLocalizedPattern(s3); logln((UnicodeString)"Extracted pattern is " + s4); - if(s4 != p2) { - errln((UnicodeString)"ERROR: toLocalizedPattern(with ParseErr) result did not match pattern applied"); - } + assertEquals("toLocalizedPattern(with ParseErr) result did not match pattern applied", p2, s4); if(pat.getSecondaryGroupingSize() != 2) { errln("FAIL: Secondary Grouping Size should be 2, not %d\n", pat.getSecondaryGroupingSize()); @@ -519,14 +514,14 @@ void IntlTestDecimalFormatAPI::testRounding(/*char *par*/) //for +2.55 with RoundingIncrement=1.0 pat.setRoundingIncrement(1.0); pat.format(Roundingnumber, resultStr); - message= (UnicodeString)"Round() failed: round(" + (double)Roundingnumber + UnicodeString(",") + mode + UnicodeString(",FALSE) with RoundingIncrement=1.0==>"); + message= (UnicodeString)"round(" + (double)Roundingnumber + UnicodeString(",") + mode + UnicodeString(",FALSE) with RoundingIncrement=1.0==>"); verify(message, resultStr, result[i++]); message.remove(); resultStr.remove(); //for -2.55 with RoundingIncrement=1.0 pat.format(Roundingnumber1, resultStr); - message= (UnicodeString)"Round() failed: round(" + (double)Roundingnumber1 + UnicodeString(",") + mode + UnicodeString(",FALSE) with RoundingIncrement=1.0==>"); + message= (UnicodeString)"round(" + (double)Roundingnumber1 + UnicodeString(",") + mode + UnicodeString(",FALSE) with RoundingIncrement=1.0==>"); verify(message, resultStr, result[i++]); message.remove(); resultStr.remove(); @@ -614,7 +609,14 @@ void IntlTestDecimalFormatAPI::TestScale() if ( i > 2 ) { pat.applyPattern(percentPattern,status); } - pat.setAttribute(UNUM_SCALE,testData[i].inputScale,status); + // Test both the attribute and the setter + if (i % 2 == 0) { + pat.setAttribute(UNUM_SCALE, testData[i].inputScale,status); + assertEquals("", testData[i].inputScale, pat.getScale()); + } else { + pat.setScale(testData[i].inputScale); + assertEquals("", testData[i].inputScale, pat.getAttribute(UNUM_SCALE, status)); + } pat.format(testData[i].inputValue, resultStr); message = UnicodeString("Unexpected output for ") + testData[i].inputValue + UnicodeString(" and scale ") + testData[i].inputScale + UnicodeString(". Got: "); From d6c6fa0404aa95257bbf6a01147953016257238e Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Tue, 17 Apr 2018 01:36:18 +0000 Subject: [PATCH 101/129] ICU-13634 DecimalQuantity work: Fixing integer overflow behavior in toLong and toFractionLong methods. Adding test for maxInt/maxFrac behavior in toScientificString and related methods. Updating a few test expectations in IntlTestDecimalFormatAPI::TestFixedDecimal, which is now passing. X-SVN-Rev: 41235 --- icu4c/source/i18n/number_decimalquantity.cpp | 77 ++++++++++--------- icu4c/source/i18n/number_decimalquantity.h | 9 ++- icu4c/source/i18n/plurrule.cpp | 10 ++- icu4c/source/i18n/plurrule_impl.h | 8 +- icu4c/source/test/intltest/dcfmapts.cpp | 10 +-- icu4c/source/test/intltest/numbertest.h | 1 + .../intltest/numbertest_decimalquantity.cpp | 27 ++++++- .../number/DecimalQuantity_AbstractBCD.java | 74 +++++++++--------- .../icu/impl/number/parse/ParsedNumber.java | 2 +- .../dev/test/number/DecimalQuantityTest.java | 16 ++++ .../dev/test/number/ExhaustiveNumberTest.java | 1 - .../src/com/ibm/icu/dev/test/TestFmwk.java | 3 + 12 files changed, 147 insertions(+), 91 deletions(-) diff --git a/icu4c/source/i18n/number_decimalquantity.cpp b/icu4c/source/i18n/number_decimalquantity.cpp index c61cd82e32..0fa6dada97 100644 --- a/icu4c/source/i18n/number_decimalquantity.cpp +++ b/icu4c/source/i18n/number_decimalquantity.cpp @@ -168,8 +168,9 @@ uint64_t DecimalQuantity::getPositionFingerprint() const { void DecimalQuantity::roundToIncrement(double roundingIncrement, RoundingMode roundingMode, int32_t maxFrac, UErrorCode& status) { - // TODO: This is innefficient. Improve? - // TODO: Should we convert to decNumber instead? + // TODO(13701): This is innefficient. Improve? + // TODO(13701): Should we convert to decNumber instead? + roundToInfinity(); double temp = toDouble(); temp /= roundingIncrement; setToDouble(temp); @@ -246,7 +247,7 @@ double DecimalQuantity::getPluralOperand(PluralOperand operand) const { switch (operand) { case PLURAL_OPERAND_I: // Invert the negative sign if necessary - return static_cast(isNegative() ? -toLong() : toLong()); + return static_cast(isNegative() ? -toLong(true) : toLong(true)); case PLURAL_OPERAND_F: return static_cast(toFractionLong(true)); case PLURAL_OPERAND_T: @@ -260,6 +261,10 @@ double DecimalQuantity::getPluralOperand(PluralOperand operand) const { } } +bool DecimalQuantity::hasIntegerValue() const { + return scale >= 0; +} + int32_t DecimalQuantity::getUpperDisplayMagnitude() const { // If this assertion fails, you need to call roundToInfinity() or some other rounding method. // See the comment in the header file explaining the "isApproximate" field. @@ -489,11 +494,17 @@ void DecimalQuantity::_setToDecNum(const DecNum& decnum, UErrorCode& status) { } } -int64_t DecimalQuantity::toLong() const { +int64_t DecimalQuantity::toLong(bool truncateIfOverflow) const { // NOTE: Call sites should be guarded by fitsInLong(), like this: // if (dq.fitsInLong()) { /* use dq.toLong() */ } else { /* use some fallback */ } + // Fallback behavior upon truncateIfOverflow is to truncate at 17 digits. + U_ASSERT(truncateIfOverflow || fitsInLong()); int64_t result = 0L; - for (int32_t magnitude = scale + precision - 1; magnitude >= 0; magnitude--) { + int32_t upperMagnitude = std::min(scale + precision, lOptPos) - 1; + if (truncateIfOverflow) { + upperMagnitude = std::min(upperMagnitude, 17); + } + for (int32_t magnitude = upperMagnitude; magnitude >= 0; magnitude--) { result = result * 10 + getDigitPos(magnitude - scale); } if (isNegative()) { @@ -502,13 +513,22 @@ int64_t DecimalQuantity::toLong() const { return result; } -int64_t DecimalQuantity::toFractionLong(bool includeTrailingZeros) const { - int64_t result = 0L; +uint64_t DecimalQuantity::toFractionLong(bool includeTrailingZeros) const { + uint64_t result = 0L; int32_t magnitude = -1; - for (; (magnitude >= scale || (includeTrailingZeros && magnitude >= rReqPos)) && - magnitude >= rOptPos; magnitude--) { + int32_t lowerMagnitude = std::max(scale, rOptPos); + if (includeTrailingZeros) { + lowerMagnitude = std::min(lowerMagnitude, rReqPos); + } + for (; magnitude >= lowerMagnitude && result <= 1e18L; magnitude--) { result = result * 10 + getDigitPos(magnitude - scale); } + // Remove trailing zeros; this can happen during integer overflow cases. + if (!includeTrailingZeros) { + while (result > 0 && (result % 10) == 0) { + result /= 10; + } + } return result; } @@ -542,9 +562,9 @@ bool DecimalQuantity::fitsInLong() const { } double DecimalQuantity::toDouble() const { - if (isApproximate) { - return toDoubleFromOriginal(); - } + // If this assertion fails, you need to call roundToInfinity() or some other rounding method. + // See the comment in the header file explaining the "isApproximate" field. + U_ASSERT(!isApproximate); if (isNaN()) { return NAN; @@ -562,24 +582,6 @@ double DecimalQuantity::toDouble() const { &count); } -double DecimalQuantity::toDoubleFromOriginal() const { - double result = origDouble; - int32_t delta = origDelta; - if (delta >= 0) { - // 1e22 is the largest exact double. - for (; delta >= 22; delta -= 22) result *= 1e22; - result *= DOUBLE_MULTIPLIERS[delta]; - } else { - // 1e22 is the largest exact double. - for (; delta <= -22; delta += 22) result /= 1e22; - result /= DOUBLE_MULTIPLIERS[-delta]; - } - if (isNegative()) { - result = -result; - } - return result; -} - void DecimalQuantity::toDecNum(DecNum& output, UErrorCode& status) const { // Special handling for zero if (precision == 0) { @@ -795,15 +797,20 @@ UnicodeString DecimalQuantity::toScientificString() const { result.append(u"0E+0", -1); return result; } - result.append(u'0' + getDigitPos(precision - 1)); - if (precision > 1) { + // NOTE: It is not safe to add to lOptPos (aka maxInt) or subtract from + // rOptPos (aka -maxFrac) due to overflow. + int32_t upperPos = std::min(precision + scale, lOptPos) - scale - 1; + int32_t lowerPos = std::max(scale, rOptPos) - scale; + int32_t p = upperPos; + result.append(u'0' + getDigitPos(p)); + if ((--p) >= lowerPos) { result.append(u'.'); - for (int32_t i = 1; i < precision; i++) { - result.append(u'0' + getDigitPos(precision - i - 1)); + for (; p >= lowerPos; p--) { + result.append(u'0' + getDigitPos(p)); } } result.append(u'E'); - int32_t _scale = scale + precision - 1; + int32_t _scale = upperPos + scale; if (_scale < 0) { _scale *= -1; result.append(u'-'); diff --git a/icu4c/source/i18n/number_decimalquantity.h b/icu4c/source/i18n/number_decimalquantity.h index 78bcdfc0b3..f440544a7b 100644 --- a/icu4c/source/i18n/number_decimalquantity.h +++ b/icu4c/source/i18n/number_decimalquantity.h @@ -143,9 +143,10 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { /** @return Whether the value represented by this {@link DecimalQuantity} is not a number. */ bool isNaN() const U_OVERRIDE; - int64_t toLong() const; + /** @param truncateIfOverflow if false and the number does NOT fit, fails with an assertion error. */ + int64_t toLong(bool truncateIfOverflow = false) const; - int64_t toFractionLong(bool includeTrailingZeros) const; + uint64_t toFractionLong(bool includeTrailingZeros) const; /** * Returns whether or not a Long can fully represent the value stored in this DecimalQuantity. @@ -200,6 +201,8 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { double getPluralOperand(PluralOperand operand) const U_OVERRIDE; + bool hasIntegerValue() const U_OVERRIDE; + /** * Gets the digit at the specified magnitude. For example, if the represented number is 12.3, * getDigit(-1) returns 3, since 3 is the digit corresponding to 10^-1. @@ -466,8 +469,6 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { void convertToAccurateDouble(); - double toDoubleFromOriginal() const; - /** Ensure that a byte array of at least 40 digits is allocated. */ void ensureCapacity(); diff --git a/icu4c/source/i18n/plurrule.cpp b/icu4c/source/i18n/plurrule.cpp index 1b0089f9c9..04e4d825f6 100644 --- a/icu4c/source/i18n/plurrule.cpp +++ b/icu4c/source/i18n/plurrule.cpp @@ -1480,7 +1480,7 @@ FixedDecimal::FixedDecimal(const FixedDecimal &other) { decimalDigits = other.decimalDigits; decimalDigitsWithoutTrailingZeros = other.decimalDigitsWithoutTrailingZeros; intValue = other.intValue; - hasIntegerValue = other.hasIntegerValue; + _hasIntegerValue = other._hasIntegerValue; isNegative = other.isNegative; _isNaN = other._isNaN; _isInfinite = other._isInfinite; @@ -1504,10 +1504,10 @@ void FixedDecimal::init(double n, int32_t v, int64_t f) { v = 0; f = 0; intValue = 0; - hasIntegerValue = FALSE; + _hasIntegerValue = FALSE; } else { intValue = (int64_t)source; - hasIntegerValue = (source == intValue); + _hasIntegerValue = (source == intValue); } visibleDecimalDigitCount = v; @@ -1641,6 +1641,10 @@ bool FixedDecimal::isInfinite() const { return _isInfinite; } +bool FixedDecimal::hasIntegerValue() const { + return _hasIntegerValue; +} + bool FixedDecimal::isNanOrInfinity() const { return _isNaN || _isInfinite; } diff --git a/icu4c/source/i18n/plurrule_impl.h b/icu4c/source/i18n/plurrule_impl.h index c85c922f15..a0d3f4a564 100644 --- a/icu4c/source/i18n/plurrule_impl.h +++ b/icu4c/source/i18n/plurrule_impl.h @@ -249,9 +249,8 @@ class U_I18N_API IFixedDecimal { virtual bool isInfinite() const = 0; - virtual bool hasIntegerValue() { - return getPluralOperand(PLURAL_OPERAND_N) == getPluralOperand(PLURAL_OPERAND_I); - } + /** Whether the number has no nonzero fraction digits. */ + virtual bool hasIntegerValue() const = 0; }; /** @@ -279,6 +278,7 @@ class U_I18N_API FixedDecimal: public IFixedDecimal, public UObject { double getPluralOperand(PluralOperand operand) const U_OVERRIDE; bool isNaN() const U_OVERRIDE; bool isInfinite() const U_OVERRIDE; + bool hasIntegerValue() const U_OVERRIDE; bool isNanOrInfinity() const; // used in decimfmtimpl.cpp @@ -297,7 +297,7 @@ class U_I18N_API FixedDecimal: public IFixedDecimal, public UObject { int64_t decimalDigits; int64_t decimalDigitsWithoutTrailingZeros; int64_t intValue; - UBool hasIntegerValue; + UBool _hasIntegerValue; UBool isNegative; UBool _isNaN; UBool _isInfinite; diff --git a/icu4c/source/test/intltest/dcfmapts.cpp b/icu4c/source/test/intltest/dcfmapts.cpp index 48f6a9e2e9..96d297074f 100644 --- a/icu4c/source/test/intltest/dcfmapts.cpp +++ b/icu4c/source/test/intltest/dcfmapts.cpp @@ -825,12 +825,12 @@ void IntlTestDecimalFormatAPI::TestFixedDecimal() { ASSERT_EQUAL(FALSE, fd.hasIntegerValue()); ASSERT_EQUAL(FALSE, fd.isNegative()); - fable.setDecimalNumber("12.345678901234567890123456789", status); + fable.setDecimalNumber("12.3456789012345678900123456789", status); TEST_ASSERT_STATUS(status); df->formatToDecimalQuantity(fable, fd, status); TEST_ASSERT_STATUS(status); ASSERT_EQUAL(22, fd.getPluralOperand(PLURAL_OPERAND_V)); - ASSERT_EQUAL(345678901234567890LL, fd.getPluralOperand(PLURAL_OPERAND_F)); + ASSERT_EQUAL(3456789012345678900LL, fd.getPluralOperand(PLURAL_OPERAND_F)); ASSERT_EQUAL(34567890123456789LL, fd.getPluralOperand(PLURAL_OPERAND_T)); ASSERT_EQUAL(12, fd.getPluralOperand(PLURAL_OPERAND_I)); ASSERT_EQUAL(FALSE, fd.hasIntegerValue()); @@ -842,8 +842,8 @@ void IntlTestDecimalFormatAPI::TestFixedDecimal() { df->formatToDecimalQuantity(fable, fd, status); TEST_ASSERT_STATUS(status); ASSERT_EQUAL(22, fd.getPluralOperand(PLURAL_OPERAND_V)); - ASSERT_EQUAL(123456789012345678LL, fd.getPluralOperand(PLURAL_OPERAND_F)); - ASSERT_EQUAL(123456789012345678LL, fd.getPluralOperand(PLURAL_OPERAND_T)); + ASSERT_EQUAL(1234567890123456789LL, fd.getPluralOperand(PLURAL_OPERAND_F)); + ASSERT_EQUAL(1234567890123456789LL, fd.getPluralOperand(PLURAL_OPERAND_T)); ASSERT_EQUAL(345678901234567890LL, fd.getPluralOperand(PLURAL_OPERAND_I)); ASSERT_EQUAL(FALSE, fd.hasIntegerValue()); ASSERT_EQUAL(FALSE, fd.isNegative()); @@ -892,7 +892,7 @@ void IntlTestDecimalFormatAPI::TestFixedDecimal() { ASSERT_EQUAL(2, fd.getPluralOperand(PLURAL_OPERAND_V)); ASSERT_EQUAL(30, fd.getPluralOperand(PLURAL_OPERAND_F)); ASSERT_EQUAL(3, fd.getPluralOperand(PLURAL_OPERAND_T)); - ASSERT_EQUAL(100000000000000000LL, fd.getPluralOperand(PLURAL_OPERAND_I)); + ASSERT_EQUAL(0, fd.getPluralOperand(PLURAL_OPERAND_I)); ASSERT_EQUAL(FALSE, fd.hasIntegerValue()); ASSERT_EQUAL(FALSE, fd.isNegative()); diff --git a/icu4c/source/test/intltest/numbertest.h b/icu4c/source/test/intltest/numbertest.h index fdb892a0fc..594788287b 100644 --- a/icu4c/source/test/intltest/numbertest.h +++ b/icu4c/source/test/intltest/numbertest.h @@ -122,6 +122,7 @@ class DecimalQuantityTest : public IntlTest { void testUseApproximateDoubleWhenAble(); void testHardDoubleConversion(); void testToDouble(); + void testMaxDigits(); void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0); diff --git a/icu4c/source/test/intltest/numbertest_decimalquantity.cpp b/icu4c/source/test/intltest/numbertest_decimalquantity.cpp index 2a19b0b908..fdd9124f0c 100644 --- a/icu4c/source/test/intltest/numbertest_decimalquantity.cpp +++ b/icu4c/source/test/intltest/numbertest_decimalquantity.cpp @@ -8,6 +8,7 @@ #include "number_decimalquantity.h" #include "math.h" #include +#include "number_utils.h" #include "numbertest.h" void DecimalQuantityTest::runIndexedTest(int32_t index, UBool exec, const char *&name, char *) { @@ -23,6 +24,7 @@ void DecimalQuantityTest::runIndexedTest(int32_t index, UBool exec, const char * TESTCASE_AUTO(testUseApproximateDoubleWhenAble); TESTCASE_AUTO(testHardDoubleConversion); TESTCASE_AUTO(testToDouble); + TESTCASE_AUTO(testMaxDigits); TESTCASE_AUTO_END; } @@ -60,9 +62,6 @@ void DecimalQuantityTest::checkDoubleBehavior(double d, bool explicitRequired) { assertTrue("Should be using approximate double", !fq.isExplicitExactDouble()); } UnicodeString baseStr = fq.toString(); - assertDoubleEquals( - UnicodeString(u"Initial construction from hard double: ") + baseStr, - d, fq.toDouble()); fq.roundToInfinity(); UnicodeString newStr = fq.toString(); if (explicitRequired) { @@ -358,4 +357,26 @@ void DecimalQuantityTest::testToDouble() { } } +void DecimalQuantityTest::testMaxDigits() { + IcuTestErrorCode status(*this, "testMaxDigits"); + DecimalQuantity dq; + dq.setToDouble(876.543); + dq.roundToInfinity(); + dq.setIntegerLength(0, 2); + dq.setFractionLength(0, 2); + assertEquals("Should trim, toPlainString", "76.54", dq.toPlainString()); + assertEquals("Should trim, toScientificString", "7.654E+1", dq.toScientificString()); + assertEquals("Should trim, toLong", 76L, dq.toLong()); + assertEquals("Should trim, toFractionLong", 54L, dq.toFractionLong(false)); + assertEquals("Should trim, toDouble", 76.54, dq.toDouble()); + // To test DecNum output, check the round-trip. + DecNum dn; + dq.toDecNum(dn, status); + DecimalQuantity copy; + copy.setToDecNum(dn, status); + if (!logKnownIssue("13701")) { + assertEquals("Should trim, toDecNum", "76.54", copy.toPlainString()); + } +} + #endif /* #if !UCONFIG_NO_FORMATTING */ 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 7cb9bb1b15..567a17ce0d 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 @@ -242,7 +242,7 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { switch (operand) { case i: // Invert the negative sign if necessary - return isNegative() ? -toLong() : toLong(); + return isNegative() ? -toLong(true) : toLong(true); case f: return toFractionLong(true); case t: @@ -572,11 +572,20 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { * Returns a long approximating the internal BCD. A long can only represent the integral part of the * number. * + * @param truncateIfOverflow if false and the number does NOT fit, fails with an assertion error. * @return A 64-bit integer representation of the internal BCD. */ - public long toLong() { + public long toLong(boolean truncateIfOverflow) { + // NOTE: Call sites should be guarded by fitsInLong(), like this: + // if (dq.fitsInLong()) { /* use dq.toLong() */ } else { /* use some fallback */ } + // Fallback behavior upon truncateIfOverflow is to truncate at 17 digits. + assert(truncateIfOverflow || fitsInLong()); long result = 0L; - for (int magnitude = scale + precision - 1; magnitude >= 0; magnitude--) { + int upperMagnitude = Math.min(scale + precision, lOptPos) - 1; + if (truncateIfOverflow) { + upperMagnitude = Math.min(upperMagnitude, 17); + } + for (int magnitude = upperMagnitude; magnitude >= 0; magnitude--) { result = result * 10 + getDigitPos(magnitude - scale); } if (isNegative()) { @@ -593,10 +602,20 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { public long toFractionLong(boolean includeTrailingZeros) { long result = 0L; int magnitude = -1; - for (; (magnitude >= scale || (includeTrailingZeros && magnitude >= rReqPos)) - && magnitude >= rOptPos; magnitude--) { + int lowerMagnitude = Math.max(scale, rOptPos); + if (includeTrailingZeros) { + lowerMagnitude = Math.min(lowerMagnitude, rReqPos); + } + // NOTE: Java has only signed longs, so we check result <= 1e17 instead of 1e18 + for (; magnitude >= lowerMagnitude && result <= 1e17; magnitude--) { result = result * 10 + getDigitPos(magnitude - scale); } + // Remove trailing zeros; this can happen during integer overflow cases. + if (!includeTrailingZeros) { + while (result > 0 && (result % 10) == 0) { + result /= 10; + } + } return result; } @@ -641,9 +660,9 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { */ @Override public double toDouble() { - if (isApproximate) { - return toDoubleFromOriginal(); - } + // If this assertion fails, you need to call roundToInfinity() or some other rounding method. + // See the comment at the top of this file explaining the "isApproximate" field. + assert !isApproximate; if (isNaN()) { return Double.NaN; @@ -652,7 +671,7 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { } // TODO: Do like in C++ and use a library function to perform this conversion? - // This code is not as not in Java because .parse() returns a BigDecimal, not a double. + // This code is not as hot in Java because .parse() returns a BigDecimal, not a double. long tempLong = 0L; int lostDigits = precision - Math.min(precision, 17); @@ -701,26 +720,6 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { return bcdToBigDecimal(); } - protected double toDoubleFromOriginal() { - double result = origDouble; - int delta = origDelta; - if (delta >= 0) { - // 1e22 is the largest exact double. - for (; delta >= 22; delta -= 22) - result *= 1e22; - result *= DOUBLE_MULTIPLIERS[delta]; - } else { - // 1e22 is the largest exact double. - for (; delta <= -22; delta += 22) - result /= 1e22; - result /= DOUBLE_MULTIPLIERS[-delta]; - } - if (isNegative()) { - result = -result; - } - return result; - } - private static int safeSubtract(int a, int b) { int diff = a - b; if (b < 0 && diff < a) @@ -952,7 +951,7 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { sb.append('0'); } for (int m = getUpperDisplayMagnitude(); m >= getLowerDisplayMagnitude(); m--) { - sb.append(getDigit(m)); + sb.append((char) ('0' + getDigit(m))); if (m == 0) sb.append('.'); } @@ -974,15 +973,20 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { result.append("0E+0"); return; } - result.append((char) ('0' + getDigitPos(precision - 1))); - if (precision > 1) { + // NOTE: It is not safe to add to lOptPos (aka maxInt) or subtract from + // rOptPos (aka -maxFrac) due to overflow. + int upperPos = Math.min(precision + scale, lOptPos) - scale - 1; + int lowerPos = Math.max(scale, rOptPos) - scale; + int p = upperPos; + result.append((char) ('0' + getDigitPos(p))); + if ((--p) >= lowerPos) { result.append('.'); - for (int i = 1; i < precision; i++) { - result.append((char) ('0' + getDigitPos(precision - i - 1))); + for (; p >= lowerPos; p--) { + result.append((char) ('0' + getDigitPos(p))); } } result.append('E'); - int _scale = scale + precision - 1; + int _scale = upperPos + scale; if (_scale < 0) { _scale *= -1; result.append('-'); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java index c6e686e64e..7ad95317b6 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java @@ -155,7 +155,7 @@ public class ParsedNumber { } if (quantity.fitsInLong() && !forceBigDecimal) { - return quantity.toLong(); + return quantity.toLong(false); } else { return quantity.toBigDecimal(); } diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java index 256f548e84..18a5dc32a2 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java @@ -496,6 +496,22 @@ public class DecimalQuantityTest extends TestFmwk { } } + @Test + public void testMaxDigits() { + DecimalQuantity_DualStorageBCD dq = new DecimalQuantity_DualStorageBCD(876.543); + dq.roundToInfinity(); + dq.setIntegerLength(0, 2); + dq.setFractionLength(0, 2); + assertEquals("Should trim, toPlainString", "76.54", dq.toPlainString()); + assertEquals("Should trim, toScientificString", "7.654E+1", dq.toScientificString()); + assertEquals("Should trim, toLong", 76, dq.toLong(true)); + assertEquals("Should trim, toFractionLong", 54, dq.toFractionLong(false)); + if (!logKnownIssue("13701", "consider cleaning up")) { + assertEquals("Should trim, toDouble", 76.54, dq.toDouble()); + assertEquals("Should trim, toBigDecimal", new BigDecimal("76.54"), dq.toBigDecimal()); + } + } + static void assertDoubleEquals(String message, double d1, double d2) { boolean equal = (Math.abs(d1 - d2) < 1e-6) || (Math.abs((d1 - d2) / d1) < 1e-6); handleAssert(equal, message, d1, d2, null, false); diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/ExhaustiveNumberTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/ExhaustiveNumberTest.java index 51dbe11ab8..b924b0525b 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/ExhaustiveNumberTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/ExhaustiveNumberTest.java @@ -170,7 +170,6 @@ public class ExhaustiveNumberTest extends TestFmwk { if (explicitRequired) { assertTrue(alert + "Should be using approximate double", !fq.explicitExactDouble); } - assertEquals(alert + "Initial construction from hard double", d, fq.toDouble()); fq.roundToInfinity(); if (explicitRequired) { assertTrue(alert + "Should not be using approximate double", fq.explicitExactDouble); diff --git a/icu4j/main/tests/framework/src/com/ibm/icu/dev/test/TestFmwk.java b/icu4j/main/tests/framework/src/com/ibm/icu/dev/test/TestFmwk.java index 13f4332ec5..ce0082eddb 100644 --- a/icu4j/main/tests/framework/src/com/ibm/icu/dev/test/TestFmwk.java +++ b/icu4j/main/tests/framework/src/com/ibm/icu/dev/test/TestFmwk.java @@ -169,6 +169,9 @@ abstract public class TestFmwk extends AbstractTestLog { return false; } + // TODO: This method currently does not do very much. + // See http://bugs.icu-project.org/trac/ticket/12589 + StringBuffer descBuf = new StringBuffer(); // TODO(junit) : what to do about this? //getParams().stack.appendPath(descBuf); From 12b34e7c9e79c72dd24adea028960e8df64c0b08 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Tue, 17 Apr 2018 08:05:20 +0000 Subject: [PATCH 102/129] ICU-13634 A variety of mostly minor changes to fix assorted unit test failures in ICU4C plus a few in ICU4J. X-SVN-Rev: 41236 --- icu4c/source/i18n/decimfmt.cpp | 4 +- icu4c/source/i18n/fmtable.cpp | 8 +- icu4c/source/i18n/number_decimalquantity.cpp | 17 +- icu4c/source/i18n/number_decimalquantity.h | 3 +- icu4c/source/i18n/number_decimfmtprops.cpp | 2 +- icu4c/source/i18n/number_decimfmtprops.h | 2 +- icu4c/source/i18n/numparse_decimal.cpp | 6 + icu4c/source/i18n/numparse_impl.cpp | 4 + icu4c/source/i18n/numparse_impl.h | 2 + icu4c/source/i18n/numparse_parsednumber.cpp | 5 +- icu4c/source/i18n/numparse_scientific.cpp | 9 +- icu4c/source/i18n/numparse_types.h | 2 +- .../intltest/numbertest_decimalquantity.cpp | 2 +- .../source/test/intltest/numbertest_parse.cpp | 2 +- icu4c/source/test/intltest/numrgts.cpp | 148 ++++++++++-------- icu4c/source/test/intltest/tmsgfmt.cpp | 7 +- icu4c/source/test/intltest/tsdcfmsy.cpp | 2 +- .../impl/number/DecimalFormatProperties.java | 2 +- .../impl/number/parse/NumberParserImpl.java | 9 +- .../icu/impl/number/parse/ParsedNumber.java | 9 +- .../icu/impl/number/parse/ParsingUtils.java | 1 + .../impl/number/parse/ScientificMatcher.java | 14 +- .../ibm/icu/number/NumberPropertyMapper.java | 3 +- .../src/com/ibm/icu/text/DecimalFormat.java | 4 +- .../format/NumberFormatDataDrivenTest.java | 5 +- .../test/format/NumberRegressionTests.java | 21 +-- .../icu/dev/test/number/NumberParserTest.java | 2 +- 27 files changed, 170 insertions(+), 125 deletions(-) diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp index f95b811231..5a625675c1 100644 --- a/icu4c/source/i18n/decimfmt.cpp +++ b/icu4c/source/i18n/decimfmt.cpp @@ -512,7 +512,7 @@ void DecimalFormat::parse(const UnicodeString& text, Formattable& output, // TODO: Do we need to check for fProperties->parseAllInput (UCONFIG_HAVE_PARSEALLINPUT) here? if (result.success()) { parsePosition.setIndex(result.charEnd); - result.populateFormattable(output); + result.populateFormattable(output, fParser->getParseFlags()); } else { parsePosition.setErrorIndex(startIndex + result.charEnd); } @@ -533,7 +533,7 @@ CurrencyAmount* DecimalFormat::parseCurrency(const UnicodeString& text, ParsePos if (result.success()) { parsePosition.setIndex(result.charEnd); Formattable formattable; - result.populateFormattable(formattable); + result.populateFormattable(formattable, fParser->getParseFlags()); return new CurrencyAmount(formattable, result.currencyCode, status); } else { parsePosition.setErrorIndex(startIndex + result.charEnd); diff --git a/icu4c/source/i18n/fmtable.cpp b/icu4c/source/i18n/fmtable.cpp index 299adfa244..dd465a60e0 100644 --- a/icu4c/source/i18n/fmtable.cpp +++ b/icu4c/source/i18n/fmtable.cpp @@ -462,12 +462,12 @@ Formattable::getInt64(UErrorCode& status) const status = U_INVALID_FORMAT_ERROR; return U_INT64_MIN; } else if (fabs(fValue.fDouble) > U_DOUBLE_MAX_EXACT_INT && fDecimalQuantity != NULL) { - if (fDecimalQuantity->fitsInLong()) { + if (fDecimalQuantity->fitsInLong(true)) { return fDecimalQuantity->toLong(); - } else if (fDecimalQuantity->isNegative()) { - return U_INT64_MIN; } else { - return U_INT64_MAX; + // Unexpected + status = U_INVALID_FORMAT_ERROR; + return fDecimalQuantity->isNegative() ? U_INT64_MIN : U_INT64_MAX; } } else { return (int64_t)fValue.fDouble; diff --git a/icu4c/source/i18n/number_decimalquantity.cpp b/icu4c/source/i18n/number_decimalquantity.cpp index 0fa6dada97..7759702288 100644 --- a/icu4c/source/i18n/number_decimalquantity.cpp +++ b/icu4c/source/i18n/number_decimalquantity.cpp @@ -173,9 +173,11 @@ void DecimalQuantity::roundToIncrement(double roundingIncrement, RoundingMode ro roundToInfinity(); double temp = toDouble(); temp /= roundingIncrement; - setToDouble(temp); - roundToMagnitude(0, roundingMode, status); - temp = toDouble(); + // Use another DecimalQuantity to perform the actual rounding... + DecimalQuantity dq; + dq.setToDouble(temp); + dq.roundToMagnitude(0, roundingMode, status); + temp = dq.toDouble(); temp *= roundingIncrement; setToDouble(temp); // Since we reset the value to a double, we need to specify the rounding boundary @@ -498,8 +500,7 @@ int64_t DecimalQuantity::toLong(bool truncateIfOverflow) const { // NOTE: Call sites should be guarded by fitsInLong(), like this: // if (dq.fitsInLong()) { /* use dq.toLong() */ } else { /* use some fallback */ } // Fallback behavior upon truncateIfOverflow is to truncate at 17 digits. - U_ASSERT(truncateIfOverflow || fitsInLong()); - int64_t result = 0L; + uint64_t result = 0L; int32_t upperMagnitude = std::min(scale + precision, lOptPos) - 1; if (truncateIfOverflow) { upperMagnitude = std::min(upperMagnitude, 17); @@ -508,7 +509,7 @@ int64_t DecimalQuantity::toLong(bool truncateIfOverflow) const { result = result * 10 + getDigitPos(magnitude - scale); } if (isNegative()) { - result = -result; + return -result; } return result; } @@ -532,11 +533,11 @@ uint64_t DecimalQuantity::toFractionLong(bool includeTrailingZeros) const { return result; } -bool DecimalQuantity::fitsInLong() const { +bool DecimalQuantity::fitsInLong(bool ignoreFraction) const { if (isZero()) { return true; } - if (scale < 0) { + if (scale < 0 && !ignoreFraction) { return false; } int magnitude = getMagnitude(); diff --git a/icu4c/source/i18n/number_decimalquantity.h b/icu4c/source/i18n/number_decimalquantity.h index f440544a7b..2a7ea8be44 100644 --- a/icu4c/source/i18n/number_decimalquantity.h +++ b/icu4c/source/i18n/number_decimalquantity.h @@ -150,8 +150,9 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { /** * Returns whether or not a Long can fully represent the value stored in this DecimalQuantity. + * @param ignoreFraction if true, silently ignore digits after the decimal place. */ - bool fitsInLong() const; + bool fitsInLong(bool ignoreFraction = false) const; /** @return The value contained in this {@link DecimalQuantity} approximated as a double. */ double toDouble() const; diff --git a/icu4c/source/i18n/number_decimfmtprops.cpp b/icu4c/source/i18n/number_decimfmtprops.cpp index 412a04d8f3..10a9eb0029 100644 --- a/icu4c/source/i18n/number_decimfmtprops.cpp +++ b/icu4c/source/i18n/number_decimfmtprops.cpp @@ -25,7 +25,7 @@ void DecimalFormatProperties::clear() { exponentSignAlwaysShown = false; formatWidth = -1; groupingSize = -1; - groupingUsed = false; + groupingUsed = true; magnitudeMultiplier = 0; maximumFractionDigits = -1; maximumIntegerDigits = -1; diff --git a/icu4c/source/i18n/number_decimfmtprops.h b/icu4c/source/i18n/number_decimfmtprops.h index 2cac011c7d..8e823a8006 100644 --- a/icu4c/source/i18n/number_decimfmtprops.h +++ b/icu4c/source/i18n/number_decimfmtprops.h @@ -119,7 +119,7 @@ struct U_I18N_API DecimalFormatProperties { bool parseIntegerOnly; NullableValue parseMode; bool parseNoExponent; - bool parseToBigDecimal; + bool parseToBigDecimal; // TODO: Not needed in ICU4C? UNumberFormatAttributeValue parseAllInput; // ICU4C-only //PluralRules pluralRules; UnicodeString positivePrefix; diff --git a/icu4c/source/i18n/numparse_decimal.cpp b/icu4c/source/i18n/numparse_decimal.cpp index 28a67d85ba..f3f17b6363 100644 --- a/icu4c/source/i18n/numparse_decimal.cpp +++ b/icu4c/source/i18n/numparse_decimal.cpp @@ -254,6 +254,12 @@ bool DecimalMatcher::match(StringSegment& segment, ParsedNumber& result, int8_t break; } + // Back up if there was a trailing grouping separator + if (backupOffset != -1) { + segment.setOffset(backupOffset); + hasPartialPrefix = true; // redundant with `groupingOverlap == segment.length()` + } + // Check the final grouping for validity if (requireGroupingMatch && !seenDecimal && seenGrouping && afterFirstGrouping && groupedDigitCount != grouping1) { diff --git a/icu4c/source/i18n/numparse_impl.cpp b/icu4c/source/i18n/numparse_impl.cpp index 547ba6b55d..a9ccbbc88c 100644 --- a/icu4c/source/i18n/numparse_impl.cpp +++ b/icu4c/source/i18n/numparse_impl.cpp @@ -232,6 +232,10 @@ void NumberParserImpl::freeze() { fFrozen = true; } +parse_flags_t NumberParserImpl::getParseFlags() const { + return fParseFlags; +} + void NumberParserImpl::parse(const UnicodeString& input, bool greedy, ParsedNumber& result, UErrorCode& status) const { return parse(input, 0, greedy, result, status); diff --git a/icu4c/source/i18n/numparse_impl.h b/icu4c/source/i18n/numparse_impl.h index 748b9415ec..f6688c1e21 100644 --- a/icu4c/source/i18n/numparse_impl.h +++ b/icu4c/source/i18n/numparse_impl.h @@ -42,6 +42,8 @@ class NumberParserImpl : public MutableMatcherCollection { void freeze(); + parse_flags_t getParseFlags() const; + void parse(const UnicodeString& input, bool greedy, ParsedNumber& result, UErrorCode& status) const; void parse(const UnicodeString& input, int32_t start, bool greedy, ParsedNumber& result, diff --git a/icu4c/source/i18n/numparse_parsednumber.cpp b/icu4c/source/i18n/numparse_parsednumber.cpp index f97219eed8..7adfb5ca56 100644 --- a/icu4c/source/i18n/numparse_parsednumber.cpp +++ b/icu4c/source/i18n/numparse_parsednumber.cpp @@ -78,9 +78,10 @@ double ParsedNumber::getDouble() const { } } -void ParsedNumber::populateFormattable(Formattable& output) const { +void ParsedNumber::populateFormattable(Formattable& output, parse_flags_t parseFlags) const { bool sawNaN = 0 != (flags & FLAG_NAN); bool sawInfinity = 0 != (flags & FLAG_INFINITY); + bool integerOnly = 0 != (parseFlags & PARSE_FLAG_INTEGER_ONLY); // Check for NaN, infinity, and -0.0 if (sawNaN) { @@ -97,7 +98,7 @@ void ParsedNumber::populateFormattable(Formattable& output) const { } } U_ASSERT(!quantity.bogus); - if (quantity.isZero() && quantity.isNegative()) { + if (quantity.isZero() && quantity.isNegative() && !integerOnly) { output.setDouble(-0.0); return; } diff --git a/icu4c/source/i18n/numparse_scientific.cpp b/icu4c/source/i18n/numparse_scientific.cpp index 849eab6837..bbf7738f99 100644 --- a/icu4c/source/i18n/numparse_scientific.cpp +++ b/icu4c/source/i18n/numparse_scientific.cpp @@ -52,9 +52,7 @@ ScientificMatcher::ScientificMatcher(const DecimalFormatSymbols& dfs, const Grou bool ScientificMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const { // Only accept scientific notation after the mantissa. - // Most places use result.hasNumber(), but we need a stronger condition here (i.e., exponent is - // not well-defined after NaN or infinity). - if (result.quantity.bogus) { + if (!result.seenNumber()) { return false; } @@ -95,8 +93,13 @@ bool ScientificMatcher::match(StringSegment& segment, ParsedNumber& result, UErr segment.adjustOffset(overlap2); } + // We are supposed to accept E0 after NaN, so we need to make sure result.quantity is available. + bool wasBogus = result.quantity.bogus; + result.quantity.bogus = false; int digitsOffset = segment.getOffset(); bool digitsReturnValue = fExponentMatcher.match(segment, result, exponentSign, status); + result.quantity.bogus = wasBogus; + if (segment.getOffset() != digitsOffset) { // At least one exponent digit was matched. result.flags |= FLAG_HAS_EXPONENT; diff --git a/icu4c/source/i18n/numparse_types.h b/icu4c/source/i18n/numparse_types.h index 4e4456538b..aa64497545 100644 --- a/icu4c/source/i18n/numparse_types.h +++ b/icu4c/source/i18n/numparse_types.h @@ -159,7 +159,7 @@ class ParsedNumber { double getDouble() const; - void populateFormattable(Formattable& output) const; + void populateFormattable(Formattable& output, parse_flags_t parseFlags) const; bool isBetterThan(const ParsedNumber& other); }; diff --git a/icu4c/source/test/intltest/numbertest_decimalquantity.cpp b/icu4c/source/test/intltest/numbertest_decimalquantity.cpp index fdd9124f0c..db594a786f 100644 --- a/icu4c/source/test/intltest/numbertest_decimalquantity.cpp +++ b/icu4c/source/test/intltest/numbertest_decimalquantity.cpp @@ -366,7 +366,7 @@ void DecimalQuantityTest::testMaxDigits() { dq.setFractionLength(0, 2); assertEquals("Should trim, toPlainString", "76.54", dq.toPlainString()); assertEquals("Should trim, toScientificString", "7.654E+1", dq.toScientificString()); - assertEquals("Should trim, toLong", 76L, dq.toLong()); + assertEquals("Should trim, toLong", 76L, dq.toLong(true)); assertEquals("Should trim, toFractionLong", 54L, dq.toFractionLong(false)); assertEquals("Should trim, toDouble", 76.54, dq.toDouble()); // To test DecNum output, check the round-trip. diff --git a/icu4c/source/test/intltest/numbertest_parse.cpp b/icu4c/source/test/intltest/numbertest_parse.cpp index e5a10c2c3f..97084dc9ee 100644 --- a/icu4c/source/test/intltest/numbertest_parse.cpp +++ b/icu4c/source/test/intltest/numbertest_parse.cpp @@ -99,7 +99,7 @@ void NumberParserTest::testBasic() { {3, u".00", u"0", 3, 0.0}, {3, u" 1,234", u"a0", 35, 1234.}, // should not hang {3, u"NaN", u"0", 3, NAN}, - {3, u"NaN E5", u"0", 3, NAN}, + {3, u"NaN E5", u"0", 6, NAN}, {3, u"0", u"0", 1, 0.0}}; parse_flags_t parseFlags = PARSE_FLAG_IGNORE_CASE | PARSE_FLAG_INCLUDE_UNPAIRED_AFFIXES; diff --git a/icu4c/source/test/intltest/numrgts.cpp b/icu4c/source/test/intltest/numrgts.cpp index e4852bd4cd..56e85120e6 100644 --- a/icu4c/source/test/intltest/numrgts.cpp +++ b/icu4c/source/test/intltest/numrgts.cpp @@ -806,29 +806,29 @@ void NumberFormatRegressionTest::Test4092480 (void) dfFoo->applyPattern("0000;-000", status); failure(status, "dfFoo->applyPattern"); UnicodeString temp; - if (dfFoo->toPattern(temp) != UnicodeString("#0000")) - errln("dfFoo.toPattern : " + dfFoo->toPattern(temp)); + if (dfFoo->toPattern(temp) != UnicodeString("0000")) + errln("ERROR: dfFoo.toPattern : " + dfFoo->toPattern(temp)); FieldPosition pos(FieldPosition::DONT_CARE); logln(dfFoo->format((int32_t)42, temp, pos)); logln(dfFoo->format((int32_t)-42, temp, pos)); dfFoo->applyPattern("000;-000", status); failure(status, "dfFoo->applyPattern"); - if (dfFoo->toPattern(temp) != UnicodeString("#000")) - errln("dfFoo.toPattern : " + dfFoo->toPattern(temp)); + if (dfFoo->toPattern(temp) != UnicodeString("000")) + errln("ERROR: dfFoo.toPattern : " + dfFoo->toPattern(temp)); logln(dfFoo->format((int32_t)42,temp, pos)); logln(dfFoo->format((int32_t)-42, temp, pos)); dfFoo->applyPattern("000;-0000", status); failure(status, "dfFoo->applyPattern"); - if (dfFoo->toPattern(temp) != UnicodeString("#000")) - errln("dfFoo.toPattern : " + dfFoo->toPattern(temp)); + if (dfFoo->toPattern(temp) != UnicodeString("000")) + errln("ERROR: dfFoo.toPattern : " + dfFoo->toPattern(temp)); logln(dfFoo->format((int32_t)42, temp, pos)); logln(dfFoo->format((int32_t)-42, temp, pos)); dfFoo->applyPattern("0000;-000", status); failure(status, "dfFoo->applyPattern"); - if (dfFoo->toPattern(temp) != UnicodeString("#0000")) - errln("dfFoo.toPattern : " + dfFoo->toPattern(temp)); + if (dfFoo->toPattern(temp) != UnicodeString("0000")) + errln("ERROR: dfFoo.toPattern : " + dfFoo->toPattern(temp)); logln(dfFoo->format((int32_t)42, temp, pos)); logln(dfFoo->format((int32_t)-42, temp, pos)); /*} catch (Exception foo) { @@ -1691,6 +1691,12 @@ void NumberFormatRegressionTest::Test4122840(void) // Create a DecimalFormat using the pattern we got and format a number DecimalFormatSymbols *symbols = new DecimalFormatSymbols(locales[i], status); failure(status, "new DecimalFormatSymbols"); + + // Disable currency spacing for the purposes of this test. + // To do this, set the spacing insert to the empty string both before and after the symbol. + symbols->setPatternForCurrencySpacing(UNUM_CURRENCY_INSERT, FALSE, u""); + symbols->setPatternForCurrencySpacing(UNUM_CURRENCY_INSERT, TRUE, u""); + DecimalFormat *fmt1 = new DecimalFormat(pattern, *symbols, status); failure(status, "new DecimalFormat"); @@ -2003,6 +2009,8 @@ void NumberFormatRegressionTest::Test4145457() { * DecimalFormat.applyPattern() sets minimum integer digits incorrectly. * CANNOT REPRODUCE * This bug is a duplicate of 4139344, which is a duplicate of 4134300 + * + * ICU 62: minInt is always at least one, and the getter should reflect that! */ void NumberFormatRegressionTest::Test4147295(void) { @@ -2013,7 +2021,7 @@ void NumberFormatRegressionTest::Test4147295(void) sdf->applyPattern(pattern, status); if (!failure(status, "sdf->applyPattern")) { int minIntDig = sdf->getMinimumIntegerDigits(); - if (minIntDig != 0) { + if (minIntDig != 1) { errln("Test failed"); errln(UnicodeString(" Minimum integer digits : ") + minIntDig); UnicodeString temp; @@ -2205,26 +2213,28 @@ void NumberFormatRegressionTest::Test4167494(void) { * DecimalFormat.parse() fails when ParseIntegerOnly set to true */ void NumberFormatRegressionTest::Test4170798(void) { - UErrorCode status = U_ZERO_ERROR; - NumberFormat *nf = NumberFormat::createInstance(Locale::getUS(), status); - if (failure(status, "NumberFormat::createInstance", TRUE)){ - delete nf; - return; - }; - DecimalFormat *df = dynamic_cast(nf); - if(df == NULL) { - errln("DecimalFormat needed to continue"); - return; + IcuTestErrorCode status(*this, "Test4170798"); + LocalPointer df(dynamic_cast( + NumberFormat::createInstance(Locale::getUS(), status)), status); + { + Formattable n; + ParsePosition pos(0); + df->parse("-0.0", n, pos); + if (n.getType() != Formattable::kDouble + || n.getDouble() != -0.0) { + errln(UnicodeString("FAIL: default parse(\"-0.0\") returns ") + toString(n)); + } } df->setParseIntegerOnly(TRUE); - Formattable n; - ParsePosition pos(0); - df->parse("-0.0", n, pos); - if (n.getType() != Formattable::kLong - || n.getLong() != 0) { - errln(UnicodeString("FAIL: parse(\"-0.0\") returns ") + toString(n)); + { + Formattable n; + ParsePosition pos(0); + df->parse("-0.0", n, pos); + if (n.getType() != Formattable::kLong + || n.getLong() != 0) { + errln(UnicodeString("FAIL: integer parse(\"-0.0\") returns ") + toString(n)); + } } - delete nf; } /** @@ -2233,15 +2243,15 @@ void NumberFormatRegressionTest::Test4170798(void) { */ void NumberFormatRegressionTest::Test4176114(void) { const char* DATA[] = { - "00", "#00", - "000", "#000", // No grouping - "#000", "#000", // No grouping + "00", "00", + "000", "000", // No grouping + "#000", "000", // No grouping "#,##0", "#,##0", "#,000", "#,000", - "0,000", "#0,000", - "00,000", "#00,000", - "000,000", "#,000,000", - "0,000,000,000,000.0000", "#0,000,000,000,000.0000", // Reported + "0,000", "0,000", + "00,000", "00,000", + "000,000", "000,000", + "0,000,000,000,000.0000", "0,000,000,000,000.0000", // Reported }; int DATA_length = UPRV_LENGTHOF(DATA); UErrorCode status = U_ZERO_ERROR; @@ -2372,9 +2382,9 @@ void NumberFormatRegressionTest::Test4212072(void) { sym.setSymbol(DecimalFormatSymbols::kCurrencySymbol, "usd"); fmt.setDecimalFormatSymbols(sym); s.remove(); - if (fmt.format(12.5, s, pos) != UnicodeString("usd12.50")) { + if (fmt.format(12.5, s, pos) != UnicodeString(u"usd\u00A012.50")) { errln(UnicodeString("FAIL: 12.5 x (currency=usd) -> ") + s + - ", exp usd12.50"); + u", exp usd\u00A012.50"); } s.remove(); if (fmt.getPositivePrefix(s) != UnicodeString("usd")) { @@ -2388,9 +2398,9 @@ void NumberFormatRegressionTest::Test4212072(void) { sym.setSymbol(DecimalFormatSymbols::kIntlCurrencySymbol, "DOL"); fmt.setDecimalFormatSymbols(sym); s.remove(); - if (fmt.format(12.5, s, pos) != UnicodeString("DOL12.50")) { + if (fmt.format(12.5, s, pos) != UnicodeString(u"DOL\u00A012.50")) { errln(UnicodeString("FAIL: 12.5 x (intlcurrency=DOL) -> ") + s + - ", exp DOL12.50"); + u", exp DOL\u00A012.50"); } s.remove(); if (fmt.getPositivePrefix(s) != UnicodeString("DOL")) { @@ -2734,7 +2744,7 @@ void NumberFormatRegressionTest::TestJ691(void) { #define TEST_ASSERT_EQUALS(x,y) \ { \ char _msg[1000]; \ - int32_t len = sprintf (_msg,"File %s, line %d: Assertion Failed: " #x "==" #y "\n", __FILE__, __LINE__); \ + int32_t len = sprintf (_msg,"File %s, line %d: " #x "==" #y, __FILE__, __LINE__); \ (void)len; \ U_ASSERT(len < (int32_t) sizeof(_msg)); \ assertEquals((const char*) _msg, x,y); \ @@ -2759,10 +2769,10 @@ void NumberFormatRegressionTest::Test8199(void) { Formattable val; nf->parse(numStr, val, status); TEST_CHECK_STATUS(status); - TEST_ASSERT(Formattable::kDouble == val.getType()); - TEST_ASSERT(1000000000 == val.getInt64(status)); + TEST_ASSERT_EQUALS(Formattable::kDouble, val.getType()); + TEST_ASSERT_EQUALS(1000000000L, val.getInt64(status)); TEST_CHECK_STATUS(status); - TEST_ASSERT(1000000000.6 == val.getDouble(status)); + TEST_ASSERT_EQUALS(1000000000.6, val.getDouble(status)); TEST_CHECK_STATUS(status); numStr = "100000000000000001.1"; // approx 1E17, parses as a double rather @@ -2770,25 +2780,25 @@ void NumberFormatRegressionTest::Test8199(void) { // even though int64 is more precise. nf->parse(numStr, val, status); TEST_CHECK_STATUS(status); - TEST_ASSERT(Formattable::kDouble == val.getType()); - TEST_ASSERT(100000000000000001LL == val.getInt64(status)); + TEST_ASSERT_EQUALS(Formattable::kDouble, val.getType()); + TEST_ASSERT_EQUALS(100000000000000001LL, val.getInt64(status)); TEST_CHECK_STATUS(status); - TEST_ASSERT(100000000000000000.0 == val.getDouble(status)); + TEST_ASSERT_EQUALS(100000000000000000.0, val.getDouble(status)); TEST_CHECK_STATUS(status); numStr = "1E17"; // Parses with the internal decimal number having non-zero exponent nf->parse(numStr, val, status); TEST_CHECK_STATUS(status); - TEST_ASSERT(Formattable::kInt64 == val.getType()); - TEST_ASSERT(100000000000000000LL == val.getInt64()); - TEST_ASSERT(1.0E17 == val.getDouble(status)); + TEST_ASSERT_EQUALS(Formattable::kInt64, val.getType()); + TEST_ASSERT_EQUALS(100000000000000000LL, val.getInt64()); + TEST_ASSERT_EQUALS(1.0E17, val.getDouble(status)); TEST_CHECK_STATUS(status); numStr = "9223372036854775807"; // largest int64_t nf->parse(numStr, val, status); TEST_CHECK_STATUS(status); - TEST_ASSERT(Formattable::kInt64 == val.getType()); - TEST_ASSERT(9223372036854775807LL == val.getInt64()); + TEST_ASSERT_EQUALS(Formattable::kInt64, val.getType()); + TEST_ASSERT_EQUALS(9223372036854775807LL, val.getInt64()); // In the following check, note that a substantial range of integers will // convert to the same double value. There are also platform variations // in the rounding at compile time of double constants. @@ -2799,31 +2809,31 @@ void NumberFormatRegressionTest::Test8199(void) { numStr = "-9223372036854775808"; // smallest int64_t nf->parse(numStr, val, status); TEST_CHECK_STATUS(status); - TEST_ASSERT(Formattable::kInt64 == val.getType()); - // TEST_ASSERT(-9223372036854775808LL == val.getInt64()); // Compiler chokes on constant. - TEST_ASSERT((int64_t)0x8000000000000000LL == val.getInt64()); - TEST_ASSERT(-9223372036854775808.0 == val.getDouble(status)); + TEST_ASSERT_EQUALS(Formattable::kInt64, val.getType()); + // TEST_ASSERT_EQUALS(-9223372036854775808LL, val.getInt64()); // Compiler chokes on constant. + TEST_ASSERT_EQUALS((int64_t)0x8000000000000000LL, val.getInt64()); + TEST_ASSERT_EQUALS(-9223372036854775808.0, val.getDouble(status)); TEST_CHECK_STATUS(status); numStr = "9223372036854775808"; // largest int64_t + 1 nf->parse(numStr, val, status); TEST_CHECK_STATUS(status); - TEST_ASSERT(Formattable::kDouble == val.getType()); - TEST_ASSERT(9223372036854775807LL == val.getInt64(status)); - TEST_ASSERT(status == U_INVALID_FORMAT_ERROR); + TEST_ASSERT_EQUALS(Formattable::kDouble, val.getType()); + TEST_ASSERT_EQUALS(9223372036854775807LL, val.getInt64(status)); + TEST_ASSERT_EQUALS(status, U_INVALID_FORMAT_ERROR); status = U_ZERO_ERROR; - TEST_ASSERT(9223372036854775810.0 == val.getDouble(status)); + TEST_ASSERT_EQUALS(9223372036854775810.0, val.getDouble(status)); TEST_CHECK_STATUS(status); numStr = "-9223372036854775809"; // smallest int64_t - 1 nf->parse(numStr, val, status); TEST_CHECK_STATUS(status); - TEST_ASSERT(Formattable::kDouble == val.getType()); - // TEST_ASSERT(-9223372036854775808LL == val.getInt64(status)); // spurious compiler warnings - TEST_ASSERT((int64_t)0x8000000000000000LL == val.getInt64(status)); - TEST_ASSERT(status == U_INVALID_FORMAT_ERROR); + TEST_ASSERT_EQUALS(Formattable::kDouble, val.getType()); + // TEST_ASSERT_EQUALS(-9223372036854775808LL, val.getInt64(status)); // spurious compiler warnings + TEST_ASSERT_EQUALS((int64_t)0x8000000000000000LL, val.getInt64(status)); + TEST_ASSERT_EQUALS(status, U_INVALID_FORMAT_ERROR); status = U_ZERO_ERROR; - TEST_ASSERT(-9223372036854775810.0 == val.getDouble(status)); + TEST_ASSERT_EQUALS(-9223372036854775810.0, val.getDouble(status)); TEST_CHECK_STATUS(status); // Test values near the limit of where doubles can represent all integers. @@ -2837,25 +2847,25 @@ void NumberFormatRegressionTest::Test8199(void) { nf->parse(numStr, val, status); TEST_CHECK_STATUS(status); // printf("getInt64() returns %lld\n", val.getInt64(status)); - TEST_ASSERT(Formattable::kDouble == val.getType()); - TEST_ASSERT(9007199254740991LL == val.getInt64(status)); - TEST_ASSERT(9007199254740991.0 == val.getDouble(status)); + TEST_ASSERT_EQUALS(Formattable::kDouble, val.getType()); + TEST_ASSERT_EQUALS(9007199254740991LL, val.getInt64(status)); + TEST_ASSERT_EQUALS(9007199254740991.0, val.getDouble(status)); TEST_CHECK_STATUS(status); status = U_ZERO_ERROR; numStr = "9007199254740992.1"; // 54 bits for the int part. nf->parse(numStr, val, status); TEST_CHECK_STATUS(status); - TEST_ASSERT(Formattable::kDouble == val.getType()); - TEST_ASSERT(9007199254740992LL == val.getInt64(status)); - TEST_ASSERT(9007199254740992.0 == val.getDouble(status)); + TEST_ASSERT_EQUALS(Formattable::kDouble, val.getType()); + TEST_ASSERT_EQUALS(9007199254740992LL, val.getInt64(status)); + TEST_ASSERT_EQUALS(9007199254740992.0, val.getDouble(status)); TEST_CHECK_STATUS(status); status = U_ZERO_ERROR; numStr = "9007199254740993.1"; // 54 bits for the int part. Double will round nf->parse(numStr, val, status); // the ones digit, putting it up to ...994 TEST_CHECK_STATUS(status); - TEST_ASSERT(Formattable::kDouble == val.getType()); + TEST_ASSERT_EQUALS(Formattable::kDouble, val.getType()); TEST_ASSERT_EQUALS((int64_t)9007199254740993LL,val.getInt64(status)); TEST_ASSERT_EQUALS((double)9007199254740994.0,(double)val.getDouble(status)); TEST_CHECK_STATUS(status); diff --git a/icu4c/source/test/intltest/tmsgfmt.cpp b/icu4c/source/test/intltest/tmsgfmt.cpp index d4e6ab6952..a3e5adfbdb 100644 --- a/icu4c/source/test/intltest/tmsgfmt.cpp +++ b/icu4c/source/test/intltest/tmsgfmt.cpp @@ -990,8 +990,8 @@ void TestMessageFormat::testSetLocale() // {sfb} to get $, would need Locale::US, not Locale::ENGLISH // Just use unlocalized currency symbol. //UnicodeString compareStrEng = "At

    + * // Setup:
    + * LocalUNumberFormatterPointer uformatter(unumf_openFromSkeletonAndLocale(u"percent", -1, "en", &ec));
    + * LocalUFormattedNumberPointer uresult(unumf_openResult(&ec));
    + * if (U_FAILURE(ec)) { return; }
    + *
    + * // Format a decimal number:
    + * unumf_formatDecimal(uformatter.getAlias(), "9.87E6", -1, uresult.getAlias(), &ec);
    + * if (U_FAILURE(ec)) { return; }
    + *
    + * // Get the location of the percent sign:
    + * UFieldPosition ufpos = {UNUM_PERCENT_FIELD, 0, 0};
    + * unumf_resultGetField(uresult.getAlias(), &ufpos, &ec);
    + * // ufpos should contain beginIndex=7 and endIndex=8 since the string is "0.00987%"
    + *
    + * // No need to do any cleanup since we are using LocalPointer.
    + * 
    */ @@ -373,6 +397,7 @@ typedef enum UNumberDecimalSeparatorDisplay { * * @draft ICU 62 */ +struct UNumberFormatter; typedef struct UNumberFormatter UNumberFormatter; @@ -383,6 +408,7 @@ typedef struct UNumberFormatter UNumberFormatter; * * @draft ICU 62 */ +struct UFormattedNumber; typedef struct UFormattedNumber UFormattedNumber; @@ -395,6 +421,10 @@ typedef struct UFormattedNumber UFormattedNumber; * * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. * + * @param skeleton The skeleton string, like u"percent round-integer" + * @param skeletonLen The number of UChars in the skeleton string, or -1 it it is NUL-terminated. + * @param locale The NUL-terminated locale ID. + * @param ec Set if an error occurs. * @draft ICU 62 */ U_DRAFT UNumberFormatter* U_EXPORT2 @@ -407,6 +437,7 @@ unumf_openFromSkeletonAndLocale(const UChar* skeleton, int32_t skeletonLen, cons * * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. * + * @param ec Set if an error occurs. * @draft ICU 62 */ U_DRAFT UFormattedNumber* U_EXPORT2 @@ -419,6 +450,10 @@ unumf_openResult(UErrorCode* ec); * * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. * + * @param uformatter A formatter object created by unumf_openFromSkeletonAndLocale or similar. + * @param value The number to be formatted. + * @param uresult The object that will be mutated to store the result; see unumf_openResult. + * @param ec Set if an error occurs. * @draft ICU 62 */ U_DRAFT void U_EXPORT2 @@ -432,6 +467,10 @@ unumf_formatInt(const UNumberFormatter* uformatter, int64_t value, UFormattedNum * * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. * + * @param uformatter A formatter object created by unumf_openFromSkeletonAndLocale or similar. + * @param value The number to be formatted. + * @param uresult The object that will be mutated to store the result; see unumf_openResult. + * @param ec Set if an error occurs. * @draft ICU 62 */ U_DRAFT void U_EXPORT2 @@ -448,6 +487,11 @@ unumf_formatDouble(const UNumberFormatter* uformatter, double value, UFormattedN * * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. * + * @param uformatter A formatter object created by unumf_openFromSkeletonAndLocale or similar. + * @param value The numeric string to be formatted. + * @param valueLen The length of the numeric string, or -1 if it is NUL-terminated. + * @param uresult The object that will be mutated to store the result; see unumf_openResult. + * @param ec Set if an error occurs. * @draft ICU 62 */ U_DRAFT void U_EXPORT2 @@ -460,12 +504,13 @@ unumf_formatDecimal(const UNumberFormatter* uformatter, const char* value, int32 * If bufferCapacity is greater than the required length, a terminating NUL is written. * If bufferCapacity is less than the required length, an error code is set. * - * If NULL is passed as the buffer argument, the required length is returned without setting an error. - * * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. * + * @param uresult The object containing the formatted number. + * @param buffer Where to save the string output. + * @param bufferCapacity The number of UChars available in the buffer. + * @param ec Set if an error occurs. * @return The required length. - * * @draft ICU 62 */ U_DRAFT int32_t U_EXPORT2 @@ -481,10 +526,13 @@ unumf_resultToString(const UFormattedNumber* uresult, UChar* buffer, int32_t buf * only ever return the first occurrence. Use unumf_resultGetAllFields() to access all occurrences of an * attribute. * + * @param uresult The object containing the formatted number. * @param fpos * A pointer to a UFieldPosition. On input, position->field is read. On output, * position->beginIndex and position->endIndex indicate the beginning and ending indices of field * number position->field, if such a field exists. + * @param ec Set if an error occurs. + * @draft ICU 62 */ U_DRAFT void U_EXPORT2 unumf_resultGetField(const UFormattedNumber* uresult, UFieldPosition* ufpos, UErrorCode* ec); @@ -496,6 +544,7 @@ unumf_resultGetField(const UFormattedNumber* uresult, UFieldPosition* ufpos, UEr * * If you need information on only one field, consider using unumf_resultGetField(). * + * @param uresult The object containing the formatted number. * @param fpositer * A pointer to a UFieldPositionIterator created by {@link #ufieldpositer_open}. Iteration * information already present in the UFieldPositionIterator is deleted, and the iterator is reset @@ -504,6 +553,8 @@ unumf_resultGetField(const UFormattedNumber* uresult, UFieldPosition* ufpos, UEr * the UNumberFormatFields enum. Fields are not returned in a guaranteed order. Fields cannot * overlap, but they may nest. For example, 1234 could format as "1,234" which might consist of a * grouping separator field for ',' and an integer field encompassing the entire string. + * @param ec Set if an error occurs. + * @draft ICU 62 */ U_DRAFT void U_EXPORT2 unumf_resultGetAllFields(const UFormattedNumber* uresult, UFieldPositionIterator* ufpositer, @@ -515,6 +566,7 @@ unumf_resultGetAllFields(const UFormattedNumber* uresult, UFieldPositionIterator * * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. * + * @param uformatter An object created by unumf_openFromSkeletonAndLocale(). * @draft ICU 62 */ U_DRAFT void U_EXPORT2 @@ -526,10 +578,52 @@ unumf_close(UNumberFormatter* uformatter); * * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. * + * @param uresult An object created by unumf_openResult(). * @draft ICU 62 */ U_DRAFT void U_EXPORT2 -unumf_closeResult(const UFormattedNumber* uresult); +unumf_closeResult(UFormattedNumber* uresult); + + +#if U_SHOW_CPLUSPLUS_API +U_NAMESPACE_BEGIN + +/** + * \class LocalUNumberFormatterPointer + * "Smart pointer" class; closes a UNumberFormatter via unumf_close(). + * For most methods see the LocalPointerBase base class. + * + * Usage: + *
    + * LocalUNumberFormatterPointer uformatter(unumf_openFromSkeletonAndLocale(...));
    + * // no need to explicitly call unumf_close()
    + * 
    + * + * @see LocalPointerBase + * @see LocalPointer + * @draft ICU 62 + */ +U_DEFINE_LOCAL_OPEN_POINTER(LocalUNumberFormatterPointer, UNumberFormatter, unumf_close); + +/** + * \class LocalUNumberFormatterPointer + * "Smart pointer" class; closes a UFormattedNumber via unumf_closeResult(). + * For most methods see the LocalPointerBase base class. + * + * Usage: + *
    + * LocalUFormattedNumberPointer uformatter(unumf_openResult(...));
    + * // no need to explicitly call unumf_closeResult()
    + * 
    + * + * @see LocalPointerBase + * @see LocalPointer + * @draft ICU 62 + */ +U_DEFINE_LOCAL_OPEN_POINTER(LocalUFormattedNumberPointer, UFormattedNumber, unumf_closeResult); + +U_NAMESPACE_END +#endif // U_SHOW_CPLUSPLUS_API #endif //__UNUMBERFORMATTER_H__ diff --git a/icu4c/source/test/cintltst/unumberformattertst.c b/icu4c/source/test/cintltst/unumberformattertst.c index 0e6aa54e19..47a389e664 100644 --- a/icu4c/source/test/cintltst/unumberformattertst.c +++ b/icu4c/source/test/cintltst/unumberformattertst.c @@ -147,8 +147,10 @@ static void TestExampleCode() { unumf_formatDouble(uformatter, 5142.3, uresult, &ec); assertSuccess("There should not be a failure in the example code", &ec); - // Export the string: + // Export the string to a malloc'd buffer: int32_t len = unumf_resultToString(uresult, NULL, 0, &ec); + assertTrue("No buffer yet", ec == U_BUFFER_OVERFLOW_ERROR); + ec = U_ZERO_ERROR; UChar* buffer = (UChar*) uprv_malloc((len+1)*sizeof(UChar)); unumf_resultToString(uresult, buffer, len+1, &ec); assertSuccess("There should not be a failure in the example code", &ec); diff --git a/icu4c/source/test/intltest/numbertest.h b/icu4c/source/test/intltest/numbertest.h index 594788287b..d1a5defdb6 100644 --- a/icu4c/source/test/intltest/numbertest.h +++ b/icu4c/source/test/intltest/numbertest.h @@ -70,6 +70,7 @@ class NumberFormatterApiTest : public IntlTest { void errors(); void validRanges(); void copyMove(); + void localPointerCAPI(); void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0); diff --git a/icu4c/source/test/intltest/numbertest_api.cpp b/icu4c/source/test/intltest/numbertest_api.cpp index d81a1cf133..0b22e57c4a 100644 --- a/icu4c/source/test/intltest/numbertest_api.cpp +++ b/icu4c/source/test/intltest/numbertest_api.cpp @@ -84,6 +84,7 @@ void NumberFormatterApiTest::runIndexedTest(int32_t index, UBool exec, const cha TESTCASE_AUTO(errors); TESTCASE_AUTO(validRanges); TESTCASE_AUTO(copyMove); + TESTCASE_AUTO(localPointerCAPI); TESTCASE_AUTO_END; } @@ -2206,6 +2207,28 @@ void NumberFormatterApiTest::copyMove() { assertEquals("FormattedNumber move assignment", u"20%", result.toString()); } +void NumberFormatterApiTest::localPointerCAPI() { + // NOTE: This is also the sample code in unumberformatter.h + UErrorCode ec = U_ZERO_ERROR; + + // Setup: + LocalUNumberFormatterPointer uformatter(unumf_openFromSkeletonAndLocale(u"percent", -1, "en", &ec)); + LocalUFormattedNumberPointer uresult(unumf_openResult(&ec)); + assertSuccess("", ec); + + // Format a decimal number: + unumf_formatDecimal(uformatter.getAlias(), "9.87E-3", -1, uresult.getAlias(), &ec); + assertSuccess("", ec); + + // Get the location of the percent sign: + UFieldPosition ufpos = {UNUM_PERCENT_FIELD, 0, 0}; + unumf_resultGetField(uresult.getAlias(), &ufpos, &ec); + assertEquals("Percent sign location within '0.00987%'", 7, ufpos.beginIndex); + assertEquals("Percent sign location within '0.00987%'", 8, ufpos.endIndex); + + // No need to do any cleanup since we are using LocalPointer. +} + void NumberFormatterApiTest::assertFormatDescending(const char16_t* umessage, const char16_t* uskeleton, const UnlocalizedNumberFormatter& f, Locale locale, From 55080e2804c089620fa4ba5c0c575d6819846c3c Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Wed, 18 Apr 2018 10:52:36 +0000 Subject: [PATCH 111/129] ICU-13634 Fixing some clang sanitizer issues, including one potentially serious one deep inside DecimalQuantity. X-SVN-Rev: 41245 --- icu4c/source/i18n/number_decimalquantity.cpp | 2 +- .../test/intltest/numberformattesttuple.cpp | 24 +++++++++---------- icu4c/source/test/intltest/numbertest_api.cpp | 18 ++++++++++++-- .../DecimalQuantity_DualStorageBCD.java | 2 +- .../test/number/NumberFormatterApiTest.java | 23 ++++++++++++++++++ 5 files changed, 52 insertions(+), 17 deletions(-) diff --git a/icu4c/source/i18n/number_decimalquantity.cpp b/icu4c/source/i18n/number_decimalquantity.cpp index 7759702288..4ad2564c6f 100644 --- a/icu4c/source/i18n/number_decimalquantity.cpp +++ b/icu4c/source/i18n/number_decimalquantity.cpp @@ -837,7 +837,7 @@ UnicodeString DecimalQuantity::toScientificString() const { int8_t DecimalQuantity::getDigitPos(int32_t position) const { if (usingBytes) { - if (position < 0 || position > precision) { return 0; } + if (position < 0 || position >= precision) { return 0; } return fBCD.bcdBytes.ptr[position]; } else { if (position < 0 || position >= 16) { return 0; } diff --git a/icu4c/source/test/intltest/numberformattesttuple.cpp b/icu4c/source/test/intltest/numberformattesttuple.cpp index d862d068cd..45d0d8de40 100644 --- a/icu4c/source/test/intltest/numberformattesttuple.cpp +++ b/icu4c/source/test/intltest/numberformattesttuple.cpp @@ -143,7 +143,7 @@ static void strToInt( status = U_ILLEGAL_ARGUMENT_ERROR; return; } - int32_t value = 0; + uint32_t value = 0; for (int32_t i = start; i < len; ++i) { UChar ch = str[i]; if (ch < 0x30 || ch > 0x39) { @@ -152,25 +152,23 @@ static void strToInt( } value = value * 10 - 0x30 + (int32_t) ch; } - if (neg) { - value = -value; - } - *static_cast(intPtr) = value; + int32_t signedValue = neg ? -value : static_cast(value); + *static_cast(intPtr) = signedValue; } static void intToStr( const void *intPtr, UnicodeString &appendTo) { UChar buffer[20]; - int32_t x = *static_cast(intPtr); - UBool neg = FALSE; - if (x < 0) { - neg = TRUE; - x = -x; - } - if (neg) { + // int64_t such that all int32_t values can be negated + int64_t xSigned = *static_cast(intPtr); + uint32_t x; + if (xSigned < 0) { appendTo.append((UChar)0x2D); + x = static_cast(-xSigned); + } else { + x = static_cast(xSigned); } - int32_t len = uprv_itou(buffer, UPRV_LENGTHOF(buffer), (uint32_t) x, 10, 1); + int32_t len = uprv_itou(buffer, UPRV_LENGTHOF(buffer), x, 10, 1); appendTo.append(buffer, 0, len); } diff --git a/icu4c/source/test/intltest/numbertest_api.cpp b/icu4c/source/test/intltest/numbertest_api.cpp index 0b22e57c4a..33b0610a5a 100644 --- a/icu4c/source/test/intltest/numbertest_api.cpp +++ b/icu4c/source/test/intltest/numbertest_api.cpp @@ -2047,9 +2047,23 @@ void NumberFormatterApiTest::locale() { void NumberFormatterApiTest::formatTypes() { UErrorCode status = U_ZERO_ERROR; LocalizedNumberFormatter formatter = NumberFormatter::withLocale(Locale::getEnglish()); - const char* str1 = "98765432123456789E1"; - UnicodeString actual = formatter.formatDecimal(str1, status).toString(); + + // Double + assertEquals("Format double", "514.23", formatter.formatDouble(514.23, status).toString()); + + // Int64 + assertEquals("Format int64", "51,423", formatter.formatDouble(51423L, status).toString()); + + // decNumber + UnicodeString actual = formatter.formatDecimal("98765432123456789E1", status).toString(); assertEquals("Format decNumber", u"987,654,321,234,567,890", actual); + + // Also test proper DecimalQuantity bytes storage when all digits are in the fraction. + // The number needs to have exactly 40 digits, which is the size of the default buffer. + // (issue discovered by the address sanitizer in C++) + static const char* str = "0.009876543210987654321098765432109876543211"; + actual = formatter.rounding(Rounder::unlimited()).formatDecimal(str, status).toString(); + assertEquals("Format decNumber to 40 digits", str, actual); } void NumberFormatterApiTest::errors() { diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java index f298d3f605..42cc2c48ec 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java @@ -90,7 +90,7 @@ public final class DecimalQuantity_DualStorageBCD extends DecimalQuantity_Abstra @Override protected byte getDigitPos(int position) { if (usingBytes) { - if (position < 0 || position > precision) + if (position < 0 || position >= precision) return 0; return bcdBytes[position]; } else { 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 0ad9fe3ce8..d27de4be1e 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 @@ -2017,6 +2017,29 @@ public class NumberFormatterApiTest { assertNotEquals(NumberFormatter.with().locale(ULocale.ENGLISH), NumberFormatter.with().locale(Locale.FRENCH)); } + @Test + public void formatTypes() { + LocalizedNumberFormatter formatter = NumberFormatter.withLocale(ULocale.ENGLISH); + + // Double + assertEquals("514.23", formatter.format(514.23).toString()); + + // Int64 + assertEquals("51,423", formatter.format(51423L).toString()); + + // BigDecimal + assertEquals("987,654,321,234,567,890", + formatter.format(new BigDecimal("98765432123456789E1")).toString()); + + // Also test proper DecimalQuantity bytes storage when all digits are in the fraction. + // The number needs to have exactly 40 digits, which is the size of the default buffer. + // (issue discovered by the address sanitizer in C++) + assertEquals("0.009876543210987654321098765432109876543211", + formatter.rounding(Rounder.unlimited()) + .format(new BigDecimal("0.009876543210987654321098765432109876543211")) + .toString()); + } + @Test public void plurals() { // TODO: Expand this test. From 1aa5185a360ee1d015ecf2d273e38d130d81c0c7 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Wed, 18 Apr 2018 23:55:55 +0000 Subject: [PATCH 112/129] ICU-13700 Renaming new getter/setter to magnitudeScale. X-SVN-Rev: 41246 --- icu4c/source/i18n/decimfmt.cpp | 12 ++++---- icu4c/source/i18n/number_decimfmtprops.cpp | 4 +-- icu4c/source/i18n/number_decimfmtprops.h | 4 +-- icu4c/source/i18n/number_multiplier.h | 2 +- icu4c/source/i18n/unicode/decimfmt.h | 34 +++++++++++++++------- icu4c/source/i18n/unicode/unum.h | 2 ++ icu4c/source/test/intltest/dcfmapts.cpp | 4 +-- icu4c/source/test/intltest/numfmtst.cpp | 11 +++++++ icu4c/source/test/intltest/numfmtst.h | 1 + 9 files changed, 50 insertions(+), 24 deletions(-) diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp index 43399d1a13..c7192dc469 100644 --- a/icu4c/source/i18n/decimfmt.cpp +++ b/icu4c/source/i18n/decimfmt.cpp @@ -167,7 +167,7 @@ DecimalFormat::setAttribute(UNumberFormatAttribute attr, int32_t newValue, UErro break; case UNUM_SCALE: - setScale(newValue); + setMultiplierScale(newValue); break; case UNUM_GROUPING_SIZE: @@ -280,7 +280,7 @@ int32_t DecimalFormat::getAttribute(UNumberFormatAttribute attr, UErrorCode& sta return getMultiplier(); case UNUM_SCALE: - return getScale(); + return getMultiplierScale(); case UNUM_GROUPING_SIZE: return getGroupingSize(); @@ -667,12 +667,12 @@ void DecimalFormat::setMultiplier(int32_t multiplier) { refreshFormatterNoError(); } -int32_t DecimalFormat::getScale() const { - return fProperties->scaleMultiplier; +int32_t DecimalFormat::getMultiplierScale() const { + return fProperties->multiplierScale; } -void DecimalFormat::setScale(int32_t newValue) { - fProperties->scaleMultiplier = newValue; +void DecimalFormat::setMultiplierScale(int32_t newValue) { + fProperties->multiplierScale = newValue; refreshFormatterNoError(); } diff --git a/icu4c/source/i18n/number_decimfmtprops.cpp b/icu4c/source/i18n/number_decimfmtprops.cpp index a292197e42..e56f955f54 100644 --- a/icu4c/source/i18n/number_decimfmtprops.cpp +++ b/icu4c/source/i18n/number_decimfmtprops.cpp @@ -37,6 +37,7 @@ void DecimalFormatProperties::clear() { minimumIntegerDigits = -1; minimumSignificantDigits = -1; multiplier = 1; + multiplierScale = 0; negativePrefix.setToBogus(); negativePrefixPattern.setToBogus(); negativeSuffix.setToBogus(); @@ -55,7 +56,6 @@ void DecimalFormatProperties::clear() { positiveSuffixPattern.setToBogus(); roundingIncrement = 0.0; roundingMode.nullify(); - scaleMultiplier = 0; secondaryGroupingSize = -1; signAlwaysShown = false; } @@ -83,6 +83,7 @@ bool DecimalFormatProperties::operator==(const DecimalFormatProperties &other) c eq = eq && minimumIntegerDigits == other.minimumIntegerDigits; eq = eq && minimumSignificantDigits == other.minimumSignificantDigits; eq = eq && multiplier == other.multiplier; + eq = eq && multiplierScale == other.multiplierScale; eq = eq && negativePrefix == other.negativePrefix; eq = eq && negativePrefixPattern == other.negativePrefixPattern; eq = eq && negativeSuffix == other.negativeSuffix; @@ -101,7 +102,6 @@ bool DecimalFormatProperties::operator==(const DecimalFormatProperties &other) c eq = eq && positiveSuffixPattern == other.positiveSuffixPattern; eq = eq && roundingIncrement == other.roundingIncrement; eq = eq && roundingMode == other.roundingMode; - eq = eq && scaleMultiplier == other.scaleMultiplier; eq = eq && secondaryGroupingSize == other.secondaryGroupingSize; eq = eq && signAlwaysShown == other.signAlwaysShown; return eq; diff --git a/icu4c/source/i18n/number_decimfmtprops.h b/icu4c/source/i18n/number_decimfmtprops.h index 067c148ac7..1c7d88f46c 100644 --- a/icu4c/source/i18n/number_decimfmtprops.h +++ b/icu4c/source/i18n/number_decimfmtprops.h @@ -100,7 +100,7 @@ struct U_I18N_API DecimalFormatProperties { int32_t formatWidth; int32_t groupingSize; bool groupingUsed; - int32_t magnitudeMultiplier; + int32_t magnitudeMultiplier; // internal field like multiplierScale but separate to avoid conflict int32_t maximumFractionDigits; int32_t maximumIntegerDigits; int32_t maximumSignificantDigits; @@ -110,6 +110,7 @@ struct U_I18N_API DecimalFormatProperties { int32_t minimumIntegerDigits; int32_t minimumSignificantDigits; int32_t multiplier; + int32_t multiplierScale; // ICU4C-only UnicodeString negativePrefix; UnicodeString negativePrefixPattern; UnicodeString negativeSuffix; @@ -129,7 +130,6 @@ struct U_I18N_API DecimalFormatProperties { UnicodeString positiveSuffixPattern; double roundingIncrement; NullableValue roundingMode; - int32_t scaleMultiplier; int32_t secondaryGroupingSize; bool signAlwaysShown; diff --git a/icu4c/source/i18n/number_multiplier.h b/icu4c/source/i18n/number_multiplier.h index b31be8ed61..94f582e6fc 100644 --- a/icu4c/source/i18n/number_multiplier.h +++ b/icu4c/source/i18n/number_multiplier.h @@ -32,7 +32,7 @@ class MultiplierFormatHandler : public MicroPropsGenerator, public UMemory { /** Gets a Scale from a DecimalFormatProperties. In Java, defined in RoundingUtils.java */ static inline Scale scaleFromProperties(const DecimalFormatProperties& properties) { - int32_t magnitudeMultiplier = properties.magnitudeMultiplier + properties.scaleMultiplier; + int32_t magnitudeMultiplier = properties.magnitudeMultiplier + properties.multiplierScale; int32_t arbitraryMultiplier = properties.multiplier; if (magnitudeMultiplier != 0 && arbitraryMultiplier != 1) { return Scale::byDoubleAndPowerOfTen(arbitraryMultiplier, magnitudeMultiplier); diff --git a/icu4c/source/i18n/unicode/decimfmt.h b/icu4c/source/i18n/unicode/decimfmt.h index d59e50d06d..0bee8ffb35 100644 --- a/icu4c/source/i18n/unicode/decimfmt.h +++ b/icu4c/source/i18n/unicode/decimfmt.h @@ -1301,7 +1301,7 @@ class U_I18N_API DecimalFormat : public NumberFormat { * @param value The new setting for whether to show plus sign on positive numbers * @internal Technical Preview */ - void setSignAlwaysShown(UBool value); + virtual void setSignAlwaysShown(UBool value); /** * Get the multiplier for use in percent, permill, etc. @@ -1309,6 +1309,8 @@ class U_I18N_API DecimalFormat : public NumberFormat { * (For Arabic, use arabic percent symbol). * For a permill, set the suffixes to have "\\u2031" and the multiplier to be 1000. * + * The number may also be multiplied by a power of ten; see getMultiplierScale(). + * * @return the multiplier for use in percent, permill, etc. * Examples: with 100, 1.23 -> "123", and "123" -> 1.23 * @stable ICU 2.0 @@ -1320,7 +1322,9 @@ class U_I18N_API DecimalFormat : public NumberFormat { * For a percentage, set the suffixes to have "%" and the multiplier to be 100. * (For Arabic, use arabic percent symbol). * For a permill, set the suffixes to have "\\u2031" and the multiplier to be 1000. - * It is possible to set both via setMultiplier() as via setScale() simultaneously. + * + * This method only supports integer multipliers. To multiply by a non-integer, pair this + * method with setMultiplierScale(). * * @param newValue the new value of the multiplier for use in percent, permill, etc. * Examples: with 100, 1.23 -> "123", and "123" -> 1.23 @@ -1332,22 +1336,30 @@ class U_I18N_API DecimalFormat : public NumberFormat { * Gets a multiplier for the given power of ten. * For example, scale of 2 corresponds to a multiplier of 100. * - * @return the multiplier for use in percent, permill, etc. - * Examples: with 100, 1.23 -> "123", and "123" -> 1.23 + * This method is analogous to UNUM_SCALE in getAttribute. + * + * @return the new value of the power-of-ten multiplier. * @draft ICU 62 */ - int32_t getScale(void) const; + int32_t getMultiplierScale(void) const; /** - * Sets a multiplier for the given power of ten. - * For example, scale of 2 corresponds to a multiplier of 100. - * It is possible to set both via setMultiplier() as via setScale() simultaneously. + * Sets a power of ten by which number should be multiplied before formatting, which + * can be combined with setMultiplier() to multiply by any arbitrary decimal value. * - * @param newValue the new value of the multiplier for use in percent, permill, etc. - * Examples: with 100, 1.23 -> "123", and "123" -> 1.23 + * For example, to multiply numbers by 0.5 before formatting, you can do: + * + *
    +     * df.setMultiplier(5);
    +     * df.setMultiplierScale(-1);
    +     * 
    + * + * This method is analogous to UNUM_SCALE in setAttribute. + * + * @param newValue the new value of the power-of-ten multiplier. * @draft ICU 62 */ - virtual void setScale(int32_t newValue); + virtual void setMultiplierScale(int32_t newValue); /** * Get the rounding increment. diff --git a/icu4c/source/i18n/unicode/unum.h b/icu4c/source/i18n/unicode/unum.h index a8894a0052..89d5e2fb5a 100644 --- a/icu4c/source/i18n/unicode/unum.h +++ b/icu4c/source/i18n/unicode/unum.h @@ -1018,6 +1018,8 @@ typedef enum UNumberFormatAttribute { *

    Example: setting the scale to 3, 123 formats as "123,000" *

    Example: setting the scale to -4, 123 formats as "0.0123" * + * This setting is analogous to getMultiplierScale() and setMultiplierScale() in decimfmt.h. + * * @stable ICU 51 */ UNUM_SCALE = 21, #ifndef U_HIDE_INTERNAL_API diff --git a/icu4c/source/test/intltest/dcfmapts.cpp b/icu4c/source/test/intltest/dcfmapts.cpp index 96d297074f..611015c159 100644 --- a/icu4c/source/test/intltest/dcfmapts.cpp +++ b/icu4c/source/test/intltest/dcfmapts.cpp @@ -612,9 +612,9 @@ void IntlTestDecimalFormatAPI::TestScale() // Test both the attribute and the setter if (i % 2 == 0) { pat.setAttribute(UNUM_SCALE, testData[i].inputScale,status); - assertEquals("", testData[i].inputScale, pat.getScale()); + assertEquals("", testData[i].inputScale, pat.getMultiplierScale()); } else { - pat.setScale(testData[i].inputScale); + pat.setMultiplierScale(testData[i].inputScale); assertEquals("", testData[i].inputScale, pat.getAttribute(UNUM_SCALE, status)); } pat.format(testData[i].inputValue, resultStr); diff --git a/icu4c/source/test/intltest/numfmtst.cpp b/icu4c/source/test/intltest/numfmtst.cpp index 524a29361d..89760cb12d 100644 --- a/icu4c/source/test/intltest/numfmtst.cpp +++ b/icu4c/source/test/intltest/numfmtst.cpp @@ -656,6 +656,7 @@ void NumberFormatTest::runIndexedTest( int32_t index, UBool exec, const char* &n TESTCASE_AUTO(Test11035_FormatCurrencyAmount); TESTCASE_AUTO(Test11318_DoubleConversion); TESTCASE_AUTO(TestParsePercentRegression); + TESTCASE_AUTO(TestMultiplierWithScale); TESTCASE_AUTO_END; } @@ -9061,4 +9062,14 @@ void NumberFormatTest::TestParsePercentRegression() { } } +void NumberFormatTest::TestMultiplierWithScale() { + IcuTestErrorCode status(*this, "TestMultiplierWithScale"); + + // Test magnitude combined with multiplier, as shown in API docs + DecimalFormat df("0", {"en", status}, status); + df.setMultiplier(5); + df.setMultiplierScale(-1); + expect2(df, 100, u"50"); // round-trip test +} + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/test/intltest/numfmtst.h b/icu4c/source/test/intltest/numfmtst.h index e17b825d03..a22b93e9eb 100644 --- a/icu4c/source/test/intltest/numfmtst.h +++ b/icu4c/source/test/intltest/numfmtst.h @@ -223,6 +223,7 @@ class NumberFormatTest: public CalendarTimeZoneTest { void Test11035_FormatCurrencyAmount(); void Test11318_DoubleConversion(); void TestParsePercentRegression(); + void TestMultiplierWithScale(); private: UBool testFormattableAsUFormattable(const char *file, int line, Formattable &f); From 2e41837217b1120d950bfe8bf47b40baf35be8d6 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Thu, 19 Apr 2018 00:14:17 +0000 Subject: [PATCH 113/129] ICU-13700 APIDoc tweaks for getMultiplierScale/setMultiplierScale. X-SVN-Rev: 41247 --- icu4c/source/i18n/unicode/decimfmt.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/icu4c/source/i18n/unicode/decimfmt.h b/icu4c/source/i18n/unicode/decimfmt.h index 0bee8ffb35..b55036f78a 100644 --- a/icu4c/source/i18n/unicode/decimfmt.h +++ b/icu4c/source/i18n/unicode/decimfmt.h @@ -1333,8 +1333,11 @@ class U_I18N_API DecimalFormat : public NumberFormat { virtual void setMultiplier(int32_t newValue); /** - * Gets a multiplier for the given power of ten. - * For example, scale of 2 corresponds to a multiplier of 100. + * Gets the power of ten by which number should be multiplied before formatting, which + * can be combined with setMultiplier() to multiply by any arbitrary decimal value. + * + * A multiplier scale of 2 corresponds to multiplication by 100, and a multiplier scale + * of -2 corresponds to multiplication by 0.01. * * This method is analogous to UNUM_SCALE in getAttribute. * @@ -1347,6 +1350,9 @@ class U_I18N_API DecimalFormat : public NumberFormat { * Sets a power of ten by which number should be multiplied before formatting, which * can be combined with setMultiplier() to multiply by any arbitrary decimal value. * + * A multiplier scale of 2 corresponds to multiplication by 100, and a multiplier scale + * of -2 corresponds to multiplication by 0.01. + * * For example, to multiply numbers by 0.5 before formatting, you can do: * *

    
    From f11ca0d3630caa149a2c71391536f16574c5d054 Mon Sep 17 00:00:00 2001
    From: Shane Carr 
    Date: Thu, 19 Apr 2018 01:13:17 +0000
    Subject: [PATCH 114/129] ICU-13634 Fixing address sanitizer issue involving
     backwards-compatible UChar* behavior in CurrencyUnit constructor. The string
     passed to the constructor need not be NUL-terminated.
    
    X-SVN-Rev: 41248
    ---
     icu4c/source/i18n/currunit.cpp                  | 17 ++++++++++++-----
     icu4c/source/i18n/decimfmt.cpp                  |  1 -
     icu4c/source/i18n/number_skeletons.cpp          |  8 ++++++--
     icu4c/source/i18n/unicode/currunit.h            |  5 +++--
     .../test/intltest/numbertest_skeletons.cpp      |  2 ++
     .../icu/dev/test/number/NumberSkeletonTest.java |  2 ++
     6 files changed, 25 insertions(+), 10 deletions(-)
    
    diff --git a/icu4c/source/i18n/currunit.cpp b/icu4c/source/i18n/currunit.cpp
    index 003ce61210..5a6ff6e7e1 100644
    --- a/icu4c/source/i18n/currunit.cpp
    +++ b/icu4c/source/i18n/currunit.cpp
    @@ -17,6 +17,7 @@
     #include "unicode/currunit.h"
     #include "unicode/ustring.h"
     #include "cstring.h"
    +#include "uinvchar.h"
     
     static constexpr char16_t kDefaultCurrency[] = u"XXX";
     
    @@ -24,14 +25,20 @@ U_NAMESPACE_BEGIN
     
     CurrencyUnit::CurrencyUnit(ConstChar16Ptr _isoCode, UErrorCode& ec) {
         // The constructor always leaves the CurrencyUnit in a valid state (with a 3-character currency code).
    +    // Note: in ICU4J Currency.getInstance(), we check string length for 3, but in ICU4C we allow a
    +    // non-NUL-terminated string to be passed as an argument, so it is not possible to check length.
    +    const char16_t* isoCodeToUse;
         if (U_FAILURE(ec) || _isoCode == nullptr) {
    -        u_strcpy(isoCode, kDefaultCurrency);
    -    } else if (u_strlen(_isoCode) != 3) {
    -        u_strcpy(isoCode, kDefaultCurrency);
    -        ec = U_ILLEGAL_ARGUMENT_ERROR;
    +        isoCodeToUse = kDefaultCurrency;
    +    } else if (!uprv_isInvariantUString(_isoCode, 3)) {
    +        // TODO: Perform a more strict ASCII check like in ICU4J isAlpha3Code?
    +        isoCodeToUse = kDefaultCurrency;
    +        ec = U_INVARIANT_CONVERSION_ERROR;
         } else {
    -        u_strcpy(isoCode, _isoCode);
    +        isoCodeToUse = _isoCode;
         }
    +    u_strncpy(isoCode, isoCodeToUse, 3);
    +    isoCode[3] = 0;
         char simpleIsoCode[4];
         u_UCharsToChars(isoCode, simpleIsoCode, 4);
         initCurrency(simpleIsoCode);
    diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp
    index c7192dc469..42cd90233b 100644
    --- a/icu4c/source/i18n/decimfmt.cpp
    +++ b/icu4c/source/i18n/decimfmt.cpp
    @@ -992,7 +992,6 @@ void DecimalFormat::setCurrency(const char16_t* theCurrency, UErrorCode& ec) {
     
     void DecimalFormat::setCurrency(const char16_t* theCurrency) {
         ErrorCode localStatus;
    -    NumberFormat::setCurrency(theCurrency, localStatus); // to set field for compatibility
         setCurrency(theCurrency, localStatus);
     }
     
    diff --git a/icu4c/source/i18n/number_skeletons.cpp b/icu4c/source/i18n/number_skeletons.cpp
    index d921b547e1..e998e8e51c 100644
    --- a/icu4c/source/i18n/number_skeletons.cpp
    +++ b/icu4c/source/i18n/number_skeletons.cpp
    @@ -800,8 +800,12 @@ blueprint_helpers::parseExponentSignOption(const StringSegment& segment, MacroPr
     
     void blueprint_helpers::parseCurrencyOption(const StringSegment& segment, MacroProps& macros,
                                                 UErrorCode& status) {
    -    // Can't use toTempUnicodeString() because getTerminatedBuffer is non-const
    -    const UChar* currencyCode = segment.toUnicodeString().getTerminatedBuffer();
    +    // Unlike ICU4J, have to check length manually because ICU4C CurrencyUnit does not check it for us
    +    if (segment.length() != 3) {
    +        status = U_NUMBER_SKELETON_SYNTAX_ERROR;
    +        return;
    +    }
    +    const UChar* currencyCode = segment.toTempUnicodeString().getBuffer();
         UErrorCode localStatus = U_ZERO_ERROR;
         CurrencyUnit currency(currencyCode, localStatus);
         if (U_FAILURE(localStatus)) {
    diff --git a/icu4c/source/i18n/unicode/currunit.h b/icu4c/source/i18n/unicode/currunit.h
    index 5ad23b1f58..ca90acb791 100644
    --- a/icu4c/source/i18n/unicode/currunit.h
    +++ b/icu4c/source/i18n/unicode/currunit.h
    @@ -44,8 +44,9 @@ class U_I18N_API CurrencyUnit: public MeasureUnit {
     
         /**
          * Construct an object with the given ISO currency code.
    -     * @param isoCode the 3-letter ISO 4217 currency code; must not be
    -     * NULL and must have length 3
    +     * @param isoCode the 3-letter ISO 4217 currency code; must have
    +     * length 3 and need not be NUL-terminated. If NULL, the currency
    +     * is initialized to the unknown currency XXX.
          * @param ec input-output error code. If the isoCode is invalid,
          * then this will be set to a failing value.
          * @stable ICU 3.0
    diff --git a/icu4c/source/test/intltest/numbertest_skeletons.cpp b/icu4c/source/test/intltest/numbertest_skeletons.cpp
    index 33de5c311b..59ad630ce9 100644
    --- a/icu4c/source/test/intltest/numbertest_skeletons.cpp
    +++ b/icu4c/source/test/intltest/numbertest_skeletons.cpp
    @@ -68,6 +68,7 @@ void NumberSkeletonTest::validTokens() {
                 u"measure-unit/energy-joule per-measure-unit/length-meter",
                 u"currency/XXX",
                 u"currency/ZZZ",
    +            u"currency/usd",
                 u"group-off",
                 u"group-min2",
                 u"group-auto",
    @@ -137,6 +138,7 @@ void NumberSkeletonTest::invalidTokens() {
                 u"scale/0.1.2",
                 u"scale/français", // non-invariant characters for C++
                 u"currency/dummy",
    +            u"currency/ççç", // three characters but not ASCII
                 u"measure-unit/foo",
                 u"integer-width/xxx",
                 u"integer-width/0+",
    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 7c7349b5e9..6e854dfbf2 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
    @@ -60,6 +60,7 @@ public class NumberSkeletonTest {
                     "measure-unit/energy-joule per-measure-unit/length-meter",
                     "currency/XXX",
                     "currency/ZZZ",
    +                "currency/usd",
                     "group-off",
                     "group-min2",
                     "group-auto",
    @@ -131,6 +132,7 @@ public class NumberSkeletonTest {
                     "scale/0.1.2",
                     "scale/français", // non-invariant characters for C++
                     "currency/dummy",
    +                "currency/ççç", // three characters but not ASCII
                     "measure-unit/foo",
                     "integer-width/xxx",
                     "integer-width/0+",
    
    From f164bc149863616e726cabb5bffe75152e4ddaf0 Mon Sep 17 00:00:00 2001
    From: Shane Carr 
    Date: Thu, 19 Apr 2018 01:16:47 +0000
    Subject: [PATCH 115/129] ICU-13634 Adding TODO comment in CurrencyUnit
     constructor.
    
    X-SVN-Rev: 41249
    ---
     icu4c/source/i18n/currunit.cpp | 1 +
     1 file changed, 1 insertion(+)
    
    diff --git a/icu4c/source/i18n/currunit.cpp b/icu4c/source/i18n/currunit.cpp
    index 5a6ff6e7e1..b65a67c112 100644
    --- a/icu4c/source/i18n/currunit.cpp
    +++ b/icu4c/source/i18n/currunit.cpp
    @@ -37,6 +37,7 @@ CurrencyUnit::CurrencyUnit(ConstChar16Ptr _isoCode, UErrorCode& ec) {
         } else {
             isoCodeToUse = _isoCode;
         }
    +    // TODO: Perform uppercasing here like in ICU4J Currency.getInstance()?
         u_strncpy(isoCode, isoCodeToUse, 3);
         isoCode[3] = 0;
         char simpleIsoCode[4];
    
    From d682d1dbb15d31996a173054252fb05046982ab0 Mon Sep 17 00:00:00 2001
    From: Shane Carr 
    Date: Thu, 19 Apr 2018 02:41:33 +0000
    Subject: [PATCH 116/129] ICU-13700 Fixing typo in APIdoc.
    
    X-SVN-Rev: 41250
    ---
     icu4c/source/i18n/unicode/decimfmt.h | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/icu4c/source/i18n/unicode/decimfmt.h b/icu4c/source/i18n/unicode/decimfmt.h
    index b55036f78a..b562be7714 100644
    --- a/icu4c/source/i18n/unicode/decimfmt.h
    +++ b/icu4c/source/i18n/unicode/decimfmt.h
    @@ -1341,7 +1341,7 @@ class U_I18N_API DecimalFormat : public NumberFormat {
          *
          * This method is analogous to UNUM_SCALE in getAttribute.
          *
    -     * @return    the new value of the power-of-ten multiplier.
    +     * @return    the current value of the power-of-ten multiplier.
          * @draft ICU 62
          */
         int32_t getMultiplierScale(void) const;
    
    From ad116997ae96b10ee57caf5cf520359816da8764 Mon Sep 17 00:00:00 2001
    From: Shane Carr 
    Date: Thu, 19 Apr 2018 09:29:39 +0000
    Subject: [PATCH 117/129] ICU-13634 Optimizing and adding a basic fastpath for
     small ints.  Greatly increases the performance of DateFormat.
    
    X-SVN-Rev: 41251
    ---
     icu4c/source/i18n/compactdecimalformat.cpp |   2 +-
     icu4c/source/i18n/decimfmt.cpp             | 338 +++++++++++++++------
     icu4c/source/i18n/number_decimfmtprops.cpp |  68 +++--
     icu4c/source/i18n/number_decimfmtprops.h   |  13 +-
     icu4c/source/i18n/numparse_currency.cpp    |  23 +-
     icu4c/source/i18n/numparse_impl.cpp        |   3 +
     icu4c/source/i18n/plurfmt.cpp              |   3 +-
     icu4c/source/i18n/unicode/decimfmt.h       |  36 ++-
     8 files changed, 360 insertions(+), 126 deletions(-)
    
    diff --git a/icu4c/source/i18n/compactdecimalformat.cpp b/icu4c/source/i18n/compactdecimalformat.cpp
    index 93033dc8fe..be25ecd32e 100644
    --- a/icu4c/source/i18n/compactdecimalformat.cpp
    +++ b/icu4c/source/i18n/compactdecimalformat.cpp
    @@ -32,7 +32,7 @@ CompactDecimalFormat::CompactDecimalFormat(const Locale& inLocale, UNumberCompac
         fProperties->compactStyle = style;
         fProperties->groupingSize = -2; // do not forward grouping information
         fProperties->minimumGroupingDigits = 2;
    -    refreshFormatter(status);
    +    touch(status);
     }
     
     CompactDecimalFormat::CompactDecimalFormat(const CompactDecimalFormat& source) = default;
    diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp
    index 42cd90233b..0e0d81fcfc 100644
    --- a/icu4c/source/i18n/decimfmt.cpp
    +++ b/icu4c/source/i18n/decimfmt.cpp
    @@ -1,7 +1,6 @@
     // © 2018 and later: Unicode, Inc. and others.
     // License & terms of use: http://www.unicode.org/copyright.html
     
    -#include 
     #include "unicode/utypes.h"
     
     #if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    @@ -10,6 +9,8 @@
     // Helpful in toString methods and elsewhere.
     #define UNISTR_FROM_STRING_EXPLICIT
     
    +#include 
    +#include 
     #include "unicode/errorcode.h"
     #include "unicode/decimfmt.h"
     #include "number_decimalquantity.h"
    @@ -19,6 +20,7 @@
     #include "number_patternstring.h"
     #include "putilimp.h"
     #include "number_utils.h"
    +#include "number_utypes.h"
     
     using namespace icu;
     using namespace icu::number;
    @@ -43,20 +45,20 @@ DecimalFormat::DecimalFormat(UErrorCode& status)
                 CLDR_PATTERN_STYLE_DECIMAL,
                 status);
         setPropertiesFromPattern(patternString, IGNORE_ROUNDING_IF_CURRENCY, status);
    -    refreshFormatter(status);
    +    touch(status);
     }
     
     DecimalFormat::DecimalFormat(const UnicodeString& pattern, UErrorCode& status)
             : DecimalFormat(nullptr, status) {
         setPropertiesFromPattern(pattern, IGNORE_ROUNDING_IF_CURRENCY, status);
    -    refreshFormatter(status);
    +    touch(status);
     }
     
     DecimalFormat::DecimalFormat(const UnicodeString& pattern, DecimalFormatSymbols* symbolsToAdopt,
                                  UErrorCode& status)
             : DecimalFormat(symbolsToAdopt, status) {
         setPropertiesFromPattern(pattern, IGNORE_ROUNDING_IF_CURRENCY, status);
    -    refreshFormatter(status);
    +    touch(status);
     }
     
     DecimalFormat::DecimalFormat(const UnicodeString& pattern, DecimalFormatSymbols* symbolsToAdopt,
    @@ -81,7 +83,7 @@ DecimalFormat::DecimalFormat(const UnicodeString& pattern, DecimalFormatSymbols*
             if (U_FAILURE(status)) { return; }
             fProperties->currencyPluralInfo.fPtr.adoptInstead(cpi.orphan());
         }
    -    refreshFormatter(status);
    +    touch(status);
     }
     
     DecimalFormat::DecimalFormat(const DecimalFormatSymbols* symbolsToAdopt, UErrorCode& status) {
    @@ -330,19 +332,19 @@ int32_t DecimalFormat::getAttribute(UNumberFormatAttribute attr, UErrorCode& sta
     void DecimalFormat::setGroupingUsed(UBool enabled) {
         NumberFormat::setGroupingUsed(enabled); // to set field for compatibility
         fProperties->groupingUsed = enabled;
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     void DecimalFormat::setParseIntegerOnly(UBool value) {
         NumberFormat::setParseIntegerOnly(value); // to set field for compatibility
         fProperties->parseIntegerOnly = value;
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     void DecimalFormat::setLenient(UBool enable) {
         NumberFormat::setLenient(enable); // to set field for compatibility
         fProperties->parseMode = enable ? PARSE_MODE_LENIENT : PARSE_MODE_STRICT;
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     DecimalFormat::DecimalFormat(const UnicodeString& pattern, DecimalFormatSymbols* symbolsToAdopt,
    @@ -350,14 +352,14 @@ DecimalFormat::DecimalFormat(const UnicodeString& pattern, DecimalFormatSymbols*
             : DecimalFormat(symbolsToAdopt, status) {
         // TODO: What is parseError for?
         setPropertiesFromPattern(pattern, IGNORE_ROUNDING_IF_CURRENCY, status);
    -    refreshFormatter(status);
    +    touch(status);
     }
     
     DecimalFormat::DecimalFormat(const UnicodeString& pattern, const DecimalFormatSymbols& symbols,
                                  UErrorCode& status)
             : DecimalFormat(new DecimalFormatSymbols(symbols), status) {
         setPropertiesFromPattern(pattern, IGNORE_ROUNDING_IF_CURRENCY, status);
    -    refreshFormatter(status);
    +    touch(status);
     }
     
     DecimalFormat::DecimalFormat(const DecimalFormat& source) : NumberFormat(source) {
    @@ -369,18 +371,21 @@ DecimalFormat::DecimalFormat(const DecimalFormat& source) : NumberFormat(source)
             fSymbols == nullptr) {
             return;
         }
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     DecimalFormat& DecimalFormat::operator=(const DecimalFormat& rhs) {
         *fProperties = *rhs.fProperties;
         fExportedProperties->clear();
         fSymbols.adoptInstead(new DecimalFormatSymbols(*rhs.fSymbols));
    -    refreshFormatterNoError();
    +    touchNoError();
         return *this;
     }
     
    -DecimalFormat::~DecimalFormat() = default;
    +DecimalFormat::~DecimalFormat() {
    +    delete fAtomicParser.load();
    +    delete fAtomicCurrencyParser.load();
    +};
     
     Format* DecimalFormat::clone() const {
         return new DecimalFormat(*this);
    @@ -395,7 +400,10 @@ UBool DecimalFormat::operator==(const Format& other) const {
     }
     
     UnicodeString& DecimalFormat::format(double number, UnicodeString& appendTo, FieldPosition& pos) const {
    -    ErrorCode localStatus;
    +    if (pos.getField() == FieldPosition::DONT_CARE && fastFormatDouble(number, appendTo)) {
    +        return appendTo;
    +    }
    +    UErrorCode localStatus = U_ZERO_ERROR;
         FormattedNumber output = fFormatter->formatDouble(number, localStatus);
         output.populateFieldPosition(pos, localStatus);
         auto appendable = UnicodeStringAppendable(appendTo);
    @@ -405,6 +413,9 @@ UnicodeString& DecimalFormat::format(double number, UnicodeString& appendTo, Fie
     
     UnicodeString& DecimalFormat::format(double number, UnicodeString& appendTo, FieldPosition& pos,
                                          UErrorCode& status) const {
    +    if (pos.getField() == FieldPosition::DONT_CARE && fastFormatDouble(number, appendTo)) {
    +        return appendTo;
    +    }
         FormattedNumber output = fFormatter->formatDouble(number, status);
         output.populateFieldPosition(pos, status);
         auto appendable = UnicodeStringAppendable(appendTo);
    @@ -415,6 +426,9 @@ UnicodeString& DecimalFormat::format(double number, UnicodeString& appendTo, Fie
     UnicodeString&
     DecimalFormat::format(double number, UnicodeString& appendTo, FieldPositionIterator* posIter,
                           UErrorCode& status) const {
    +    if (posIter == nullptr && fastFormatDouble(number, appendTo)) {
    +        return appendTo;
    +    }
         FormattedNumber output = fFormatter->formatDouble(number, status);
         if (posIter != nullptr) {
             output.populateFieldPositionIterator(*posIter, status);
    @@ -440,7 +454,10 @@ DecimalFormat::format(int32_t number, UnicodeString& appendTo, FieldPositionIter
     }
     
     UnicodeString& DecimalFormat::format(int64_t number, UnicodeString& appendTo, FieldPosition& pos) const {
    -    ErrorCode localStatus;
    +    if (pos.getField() == FieldPosition::DONT_CARE && fastFormatInt64(number, appendTo)) {
    +        return appendTo;
    +    }
    +    UErrorCode localStatus = U_ZERO_ERROR;
         FormattedNumber output = fFormatter->formatInt(number, localStatus);
         output.populateFieldPosition(pos, localStatus);
         auto appendable = UnicodeStringAppendable(appendTo);
    @@ -450,6 +467,9 @@ UnicodeString& DecimalFormat::format(int64_t number, UnicodeString& appendTo, Fi
     
     UnicodeString& DecimalFormat::format(int64_t number, UnicodeString& appendTo, FieldPosition& pos,
                                          UErrorCode& status) const {
    +    if (pos.getField() == FieldPosition::DONT_CARE && fastFormatInt64(number, appendTo)) {
    +        return appendTo;
    +    }
         FormattedNumber output = fFormatter->formatInt(number, status);
         output.populateFieldPosition(pos, status);
         auto appendable = UnicodeStringAppendable(appendTo);
    @@ -460,6 +480,9 @@ UnicodeString& DecimalFormat::format(int64_t number, UnicodeString& appendTo, Fi
     UnicodeString&
     DecimalFormat::format(int64_t number, UnicodeString& appendTo, FieldPositionIterator* posIter,
                           UErrorCode& status) const {
    +    if (posIter == nullptr && fastFormatInt64(number, appendTo)) {
    +        return appendTo;
    +    }
         FormattedNumber output = fFormatter->formatInt(number, status);
         if (posIter != nullptr) {
             output.populateFieldPositionIterator(*posIter, status);
    @@ -513,11 +536,13 @@ void DecimalFormat::parse(const UnicodeString& text, Formattable& output,
         // Note: if this is a currency instance, currencies will be matched despite the fact that we are not in the
         // parseCurrency method (backwards compatibility)
         int32_t startIndex = parsePosition.getIndex();
    -    fParser->parse(text, startIndex, true, result, status);
    +    const NumberParserImpl& parser = getParser(status);
    +    if (U_FAILURE(status)) { return; }
    +    parser.parse(text, startIndex, true, result, status);
         // TODO: Do we need to check for fProperties->parseAllInput (UCONFIG_HAVE_PARSEALLINPUT) here?
         if (result.success()) {
             parsePosition.setIndex(result.charEnd);
    -        result.populateFormattable(output, fParser->getParseFlags());
    +        result.populateFormattable(output, parser.getParseFlags());
         } else {
             parsePosition.setErrorIndex(startIndex + result.charEnd);
         }
    @@ -533,12 +558,14 @@ CurrencyAmount* DecimalFormat::parseCurrency(const UnicodeString& text, ParsePos
         // Note: if this is a currency instance, currencies will be matched despite the fact that we are not in the
         // parseCurrency method (backwards compatibility)
         int32_t startIndex = parsePosition.getIndex();
    -    fParserWithCurrency->parse(text, startIndex, true, result, status);
    +    const NumberParserImpl& parser = getCurrencyParser(status);
    +    if (U_FAILURE(status)) { return nullptr; }
    +    parser.parse(text, startIndex, true, result, status);
         // TODO: Do we need to check for fProperties->parseAllInput (UCONFIG_HAVE_PARSEALLINPUT) here?
         if (result.success()) {
             parsePosition.setIndex(result.charEnd);
             Formattable formattable;
    -        result.populateFormattable(formattable, fParser->getParseFlags());
    +        result.populateFormattable(formattable, parser.getParseFlags());
             return new CurrencyAmount(formattable, result.currencyCode, status);
         } else {
             parsePosition.setErrorIndex(startIndex + result.charEnd);
    @@ -555,12 +582,12 @@ void DecimalFormat::adoptDecimalFormatSymbols(DecimalFormatSymbols* symbolsToAdo
             return; // do not allow caller to set fSymbols to NULL
         }
         fSymbols.adoptInstead(symbolsToAdopt);
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     void DecimalFormat::setDecimalFormatSymbols(const DecimalFormatSymbols& symbols) {
         fSymbols.adoptInstead(new DecimalFormatSymbols(symbols));
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     const CurrencyPluralInfo* DecimalFormat::getCurrencyPluralInfo(void) const {
    @@ -569,12 +596,12 @@ const CurrencyPluralInfo* DecimalFormat::getCurrencyPluralInfo(void) const {
     
     void DecimalFormat::adoptCurrencyPluralInfo(CurrencyPluralInfo* toAdopt) {
         fProperties->currencyPluralInfo.fPtr.adoptInstead(toAdopt);
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     void DecimalFormat::setCurrencyPluralInfo(const CurrencyPluralInfo& info) {
         *fProperties->currencyPluralInfo.fPtr = info; // copy-assignment operator
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     UnicodeString& DecimalFormat::getPositivePrefix(UnicodeString& result) const {
    @@ -585,7 +612,7 @@ UnicodeString& DecimalFormat::getPositivePrefix(UnicodeString& result) const {
     
     void DecimalFormat::setPositivePrefix(const UnicodeString& newValue) {
         fProperties->positivePrefix = newValue;
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     UnicodeString& DecimalFormat::getNegativePrefix(UnicodeString& result) const {
    @@ -596,7 +623,7 @@ UnicodeString& DecimalFormat::getNegativePrefix(UnicodeString& result) const {
     
     void DecimalFormat::setNegativePrefix(const UnicodeString& newValue) {
         fProperties->negativePrefix = newValue;
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     UnicodeString& DecimalFormat::getPositiveSuffix(UnicodeString& result) const {
    @@ -607,7 +634,7 @@ UnicodeString& DecimalFormat::getPositiveSuffix(UnicodeString& result) const {
     
     void DecimalFormat::setPositiveSuffix(const UnicodeString& newValue) {
         fProperties->positiveSuffix = newValue;
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     UnicodeString& DecimalFormat::getNegativeSuffix(UnicodeString& result) const {
    @@ -618,7 +645,7 @@ UnicodeString& DecimalFormat::getNegativeSuffix(UnicodeString& result) const {
     
     void DecimalFormat::setNegativeSuffix(const UnicodeString& newValue) {
         fProperties->negativeSuffix = newValue;
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     UBool DecimalFormat::isSignAlwaysShown() const {
    @@ -627,7 +654,7 @@ UBool DecimalFormat::isSignAlwaysShown() const {
     
     void DecimalFormat::setSignAlwaysShown(UBool value) {
         fProperties->signAlwaysShown = value;
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     int32_t DecimalFormat::getMultiplier(void) const {
    @@ -664,7 +691,7 @@ void DecimalFormat::setMultiplier(int32_t multiplier) {
             fProperties->magnitudeMultiplier = 0;
             fProperties->multiplier = multiplier;
         }
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     int32_t DecimalFormat::getMultiplierScale() const {
    @@ -673,7 +700,7 @@ int32_t DecimalFormat::getMultiplierScale() const {
     
     void DecimalFormat::setMultiplierScale(int32_t newValue) {
         fProperties->multiplierScale = newValue;
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     double DecimalFormat::getRoundingIncrement(void) const {
    @@ -682,7 +709,7 @@ double DecimalFormat::getRoundingIncrement(void) const {
     
     void DecimalFormat::setRoundingIncrement(double newValue) {
         fProperties->roundingIncrement = newValue;
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     ERoundingMode DecimalFormat::getRoundingMode(void) const {
    @@ -693,7 +720,7 @@ ERoundingMode DecimalFormat::getRoundingMode(void) const {
     void DecimalFormat::setRoundingMode(ERoundingMode roundingMode) {
         NumberFormat::setMaximumIntegerDigits(roundingMode); // to set field for compatibility
         fProperties->roundingMode = static_cast(roundingMode);
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     int32_t DecimalFormat::getFormatWidth(void) const {
    @@ -702,7 +729,7 @@ int32_t DecimalFormat::getFormatWidth(void) const {
     
     void DecimalFormat::setFormatWidth(int32_t width) {
         fProperties->formatWidth = width;
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     UnicodeString DecimalFormat::getPadCharacterString() const {
    @@ -720,7 +747,7 @@ void DecimalFormat::setPadCharacter(const UnicodeString& padChar) {
         } else {
             fProperties->padString.setToBogus();
         }
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     EPadPosition DecimalFormat::getPadPosition(void) const {
    @@ -734,7 +761,7 @@ EPadPosition DecimalFormat::getPadPosition(void) const {
     
     void DecimalFormat::setPadPosition(EPadPosition padPos) {
         fProperties->padPosition = static_cast(padPos);
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     UBool DecimalFormat::isScientificNotation(void) const {
    @@ -747,7 +774,7 @@ void DecimalFormat::setScientificNotation(UBool useScientific) {
         } else {
             fProperties->minimumExponentDigits = -1;
         }
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     int8_t DecimalFormat::getMinimumExponentDigits(void) const {
    @@ -756,7 +783,7 @@ int8_t DecimalFormat::getMinimumExponentDigits(void) const {
     
     void DecimalFormat::setMinimumExponentDigits(int8_t minExpDig) {
         fProperties->minimumExponentDigits = minExpDig;
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     UBool DecimalFormat::isExponentSignAlwaysShown(void) const {
    @@ -765,7 +792,7 @@ UBool DecimalFormat::isExponentSignAlwaysShown(void) const {
     
     void DecimalFormat::setExponentSignAlwaysShown(UBool expSignAlways) {
         fProperties->exponentSignAlwaysShown = expSignAlways;
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     int32_t DecimalFormat::getGroupingSize(void) const {
    @@ -777,7 +804,7 @@ int32_t DecimalFormat::getGroupingSize(void) const {
     
     void DecimalFormat::setGroupingSize(int32_t newValue) {
         fProperties->groupingSize = newValue;
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     int32_t DecimalFormat::getSecondaryGroupingSize(void) const {
    @@ -790,7 +817,7 @@ int32_t DecimalFormat::getSecondaryGroupingSize(void) const {
     
     void DecimalFormat::setSecondaryGroupingSize(int32_t newValue) {
         fProperties->secondaryGroupingSize = newValue;
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     int32_t DecimalFormat::getMinimumGroupingDigits() const {
    @@ -799,7 +826,7 @@ int32_t DecimalFormat::getMinimumGroupingDigits() const {
     
     void DecimalFormat::setMinimumGroupingDigits(int32_t newValue) {
         fProperties->minimumGroupingDigits = newValue;
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     UBool DecimalFormat::isDecimalSeparatorAlwaysShown(void) const {
    @@ -808,7 +835,7 @@ UBool DecimalFormat::isDecimalSeparatorAlwaysShown(void) const {
     
     void DecimalFormat::setDecimalSeparatorAlwaysShown(UBool newValue) {
         fProperties->decimalSeparatorAlwaysShown = newValue;
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     UBool DecimalFormat::isDecimalPatternMatchRequired(void) const {
    @@ -817,7 +844,7 @@ UBool DecimalFormat::isDecimalPatternMatchRequired(void) const {
     
     void DecimalFormat::setDecimalPatternMatchRequired(UBool newValue) {
         fProperties->decimalPatternMatchRequired = newValue;
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     UBool DecimalFormat::isParseNoExponent() const {
    @@ -826,7 +853,7 @@ UBool DecimalFormat::isParseNoExponent() const {
     
     void DecimalFormat::setParseNoExponent(UBool value) {
         fProperties->parseNoExponent = value;
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     UBool DecimalFormat::isParseCaseSensitive() const {
    @@ -835,7 +862,7 @@ UBool DecimalFormat::isParseCaseSensitive() const {
     
     void DecimalFormat::setParseCaseSensitive(UBool value) {
         fProperties->parseCaseSensitive = value;
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     UBool DecimalFormat::isFormatFailIfMoreThanMaxDigits() const {
    @@ -844,7 +871,7 @@ UBool DecimalFormat::isFormatFailIfMoreThanMaxDigits() const {
     
     void DecimalFormat::setFormatFailIfMoreThanMaxDigits(UBool value) {
         fProperties->formatFailIfMoreThanMaxDigits = value;
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     UnicodeString& DecimalFormat::toPattern(UnicodeString& result) const {
    @@ -886,7 +913,7 @@ void DecimalFormat::applyPattern(const UnicodeString& pattern, UParseError&, UEr
     
     void DecimalFormat::applyPattern(const UnicodeString& pattern, UErrorCode& status) {
         setPropertiesFromPattern(pattern, IGNORE_ROUNDING_NEVER, status);
    -    refreshFormatter(status);
    +    touch(status);
     }
     
     void DecimalFormat::applyLocalizedPattern(const UnicodeString& localizedPattern, UParseError&,
    @@ -908,7 +935,7 @@ void DecimalFormat::setMaximumIntegerDigits(int32_t newValue) {
             fProperties->minimumIntegerDigits = newValue;
         }
         fProperties->maximumIntegerDigits = newValue;
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     void DecimalFormat::setMinimumIntegerDigits(int32_t newValue) {
    @@ -918,7 +945,7 @@ void DecimalFormat::setMinimumIntegerDigits(int32_t newValue) {
             fProperties->maximumIntegerDigits = newValue;
         }
         fProperties->minimumIntegerDigits = newValue;
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     void DecimalFormat::setMaximumFractionDigits(int32_t newValue) {
    @@ -928,7 +955,7 @@ void DecimalFormat::setMaximumFractionDigits(int32_t newValue) {
             fProperties->minimumFractionDigits = newValue;
         }
         fProperties->maximumFractionDigits = newValue;
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     void DecimalFormat::setMinimumFractionDigits(int32_t newValue) {
    @@ -938,7 +965,7 @@ void DecimalFormat::setMinimumFractionDigits(int32_t newValue) {
             fProperties->maximumFractionDigits = newValue;
         }
         fProperties->minimumFractionDigits = newValue;
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     int32_t DecimalFormat::getMinimumSignificantDigits() const {
    @@ -955,7 +982,7 @@ void DecimalFormat::setMinimumSignificantDigits(int32_t value) {
             fProperties->maximumSignificantDigits = value;
         }
         fProperties->minimumSignificantDigits = value;
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     void DecimalFormat::setMaximumSignificantDigits(int32_t value) {
    @@ -964,7 +991,7 @@ void DecimalFormat::setMaximumSignificantDigits(int32_t value) {
             fProperties->minimumSignificantDigits = value;
         }
         fProperties->maximumSignificantDigits = value;
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     UBool DecimalFormat::areSignificantDigitsUsed() const {
    @@ -980,14 +1007,14 @@ void DecimalFormat::setSignificantDigitsUsed(UBool useSignificantDigits) {
             fProperties->minimumSignificantDigits = -1;
             fProperties->maximumSignificantDigits = -1;
         }
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     void DecimalFormat::setCurrency(const char16_t* theCurrency, UErrorCode& ec) {
         NumberFormat::setCurrency(theCurrency, ec); // to set field for compatibility
         fProperties->currency = CurrencyUnit(theCurrency, ec);
         // TODO: Set values in fSymbols, too?
    -    refreshFormatterNoError();
    +    touchNoError();
     }
     
     void DecimalFormat::setCurrency(const char16_t* theCurrency) {
    @@ -997,7 +1024,7 @@ void DecimalFormat::setCurrency(const char16_t* theCurrency) {
     
     void DecimalFormat::setCurrencyUsage(UCurrencyUsage newUsage, UErrorCode* ec) {
         fProperties->currencyUsage = newUsage;
    -    refreshFormatter(*ec);
    +    touch(*ec);
     }
     
     UCurrencyUsage DecimalFormat::getCurrencyUsage() const {
    @@ -1016,55 +1043,43 @@ DecimalFormat::formatToDecimalQuantity(double number, DecimalQuantity& output, U
     
     void DecimalFormat::formatToDecimalQuantity(const Formattable& number, DecimalQuantity& output,
                                                 UErrorCode& status) const {
    -    // Check if the Formattable is a DecimalQuantity
    -    DecimalQuantity* dq = number.getDecimalQuantity();
    -    if (dq != nullptr) {
    -        fFormatter->formatDecimalQuantity(*dq, status).getDecimalQuantity(output, status);
    -        return;
    -    }
    -
    -    // If not, it must be Double, Long (int32_t), or Int64:
    -    switch (number.getType()) {
    -        case Formattable::kDouble:
    -            fFormatter->formatDouble(number.getDouble(), status).getDecimalQuantity(output, status);
    -            break;
    -        case Formattable::kLong:
    -            fFormatter->formatInt(number.getLong(), status).getDecimalQuantity(output, status);
    -            break;
    -        case Formattable::kInt64:
    -        default:
    -            fFormatter->formatInt(number.getInt64(), status).getDecimalQuantity(output, status);
    -    }
    +    UFormattedNumberData obj;
    +    number.populateDecimalQuantity(obj.quantity, status);
    +    fFormatter->formatImpl(&obj, status);
    +    output = std::move(obj.quantity);
     }
     
    -const number::LocalizedNumberFormatter& DecimalFormat::toNumberFormatter() const {
    -    return *fFormatter;
    +number::LocalizedNumberFormatter&
    +DecimalFormat::toNumberFormatter(number::LocalizedNumberFormatter& output) const {
    +    output = *fFormatter; // copy assignment
    +    return output;
     }
     
     /** Rebuilds the formatter object from the property bag. */
    -void DecimalFormat::refreshFormatter(UErrorCode& status) {
    +void DecimalFormat::touch(UErrorCode& status) {
         if (fExportedProperties == nullptr) {
             // fExportedProperties is null only when the formatter is not ready yet.
             // The only time when this happens is during legacy deserialization.
             return;
         }
     
    +    setupFastFormat();
    +
         // In C++, fSymbols is the source of truth for the locale.
         Locale locale = fSymbols->getLocale();
     
    -    fFormatter.adoptInsteadAndCheckErrorCode(
    +    // Note: The formatter is relatively cheap to create, and we need it to populate fExportedProperties,
    +    // so automatically compute it here. The parser is a bit more expensive and is not needed until the
    +    // parse method is called, so defer that until needed.
    +    fFormatter.adoptInstead(
                 new LocalizedNumberFormatter(
                         NumberPropertyMapper::create(
                                 *fProperties, *fSymbols, *fWarehouse, *fExportedProperties, status).locale(
    -                            locale)), status);
    +                            locale)));
     
    -    fParser.adoptInsteadAndCheckErrorCode(
    -            NumberParserImpl::createParserFromProperties(
    -                    *fProperties, *fSymbols, false, status), status);
    -
    -    fParserWithCurrency.adoptInsteadAndCheckErrorCode(
    -            NumberParserImpl::createParserFromProperties(
    -                    *fProperties, *fSymbols, true, status), status);
    +    // Delete the parsers if they were made previously
    +    delete fAtomicParser.exchange(nullptr, std::memory_order_relaxed);
    +    delete fAtomicCurrencyParser.exchange(nullptr, std::memory_order_relaxed);
     
         // In order for the getters to work, we need to populate some fields in NumberFormat.
         NumberFormat::setCurrency(fExportedProperties->currency.get(status).getISOCurrency(), status);
    @@ -1076,9 +1091,9 @@ void DecimalFormat::refreshFormatter(UErrorCode& status) {
         NumberFormat::setGroupingUsed(fProperties->groupingUsed);
     }
     
    -void DecimalFormat::refreshFormatterNoError() {
    -    ErrorCode localStatus;
    -    refreshFormatter(localStatus);
    +void DecimalFormat::touchNoError() {
    +    UErrorCode localStatus = U_ZERO_ERROR;
    +    touch(localStatus);
     }
     
     void DecimalFormat::setPropertiesFromPattern(const UnicodeString& pattern, int32_t ignoreRounding,
    @@ -1088,5 +1103,148 @@ void DecimalFormat::setPropertiesFromPattern(const UnicodeString& pattern, int32
         PatternParser::parseToExistingProperties(pattern, *fProperties, actualIgnoreRounding, status);
     }
     
    +const numparse::impl::NumberParserImpl& DecimalFormat::getParser(UErrorCode& status) const {
    +    auto* ptr = fAtomicParser.load();
    +    if (ptr != nullptr) {
    +        return *ptr;
    +    }
    +
    +    ptr = NumberParserImpl::createParserFromProperties(*fProperties, *fSymbols, false, status);
    +
    +    if (ptr == nullptr) {
    +        status = U_MEMORY_ALLOCATION_ERROR;
    +        // although we still dereference, call sites should be guarded
    +    }
    +
    +    // Store the new pointer, and delete the old one if it got created.
    +    auto* nonConstThis = const_cast(this);
    +    delete nonConstThis->fAtomicParser.exchange(ptr, std::memory_order_relaxed);
    +    return *ptr;
    +}
    +
    +const numparse::impl::NumberParserImpl& DecimalFormat::getCurrencyParser(UErrorCode& status) const {
    +    auto* ptr = fAtomicCurrencyParser.load();
    +    if (ptr != nullptr) {
    +        return *ptr;
    +    }
    +
    +    ptr = NumberParserImpl::createParserFromProperties(*fProperties, *fSymbols, true, status);
    +
    +    if (ptr == nullptr) {
    +        status = U_MEMORY_ALLOCATION_ERROR;
    +        // although we still dereference, call sites should be guarded
    +    }
    +
    +    // Store the new pointer, and delete the old one if it got created.
    +    auto* nonConstThis = const_cast(this);
    +    delete nonConstThis->fAtomicCurrencyParser.exchange(ptr, std::memory_order_relaxed);
    +    return *ptr;
    +}
    +
    +void DecimalFormat::setupFastFormat() {
    +    // Check the majority of properties:
    +    if (!fProperties->equalsDefaultExceptFastFormat()) {
    +        fCanUseFastFormat = false;
    +        return;
    +    }
    +
    +    // Now check the remaining properties.
    +    // Nontrivial affixes:
    +    UBool trivialPP = fProperties->positivePrefixPattern.isEmpty();
    +    UBool trivialPS = fProperties->positiveSuffixPattern.isEmpty();
    +    UBool trivialNP = fProperties->negativePrefixPattern.isBogus() || (
    +            fProperties->negativePrefixPattern.length() == 1 &&
    +            fProperties->negativePrefixPattern.charAt(0) == u'-');
    +    UBool trivialNS = fProperties->negativeSuffixPattern.isEmpty();
    +    if (!trivialPP || !trivialPS || !trivialNP || !trivialNS) {
    +        fCanUseFastFormat = false;
    +        return;
    +    }
    +
    +    // Grouping (secondary grouping is forbidden in equalsDefaultExceptFastFormat):
    +    bool groupingUsed = fProperties->groupingUsed;
    +    bool unusualGroupingSize = fProperties->groupingSize > 0 && fProperties->groupingSize != 3;
    +    const UnicodeString& groupingString = fSymbols->getConstSymbol(DecimalFormatSymbols::kGroupingSeparatorSymbol);
    +    if (groupingUsed && (unusualGroupingSize || groupingString.length() != 1)) {
    +        fCanUseFastFormat = false;
    +        return;
    +    }
    +
    +    // Integer length:
    +    int32_t minInt = fProperties->minimumIntegerDigits;
    +    int32_t maxInt = fProperties->maximumIntegerDigits;
    +    // Fastpath supports up to only 10 digits (length of INT32_MIN)
    +    if (minInt > 10) {
    +        fCanUseFastFormat = false;
    +        return;
    +    }
    +
    +    // Other symbols:
    +    const UnicodeString& minusSignString = fSymbols->getConstSymbol(DecimalFormatSymbols::kMinusSignSymbol);
    +    UChar32 codePointZero = fSymbols->getCodePointZero();
    +    if (minusSignString.length() != 1 || U16_LENGTH(codePointZero) != 1) {
    +        fCanUseFastFormat = false;
    +        return;
    +    }
    +
    +    // Good to go!
    +    fCanUseFastFormat = true;
    +    fFastData.cpZero = static_cast(codePointZero);
    +    fFastData.cpGroupingSeparator = groupingUsed ? groupingString.charAt(0) : 0;
    +    fFastData.cpMinusSign = minusSignString.charAt(0);
    +    fFastData.minInt = static_cast(minInt);
    +    fFastData.maxInt = static_cast(maxInt < 0 ? 127 : maxInt);
    +}
    +
    +bool DecimalFormat::fastFormatDouble(double input, UnicodeString& output) const {
    +    if (!fCanUseFastFormat) {
    +        return false;
    +    }
    +    auto i32 = static_cast(input);
    +    if (i32 != input || i32 == INT32_MIN) {
    +        return false;
    +    }
    +    doFastFormatInt32(i32, output);
    +    return true;
    +}
    +
    +bool DecimalFormat::fastFormatInt64(int64_t input, UnicodeString& output) const {
    +    if (!fCanUseFastFormat) {
    +        return false;
    +    }
    +    auto i32 = static_cast(input);
    +    if (i32 != input || i32 == INT32_MIN) {
    +        return false;
    +    }
    +    doFastFormatInt32(i32, output);
    +    return true;
    +}
    +
    +void DecimalFormat::doFastFormatInt32(int32_t input, UnicodeString& output) const {
    +    U_ASSERT(fCanUseFastFormat);
    +    if (input < 0) {
    +        output.append(fFastData.cpMinusSign);
    +        U_ASSERT(input != INT32_MIN);  // handled by callers
    +        input = -input;
    +    }
    +    // Cap at int32_t to make the buffer small and operations fast.
    +    // Longest string: "2,147,483,648" (13 chars in length)
    +    static constexpr int32_t localCapacity = 13;
    +    char16_t localBuffer[localCapacity];
    +    char16_t* ptr = localBuffer + localCapacity;
    +    int8_t group = 0;
    +    for (int8_t i = 0; i < fFastData.maxInt && (input != 0 || i < fFastData.minInt); i++) {
    +        std::div_t res = std::div(input, 10);
    +        *(--ptr) = static_cast(fFastData.cpZero + res.rem);
    +        input = res.quot;
    +        if (++group == 3 && fFastData.cpGroupingSeparator != 0) {
    +            *(--ptr) = fFastData.cpGroupingSeparator;
    +            group = 0;
    +        }
    +    }
    +    int32_t len = localCapacity - static_cast(ptr - localBuffer);
    +    output.append(ptr, len);
    +}
    +
     
     #endif /* #if !UCONFIG_NO_FORMATTING */
    diff --git a/icu4c/source/i18n/number_decimfmtprops.cpp b/icu4c/source/i18n/number_decimfmtprops.cpp
    index e56f955f54..b12dccfc0b 100644
    --- a/icu4c/source/i18n/number_decimfmtprops.cpp
    +++ b/icu4c/source/i18n/number_decimfmtprops.cpp
    @@ -6,11 +6,26 @@
     #if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
     
     #include "number_decimfmtprops.h"
    +#include "umutex.h"
     
     using namespace icu;
     using namespace icu::number;
     using namespace icu::number::impl;
     
    +
    +namespace {
    +
    +char kRawDefaultProperties[sizeof(DecimalFormatProperties)];
    +
    +icu::UInitOnce gDefaultPropertiesInitOnce = U_INITONCE_INITIALIZER;
    +
    +void U_CALLCONV initDefaultProperties(UErrorCode&) {
    +    *reinterpret_cast(kRawDefaultProperties) = {}; // set to the default instance
    +}
    +
    +}
    +
    +
     DecimalFormatProperties::DecimalFormatProperties() {
         clear();
     }
    @@ -60,51 +75,70 @@ void DecimalFormatProperties::clear() {
         signAlwaysShown = false;
     }
     
    -bool DecimalFormatProperties::operator==(const DecimalFormatProperties &other) const {
    +bool
    +DecimalFormatProperties::_equals(const DecimalFormatProperties& other, bool ignoreForFastFormat) const {
         bool eq = true;
    +
    +    // Properties that must be equal both normally and for fast-path formatting
         eq = eq && compactStyle == other.compactStyle;
         eq = eq && currency == other.currency;
         eq = eq && currencyPluralInfo.fPtr.getAlias() == other.currencyPluralInfo.fPtr.getAlias();
         eq = eq && currencyUsage == other.currencyUsage;
    -    eq = eq && decimalPatternMatchRequired == other.decimalPatternMatchRequired;
         eq = eq && decimalSeparatorAlwaysShown == other.decimalSeparatorAlwaysShown;
         eq = eq && exponentSignAlwaysShown == other.exponentSignAlwaysShown;
         eq = eq && formatFailIfMoreThanMaxDigits == other.formatFailIfMoreThanMaxDigits;
         eq = eq && formatWidth == other.formatWidth;
    -    eq = eq && groupingSize == other.groupingSize;
    -    eq = eq && groupingUsed == other.groupingUsed;
         eq = eq && magnitudeMultiplier == other.magnitudeMultiplier;
    -    eq = eq && maximumFractionDigits == other.maximumFractionDigits;
    -    eq = eq && maximumIntegerDigits == other.maximumIntegerDigits;
         eq = eq && maximumSignificantDigits == other.maximumSignificantDigits;
         eq = eq && minimumExponentDigits == other.minimumExponentDigits;
         eq = eq && minimumFractionDigits == other.minimumFractionDigits;
         eq = eq && minimumGroupingDigits == other.minimumGroupingDigits;
    -    eq = eq && minimumIntegerDigits == other.minimumIntegerDigits;
         eq = eq && minimumSignificantDigits == other.minimumSignificantDigits;
         eq = eq && multiplier == other.multiplier;
         eq = eq && multiplierScale == other.multiplierScale;
         eq = eq && negativePrefix == other.negativePrefix;
    -    eq = eq && negativePrefixPattern == other.negativePrefixPattern;
         eq = eq && negativeSuffix == other.negativeSuffix;
    -    eq = eq && negativeSuffixPattern == other.negativeSuffixPattern;
         eq = eq && padPosition == other.padPosition;
         eq = eq && padString == other.padString;
    +    eq = eq && positivePrefix == other.positivePrefix;
    +    eq = eq && positiveSuffix == other.positiveSuffix;
    +    eq = eq && roundingIncrement == other.roundingIncrement;
    +    eq = eq && roundingMode == other.roundingMode;
    +    eq = eq && secondaryGroupingSize == other.secondaryGroupingSize;
    +    eq = eq && signAlwaysShown == other.signAlwaysShown;
    +
    +    if (ignoreForFastFormat) {
    +        return eq;
    +    }
    +
    +    // Properties ignored by fast-path formatting
    +    // Formatting (special handling required):
    +    eq = eq && groupingSize == other.groupingSize;
    +    eq = eq && groupingUsed == other.groupingUsed;
    +    eq = eq && maximumFractionDigits == other.maximumFractionDigits;
    +    eq = eq && maximumIntegerDigits == other.maximumIntegerDigits;
    +    eq = eq && minimumIntegerDigits == other.minimumIntegerDigits;
    +    eq = eq && negativePrefixPattern == other.negativePrefixPattern;
    +    eq = eq && negativeSuffixPattern == other.negativeSuffixPattern;
    +    eq = eq && positivePrefixPattern == other.positivePrefixPattern;
    +    eq = eq && positiveSuffixPattern == other.positiveSuffixPattern;
    +
    +    // Parsing (always safe to ignore):
    +    eq = eq && decimalPatternMatchRequired == other.decimalPatternMatchRequired;
         eq = eq && parseCaseSensitive == other.parseCaseSensitive;
         eq = eq && parseIntegerOnly == other.parseIntegerOnly;
         eq = eq && parseMode == other.parseMode;
         eq = eq && parseNoExponent == other.parseNoExponent;
         eq = eq && parseToBigDecimal == other.parseToBigDecimal;
         eq = eq && parseAllInput == other.parseAllInput;
    -    eq = eq && positivePrefix == other.positivePrefix;
    -    eq = eq && positivePrefixPattern == other.positivePrefixPattern;
    -    eq = eq && positiveSuffix == other.positiveSuffix;
    -    eq = eq && positiveSuffixPattern == other.positiveSuffixPattern;
    -    eq = eq && roundingIncrement == other.roundingIncrement;
    -    eq = eq && roundingMode == other.roundingMode;
    -    eq = eq && secondaryGroupingSize == other.secondaryGroupingSize;
    -    eq = eq && signAlwaysShown == other.signAlwaysShown;
    +
         return eq;
     }
     
    +bool DecimalFormatProperties::equalsDefaultExceptFastFormat() const {
    +    UErrorCode localStatus = U_ZERO_ERROR;
    +    umtx_initOnce(gDefaultPropertiesInitOnce, &initDefaultProperties, localStatus);
    +    return _equals(*reinterpret_cast(kRawDefaultProperties), true);
    +}
    +
     #endif /* #if !UCONFIG_NO_FORMATTING */
    diff --git a/icu4c/source/i18n/number_decimfmtprops.h b/icu4c/source/i18n/number_decimfmtprops.h
    index 1c7d88f46c..43f61f579e 100644
    --- a/icu4c/source/i18n/number_decimfmtprops.h
    +++ b/icu4c/source/i18n/number_decimfmtprops.h
    @@ -135,9 +135,20 @@ struct U_I18N_API DecimalFormatProperties {
     
         DecimalFormatProperties();
     
    -    bool operator==(const DecimalFormatProperties& other) const;
    +    inline bool operator==(const DecimalFormatProperties& other) const {
    +        return _equals(other, false);
    +    }
     
         void clear();
    +
    +    /**
    +     * Checks for equality to the default DecimalFormatProperties, but ignores the prescribed set of
    +     * options for fast-path formatting.
    +     */
    +    bool equalsDefaultExceptFastFormat() const;
    +
    +  private:
    +    bool _equals(const DecimalFormatProperties& other, bool ignoreForFastFormat) const;
     };
     
     } // namespace impl
    diff --git a/icu4c/source/i18n/numparse_currency.cpp b/icu4c/source/i18n/numparse_currency.cpp
    index 0848375962..80ec4e7433 100644
    --- a/icu4c/source/i18n/numparse_currency.cpp
    +++ b/icu4c/source/i18n/numparse_currency.cpp
    @@ -29,15 +29,16 @@ CombinedCurrencyMatcher::CombinedCurrencyMatcher(const CurrencySymbols& currency
               fLocaleName(dfs.getLocale().getName(), -1, status) {
         utils::copyCurrencyCode(fCurrencyCode, currencySymbols.getIsoCode());
     
    -    // Compute the full set of characters that could be the first in a currency to allow for
    -    // efficient smoke test.
    -    fLeadCodePoints.add(fCurrency1.char32At(0));
    -    fLeadCodePoints.add(fCurrency2.char32At(0));
    -    fLeadCodePoints.add(beforeSuffixInsert.char32At(0));
    -    uprv_currencyLeads(fLocaleName.data(), fLeadCodePoints, status);
    -    // Always apply case mapping closure for currencies
    -    fLeadCodePoints.closeOver(USET_ADD_CASE_MAPPINGS);
    -    fLeadCodePoints.freeze();
    +    // TODO: Figure out how to make this faster and re-enable.
    +//    // Compute the full set of characters that could be the first in a currency to allow for
    +//    // efficient smoke test.
    +//    fLeadCodePoints.add(fCurrency1.char32At(0));
    +//    fLeadCodePoints.add(fCurrency2.char32At(0));
    +//    fLeadCodePoints.add(beforeSuffixInsert.char32At(0));
    +//    uprv_currencyLeads(fLocaleName.data(), fLeadCodePoints, status);
    +//    // Always apply case mapping closure for currencies
    +//    fLeadCodePoints.closeOver(USET_ADD_CASE_MAPPINGS);
    +//    fLeadCodePoints.freeze();
     }
     
     bool CombinedCurrencyMatcher::match(StringSegment& segment, ParsedNumber& result,
    @@ -124,7 +125,9 @@ bool CombinedCurrencyMatcher::matchCurrency(StringSegment& segment, ParsedNumber
     }
     
     bool CombinedCurrencyMatcher::smokeTest(const StringSegment& segment) const {
    -    return segment.startsWith(fLeadCodePoints);
    +    // TODO: See constructor
    +    return true;
    +    //return segment.startsWith(fLeadCodePoints);
     }
     
     UnicodeString CombinedCurrencyMatcher::toString() const {
    diff --git a/icu4c/source/i18n/numparse_impl.cpp b/icu4c/source/i18n/numparse_impl.cpp
    index cfdf016227..11d8a14ae3 100644
    --- a/icu4c/source/i18n/numparse_impl.cpp
    +++ b/icu4c/source/i18n/numparse_impl.cpp
    @@ -244,6 +244,9 @@ void NumberParserImpl::parse(const UnicodeString& input, bool greedy, ParsedNumb
     
     void NumberParserImpl::parse(const UnicodeString& input, int32_t start, bool greedy, ParsedNumber& result,
                                  UErrorCode& status) const {
    +    if (U_FAILURE(status)) {
    +        return;
    +    }
         U_ASSERT(fFrozen);
         // TODO: Check start >= 0 and start < input.length()
         StringSegment segment(input, 0 != (fParseFlags & PARSE_FLAG_IGNORE_CASE));
    diff --git a/icu4c/source/i18n/plurfmt.cpp b/icu4c/source/i18n/plurfmt.cpp
    index 2775766d32..a22941dc9c 100644
    --- a/icu4c/source/i18n/plurfmt.cpp
    +++ b/icu4c/source/i18n/plurfmt.cpp
    @@ -277,7 +277,8 @@ PluralFormat::format(const Formattable& numberObject, double number,
         UnicodeString numberString;
         auto *decFmt = dynamic_cast(numberFormat);
         if(decFmt != nullptr) {
    -        decFmt->toNumberFormatter().formatImpl(&data, status); // mutates &data
    +        number::LocalizedNumberFormatter formatter;
    +        decFmt->toNumberFormatter(formatter).formatImpl(&data, status); // mutates &data
             numberString = data.string.toUnicodeString();
         } else {
             if (offset == 0) {
    diff --git a/icu4c/source/i18n/unicode/decimfmt.h b/icu4c/source/i18n/unicode/decimfmt.h
    index b562be7714..4203e9de02 100644
    --- a/icu4c/source/i18n/unicode/decimfmt.h
    +++ b/icu4c/source/i18n/unicode/decimfmt.h
    @@ -27,6 +27,7 @@
     #ifndef DECIMFMT_H
     #define DECIMFMT_H
     
    +#include 
     #include "unicode/utypes.h"
     /**
      * \file
    @@ -2057,10 +2058,11 @@ class U_I18N_API DecimalFormat : public NumberFormat {
          * Converts this DecimalFormat to a NumberFormatter.  Starting in ICU 60,
          * NumberFormatter is the recommended way to format numbers.
          *
    -     * @return An instance of LocalizedNumberFormatter with the same behavior as this DecimalFormat.
    +     * @param output The variable into which to store the LocalizedNumberFormatter.
    +     * @return The output variable, for chaining.
          * @draft ICU 62
          */
    -    const number::LocalizedNumberFormatter& toNumberFormatter() const;
    +    number::LocalizedNumberFormatter& toNumberFormatter(number::LocalizedNumberFormatter& output) const;
     
         /**
          * Return the class ID for this class.  This is useful only for
    @@ -2091,10 +2093,10 @@ class U_I18N_API DecimalFormat : public NumberFormat {
       private:
     
         /** Rebuilds the formatter object from the property bag. */
    -    void refreshFormatter(UErrorCode& status);
    +    void touch(UErrorCode& status);
     
         /** Rebuilds the formatter object, hiding the error code. */
    -    void refreshFormatterNoError();
    +    void touchNoError();
     
         /**
          * Updates the property bag with settings from the given pattern.
    @@ -2110,6 +2112,18 @@ class U_I18N_API DecimalFormat : public NumberFormat {
         void setPropertiesFromPattern(const UnicodeString& pattern, int32_t ignoreRounding,
                                       UErrorCode& status);
     
    +    const numparse::impl::NumberParserImpl& getParser(UErrorCode& status) const;
    +
    +    const numparse::impl::NumberParserImpl& getCurrencyParser(UErrorCode& status) const;
    +
    +    void setupFastFormat();
    +
    +    bool fastFormatDouble(double input, UnicodeString& output) const;
    +
    +    bool fastFormatInt64(int64_t input, UnicodeString& output) const;
    +
    +    void doFastFormatInt32(int32_t input, UnicodeString& output) const;
    +
         //=====================================================================================//
         //                                   INSTANCE FIELDS                                   //
         //=====================================================================================//
    @@ -2138,8 +2152,18 @@ class U_I18N_API DecimalFormat : public NumberFormat {
         /** A field for a few additional helper object that need ownership. */
         LocalPointer fWarehouse;
     
    -    LocalPointer fParser;
    -    LocalPointer fParserWithCurrency;
    +    std::atomic fAtomicParser = {};
    +    std::atomic fAtomicCurrencyParser = {};
    +
    +    // Data for fastpath
    +    bool fCanUseFastFormat;
    +    struct FastFormatData {
    +        char16_t cpZero;
    +        char16_t cpGroupingSeparator;
    +        char16_t cpMinusSign;
    +        int8_t minInt;
    +        int8_t maxInt;
    +    } fFastData;
     
         // Allow child class CompactDecimalFormat to access fProperties:
         friend class CompactDecimalFormat;
    
    From f0aadfe714199c3ae28e1a451c661eba08f2ad04 Mon Sep 17 00:00:00 2001
    From: Shane Carr 
    Date: Fri, 20 Apr 2018 01:32:53 +0000
    Subject: [PATCH 118/129] ICU-13634 Fixing lazy-compute call site and other
     minor changes.
    
    X-SVN-Rev: 41252
    ---
     icu4c/source/i18n/currunit.cpp             |  2 +-
     icu4c/source/i18n/decimfmt.cpp             | 52 ++++++++++++++--------
     icu4c/source/i18n/number_patternstring.cpp |  4 +-
     icu4c/source/i18n/number_patternstring.h   |  2 +-
     4 files changed, 38 insertions(+), 22 deletions(-)
    
    diff --git a/icu4c/source/i18n/currunit.cpp b/icu4c/source/i18n/currunit.cpp
    index b65a67c112..3de6856a21 100644
    --- a/icu4c/source/i18n/currunit.cpp
    +++ b/icu4c/source/i18n/currunit.cpp
    @@ -38,7 +38,7 @@ CurrencyUnit::CurrencyUnit(ConstChar16Ptr _isoCode, UErrorCode& ec) {
             isoCodeToUse = _isoCode;
         }
         // TODO: Perform uppercasing here like in ICU4J Currency.getInstance()?
    -    u_strncpy(isoCode, isoCodeToUse, 3);
    +    uprv_memcpy(isoCode, isoCodeToUse, sizeof(UChar) * 3);
         isoCode[3] = 0;
         char simpleIsoCode[4];
         u_UCharsToChars(isoCode, simpleIsoCode, 4);
    diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp
    index 0e0d81fcfc..2133210781 100644
    --- a/icu4c/source/i18n/decimfmt.cpp
    +++ b/icu4c/source/i18n/decimfmt.cpp
    @@ -383,8 +383,8 @@ DecimalFormat& DecimalFormat::operator=(const DecimalFormat& rhs) {
     }
     
     DecimalFormat::~DecimalFormat() {
    -    delete fAtomicParser.load();
    -    delete fAtomicCurrencyParser.load();
    +    delete fAtomicParser.exchange(nullptr);
    +    delete fAtomicCurrencyParser.exchange(nullptr);
     };
     
     Format* DecimalFormat::clone() const {
    @@ -1078,8 +1078,8 @@ void DecimalFormat::touch(UErrorCode& status) {
                                 locale)));
     
         // Delete the parsers if they were made previously
    -    delete fAtomicParser.exchange(nullptr, std::memory_order_relaxed);
    -    delete fAtomicCurrencyParser.exchange(nullptr, std::memory_order_relaxed);
    +    delete fAtomicParser.exchange(nullptr);
    +    delete fAtomicCurrencyParser.exchange(nullptr);
     
         // In order for the getters to work, we need to populate some fields in NumberFormat.
         NumberFormat::setCurrency(fExportedProperties->currency.get(status).getISOCurrency(), status);
    @@ -1104,41 +1104,57 @@ void DecimalFormat::setPropertiesFromPattern(const UnicodeString& pattern, int32
     }
     
     const numparse::impl::NumberParserImpl& DecimalFormat::getParser(UErrorCode& status) const {
    +    // First try to get the pre-computed parser
         auto* ptr = fAtomicParser.load();
         if (ptr != nullptr) {
             return *ptr;
         }
     
    -    ptr = NumberParserImpl::createParserFromProperties(*fProperties, *fSymbols, false, status);
    -
    -    if (ptr == nullptr) {
    +    // Try computing the parser on our own
    +    auto* temp = NumberParserImpl::createParserFromProperties(*fProperties, *fSymbols, false, status);
    +    if (temp == nullptr) {
             status = U_MEMORY_ALLOCATION_ERROR;
    -        // although we still dereference, call sites should be guarded
    +        // although we may still dereference, call sites should be guarded
         }
     
    -    // Store the new pointer, and delete the old one if it got created.
    +    // Note: ptr starts as nullptr; during compare_exchange, it is set to what is actually stored in the
    +    // atomic, which may me temp or may be another object computed by a different thread.
         auto* nonConstThis = const_cast(this);
    -    delete nonConstThis->fAtomicParser.exchange(ptr, std::memory_order_relaxed);
    -    return *ptr;
    +    if (!nonConstThis->fAtomicParser.compare_exchange_strong(ptr, temp)) {
    +        // Another thread beat us to computing the parser
    +        delete temp;
    +        return *ptr;
    +    } else {
    +        // Our copy of the parser got stored in the atomic
    +        return *temp;
    +    }
     }
     
     const numparse::impl::NumberParserImpl& DecimalFormat::getCurrencyParser(UErrorCode& status) const {
    +    // First try to get the pre-computed parser
         auto* ptr = fAtomicCurrencyParser.load();
         if (ptr != nullptr) {
             return *ptr;
         }
     
    -    ptr = NumberParserImpl::createParserFromProperties(*fProperties, *fSymbols, true, status);
    -
    -    if (ptr == nullptr) {
    +    // Try computing the parser on our own
    +    auto* temp = NumberParserImpl::createParserFromProperties(*fProperties, *fSymbols, true, status);
    +    if (temp == nullptr) {
             status = U_MEMORY_ALLOCATION_ERROR;
    -        // although we still dereference, call sites should be guarded
    +        // although we may still dereference, call sites should be guarded
         }
     
    -    // Store the new pointer, and delete the old one if it got created.
    +    // Note: ptr starts as nullptr; during compare_exchange, it is set to what is actually stored in the
    +    // atomic, which may me temp or may be another object computed by a different thread.
         auto* nonConstThis = const_cast(this);
    -    delete nonConstThis->fAtomicCurrencyParser.exchange(ptr, std::memory_order_relaxed);
    -    return *ptr;
    +    if (!nonConstThis->fAtomicCurrencyParser.compare_exchange_strong(ptr, temp)) {
    +        // Another thread beat us to computing the parser
    +        delete temp;
    +        return *ptr;
    +    } else {
    +        // Our copy of the parser got stored in the atomic
    +        return *temp;
    +    }
     }
     
     void DecimalFormat::setupFastFormat() {
    diff --git a/icu4c/source/i18n/number_patternstring.cpp b/icu4c/source/i18n/number_patternstring.cpp
    index f967fb4bba..21bccbab69 100644
    --- a/icu4c/source/i18n/number_patternstring.cpp
    +++ b/icu4c/source/i18n/number_patternstring.cpp
    @@ -865,8 +865,8 @@ int PatternStringUtils::escapePaddingString(UnicodeString input, UnicodeString&
     }
     
     UnicodeString
    -PatternStringUtils::convertLocalized(UnicodeString input, DecimalFormatSymbols symbols, bool toLocalized,
    -                                     UErrorCode& status) {
    +PatternStringUtils::convertLocalized(const UnicodeString& input, const DecimalFormatSymbols& symbols,
    +                                     bool toLocalized, UErrorCode& status) {
         // Construct a table of strings to be converted between localized and standard.
         static constexpr int32_t LEN = 21;
         UnicodeString table[LEN][2];
    diff --git a/icu4c/source/i18n/number_patternstring.h b/icu4c/source/i18n/number_patternstring.h
    index a87cc7415c..fc4100d803 100644
    --- a/icu4c/source/i18n/number_patternstring.h
    +++ b/icu4c/source/i18n/number_patternstring.h
    @@ -264,7 +264,7 @@ class U_I18N_API PatternStringUtils {
          *            notation.
          * @return The pattern expressed in the other notation.
          */
    -    static UnicodeString convertLocalized(UnicodeString input, DecimalFormatSymbols symbols,
    +    static UnicodeString convertLocalized(const UnicodeString& input, const DecimalFormatSymbols& symbols,
                                               bool toLocalized, UErrorCode& status);
     
         /**
    
    From f412770e9d9092cf8909399a0ff341d553f75bed Mon Sep 17 00:00:00 2001
    From: Shane Carr 
    Date: Sat, 21 Apr 2018 06:00:56 +0000
    Subject: [PATCH 119/129] ICU-13634 A few more DecimalFormat optimizations.
    
    X-SVN-Rev: 41257
    ---
     icu4c/source/i18n/decimfmt.cpp                | 163 +++++++++++++-----
     icu4c/source/i18n/measunit.cpp                |   7 +-
     icu4c/source/i18n/number_decimfmtprops.cpp    |   2 +-
     icu4c/source/i18n/unicode/decimfmt.h          |   6 +-
     icu4c/source/test/intltest/numfmtst.cpp       |  48 ++++++
     icu4c/source/test/intltest/numfmtst.h         |   1 +
     .../icu/dev/test/format/MeasureUnitTest.java  |  13 ++
     7 files changed, 191 insertions(+), 49 deletions(-)
    
    diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp
    index 2133210781..febcaebbc1 100644
    --- a/icu4c/source/i18n/decimfmt.cpp
    +++ b/icu4c/source/i18n/decimfmt.cpp
    @@ -100,6 +100,7 @@ DecimalFormat::DecimalFormat(const DecimalFormatSymbols* symbolsToAdopt, UErrorC
     #if UCONFIG_HAVE_PARSEALLINPUT
     
     void DecimalFormat::setParseAllInput(UNumberFormatAttributeValue value) {
    +    if (value == fProperties->parseAllInput) { return; }
         fProperties->parseAllInput = value;
     }
     
    @@ -330,20 +331,24 @@ int32_t DecimalFormat::getAttribute(UNumberFormatAttribute attr, UErrorCode& sta
     }
     
     void DecimalFormat::setGroupingUsed(UBool enabled) {
    +    if (enabled == fProperties->groupingUsed) { return; }
         NumberFormat::setGroupingUsed(enabled); // to set field for compatibility
         fProperties->groupingUsed = enabled;
         touchNoError();
     }
     
     void DecimalFormat::setParseIntegerOnly(UBool value) {
    +    if (value == fProperties->parseIntegerOnly) { return; }
         NumberFormat::setParseIntegerOnly(value); // to set field for compatibility
         fProperties->parseIntegerOnly = value;
         touchNoError();
     }
     
     void DecimalFormat::setLenient(UBool enable) {
    +    ParseMode mode = enable ? PARSE_MODE_LENIENT : PARSE_MODE_STRICT;
    +    if (!fProperties->parseMode.isNull() && mode == fProperties->parseMode.getNoError()) { return; }
         NumberFormat::setLenient(enable); // to set field for compatibility
    -    fProperties->parseMode = enable ? PARSE_MODE_LENIENT : PARSE_MODE_STRICT;
    +    fProperties->parseMode = mode;
         touchNoError();
     }
     
    @@ -363,12 +368,15 @@ DecimalFormat::DecimalFormat(const UnicodeString& pattern, const DecimalFormatSy
     }
     
     DecimalFormat::DecimalFormat(const DecimalFormat& source) : NumberFormat(source) {
    +    // Note: it is not safe to copy fFormatter or fWarehouse directly because fFormatter might have
    +    // dangling pointers to fields inside fWarehouse. The safe thing is to re-construct fFormatter from
    +    // the property bag, despite being somewhat slower.
         fProperties.adoptInstead(new DecimalFormatProperties(*source.fProperties));
    +    fSymbols.adoptInstead(new DecimalFormatSymbols(*source.fSymbols));
         fExportedProperties.adoptInstead(new DecimalFormatProperties());
         fWarehouse.adoptInstead(new DecimalFormatWarehouse());
    -    fSymbols.adoptInstead(new DecimalFormatSymbols(*source.fSymbols));
    -    if (fProperties == nullptr || fExportedProperties == nullptr || fWarehouse == nullptr ||
    -        fSymbols == nullptr) {
    +    if (fProperties == nullptr || fSymbols == nullptr || fExportedProperties == nullptr ||
    +        fWarehouse == nullptr) {
             return;
         }
         touchNoError();
    @@ -536,13 +544,13 @@ void DecimalFormat::parse(const UnicodeString& text, Formattable& output,
         // Note: if this is a currency instance, currencies will be matched despite the fact that we are not in the
         // parseCurrency method (backwards compatibility)
         int32_t startIndex = parsePosition.getIndex();
    -    const NumberParserImpl& parser = getParser(status);
    +    const NumberParserImpl* parser = getParser(status);
         if (U_FAILURE(status)) { return; }
    -    parser.parse(text, startIndex, true, result, status);
    +    parser->parse(text, startIndex, true, result, status);
         // TODO: Do we need to check for fProperties->parseAllInput (UCONFIG_HAVE_PARSEALLINPUT) here?
         if (result.success()) {
             parsePosition.setIndex(result.charEnd);
    -        result.populateFormattable(output, parser.getParseFlags());
    +        result.populateFormattable(output, parser->getParseFlags());
         } else {
             parsePosition.setErrorIndex(startIndex + result.charEnd);
         }
    @@ -558,14 +566,14 @@ CurrencyAmount* DecimalFormat::parseCurrency(const UnicodeString& text, ParsePos
         // Note: if this is a currency instance, currencies will be matched despite the fact that we are not in the
         // parseCurrency method (backwards compatibility)
         int32_t startIndex = parsePosition.getIndex();
    -    const NumberParserImpl& parser = getCurrencyParser(status);
    +    const NumberParserImpl* parser = getCurrencyParser(status);
         if (U_FAILURE(status)) { return nullptr; }
    -    parser.parse(text, startIndex, true, result, status);
    +    parser->parse(text, startIndex, true, result, status);
         // TODO: Do we need to check for fProperties->parseAllInput (UCONFIG_HAVE_PARSEALLINPUT) here?
         if (result.success()) {
             parsePosition.setIndex(result.charEnd);
             Formattable formattable;
    -        result.populateFormattable(formattable, parser.getParseFlags());
    +        result.populateFormattable(formattable, parser->getParseFlags());
             return new CurrencyAmount(formattable, result.currencyCode, status);
         } else {
             parsePosition.setErrorIndex(startIndex + result.charEnd);
    @@ -611,6 +619,7 @@ UnicodeString& DecimalFormat::getPositivePrefix(UnicodeString& result) const {
     }
     
     void DecimalFormat::setPositivePrefix(const UnicodeString& newValue) {
    +    if (newValue == fProperties->positivePrefix) { return; }
         fProperties->positivePrefix = newValue;
         touchNoError();
     }
    @@ -622,6 +631,7 @@ UnicodeString& DecimalFormat::getNegativePrefix(UnicodeString& result) const {
     }
     
     void DecimalFormat::setNegativePrefix(const UnicodeString& newValue) {
    +    if (newValue == fProperties->negativePrefix) { return; }
         fProperties->negativePrefix = newValue;
         touchNoError();
     }
    @@ -633,6 +643,7 @@ UnicodeString& DecimalFormat::getPositiveSuffix(UnicodeString& result) const {
     }
     
     void DecimalFormat::setPositiveSuffix(const UnicodeString& newValue) {
    +    if (newValue == fProperties->positiveSuffix) { return; }
         fProperties->positiveSuffix = newValue;
         touchNoError();
     }
    @@ -644,6 +655,7 @@ UnicodeString& DecimalFormat::getNegativeSuffix(UnicodeString& result) const {
     }
     
     void DecimalFormat::setNegativeSuffix(const UnicodeString& newValue) {
    +    if (newValue == fProperties->negativeSuffix) { return; }
         fProperties->negativeSuffix = newValue;
         touchNoError();
     }
    @@ -653,6 +665,7 @@ UBool DecimalFormat::isSignAlwaysShown() const {
     }
     
     void DecimalFormat::setSignAlwaysShown(UBool value) {
    +    if (value == fProperties->signAlwaysShown) { return; }
         fProperties->signAlwaysShown = value;
         touchNoError();
     }
    @@ -699,6 +712,7 @@ int32_t DecimalFormat::getMultiplierScale() const {
     }
     
     void DecimalFormat::setMultiplierScale(int32_t newValue) {
    +    if (newValue == fProperties->multiplierScale) { return; }
         fProperties->multiplierScale = newValue;
         touchNoError();
     }
    @@ -708,6 +722,7 @@ double DecimalFormat::getRoundingIncrement(void) const {
     }
     
     void DecimalFormat::setRoundingIncrement(double newValue) {
    +    if (newValue == fProperties->roundingIncrement) { return; }
         fProperties->roundingIncrement = newValue;
         touchNoError();
     }
    @@ -718,8 +733,12 @@ ERoundingMode DecimalFormat::getRoundingMode(void) const {
     }
     
     void DecimalFormat::setRoundingMode(ERoundingMode roundingMode) {
    +    auto uRoundingMode = static_cast(roundingMode);
    +    if (!fProperties->roundingMode.isNull() && uRoundingMode == fProperties->roundingMode.getNoError()) {
    +        return;
    +    }
         NumberFormat::setMaximumIntegerDigits(roundingMode); // to set field for compatibility
    -    fProperties->roundingMode = static_cast(roundingMode);
    +    fProperties->roundingMode = uRoundingMode;
         touchNoError();
     }
     
    @@ -728,6 +747,7 @@ int32_t DecimalFormat::getFormatWidth(void) const {
     }
     
     void DecimalFormat::setFormatWidth(int32_t width) {
    +    if (width == fProperties->formatWidth) { return; }
         fProperties->formatWidth = width;
         touchNoError();
     }
    @@ -742,6 +762,7 @@ UnicodeString DecimalFormat::getPadCharacterString() const {
     }
     
     void DecimalFormat::setPadCharacter(const UnicodeString& padChar) {
    +    if (padChar == fProperties->padString) { return; }
         if (padChar.length() > 0) {
             fProperties->padString = UnicodeString(padChar.char32At(0));
         } else {
    @@ -760,7 +781,11 @@ EPadPosition DecimalFormat::getPadPosition(void) const {
     }
     
     void DecimalFormat::setPadPosition(EPadPosition padPos) {
    -    fProperties->padPosition = static_cast(padPos);
    +    auto uPadPos = static_cast(padPos);
    +    if (!fProperties->padPosition.isNull() && uPadPos == fProperties->padPosition.getNoError()) {
    +        return;
    +    }
    +    fProperties->padPosition = uPadPos;
         touchNoError();
     }
     
    @@ -769,6 +794,8 @@ UBool DecimalFormat::isScientificNotation(void) const {
     }
     
     void DecimalFormat::setScientificNotation(UBool useScientific) {
    +    int32_t minExp = useScientific ? 1 : -1;
    +    if (fProperties->minimumExponentDigits == minExp) { return; }
         if (useScientific) {
             fProperties->minimumExponentDigits = 1;
         } else {
    @@ -782,6 +809,7 @@ int8_t DecimalFormat::getMinimumExponentDigits(void) const {
     }
     
     void DecimalFormat::setMinimumExponentDigits(int8_t minExpDig) {
    +    if (minExpDig == fProperties->minimumExponentDigits) { return; }
         fProperties->minimumExponentDigits = minExpDig;
         touchNoError();
     }
    @@ -791,6 +819,7 @@ UBool DecimalFormat::isExponentSignAlwaysShown(void) const {
     }
     
     void DecimalFormat::setExponentSignAlwaysShown(UBool expSignAlways) {
    +    if (expSignAlways == fProperties->exponentSignAlwaysShown) { return; }
         fProperties->exponentSignAlwaysShown = expSignAlways;
         touchNoError();
     }
    @@ -803,6 +832,7 @@ int32_t DecimalFormat::getGroupingSize(void) const {
     }
     
     void DecimalFormat::setGroupingSize(int32_t newValue) {
    +    if (newValue == fProperties->groupingSize) { return; }
         fProperties->groupingSize = newValue;
         touchNoError();
     }
    @@ -816,6 +846,7 @@ int32_t DecimalFormat::getSecondaryGroupingSize(void) const {
     }
     
     void DecimalFormat::setSecondaryGroupingSize(int32_t newValue) {
    +    if (newValue == fProperties->secondaryGroupingSize) { return; }
         fProperties->secondaryGroupingSize = newValue;
         touchNoError();
     }
    @@ -825,6 +856,7 @@ int32_t DecimalFormat::getMinimumGroupingDigits() const {
     }
     
     void DecimalFormat::setMinimumGroupingDigits(int32_t newValue) {
    +    if (newValue == fProperties->minimumGroupingDigits) { return; }
         fProperties->minimumGroupingDigits = newValue;
         touchNoError();
     }
    @@ -834,6 +866,7 @@ UBool DecimalFormat::isDecimalSeparatorAlwaysShown(void) const {
     }
     
     void DecimalFormat::setDecimalSeparatorAlwaysShown(UBool newValue) {
    +    if (newValue == fProperties->decimalSeparatorAlwaysShown) { return; }
         fProperties->decimalSeparatorAlwaysShown = newValue;
         touchNoError();
     }
    @@ -843,6 +876,7 @@ UBool DecimalFormat::isDecimalPatternMatchRequired(void) const {
     }
     
     void DecimalFormat::setDecimalPatternMatchRequired(UBool newValue) {
    +    if (newValue == fProperties->decimalPatternMatchRequired) { return; }
         fProperties->decimalPatternMatchRequired = newValue;
         touchNoError();
     }
    @@ -852,6 +886,7 @@ UBool DecimalFormat::isParseNoExponent() const {
     }
     
     void DecimalFormat::setParseNoExponent(UBool value) {
    +    if (value == fProperties->parseNoExponent) { return; }
         fProperties->parseNoExponent = value;
         touchNoError();
     }
    @@ -861,6 +896,7 @@ UBool DecimalFormat::isParseCaseSensitive() const {
     }
     
     void DecimalFormat::setParseCaseSensitive(UBool value) {
    +    if (value == fProperties->parseCaseSensitive) { return; }
         fProperties->parseCaseSensitive = value;
         touchNoError();
     }
    @@ -870,6 +906,7 @@ UBool DecimalFormat::isFormatFailIfMoreThanMaxDigits() const {
     }
     
     void DecimalFormat::setFormatFailIfMoreThanMaxDigits(UBool value) {
    +    if (value == fProperties->formatFailIfMoreThanMaxDigits) { return; }
         fProperties->formatFailIfMoreThanMaxDigits = value;
         touchNoError();
     }
    @@ -929,6 +966,7 @@ void DecimalFormat::applyLocalizedPattern(const UnicodeString& localizedPattern,
     }
     
     void DecimalFormat::setMaximumIntegerDigits(int32_t newValue) {
    +    if (newValue == fProperties->maximumIntegerDigits) { return; }
         // For backwards compatibility, conflicting min/max need to keep the most recent setting.
         int32_t min = fProperties->minimumIntegerDigits;
         if (min >= 0 && min > newValue) {
    @@ -939,6 +977,7 @@ void DecimalFormat::setMaximumIntegerDigits(int32_t newValue) {
     }
     
     void DecimalFormat::setMinimumIntegerDigits(int32_t newValue) {
    +    if (newValue == fProperties->minimumIntegerDigits) { return; }
         // For backwards compatibility, conflicting min/max need to keep the most recent setting.
         int32_t max = fProperties->maximumIntegerDigits;
         if (max >= 0 && max < newValue) {
    @@ -949,6 +988,7 @@ void DecimalFormat::setMinimumIntegerDigits(int32_t newValue) {
     }
     
     void DecimalFormat::setMaximumFractionDigits(int32_t newValue) {
    +    if (newValue == fProperties->maximumFractionDigits) { return; }
         // For backwards compatibility, conflicting min/max need to keep the most recent setting.
         int32_t min = fProperties->minimumFractionDigits;
         if (min >= 0 && min > newValue) {
    @@ -959,6 +999,7 @@ void DecimalFormat::setMaximumFractionDigits(int32_t newValue) {
     }
     
     void DecimalFormat::setMinimumFractionDigits(int32_t newValue) {
    +    if (newValue == fProperties->minimumFractionDigits) { return; }
         // For backwards compatibility, conflicting min/max need to keep the most recent setting.
         int32_t max = fProperties->maximumFractionDigits;
         if (max >= 0 && max < newValue) {
    @@ -977,6 +1018,7 @@ int32_t DecimalFormat::getMaximumSignificantDigits() const {
     }
     
     void DecimalFormat::setMinimumSignificantDigits(int32_t value) {
    +    if (value == fProperties->minimumSignificantDigits) { return; }
         int32_t max = fProperties->maximumSignificantDigits;
         if (max >= 0 && max < value) {
             fProperties->maximumSignificantDigits = value;
    @@ -986,6 +1028,7 @@ void DecimalFormat::setMinimumSignificantDigits(int32_t value) {
     }
     
     void DecimalFormat::setMaximumSignificantDigits(int32_t value) {
    +    if (value == fProperties->maximumSignificantDigits) { return; }
         int32_t min = fProperties->minimumSignificantDigits;
         if (min >= 0 && min > value) {
             fProperties->minimumSignificantDigits = value;
    @@ -999,20 +1042,26 @@ UBool DecimalFormat::areSignificantDigitsUsed() const {
     }
     
     void DecimalFormat::setSignificantDigitsUsed(UBool useSignificantDigits) {
    -    if (useSignificantDigits) {
    -        // These are the default values from the old implementation.
    -        fProperties->minimumSignificantDigits = 1;
    -        fProperties->maximumSignificantDigits = 6;
    -    } else {
    -        fProperties->minimumSignificantDigits = -1;
    -        fProperties->maximumSignificantDigits = -1;
    +    // These are the default values from the old implementation.
    +    int32_t minSig = useSignificantDigits ? 1 : -1;
    +    int32_t maxSig = useSignificantDigits ? 6 : -1;
    +    if (fProperties->minimumSignificantDigits == minSig &&
    +        fProperties->maximumSignificantDigits == maxSig) {
    +        return;
         }
    +    fProperties->minimumSignificantDigits = minSig;
    +    fProperties->maximumSignificantDigits = maxSig;
         touchNoError();
     }
     
     void DecimalFormat::setCurrency(const char16_t* theCurrency, UErrorCode& ec) {
    +    CurrencyUnit currencyUnit(theCurrency, ec);
    +    if (U_FAILURE(ec)) { return; }
    +    if (!fProperties->currency.isNull() && fProperties->currency.getNoError() == currencyUnit) {
    +        return;
    +    }
         NumberFormat::setCurrency(theCurrency, ec); // to set field for compatibility
    -    fProperties->currency = CurrencyUnit(theCurrency, ec);
    +    fProperties->currency = currencyUnit;
         // TODO: Set values in fSymbols, too?
         touchNoError();
     }
    @@ -1023,6 +1072,9 @@ void DecimalFormat::setCurrency(const char16_t* theCurrency) {
     }
     
     void DecimalFormat::setCurrencyUsage(UCurrencyUsage newUsage, UErrorCode* ec) {
    +    if (!fProperties->currencyUsage.isNull() && newUsage == fProperties->currencyUsage.getNoError()) {
    +        return;
    +    }
         fProperties->currencyUsage = newUsage;
         touch(*ec);
     }
    @@ -1063,20 +1115,22 @@ void DecimalFormat::touch(UErrorCode& status) {
             return;
         }
     
    -    setupFastFormat();
    -
         // In C++, fSymbols is the source of truth for the locale.
         Locale locale = fSymbols->getLocale();
     
         // Note: The formatter is relatively cheap to create, and we need it to populate fExportedProperties,
         // so automatically compute it here. The parser is a bit more expensive and is not needed until the
         // parse method is called, so defer that until needed.
    +    // TODO: Only update the pieces that changed instead of re-computing the whole formatter?
         fFormatter.adoptInstead(
                 new LocalizedNumberFormatter(
                         NumberPropertyMapper::create(
                                 *fProperties, *fSymbols, *fWarehouse, *fExportedProperties, status).locale(
                                 locale)));
     
    +    // Do this after fExportedProperties are set up
    +    setupFastFormat();
    +
         // Delete the parsers if they were made previously
         delete fAtomicParser.exchange(nullptr);
         delete fAtomicCurrencyParser.exchange(nullptr);
    @@ -1103,11 +1157,13 @@ void DecimalFormat::setPropertiesFromPattern(const UnicodeString& pattern, int32
         PatternParser::parseToExistingProperties(pattern, *fProperties, actualIgnoreRounding, status);
     }
     
    -const numparse::impl::NumberParserImpl& DecimalFormat::getParser(UErrorCode& status) const {
    +const numparse::impl::NumberParserImpl* DecimalFormat::getParser(UErrorCode& status) const {
    +    if (U_FAILURE(status)) { return nullptr; }
    +
         // First try to get the pre-computed parser
         auto* ptr = fAtomicParser.load();
         if (ptr != nullptr) {
    -        return *ptr;
    +        return ptr;
         }
     
         // Try computing the parser on our own
    @@ -1118,23 +1174,25 @@ const numparse::impl::NumberParserImpl& DecimalFormat::getParser(UErrorCode& sta
         }
     
         // Note: ptr starts as nullptr; during compare_exchange, it is set to what is actually stored in the
    -    // atomic, which may me temp or may be another object computed by a different thread.
    +    // atomic if another thread beat us to computing the parser object.
         auto* nonConstThis = const_cast(this);
         if (!nonConstThis->fAtomicParser.compare_exchange_strong(ptr, temp)) {
             // Another thread beat us to computing the parser
             delete temp;
    -        return *ptr;
    +        return ptr;
         } else {
             // Our copy of the parser got stored in the atomic
    -        return *temp;
    +        return temp;
         }
     }
     
    -const numparse::impl::NumberParserImpl& DecimalFormat::getCurrencyParser(UErrorCode& status) const {
    +const numparse::impl::NumberParserImpl* DecimalFormat::getCurrencyParser(UErrorCode& status) const {
    +    if (U_FAILURE(status)) { return nullptr; }
    +
         // First try to get the pre-computed parser
         auto* ptr = fAtomicCurrencyParser.load();
         if (ptr != nullptr) {
    -        return *ptr;
    +        return ptr;
         }
     
         // Try computing the parser on our own
    @@ -1145,21 +1203,25 @@ const numparse::impl::NumberParserImpl& DecimalFormat::getCurrencyParser(UErrorC
         }
     
         // Note: ptr starts as nullptr; during compare_exchange, it is set to what is actually stored in the
    -    // atomic, which may me temp or may be another object computed by a different thread.
    +    // atomic if another thread beat us to computing the parser object.
         auto* nonConstThis = const_cast(this);
         if (!nonConstThis->fAtomicCurrencyParser.compare_exchange_strong(ptr, temp)) {
             // Another thread beat us to computing the parser
             delete temp;
    -        return *ptr;
    +        return ptr;
         } else {
             // Our copy of the parser got stored in the atomic
    -        return *temp;
    +        return temp;
         }
     }
     
    +// To debug fast-format, change void(x) to printf(x)
    +#define trace(x) void(x)
    +
     void DecimalFormat::setupFastFormat() {
         // Check the majority of properties:
         if (!fProperties->equalsDefaultExceptFastFormat()) {
    +        trace("no fast format: equality\n");
             fCanUseFastFormat = false;
             return;
         }
    @@ -1173,6 +1235,7 @@ void DecimalFormat::setupFastFormat() {
                 fProperties->negativePrefixPattern.charAt(0) == u'-');
         UBool trivialNS = fProperties->negativeSuffixPattern.isEmpty();
         if (!trivialPP || !trivialPS || !trivialNP || !trivialNS) {
    +        trace("no fast format: affixes\n");
             fCanUseFastFormat = false;
             return;
         }
    @@ -1182,15 +1245,25 @@ void DecimalFormat::setupFastFormat() {
         bool unusualGroupingSize = fProperties->groupingSize > 0 && fProperties->groupingSize != 3;
         const UnicodeString& groupingString = fSymbols->getConstSymbol(DecimalFormatSymbols::kGroupingSeparatorSymbol);
         if (groupingUsed && (unusualGroupingSize || groupingString.length() != 1)) {
    +        trace("no fast format: grouping\n");
             fCanUseFastFormat = false;
             return;
         }
     
         // Integer length:
    -    int32_t minInt = fProperties->minimumIntegerDigits;
    -    int32_t maxInt = fProperties->maximumIntegerDigits;
    +    int32_t minInt = fExportedProperties->minimumIntegerDigits;
    +    int32_t maxInt = fExportedProperties->maximumIntegerDigits;
         // Fastpath supports up to only 10 digits (length of INT32_MIN)
         if (minInt > 10) {
    +        trace("no fast format: integer\n");
    +        fCanUseFastFormat = false;
    +        return;
    +    }
    +
    +    // Fraction length (no fraction part allowed in fast path):
    +    int32_t minFrac = fExportedProperties->minimumFractionDigits;
    +    if (minFrac > 0) {
    +        trace("no fast format: fraction\n");
             fCanUseFastFormat = false;
             return;
         }
    @@ -1199,17 +1272,19 @@ void DecimalFormat::setupFastFormat() {
         const UnicodeString& minusSignString = fSymbols->getConstSymbol(DecimalFormatSymbols::kMinusSignSymbol);
         UChar32 codePointZero = fSymbols->getCodePointZero();
         if (minusSignString.length() != 1 || U16_LENGTH(codePointZero) != 1) {
    +        trace("no fast format: symbols\n");
             fCanUseFastFormat = false;
             return;
         }
     
         // Good to go!
    +    trace("can use fast format!\n");
         fCanUseFastFormat = true;
         fFastData.cpZero = static_cast(codePointZero);
         fFastData.cpGroupingSeparator = groupingUsed ? groupingString.charAt(0) : 0;
         fFastData.cpMinusSign = minusSignString.charAt(0);
    -    fFastData.minInt = static_cast(minInt);
    -    fFastData.maxInt = static_cast(maxInt < 0 ? 127 : maxInt);
    +    fFastData.minInt = (minInt < 0 || minInt > 127) ? 0 : static_cast(minInt);
    +    fFastData.maxInt = (maxInt < 0 || maxInt > 127) ? 127 : static_cast(maxInt);
     }
     
     bool DecimalFormat::fastFormatDouble(double input, UnicodeString& output) const {
    @@ -1220,7 +1295,7 @@ bool DecimalFormat::fastFormatDouble(double input, UnicodeString& output) const
         if (i32 != input || i32 == INT32_MIN) {
             return false;
         }
    -    doFastFormatInt32(i32, output);
    +    doFastFormatInt32(i32, std::signbit(input), output);
         return true;
     }
     
    @@ -1232,13 +1307,13 @@ bool DecimalFormat::fastFormatInt64(int64_t input, UnicodeString& output) const
         if (i32 != input || i32 == INT32_MIN) {
             return false;
         }
    -    doFastFormatInt32(i32, output);
    +    doFastFormatInt32(i32, std::signbit(input), output);
         return true;
     }
     
    -void DecimalFormat::doFastFormatInt32(int32_t input, UnicodeString& output) const {
    +void DecimalFormat::doFastFormatInt32(int32_t input, bool isNegative, UnicodeString& output) const {
         U_ASSERT(fCanUseFastFormat);
    -    if (input < 0) {
    +    if (isNegative) {
             output.append(fFastData.cpMinusSign);
             U_ASSERT(input != INT32_MIN);  // handled by callers
             input = -input;
    @@ -1250,13 +1325,13 @@ void DecimalFormat::doFastFormatInt32(int32_t input, UnicodeString& output) cons
         char16_t* ptr = localBuffer + localCapacity;
         int8_t group = 0;
         for (int8_t i = 0; i < fFastData.maxInt && (input != 0 || i < fFastData.minInt); i++) {
    +        if (group++ == 3 && fFastData.cpGroupingSeparator != 0) {
    +            *(--ptr) = fFastData.cpGroupingSeparator;
    +            group = 1;
    +        }
             std::div_t res = std::div(input, 10);
             *(--ptr) = static_cast(fFastData.cpZero + res.rem);
             input = res.quot;
    -        if (++group == 3 && fFastData.cpGroupingSeparator != 0) {
    -            *(--ptr) = fFastData.cpGroupingSeparator;
    -            group = 0;
    -        }
         }
         int32_t len = localCapacity - static_cast(ptr - localBuffer);
         output.append(ptr, len);
    diff --git a/icu4c/source/i18n/measunit.cpp b/icu4c/source/i18n/measunit.cpp
    index ce6309f796..211d9591e0 100644
    --- a/icu4c/source/i18n/measunit.cpp
    +++ b/icu4c/source/i18n/measunit.cpp
    @@ -557,6 +557,10 @@ static int32_t unitPerUnitToSingleUnit[][4] = {
             {427, 363, 4, 1}
     };
     
    +// Shortcuts to the base unit in order to make the default constructor fast
    +static const int32_t kBaseTypeIdx = 14;
    +static const int32_t kBaseSubTypeIdx = 0;
    +
     MeasureUnit *MeasureUnit::createGForce(UErrorCode &status) {
         return MeasureUnit::create(0, 0, status);
     }
    @@ -1118,7 +1122,8 @@ static int32_t binarySearch(
     
     MeasureUnit::MeasureUnit() {
         fCurrency[0] = 0;
    -    initNoUnit("base");
    +    fTypeId = kBaseTypeIdx;
    +    fSubTypeId = kBaseSubTypeIdx;
     }
     
     MeasureUnit::MeasureUnit(const MeasureUnit &other)
    diff --git a/icu4c/source/i18n/number_decimfmtprops.cpp b/icu4c/source/i18n/number_decimfmtprops.cpp
    index b12dccfc0b..f14670844c 100644
    --- a/icu4c/source/i18n/number_decimfmtprops.cpp
    +++ b/icu4c/source/i18n/number_decimfmtprops.cpp
    @@ -91,7 +91,6 @@ DecimalFormatProperties::_equals(const DecimalFormatProperties& other, bool igno
         eq = eq && magnitudeMultiplier == other.magnitudeMultiplier;
         eq = eq && maximumSignificantDigits == other.maximumSignificantDigits;
         eq = eq && minimumExponentDigits == other.minimumExponentDigits;
    -    eq = eq && minimumFractionDigits == other.minimumFractionDigits;
         eq = eq && minimumGroupingDigits == other.minimumGroupingDigits;
         eq = eq && minimumSignificantDigits == other.minimumSignificantDigits;
         eq = eq && multiplier == other.multiplier;
    @@ -115,6 +114,7 @@ DecimalFormatProperties::_equals(const DecimalFormatProperties& other, bool igno
         // Formatting (special handling required):
         eq = eq && groupingSize == other.groupingSize;
         eq = eq && groupingUsed == other.groupingUsed;
    +    eq = eq && minimumFractionDigits == other.minimumFractionDigits;
         eq = eq && maximumFractionDigits == other.maximumFractionDigits;
         eq = eq && maximumIntegerDigits == other.maximumIntegerDigits;
         eq = eq && minimumIntegerDigits == other.minimumIntegerDigits;
    diff --git a/icu4c/source/i18n/unicode/decimfmt.h b/icu4c/source/i18n/unicode/decimfmt.h
    index 4203e9de02..8e42b0aabd 100644
    --- a/icu4c/source/i18n/unicode/decimfmt.h
    +++ b/icu4c/source/i18n/unicode/decimfmt.h
    @@ -2112,9 +2112,9 @@ class U_I18N_API DecimalFormat : public NumberFormat {
         void setPropertiesFromPattern(const UnicodeString& pattern, int32_t ignoreRounding,
                                       UErrorCode& status);
     
    -    const numparse::impl::NumberParserImpl& getParser(UErrorCode& status) const;
    +    const numparse::impl::NumberParserImpl* getParser(UErrorCode& status) const;
     
    -    const numparse::impl::NumberParserImpl& getCurrencyParser(UErrorCode& status) const;
    +    const numparse::impl::NumberParserImpl* getCurrencyParser(UErrorCode& status) const;
     
         void setupFastFormat();
     
    @@ -2122,7 +2122,7 @@ class U_I18N_API DecimalFormat : public NumberFormat {
     
         bool fastFormatInt64(int64_t input, UnicodeString& output) const;
     
    -    void doFastFormatInt32(int32_t input, UnicodeString& output) const;
    +    void doFastFormatInt32(int32_t input, bool isNegative, UnicodeString& output) const;
     
         //=====================================================================================//
         //                                   INSTANCE FIELDS                                   //
    diff --git a/icu4c/source/test/intltest/numfmtst.cpp b/icu4c/source/test/intltest/numfmtst.cpp
    index 89760cb12d..411cbdb4e3 100644
    --- a/icu4c/source/test/intltest/numfmtst.cpp
    +++ b/icu4c/source/test/intltest/numfmtst.cpp
    @@ -40,6 +40,7 @@
     #include "datadrivennumberformattestsuite.h"
     #include "unicode/msgfmt.h"
     #include "number_decimalquantity.h"
    +#include "unicode/numberformatter.h"
     
     #if (U_PLATFORM == U_PF_AIX) || (U_PLATFORM == U_PF_OS390)
     // These should not be macros. If they are,
    @@ -63,6 +64,7 @@ namespace std {
     #endif
     
     using icu::number::impl::DecimalQuantity;
    +using namespace icu::number;
     
     class NumberFormatTestDataDriven : public DataDrivenNumberFormatTestSuite {
     protected:
    @@ -657,6 +659,7 @@ void NumberFormatTest::runIndexedTest( int32_t index, UBool exec, const char* &n
       TESTCASE_AUTO(Test11318_DoubleConversion);
       TESTCASE_AUTO(TestParsePercentRegression);
       TESTCASE_AUTO(TestMultiplierWithScale);
    +  TESTCASE_AUTO(TestFastFormatInt32);
       TESTCASE_AUTO_END;
     }
     
    @@ -9072,4 +9075,49 @@ void NumberFormatTest::TestMultiplierWithScale() {
         expect2(df, 100, u"50"); // round-trip test
     }
     
    +void NumberFormatTest::TestFastFormatInt32() {
    +    IcuTestErrorCode status(*this, "TestFastFormatInt32");
    +
    +    // The two simplest formatters, old API and new API.
    +    // Old API should use the fastpath for ints.
    +    LocalizedNumberFormatter lnf = NumberFormatter::withLocale("en");
    +    LocalPointer df(NumberFormat::createInstance("en", status));
    +
    +    double nums[] = {
    +            0.0,
    +            -0.0,
    +            NAN,
    +            INFINITY,
    +            0.1,
    +            1.0,
    +            1.1,
    +            2.0,
    +            3.0,
    +            9.0,
    +            10.0,
    +            99.0,
    +            100.0,
    +            999.0,
    +            1000.0,
    +            9999.0,
    +            10000.0,
    +            99999.0,
    +            100000.0,
    +            999999.0,
    +            1000000.0,
    +            static_cast(INT32_MAX) - 1,
    +            static_cast(INT32_MAX),
    +            static_cast(INT32_MAX) + 1,
    +            static_cast(INT32_MIN) - 1,
    +            static_cast(INT32_MIN),
    +            static_cast(INT32_MIN) + 1};
    +
    +    for (auto num : nums) {
    +        UnicodeString expected = lnf.formatDouble(num, status).toString();
    +        UnicodeString actual;
    +        df->format(num, actual);
    +        assertEquals(UnicodeString("d = ") + num, expected, actual);
    +    }
    +}
    +
     #endif /* #if !UCONFIG_NO_FORMATTING */
    diff --git a/icu4c/source/test/intltest/numfmtst.h b/icu4c/source/test/intltest/numfmtst.h
    index a22b93e9eb..7c463a79d5 100644
    --- a/icu4c/source/test/intltest/numfmtst.h
    +++ b/icu4c/source/test/intltest/numfmtst.h
    @@ -224,6 +224,7 @@ class NumberFormatTest: public CalendarTimeZoneTest {
         void Test11318_DoubleConversion();
         void TestParsePercentRegression();
         void TestMultiplierWithScale();
    +    void TestFastFormatInt32();
     
      private:
         UBool testFormattableAsUFormattable(const char *file, int line, Formattable &f);
    diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/MeasureUnitTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/MeasureUnitTest.java
    index 2523297371..0af4f7deb5 100644
    --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/MeasureUnitTest.java
    +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/MeasureUnitTest.java
    @@ -2164,7 +2164,10 @@ public class MeasureUnitTest extends TestFmwk {
             TreeMap> allUnits = getAllUnits();
     
             // Hack: for C++, add NoUnits here, but ignore them when printing the create methods.
    +        // ALso keep track of the base unit offset to make the C++ default constructor faster.
             allUnits.put("none", Arrays.asList(new MeasureUnit[]{NoUnit.BASE, NoUnit.PERCENT, NoUnit.PERMILLE}));
    +        int baseTypeIdx = -1;
    +        int baseSubTypeIdx = -1;
     
             System.out.println("static const int32_t gOffsets[] = {");
             int index = 0;
    @@ -2217,6 +2220,10 @@ public class MeasureUnitTest extends TestFmwk {
                     first = false;
                     measureUnitToOffset.put(unit, offset);
                     measureUnitToTypeSubType.put(unit, Pair.of(typeIdx, subTypeIdx));
    +                if (unit == NoUnit.BASE) {
    +                    baseTypeIdx = typeIdx;
    +                    baseSubTypeIdx = subTypeIdx;
    +                }
                     offset++;
                     subTypeIdx++;
                 }
    @@ -2261,6 +2268,12 @@ public class MeasureUnitTest extends TestFmwk {
             System.out.println("};");
             System.out.println();
     
    +        // Print out the fast-path for the default constructor
    +        System.out.println("// Shortcuts to the base unit in order to make the default constructor fast");
    +        System.out.println("static const int32_t kBaseTypeIdx = " + baseTypeIdx + ";");
    +        System.out.println("static const int32_t kBaseSubTypeIdx = " + baseSubTypeIdx + ";");
    +        System.out.println();
    +
             Map seen = new HashMap();
             for (Map.Entry> entry : allUnits.entrySet()) {
     
    
    From e59eb483143a659d13498b756c91cbbdeed2573c Mon Sep 17 00:00:00 2001
    From: Shane Carr 
    Date: Sat, 21 Apr 2018 08:01:19 +0000
    Subject: [PATCH 120/129] ICU-13634 Refactoring getPrefixSuffix methods.  In
     ICU4C, the pattern modifier is now accessed directly.  In ICU4J, they use the
     same detour through the pipeline code path as before with a TODO to improve
     to be closer to ICU4C.  In addition, in both ICU4C and ICU4J, getPrefixSuffix
     now uses the compiled formatter when available.
    
    X-SVN-Rev: 41258
    ---
     icu4c/source/i18n/decimfmt.cpp                |  8 +--
     icu4c/source/i18n/number_fluent.cpp           | 52 +++++++++++--------
     icu4c/source/i18n/number_formatimpl.cpp       | 31 +++++++----
     icu4c/source/i18n/number_formatimpl.h         | 19 ++++---
     icu4c/source/i18n/number_patternmodifier.cpp  |  9 ++++
     icu4c/source/i18n/number_patternmodifier.h    |  2 +
     icu4c/source/i18n/unicode/numberformatter.h   |  7 ++-
     .../impl/number/MutablePatternModifier.java   | 11 ++++
     .../icu/number/LocalizedNumberFormatter.java  | 51 +++++++++++-------
     .../ibm/icu/number/NumberFormatterImpl.java   | 29 ++++++++---
     .../src/com/ibm/icu/text/DecimalFormat.java   |  8 +--
     .../icu/dev/test/format/NumberFormatTest.java |  2 +-
     12 files changed, 158 insertions(+), 71 deletions(-)
    
    diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp
    index febcaebbc1..b9b95077fc 100644
    --- a/icu4c/source/i18n/decimfmt.cpp
    +++ b/icu4c/source/i18n/decimfmt.cpp
    @@ -614,7 +614,7 @@ void DecimalFormat::setCurrencyPluralInfo(const CurrencyPluralInfo& info) {
     
     UnicodeString& DecimalFormat::getPositivePrefix(UnicodeString& result) const {
         ErrorCode localStatus;
    -    fFormatter->getAffix(true, false, result, localStatus);
    +    fFormatter->getAffixImpl(true, false, result, localStatus);
         return result;
     }
     
    @@ -626,7 +626,7 @@ void DecimalFormat::setPositivePrefix(const UnicodeString& newValue) {
     
     UnicodeString& DecimalFormat::getNegativePrefix(UnicodeString& result) const {
         ErrorCode localStatus;
    -    fFormatter->getAffix(true, true, result, localStatus);
    +    fFormatter->getAffixImpl(true, true, result, localStatus);
         return result;
     }
     
    @@ -638,7 +638,7 @@ void DecimalFormat::setNegativePrefix(const UnicodeString& newValue) {
     
     UnicodeString& DecimalFormat::getPositiveSuffix(UnicodeString& result) const {
         ErrorCode localStatus;
    -    fFormatter->getAffix(false, false, result, localStatus);
    +    fFormatter->getAffixImpl(false, false, result, localStatus);
         return result;
     }
     
    @@ -650,7 +650,7 @@ void DecimalFormat::setPositiveSuffix(const UnicodeString& newValue) {
     
     UnicodeString& DecimalFormat::getNegativeSuffix(UnicodeString& result) const {
         ErrorCode localStatus;
    -    fFormatter->getAffix(false, true, result, localStatus);
    +    fFormatter->getAffixImpl(false, true, result, localStatus);
         return result;
     }
     
    diff --git a/icu4c/source/i18n/number_fluent.cpp b/icu4c/source/i18n/number_fluent.cpp
    index a22ab4de8c..fe9105d65f 100644
    --- a/icu4c/source/i18n/number_fluent.cpp
    +++ b/icu4c/source/i18n/number_fluent.cpp
    @@ -640,6 +640,34 @@ LocalizedNumberFormatter::formatDecimalQuantity(const DecimalQuantity& dq, UErro
     }
     
     void LocalizedNumberFormatter::formatImpl(impl::UFormattedNumberData* results, UErrorCode& status) const {
    +    if (computeCompiled(status)) {
    +        fCompiled->apply(results->quantity, results->string, status);
    +    } else {
    +        NumberFormatterImpl::applyStatic(fMacros, results->quantity, results->string, status);
    +    }
    +}
    +
    +void LocalizedNumberFormatter::getAffixImpl(bool isPrefix, bool isNegative, UnicodeString& result,
    +                                            UErrorCode& status) const {
    +    NumberStringBuilder string;
    +    auto signum = static_cast(isNegative ? -1 : 1);
    +    // Always return affixes for plural form OTHER.
    +    static const StandardPlural::Form plural = StandardPlural::OTHER;
    +    int32_t prefixLength;
    +    if (computeCompiled(status)) {
    +        prefixLength = fCompiled->getPrefixSuffix(signum, plural, string, status);
    +    } else {
    +        prefixLength = NumberFormatterImpl::getPrefixSuffixStatic(fMacros, signum, plural, string, status);
    +    }
    +    result.remove();
    +    if (isPrefix) {
    +        result.append(string.toTempUnicodeString().tempSubStringBetween(0, prefixLength));
    +    } else {
    +        result.append(string.toTempUnicodeString().tempSubStringBetween(prefixLength, string.length()));
    +    }
    +}
    +
    +bool LocalizedNumberFormatter::computeCompiled(UErrorCode& status) const {
         // fUnsafeCallCount contains memory to be interpreted as an atomic int, most commonly
         // std::atomic.  Since the type of atomic int is platform-dependent, we cast the
         // bytes in fUnsafeCallCount to u_atomic_int32_t, a typedef for the platform-dependent
    @@ -666,32 +694,14 @@ void LocalizedNumberFormatter::formatImpl(impl::UFormattedNumberData* results, U
             U_ASSERT(fCompiled == nullptr);
             const_cast(this)->fCompiled = compiled;
             umtx_storeRelease(*callCount, INT32_MIN);
    -        compiled->apply(results->quantity, results->string, status);
    +        return true;
         } else if (currentCount < 0) {
             // The data structure is already built; use it (fast path).
             U_ASSERT(fCompiled != nullptr);
    -        fCompiled->apply(results->quantity, results->string, status);
    +        return true;
         } else {
             // Format the number without building the data structure (slow path).
    -        NumberFormatterImpl::applyStatic(fMacros, results->quantity, results->string, status);
    -    }
    -}
    -
    -void LocalizedNumberFormatter::getAffix(bool isPrefix, bool isNegative, UnicodeString& result,
    -                                        UErrorCode& status) const {
    -    NumberStringBuilder nsb;
    -    DecimalQuantity dq;
    -    if (isNegative) {
    -        dq.setToInt(-1);
    -    } else {
    -        dq.setToInt(1);
    -    }
    -    int prefixLength = NumberFormatterImpl::getPrefixSuffix(fMacros, dq, nsb, status);
    -    result.remove();
    -    if (isPrefix) {
    -        result.append(nsb.toTempUnicodeString().tempSubStringBetween(0, prefixLength));
    -    } else {
    -        result.append(nsb.toTempUnicodeString().tempSubStringBetween(prefixLength, nsb.length()));
    +        return false;
         }
     }
     
    diff --git a/icu4c/source/i18n/number_formatimpl.cpp b/icu4c/source/i18n/number_formatimpl.cpp
    index 885fd3fd3b..10f327cf9a 100644
    --- a/icu4c/source/i18n/number_formatimpl.cpp
    +++ b/icu4c/source/i18n/number_formatimpl.cpp
    @@ -73,10 +73,11 @@ void NumberFormatterImpl::applyStatic(const MacroProps& macros, DecimalQuantity&
         impl.applyUnsafe(inValue, outString, status);
     }
     
    -int32_t NumberFormatterImpl::getPrefixSuffix(const MacroProps& macros, DecimalQuantity& inValue,
    -                                             NumberStringBuilder& outString, UErrorCode& status) {
    +int32_t NumberFormatterImpl::getPrefixSuffixStatic(const MacroProps& macros, int8_t signum,
    +                                                   StandardPlural::Form plural,
    +                                                   NumberStringBuilder& outString, UErrorCode& status) {
         NumberFormatterImpl impl(macros, false, status);
    -    return impl.getPrefixSuffixUnsafe(inValue, outString, status);
    +    return impl.getPrefixSuffixUnsafe(signum, plural, outString, status);
     }
     
     // NOTE: C++ SPECIFIC DIFFERENCE FROM JAVA:
    @@ -101,16 +102,26 @@ void NumberFormatterImpl::applyUnsafe(DecimalQuantity& inValue, NumberStringBuil
         microsToString(fMicros, inValue, outString, status);
     }
     
    -int32_t
    -NumberFormatterImpl::getPrefixSuffixUnsafe(DecimalQuantity& inValue, NumberStringBuilder& outString,
    -                                           UErrorCode& status) {
    +int32_t NumberFormatterImpl::getPrefixSuffix(int8_t signum, StandardPlural::Form plural,
    +                                             NumberStringBuilder& outString, UErrorCode& status) const {
         if (U_FAILURE(status)) { return 0; }
    -    fMicroPropsGenerator->processQuantity(inValue, fMicros, status);
    +    // #13453: DecimalFormat wants the affixes from the pattern only (modMiddle, aka pattern modifier).
    +    // Safe path: use fImmutablePatternModifier.
    +    const Modifier* modifier = fImmutablePatternModifier->getModifier(signum, plural);
    +    modifier->apply(outString, 0, 0, status);
         if (U_FAILURE(status)) { return 0; }
    -    // #13453: DecimalFormat wants the affixes from the pattern only (modMiddle).
    -    fMicros.modMiddle->apply(outString, 0, 0, status);
    +    return modifier->getPrefixLength(status);
    +}
    +
    +int32_t NumberFormatterImpl::getPrefixSuffixUnsafe(int8_t signum, StandardPlural::Form plural,
    +                                                   NumberStringBuilder& outString, UErrorCode& status) {
         if (U_FAILURE(status)) { return 0; }
    -    return fMicros.modMiddle->getPrefixLength(status);
    +    // #13453: DecimalFormat wants the affixes from the pattern only (modMiddle, aka pattern modifier).
    +    // Unsafe path: use fPatternModifier.
    +    fPatternModifier->setNumberProperties(signum, plural);
    +    fPatternModifier->apply(outString, 0, 0, status);
    +    if (U_FAILURE(status)) { return 0; }
    +    return fPatternModifier->getPrefixLength(status);
     }
     
     NumberFormatterImpl::NumberFormatterImpl(const MacroProps& macros, bool safe, UErrorCode& status) {
    diff --git a/icu4c/source/i18n/number_formatimpl.h b/icu4c/source/i18n/number_formatimpl.h
    index ef8ee7064e..4e6c9ca50f 100644
    --- a/icu4c/source/i18n/number_formatimpl.h
    +++ b/icu4c/source/i18n/number_formatimpl.h
    @@ -43,13 +43,20 @@ class NumberFormatterImpl : public UMemory {
          * @return The index into the output at which the prefix ends and the suffix starts; in other words,
          *         the prefix length.
          */
    -    static int32_t getPrefixSuffix(const MacroProps& macros, DecimalQuantity& inValue,
    -                                   NumberStringBuilder& outString, UErrorCode& status);
    +    static int32_t getPrefixSuffixStatic(const MacroProps& macros, int8_t signum,
    +                                         StandardPlural::Form plural, NumberStringBuilder& outString,
    +                                         UErrorCode& status);
     
         /**
          * Evaluates the "safe" MicroPropsGenerator created by "fromMacros".
          */
    -    void apply(DecimalQuantity &inValue, NumberStringBuilder &outString, UErrorCode &status) const;
    +    void apply(DecimalQuantity& inValue, NumberStringBuilder& outString, UErrorCode& status) const;
    +
    +    /**
    +     * Like getPrefixSuffixStatic() but uses the safe compiled object.
    +     */
    +    int32_t getPrefixSuffix(int8_t signum, StandardPlural::Form plural, NumberStringBuilder& outString,
    +                            UErrorCode& status) const;
     
       private:
         // Head of the MicroPropsGenerator linked list:
    @@ -64,7 +71,7 @@ class NumberFormatterImpl : public UMemory {
         LocalPointer fRules;
         LocalPointer fPatternInfo;
         LocalPointer fScientificHandler;
    -    LocalPointer fPatternModifier;
    +    LocalPointer fPatternModifier;
         LocalPointer fImmutablePatternModifier;
         LocalPointer fLongNameHandler;
         LocalPointer fCompactHandler;
    @@ -79,8 +86,8 @@ class NumberFormatterImpl : public UMemory {
     
         void applyUnsafe(DecimalQuantity &inValue, NumberStringBuilder &outString, UErrorCode &status);
     
    -    int32_t getPrefixSuffixUnsafe(DecimalQuantity& inValue, NumberStringBuilder& outString,
    -                                  UErrorCode& status);
    +    int32_t getPrefixSuffixUnsafe(int8_t signum, StandardPlural::Form plural,
    +                                  NumberStringBuilder& outString, UErrorCode& status);
     
         /**
          * If rulesPtr is non-null, return it.  Otherwise, return a PluralRules owned by this object for the
    diff --git a/icu4c/source/i18n/number_patternmodifier.cpp b/icu4c/source/i18n/number_patternmodifier.cpp
    index 5b6c6bdb04..52f0e4ef46 100644
    --- a/icu4c/source/i18n/number_patternmodifier.cpp
    +++ b/icu4c/source/i18n/number_patternmodifier.cpp
    @@ -137,6 +137,15 @@ void ImmutablePatternModifier::applyToMicros(MicroProps& micros, DecimalQuantity
         }
     }
     
    +const Modifier* ImmutablePatternModifier::getModifier(int8_t signum, StandardPlural::Form plural) const {
    +    if (rules == nullptr) {
    +        return pm->getModifier(signum);
    +    } else {
    +        return pm->getModifier(signum, plural);
    +    }
    +}
    +
    +
     /** Used by the unsafe code path. */
     MicroPropsGenerator& MutablePatternModifier::addToChain(const MicroPropsGenerator* parent) {
         this->parent = parent;
    diff --git a/icu4c/source/i18n/number_patternmodifier.h b/icu4c/source/i18n/number_patternmodifier.h
    index 74b6f8b83b..a8c1f22f0b 100644
    --- a/icu4c/source/i18n/number_patternmodifier.h
    +++ b/icu4c/source/i18n/number_patternmodifier.h
    @@ -42,6 +42,8 @@ class U_I18N_API ImmutablePatternModifier : public MicroPropsGenerator, public U
     
         void applyToMicros(MicroProps& micros, DecimalQuantity& quantity) const;
     
    +    const Modifier* getModifier(int8_t signum, StandardPlural::Form plural) const;
    +
       private:
         ImmutablePatternModifier(ParameterizedModifier* pm, const PluralRules* rules,
                                  const MicroPropsGenerator* parent);
    diff --git a/icu4c/source/i18n/unicode/numberformatter.h b/icu4c/source/i18n/unicode/numberformatter.h
    index 5f4a6bb7ac..b3f80afe6d 100644
    --- a/icu4c/source/i18n/unicode/numberformatter.h
    +++ b/icu4c/source/i18n/unicode/numberformatter.h
    @@ -2206,7 +2206,7 @@ class U_I18N_API LocalizedNumberFormatter
         /** Internal method for DecimalFormat compatibility.
          * @internal
          */
    -    void getAffix(bool isPrefix, bool isNegative, UnicodeString& result, UErrorCode& status) const;
    +    void getAffixImpl(bool isPrefix, bool isNegative, UnicodeString& result, UErrorCode& status) const;
     
         /**
          * Internal method for testing.
    @@ -2293,6 +2293,11 @@ class U_I18N_API LocalizedNumberFormatter
     
         LocalizedNumberFormatter(impl::MacroProps &¯os, const Locale &locale);
     
    +    /**
    +     * @return true if the compiled formatter is available.
    +     */
    +    bool computeCompiled(UErrorCode& status) const;
    +
         // To give the fluent setters access to this class's constructor:
         friend class NumberFormatterSettings;
         friend class NumberFormatterSettings;
    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 609259ab07..801af947f0 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
    @@ -244,6 +244,17 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, MicroPr
                     micros.modMiddle = pm.getModifier(quantity.signum(), plural);
                 }
             }
    +
    +        // NOTE: This method is not used in ICU4J right now.
    +        // In ICU4C, it is used by getPrefixSuffix().
    +        // Un-comment this method when getPrefixSuffix() is cleaned up in ICU4J.
    +        // public Modifier getModifier(byte signum, StandardPlural plural) {
    +        // if (rules == null) {
    +        // return pm.getModifier(signum);
    +        // } else {
    +        // return pm.getModifier(signum, plural);
    +        // }
    +        // }
         }
     
         /** Used by the unsafe code path. */
    diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/LocalizedNumberFormatter.java b/icu4j/main/classes/core/src/com/ibm/icu/number/LocalizedNumberFormatter.java
    index 9e1404c961..245447f6da 100644
    --- a/icu4j/main/classes/core/src/com/ibm/icu/number/LocalizedNumberFormatter.java
    +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/LocalizedNumberFormatter.java
    @@ -5,6 +5,7 @@ package com.ibm.icu.number;
     import java.math.BigInteger;
     import java.util.concurrent.atomic.AtomicLongFieldUpdater;
     
    +import com.ibm.icu.impl.StandardPlural;
     import com.ibm.icu.impl.Utility;
     import com.ibm.icu.impl.number.DecimalQuantity;
     import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD;
    @@ -130,19 +131,11 @@ public class LocalizedNumberFormatter extends NumberFormatterSettings
    Date: Sat, 21 Apr 2018 08:55:58 +0000
    Subject: [PATCH 121/129] ICU-13634 Porting some minor ICU4C performance
     enhancements to Java.
    
    X-SVN-Rev: 41259
    ---
     icu4c/source/i18n/numparse_currency.cpp       |  6 ++-
     icu4c/source/i18n/numparse_currency.h         |  3 +-
     .../number/parse/CombinedCurrencyMatcher.java | 41 +++++++++++--------
     .../src/com/ibm/icu/text/DecimalFormat.java   | 28 ++++++++++---
     4 files changed, 52 insertions(+), 26 deletions(-)
    
    diff --git a/icu4c/source/i18n/numparse_currency.cpp b/icu4c/source/i18n/numparse_currency.cpp
    index 80ec4e7433..7b59885072 100644
    --- a/icu4c/source/i18n/numparse_currency.cpp
    +++ b/icu4c/source/i18n/numparse_currency.cpp
    @@ -30,6 +30,8 @@ CombinedCurrencyMatcher::CombinedCurrencyMatcher(const CurrencySymbols& currency
         utils::copyCurrencyCode(fCurrencyCode, currencySymbols.getIsoCode());
     
         // TODO: Figure out how to make this faster and re-enable.
    +    // Computing the "lead code points" set for fastpathing is too slow to use in production.
    +    // See http://bugs.icu-project.org/trac/ticket/13584
     //    // Compute the full set of characters that could be the first in a currency to allow for
     //    // efficient smoke test.
     //    fLeadCodePoints.add(fCurrency1.char32At(0));
    @@ -41,8 +43,8 @@ CombinedCurrencyMatcher::CombinedCurrencyMatcher(const CurrencySymbols& currency
     //    fLeadCodePoints.freeze();
     }
     
    -bool CombinedCurrencyMatcher::match(StringSegment& segment, ParsedNumber& result,
    -                                    UErrorCode& status) const {
    +bool
    +CombinedCurrencyMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const {
         if (result.currencyCode[0] != 0) {
             return false;
         }
    diff --git a/icu4c/source/i18n/numparse_currency.h b/icu4c/source/i18n/numparse_currency.h
    index fa7d67b9bd..63007451af 100644
    --- a/icu4c/source/i18n/numparse_currency.h
    +++ b/icu4c/source/i18n/numparse_currency.h
    @@ -53,7 +53,8 @@ class CombinedCurrencyMatcher : public NumberParseMatcher, public UMemory {
         // Locale has a non-trivial default constructor.
         CharString fLocaleName;
     
    -    UnicodeSet fLeadCodePoints;
    +    // TODO: See comments in constructor in numparse_currency.cpp
    +    // UnicodeSet fLeadCodePoints;
     
         /** Matches the currency string without concern for currency spacing. */
         bool matchCurrency(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const;
    diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CombinedCurrencyMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CombinedCurrencyMatcher.java
    index ee303092b3..b4a1caa1c8 100644
    --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CombinedCurrencyMatcher.java
    +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CombinedCurrencyMatcher.java
    @@ -7,7 +7,6 @@ import java.util.Iterator;
     import com.ibm.icu.impl.StringSegment;
     import com.ibm.icu.impl.TextTrieMap;
     import com.ibm.icu.text.DecimalFormatSymbols;
    -import com.ibm.icu.text.UnicodeSet;
     import com.ibm.icu.util.Currency;
     import com.ibm.icu.util.Currency.CurrencyStringInfo;
     
    @@ -34,7 +33,8 @@ public class CombinedCurrencyMatcher implements NumberParseMatcher {
         private final TextTrieMap longNameTrie;
         private final TextTrieMap symbolTrie;
     
    -    private final UnicodeSet leadCodePoints;
    +    // TODO: See comments in constructor.
    +    // private final UnicodeSet leadCodePoints;
     
         public static CombinedCurrencyMatcher getInstance(Currency currency, DecimalFormatSymbols dfs) {
             // TODO: Cache these instances. They are somewhat expensive.
    @@ -46,27 +46,30 @@ public class CombinedCurrencyMatcher implements NumberParseMatcher {
             this.currency1 = currency.getSymbol(dfs.getULocale());
             this.currency2 = currency.getCurrencyCode();
     
    -        afterPrefixInsert = dfs
    -                .getPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_INSERT, false);
    -        beforeSuffixInsert = dfs
    -                .getPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_INSERT, true);
    +        afterPrefixInsert = dfs.getPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_INSERT,
    +                false);
    +        beforeSuffixInsert = dfs.getPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_INSERT,
    +                true);
     
             // TODO: Currency trie does not currently have an option for case folding. It defaults to use
             // case folding on long-names but not symbols.
             longNameTrie = Currency.getParsingTrie(dfs.getULocale(), Currency.LONG_NAME);
             symbolTrie = Currency.getParsingTrie(dfs.getULocale(), Currency.SYMBOL_NAME);
     
    -        // Compute the full set of characters that could be the first in a currency to allow for
    -        // efficient smoke test.
    -        leadCodePoints = new UnicodeSet();
    -        leadCodePoints.add(currency1.codePointAt(0));
    -        leadCodePoints.add(currency2.codePointAt(0));
    -        leadCodePoints.add(beforeSuffixInsert.codePointAt(0));
    -        longNameTrie.putLeadCodePoints(leadCodePoints);
    -        symbolTrie.putLeadCodePoints(leadCodePoints);
    -        // Always apply case mapping closure for currencies
    -        leadCodePoints.closeOver(UnicodeSet.ADD_CASE_MAPPINGS);
    -        leadCodePoints.freeze();
    +        // TODO: Figure out how to make this faster and re-enable.
    +        // Computing the "lead code points" set for fastpathing is too slow to use in production.
    +        // See http://bugs.icu-project.org/trac/ticket/13584
    +        // // Compute the full set of characters that could be the first in a currency to allow for
    +        // // efficient smoke test.
    +        // leadCodePoints = new UnicodeSet();
    +        // leadCodePoints.add(currency1.codePointAt(0));
    +        // leadCodePoints.add(currency2.codePointAt(0));
    +        // leadCodePoints.add(beforeSuffixInsert.codePointAt(0));
    +        // longNameTrie.putLeadCodePoints(leadCodePoints);
    +        // symbolTrie.putLeadCodePoints(leadCodePoints);
    +        // // Always apply case mapping closure for currencies
    +        // leadCodePoints.closeOver(UnicodeSet.ADD_CASE_MAPPINGS);
    +        // leadCodePoints.freeze();
         }
     
         @Override
    @@ -141,7 +144,9 @@ public class CombinedCurrencyMatcher implements NumberParseMatcher {
     
         @Override
         public boolean smokeTest(StringSegment segment) {
    -        return segment.startsWith(leadCodePoints);
    +        // TODO: See constructor
    +        return true;
    +        // return segment.startsWith(leadCodePoints);
         }
     
         @Override
    diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java
    index 38a89e18b5..1800efc47a 100644
    --- a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java
    +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java
    @@ -289,7 +289,7 @@ public class DecimalFormat extends NumberFormat {
       transient volatile DecimalFormatProperties exportedProperties;
     
       transient volatile NumberParserImpl parser;
    -  transient volatile NumberParserImpl parserWithCurrency;
    +  transient volatile NumberParserImpl currencyParser;
     
       //=====================================================================================//
       //                                    CONSTRUCTORS                                     //
    @@ -818,6 +818,7 @@ public class DecimalFormat extends NumberFormat {
           // Note: if this is a currency instance, currencies will be matched despite the fact that we are not in the
           // parseCurrency method (backwards compatibility)
           int startIndex = parsePosition.getIndex();
    +      NumberParserImpl parser = getParser();
           parser.parse(text, startIndex, true, result);
           if (result.success()) {
               parsePosition.setIndex(result.charEnd);
    @@ -857,11 +858,12 @@ public class DecimalFormat extends NumberFormat {
     
           ParsedNumber result = new ParsedNumber();
           int startIndex = parsePosition.getIndex();
    -      parserWithCurrency.parse(text.toString(), startIndex, true, result);
    +      NumberParserImpl parser = getCurrencyParser();
    +      parser.parse(text.toString(), startIndex, true, result);
           if (result.success()) {
               parsePosition.setIndex(result.charEnd);
               // TODO: Accessing properties here is technically not thread-safe
    -          Number number = result.getNumber(parserWithCurrency.getParseFlags());
    +          Number number = result.getNumber(parser.getParseFlags());
               // Backwards compatibility: return com.ibm.icu.math.BigDecimal
               if (number instanceof java.math.BigDecimal) {
                   number = safeConvertBigDecimal((java.math.BigDecimal) number);
    @@ -2503,8 +2505,24 @@ public class DecimalFormat extends NumberFormat {
         }
         assert locale != null;
         formatter = NumberFormatter.fromDecimalFormat(properties, symbols, exportedProperties).locale(locale);
    -    parser = NumberParserImpl.createParserFromProperties(properties, symbols, false);
    -    parserWithCurrency = NumberParserImpl.createParserFromProperties(properties, symbols, true);
    +
    +    // Lazy-initialize the parsers only when we need them.
    +    parser = null;
    +    currencyParser = null;
    +  }
    +
    +  NumberParserImpl getParser() {
    +    if (parser == null) {
    +      parser = NumberParserImpl.createParserFromProperties(properties, symbols, false);
    +    }
    +    return parser;
    +  }
    +
    +  NumberParserImpl getCurrencyParser() {
    +    if (currencyParser == null) {
    +      currencyParser = NumberParserImpl.createParserFromProperties(properties, symbols, true);
    +    }
    +    return currencyParser;
       }
     
       /**
    
    From f7dddad5c037699ce00b0061cc95e35988c330ae Mon Sep 17 00:00:00 2001
    From: Shane Carr 
    Date: Sat, 21 Apr 2018 09:08:53 +0000
    Subject: [PATCH 122/129] ICU-13634 Removing superfluous abstraction
     "CharSequence" and "UnicodeStringCharSequence" from ICU4C number code.
    
    X-SVN-Rev: 41260
    ---
     icu4c/source/i18n/decimfmt.cpp                | 11 +++---
     icu4c/source/i18n/number_affixutils.cpp       | 30 ++++++++--------
     icu4c/source/i18n/number_affixutils.h         | 22 ++++++------
     icu4c/source/i18n/number_mapper.cpp           | 32 ++++++++---------
     icu4c/source/i18n/number_patternmodifier.cpp  | 21 +++---------
     icu4c/source/i18n/number_patternstring.cpp    | 14 ++++----
     icu4c/source/i18n/number_types.h              | 33 ------------------
     icu4c/source/i18n/number_utils.h              | 34 -------------------
     icu4c/source/i18n/numparse_affixes.cpp        | 14 ++++----
     icu4c/source/i18n/numparse_currency.cpp       |  2 +-
     icu4c/source/i18n/numparse_types.h            | 12 +++----
     .../test/intltest/numbertest_affixutils.cpp   | 24 ++++++-------
     12 files changed, 84 insertions(+), 165 deletions(-)
    
    diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp
    index b9b95077fc..4a42fd1870 100644
    --- a/icu4c/source/i18n/decimfmt.cpp
    +++ b/icu4c/source/i18n/decimfmt.cpp
    @@ -920,13 +920,10 @@ UnicodeString& DecimalFormat::toPattern(UnicodeString& result) const {
         DecimalFormatProperties tprops(*fProperties);
         bool useCurrency = ((!tprops.currency.isNull()) || !tprops.currencyPluralInfo.fPtr.isNull() ||
                             !tprops.currencyUsage.isNull() || AffixUtils::hasCurrencySymbols(
    -            UnicodeStringCharSequence(tprops.positivePrefixPattern), localStatus) ||
    -                        AffixUtils::hasCurrencySymbols(
    -                                UnicodeStringCharSequence(tprops.positiveSuffixPattern), localStatus) ||
    -                        AffixUtils::hasCurrencySymbols(
    -                                UnicodeStringCharSequence(tprops.negativePrefixPattern), localStatus) ||
    -                        AffixUtils::hasCurrencySymbols(
    -                                UnicodeStringCharSequence(tprops.negativeSuffixPattern), localStatus));
    +            tprops.positivePrefixPattern, localStatus) || AffixUtils::hasCurrencySymbols(
    +            tprops.positiveSuffixPattern, localStatus) || AffixUtils::hasCurrencySymbols(
    +            tprops.negativePrefixPattern, localStatus) || AffixUtils::hasCurrencySymbols(
    +            tprops.negativeSuffixPattern, localStatus));
         if (useCurrency) {
             tprops.minimumFractionDigits = fExportedProperties->minimumFractionDigits;
             tprops.maximumFractionDigits = fExportedProperties->maximumFractionDigits;
    diff --git a/icu4c/source/i18n/number_affixutils.cpp b/icu4c/source/i18n/number_affixutils.cpp
    index 2d08414ddd..a61d8cdda4 100644
    --- a/icu4c/source/i18n/number_affixutils.cpp
    +++ b/icu4c/source/i18n/number_affixutils.cpp
    @@ -13,12 +13,12 @@ using namespace icu;
     using namespace icu::number;
     using namespace icu::number::impl;
     
    -int32_t AffixUtils::estimateLength(const CharSequence &patternString, UErrorCode &status) {
    +int32_t AffixUtils::estimateLength(const UnicodeString &patternString, UErrorCode &status) {
         AffixPatternState state = STATE_BASE;
         int32_t offset = 0;
         int32_t length = 0;
         for (; offset < patternString.length();) {
    -        UChar32 cp = patternString.codePointAt(offset);
    +        UChar32 cp = patternString.char32At(offset);
     
             switch (state) {
                 case STATE_BASE:
    @@ -79,12 +79,12 @@ int32_t AffixUtils::estimateLength(const CharSequence &patternString, UErrorCode
         return length;
     }
     
    -UnicodeString AffixUtils::escape(const CharSequence &input) {
    +UnicodeString AffixUtils::escape(const UnicodeString &input) {
         AffixPatternState state = STATE_BASE;
         int32_t offset = 0;
         UnicodeString output;
         for (; offset < input.length();) {
    -        UChar32 cp = input.codePointAt(offset);
    +        UChar32 cp = input.char32At(offset);
     
             switch (cp) {
                 case u'\'':
    @@ -154,7 +154,7 @@ Field AffixUtils::getFieldForType(AffixPatternType type) {
     }
     
     int32_t
    -AffixUtils::unescape(const CharSequence &affixPattern, NumberStringBuilder &output, int32_t position,
    +AffixUtils::unescape(const UnicodeString &affixPattern, NumberStringBuilder &output, int32_t position,
                          const SymbolProvider &provider, UErrorCode &status) {
         int32_t length = 0;
         AffixTag tag;
    @@ -174,7 +174,7 @@ AffixUtils::unescape(const CharSequence &affixPattern, NumberStringBuilder &outp
         return length;
     }
     
    -int32_t AffixUtils::unescapedCodePointCount(const CharSequence &affixPattern,
    +int32_t AffixUtils::unescapedCodePointCount(const UnicodeString &affixPattern,
                                                 const SymbolProvider &provider, UErrorCode &status) {
         int32_t length = 0;
         AffixTag tag;
    @@ -193,7 +193,7 @@ int32_t AffixUtils::unescapedCodePointCount(const CharSequence &affixPattern,
     }
     
     bool
    -AffixUtils::containsType(const CharSequence &affixPattern, AffixPatternType type, UErrorCode &status) {
    +AffixUtils::containsType(const UnicodeString &affixPattern, AffixPatternType type, UErrorCode &status) {
         if (affixPattern.length() == 0) {
             return false;
         }
    @@ -208,7 +208,7 @@ AffixUtils::containsType(const CharSequence &affixPattern, AffixPatternType type
         return false;
     }
     
    -bool AffixUtils::hasCurrencySymbols(const CharSequence &affixPattern, UErrorCode &status) {
    +bool AffixUtils::hasCurrencySymbols(const UnicodeString &affixPattern, UErrorCode &status) {
         if (affixPattern.length() == 0) {
             return false;
         }
    @@ -223,9 +223,9 @@ bool AffixUtils::hasCurrencySymbols(const CharSequence &affixPattern, UErrorCode
         return false;
     }
     
    -UnicodeString AffixUtils::replaceType(const CharSequence &affixPattern, AffixPatternType type,
    +UnicodeString AffixUtils::replaceType(const UnicodeString &affixPattern, AffixPatternType type,
                                           char16_t replacementChar, UErrorCode &status) {
    -    UnicodeString output = affixPattern.toUnicodeString();
    +    UnicodeString output(affixPattern); // copy
         if (affixPattern.length() == 0) {
             return output;
         };
    @@ -240,7 +240,7 @@ UnicodeString AffixUtils::replaceType(const CharSequence &affixPattern, AffixPat
         return output;
     }
     
    -bool AffixUtils::containsOnlySymbolsAndIgnorables(const CharSequence& affixPattern,
    +bool AffixUtils::containsOnlySymbolsAndIgnorables(const UnicodeString& affixPattern,
                                                       const UnicodeSet& ignorables, UErrorCode& status) {
         if (affixPattern.length() == 0) {
             return true;
    @@ -256,7 +256,7 @@ bool AffixUtils::containsOnlySymbolsAndIgnorables(const CharSequence& affixPatte
         return true;
     }
     
    -void AffixUtils::iterateWithConsumer(const CharSequence& affixPattern, TokenConsumer& consumer,
    +void AffixUtils::iterateWithConsumer(const UnicodeString& affixPattern, TokenConsumer& consumer,
                                          UErrorCode& status) {
         if (affixPattern.length() == 0) {
             return;
    @@ -270,11 +270,11 @@ void AffixUtils::iterateWithConsumer(const CharSequence& affixPattern, TokenCons
         }
     }
     
    -AffixTag AffixUtils::nextToken(AffixTag tag, const CharSequence &patternString, UErrorCode &status) {
    +AffixTag AffixUtils::nextToken(AffixTag tag, const UnicodeString &patternString, UErrorCode &status) {
         int32_t offset = tag.offset;
         int32_t state = tag.state;
         for (; offset < patternString.length();) {
    -        UChar32 cp = patternString.codePointAt(offset);
    +        UChar32 cp = patternString.char32At(offset);
             int32_t count = U16_LENGTH(cp);
     
             switch (state) {
    @@ -413,7 +413,7 @@ AffixTag AffixUtils::nextToken(AffixTag tag, const CharSequence &patternString,
         }
     }
     
    -bool AffixUtils::hasNext(const AffixTag &tag, const CharSequence &string) {
    +bool AffixUtils::hasNext(const AffixTag &tag, const UnicodeString &string) {
         // First check for the {-1} and default initializer syntax.
         if (tag.offset < 0) {
             return false;
    diff --git a/icu4c/source/i18n/number_affixutils.h b/icu4c/source/i18n/number_affixutils.h
    index d8b525a6e1..b34420dfee 100644
    --- a/icu4c/source/i18n/number_affixutils.h
    +++ b/icu4c/source/i18n/number_affixutils.h
    @@ -113,7 +113,7 @@ class U_I18N_API AffixUtils {
          * @param patternString The original string whose width will be estimated.
          * @return The length of the unescaped string.
          */
    -    static int32_t estimateLength(const CharSequence& patternString, UErrorCode& status);
    +    static int32_t estimateLength(const UnicodeString& patternString, UErrorCode& status);
     
         /**
          * Takes a string and escapes (quotes) characters that have special meaning in the affix pattern
    @@ -124,7 +124,7 @@ class U_I18N_API AffixUtils {
          * @param input The string to be escaped.
          * @return The resulting UnicodeString.
          */
    -    static UnicodeString escape(const CharSequence& input);
    +    static UnicodeString escape(const UnicodeString& input);
     
         static Field getFieldForType(AffixPatternType type);
     
    @@ -140,7 +140,7 @@ class U_I18N_API AffixUtils {
          * @param position The index into the NumberStringBuilder to insert the string.
          * @param provider An object to generate locale symbols.
          */
    -    static int32_t unescape(const CharSequence& affixPattern, NumberStringBuilder& output,
    +    static int32_t unescape(const UnicodeString& affixPattern, NumberStringBuilder& output,
                                 int32_t position, const SymbolProvider& provider, UErrorCode& status);
     
         /**
    @@ -151,7 +151,7 @@ class U_I18N_API AffixUtils {
          * @param provider An object to generate locale symbols.
          * @return The same return value as if you called {@link #unescape}.
          */
    -    static int32_t unescapedCodePointCount(const CharSequence& affixPattern,
    +    static int32_t unescapedCodePointCount(const UnicodeString& affixPattern,
                                                const SymbolProvider& provider, UErrorCode& status);
     
         /**
    @@ -162,7 +162,7 @@ class U_I18N_API AffixUtils {
          * @param type The token type.
          * @return true if the affix pattern contains the given token type; false otherwise.
          */
    -    static bool containsType(const CharSequence& affixPattern, AffixPatternType type, UErrorCode& status);
    +    static bool containsType(const UnicodeString& affixPattern, AffixPatternType type, UErrorCode& status);
     
         /**
          * Checks whether the specified affix pattern has any unquoted currency symbols ("¤").
    @@ -170,7 +170,7 @@ class U_I18N_API AffixUtils {
          * @param affixPattern The string to check for currency symbols.
          * @return true if the literal has at least one unquoted currency symbol; false otherwise.
          */
    -    static bool hasCurrencySymbols(const CharSequence& affixPattern, UErrorCode& status);
    +    static bool hasCurrencySymbols(const UnicodeString& affixPattern, UErrorCode& status);
     
         /**
          * Replaces all occurrences of tokens with the given type with the given replacement char.
    @@ -180,20 +180,20 @@ class U_I18N_API AffixUtils {
          * @param replacementChar The char to substitute in place of chars of the given token type.
          * @return A string containing the new affix pattern.
          */
    -    static UnicodeString replaceType(const CharSequence& affixPattern, AffixPatternType type,
    +    static UnicodeString replaceType(const UnicodeString& affixPattern, AffixPatternType type,
                                          char16_t replacementChar, UErrorCode& status);
     
         /**
          * Returns whether the given affix pattern contains only symbols and ignorables as defined by the
          * given ignorables set.
          */
    -    static bool containsOnlySymbolsAndIgnorables(const CharSequence& affixPattern,
    +    static bool containsOnlySymbolsAndIgnorables(const UnicodeString& affixPattern,
                                                      const UnicodeSet& ignorables, UErrorCode& status);
     
         /**
          * Iterates over the affix pattern, calling the TokenConsumer for each token.
          */
    -    static void iterateWithConsumer(const CharSequence& affixPattern, TokenConsumer& consumer,
    +    static void iterateWithConsumer(const UnicodeString& affixPattern, TokenConsumer& consumer,
                                         UErrorCode& status);
     
         /**
    @@ -206,7 +206,7 @@ class U_I18N_API AffixUtils {
          *     (never negative), or -1 if there were no more tokens in the affix pattern.
          * @see #hasNext
          */
    -    static AffixTag nextToken(AffixTag tag, const CharSequence& patternString, UErrorCode& status);
    +    static AffixTag nextToken(AffixTag tag, const UnicodeString& patternString, UErrorCode& status);
     
         /**
          * Returns whether the affix pattern string has any more tokens to be retrieved from a call to
    @@ -216,7 +216,7 @@ class U_I18N_API AffixUtils {
          * @param string The affix pattern.
          * @return true if there are more tokens to consume; false otherwise.
          */
    -    static bool hasNext(const AffixTag& tag, const CharSequence& string);
    +    static bool hasNext(const AffixTag& tag, const UnicodeString& string);
     
       private:
         /**
    diff --git a/icu4c/source/i18n/number_mapper.cpp b/icu4c/source/i18n/number_mapper.cpp
    index b2cad1224d..cf48143ced 100644
    --- a/icu4c/source/i18n/number_mapper.cpp
    +++ b/icu4c/source/i18n/number_mapper.cpp
    @@ -326,10 +326,10 @@ void PropertiesAffixPatternProvider::setTo(const DecimalFormatProperties& proper
         // [p/n] => p for positive, n for negative
         // [p/s] => p for prefix, s for suffix
         // [o/p] => o for escaped custom override string, p for pattern string
    -    UnicodeString ppo = AffixUtils::escape(UnicodeStringCharSequence(properties.positivePrefix));
    -    UnicodeString pso = AffixUtils::escape(UnicodeStringCharSequence(properties.positiveSuffix));
    -    UnicodeString npo = AffixUtils::escape(UnicodeStringCharSequence(properties.negativePrefix));
    -    UnicodeString nso = AffixUtils::escape(UnicodeStringCharSequence(properties.negativeSuffix));
    +    UnicodeString ppo = AffixUtils::escape(properties.positivePrefix);
    +    UnicodeString pso = AffixUtils::escape(properties.positiveSuffix);
    +    UnicodeString npo = AffixUtils::escape(properties.negativePrefix);
    +    UnicodeString nso = AffixUtils::escape(properties.negativeSuffix);
         const UnicodeString& ppp = properties.positivePrefixPattern;
         const UnicodeString& psp = properties.positiveSuffixPattern;
         const UnicodeString& npp = properties.negativePrefixPattern;
    @@ -402,8 +402,8 @@ const UnicodeString& PropertiesAffixPatternProvider::getStringInternal(int32_t f
     bool PropertiesAffixPatternProvider::positiveHasPlusSign() const {
         // TODO: Change the internal APIs to propagate out the error?
         ErrorCode localStatus;
    -    return AffixUtils::containsType(UnicodeStringCharSequence(posPrefix), TYPE_PLUS_SIGN, localStatus) ||
    -           AffixUtils::containsType(UnicodeStringCharSequence(posSuffix), TYPE_PLUS_SIGN, localStatus);
    +    return AffixUtils::containsType(posPrefix, TYPE_PLUS_SIGN, localStatus) ||
    +           AffixUtils::containsType(posSuffix, TYPE_PLUS_SIGN, localStatus);
     }
     
     bool PropertiesAffixPatternProvider::hasNegativeSubpattern() const {
    @@ -413,23 +413,23 @@ bool PropertiesAffixPatternProvider::hasNegativeSubpattern() const {
     
     bool PropertiesAffixPatternProvider::negativeHasMinusSign() const {
         ErrorCode localStatus;
    -    return AffixUtils::containsType(UnicodeStringCharSequence(negPrefix), TYPE_MINUS_SIGN, localStatus) ||
    -           AffixUtils::containsType(UnicodeStringCharSequence(negSuffix), TYPE_MINUS_SIGN, localStatus);
    +    return AffixUtils::containsType(negPrefix, TYPE_MINUS_SIGN, localStatus) ||
    +           AffixUtils::containsType(negSuffix, TYPE_MINUS_SIGN, localStatus);
     }
     
     bool PropertiesAffixPatternProvider::hasCurrencySign() const {
         ErrorCode localStatus;
    -    return AffixUtils::hasCurrencySymbols(UnicodeStringCharSequence(posPrefix), localStatus) ||
    -           AffixUtils::hasCurrencySymbols(UnicodeStringCharSequence(posSuffix), localStatus) ||
    -           AffixUtils::hasCurrencySymbols(UnicodeStringCharSequence(negPrefix), localStatus) ||
    -           AffixUtils::hasCurrencySymbols(UnicodeStringCharSequence(negSuffix), localStatus);
    +    return AffixUtils::hasCurrencySymbols(posPrefix, localStatus) ||
    +           AffixUtils::hasCurrencySymbols(posSuffix, localStatus) ||
    +           AffixUtils::hasCurrencySymbols(negPrefix, localStatus) ||
    +           AffixUtils::hasCurrencySymbols(negSuffix, localStatus);
     }
     
     bool PropertiesAffixPatternProvider::containsSymbolType(AffixPatternType type, UErrorCode& status) const {
    -    return AffixUtils::containsType(UnicodeStringCharSequence(posPrefix), type, status) ||
    -           AffixUtils::containsType(UnicodeStringCharSequence(posSuffix), type, status) ||
    -           AffixUtils::containsType(UnicodeStringCharSequence(negPrefix), type, status) ||
    -           AffixUtils::containsType(UnicodeStringCharSequence(negSuffix), type, status);
    +    return AffixUtils::containsType(posPrefix, type, status) ||
    +           AffixUtils::containsType(posSuffix, type, status) ||
    +           AffixUtils::containsType(negPrefix, type, status) ||
    +           AffixUtils::containsType(negSuffix, type, status);
     }
     
     bool PropertiesAffixPatternProvider::hasBody() const {
    diff --git a/icu4c/source/i18n/number_patternmodifier.cpp b/icu4c/source/i18n/number_patternmodifier.cpp
    index 52f0e4ef46..1f88742608 100644
    --- a/icu4c/source/i18n/number_patternmodifier.cpp
    +++ b/icu4c/source/i18n/number_patternmodifier.cpp
    @@ -206,10 +206,7 @@ int32_t MutablePatternModifier::getPrefixLength(UErrorCode& status) const {
     
         // Enter and exit CharSequence Mode to get the length.
         nonConstThis->prepareAffix(true);
    -    int result = AffixUtils::unescapedCodePointCount(
    -            UnicodeStringCharSequence(currentAffix),
    -            *this,
    -            status);  // prefix length
    +    int result = AffixUtils::unescapedCodePointCount(currentAffix, *this, status);  // prefix length
         return result;
     }
     
    @@ -220,15 +217,9 @@ int32_t MutablePatternModifier::getCodePointCount(UErrorCode& status) const {
     
         // Render the affixes to get the length
         nonConstThis->prepareAffix(true);
    -    int result = AffixUtils::unescapedCodePointCount(
    -            UnicodeStringCharSequence(currentAffix),
    -            *this,
    -            status);  // prefix length
    +    int result = AffixUtils::unescapedCodePointCount(currentAffix, *this, status);  // prefix length
         nonConstThis->prepareAffix(false);
    -    result += AffixUtils::unescapedCodePointCount(
    -            UnicodeStringCharSequence(currentAffix),
    -            *this,
    -            status);  // suffix length
    +    result += AffixUtils::unescapedCodePointCount(currentAffix, *this, status);  // suffix length
         return result;
     }
     
    @@ -238,15 +229,13 @@ bool MutablePatternModifier::isStrong() const {
     
     int32_t MutablePatternModifier::insertPrefix(NumberStringBuilder& sb, int position, UErrorCode& status) {
         prepareAffix(true);
    -    int length = AffixUtils::unescape(
    -            UnicodeStringCharSequence(currentAffix), sb, position, *this, status);
    +    int length = AffixUtils::unescape(currentAffix, sb, position, *this, status);
         return length;
     }
     
     int32_t MutablePatternModifier::insertSuffix(NumberStringBuilder& sb, int position, UErrorCode& status) {
         prepareAffix(false);
    -    int length = AffixUtils::unescape(
    -            UnicodeStringCharSequence(currentAffix), sb, position, *this, status);
    +    int length = AffixUtils::unescape(currentAffix, sb, position, *this, status);
         return length;
     }
     
    diff --git a/icu4c/source/i18n/number_patternstring.cpp b/icu4c/source/i18n/number_patternstring.cpp
    index 21bccbab69..3dea96330e 100644
    --- a/icu4c/source/i18n/number_patternstring.cpp
    +++ b/icu4c/source/i18n/number_patternstring.cpp
    @@ -107,7 +107,7 @@ bool ParsedPatternInfo::hasCurrencySign() const {
     }
     
     bool ParsedPatternInfo::containsSymbolType(AffixPatternType type, UErrorCode& status) const {
    -    return AffixUtils::containsType(UnicodeStringCharSequence(pattern), type, status);
    +    return AffixUtils::containsType(pattern, type, status);
     }
     
     bool ParsedPatternInfo::hasBody() const {
    @@ -592,8 +592,8 @@ PatternParser::patternInfoToProperties(DecimalFormatProperties& properties, Pars
         if (positive.hasPadding) {
             // The width of the positive prefix and suffix templates are included in the padding
             int paddingWidth = positive.widthExceptAffixes +
    -                           AffixUtils::estimateLength(UnicodeStringCharSequence(posPrefix), status) +
    -                           AffixUtils::estimateLength(UnicodeStringCharSequence(posSuffix), status);
    +                           AffixUtils::estimateLength(posPrefix, status) +
    +                           AffixUtils::estimateLength(posSuffix, status);
             properties.formatWidth = paddingWidth;
             UnicodeString rawPaddingString = patternInfo.getString(AffixPatternProvider::AFFIX_PADDING);
             if (rawPaddingString.length() == 1) {
    @@ -677,7 +677,7 @@ UnicodeString PatternStringUtils::propertiesToPatternString(const DecimalFormatP
         if (!ppp.isBogus()) {
             sb.append(ppp);
         }
    -    sb.append(AffixUtils::escape(UnicodeStringCharSequence(pp)));
    +    sb.append(AffixUtils::escape(pp));
         int afterPrefixPos = sb.length();
     
         // Figure out the grouping sizes.
    @@ -774,7 +774,7 @@ UnicodeString PatternStringUtils::propertiesToPatternString(const DecimalFormatP
         if (!psp.isBogus()) {
             sb.append(psp);
         }
    -    sb.append(AffixUtils::escape(UnicodeStringCharSequence(ps)));
    +    sb.append(AffixUtils::escape(ps));
     
         // Resolve Padding
         if (paddingWidth != -1 && !paddingLocation.isNull()) {
    @@ -816,7 +816,7 @@ UnicodeString PatternStringUtils::propertiesToPatternString(const DecimalFormatP
             if (!npp.isBogus()) {
                 sb.append(npp);
             }
    -        sb.append(AffixUtils::escape(UnicodeStringCharSequence(np)));
    +        sb.append(AffixUtils::escape(np));
             // Copy the positive digit format into the negative.
             // This is optional; the pattern is the same as if '#' were appended here instead.
             // NOTE: It is not safe to append the UnicodeString to itself, so we need to copy.
    @@ -826,7 +826,7 @@ UnicodeString PatternStringUtils::propertiesToPatternString(const DecimalFormatP
             if (!nsp.isBogus()) {
                 sb.append(nsp);
             }
    -        sb.append(AffixUtils::escape(UnicodeStringCharSequence(ns)));
    +        sb.append(AffixUtils::escape(ns));
         }
     
         return sb;
    diff --git a/icu4c/source/i18n/number_types.h b/icu4c/source/i18n/number_types.h
    index ac7b095932..5aaf538a17 100644
    --- a/icu4c/source/i18n/number_types.h
    +++ b/icu4c/source/i18n/number_types.h
    @@ -87,39 +87,6 @@ enum CompactType {
     };
     
     
    -// TODO: Should this be moved somewhere else, maybe where other ICU classes can use it?
    -// Exported as U_I18N_API because it is a base class for other exported types
    -class U_I18N_API CharSequence {
    -  public:
    -    virtual ~CharSequence() = default;
    -
    -    virtual int32_t length() const = 0;
    -
    -    virtual char16_t charAt(int32_t index) const = 0;
    -
    -    virtual UChar32 codePointAt(int32_t index) const {
    -        // Default implementation; can be overridden with a more efficient version
    -        char16_t leading = charAt(index);
    -        if (U16_IS_LEAD(leading) && length() > index + 1) {
    -            char16_t trailing = charAt(index + 1);
    -            return U16_GET_SUPPLEMENTARY(leading, trailing);
    -        } else {
    -            return leading;
    -        }
    -    }
    -
    -    /**
    -     * Gets a "safe" UnicodeString that can be used even after the CharSequence is destructed.
    -     * */
    -    virtual UnicodeString toUnicodeString() const = 0;
    -
    -    /**
    -     * Gets an "unsafe" UnicodeString that is valid only as long as the CharSequence is alive and
    -     * unchanged. Slightly faster than toUnicodeString().
    -     */
    -    virtual const UnicodeString toTempUnicodeString() const = 0;
    -};
    -
     class U_I18N_API AffixPatternProvider {
       public:
         static const int32_t AFFIX_PLURAL_MASK = 0xff;
    diff --git a/icu4c/source/i18n/number_utils.h b/icu4c/source/i18n/number_utils.h
    index 1b4000d78c..3ac541de02 100644
    --- a/icu4c/source/i18n/number_utils.h
    +++ b/icu4c/source/i18n/number_utils.h
    @@ -20,40 +20,6 @@
     U_NAMESPACE_BEGIN namespace number {
     namespace impl {
     
    -class UnicodeStringCharSequence : public CharSequence {
    -  public:
    -    explicit UnicodeStringCharSequence(const UnicodeString& other) {
    -        fStr = other;
    -    }
    -
    -    ~UnicodeStringCharSequence() U_OVERRIDE = default;
    -
    -    int32_t length() const U_OVERRIDE {
    -        return fStr.length();
    -    }
    -
    -    char16_t charAt(int32_t index) const U_OVERRIDE {
    -        return fStr.charAt(index);
    -    }
    -
    -    UChar32 codePointAt(int32_t index) const U_OVERRIDE {
    -        return fStr.char32At(index);
    -    }
    -
    -    UnicodeString toUnicodeString() const U_OVERRIDE {
    -        // Performs a copy:
    -        return fStr;
    -    }
    -
    -    const UnicodeString toTempUnicodeString() const U_OVERRIDE {
    -        // Readonly alias:
    -        return UnicodeString().fastCopyFrom(fStr);
    -    }
    -
    -  private:
    -    UnicodeString fStr;
    -};
    -
     struct MicroProps : public MicroPropsGenerator {
     
         // NOTE: All of these fields are properly initialized in NumberFormatterImpl.
    diff --git a/icu4c/source/i18n/numparse_affixes.cpp b/icu4c/source/i18n/numparse_affixes.cpp
    index 0ac1fe7c0e..259fab7c6c 100644
    --- a/icu4c/source/i18n/numparse_affixes.cpp
    +++ b/icu4c/source/i18n/numparse_affixes.cpp
    @@ -240,7 +240,7 @@ AffixPatternMatcher AffixPatternMatcher::fromAffixPattern(const UnicodeString& a
         }
     
         AffixPatternMatcherBuilder builder(affixPattern, tokenWarehouse, ignorables);
    -    AffixUtils::iterateWithConsumer(UnicodeStringCharSequence(affixPattern), builder, status);
    +    AffixUtils::iterateWithConsumer(affixPattern, builder, status);
         return builder.build();
     }
     
    @@ -264,13 +264,13 @@ AffixMatcherWarehouse::AffixMatcherWarehouse(AffixTokenMatcherWarehouse* tokenWa
     bool AffixMatcherWarehouse::isInteresting(const AffixPatternProvider& patternInfo,
                                               const IgnorablesMatcher& ignorables, parse_flags_t parseFlags,
                                               UErrorCode& status) {
    -    UnicodeStringCharSequence posPrefixString(patternInfo.getString(AffixPatternProvider::AFFIX_POS_PREFIX));
    -    UnicodeStringCharSequence posSuffixString(patternInfo.getString(AffixPatternProvider::AFFIX_POS_SUFFIX));
    -    UnicodeStringCharSequence negPrefixString(UnicodeString(u""));
    -    UnicodeStringCharSequence negSuffixString(UnicodeString(u""));
    +    UnicodeString posPrefixString = patternInfo.getString(AffixPatternProvider::AFFIX_POS_PREFIX);
    +    UnicodeString posSuffixString = patternInfo.getString(AffixPatternProvider::AFFIX_POS_SUFFIX);
    +    UnicodeString negPrefixString;
    +    UnicodeString negSuffixString;
         if (patternInfo.hasNegativeSubpattern()) {
    -        negPrefixString = UnicodeStringCharSequence(patternInfo.getString(AffixPatternProvider::AFFIX_NEG_PREFIX));
    -        negSuffixString = UnicodeStringCharSequence(patternInfo.getString(AffixPatternProvider::AFFIX_NEG_SUFFIX));
    +        negPrefixString = patternInfo.getString(AffixPatternProvider::AFFIX_NEG_PREFIX);
    +        negSuffixString = patternInfo.getString(AffixPatternProvider::AFFIX_NEG_SUFFIX);
         }
     
         if (0 == (parseFlags & PARSE_FLAG_USE_FULL_AFFIXES) &&
    diff --git a/icu4c/source/i18n/numparse_currency.cpp b/icu4c/source/i18n/numparse_currency.cpp
    index 7b59885072..52bbb51794 100644
    --- a/icu4c/source/i18n/numparse_currency.cpp
    +++ b/icu4c/source/i18n/numparse_currency.cpp
    @@ -126,7 +126,7 @@ bool CombinedCurrencyMatcher::matchCurrency(StringSegment& segment, ParsedNumber
                partialMatchLen == segment.length();
     }
     
    -bool CombinedCurrencyMatcher::smokeTest(const StringSegment& segment) const {
    +bool CombinedCurrencyMatcher::smokeTest(const StringSegment&) const {
         // TODO: See constructor
         return true;
         //return segment.startsWith(fLeadCodePoints);
    diff --git a/icu4c/source/i18n/numparse_types.h b/icu4c/source/i18n/numparse_types.h
    index aa64497545..469d827040 100644
    --- a/icu4c/source/i18n/numparse_types.h
    +++ b/icu4c/source/i18n/numparse_types.h
    @@ -171,7 +171,7 @@ class ParsedNumber {
      *
      * @author sffc
      */
    -class StringSegment : public UMemory, public ::icu::number::impl::CharSequence {
    +class StringSegment : public UMemory {
       public:
         StringSegment(const UnicodeString& str, bool ignoreCase);
     
    @@ -198,15 +198,15 @@ class StringSegment : public UMemory, public ::icu::number::impl::CharSequence {
     
         void resetLength();
     
    -    int32_t length() const override;
    +    int32_t length() const;
     
    -    char16_t charAt(int32_t index) const override;
    +    char16_t charAt(int32_t index) const;
     
    -    UChar32 codePointAt(int32_t index) const override;
    +    UChar32 codePointAt(int32_t index) const;
     
    -    UnicodeString toUnicodeString() const override;
    +    UnicodeString toUnicodeString() const;
     
    -    const UnicodeString toTempUnicodeString() const override;
    +    const UnicodeString toTempUnicodeString() const;
     
         /**
          * Returns the first code point in the string segment, or -1 if the string starts with an invalid
    diff --git a/icu4c/source/test/intltest/numbertest_affixutils.cpp b/icu4c/source/test/intltest/numbertest_affixutils.cpp
    index 1d0dcc710b..cf0bf39944 100644
    --- a/icu4c/source/test/intltest/numbertest_affixutils.cpp
    +++ b/icu4c/source/test/intltest/numbertest_affixutils.cpp
    @@ -77,7 +77,7 @@ void AffixUtilsTest::testEscape() {
         for (auto &cas : cases) {
             UnicodeString input(cas[0]);
             UnicodeString expected(cas[1]);
    -        UnicodeString result = AffixUtils::escape(UnicodeStringCharSequence(input));
    +        UnicodeString result = AffixUtils::escape(input);
             assertEquals(input, expected, result);
         }
     }
    @@ -130,16 +130,16 @@ void AffixUtilsTest::testUnescape() {
             UnicodeString input(cas.input);
             UnicodeString output(cas.output);
     
    -        assertEquals(input, cas.currency, AffixUtils::hasCurrencySymbols(UnicodeStringCharSequence(input), status));
    +        assertEquals(input, cas.currency, AffixUtils::hasCurrencySymbols(input, status));
             assertSuccess("Spot 1", status);
    -        assertEquals(input, cas.expectedLength, AffixUtils::estimateLength(UnicodeStringCharSequence(input), status));
    +        assertEquals(input, cas.expectedLength, AffixUtils::estimateLength(input, status));
             assertSuccess("Spot 2", status);
     
             UnicodeString actual = unescapeWithDefaults(defaultProvider, input, status);
             assertSuccess("Spot 3", status);
             assertEquals(input, output, actual);
     
    -        int32_t ulength = AffixUtils::unescapedCodePointCount(UnicodeStringCharSequence(input), defaultProvider, status);
    +        int32_t ulength = AffixUtils::unescapedCodePointCount(input, defaultProvider, status);
             assertSuccess("Spot 4", status);
             assertEquals(input, output.countChar32(), ulength);
         }
    @@ -165,10 +165,10 @@ void AffixUtilsTest::testContainsReplaceType() {
             UnicodeString output(cas.output);
     
             assertEquals(
    -                input, hasMinusSign, AffixUtils::containsType(UnicodeStringCharSequence(input), TYPE_MINUS_SIGN, status));
    +                input, hasMinusSign, AffixUtils::containsType(input, TYPE_MINUS_SIGN, status));
             assertSuccess("Spot 1", status);
             assertEquals(
    -                input, output, AffixUtils::replaceType(UnicodeStringCharSequence(input), TYPE_MINUS_SIGN, u'+', status));
    +                input, output, AffixUtils::replaceType(input, TYPE_MINUS_SIGN, u'+', status));
             assertSuccess("Spot 2", status);
         }
     }
    @@ -185,11 +185,11 @@ void AffixUtilsTest::testInvalid() {
             UnicodeString str(strPtr);
     
             status = U_ZERO_ERROR;
    -        AffixUtils::hasCurrencySymbols(UnicodeStringCharSequence(str), status);
    +        AffixUtils::hasCurrencySymbols(str, status);
             assertEquals("Should set error code spot 1", status, U_ILLEGAL_ARGUMENT_ERROR);
     
             status = U_ZERO_ERROR;
    -        AffixUtils::estimateLength(UnicodeStringCharSequence(str), status);
    +        AffixUtils::estimateLength(str, status);
             assertEquals("Should set error code spot 2", status, U_ILLEGAL_ARGUMENT_ERROR);
     
             status = U_ZERO_ERROR;
    @@ -219,11 +219,11 @@ void AffixUtilsTest::testUnescapeWithSymbolProvider() {
     
         UErrorCode status = U_ZERO_ERROR;
         NumberStringBuilder sb;
    -    for (auto cas : cases) {
    +    for (auto& cas : cases) {
             UnicodeString input(cas[0]);
             UnicodeString expected(cas[1]);
             sb.clear();
    -        AffixUtils::unescape(UnicodeStringCharSequence(input), sb, 0, provider, status);
    +        AffixUtils::unescape(input, sb, 0, provider, status);
             assertSuccess("Spot 1", status);
             assertEquals(input, expected, sb.toUnicodeString());
             assertEquals(input, expected, sb.toTempUnicodeString());
    @@ -233,7 +233,7 @@ void AffixUtilsTest::testUnescapeWithSymbolProvider() {
         sb.clear();
         sb.append(u"abcdefg", UNUM_FIELD_COUNT, status);
         assertSuccess("Spot 2", status);
    -    AffixUtils::unescape(UnicodeStringCharSequence(UnicodeString(u"-+%")), sb, 4, provider, status);
    +    AffixUtils::unescape(u"-+%", sb, 4, provider, status);
         assertSuccess("Spot 3", status);
         assertEquals(u"Symbol provider into middle", u"abcd123efg", sb.toUnicodeString());
     }
    @@ -241,7 +241,7 @@ void AffixUtilsTest::testUnescapeWithSymbolProvider() {
     UnicodeString AffixUtilsTest::unescapeWithDefaults(const SymbolProvider &defaultProvider,
                                                               UnicodeString input, UErrorCode &status) {
         NumberStringBuilder nsb;
    -    int32_t length = AffixUtils::unescape(UnicodeStringCharSequence(input), nsb, 0, defaultProvider, status);
    +    int32_t length = AffixUtils::unescape(input, nsb, 0, defaultProvider, status);
         assertEquals("Return value of unescape", nsb.length(), length);
         return nsb.toUnicodeString();
     }
    
    From 328730604bd3a91abf33ffdcea15a193352e745c Mon Sep 17 00:00:00 2001
    From: Shane Carr 
    Date: Mon, 23 Apr 2018 21:16:24 +0000
    Subject: [PATCH 123/129] ICU-13634 Fixing grouping size fallback in fast path.
    
    X-SVN-Rev: 41262
    ---
     icu4c/source/i18n/decimfmt.cpp | 5 +++--
     1 file changed, 3 insertions(+), 2 deletions(-)
    
    diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp
    index 4a42fd1870..b53b82b6a6 100644
    --- a/icu4c/source/i18n/decimfmt.cpp
    +++ b/icu4c/source/i18n/decimfmt.cpp
    @@ -1239,7 +1239,8 @@ void DecimalFormat::setupFastFormat() {
     
         // Grouping (secondary grouping is forbidden in equalsDefaultExceptFastFormat):
         bool groupingUsed = fProperties->groupingUsed;
    -    bool unusualGroupingSize = fProperties->groupingSize > 0 && fProperties->groupingSize != 3;
    +    int32_t groupingSize = fProperties->groupingSize;
    +    bool unusualGroupingSize = groupingSize > 0 && groupingSize != 3;
         const UnicodeString& groupingString = fSymbols->getConstSymbol(DecimalFormatSymbols::kGroupingSeparatorSymbol);
         if (groupingUsed && (unusualGroupingSize || groupingString.length() != 1)) {
             trace("no fast format: grouping\n");
    @@ -1278,7 +1279,7 @@ void DecimalFormat::setupFastFormat() {
         trace("can use fast format!\n");
         fCanUseFastFormat = true;
         fFastData.cpZero = static_cast(codePointZero);
    -    fFastData.cpGroupingSeparator = groupingUsed ? groupingString.charAt(0) : 0;
    +    fFastData.cpGroupingSeparator = groupingUsed && groupingSize == 3 ? groupingString.charAt(0) : 0;
         fFastData.cpMinusSign = minusSignString.charAt(0);
         fFastData.minInt = (minInt < 0 || minInt > 127) ? 0 : static_cast(minInt);
         fFastData.maxInt = (maxInt < 0 || maxInt > 127) ? 127 : static_cast(maxInt);
    
    From 16aedd5e1a683e75f88726c291cf8580a04677db Mon Sep 17 00:00:00 2001
    From: Shane Carr 
    Date: Mon, 23 Apr 2018 21:16:52 +0000
    Subject: [PATCH 124/129] ICU-13670 Changing CFLAGS to C11 and fixing check for
     unicode string literals.
    
    X-SVN-Rev: 41263
    ---
     icu4c/source/acinclude.m4      |  6 +++---
     icu4c/source/config/mh-solaris |  2 +-
     icu4c/source/configure         | 15 +++++----------
     icu4c/source/configure.ac      |  9 ++-------
     4 files changed, 11 insertions(+), 21 deletions(-)
    
    diff --git a/icu4c/source/acinclude.m4 b/icu4c/source/acinclude.m4
    index 5c8fda71de..507f41f521 100644
    --- a/icu4c/source/acinclude.m4
    +++ b/icu4c/source/acinclude.m4
    @@ -464,12 +464,12 @@ AC_DEFUN([AC_CHECK_STRICT_COMPILE],
             then
                 case "${host}" in
                 *-*-solaris*)
    -                # Don't use -std=c99 on Solaris because of timezone check fails
    +                # Don't use -std=c11 on Solaris because of timezone check fails
                     ;;
                 *)
                     # Do not use -ansi. It limits us to C90, and it breaks some platforms.
    -                # We use -std=c99 to disable the gnu99 defaults and its associated warnings
    -                CFLAGS="$CFLAGS -std=c99"
    +                # We use -std=c11 to disable the gnu99 defaults and its associated warnings
    +                CFLAGS="$CFLAGS -std=c11"
                     ;;
                 esac
                 
    diff --git a/icu4c/source/config/mh-solaris b/icu4c/source/config/mh-solaris
    index e1d4ccf900..3d7e6fe79f 100644
    --- a/icu4c/source/config/mh-solaris
    +++ b/icu4c/source/config/mh-solaris
    @@ -7,7 +7,7 @@
     
     ## Flags for ICU 59+
     CXXFLAGS += -std=c++11
    -CFLAGS   += -std=c99
    +CFLAGS   += -std=c11
     
     ## Flags for position independent code
     SHAREDLIBCFLAGS = -KPIC
    diff --git a/icu4c/source/configure b/icu4c/source/configure
    index 56f87c5527..6b7c9c430e 100755
    --- a/icu4c/source/configure
    +++ b/icu4c/source/configure
    @@ -4354,12 +4354,12 @@ $as_echo "$ac_use_strict_options" >&6; }
             then
                 case "${host}" in
                 *-*-solaris*)
    -                # Don't use -std=c99 on Solaris because of timezone check fails
    +                # Don't use -std=c11 on Solaris because of timezone check fails
                     ;;
                 *)
                     # Do not use -ansi. It limits us to C90, and it breaks some platforms.
    -                # We use -std=c99 to disable the gnu99 defaults and its associated warnings
    -                CFLAGS="$CFLAGS -std=c99"
    +                # We use -std=c11 to disable the gnu99 defaults and its associated warnings
    +                CFLAGS="$CFLAGS -std=c11"
                     ;;
                 esac
     
    @@ -7473,15 +7473,13 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
         ;;
     esac
     
    -# GCC >= 4.4 supports UTF16 string literals. The CFLAGS and CXXFLAGS may change in the future.
    +# GCC >= 4.4 supports UTF16 string literals. As of ICU 62, both C and C++ files require them.
     if test "$CHECK_UTF16_STRING_RESULT" = "unknown"; then
         if test "$GCC" = yes; then
    -        OLD_CFLAGS="${CFLAGS}"
    -        CFLAGS="${CFLAGS} -std=gnu99"
             cat confdefs.h - <<_ACEOF >conftest.$ac_ext
     /* end confdefs.h.  */
     
    -static const char16_t test[] = u"This is a UTF16 literal string.";
    +static const unsigned short test[] = u"This is a UTF16 literal string.";
     
     int
     main ()
    @@ -7498,10 +7496,7 @@ else
     fi
     rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
             if test "$CC_UTF16_STRING" = 1; then
    -            UCONFIG_CFLAGS="${UCONFIG_CFLAGS} -std=gnu99"
                 CHECK_UTF16_STRING_RESULT="C only";
    -        else
    -            CFLAGS="${OLD_CFLAGS}"
             fi
         fi
         if test "$GXX" = yes; then
    diff --git a/icu4c/source/configure.ac b/icu4c/source/configure.ac
    index a2fdf84ab2..a05f4db4bf 100644
    --- a/icu4c/source/configure.ac
    +++ b/icu4c/source/configure.ac
    @@ -1038,19 +1038,14 @@ case "${host}" in
         ;;
     esac
     
    -# GCC >= 4.4 supports UTF16 string literals. The CFLAGS and CXXFLAGS may change in the future.
    +# GCC >= 4.4 supports UTF16 string literals. As of spring 2018, all tested compilers support them.
     if test "$CHECK_UTF16_STRING_RESULT" = "unknown"; then
         if test "$GCC" = yes; then
    -        OLD_CFLAGS="${CFLAGS}"
    -        CFLAGS="${CFLAGS} -std=gnu99"
             AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
    -static const char16_t test[] = u"This is a UTF16 literal string.";
    +static const unsigned short test[] = u"This is a UTF16 literal string.";
             ]], [[]])],[CC_UTF16_STRING=1],[CC_UTF16_STRING=0])
             if test "$CC_UTF16_STRING" = 1; then
    -            UCONFIG_CFLAGS="${UCONFIG_CFLAGS} -std=gnu99"
                 CHECK_UTF16_STRING_RESULT="C only";
    -        else
    -            CFLAGS="${OLD_CFLAGS}"
             fi
         fi
         if test "$GXX" = yes; then
    
    From b2be6b57a9221c6ced05d741c39f4d15ecb530d7 Mon Sep 17 00:00:00 2001
    From: Shane Carr 
    Date: Mon, 23 Apr 2018 22:42:51 +0000
    Subject: [PATCH 125/129] ICU-13597 Adding comments about thread safety to
     UNumberFormatter.h
    
    X-SVN-Rev: 41264
    ---
     icu4c/source/i18n/unicode/unumberformatter.h | 13 +++++++++++++
     1 file changed, 13 insertions(+)
    
    diff --git a/icu4c/source/i18n/unicode/unumberformatter.h b/icu4c/source/i18n/unicode/unumberformatter.h
    index ebd0585b33..acf5c0f1cf 100644
    --- a/icu4c/source/i18n/unicode/unumberformatter.h
    +++ b/icu4c/source/i18n/unicode/unumberformatter.h
    @@ -416,6 +416,8 @@ typedef struct UFormattedNumber UFormattedNumber;
      * Creates a new UNumberFormatter from the given skeleton string and locale. This is currently the only
      * method for creating a new UNumberFormatter.
      *
    + * Objects of type UNumberFormatter returned by this method are threadsafe.
    + *
      * For more details on skeleton strings, see the documentation in numberformatter.h. For more details on
      * the usage of this API, see the documentation at the top of unumberformatter.h.
      *
    @@ -435,6 +437,8 @@ unumf_openFromSkeletonAndLocale(const UChar* skeleton, int32_t skeletonLen, cons
     /**
      * Creates a new UFormattedNumber for holding the result of a number formatting operation.
      *
    + * Objects of type UFormattedNumber are not guaranteed to be threadsafe.
    + *
      * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead.
      *
      * @param ec Set if an error occurs.
    @@ -448,6 +452,9 @@ unumf_openResult(UErrorCode* ec);
      * Uses a UNumberFormatter to format an integer to a UFormattedNumber. A string, field position, and other
      * information can be retrieved from the UFormattedNumber.
      *
    + * The UNumberFormatter can be shared between threads. Each thread should have its own local
    + * UFormattedNumber, however, for storing the result of the formatting operation.
    + *
      * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead.
      *
      * @param uformatter A formatter object created by unumf_openFromSkeletonAndLocale or similar.
    @@ -465,6 +472,9 @@ unumf_formatInt(const UNumberFormatter* uformatter, int64_t value, UFormattedNum
      * Uses a UNumberFormatter to format a double to a UFormattedNumber. A string, field position, and other
      * information can be retrieved from the UFormattedNumber.
      *
    + * The UNumberFormatter can be shared between threads. Each thread should have its own local
    + * UFormattedNumber, however, for storing the result of the formatting operation.
    + *
      * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead.
      *
      * @param uformatter A formatter object created by unumf_openFromSkeletonAndLocale or similar.
    @@ -482,6 +492,9 @@ unumf_formatDouble(const UNumberFormatter* uformatter, double value, UFormattedN
      * Uses a UNumberFormatter to format a decimal number to a UFormattedNumber. A string, field position, and
      * other information can be retrieved from the UFormattedNumber.
      *
    + * The UNumberFormatter can be shared between threads. Each thread should have its own local
    + * UFormattedNumber, however, for storing the result of the formatting operation.
    + *
      * The syntax of the unformatted number is a "numeric string" as defined in the Decimal Arithmetic
      * Specification, available at http://speleotrove.com/decimal
      *
    
    From 8fab60c93b9c4915f11082f5219b7d38f8b58831 Mon Sep 17 00:00:00 2001
    From: Shane Carr 
    Date: Mon, 23 Apr 2018 22:48:52 +0000
    Subject: [PATCH 126/129] ICU-13634 Syncing MSVC build files with Makefiles.
    
    X-SVN-Rev: 41265
    ---
     icu4c/source/common/common.vcxproj            |   2 +
     icu4c/source/common/common.vcxproj.filters    |   6 +
     icu4c/source/common/common_uwp.vcxproj        |   2 +
     icu4c/source/i18n/dcfmtimp.h                  |   2 +
     icu4c/source/i18n/i18n.vcxproj                |  63 +++---
     icu4c/source/i18n/i18n.vcxproj.filters        | 205 +++++++++---------
     icu4c/source/i18n/i18n_uwp.vcxproj            |  63 +++---
     icu4c/source/i18n/significantdigitinterval.h  |  92 --------
     icu4c/source/test/cintltst/cintltst.vcxproj   |   1 +
     .../test/cintltst/cintltst.vcxproj.filters    |   3 +
     icu4c/source/test/intltest/intltest.vcxproj   |   7 +-
     .../test/intltest/intltest.vcxproj.filters    |  21 +-
     12 files changed, 199 insertions(+), 268 deletions(-)
     delete mode 100644 icu4c/source/i18n/significantdigitinterval.h
    
    diff --git a/icu4c/source/common/common.vcxproj b/icu4c/source/common/common.vcxproj
    index 833807fc38..894494d686 100644
    --- a/icu4c/source/common/common.vcxproj
    +++ b/icu4c/source/common/common.vcxproj
    @@ -334,6 +334,7 @@
         
         
         
    +    
         
         
         
    @@ -439,6 +440,7 @@
         
         
         
    +    
       
       
         
    diff --git a/icu4c/source/common/common.vcxproj.filters b/icu4c/source/common/common.vcxproj.filters
    index f13253be5c..3731c33d1f 100644
    --- a/icu4c/source/common/common.vcxproj.filters
    +++ b/icu4c/source/common/common.vcxproj.filters
    @@ -607,6 +607,9 @@
         
           bidi
         
    +    
    +      formatting
    +    
       
       
         
    @@ -936,6 +939,9 @@
         
           bidi
         
    +    
    +      formatting
    +    
       
       
         
    diff --git a/icu4c/source/common/common_uwp.vcxproj b/icu4c/source/common/common_uwp.vcxproj
    index af2f6538ba..690ed860f4 100644
    --- a/icu4c/source/common/common_uwp.vcxproj
    +++ b/icu4c/source/common/common_uwp.vcxproj
    @@ -459,6 +459,7 @@
         
         
         
    +    
       
       
         
    @@ -565,6 +566,7 @@
         
         
         
    +    
       
       
         
    diff --git a/icu4c/source/i18n/dcfmtimp.h b/icu4c/source/i18n/dcfmtimp.h
    index e582efb344..387fad596a 100644
    --- a/icu4c/source/i18n/dcfmtimp.h
    +++ b/icu4c/source/i18n/dcfmtimp.h
    @@ -6,6 +6,8 @@
     *   Corporation and others.  All Rights Reserved.
     ********************************************************************************/
     
    +// TODO: This file, and the corresponding UCONFIG settings, may be obsolete.
    +
     #ifndef DCFMTIMP_H
     #define DCFMTIMP_H
     
    diff --git a/icu4c/source/i18n/i18n.vcxproj b/icu4c/source/i18n/i18n.vcxproj
    index de4784aeeb..bdd87d3cff 100644
    --- a/icu4c/source/i18n/i18n.vcxproj
    +++ b/icu4c/source/i18n/i18n.vcxproj
    @@ -197,18 +197,6 @@
         
         
         
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
         
         
         
    @@ -229,11 +217,8 @@
         
         
         
    -    
    -    
         
         
    -    
         
         
         
    @@ -284,6 +269,22 @@
         
         
         
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +    
         
         
         
    @@ -386,20 +387,6 @@
       
       
         
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
         
         
         
    @@ -437,11 +424,8 @@
         
         
         
    -    
    -    
         
         
    -    
         
         
         
    @@ -545,6 +529,21 @@
         
         
         
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +    
       
       
         
    diff --git a/icu4c/source/i18n/i18n.vcxproj.filters b/icu4c/source/i18n/i18n.vcxproj.filters
    index d14910b010..4b5743c505 100644
    --- a/icu4c/source/i18n/i18n.vcxproj.filters
    +++ b/icu4c/source/i18n/i18n.vcxproj.filters
    @@ -54,45 +54,6 @@
         
           collation
         
    -    
    -      formatting
    -    
    -    
    -      formatting
    -    
    -    
    -      formatting
    -    
    -    
    -      formatting
    -    
    -    
    -      formatting
    -    
    -    
    -      formatting
    -    
    -    
    -      formatting
    -    
    -    
    -      formatting
    -    
    -    
    -      formatting
    -    
    -    
    -      formatting
    -    
    -    
    -      formatting
    -    
    -    
    -      formatting
    -    
    -    
    -      formatting
    -    
         
           formatting
         
    @@ -141,21 +102,12 @@
         
           formatting
         
    -    
    -      formatting
    -    
    -    
    -      formatting
    -    
         
           formatting
         
         
           formatting
         
    -    
    -      formatting
    -    
         
           formatting
         
    @@ -561,8 +513,8 @@
         
           formatting
         
    -    
    -      formatting
    +    
    +      misc
         
         
           formatting
    @@ -612,8 +564,56 @@
         
           formatting
         
    -    
    -      misc
    +    
    +      formatting
    +    
    +    
    +      formatting
    +    
    +    
    +      formatting
    +    
    +    
    +      formatting
    +    
    +    
    +      formatting
    +    
    +    
    +      formatting
    +    
    +    
    +      formatting
    +    
    +    
    +      formatting
    +    
    +    
    +      formatting
    +    
    +    
    +      formatting
    +    
    +    
    +      formatting
    +    
    +    
    +      formatting
    +    
    +    
    +      formatting
    +    
    +    
    +      formatting
    +    
    +    
    +      formatting
    +    
    +    
    +      formatting
    +    
    +    
    +      formatting
         
       
       
    @@ -698,48 +698,6 @@
         
           collation
         
    -    
    -      formatting
    -    
    -    
    -      formatting
    -    
    -    
    -      formatting
    -    
    -    
    -      formatting
    -    
    -    
    -      formatting
    -    
    -    
    -      formatting
    -    
    -    
    -      formatting
    -    
    -    
    -      formatting
    -    
    -    
    -      formatting
    -    
    -    
    -      formatting
    -    
    -    
    -      formatting
    -    
    -    
    -      formatting
    -    
    -    
    -      formatting
    -    
    -    
    -      formatting
    -    
         
           formatting
         
    @@ -767,21 +725,12 @@
         
           formatting
         
    -    
    -      formatting
    -    
    -    
    -      formatting
    -    
         
           formatting
         
         
           formatting
         
    -    
    -      formatting
    -    
         
           formatting
         
    @@ -893,6 +842,51 @@
         
           formatting
         
    +    
    +      formatting
    +    
    +    
    +      formatting
    +    
    +    
    +      formatting
    +    
    +    
    +      formatting
    +    
    +    
    +      formatting
    +    
    +    
    +      formatting
    +    
    +    
    +      formatting
    +    
    +    
    +      formatting
    +    
    +    
    +      formatting
    +    
    +    
    +      formatting
    +    
    +    
    +      formatting
    +    
    +    
    +      formatting
    +    
    +    
    +      formatting
    +    
    +    
    +      formatting
    +    
    +    
    +      formatting
    +    
         
           formatting
         
    @@ -1411,10 +1405,13 @@
           formatting
         
         
    -      misc
    +      formatting
         
         
           formatting
         
    +    
    +      formatting
    +    
       
     
    \ No newline at end of file
    diff --git a/icu4c/source/i18n/i18n_uwp.vcxproj b/icu4c/source/i18n/i18n_uwp.vcxproj
    index 266e3c1e27..972b8ddf95 100644
    --- a/icu4c/source/i18n/i18n_uwp.vcxproj
    +++ b/icu4c/source/i18n/i18n_uwp.vcxproj
    @@ -304,18 +304,6 @@
         
         
         
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
         
         
         
    @@ -336,11 +324,8 @@
         
         
         
    -    
    -    
         
         
    -    
         
         
         
    @@ -391,6 +376,22 @@
         
         
         
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +    
         
         
         
    @@ -491,20 +492,6 @@
       
       
         
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
         
         
         
    @@ -542,11 +529,8 @@
         
         
         
    -    
    -    
         
         
    -    
         
         
         
    @@ -650,6 +634,21 @@
         
         
         
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +    
       
       
         
    diff --git a/icu4c/source/i18n/significantdigitinterval.h b/icu4c/source/i18n/significantdigitinterval.h
    deleted file mode 100644
    index fc23370de5..0000000000
    --- a/icu4c/source/i18n/significantdigitinterval.h
    +++ /dev/null
    @@ -1,92 +0,0 @@
    -// © 2016 and later: Unicode, Inc. and others.
    -// License & terms of use: http://www.unicode.org/copyright.html
    -/*
    -*******************************************************************************
    -* Copyright (C) 2015, International Business Machines
    -* Corporation and others.  All Rights Reserved.
    -*******************************************************************************
    -* significantdigitinterval.h
    -*
    -* created on: 2015jan6
    -* created by: Travis Keep
    -*/
    -
    -#ifndef __SIGNIFICANTDIGITINTERVAL_H__
    -#define __SIGNIFICANTDIGITINTERVAL_H__
    -
    -#include "unicode/uobject.h"
    -#include "unicode/utypes.h"
    -
    -U_NAMESPACE_BEGIN
    -
    -/**
    - * An interval of allowed significant digit counts.
    - */
    -class U_I18N_API SignificantDigitInterval : public UMemory {
    -public:
    -
    -    /**
    -     * No limits on significant digits.
    -     */
    -    SignificantDigitInterval()
    -            : fMax(INT32_MAX), fMin(0) { }
    -
    -    /**
    -     * Make this instance have no limit on significant digits.
    -     */
    -    void clear() {
    -        fMin = 0;
    -        fMax = INT32_MAX;
    -    }
    -
    -    /**
    -     * Returns TRUE if this object is equal to rhs.
    -     */
    -    UBool equals(const SignificantDigitInterval &rhs) const {
    -        return ((fMax == rhs.fMax) && (fMin == rhs.fMin));
    -    }
    -
    -    /**
    -     * Sets maximum significant digits. 0 or negative means no maximum.
    -     */
    -    void setMax(int32_t count) {
    -        fMax = count <= 0 ? INT32_MAX : count;
    -    }
    -
    -    /**
    -     * Get maximum significant digits. INT32_MAX means no maximum.
    -     */
    -    int32_t getMax() const {
    -        return fMax;
    -    }
    -
    -    /**
    -     * Sets minimum significant digits. 0 or negative means no minimum.
    -     */
    -    void setMin(int32_t count) {
    -        fMin = count <= 0 ? 0 : count;
    -    }
    -
    -    /**
    -     * Get maximum significant digits. 0 means no minimum.
    -     */
    -    int32_t getMin() const {
    -        return fMin;
    -    }
    -
    -    /**
    -     * Returns TRUE if this instance represents no constraints on significant
    -     * digits.
    -     */
    -    UBool isNoConstraints() const {
    -        return fMin == 0 && fMax == INT32_MAX;
    -    }
    -
    -private:
    -    int32_t fMax;
    -    int32_t fMin;
    -};
    -
    -U_NAMESPACE_END
    -
    -#endif  // __SIGNIFICANTDIGITINTERVAL_H__
    diff --git a/icu4c/source/test/cintltst/cintltst.vcxproj b/icu4c/source/test/cintltst/cintltst.vcxproj
    index f64f9d2004..143e9176a1 100644
    --- a/icu4c/source/test/cintltst/cintltst.vcxproj
    +++ b/icu4c/source/test/cintltst/cintltst.vcxproj
    @@ -242,6 +242,7 @@
         
         
         
    +    
       
       
         
    diff --git a/icu4c/source/test/cintltst/cintltst.vcxproj.filters b/icu4c/source/test/cintltst/cintltst.vcxproj.filters
    index e51a98f964..be69d72a8a 100644
    --- a/icu4c/source/test/cintltst/cintltst.vcxproj.filters
    +++ b/icu4c/source/test/cintltst/cintltst.vcxproj.filters
    @@ -216,6 +216,9 @@
         
           formatting
         
    +    
    +      formatting
    +    
         
           locales & resources
         
    diff --git a/icu4c/source/test/intltest/intltest.vcxproj b/icu4c/source/test/intltest/intltest.vcxproj
    index 32bd338d79..8dda453125 100644
    --- a/icu4c/source/test/intltest/intltest.vcxproj
    +++ b/icu4c/source/test/intltest/intltest.vcxproj
    @@ -248,12 +248,15 @@
         
         
         
    -    
         
         
         
         
    -    
    +    
    +    
    +    
    +    
    +    
         
         
         
    diff --git a/icu4c/source/test/intltest/intltest.vcxproj.filters b/icu4c/source/test/intltest/intltest.vcxproj.filters
    index 442793e69d..2e54fee092 100644
    --- a/icu4c/source/test/intltest/intltest.vcxproj.filters
    +++ b/icu4c/source/test/intltest/intltest.vcxproj.filters
    @@ -256,9 +256,6 @@
         
           formatting
         
    -    
    -      formatting
    -    
         
           formatting
         
    @@ -268,9 +265,6 @@
         
           formatting
         
    -    
    -      formatting
    -    
         
           formatting
         
    @@ -283,6 +277,21 @@
         
           formatting
         
    +    
    +      formatting
    +    
    +    
    +      formatting
    +    
    +    
    +      formatting
    +    
    +    
    +      formatting
    +    
    +    
    +      formatting
    +    
         
           formatting
         
    
    From 607a60bc3b16e07000a00d92a39582aaa8680946 Mon Sep 17 00:00:00 2001
    From: Shane Carr 
    Date: Mon, 23 Apr 2018 23:02:26 +0000
    Subject: [PATCH 127/129] ICU-13393 Removing the UPRV_INCOMPLETE_CPP11_SUPPORT
     flag since the number formatting code is no longer isolated from the rest of
     ICU.
    
    X-SVN-Rev: 41266
    ---
     icu4c/source/common/numparse_unisets.cpp           |  2 +-
     icu4c/source/common/numparse_unisets.h             |  2 +-
     icu4c/source/common/unicode/platform.h             | 14 --------------
     icu4c/source/i18n/compactdecimalformat.cpp         |  2 +-
     icu4c/source/i18n/decimfmt.cpp                     |  2 +-
     icu4c/source/i18n/number_affixutils.cpp            |  2 +-
     icu4c/source/i18n/number_affixutils.h              |  2 +-
     icu4c/source/i18n/number_capi.cpp                  |  2 +-
     icu4c/source/i18n/number_compact.cpp               |  2 +-
     icu4c/source/i18n/number_compact.h                 |  2 +-
     icu4c/source/i18n/number_currencysymbols.cpp       |  2 +-
     icu4c/source/i18n/number_currencysymbols.h         |  2 +-
     icu4c/source/i18n/number_decimalquantity.cpp       |  2 +-
     icu4c/source/i18n/number_decimalquantity.h         |  2 +-
     icu4c/source/i18n/number_decimfmtprops.cpp         |  2 +-
     icu4c/source/i18n/number_decimfmtprops.h           |  2 +-
     icu4c/source/i18n/number_fluent.cpp                |  2 +-
     icu4c/source/i18n/number_formatimpl.cpp            |  2 +-
     icu4c/source/i18n/number_formatimpl.h              |  2 +-
     icu4c/source/i18n/number_grouping.cpp              |  2 +-
     icu4c/source/i18n/number_integerwidth.cpp          |  2 +-
     icu4c/source/i18n/number_longnames.cpp             |  2 +-
     icu4c/source/i18n/number_longnames.h               |  2 +-
     icu4c/source/i18n/number_mapper.cpp                |  2 +-
     icu4c/source/i18n/number_mapper.h                  |  2 +-
     icu4c/source/i18n/number_modifiers.cpp             |  2 +-
     icu4c/source/i18n/number_modifiers.h               |  2 +-
     icu4c/source/i18n/number_multiplier.cpp            |  2 +-
     icu4c/source/i18n/number_multiplier.h              |  2 +-
     icu4c/source/i18n/number_notation.cpp              |  2 +-
     icu4c/source/i18n/number_padding.cpp               |  2 +-
     icu4c/source/i18n/number_patternmodifier.cpp       |  2 +-
     icu4c/source/i18n/number_patternmodifier.h         |  2 +-
     icu4c/source/i18n/number_patternstring.cpp         |  2 +-
     icu4c/source/i18n/number_patternstring.h           |  2 +-
     icu4c/source/i18n/number_rounding.cpp              |  2 +-
     icu4c/source/i18n/number_roundingutils.h           |  2 +-
     icu4c/source/i18n/number_scientific.cpp            |  2 +-
     icu4c/source/i18n/number_scientific.h              |  2 +-
     icu4c/source/i18n/number_skeletons.cpp             |  2 +-
     icu4c/source/i18n/number_skeletons.h               |  2 +-
     icu4c/source/i18n/number_stringbuilder.cpp         |  2 +-
     icu4c/source/i18n/number_stringbuilder.h           |  2 +-
     icu4c/source/i18n/number_types.h                   |  2 +-
     icu4c/source/i18n/number_utils.cpp                 |  2 +-
     icu4c/source/i18n/number_utils.h                   |  2 +-
     icu4c/source/i18n/number_utypes.h                  |  2 +-
     icu4c/source/i18n/numparse_affixes.cpp             |  2 +-
     icu4c/source/i18n/numparse_affixes.h               |  2 +-
     icu4c/source/i18n/numparse_compositions.cpp        |  2 +-
     icu4c/source/i18n/numparse_compositions.h          |  2 +-
     icu4c/source/i18n/numparse_currency.cpp            |  2 +-
     icu4c/source/i18n/numparse_currency.h              |  2 +-
     icu4c/source/i18n/numparse_decimal.cpp             |  2 +-
     icu4c/source/i18n/numparse_decimal.h               |  2 +-
     icu4c/source/i18n/numparse_impl.cpp                |  2 +-
     icu4c/source/i18n/numparse_impl.h                  |  2 +-
     icu4c/source/i18n/numparse_parsednumber.cpp        |  2 +-
     icu4c/source/i18n/numparse_scientific.cpp          |  2 +-
     icu4c/source/i18n/numparse_scientific.h            |  2 +-
     icu4c/source/i18n/numparse_stringsegment.cpp       |  2 +-
     icu4c/source/i18n/numparse_stringsegment.h         |  2 +-
     icu4c/source/i18n/numparse_symbols.cpp             |  2 +-
     icu4c/source/i18n/numparse_symbols.h               |  2 +-
     icu4c/source/i18n/numparse_types.h                 |  2 +-
     icu4c/source/i18n/numparse_utils.h                 |  2 +-
     icu4c/source/i18n/numparse_validators.cpp          |  2 +-
     icu4c/source/i18n/numparse_validators.h            |  2 +-
     icu4c/source/i18n/unicode/unumberformatter.h       |  2 +-
     icu4c/source/test/cintltst/unumberformattertst.c   |  2 +-
     icu4c/source/test/intltest/numbertest.h            |  2 +-
     .../source/test/intltest/numbertest_affixutils.cpp |  2 +-
     icu4c/source/test/intltest/numbertest_api.cpp      |  2 +-
     .../test/intltest/numbertest_decimalquantity.cpp   |  2 +-
     .../test/intltest/numbertest_doubleconversion.cpp  |  2 +-
     .../source/test/intltest/numbertest_modifiers.cpp  |  2 +-
     icu4c/source/test/intltest/numbertest_parse.cpp    |  2 +-
     .../test/intltest/numbertest_patternmodifier.cpp   |  2 +-
     .../test/intltest/numbertest_patternstring.cpp     |  2 +-
     .../source/test/intltest/numbertest_skeletons.cpp  |  2 +-
     .../test/intltest/numbertest_stringbuilder.cpp     |  2 +-
     .../test/intltest/numbertest_stringsegment.cpp     |  2 +-
     icu4c/source/test/intltest/numbertest_unisets.cpp  |  2 +-
     icu4c/source/test/intltest/strcase.cpp             |  2 +-
     84 files changed, 83 insertions(+), 97 deletions(-)
    
    diff --git a/icu4c/source/common/numparse_unisets.cpp b/icu4c/source/common/numparse_unisets.cpp
    index 1c328d9399..5f052354d0 100644
    --- a/icu4c/source/common/numparse_unisets.cpp
    +++ b/icu4c/source/common/numparse_unisets.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     // Allow implicit conversion from char16_t* to UnicodeString for this file:
     // Helpful in toString methods and elsewhere.
    diff --git a/icu4c/source/common/numparse_unisets.h b/icu4c/source/common/numparse_unisets.h
    index 7cf3f6aeb1..e6a51e1492 100644
    --- a/icu4c/source/common/numparse_unisets.h
    +++ b/icu4c/source/common/numparse_unisets.h
    @@ -5,7 +5,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     #ifndef __NUMPARSE_UNISETS_H__
     #define __NUMPARSE_UNISETS_H__
     
    diff --git a/icu4c/source/common/unicode/platform.h b/icu4c/source/common/unicode/platform.h
    index c63fce6e6d..7a3a87a08d 100644
    --- a/icu4c/source/common/unicode/platform.h
    +++ b/icu4c/source/common/unicode/platform.h
    @@ -196,20 +196,6 @@
     #   define U_PLATFORM U_PF_UNKNOWN
     #endif
     
    -/**
    - * \def UPRV_INCOMPLETE_CPP11_SUPPORT
    - * This switch turns off ICU 60 NumberFormatter code.
    - * By default, this switch is enabled on AIX and z/OS,
    - * which have poor C++11 support.
    - *
    - * NOTE: This switch is intended to be temporary; see #13393.
    - *
    - * @internal
    - */
    -#ifndef UPRV_INCOMPLETE_CPP11_SUPPORT
    -#   define UPRV_INCOMPLETE_CPP11_SUPPORT (U_PLATFORM == U_PF_AIX || U_PLATFORM == U_PF_OS390 || U_PLATFORM == U_PF_SOLARIS )
    -#endif
    -
     /**
      * \def CYGWINMSVC
      * Defined if this is Windows with Cygwin, but using MSVC rather than gcc.
    diff --git a/icu4c/source/i18n/compactdecimalformat.cpp b/icu4c/source/i18n/compactdecimalformat.cpp
    index be25ecd32e..7405330a6a 100644
    --- a/icu4c/source/i18n/compactdecimalformat.cpp
    +++ b/icu4c/source/i18n/compactdecimalformat.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     // Allow implicit conversion from char16_t* to UnicodeString for this file:
     // Helpful in toString methods and elsewhere.
    diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp
    index b53b82b6a6..b18da05075 100644
    --- a/icu4c/source/i18n/decimfmt.cpp
    +++ b/icu4c/source/i18n/decimfmt.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     // Allow implicit conversion from char16_t* to UnicodeString for this file:
     // Helpful in toString methods and elsewhere.
    diff --git a/icu4c/source/i18n/number_affixutils.cpp b/icu4c/source/i18n/number_affixutils.cpp
    index a61d8cdda4..3511e40413 100644
    --- a/icu4c/source/i18n/number_affixutils.cpp
    +++ b/icu4c/source/i18n/number_affixutils.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     #include "number_affixutils.h"
     #include "unicode/utf16.h"
    diff --git a/icu4c/source/i18n/number_affixutils.h b/icu4c/source/i18n/number_affixutils.h
    index b34420dfee..2bd00d4301 100644
    --- a/icu4c/source/i18n/number_affixutils.h
    +++ b/icu4c/source/i18n/number_affixutils.h
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     #ifndef __NUMBER_AFFIXUTILS_H__
     #define __NUMBER_AFFIXUTILS_H__
     
    diff --git a/icu4c/source/i18n/number_capi.cpp b/icu4c/source/i18n/number_capi.cpp
    index 6160f45eeb..7e79e8ec64 100644
    --- a/icu4c/source/i18n/number_capi.cpp
    +++ b/icu4c/source/i18n/number_capi.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     // Allow implicit conversion from char16_t* to UnicodeString for this file:
     // Helpful in toString methods and elsewhere.
    diff --git a/icu4c/source/i18n/number_compact.cpp b/icu4c/source/i18n/number_compact.cpp
    index cc0d8fd2a2..c19d495fd1 100644
    --- a/icu4c/source/i18n/number_compact.cpp
    +++ b/icu4c/source/i18n/number_compact.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     #include "resource.h"
     #include "number_compact.h"
    diff --git a/icu4c/source/i18n/number_compact.h b/icu4c/source/i18n/number_compact.h
    index f7adf36416..dda5f9f9b2 100644
    --- a/icu4c/source/i18n/number_compact.h
    +++ b/icu4c/source/i18n/number_compact.h
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     #ifndef __NUMBER_COMPACT_H__
     #define __NUMBER_COMPACT_H__
     
    diff --git a/icu4c/source/i18n/number_currencysymbols.cpp b/icu4c/source/i18n/number_currencysymbols.cpp
    index 51f37076e7..0b79d6596f 100644
    --- a/icu4c/source/i18n/number_currencysymbols.cpp
    +++ b/icu4c/source/i18n/number_currencysymbols.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     // Allow implicit conversion from char16_t* to UnicodeString for this file:
     // Helpful in toString methods and elsewhere.
    diff --git a/icu4c/source/i18n/number_currencysymbols.h b/icu4c/source/i18n/number_currencysymbols.h
    index eab1a661db..9383cd3c16 100644
    --- a/icu4c/source/i18n/number_currencysymbols.h
    +++ b/icu4c/source/i18n/number_currencysymbols.h
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     #ifndef __SOURCE_NUMBER_CURRENCYSYMBOLS_H__
     #define __SOURCE_NUMBER_CURRENCYSYMBOLS_H__
     
    diff --git a/icu4c/source/i18n/number_decimalquantity.cpp b/icu4c/source/i18n/number_decimalquantity.cpp
    index 4ad2564c6f..9f11b0d097 100644
    --- a/icu4c/source/i18n/number_decimalquantity.cpp
    +++ b/icu4c/source/i18n/number_decimalquantity.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     #include "uassert.h"
     #include 
    diff --git a/icu4c/source/i18n/number_decimalquantity.h b/icu4c/source/i18n/number_decimalquantity.h
    index 2a7ea8be44..be961629b4 100644
    --- a/icu4c/source/i18n/number_decimalquantity.h
    +++ b/icu4c/source/i18n/number_decimalquantity.h
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     #ifndef __NUMBER_DECIMALQUANTITY_H__
     #define __NUMBER_DECIMALQUANTITY_H__
     
    diff --git a/icu4c/source/i18n/number_decimfmtprops.cpp b/icu4c/source/i18n/number_decimfmtprops.cpp
    index f14670844c..c61e06c650 100644
    --- a/icu4c/source/i18n/number_decimfmtprops.cpp
    +++ b/icu4c/source/i18n/number_decimfmtprops.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     #include "number_decimfmtprops.h"
     #include "umutex.h"
    diff --git a/icu4c/source/i18n/number_decimfmtprops.h b/icu4c/source/i18n/number_decimfmtprops.h
    index 43f61f579e..945cfd4195 100644
    --- a/icu4c/source/i18n/number_decimfmtprops.h
    +++ b/icu4c/source/i18n/number_decimfmtprops.h
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     #ifndef __NUMBER_DECIMFMTPROPS_H__
     #define __NUMBER_DECIMFMTPROPS_H__
     
    diff --git a/icu4c/source/i18n/number_fluent.cpp b/icu4c/source/i18n/number_fluent.cpp
    index fe9105d65f..e0945f9ba9 100644
    --- a/icu4c/source/i18n/number_fluent.cpp
    +++ b/icu4c/source/i18n/number_fluent.cpp
    @@ -4,7 +4,7 @@
     #include 
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     #include "uassert.h"
     #include "unicode/numberformatter.h"
    diff --git a/icu4c/source/i18n/number_formatimpl.cpp b/icu4c/source/i18n/number_formatimpl.cpp
    index 10f327cf9a..93f16d197b 100644
    --- a/icu4c/source/i18n/number_formatimpl.cpp
    +++ b/icu4c/source/i18n/number_formatimpl.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     #include "cstring.h"
     #include "unicode/ures.h"
    diff --git a/icu4c/source/i18n/number_formatimpl.h b/icu4c/source/i18n/number_formatimpl.h
    index 4e6c9ca50f..f425bbca48 100644
    --- a/icu4c/source/i18n/number_formatimpl.h
    +++ b/icu4c/source/i18n/number_formatimpl.h
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     #ifndef __NUMBER_FORMATIMPL_H__
     #define __NUMBER_FORMATIMPL_H__
     
    diff --git a/icu4c/source/i18n/number_grouping.cpp b/icu4c/source/i18n/number_grouping.cpp
    index 7bda111046..5e94fca8f3 100644
    --- a/icu4c/source/i18n/number_grouping.cpp
    +++ b/icu4c/source/i18n/number_grouping.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     #include "unicode/numberformatter.h"
     #include "number_patternstring.h"
    diff --git a/icu4c/source/i18n/number_integerwidth.cpp b/icu4c/source/i18n/number_integerwidth.cpp
    index fa9991be0f..6ed930af96 100644
    --- a/icu4c/source/i18n/number_integerwidth.cpp
    +++ b/icu4c/source/i18n/number_integerwidth.cpp
    @@ -4,7 +4,7 @@
     #include 
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     #include "unicode/numberformatter.h"
     #include "number_types.h"
    diff --git a/icu4c/source/i18n/number_longnames.cpp b/icu4c/source/i18n/number_longnames.cpp
    index 5c363442e7..e84766cbf2 100644
    --- a/icu4c/source/i18n/number_longnames.cpp
    +++ b/icu4c/source/i18n/number_longnames.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     #include "unicode/simpleformatter.h"
     #include "unicode/ures.h"
    diff --git a/icu4c/source/i18n/number_longnames.h b/icu4c/source/i18n/number_longnames.h
    index 8738bb99e7..1d1e7dd3e8 100644
    --- a/icu4c/source/i18n/number_longnames.h
    +++ b/icu4c/source/i18n/number_longnames.h
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     #ifndef __NUMBER_LONGNAMES_H__
     #define __NUMBER_LONGNAMES_H__
     
    diff --git a/icu4c/source/i18n/number_mapper.cpp b/icu4c/source/i18n/number_mapper.cpp
    index cf48143ced..51dd0a9c8c 100644
    --- a/icu4c/source/i18n/number_mapper.cpp
    +++ b/icu4c/source/i18n/number_mapper.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     // Allow implicit conversion from char16_t* to UnicodeString for this file:
     // Helpful in toString methods and elsewhere.
    diff --git a/icu4c/source/i18n/number_mapper.h b/icu4c/source/i18n/number_mapper.h
    index ef9de18f77..9d7784ee28 100644
    --- a/icu4c/source/i18n/number_mapper.h
    +++ b/icu4c/source/i18n/number_mapper.h
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     #ifndef __NUMBER_MAPPER_H__
     #define __NUMBER_MAPPER_H__
     
    diff --git a/icu4c/source/i18n/number_modifiers.cpp b/icu4c/source/i18n/number_modifiers.cpp
    index 13a86ebeff..147e2182f5 100644
    --- a/icu4c/source/i18n/number_modifiers.cpp
    +++ b/icu4c/source/i18n/number_modifiers.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     #include "umutex.h"
     #include "ucln_cmn.h"
    diff --git a/icu4c/source/i18n/number_modifiers.h b/icu4c/source/i18n/number_modifiers.h
    index 4762a6f6d3..a553100cd9 100644
    --- a/icu4c/source/i18n/number_modifiers.h
    +++ b/icu4c/source/i18n/number_modifiers.h
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     #ifndef __NUMBER_MODIFIERS_H__
     #define __NUMBER_MODIFIERS_H__
     
    diff --git a/icu4c/source/i18n/number_multiplier.cpp b/icu4c/source/i18n/number_multiplier.cpp
    index ccce71a33b..1faeac41d7 100644
    --- a/icu4c/source/i18n/number_multiplier.cpp
    +++ b/icu4c/source/i18n/number_multiplier.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     // Allow implicit conversion from char16_t* to UnicodeString for this file:
     // Helpful in toString methods and elsewhere.
    diff --git a/icu4c/source/i18n/number_multiplier.h b/icu4c/source/i18n/number_multiplier.h
    index 94f582e6fc..4e3415cb45 100644
    --- a/icu4c/source/i18n/number_multiplier.h
    +++ b/icu4c/source/i18n/number_multiplier.h
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     #ifndef __SOURCE_NUMBER_MULTIPLIER_H__
     #define __SOURCE_NUMBER_MULTIPLIER_H__
     
    diff --git a/icu4c/source/i18n/number_notation.cpp b/icu4c/source/i18n/number_notation.cpp
    index da61433b1c..b3cabb57a5 100644
    --- a/icu4c/source/i18n/number_notation.cpp
    +++ b/icu4c/source/i18n/number_notation.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     #include "unicode/numberformatter.h"
     #include "number_types.h"
    diff --git a/icu4c/source/i18n/number_padding.cpp b/icu4c/source/i18n/number_padding.cpp
    index d2d0a87b15..f6abf9bd80 100644
    --- a/icu4c/source/i18n/number_padding.cpp
    +++ b/icu4c/source/i18n/number_padding.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     #include "unicode/numberformatter.h"
     #include "number_types.h"
    diff --git a/icu4c/source/i18n/number_patternmodifier.cpp b/icu4c/source/i18n/number_patternmodifier.cpp
    index 1f88742608..45f2dd9914 100644
    --- a/icu4c/source/i18n/number_patternmodifier.cpp
    +++ b/icu4c/source/i18n/number_patternmodifier.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     #include "cstring.h"
     #include "number_patternmodifier.h"
    diff --git a/icu4c/source/i18n/number_patternmodifier.h b/icu4c/source/i18n/number_patternmodifier.h
    index a8c1f22f0b..f1359bd574 100644
    --- a/icu4c/source/i18n/number_patternmodifier.h
    +++ b/icu4c/source/i18n/number_patternmodifier.h
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     #ifndef __NUMBER_PATTERNMODIFIER_H__
     #define __NUMBER_PATTERNMODIFIER_H__
     
    diff --git a/icu4c/source/i18n/number_patternstring.cpp b/icu4c/source/i18n/number_patternstring.cpp
    index 3dea96330e..63195eed98 100644
    --- a/icu4c/source/i18n/number_patternstring.cpp
    +++ b/icu4c/source/i18n/number_patternstring.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     // Allow implicit conversion from char16_t* to UnicodeString for this file:
     // Helpful in toString methods and elsewhere.
    diff --git a/icu4c/source/i18n/number_patternstring.h b/icu4c/source/i18n/number_patternstring.h
    index fc4100d803..5d8a526d2b 100644
    --- a/icu4c/source/i18n/number_patternstring.h
    +++ b/icu4c/source/i18n/number_patternstring.h
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     #ifndef __NUMBER_PATTERNSTRING_H__
     #define __NUMBER_PATTERNSTRING_H__
     
    diff --git a/icu4c/source/i18n/number_rounding.cpp b/icu4c/source/i18n/number_rounding.cpp
    index ba77cadef0..b5c39fcd8f 100644
    --- a/icu4c/source/i18n/number_rounding.cpp
    +++ b/icu4c/source/i18n/number_rounding.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     #include "uassert.h"
     #include "unicode/numberformatter.h"
    diff --git a/icu4c/source/i18n/number_roundingutils.h b/icu4c/source/i18n/number_roundingutils.h
    index 460235a0d6..0201623ac8 100644
    --- a/icu4c/source/i18n/number_roundingutils.h
    +++ b/icu4c/source/i18n/number_roundingutils.h
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     #ifndef __NUMBER_ROUNDINGUTILS_H__
     #define __NUMBER_ROUNDINGUTILS_H__
     
    diff --git a/icu4c/source/i18n/number_scientific.cpp b/icu4c/source/i18n/number_scientific.cpp
    index ce7bb3b689..b5490536e7 100644
    --- a/icu4c/source/i18n/number_scientific.cpp
    +++ b/icu4c/source/i18n/number_scientific.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     #include 
     #include "number_scientific.h"
    diff --git a/icu4c/source/i18n/number_scientific.h b/icu4c/source/i18n/number_scientific.h
    index f5e4d30e6a..974ab3adb6 100644
    --- a/icu4c/source/i18n/number_scientific.h
    +++ b/icu4c/source/i18n/number_scientific.h
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     #ifndef __NUMBER_SCIENTIFIC_H__
     #define __NUMBER_SCIENTIFIC_H__
     
    diff --git a/icu4c/source/i18n/number_skeletons.cpp b/icu4c/source/i18n/number_skeletons.cpp
    index e998e8e51c..d560581704 100644
    --- a/icu4c/source/i18n/number_skeletons.cpp
    +++ b/icu4c/source/i18n/number_skeletons.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     // Allow implicit conversion from char16_t* to UnicodeString for this file:
     // Helpful in toString methods and elsewhere.
    diff --git a/icu4c/source/i18n/number_skeletons.h b/icu4c/source/i18n/number_skeletons.h
    index 3510aa2b80..b84f47cc32 100644
    --- a/icu4c/source/i18n/number_skeletons.h
    +++ b/icu4c/source/i18n/number_skeletons.h
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     #ifndef __SOURCE_NUMBER_SKELETONS_H__
     #define __SOURCE_NUMBER_SKELETONS_H__
     
    diff --git a/icu4c/source/i18n/number_stringbuilder.cpp b/icu4c/source/i18n/number_stringbuilder.cpp
    index dd189067a7..501b02bdcb 100644
    --- a/icu4c/source/i18n/number_stringbuilder.cpp
    +++ b/icu4c/source/i18n/number_stringbuilder.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     #include "number_stringbuilder.h"
     #include "unicode/utf16.h"
    diff --git a/icu4c/source/i18n/number_stringbuilder.h b/icu4c/source/i18n/number_stringbuilder.h
    index f92547679c..f8d11a0fdd 100644
    --- a/icu4c/source/i18n/number_stringbuilder.h
    +++ b/icu4c/source/i18n/number_stringbuilder.h
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     #ifndef __NUMBER_STRINGBUILDER_H__
     #define __NUMBER_STRINGBUILDER_H__
     
    diff --git a/icu4c/source/i18n/number_types.h b/icu4c/source/i18n/number_types.h
    index 5aaf538a17..ce43152e1e 100644
    --- a/icu4c/source/i18n/number_types.h
    +++ b/icu4c/source/i18n/number_types.h
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     #ifndef __NUMBER_TYPES_H__
     #define __NUMBER_TYPES_H__
     
    diff --git a/icu4c/source/i18n/number_utils.cpp b/icu4c/source/i18n/number_utils.cpp
    index d98f061aa3..5d0a4bc80c 100644
    --- a/icu4c/source/i18n/number_utils.cpp
    +++ b/icu4c/source/i18n/number_utils.cpp
    @@ -5,7 +5,7 @@
     #include 
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     // Allow implicit conversion from char16_t* to UnicodeString for this file:
     // Helpful in toString methods and elsewhere.
    diff --git a/icu4c/source/i18n/number_utils.h b/icu4c/source/i18n/number_utils.h
    index 3ac541de02..5c6be2d73c 100644
    --- a/icu4c/source/i18n/number_utils.h
    +++ b/icu4c/source/i18n/number_utils.h
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     #ifndef __NUMBER_UTILS_H__
     #define __NUMBER_UTILS_H__
     
    diff --git a/icu4c/source/i18n/number_utypes.h b/icu4c/source/i18n/number_utypes.h
    index bff097eff1..48bfce1969 100644
    --- a/icu4c/source/i18n/number_utypes.h
    +++ b/icu4c/source/i18n/number_utypes.h
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     #ifndef __SOURCE_NUMBER_UTYPES_H__
     #define __SOURCE_NUMBER_UTYPES_H__
     
    diff --git a/icu4c/source/i18n/numparse_affixes.cpp b/icu4c/source/i18n/numparse_affixes.cpp
    index 259fab7c6c..978081dd84 100644
    --- a/icu4c/source/i18n/numparse_affixes.cpp
    +++ b/icu4c/source/i18n/numparse_affixes.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     // Allow implicit conversion from char16_t* to UnicodeString for this file:
     // Helpful in toString methods and elsewhere.
    diff --git a/icu4c/source/i18n/numparse_affixes.h b/icu4c/source/i18n/numparse_affixes.h
    index fe27cad03e..add421be70 100644
    --- a/icu4c/source/i18n/numparse_affixes.h
    +++ b/icu4c/source/i18n/numparse_affixes.h
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     #ifndef __NUMPARSE_AFFIXES_H__
     #define __NUMPARSE_AFFIXES_H__
     
    diff --git a/icu4c/source/i18n/numparse_compositions.cpp b/icu4c/source/i18n/numparse_compositions.cpp
    index 8c7b7d251c..19253da805 100644
    --- a/icu4c/source/i18n/numparse_compositions.cpp
    +++ b/icu4c/source/i18n/numparse_compositions.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     // Allow implicit conversion from char16_t* to UnicodeString for this file:
     // Helpful in toString methods and elsewhere.
    diff --git a/icu4c/source/i18n/numparse_compositions.h b/icu4c/source/i18n/numparse_compositions.h
    index a0b20c3433..252b007c19 100644
    --- a/icu4c/source/i18n/numparse_compositions.h
    +++ b/icu4c/source/i18n/numparse_compositions.h
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     #ifndef __SOURCE_NUMPARSE_COMPOSITIONS__
     #define __SOURCE_NUMPARSE_COMPOSITIONS__
     
    diff --git a/icu4c/source/i18n/numparse_currency.cpp b/icu4c/source/i18n/numparse_currency.cpp
    index 52bbb51794..1b49fa9fdc 100644
    --- a/icu4c/source/i18n/numparse_currency.cpp
    +++ b/icu4c/source/i18n/numparse_currency.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     // Allow implicit conversion from char16_t* to UnicodeString for this file:
     // Helpful in toString methods and elsewhere.
    diff --git a/icu4c/source/i18n/numparse_currency.h b/icu4c/source/i18n/numparse_currency.h
    index 63007451af..7fb0913e50 100644
    --- a/icu4c/source/i18n/numparse_currency.h
    +++ b/icu4c/source/i18n/numparse_currency.h
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     #ifndef __NUMPARSE_CURRENCY_H__
     #define __NUMPARSE_CURRENCY_H__
     
    diff --git a/icu4c/source/i18n/numparse_decimal.cpp b/icu4c/source/i18n/numparse_decimal.cpp
    index f3f17b6363..68b4d32570 100644
    --- a/icu4c/source/i18n/numparse_decimal.cpp
    +++ b/icu4c/source/i18n/numparse_decimal.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     // Allow implicit conversion from char16_t* to UnicodeString for this file:
     // Helpful in toString methods and elsewhere.
    diff --git a/icu4c/source/i18n/numparse_decimal.h b/icu4c/source/i18n/numparse_decimal.h
    index f3ddcd8d61..dc0b8f9948 100644
    --- a/icu4c/source/i18n/numparse_decimal.h
    +++ b/icu4c/source/i18n/numparse_decimal.h
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     #ifndef __NUMPARSE_DECIMAL_H__
     #define __NUMPARSE_DECIMAL_H__
     
    diff --git a/icu4c/source/i18n/numparse_impl.cpp b/icu4c/source/i18n/numparse_impl.cpp
    index 11d8a14ae3..b986ce0d2d 100644
    --- a/icu4c/source/i18n/numparse_impl.cpp
    +++ b/icu4c/source/i18n/numparse_impl.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     // Allow implicit conversion from char16_t* to UnicodeString for this file:
     // Helpful in toString methods and elsewhere.
    diff --git a/icu4c/source/i18n/numparse_impl.h b/icu4c/source/i18n/numparse_impl.h
    index f6688c1e21..5a28ff5dbc 100644
    --- a/icu4c/source/i18n/numparse_impl.h
    +++ b/icu4c/source/i18n/numparse_impl.h
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     #ifndef __NUMPARSE_IMPL_H__
     #define __NUMPARSE_IMPL_H__
     
    diff --git a/icu4c/source/i18n/numparse_parsednumber.cpp b/icu4c/source/i18n/numparse_parsednumber.cpp
    index 7adfb5ca56..6e0478c749 100644
    --- a/icu4c/source/i18n/numparse_parsednumber.cpp
    +++ b/icu4c/source/i18n/numparse_parsednumber.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     // Allow implicit conversion from char16_t* to UnicodeString for this file:
     // Helpful in toString methods and elsewhere.
    diff --git a/icu4c/source/i18n/numparse_scientific.cpp b/icu4c/source/i18n/numparse_scientific.cpp
    index bbf7738f99..8fed94f41d 100644
    --- a/icu4c/source/i18n/numparse_scientific.cpp
    +++ b/icu4c/source/i18n/numparse_scientific.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     // Allow implicit conversion from char16_t* to UnicodeString for this file:
     // Helpful in toString methods and elsewhere.
    diff --git a/icu4c/source/i18n/numparse_scientific.h b/icu4c/source/i18n/numparse_scientific.h
    index 0581b7e5ed..ddecf858af 100644
    --- a/icu4c/source/i18n/numparse_scientific.h
    +++ b/icu4c/source/i18n/numparse_scientific.h
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     #ifndef __NUMPARSE_SCIENTIFIC_H__
     #define __NUMPARSE_SCIENTIFIC_H__
     
    diff --git a/icu4c/source/i18n/numparse_stringsegment.cpp b/icu4c/source/i18n/numparse_stringsegment.cpp
    index 6f35deb7e7..0c5d4267dc 100644
    --- a/icu4c/source/i18n/numparse_stringsegment.cpp
    +++ b/icu4c/source/i18n/numparse_stringsegment.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     // Allow implicit conversion from char16_t* to UnicodeString for this file:
     // Helpful in toString methods and elsewhere.
    diff --git a/icu4c/source/i18n/numparse_stringsegment.h b/icu4c/source/i18n/numparse_stringsegment.h
    index d2e6154cd3..7a84444d41 100644
    --- a/icu4c/source/i18n/numparse_stringsegment.h
    +++ b/icu4c/source/i18n/numparse_stringsegment.h
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     #ifndef __NUMPARSE_STRINGSEGMENT_H__
     #define __NUMPARSE_STRINGSEGMENT_H__
     
    diff --git a/icu4c/source/i18n/numparse_symbols.cpp b/icu4c/source/i18n/numparse_symbols.cpp
    index 9882fb06ce..9ccceec847 100644
    --- a/icu4c/source/i18n/numparse_symbols.cpp
    +++ b/icu4c/source/i18n/numparse_symbols.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     // Allow implicit conversion from char16_t* to UnicodeString for this file:
     // Helpful in toString methods and elsewhere.
    diff --git a/icu4c/source/i18n/numparse_symbols.h b/icu4c/source/i18n/numparse_symbols.h
    index 6f0edc7107..16a009ccd7 100644
    --- a/icu4c/source/i18n/numparse_symbols.h
    +++ b/icu4c/source/i18n/numparse_symbols.h
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     #ifndef __NUMPARSE_SYMBOLS_H__
     #define __NUMPARSE_SYMBOLS_H__
     
    diff --git a/icu4c/source/i18n/numparse_types.h b/icu4c/source/i18n/numparse_types.h
    index 469d827040..3b3fe13cc3 100644
    --- a/icu4c/source/i18n/numparse_types.h
    +++ b/icu4c/source/i18n/numparse_types.h
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     #ifndef __NUMPARSE_TYPES_H__
     #define __NUMPARSE_TYPES_H__
     
    diff --git a/icu4c/source/i18n/numparse_utils.h b/icu4c/source/i18n/numparse_utils.h
    index 590c7943f3..162954bae0 100644
    --- a/icu4c/source/i18n/numparse_utils.h
    +++ b/icu4c/source/i18n/numparse_utils.h
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     #ifndef __NUMPARSE_UTILS_H__
     #define __NUMPARSE_UTILS_H__
     
    diff --git a/icu4c/source/i18n/numparse_validators.cpp b/icu4c/source/i18n/numparse_validators.cpp
    index 724b0cf031..6e9632588b 100644
    --- a/icu4c/source/i18n/numparse_validators.cpp
    +++ b/icu4c/source/i18n/numparse_validators.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     // Allow implicit conversion from char16_t* to UnicodeString for this file:
     // Helpful in toString methods and elsewhere.
    diff --git a/icu4c/source/i18n/numparse_validators.h b/icu4c/source/i18n/numparse_validators.h
    index 21acb625a5..1479d897bb 100644
    --- a/icu4c/source/i18n/numparse_validators.h
    +++ b/icu4c/source/i18n/numparse_validators.h
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     #ifndef __SOURCE_NUMPARSE_VALIDATORS_H__
     #define __SOURCE_NUMPARSE_VALIDATORS_H__
     
    diff --git a/icu4c/source/i18n/unicode/unumberformatter.h b/icu4c/source/i18n/unicode/unumberformatter.h
    index acf5c0f1cf..040213fcf5 100644
    --- a/icu4c/source/i18n/unicode/unumberformatter.h
    +++ b/icu4c/source/i18n/unicode/unumberformatter.h
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     #ifndef __UNUMBERFORMATTER_H__
     #define __UNUMBERFORMATTER_H__
     
    diff --git a/icu4c/source/test/cintltst/unumberformattertst.c b/icu4c/source/test/cintltst/unumberformattertst.c
    index 47a389e664..c7abb23200 100644
    --- a/icu4c/source/test/cintltst/unumberformattertst.c
    +++ b/icu4c/source/test/cintltst/unumberformattertst.c
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     // Allow implicit conversion from char16_t* to UnicodeString for this file:
     // Helpful in toString methods and elsewhere.
    diff --git a/icu4c/source/test/intltest/numbertest.h b/icu4c/source/test/intltest/numbertest.h
    index d1a5defdb6..33a35e570f 100644
    --- a/icu4c/source/test/intltest/numbertest.h
    +++ b/icu4c/source/test/intltest/numbertest.h
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     #pragma once
     
     #include "number_stringbuilder.h"
    diff --git a/icu4c/source/test/intltest/numbertest_affixutils.cpp b/icu4c/source/test/intltest/numbertest_affixutils.cpp
    index cf0bf39944..1815f8ed99 100644
    --- a/icu4c/source/test/intltest/numbertest_affixutils.cpp
    +++ b/icu4c/source/test/intltest/numbertest_affixutils.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     #include "putilimp.h"
     #include "unicode/dcfmtsym.h"
    diff --git a/icu4c/source/test/intltest/numbertest_api.cpp b/icu4c/source/test/intltest/numbertest_api.cpp
    index 33b0610a5a..f374d8e3e3 100644
    --- a/icu4c/source/test/intltest/numbertest_api.cpp
    +++ b/icu4c/source/test/intltest/numbertest_api.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     #include "charstr.h"
     #include 
    diff --git a/icu4c/source/test/intltest/numbertest_decimalquantity.cpp b/icu4c/source/test/intltest/numbertest_decimalquantity.cpp
    index db594a786f..e8aaffa193 100644
    --- a/icu4c/source/test/intltest/numbertest_decimalquantity.cpp
    +++ b/icu4c/source/test/intltest/numbertest_decimalquantity.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     #include "number_decimalquantity.h"
     #include "math.h"
    diff --git a/icu4c/source/test/intltest/numbertest_doubleconversion.cpp b/icu4c/source/test/intltest/numbertest_doubleconversion.cpp
    index a52865d8d3..a98fa903f3 100644
    --- a/icu4c/source/test/intltest/numbertest_doubleconversion.cpp
    +++ b/icu4c/source/test/intltest/numbertest_doubleconversion.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     #include "numbertest.h"
     #include "double-conversion.h"
    diff --git a/icu4c/source/test/intltest/numbertest_modifiers.cpp b/icu4c/source/test/intltest/numbertest_modifiers.cpp
    index bebb3f8b2b..e76972b65b 100644
    --- a/icu4c/source/test/intltest/numbertest_modifiers.cpp
    +++ b/icu4c/source/test/intltest/numbertest_modifiers.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     #include "putilimp.h"
     #include "intltest.h"
    diff --git a/icu4c/source/test/intltest/numbertest_parse.cpp b/icu4c/source/test/intltest/numbertest_parse.cpp
    index 97084dc9ee..2132c18b2f 100644
    --- a/icu4c/source/test/intltest/numbertest_parse.cpp
    +++ b/icu4c/source/test/intltest/numbertest_parse.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     #include "numbertest.h"
     #include "numparse_impl.h"
    diff --git a/icu4c/source/test/intltest/numbertest_patternmodifier.cpp b/icu4c/source/test/intltest/numbertest_patternmodifier.cpp
    index a98c3c9d5e..f9e08ae96c 100644
    --- a/icu4c/source/test/intltest/numbertest_patternmodifier.cpp
    +++ b/icu4c/source/test/intltest/numbertest_patternmodifier.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     #include "numbertest.h"
     #include "number_patternmodifier.h"
    diff --git a/icu4c/source/test/intltest/numbertest_patternstring.cpp b/icu4c/source/test/intltest/numbertest_patternstring.cpp
    index b0b1deb3d2..696e120efc 100644
    --- a/icu4c/source/test/intltest/numbertest_patternstring.cpp
    +++ b/icu4c/source/test/intltest/numbertest_patternstring.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     #include "numbertest.h"
     #include "number_patternstring.h"
    diff --git a/icu4c/source/test/intltest/numbertest_skeletons.cpp b/icu4c/source/test/intltest/numbertest_skeletons.cpp
    index 59ad630ce9..f5ea2a9d07 100644
    --- a/icu4c/source/test/intltest/numbertest_skeletons.cpp
    +++ b/icu4c/source/test/intltest/numbertest_skeletons.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     #include "putilimp.h"
     #include "unicode/dcfmtsym.h"
    diff --git a/icu4c/source/test/intltest/numbertest_stringbuilder.cpp b/icu4c/source/test/intltest/numbertest_stringbuilder.cpp
    index cdc3836173..04ef81b7da 100644
    --- a/icu4c/source/test/intltest/numbertest_stringbuilder.cpp
    +++ b/icu4c/source/test/intltest/numbertest_stringbuilder.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     #include "putilimp.h"
     #include "numbertest.h"
    diff --git a/icu4c/source/test/intltest/numbertest_stringsegment.cpp b/icu4c/source/test/intltest/numbertest_stringsegment.cpp
    index b174828e1f..34c4356c7d 100644
    --- a/icu4c/source/test/intltest/numbertest_stringsegment.cpp
    +++ b/icu4c/source/test/intltest/numbertest_stringsegment.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     #include "numbertest.h"
     #include "numparse_stringsegment.h"
    diff --git a/icu4c/source/test/intltest/numbertest_unisets.cpp b/icu4c/source/test/intltest/numbertest_unisets.cpp
    index 74f65e4b41..7311332dd1 100644
    --- a/icu4c/source/test/intltest/numbertest_unisets.cpp
    +++ b/icu4c/source/test/intltest/numbertest_unisets.cpp
    @@ -3,7 +3,7 @@
     
     #include "unicode/utypes.h"
     
    -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
    +#if !UCONFIG_NO_FORMATTING
     
     #include "numbertest.h"
     #include "numparse_unisets.h"
    diff --git a/icu4c/source/test/intltest/strcase.cpp b/icu4c/source/test/intltest/strcase.cpp
    index 83444bf3c9..ec16555431 100644
    --- a/icu4c/source/test/intltest/strcase.cpp
    +++ b/icu4c/source/test/intltest/strcase.cpp
    @@ -1005,7 +1005,7 @@ void StringCaseTest::TestCopyMoveEdits() {
     
         // std::move trouble on these platforms.
         // See https://ssl.icu-project.org/trac/ticket/13393
    -#if !UPRV_INCOMPLETE_CPP11_SUPPORT && !(U_PLATFORM == U_PF_AIX || U_PLATFORM == U_PF_OS390)
    +#if !(U_PLATFORM == U_PF_AIX || U_PLATFORM == U_PF_OS390)
         // move constructor empties object with heap array
         Edits d(std::move(a));
         assertEquals("d: move-constructed many edits, length delta", 250, d.lengthDelta());
    
    From faa2897561c2c49f22d2b0515f2a6f5165cdbfa6 Mon Sep 17 00:00:00 2001
    From: Shane Carr 
    Date: Tue, 24 Apr 2018 01:19:44 +0000
    Subject: [PATCH 128/129] ICU-13634 Changes from pre-merge code light-review.
    
    X-SVN-Rev: 41267
    ---
     icu4c/source/i18n/compactdecimalformat.cpp    | 26 +++++++++
     icu4c/source/i18n/i18n.vcxproj                |  2 +
     icu4c/source/i18n/i18n.vcxproj.filters        |  6 +++
     icu4c/source/i18n/i18n_uwp.vcxproj            |  2 +
     icu4c/source/i18n/plurrule_impl.h             |  2 +
     .../i18n/unicode/compactdecimalformat.h       | 54 +++++++++++++++++++
     icu4c/source/i18n/unicode/decimfmt.h          | 17 +++---
     icu4c/source/i18n/unicode/numberformatter.h   |  4 +-
     icu4c/source/i18n/unicode/unum.h              |  2 +
     .../datadrivennumberformattestsuite.h         |  2 +-
     icu4c/source/test/intltest/dcfmapts.cpp       |  3 +-
     .../numberformattestspecification.txt         |  9 ++++
     12 files changed, 114 insertions(+), 15 deletions(-)
    
    diff --git a/icu4c/source/i18n/compactdecimalformat.cpp b/icu4c/source/i18n/compactdecimalformat.cpp
    index 7405330a6a..c3facfbc61 100644
    --- a/icu4c/source/i18n/compactdecimalformat.cpp
    +++ b/icu4c/source/i18n/compactdecimalformat.cpp
    @@ -44,5 +44,31 @@ CompactDecimalFormat& CompactDecimalFormat::operator=(const CompactDecimalFormat
         return *this;
     }
     
    +Format* CompactDecimalFormat::clone() const {
    +    return new CompactDecimalFormat(*this);
    +}
    +
    +void
    +CompactDecimalFormat::parse(
    +        const UnicodeString& /* text */,
    +        Formattable& /* result */,
    +        ParsePosition& /* parsePosition */) const {
    +}
    +
    +void
    +CompactDecimalFormat::parse(
    +        const UnicodeString& /* text */,
    +        Formattable& /* result */,
    +        UErrorCode& status) const {
    +    status = U_UNSUPPORTED_ERROR;
    +}
    +
    +CurrencyAmount*
    +CompactDecimalFormat::parseCurrency(
    +        const UnicodeString& /* text */,
    +        ParsePosition& /* pos */) const {
    +    return nullptr;
    +}
    +
     
     #endif /* #if !UCONFIG_NO_FORMATTING */
    diff --git a/icu4c/source/i18n/i18n.vcxproj b/icu4c/source/i18n/i18n.vcxproj
    index bdd87d3cff..a2cce5f62c 100644
    --- a/icu4c/source/i18n/i18n.vcxproj
    +++ b/icu4c/source/i18n/i18n.vcxproj
    @@ -528,11 +528,13 @@
         
         
         
    +    
         
         
         
         
         
    +    
         
         
         
    diff --git a/icu4c/source/i18n/i18n.vcxproj.filters b/icu4c/source/i18n/i18n.vcxproj.filters
    index 4b5743c505..7e1dbb8c11 100644
    --- a/icu4c/source/i18n/i18n.vcxproj.filters
    +++ b/icu4c/source/i18n/i18n.vcxproj.filters
    @@ -839,6 +839,9 @@
         
           formatting
         
    +    
    +      formatting
    +    
         
           formatting
         
    @@ -854,6 +857,9 @@
         
           formatting
         
    +    
    +      formatting
    +    
         
           formatting
         
    diff --git a/icu4c/source/i18n/i18n_uwp.vcxproj b/icu4c/source/i18n/i18n_uwp.vcxproj
    index 972b8ddf95..2cd3e7af62 100644
    --- a/icu4c/source/i18n/i18n_uwp.vcxproj
    +++ b/icu4c/source/i18n/i18n_uwp.vcxproj
    @@ -633,11 +633,13 @@
         
         
         
    +    
         
         
         
         
         
    +    
         
         
         
    diff --git a/icu4c/source/i18n/plurrule_impl.h b/icu4c/source/i18n/plurrule_impl.h
    index a0d3f4a564..43ca84f27f 100644
    --- a/icu4c/source/i18n/plurrule_impl.h
    +++ b/icu4c/source/i18n/plurrule_impl.h
    @@ -42,6 +42,8 @@ class VisibleDigits;
     
     namespace pluralimpl {
     
    +// TODO: Remove this and replace with u"" literals. Was for EBCDIC compatibility.
    +
     static const UChar DOT = ((UChar) 0x002E);
     static const UChar SINGLE_QUOTE = ((UChar) 0x0027);
     static const UChar SLASH = ((UChar) 0x002F);
    diff --git a/icu4c/source/i18n/unicode/compactdecimalformat.h b/icu4c/source/i18n/unicode/compactdecimalformat.h
    index 967a9adbee..e7995d375b 100644
    --- a/icu4c/source/i18n/unicode/compactdecimalformat.h
    +++ b/icu4c/source/i18n/unicode/compactdecimalformat.h
    @@ -94,8 +94,62 @@ public:
          */
         CompactDecimalFormat& operator=(const CompactDecimalFormat& rhs);
     
    +    /**
    +     * Clone this Format object polymorphically. The caller owns the
    +     * result and should delete it when done.
    +     *
    +     * @return    a polymorphic copy of this CompactDecimalFormat.
    +     * @stable ICU 51
    +     */
    +    virtual Format* clone() const;
    +
         using DecimalFormat::format;
     
    +    /**
    +     * CompactDecimalFormat does not support parsing. This implementation
    +     * does nothing.
    +     * @param text           Unused.
    +     * @param result         Does not change.
    +     * @param parsePosition  Does not change.
    +     * @see Formattable
    +     * @stable ICU 51
    +     */
    +    void parse(const UnicodeString& text, Formattable& result,
    +               ParsePosition& parsePosition) const U_OVERRIDE;
    +
    +    /**
    +     * CompactDecimalFormat does not support parsing. This implementation
    +     * sets status to U_UNSUPPORTED_ERROR
    +     *
    +     * @param text      Unused.
    +     * @param result    Does not change.
    +     * @param status    Always set to U_UNSUPPORTED_ERROR.
    +     * @stable ICU 51
    +     */
    +    void parse(const UnicodeString& text, Formattable& result, UErrorCode& status) const U_OVERRIDE;
    +
    +    /**
    +     * Parses text from the given string as a currency amount.  Unlike
    +     * the parse() method, this method will attempt to parse a generic
    +     * currency name, searching for a match of this object's locale's
    +     * currency display names, or for a 3-letter ISO currency code.
    +     * This method will fail if this format is not a currency format,
    +     * that is, if it does not contain the currency pattern symbol
    +     * (U+00A4) in its prefix or suffix. This implementation always returns
    +     * NULL.
    +     *
    +     * @param text the string to parse
    +     * @param pos  input-output position; on input, the position within text
    +     *             to match; must have 0 <= pos.getIndex() < text.length();
    +     *             on output, the position after the last matched character.
    +     *             If the parse fails, the position in unchanged upon output.
    +     * @return     if parse succeeds, a pointer to a newly-created CurrencyAmount
    +     *             object (owned by the caller) containing information about
    +     *             the parsed currency; if parse fails, this is NULL.
    +     * @internal
    +     */
    +    CurrencyAmount* parseCurrency(const UnicodeString& text, ParsePosition& pos) const U_OVERRIDE;
    +
         /**
          * Return the class ID for this class.  This is useful only for
          * comparing to a return value from getDynamicClassID().  For example:
    diff --git a/icu4c/source/i18n/unicode/decimfmt.h b/icu4c/source/i18n/unicode/decimfmt.h
    index 8e42b0aabd..a8339559d1 100644
    --- a/icu4c/source/i18n/unicode/decimfmt.h
    +++ b/icu4c/source/i18n/unicode/decimfmt.h
    @@ -27,7 +27,6 @@
     #ifndef DECIMFMT_H
     #define DECIMFMT_H
     
    -#include 
     #include "unicode/utypes.h"
     /**
      * \file
    @@ -36,6 +35,7 @@
     
     #if !UCONFIG_NO_FORMATTING
     
    +#include 
     #include "unicode/dcfmtsym.h"
     #include "unicode/numfmt.h"
     #include "unicode/locid.h"
    @@ -56,13 +56,8 @@
     
     U_NAMESPACE_BEGIN
     
    -class DigitList;
     class CurrencyPluralInfo;
    -class Hashtable;
    -class UnicodeSet;
     class FieldPositionHandler;
    -class FixedDecimal;
    -class PluralRules;
     class CompactDecimalFormat;
     
     namespace number {
    @@ -712,7 +707,7 @@ class U_I18N_API DecimalFormat : public NumberFormat {
          *                  pattern is invalid this will be set to a failure code.
          * @stable ICU 2.0
          */
    -    explicit DecimalFormat(UErrorCode& status);
    +    DecimalFormat(UErrorCode& status);
     
         /**
          * Create a DecimalFormat from the given pattern and the symbols
    @@ -1733,7 +1728,7 @@ class U_I18N_API DecimalFormat : public NumberFormat {
          * @see #setParseNoExponent
          * @internal This API is a technical preview. It may change in an upcoming release.
          */
    -    UBool isParseNoExponent() const;
    +    virtual UBool isParseNoExponent() const;
     
         /**
          * {@icu} Specifies whether to stop parsing when an exponent separator is encountered. For
    @@ -1751,7 +1746,7 @@ class U_I18N_API DecimalFormat : public NumberFormat {
          * @see #setParseCaseSensitive
          * @internal This API is a technical preview. It may change in an upcoming release.
          */
    -    UBool isParseCaseSensitive() const;
    +    virtual UBool isParseCaseSensitive() const;
     
         /**
          * {@icu} Whether to pay attention to case when parsing; default is to ignore case (perform
    @@ -1762,7 +1757,7 @@ class U_I18N_API DecimalFormat : public NumberFormat {
          *
          * @internal This API is a technical preview. It may change in an upcoming release.
          */
    -    void setParseCaseSensitive(UBool value);
    +    virtual void setParseCaseSensitive(UBool value);
     
         /**
          * {@icu} Returns whether truncation of high-order integer digits should result in an error.
    @@ -1771,7 +1766,7 @@ class U_I18N_API DecimalFormat : public NumberFormat {
          * @see setFormatFailIfMoreThanMaxDigits
          * @internal This API is a technical preview. It may change in an upcoming release.
          */
    -    UBool isFormatFailIfMoreThanMaxDigits() const;
    +    virtual UBool isFormatFailIfMoreThanMaxDigits() const;
     
         /**
          * {@icu} Sets whether truncation of high-order integer digits should result in an error.
    diff --git a/icu4c/source/i18n/unicode/numberformatter.h b/icu4c/source/i18n/unicode/numberformatter.h
    index b3f80afe6d..9b3a1ed729 100644
    --- a/icu4c/source/i18n/unicode/numberformatter.h
    +++ b/icu4c/source/i18n/unicode/numberformatter.h
    @@ -1676,14 +1676,14 @@ class U_I18N_API NumberFormatterSettings {
         /**
          * Overload of grouping() for use on an rvalue reference.
          *
    -     * @param rounder
    +     * @param strategy
          *            The grouping strategy to use.
          * @return The fluent chain.
          * @see #grouping
          * @provisional This API might change or be removed in a future release.
          * @draft ICU 62
          */
    -    Derived grouping(const UGroupingStrategy& rounder) &&;
    +    Derived grouping(const UGroupingStrategy& strategy) &&;
     
         /**
          * Specifies the minimum and maximum number of digits to render before the decimal mark.
    diff --git a/icu4c/source/i18n/unicode/unum.h b/icu4c/source/i18n/unicode/unum.h
    index 89d5e2fb5a..1808c8d36b 100644
    --- a/icu4c/source/i18n/unicode/unum.h
    +++ b/icu4c/source/i18n/unicode/unum.h
    @@ -1078,12 +1078,14 @@ typedef enum UNumberFormatAttribute {
     
       /**
        * Whether parsing is sensitive to case (lowercase/uppercase).
    +   * TODO: Add to the test suite.
        * @internal This API is a technical preview. It may change in an upcoming release.
        */
       UNUM_PARSE_CASE_SENSITIVE = 0x1004,
     
       /**
        * Formatting: whether to show the plus sign on non-negative numbers.
    +   * TODO: Add to the test suite.
        * @internal This API is a technical preview. It may change in an upcoming release.
        */
       UNUM_SIGN_ALWAYS_SHOWN = 0x1005,
    diff --git a/icu4c/source/test/intltest/datadrivennumberformattestsuite.h b/icu4c/source/test/intltest/datadrivennumberformattestsuite.h
    index 4363e900ba..45c2846872 100644
    --- a/icu4c/source/test/intltest/datadrivennumberformattestsuite.h
    +++ b/icu4c/source/test/intltest/datadrivennumberformattestsuite.h
    @@ -9,7 +9,6 @@
     #ifndef _DATADRIVENNUMBERFORMATTESTSUITE_H__
     #define _DATADRIVENNUMBERFORMATTESTSUITE_H__
     
    -#include "cmemory.h"
     #include "unicode/utypes.h"
     
     #if !UCONFIG_NO_FORMATTING
    @@ -18,6 +17,7 @@
     #include "unicode/unistr.h"
     #include "numberformattesttuple.h"
     #include "intltest.h"
    +#include "cmemory.h"
     
     struct UCHARBUF;
     class IntlTest;
    diff --git a/icu4c/source/test/intltest/dcfmapts.cpp b/icu4c/source/test/intltest/dcfmapts.cpp
    index 611015c159..33014340c3 100644
    --- a/icu4c/source/test/intltest/dcfmapts.cpp
    +++ b/icu4c/source/test/intltest/dcfmapts.cpp
    @@ -22,8 +22,9 @@
     
     #include "putilimp.h"
     #include "plurrule_impl.h"
    +#include "number_decimalquantity.h"
    +
     #include 
    -#include 
     
     // This is an API test, not a unit test.  It doesn't test very many cases, and doesn't
     // try to test the full functionality.  It just calls each function in the class and
    diff --git a/icu4c/source/test/testdata/numberformattestspecification.txt b/icu4c/source/test/testdata/numberformattestspecification.txt
    index cd5b479d1f..5f607833e6 100644
    --- a/icu4c/source/test/testdata/numberformattestspecification.txt
    +++ b/icu4c/source/test/testdata/numberformattestspecification.txt
    @@ -8,6 +8,15 @@
     // for that test suite. After the global settings, comes "begin", the
     // per-test field names, and finally the test specific field values, 1 test
     // per line.
    +//
    +// Field names:
    +//  J = ICU58
    +//  K = JDK
    +//  C = ICU4C
    +//  P = ICU4J parsing
    +//  Q = ICU4J formatting
    +//  S = ICU4J toPattern
    +//
     // For more information on the format of this file, including all the available
     // field names, please see
     // https://docs.google.com/document/d/1T2P0p953_Lh1pRwo-5CuPVrHlIBa_wcXElG-Hhg_WHM/edit?usp=sharing
    
    From 629238d3a1e32f3ea41c765e7aa35e4c2875ca72 Mon Sep 17 00:00:00 2001
    From: Shane Carr 
    Date: Tue, 24 Apr 2018 01:25:05 +0000
    Subject: [PATCH 129/129] ICU-13634 Fixing assorted build errors.
    
    X-SVN-Rev: 41268
    ---
     icu4c/source/i18n/number_affixutils.h            | 1 +
     icu4c/source/i18n/number_types.h                 | 1 +
     icu4c/source/i18n/unicode/compactdecimalformat.h | 2 +-
     3 files changed, 3 insertions(+), 1 deletion(-)
    
    diff --git a/icu4c/source/i18n/number_affixutils.h b/icu4c/source/i18n/number_affixutils.h
    index 2bd00d4301..d515d7b80a 100644
    --- a/icu4c/source/i18n/number_affixutils.h
    +++ b/icu4c/source/i18n/number_affixutils.h
    @@ -12,6 +12,7 @@
     #include "unicode/stringpiece.h"
     #include "unicode/unistr.h"
     #include "number_stringbuilder.h"
    +#include "unicode/uniset.h"
     
     U_NAMESPACE_BEGIN namespace number {
     namespace impl {
    diff --git a/icu4c/source/i18n/number_types.h b/icu4c/source/i18n/number_types.h
    index ce43152e1e..0261e2cee0 100644
    --- a/icu4c/source/i18n/number_types.h
    +++ b/icu4c/source/i18n/number_types.h
    @@ -15,6 +15,7 @@
     #include "unicode/utf16.h"
     #include "uassert.h"
     #include "unicode/platform.h"
    +#include "unicode/uniset.h"
     
     U_NAMESPACE_BEGIN namespace number {
     namespace impl {
    diff --git a/icu4c/source/i18n/unicode/compactdecimalformat.h b/icu4c/source/i18n/unicode/compactdecimalformat.h
    index e7995d375b..a529ae5c8c 100644
    --- a/icu4c/source/i18n/unicode/compactdecimalformat.h
    +++ b/icu4c/source/i18n/unicode/compactdecimalformat.h
    @@ -101,7 +101,7 @@ public:
          * @return    a polymorphic copy of this CompactDecimalFormat.
          * @stable ICU 51
          */
    -    virtual Format* clone() const;
    +    Format* clone() const U_OVERRIDE;
     
         using DecimalFormat::format;