diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp index c88857c2719..e84bb822386 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 ecee7388dab..299adfa2449 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 86a5a8cfa76..0a559b445e5 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 48a45c00449..c0dee2e6153 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 13c22aec75c..ef8ee7064ed 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 3c470df5045..d2d0a87b152 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 67278039110..9f729c918a4 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 a95e1da2d1f..bf4b1a7a152 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 dc014a3c92d..9ac2f98d3dc 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 4759745b217..900f77e6a12 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 b267fd2be6a..8c8a10aeace 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 f614d1d70d5..9e1404c961f 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