X-Git-Url: https://git.saurik.com/apple/icu.git/blobdiff_plain/08b89b0a244153b9f5bbb2f49c55ab0f7298122e..a01113dcd0f39d5da295ef82785beff9ed86fe38:/icuSources/i18n/decimfmt.cpp?ds=sidebyside diff --git a/icuSources/i18n/decimfmt.cpp b/icuSources/i18n/decimfmt.cpp index 207abaf7..d428cddc 100644 --- a/icuSources/i18n/decimfmt.cpp +++ b/icuSources/i18n/decimfmt.cpp @@ -1,5593 +1,1952 @@ -/* -******************************************************************************* -* Copyright (C) 1997-2014, 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 -******************************************************************************** -*/ +// © 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 -#include "fphdlimp.h" +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + +#include +#include +#include +#include "unicode/errorcode.h" #include "unicode/decimfmt.h" -#include "unicode/choicfmt.h" #include "unicode/ucurr.h" -#include "unicode/ustring.h" -#include "unicode/dcfmtsym.h" -#include "unicode/ures.h" -#include "unicode/uchar.h" -#include "unicode/uniset.h" -#include "unicode/curramt.h" -#include "unicode/currpinf.h" -#include "unicode/plurrule.h" -#include "unicode/utf16.h" -#include "unicode/numsys.h" -#include "unicode/localpointer.h" -#include "uresimp.h" -#include "ucurrimp.h" -#include "charstr.h" -#include "cmemory.h" -#include "patternprops.h" -#include "digitlst.h" -#include "cstring.h" -#include "umutex.h" -#include "uassert.h" +#include "number_decimalquantity.h" +#include "number_types.h" +#include "numparse_impl.h" +#include "number_mapper.h" +#include "number_patternstring.h" #include "putilimp.h" -#include -#include "hash.h" -#include "decfmtst.h" -#include "dcfmtimp.h" -#include "plurrule_impl.h" -#include "decimalformatpattern.h" - -/* - * 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 +#include "number_utils.h" +#include "number_utypes.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; + +// MSVC VS2015 warns C4805 when comparing bool with UBool, VS2017 no longer emits this warning. +// TODO: Move this macro into a better place? +#if U_PF_WINDOWS <= U_PLATFORM && U_PLATFORM <= U_PF_CYGWIN +#define UBOOL_TO_BOOL(b) static_cast(b) +#else +#define UBOOL_TO_BOOL(b) b #endif -U_NAMESPACE_BEGIN +UOBJECT_DEFINE_RTTI_IMPLEMENTATION(DecimalFormat) + -#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); +DecimalFormat::DecimalFormat(UErrorCode& status) + : DecimalFormat(nullptr, status) { + if (U_FAILURE(status)) { return; } + // Use the default locale and decimal pattern. + const char* localeName = Locale::getDefault().getName(); + LocalPointer ns(NumberingSystem::createInstance(status)); + UnicodeString patternString = utils::getPatternForStyle( + localeName, + ns->getName(), + CLDR_PATTERN_STYLE_DECIMAL, + status); + setPropertiesFromPattern(patternString, IGNORE_ROUNDING_IF_CURRENCY, status); + touch(status); +} + +DecimalFormat::DecimalFormat(const UnicodeString& pattern, UErrorCode& status) + : DecimalFormat(nullptr, status) { + if (U_FAILURE(status)) { return; } + setPropertiesFromPattern(pattern, IGNORE_ROUNDING_IF_CURRENCY, status); + touch(status); +} + +DecimalFormat::DecimalFormat(const UnicodeString& pattern, DecimalFormatSymbols* symbolsToAdopt, + UErrorCode& status) + : DecimalFormat(symbolsToAdopt, status) { + if (U_FAILURE(status)) { return; } + setPropertiesFromPattern(pattern, IGNORE_ROUNDING_IF_CURRENCY, status); + touch(status); +} + +DecimalFormat::DecimalFormat(const UnicodeString& pattern, DecimalFormatSymbols* symbolsToAdopt, + UNumberFormatStyle style, UErrorCode& status) + : DecimalFormat(symbolsToAdopt, status) { + if (U_FAILURE(status)) { return; } + // 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); + } + // 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(fields->symbols->getLocale(), status), + status); + if (U_FAILURE(status)) { return; } + fields->properties->currencyPluralInfo.fPtr.adoptInstead(cpi.orphan()); + } + touch(status); } -#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 +DecimalFormat::DecimalFormat(const DecimalFormatSymbols* symbolsToAdopt, UErrorCode& status) { + // we must take ownership of symbolsToAdopt, even in a failure case. + LocalPointer adoptedSymbols(symbolsToAdopt); + if (U_FAILURE(status)) { + return; + } + fields = new DecimalFormatFields(); + if (fields == nullptr) { + status = U_MEMORY_ALLOCATION_ERROR; + return; + } + fields->formatter.adoptInsteadAndCheckErrorCode(new LocalizedNumberFormatter(), status); + fields->properties.adoptInsteadAndCheckErrorCode(new DecimalFormatProperties(), status); + fields->exportedProperties.adoptInsteadAndCheckErrorCode(new DecimalFormatProperties(), status); + if (adoptedSymbols.isNull()) { + fields->symbols.adoptInsteadAndCheckErrorCode(new DecimalFormatSymbols(status), status); + } else { + fields->symbols.adoptInsteadAndCheckErrorCode(adoptedSymbols.orphan(), status); + } + // In order to simplify error handling logic in the various getters/setters/etc, we do not allow + // any partially populated DecimalFormatFields object. We must have a fully complete fields object + // or else we set it to nullptr. + if (fields->formatter.isNull() || fields->properties.isNull() || fields->exportedProperties.isNull() || fields->symbols.isNull()) { + delete fields; + fields = nullptr; + status = U_MEMORY_ALLOCATION_ERROR; + } +} +#if UCONFIG_HAVE_PARSEALLINPUT -/* == Fastpath calculation. == - */ -#if UCONFIG_FORMAT_FASTPATHS_49 -inline DecimalFormatInternal& internalData(uint8_t *reserved) { - return *reinterpret_cast(reserved); -} -inline const DecimalFormatInternal& internalData(const uint8_t *reserved) { - return *reinterpret_cast(reserved); +void DecimalFormat::setParseAllInput(UNumberFormatAttributeValue value) { + if (fields == nullptr) { return; } + if (value == fields->properties->parseAllInput) { return; } + fields->properties->parseAllInput = value; } -#else -#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 decimfmtAffixValueComparator(UHashTok val1, UHashTok val2); - -/** - * @internal ICU 4.2 - */ -static UBool U_CALLCONV decimfmtAffixPatternValueComparator(UHashTok val1, UHashTok val2); - - -static UBool -U_CALLCONV decimfmtAffixValueComparator(UHashTok val1, UHashTok val2) { - const AffixesForCurrency* affix_1 = - (AffixesForCurrency*)val1.pointer; - const AffixesForCurrency* affix_2 = - (AffixesForCurrency*)val2.pointer; - return affix_1->negPrefixForCurrency == affix_2->negPrefixForCurrency && - affix_1->negSuffixForCurrency == affix_2->negSuffixForCurrency && - affix_1->posPrefixForCurrency == affix_2->posPrefixForCurrency && - affix_1->posSuffixForCurrency == affix_2->posSuffixForCurrency; -} +DecimalFormat& +DecimalFormat::setAttribute(UNumberFormatAttribute attr, int32_t newValue, UErrorCode& status) { + if (U_FAILURE(status)) { return *this; } -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; -} + if (fields == nullptr) { + // We only get here if an OOM error happend during construction, copy construction, assignment, or modification. + status = U_MEMORY_ALLOCATION_ERROR; + return *this; + } + + switch (attr) { + case UNUM_LENIENT_PARSE: + setLenient(newValue != 0); + break; -U_CDECL_END + 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; -// ***************************************************************************** -// class DecimalFormat -// ***************************************************************************** + case UNUM_MIN_INTEGER_DIGITS: + setMinimumIntegerDigits(newValue); + break; -UOBJECT_DEFINE_RTTI_IMPLEMENTATION(DecimalFormat) + case UNUM_INTEGER_DIGITS: + setMinimumIntegerDigits(newValue); + setMaximumIntegerDigits(newValue); + break; -// 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"; - -static const UChar fgTripleCurrencySign[] = {0xA4, 0xA4, 0xA4, 0}; - -inline int32_t _min(int32_t a, int32_t b) { return (agetName(), 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); - } + case UNUM_PARSE_CASE_SENSITIVE: + setParseCaseSensitive(static_cast(newValue)); + break; - delete ns; + case UNUM_SIGN_ALWAYS_SHOWN: + setSignAlwaysShown(static_cast(newValue)); + break; - if (U_FAILURE(status)) - { - return; + case UNUM_FORMAT_FAIL_IF_MORE_THAN_MAX_DIGITS: + setFormatFailIfMoreThanMaxDigits(static_cast(newValue)); + break; + + case UNUM_FORMAT_WITH_FULL_PRECISION: // Apple addition for + { + bool boolVal = UBOOL_TO_BOOL(static_cast(newValue)); + if (boolVal != fields->properties->formatFullPrecision) { + fields->properties->formatFullPrecision = boolVal; + touchNoError(); + } + } + break; + + default: + status = U_UNSUPPORTED_ERROR; + break; } + return *this; +} - if (pattern->indexOf((UChar)kCurrencySign) >= 0) { - // If it looks like we are going to use a currency pattern - // then do the time consuming lookup. - setCurrencyForSymbols(); - } else { - setCurrencyInternally(NULL, status); +int32_t DecimalFormat::getAttribute(UNumberFormatAttribute attr, UErrorCode& status) const { + if (U_FAILURE(status)) { return -1; } + + if (fields == nullptr) { + // We only get here if an OOM error happend during construction, copy construction, assignment, or modification. + status = U_MEMORY_ALLOCATION_ERROR; + return -1; } - const UnicodeString* patternUsed; - UnicodeString currencyPluralPatternForOther; - // apply pattern - if (fStyle == UNUM_CURRENCY_PLURAL) { - fCurrencyPluralInfo = new CurrencyPluralInfo(fSymbols->getLocale(), status); - if (U_FAILURE(status)) { - return; - } + switch (attr) { + case UNUM_LENIENT_PARSE: + return isLenient(); - // 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); - patternUsed = ¤cyPluralPatternForOther; - // TODO: not needed? - setCurrencyForSymbols(); + case UNUM_PARSE_INT_ONLY: + return isParseIntegerOnly(); - } else { - patternUsed = pattern; - } + case UNUM_GROUPING_USED: + return isGroupingUsed(); - if (patternUsed->indexOf(kCurrencySign) != -1) { - // initialize for currency, not only for plural format, - // but also for mix parsing - if (fCurrencyPluralInfo == NULL) { - fCurrencyPluralInfo = new CurrencyPluralInfo(fSymbols->getLocale(), status); - if (U_FAILURE(status)) { - return; - } - } - // need it for mix parsing - setupCurrencyAffixPatterns(status); - // expanded affixes for plural names - if (patternUsed->indexOf(fgTripleCurrencySign, 3, 0) != -1) { - setupCurrencyAffixes(*patternUsed, TRUE, TRUE, status); - } - } + case UNUM_DECIMAL_ALWAYS_SHOWN: + return isDecimalSeparatorAlwaysShown(); - applyPatternWithoutExpandAffix(*patternUsed,FALSE, parseErr, status); + case UNUM_MAX_INTEGER_DIGITS: + return getMaximumIntegerDigits(); - // expand affixes - if (fCurrencySignCount != fgCurrencySignCountInPluralFormat) { - expandAffixAdjustWidth(NULL); - } + case UNUM_MIN_INTEGER_DIGITS: + return getMinimumIntegerDigits(); - // If it was a currency format, apply the appropriate rounding by - // resetting the currency. NOTE: this copies fCurrency on top of itself. - if (fCurrencySignCount != fgCurrencySignCountZero) { - setCurrencyInternally(getCurrency(), status); - } -#if UCONFIG_FORMAT_FASTPATHS_49 - DecimalFormatInternal &data = internalData(fReserved); - data.fFastFormatStatus = kFastpathNO; // allow it to be calculated - data.fFastParseStatus = kFastpathNO; // allow it to be calculated - handleChanged(); -#endif -} + case UNUM_INTEGER_DIGITS: + // TBD: what should this return? + return getMinimumIntegerDigits(); + case UNUM_MAX_FRACTION_DIGITS: + return getMaximumFractionDigits(); -void -DecimalFormat::setupCurrencyAffixPatterns(UErrorCode& status) { - if (U_FAILURE(status)) { - return; + 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_SCALE: + return getMultiplierScale(); + + 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 isParseNoExponent(); + + case UNUM_PARSE_DECIMAL_MARK_REQUIRED: + return isDecimalPatternMatchRequired(); + + case UNUM_CURRENCY_USAGE: + return getCurrencyUsage(); + + case UNUM_MINIMUM_GROUPING_DIGITS: + return getMinimumGroupingDigits(); + + case UNUM_PARSE_CASE_SENSITIVE: + return isParseCaseSensitive(); + + case UNUM_SIGN_ALWAYS_SHOWN: + return isSignAlwaysShown(); + + case UNUM_FORMAT_FAIL_IF_MORE_THAN_MAX_DIGITS: + return isFormatFailIfMoreThanMaxDigits(); + + case UNUM_FORMAT_WITH_FULL_PRECISION: // Apple addition for + return (UBool)fields->properties->formatFullPrecision; + + default: + status = U_UNSUPPORTED_ERROR; + break; } - UParseError parseErr; - fAffixPatternsForCurrency = initHashForAffixPattern(status); - if (U_FAILURE(status)) { + + return -1; /* undefined */ +} + +void DecimalFormat::setGroupingUsed(UBool enabled) { + if (fields == nullptr) { return; } + if (UBOOL_TO_BOOL(enabled) == fields->properties->groupingUsed) { return; } + NumberFormat::setGroupingUsed(enabled); // to set field for compatibility + fields->properties->groupingUsed = enabled; + touchNoError(); +} - NumberingSystem *ns = NumberingSystem::createInstance(fSymbols->getLocale(),status); - if (U_FAILURE(status)) { +void DecimalFormat::setParseIntegerOnly(UBool value) { + if (fields == nullptr) { return; } + if (UBOOL_TO_BOOL(value) == fields->properties->parseIntegerOnly) { return; } + NumberFormat::setParseIntegerOnly(value); // to set field for compatibility + fields->properties->parseIntegerOnly = value; + touchNoError(); +} - // 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, 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)) { - applyPatternWithoutExpandAffix(UnicodeString(patResStr, patLen), false, - parseErr, status); - AffixPatternsForCurrency* affixPtn = new AffixPatternsForCurrency( - *fNegPrefixPattern, - *fNegSuffixPattern, - *fPosPrefixPattern, - *fPosSuffixPattern, - 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 = -1; - 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) { - pluralPatternSet.puti(*value, 1, status); - applyPatternWithoutExpandAffix(*value, false, parseErr, status); - AffixPatternsForCurrency* affixPtn = new AffixPatternsForCurrency( - *fNegPrefixPattern, - *fNegSuffixPattern, - *fPosPrefixPattern, - *fPosSuffixPattern, - UCURR_LONG_NAME); - fAffixPatternsForCurrency->put(*key, affixPtn, status); - } +void DecimalFormat::setLenient(UBool enable) { + if (fields == nullptr) { + return; } + ParseMode mode = enable ? PARSE_MODE_LENIENT : PARSE_MODE_STRICT; + if (!fields->properties->parseMode.isNull() && mode == fields->properties->parseMode.getNoError()) { return; } + NumberFormat::setLenient(enable); // to set field for compatibility + fields->properties->parseMode = mode; + touchNoError(); } +DecimalFormat::DecimalFormat(const UnicodeString& pattern, DecimalFormatSymbols* symbolsToAdopt, + UParseError&, UErrorCode& status) + : DecimalFormat(symbolsToAdopt, status) { + if (U_FAILURE(status)) { return; } + // TODO: What is parseError for? + setPropertiesFromPattern(pattern, IGNORE_ROUNDING_IF_CURRENCY, status); + touch(status); +} -void -DecimalFormat::setupCurrencyAffixes(const UnicodeString& pattern, - UBool setupForCurrentPattern, - UBool setupForPluralPattern, - UErrorCode& status) { +DecimalFormat::DecimalFormat(const UnicodeString& pattern, const DecimalFormatSymbols& symbols, + UErrorCode& status) + : DecimalFormat(nullptr, status) { + if (U_FAILURE(status)) { return; } + LocalPointer dfs(new DecimalFormatSymbols(symbols), status); if (U_FAILURE(status)) { + // If we failed to allocate DecimalFormatSymbols, then release fields and its members. + // We must have a fully complete fields object, we cannot have partially populated members. + delete fields; + fields = nullptr; + status = U_MEMORY_ALLOCATION_ERROR; return; } - UParseError parseErr; - if (setupForCurrentPattern) { - if (fAffixesForCurrency) { - deleteHashForAffix(fAffixesForCurrency); - } - fAffixesForCurrency = initHashForAffix(status); - if (U_SUCCESS(status)) { - applyPatternWithoutExpandAffix(pattern, false, parseErr, status); - const PluralRules* pluralRules = fCurrencyPluralInfo->getPluralRules(); - StringEnumeration* keywords = pluralRules->getKeywords(status); - if (U_SUCCESS(status)) { - const UnicodeString* pluralCount; - while ((pluralCount = keywords->snext(status)) != NULL) { - if ( U_SUCCESS(status) ) { - expandAffixAdjustWidth(pluralCount); - AffixesForCurrency* affix = new AffixesForCurrency( - fNegativePrefix, fNegativeSuffix, fPositivePrefix, fPositiveSuffix); - fAffixesForCurrency->put(*pluralCount, affix, status); - } - } - } - delete keywords; - } - } + fields->symbols.adoptInstead(dfs.orphan()); + setPropertiesFromPattern(pattern, IGNORE_ROUNDING_IF_CURRENCY, status); + touch(status); +} - if (U_FAILURE(status)) { +DecimalFormat::DecimalFormat(const DecimalFormat& source) : NumberFormat(source) { + // If the object that we are copying from is invalid, no point in going further. + if (source.fields == nullptr) { return; } - - if (setupForPluralPattern) { - if (fPluralAffixesForCurrency) { - deleteHashForAffix(fPluralAffixesForCurrency); - } - fPluralAffixesForCurrency = initHashForAffix(status); - if (U_SUCCESS(status)) { - const PluralRules* pluralRules = fCurrencyPluralInfo->getPluralRules(); - StringEnumeration* keywords = pluralRules->getKeywords(status); - if (U_SUCCESS(status)) { - const UnicodeString* pluralCount; - while ((pluralCount = keywords->snext(status)) != NULL) { - if ( U_SUCCESS(status) ) { - UnicodeString ptn; - fCurrencyPluralInfo->getCurrencyPluralPattern(*pluralCount, ptn); - applyPatternInternally(*pluralCount, ptn, false, parseErr, status); - AffixesForCurrency* affix = new AffixesForCurrency( - fNegativePrefix, fNegativeSuffix, fPositivePrefix, fPositiveSuffix); - fPluralAffixesForCurrency->put(*pluralCount, affix, status); - } - } - } - delete keywords; - } + // Note: it is not safe to copy fields->formatter or fWarehouse directly because fields->formatter might have + // dangling pointers to fields inside fWarehouse. The safe thing is to re-construct fields->formatter from + // the property bag, despite being somewhat slower. + fields = new DecimalFormatFields(); + if (fields == nullptr) { + return; // no way to report an error. + } + UErrorCode status = U_ZERO_ERROR; + fields->formatter.adoptInsteadAndCheckErrorCode(new LocalizedNumberFormatter(), status); + fields->properties.adoptInsteadAndCheckErrorCode(new DecimalFormatProperties(*source.fields->properties), status); + fields->symbols.adoptInsteadAndCheckErrorCode(new DecimalFormatSymbols(*source.fields->symbols), status); + fields->exportedProperties.adoptInsteadAndCheckErrorCode(new DecimalFormatProperties(), status); + // In order to simplify error handling logic in the various getters/setters/etc, we do not allow + // any partially populated DecimalFormatFields object. We must have a fully complete fields object + // or else we set it to nullptr. + if (fields->formatter.isNull() || fields->properties.isNull() || fields->exportedProperties.isNull() || fields->symbols.isNull()) { + delete fields; + fields = nullptr; + return; } + touch(status); } +DecimalFormat& DecimalFormat::operator=(const DecimalFormat& rhs) { + // guard against self-assignment + if (this == &rhs) { + return *this; + } + // Make sure both objects are valid. + if (fields == nullptr || rhs.fields == nullptr) { + return *this; // unfortunately, no way to report an error. + } + *fields->properties = *rhs.fields->properties; + fields->exportedProperties->clear(); + UErrorCode status = U_ZERO_ERROR; + LocalPointer dfs(new DecimalFormatSymbols(*rhs.fields->symbols), status); + if (U_FAILURE(status)) { + // We failed to allocate DecimalFormatSymbols, release fields and its members. + // We must have a fully complete fields object, we cannot have partially populated members. + delete fields; + fields = nullptr; + return *this; + } + fields->symbols.adoptInstead(dfs.orphan()); + touch(status); -//------------------------------------------------------------------------------ - -DecimalFormat::~DecimalFormat() -{ - delete fPosPrefixPattern; - delete fPosSuffixPattern; - delete fNegPrefixPattern; - delete fNegSuffixPattern; - delete fCurrencyChoice; - delete fMultiplier; - delete fSymbols; - delete fRoundingIncrement; - deleteHashForAffixPattern(); - deleteHashForAffix(fAffixesForCurrency); - deleteHashForAffix(fPluralAffixesForCurrency); - delete fCurrencyPluralInfo; + return *this; } -//------------------------------------------------------------------------------ -// copy constructor +DecimalFormat::~DecimalFormat() { + if (fields == nullptr) { return; } -DecimalFormat::DecimalFormat(const DecimalFormat &source) : - NumberFormat(source) { - init(); - *this = source; + delete fields->atomicParser.exchange(nullptr); + delete fields->atomicCurrencyParser.exchange(nullptr); + delete fields; } -//------------------------------------------------------------------------------ -// assignment operator - -template -static void _copy_ptr(T** pdest, const T* source) { - if (source == NULL) { - delete *pdest; - *pdest = NULL; - } else if (*pdest == NULL) { - *pdest = new T(*source); - } else { - **pdest = *source; +Format* DecimalFormat::clone() const { + // can only clone valid objects. + if (fields == nullptr) { + return nullptr; } + LocalPointer df(new DecimalFormat(*this)); + if (df.isValid() && df->fields != nullptr) { + return df.orphan(); + } + return nullptr; } -template -static void _clone_ptr(T** pdest, const T* source) { - delete *pdest; - if (source == NULL) { - *pdest = NULL; - } else { - *pdest = static_cast(source->clone()); +UBool DecimalFormat::operator==(const Format& other) const { + auto* otherDF = dynamic_cast(&other); + if (otherDF == nullptr) { + return false; } + // If either object is in an invalid state, prevent dereferencing nullptr below. + // Additionally, invalid objects should not be considered equal to anything. + if (fields == nullptr || otherDF->fields == nullptr) { + return false; + } + return *fields->properties == *otherDF->fields->properties && *fields->symbols == *otherDF->fields->symbols; } -DecimalFormat& -DecimalFormat::operator=(const DecimalFormat& rhs) -{ - if(this != &rhs) { - UErrorCode status = U_ZERO_ERROR; - NumberFormat::operator=(rhs); - fStaticSets = DecimalFormatStaticSets::getStaticSets(status); - fPositivePrefix = rhs.fPositivePrefix; - fPositiveSuffix = rhs.fPositiveSuffix; - fNegativePrefix = rhs.fNegativePrefix; - fNegativeSuffix = rhs.fNegativeSuffix; - _copy_ptr(&fPosPrefixPattern, rhs.fPosPrefixPattern); - _copy_ptr(&fPosSuffixPattern, rhs.fPosSuffixPattern); - _copy_ptr(&fNegPrefixPattern, rhs.fNegPrefixPattern); - _copy_ptr(&fNegSuffixPattern, rhs.fNegSuffixPattern); - _clone_ptr(&fCurrencyChoice, rhs.fCurrencyChoice); - setRoundingIncrement(rhs.getRoundingIncrement()); - fRoundingMode = rhs.fRoundingMode; - setMultiplier(rhs.getMultiplier()); - fGroupingSize = rhs.fGroupingSize; - fGroupingSize2 = rhs.fGroupingSize2; - fDecimalSeparatorAlwaysShown = rhs.fDecimalSeparatorAlwaysShown; - _copy_ptr(&fSymbols, rhs.fSymbols); - fUseExponentialNotation = rhs.fUseExponentialNotation; - fExponentSignAlwaysShown = rhs.fExponentSignAlwaysShown; - fBoolFlags = rhs.fBoolFlags; - /*Bertrand A. D. Update 98.03.17*/ - fCurrencySignCount = rhs.fCurrencySignCount; - /*end of Update*/ - fMinExponentDigits = rhs.fMinExponentDigits; - - /* sfb 990629 */ - fFormatWidth = rhs.fFormatWidth; - fPad = rhs.fPad; - fPadPosition = rhs.fPadPosition; - /* end sfb */ - fMinSignificantDigits = rhs.fMinSignificantDigits; - fMaxSignificantDigits = rhs.fMaxSignificantDigits; - fUseSignificantDigits = rhs.fUseSignificantDigits; - fFormatPattern = rhs.fFormatPattern; - 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); - } - deleteHashForAffix(fAffixesForCurrency); - if (rhs.fAffixesForCurrency) { - UErrorCode status = U_ZERO_ERROR; - fAffixesForCurrency = initHashForAffixPattern(status); - copyHashForAffix(rhs.fAffixesForCurrency, fAffixesForCurrency, status); - } - deleteHashForAffix(fPluralAffixesForCurrency); - if (rhs.fPluralAffixesForCurrency) { - UErrorCode status = U_ZERO_ERROR; - fPluralAffixesForCurrency = initHashForAffixPattern(status); - copyHashForAffix(rhs.fPluralAffixesForCurrency, fPluralAffixesForCurrency, status); - } -#if UCONFIG_FORMAT_FASTPATHS_49 - DecimalFormatInternal &data = internalData(fReserved); - const DecimalFormatInternal &rhsData = internalData(rhs.fReserved); - data = rhsData; -#endif +UnicodeString& DecimalFormat::format(double number, UnicodeString& appendTo, FieldPosition& pos) const { + if (fields == nullptr) { + appendTo.setToBogus(); + return appendTo; } - return *this; + if (pos.getField() == FieldPosition::DONT_CARE && fastFormatDouble(number, appendTo)) { + return appendTo; + } + UErrorCode localStatus = U_ZERO_ERROR; + FormattedNumber output = fields->formatter->formatDouble(number, localStatus); + fieldPositionHelper(output, pos, appendTo.length(), localStatus); + auto appendable = UnicodeStringAppendable(appendTo); + output.appendTo(appendable, localStatus); + return appendTo; } -//------------------------------------------------------------------------------ - -UBool -DecimalFormat::operator==(const Format& that) const -{ - if (this == &that) - return TRUE; - - // NumberFormat::operator== guarantees this cast is safe - const DecimalFormat* other = (DecimalFormat*)&that; - -#ifdef FMT_DEBUG - // This code makes it easy to determine why two format objects that should - // be equal aren't. - UBool first = TRUE; - if (!NumberFormat::operator==(that)) { - if (first) { printf("[ "); first = FALSE; } else { printf(", "); } - debug("NumberFormat::!="); - } else { - if (!((fPosPrefixPattern == other->fPosPrefixPattern && // both null - fPositivePrefix == other->fPositivePrefix) - || (fPosPrefixPattern != 0 && other->fPosPrefixPattern != 0 && - *fPosPrefixPattern == *other->fPosPrefixPattern))) { - if (first) { printf("[ "); first = FALSE; } else { printf(", "); } - debug("Pos Prefix !="); - } - if (!((fPosSuffixPattern == other->fPosSuffixPattern && // both null - fPositiveSuffix == other->fPositiveSuffix) - || (fPosSuffixPattern != 0 && other->fPosSuffixPattern != 0 && - *fPosSuffixPattern == *other->fPosSuffixPattern))) { - if (first) { printf("[ "); first = FALSE; } else { printf(", "); } - debug("Pos Suffix !="); - } - if (!((fNegPrefixPattern == other->fNegPrefixPattern && // both null - fNegativePrefix == other->fNegativePrefix) - || (fNegPrefixPattern != 0 && other->fNegPrefixPattern != 0 && - *fNegPrefixPattern == *other->fNegPrefixPattern))) { - if (first) { printf("[ "); first = FALSE; } else { printf(", "); } - debug("Neg Prefix "); - if (fNegPrefixPattern == NULL) { - debug("NULL("); - debugout(fNegativePrefix); - debug(")"); - } else { - debugout(*fNegPrefixPattern); - } - debug(" != "); - if (other->fNegPrefixPattern == NULL) { - debug("NULL("); - debugout(other->fNegativePrefix); - debug(")"); - } else { - debugout(*other->fNegPrefixPattern); - } - } - if (!((fNegSuffixPattern == other->fNegSuffixPattern && // both null - fNegativeSuffix == other->fNegativeSuffix) - || (fNegSuffixPattern != 0 && other->fNegSuffixPattern != 0 && - *fNegSuffixPattern == *other->fNegSuffixPattern))) { - if (first) { printf("[ "); first = FALSE; } else { printf(", "); } - debug("Neg Suffix "); - if (fNegSuffixPattern == NULL) { - debug("NULL("); - debugout(fNegativeSuffix); - debug(")"); - } else { - debugout(*fNegSuffixPattern); - } - debug(" != "); - if (other->fNegSuffixPattern == NULL) { - debug("NULL("); - debugout(other->fNegativeSuffix); - debug(")"); - } else { - debugout(*other->fNegSuffixPattern); - } +UnicodeString& DecimalFormat::format(double number, UnicodeString& appendTo, FieldPosition& pos, + UErrorCode& status) const { + if (U_FAILURE(status)) { + return appendTo; // don't overwrite status if it's already a failure. } - if (!((fRoundingIncrement == other->fRoundingIncrement) // both null - || (fRoundingIncrement != NULL && - other->fRoundingIncrement != NULL && - *fRoundingIncrement == *other->fRoundingIncrement))) { - if (first) { printf("[ "); first = FALSE; } else { printf(", "); } - debug("Rounding Increment !="); - } - if (fRoundingMode != other->fRoundingMode) { - if (first) { printf("[ "); first = FALSE; } else { printf(", "); } - printf("Rounding Mode %d != %d", (int)fRoundingMode, (int)other->fRoundingMode); - } - if (getMultiplier() != other->getMultiplier()) { - if (first) { printf("[ "); first = FALSE; } - printf("Multiplier %ld != %ld", getMultiplier(), other->getMultiplier()); - } - if (fGroupingSize != other->fGroupingSize) { - if (first) { printf("[ "); first = FALSE; } else { printf(", "); } - printf("Grouping Size %ld != %ld", fGroupingSize, other->fGroupingSize); - } - if (fGroupingSize2 != other->fGroupingSize2) { - if (first) { printf("[ "); first = FALSE; } else { printf(", "); } - printf("Secondary Grouping Size %ld != %ld", fGroupingSize2, other->fGroupingSize2); - } - if (fDecimalSeparatorAlwaysShown != other->fDecimalSeparatorAlwaysShown) { - if (first) { printf("[ "); first = FALSE; } else { printf(", "); } - printf("fDecimalSeparatorAlwaysShown %d != %d", fDecimalSeparatorAlwaysShown, other->fDecimalSeparatorAlwaysShown); - } - if (fUseExponentialNotation != other->fUseExponentialNotation) { - if (first) { printf("[ "); first = FALSE; } else { printf(", "); } - debug("fUseExponentialNotation !="); - } - if (fUseExponentialNotation && - fMinExponentDigits != other->fMinExponentDigits) { - if (first) { printf("[ "); first = FALSE; } else { printf(", "); } - debug("fMinExponentDigits !="); - } - if (fUseExponentialNotation && - fExponentSignAlwaysShown != other->fExponentSignAlwaysShown) { - if (first) { printf("[ "); first = FALSE; } else { printf(", "); } - debug("fExponentSignAlwaysShown !="); - } - if (fBoolFlags.getAll() != other->fBoolFlags.getAll()) { - if (first) { printf("[ "); first = FALSE; } else { printf(", "); } - debug("fBoolFlags !="); - } - if (*fSymbols != *(other->fSymbols)) { - if (first) { printf("[ "); first = FALSE; } else { printf(", "); } - debug("Symbols !="); - } - // TODO Add debug stuff for significant digits here - if (fUseSignificantDigits != other->fUseSignificantDigits) { - if (first) { printf("[ "); first = FALSE; } else { printf(", "); } - debug("fUseSignificantDigits !="); - } - if (fUseSignificantDigits && - fMinSignificantDigits != other->fMinSignificantDigits) { - if (first) { printf("[ "); first = FALSE; } else { printf(", "); } - debug("fMinSignificantDigits !="); - } - if (fUseSignificantDigits && - fMaxSignificantDigits != other->fMaxSignificantDigits) { - if (first) { printf("[ "); first = FALSE; } else { printf(", "); } - debug("fMaxSignificantDigits !="); - } - if (fFormatWidth != other->fFormatWidth) { - if (first) { printf("[ "); first = FALSE; } else { printf(", "); } - debug("fFormatWidth !="); - } - if (fPad != other->fPad) { - if (first) { printf("[ "); first = FALSE; } else { printf(", "); } - debug("fPad !="); - } - if (fPadPosition != other->fPadPosition) { - if (first) { printf("[ "); first = FALSE; } else { printf(", "); } - debug("fPadPosition !="); - } - if (fStyle == UNUM_CURRENCY_PLURAL && - fStyle != other->fStyle) - if (first) { printf("[ "); first = FALSE; } else { printf(", "); } - debug("fStyle !="); - } - if (fStyle == UNUM_CURRENCY_PLURAL && - fFormatPattern != other->fFormatPattern) { - if (first) { printf("[ "); first = FALSE; } else { printf(", "); } - debug("fFormatPattern !="); - } - - if (!first) { printf(" ]"); } - if (fCurrencySignCount != other->fCurrencySignCount) { - debug("fCurrencySignCount !="); - } - if (fCurrencyPluralInfo == other->fCurrencyPluralInfo) { - debug("fCurrencyPluralInfo == "); - if (fCurrencyPluralInfo == NULL) { - debug("fCurrencyPluralInfo == NULL"); - } + if (fields == nullptr) { + // We only get here if an OOM error happend during construction, copy construction, assignment, or modification. + status = U_MEMORY_ALLOCATION_ERROR; + appendTo.setToBogus(); + return appendTo; } - if (fCurrencyPluralInfo != NULL && other->fCurrencyPluralInfo != NULL && - *fCurrencyPluralInfo != *(other->fCurrencyPluralInfo)) { - debug("fCurrencyPluralInfo !="); + if (pos.getField() == FieldPosition::DONT_CARE && fastFormatDouble(number, appendTo)) { + return appendTo; } - if (fCurrencyPluralInfo != NULL && other->fCurrencyPluralInfo == NULL || - fCurrencyPluralInfo == NULL && other->fCurrencyPluralInfo != NULL) { - debug("fCurrencyPluralInfo one NULL, the other not"); + FormattedNumber output = fields->formatter->formatDouble(number, status); + fieldPositionHelper(output, pos, appendTo.length(), status); + auto appendable = UnicodeStringAppendable(appendTo); + output.appendTo(appendable, status); + return appendTo; +} + +UnicodeString& +DecimalFormat::format(double number, UnicodeString& appendTo, FieldPositionIterator* posIter, + UErrorCode& status) const { + if (U_FAILURE(status)) { + return appendTo; // don't overwrite status if it's already a failure. } - if (fCurrencyPluralInfo == NULL && other->fCurrencyPluralInfo == NULL) { - debug("fCurrencyPluralInfo == "); + if (fields == nullptr) { + // We only get here if an OOM error happend during construction, copy construction, assignment, or modification. + status = U_MEMORY_ALLOCATION_ERROR; + appendTo.setToBogus(); + return appendTo; } + if (posIter == nullptr && fastFormatDouble(number, appendTo)) { + return appendTo; } -#endif - - return ( - NumberFormat::operator==(that) && - - ((fCurrencySignCount == fgCurrencySignCountInPluralFormat) ? - (fAffixPatternsForCurrency->equals(*other->fAffixPatternsForCurrency)) : - (((fPosPrefixPattern == other->fPosPrefixPattern && // both null - fPositivePrefix == other->fPositivePrefix) - || (fPosPrefixPattern != 0 && other->fPosPrefixPattern != 0 && - *fPosPrefixPattern == *other->fPosPrefixPattern)) && - ((fPosSuffixPattern == other->fPosSuffixPattern && // both null - fPositiveSuffix == other->fPositiveSuffix) - || (fPosSuffixPattern != 0 && other->fPosSuffixPattern != 0 && - *fPosSuffixPattern == *other->fPosSuffixPattern)) && - ((fNegPrefixPattern == other->fNegPrefixPattern && // both null - fNegativePrefix == other->fNegativePrefix) - || (fNegPrefixPattern != 0 && other->fNegPrefixPattern != 0 && - *fNegPrefixPattern == *other->fNegPrefixPattern)) && - ((fNegSuffixPattern == other->fNegSuffixPattern && // both null - fNegativeSuffix == other->fNegativeSuffix) - || (fNegSuffixPattern != 0 && other->fNegSuffixPattern != 0 && - *fNegSuffixPattern == *other->fNegSuffixPattern)))) && - - ((fRoundingIncrement == other->fRoundingIncrement) // both null - || (fRoundingIncrement != NULL && - other->fRoundingIncrement != NULL && - *fRoundingIncrement == *other->fRoundingIncrement)) && - - fRoundingMode == other->fRoundingMode && - getMultiplier() == other->getMultiplier() && - fGroupingSize == other->fGroupingSize && - fGroupingSize2 == other->fGroupingSize2 && - fDecimalSeparatorAlwaysShown == other->fDecimalSeparatorAlwaysShown && - fUseExponentialNotation == other->fUseExponentialNotation && - - (!fUseExponentialNotation || - (fMinExponentDigits == other->fMinExponentDigits && fExponentSignAlwaysShown == other->fExponentSignAlwaysShown)) && - - fBoolFlags.getAll() == other->fBoolFlags.getAll() && - *fSymbols == *(other->fSymbols) && - fUseSignificantDigits == other->fUseSignificantDigits && - - (!fUseSignificantDigits || - (fMinSignificantDigits == other->fMinSignificantDigits && fMaxSignificantDigits == other->fMaxSignificantDigits)) && - - fFormatWidth == other->fFormatWidth && - fPad == other->fPad && - fPadPosition == other->fPadPosition && - - (fStyle != UNUM_CURRENCY_PLURAL || - (fStyle == other->fStyle && fFormatPattern == other->fFormatPattern)) && - - fCurrencySignCount == other->fCurrencySignCount && - - ((fCurrencyPluralInfo == other->fCurrencyPluralInfo && - fCurrencyPluralInfo == NULL) || - (fCurrencyPluralInfo != NULL && other->fCurrencyPluralInfo != NULL && - *fCurrencyPluralInfo == *(other->fCurrencyPluralInfo))) - - // depending on other settings we may also need to compare - // fCurrencyChoice (mostly deprecated?), - // fAffixesForCurrency & fPluralAffixesForCurrency (only relevant in some cases) - ); + FormattedNumber output = fields->formatter->formatDouble(number, status); + fieldPositionIteratorHelper(output, posIter, appendTo.length(), status); + auto appendable = UnicodeStringAppendable(appendTo); + output.appendTo(appendable, status); + return appendTo; } -//------------------------------------------------------------------------------ +UnicodeString& DecimalFormat::format(int32_t number, UnicodeString& appendTo, FieldPosition& pos) const { + return format(static_cast (number), appendTo, pos); +} -Format* -DecimalFormat::clone() const -{ - return new DecimalFormat(*this); +UnicodeString& DecimalFormat::format(int32_t number, UnicodeString& appendTo, FieldPosition& pos, + UErrorCode& status) const { + return format(static_cast (number), appendTo, pos, status); } +UnicodeString& +DecimalFormat::format(int32_t number, UnicodeString& appendTo, FieldPositionIterator* posIter, + UErrorCode& status) const { + return format(static_cast (number), appendTo, posIter, status); +} -FixedDecimal -DecimalFormat::getFixedDecimal(double number, UErrorCode &status) const { - FixedDecimal result; +UnicodeString& DecimalFormat::format(int64_t number, UnicodeString& appendTo, FieldPosition& pos) const { + if (fields == nullptr) { + appendTo.setToBogus(); + return appendTo; + } + if (pos.getField() == FieldPosition::DONT_CARE && fastFormatInt64(number, appendTo)) { + return appendTo; + } + UErrorCode localStatus = U_ZERO_ERROR; + FormattedNumber output = fields->formatter->formatInt(number, localStatus); + fieldPositionHelper(output, pos, appendTo.length(), localStatus); + auto appendable = UnicodeStringAppendable(appendTo); + output.appendTo(appendable, localStatus); + return appendTo; +} +UnicodeString& DecimalFormat::format(int64_t number, UnicodeString& appendTo, FieldPosition& pos, + UErrorCode& status) const { if (U_FAILURE(status)) { - return result; + return appendTo; // don't overwrite status if it's already a failure. } - - if (uprv_isNaN(number) || uprv_isPositiveInfinity(fabs(number))) { - // For NaN and Infinity the state of the formatter is ignored. - result.init(number); - return result; + if (fields == nullptr) { + // We only get here if an OOM error happend during construction, copy construction, assignment, or modification. + status = U_MEMORY_ALLOCATION_ERROR; + appendTo.setToBogus(); + return appendTo; } - - if (fMultiplier == NULL && fScale == 0 && fRoundingIncrement == 0 && areSignificantDigitsUsed() == FALSE && - result.quickInit(number) && result.visibleDecimalDigitCount <= getMaximumFractionDigits()) { - // Fast Path. Construction of an exact FixedDecimal directly from the double, without passing - // through a DigitList, was successful, and the formatter is doing nothing tricky with rounding. - // printf("getFixedDecimal(%g): taking fast path.\n", number); - result.adjustForMinFractionDigits(getMinimumFractionDigits()); - } else { - // Slow path. Create a DigitList, and have this formatter round it according to the - // requirements of the format, and fill the fixedDecimal from that. - DigitList digits; - digits.set(number); - result = getFixedDecimal(digits, status); + if (pos.getField() == FieldPosition::DONT_CARE && fastFormatInt64(number, appendTo)) { + return appendTo; } - return result; + FormattedNumber output = fields->formatter->formatInt(number, status); + fieldPositionHelper(output, pos, appendTo.length(), status); + auto appendable = UnicodeStringAppendable(appendTo); + output.appendTo(appendable, status); + return appendTo; } -// MSVC optimizer bug? -// turn off optimization as it causes different behavior in the int64->double->int64 conversion -#if defined (_MSC_VER) -#pragma optimize ( "", off ) -#endif -FixedDecimal -DecimalFormat::getFixedDecimal(const Formattable &number, UErrorCode &status) const { +UnicodeString& +DecimalFormat::format(int64_t number, UnicodeString& appendTo, FieldPositionIterator* posIter, + UErrorCode& status) const { if (U_FAILURE(status)) { - return FixedDecimal(); + return appendTo; // don't overwrite status if it's already a failure. } - if (!number.isNumeric()) { - status = U_ILLEGAL_ARGUMENT_ERROR; - return FixedDecimal(); + if (fields == nullptr) { + // We only get here if an OOM error happend during construction, copy construction, assignment, or modification. + status = U_MEMORY_ALLOCATION_ERROR; + appendTo.setToBogus(); + return appendTo; } - - DigitList *dl = number.getDigitList(); - if (dl != NULL) { - DigitList clonedDL(*dl); - return getFixedDecimal(clonedDL, status); + if (posIter == nullptr && fastFormatInt64(number, appendTo)) { + return appendTo; } + FormattedNumber output = fields->formatter->formatInt(number, status); + fieldPositionIteratorHelper(output, posIter, appendTo.length(), status); + auto appendable = UnicodeStringAppendable(appendTo); + output.appendTo(appendable, status); + return appendTo; +} - Formattable::Type type = number.getType(); - if (type == Formattable::kDouble || type == Formattable::kLong) { - return getFixedDecimal(number.getDouble(status), status); +UnicodeString& +DecimalFormat::format(StringPiece number, UnicodeString& appendTo, FieldPositionIterator* posIter, + UErrorCode& status) const { + if (U_FAILURE(status)) { + return appendTo; // don't overwrite status if it's already a failure. } - - if (type == Formattable::kInt64) { - // "volatile" here is a workaround to avoid optimization issues. - volatile double fdv = number.getDouble(status); - // Note: conversion of int64_t -> double rounds with some compilers to - // values beyond what can be represented as a 64 bit int. Subsequent - // testing or conversion with int64_t produces bad results. - // So filter the problematic values, route them to DigitList. - if (fdv != (double)U_INT64_MAX && fdv != (double)U_INT64_MIN && - number.getInt64() == (int64_t)fdv) { - return getFixedDecimal(number.getDouble(status), status); - } + if (fields == nullptr) { + // We only get here if an OOM error happend during construction, copy construction, assignment, or modification. + status = U_MEMORY_ALLOCATION_ERROR; + appendTo.setToBogus(); + return appendTo; } - - // The only case left is type==int64_t, with a value with more digits than a double can represent. - // Any formattable originating as a big decimal will have had a pre-existing digit list. - // Any originating as a double or int32 will have been handled as a double. - - U_ASSERT(type == Formattable::kInt64); - DigitList digits; - digits.set(number.getInt64()); - return getFixedDecimal(digits, status); + FormattedNumber output = fields->formatter->formatDecimal(number, status); + fieldPositionIteratorHelper(output, posIter, appendTo.length(), status); + auto appendable = UnicodeStringAppendable(appendTo); + output.appendTo(appendable, status); + return appendTo; } -// end workaround MSVC optimizer bug -#if defined (_MSC_VER) -#pragma optimize ( "", on ) -#endif - -// Create a fixed decimal from a DigitList. -// The digit list may be modified. -// Internal function only. -FixedDecimal -DecimalFormat::getFixedDecimal(DigitList &number, UErrorCode &status) const { - // Round the number according to the requirements of this Format. - FixedDecimal result; - _round(number, number, result.isNegative, status); - - // The int64_t fields in FixedDecimal can easily overflow. - // In deciding what to discard in this event, consider that fixedDecimal - // is being used only with PluralRules, and those rules mostly look at least significant - // few digits of the integer part, and whether the fraction part is zero or not. - // - // So, in case of overflow when filling in the fields of the FixedDecimal object, - // for the integer part, discard the most significant digits. - // for the fraction part, discard the least significant digits, - // don't truncate the fraction value to zero. - // For simplicity, the int64_t fields are limited to 18 decimal digits, even - // though they could hold most (but not all) 19 digit values. - - // Integer Digits. - int32_t di = number.getDecimalAt()-18; // Take at most 18 digits. - if (di < 0) { - di = 0; - } - result.intValue = 0; - for (; di 0) { - // The number is something like 100000000000000000000000. - // More than 18 digits integer digits, but the least significant 18 are all zero. - // We don't want to return zero as the int part, but want to keep zeros - // for several of the least significant digits. - result.intValue = 100000000000000000LL; +UnicodeString& DecimalFormat::format(const DecimalQuantity& number, UnicodeString& appendTo, + FieldPositionIterator* posIter, UErrorCode& status) const { + if (U_FAILURE(status)) { + return appendTo; // don't overwrite status if it's already a failure. } - - // Fraction digits. - result.decimalDigits = result.decimalDigitsWithoutTrailingZeros = result.visibleDecimalDigitCount = 0; - for (di = number.getDecimalAt(); di < number.getCount(); di++) { - result.visibleDecimalDigitCount++; - if (result.decimalDigits < 100000000000000000LL) { - // 9223372036854775807 Largest 64 bit signed integer - int32_t digitVal = number.getDigit(di) & 0x0f; // getDigit() returns a char, '0'-'9'. - result.decimalDigits = result.decimalDigits * 10 + digitVal; - if (digitVal > 0) { - result.decimalDigitsWithoutTrailingZeros = result.decimalDigits; - } - } - } - - result.hasIntegerValue = (result.decimalDigits == 0); - - // Trailing fraction zeros. The format specification may require more trailing - // zeros than the numeric value. Add any such on now. - - int32_t minFractionDigits; - if (areSignificantDigitsUsed()) { - minFractionDigits = getMinimumSignificantDigits() - number.getDecimalAt(); - if (minFractionDigits < 0) { - minFractionDigits = 0; - } - } else { - minFractionDigits = getMinimumFractionDigits(); - } - result.adjustForMinFractionDigits(minFractionDigits); - - return result; -} - - -//------------------------------------------------------------------------------ - -UnicodeString& -DecimalFormat::format(int32_t number, - UnicodeString& appendTo, - FieldPosition& fieldPosition) const -{ - return format((int64_t)number, appendTo, fieldPosition); -} - -UnicodeString& -DecimalFormat::format(int32_t number, - UnicodeString& appendTo, - FieldPosition& fieldPosition, - UErrorCode& status) const -{ - return format((int64_t)number, appendTo, fieldPosition, status); -} - -UnicodeString& -DecimalFormat::format(int32_t number, - UnicodeString& appendTo, - FieldPositionIterator* posIter, - UErrorCode& status) const -{ - return format((int64_t)number, appendTo, posIter, status); -} - - -#if UCONFIG_FORMAT_FASTPATHS_49 -void DecimalFormat::handleChanged() { - DecimalFormatInternal &data = internalData(fReserved); - - if(data.fFastFormatStatus == kFastpathUNKNOWN || data.fFastParseStatus == kFastpathUNKNOWN) { - return; // still constructing. Wait. - } - - data.fFastParseStatus = data.fFastFormatStatus = kFastpathNO; - -#if UCONFIG_HAVE_PARSEALLINPUT - if(fParseAllInput == UNUM_NO) { - debug("No Parse fastpath: fParseAllInput==UNUM_NO"); - } else -#endif - if (fFormatWidth!=0) { - debug("No Parse fastpath: fFormatWidth"); - } else if(fPositivePrefix.length()>0) { - debug("No Parse fastpath: positive prefix"); - } else if(fPositiveSuffix.length()>0) { - debug("No Parse fastpath: positive suffix"); - } else if(fNegativePrefix.length()>1 - || ((fNegativePrefix.length()==1) && (fNegativePrefix.charAt(0)!=0x002D))) { - debug("No Parse fastpath: negative prefix that isn't '-'"); - } else if(fNegativeSuffix.length()>0) { - debug("No Parse fastpath: negative suffix"); - } else { - data.fFastParseStatus = kFastpathYES; - debug("parse fastpath: YES"); - } - - if (fGroupingSize!=0 && isGroupingUsed()) { - debug("No format fastpath: fGroupingSize!=0 and grouping is used"); -#ifdef FMT_DEBUG - printf("groupingsize=%d\n", fGroupingSize); -#endif - } else if(fGroupingSize2!=0 && isGroupingUsed()) { - debug("No format fastpath: fGroupingSize2!=0"); - } else if(fUseExponentialNotation) { - debug("No format fastpath: fUseExponentialNotation"); - } else if(fFormatWidth!=0) { - debug("No format fastpath: fFormatWidth!=0"); - } else if(fMinSignificantDigits!=1) { - debug("No format fastpath: fMinSignificantDigits!=1"); - } else if(fMultiplier!=NULL) { - debug("No format fastpath: fMultiplier!=NULL"); - } else if(fScale!=0) { - debug("No format fastpath: fScale!=0"); - } else if(0x0030 != getConstSymbol(DecimalFormatSymbols::kZeroDigitSymbol).char32At(0)) { - debug("No format fastpath: 0x0030 != getConstSymbol(DecimalFormatSymbols::kZeroDigitSymbol).char32At(0)"); - } else if(fDecimalSeparatorAlwaysShown) { - debug("No format fastpath: fDecimalSeparatorAlwaysShown"); - } else if(getMinimumFractionDigits()>0) { - debug("No format fastpath: fMinFractionDigits>0"); - } else if(fCurrencySignCount != fgCurrencySignCountZero) { - debug("No format fastpath: fCurrencySignCount != fgCurrencySignCountZero"); - } else if(fRoundingIncrement!=0) { - debug("No format fastpath: fRoundingIncrement!=0"); - } else { - data.fFastFormatStatus = kFastpathYES; - debug("format:kFastpathYES!"); - } - - -} -#endif -//------------------------------------------------------------------------------ - -UnicodeString& -DecimalFormat::format(int64_t number, - UnicodeString& appendTo, - FieldPosition& fieldPosition) const -{ - UErrorCode status = U_ZERO_ERROR; /* ignored */ - FieldPositionOnlyHandler handler(fieldPosition); - return _format(number, appendTo, handler, status); -} - -UnicodeString& -DecimalFormat::format(int64_t number, - UnicodeString& appendTo, - FieldPosition& fieldPosition, - UErrorCode& status) const -{ - FieldPositionOnlyHandler handler(fieldPosition); - return _format(number, appendTo, handler, status); -} - -UnicodeString& -DecimalFormat::format(int64_t number, - UnicodeString& appendTo, - FieldPositionIterator* posIter, - UErrorCode& status) const -{ - FieldPositionIteratorHandler handler(posIter, status); - return _format(number, appendTo, handler, status); -} - -UnicodeString& -DecimalFormat::_format(int64_t number, - UnicodeString& appendTo, - FieldPositionHandler& handler, - UErrorCode &status) const -{ - // Bottleneck function for formatting int64_t - if (U_FAILURE(status)) { - return appendTo; - } - -#if UCONFIG_FORMAT_FASTPATHS_49 - // const UnicodeString *posPrefix = fPosPrefixPattern; - // const UnicodeString *posSuffix = fPosSuffixPattern; - // const UnicodeString *negSuffix = fNegSuffixPattern; - - const DecimalFormatInternal &data = internalData(fReserved); - -#ifdef FMT_DEBUG - data.dump(); - printf("fastpath? [%d]\n", number); -#endif - - if( data.fFastFormatStatus==kFastpathYES) { - -#define kZero 0x0030 - const int32_t MAX_IDX = MAX_DIGITS+2; - UChar outputStr[MAX_IDX]; - int32_t destIdx = MAX_IDX; - outputStr[--destIdx] = 0; // term - - int64_t n = number; - if (number < 1) { - // Negative numbers are slightly larger than positive - // output the first digit (or the leading zero) - outputStr[--destIdx] = (-(n % 10) + kZero); - n /= -10; - } - // get any remaining digits - while (n > 0) { - outputStr[--destIdx] = (n % 10) + kZero; - n /= 10; - } - - - // Slide the number to the start of the output str - U_ASSERT(destIdx >= 0); - int32_t length = MAX_IDX - destIdx -1; - /*int32_t prefixLen = */ appendAffix(appendTo, number, handler, number<0, TRUE); - int32_t maxIntDig = getMaximumIntegerDigits(); - int32_t destlength = length<=maxIntDig?length:maxIntDig; // dest length pinned to max int digits - - if(length>maxIntDig && fBoolFlags.contains(UNUM_FORMAT_FAIL_IF_MORE_THAN_MAX_DIGITS)) { - status = U_ILLEGAL_ARGUMENT_ERROR; - } - - int32_t prependZero = getMinimumIntegerDigits() - destlength; - -#ifdef FMT_DEBUG - printf("prependZero=%d, length=%d, minintdig=%d maxintdig=%d destlength=%d skip=%d\n", prependZero, length, getMinimumIntegerDigits(), maxIntDig, destlength, length-destlength); -#endif - int32_t intBegin = appendTo.length(); - - while((prependZero--)>0) { - appendTo.append((UChar)0x0030); // '0' - } - - appendTo.append(outputStr+destIdx+ - (length-destlength), // skip any leading digits - destlength); - handler.addAttribute(kIntegerField, intBegin, appendTo.length()); - - /*int32_t suffixLen =*/ appendAffix(appendTo, number, handler, number<0, FALSE); - - //outputStr[length]=0; - -#ifdef FMT_DEBUG - printf("Writing [%s] length [%d] max %d for [%d]\n", outputStr+destIdx, length, MAX_IDX, number); -#endif - -#undef kZero - - return appendTo; - } // end fastpath -#endif - - // Else the slow way - via DigitList - DigitList digits; - digits.set(number); - return _format(digits, appendTo, handler, status); -} - -//------------------------------------------------------------------------------ - -UnicodeString& -DecimalFormat::format( double number, - UnicodeString& appendTo, - FieldPosition& fieldPosition) const -{ - UErrorCode status = U_ZERO_ERROR; /* ignored */ - FieldPositionOnlyHandler handler(fieldPosition); - return _format(number, appendTo, handler, status); -} - -UnicodeString& -DecimalFormat::format( double number, - UnicodeString& appendTo, - FieldPosition& fieldPosition, - UErrorCode& status) const -{ - FieldPositionOnlyHandler handler(fieldPosition); - return _format(number, appendTo, handler, status); -} - -UnicodeString& -DecimalFormat::format( double number, - UnicodeString& appendTo, - FieldPositionIterator* posIter, - UErrorCode& status) const -{ - FieldPositionIteratorHandler handler(posIter, status); - return _format(number, appendTo, handler, status); -} - -UnicodeString& -DecimalFormat::_format( double number, - UnicodeString& appendTo, - FieldPositionHandler& handler, - UErrorCode &status) const -{ - if (U_FAILURE(status)) { - return appendTo; - } - // Special case for NaN, sets the begin and end index to be the - // the string length of localized name of NaN. - // TODO: let NaNs go through DigitList. - if (uprv_isNaN(number)) - { - int begin = appendTo.length(); - appendTo += getConstSymbol(DecimalFormatSymbols::kNaNSymbol); - - handler.addAttribute(kIntegerField, begin, appendTo.length()); - - addPadding(appendTo, handler, 0, 0); - return appendTo; - } - - DigitList digits; - digits.set(number); - _format(digits, appendTo, handler, status); - // No way to return status from here. - return appendTo; -} - -//------------------------------------------------------------------------------ - - -UnicodeString& -DecimalFormat::format(const StringPiece &number, - UnicodeString &toAppendTo, - FieldPositionIterator *posIter, - UErrorCode &status) const -{ -#if UCONFIG_FORMAT_FASTPATHS_49 - // don't bother if the int64 path is not optimized - int32_t len = number.length(); - - if(len>0&&len<10) { /* 10 or more digits may not be an int64 */ - const char *data = number.data(); - int64_t num = 0; - UBool neg = FALSE; - UBool ok = TRUE; - - int32_t start = 0; - - if(data[start]=='+') { - start++; - } else if(data[start]=='-') { - neg=TRUE; - start++; - } - - int32_t place = 1; /* 1, 10, ... */ - for(int32_t i=len-1;i>=start;i--) { - if(data[i]>='0'&&data[i]<='9') { - num+=place*(int64_t)(data[i]-'0'); - } else { - ok=FALSE; - break; - } - place *= 10; - } - - if(ok) { - if(neg) { - num = -num;// add minus bit - } - // format as int64_t - return format(num, toAppendTo, posIter, status); - } - // else fall through - } -#endif - - DigitList dnum; - dnum.set(number, status); - if (U_FAILURE(status)) { - return toAppendTo; - } - FieldPositionIteratorHandler handler(posIter, status); - _format(dnum, toAppendTo, handler, status); - return toAppendTo; -} - - -UnicodeString& -DecimalFormat::format(const DigitList &number, - UnicodeString &appendTo, - FieldPositionIterator *posIter, - UErrorCode &status) const { - FieldPositionIteratorHandler handler(posIter, status); - _format(number, appendTo, handler, status); - return appendTo; -} - - - -UnicodeString& -DecimalFormat::format(const DigitList &number, - UnicodeString& appendTo, - FieldPosition& pos, - UErrorCode &status) const { - FieldPositionOnlyHandler handler(pos); - _format(number, appendTo, handler, status); - return appendTo; -} - -DigitList& -DecimalFormat::_round(const DigitList &number, DigitList &adjustedNum, UBool& isNegative, UErrorCode &status) const { - if (U_FAILURE(status)) { - return adjustedNum; - } - - // note: number and adjustedNum may refer to the same DigitList, in cases where a copy - // is not needed by the caller. - - adjustedNum = number; - isNegative = false; - if (number.isNaN()) { - return adjustedNum; - } - - // Do this BEFORE checking to see if value is infinite or negative! Sets the - // begin and end index to be length of the string composed of - // localized name of Infinite and the positive/negative localized - // signs. - - adjustedNum.setRoundingMode(fRoundingMode); - if (fMultiplier != NULL) { - adjustedNum.mult(*fMultiplier, status); - if (U_FAILURE(status)) { - return adjustedNum; - } - } - - if (fScale != 0) { - DigitList ten; - ten.set((int32_t)10); - if (fScale > 0) { - for (int32_t i = fScale ; i > 0 ; i--) { - adjustedNum.mult(ten, status); - if (U_FAILURE(status)) { - return adjustedNum; - } - } - } else { - for (int32_t i = fScale ; i < 0 ; i++) { - adjustedNum.div(ten, status); - if (U_FAILURE(status)) { - return adjustedNum; - } - } - } - } - - /* - * Note: sign is important for zero as well as non-zero numbers. - * Proper detection of -0.0 is needed to deal with the - * issues raised by bugs 4106658, 4106667, and 4147706. Liu 7/6/98. - */ - isNegative = !adjustedNum.isPositive(); - - // Apply rounding after multiplier - - adjustedNum.fContext.status &= ~DEC_Inexact; - if (fRoundingIncrement != NULL) { - adjustedNum.div(*fRoundingIncrement, status); - adjustedNum.toIntegralValue(); - adjustedNum.mult(*fRoundingIncrement, status); - adjustedNum.trim(); - if (U_FAILURE(status)) { - return adjustedNum; - } - } - if (fRoundingMode == kRoundUnnecessary && (adjustedNum.fContext.status & DEC_Inexact)) { - status = U_FORMAT_INEXACT_ERROR; - return adjustedNum; - } - - if (adjustedNum.isInfinite()) { - return adjustedNum; - } - - if (fUseExponentialNotation || areSignificantDigitsUsed()) { - int32_t sigDigits = precision(); - if (sigDigits > 0) { - adjustedNum.round(sigDigits); - // Travis Keep (21/2/2014): Calling round on a digitList does not necessarily - // preserve the sign of that digit list. Preserving the sign is especially - // important when formatting -0.0 for instance. Not preserving the sign seems - // like a bug because I cannot think of any case where the sign would actually - // have to change when rounding. For now, we preserve the sign by setting the - // positive attribute directly. - adjustedNum.setPositive(!isNegative); - } - } else { - // Fixed point format. Round to a set number of fraction digits. - int32_t numFractionDigits = precision(); - adjustedNum.roundFixedPoint(numFractionDigits); - } - if (fRoundingMode == kRoundUnnecessary && (adjustedNum.fContext.status & DEC_Inexact)) { - status = U_FORMAT_INEXACT_ERROR; - return adjustedNum; - } - return adjustedNum; -} - -UnicodeString& -DecimalFormat::_format(const DigitList &number, - UnicodeString& appendTo, - FieldPositionHandler& handler, - UErrorCode &status) const -{ - if (U_FAILURE(status)) { - return appendTo; - } - - // Special case for NaN, sets the begin and end index to be the - // the string length of localized name of NaN. - if (number.isNaN()) - { - int begin = appendTo.length(); - appendTo += getConstSymbol(DecimalFormatSymbols::kNaNSymbol); - - handler.addAttribute(kIntegerField, begin, appendTo.length()); - - addPadding(appendTo, handler, 0, 0); - return appendTo; - } - - DigitList adjustedNum; - UBool isNegative; - _round(number, adjustedNum, isNegative, status); - if (U_FAILURE(status)) { - return appendTo; - } - - // Special case for INFINITE, - if (adjustedNum.isInfinite()) { - int32_t prefixLen = appendAffix(appendTo, adjustedNum.getDouble(), handler, isNegative, TRUE); - - int begin = appendTo.length(); - appendTo += getConstSymbol(DecimalFormatSymbols::kInfinitySymbol); - - handler.addAttribute(kIntegerField, begin, appendTo.length()); - - int32_t suffixLen = appendAffix(appendTo, adjustedNum.getDouble(), handler, isNegative, FALSE); - - addPadding(appendTo, handler, prefixLen, suffixLen); - return appendTo; - } - return subformat(appendTo, handler, adjustedNum, FALSE, status); -} - -/** - * Return true if a grouping separator belongs at the given - * position, based on whether grouping is in use and the values of - * the primary and secondary grouping interval. - * @param pos the number of integer digits to the right of - * the current position. Zero indicates the position after the - * rightmost integer digit. - * @return true if a grouping character belongs at the current - * position. - */ -UBool DecimalFormat::isGroupingPosition(int32_t pos) const { - UBool result = FALSE; - if (isGroupingUsed() && (pos > 0) && (fGroupingSize > 0)) { - if ((fGroupingSize2 > 0) && (pos > fGroupingSize)) { - result = ((pos - fGroupingSize) % fGroupingSize2) == 0; - } else { - result = pos % fGroupingSize == 0; - } - } - return result; -} - -//------------------------------------------------------------------------------ - -/** - * Complete the formatting of a finite number. On entry, the DigitList must - * be filled in with the correct digits. - */ -UnicodeString& -DecimalFormat::subformat(UnicodeString& appendTo, - FieldPositionHandler& handler, - DigitList& digits, - UBool isInteger, - UErrorCode& status) const -{ - // char zero = '0'; - // DigitList returns digits as '0' thru '9', so we will need to - // always need to subtract the character 0 to get the numeric value to use for indexing. - - UChar32 localizedDigits[10]; - localizedDigits[0] = getConstSymbol(DecimalFormatSymbols::kZeroDigitSymbol).char32At(0); - localizedDigits[1] = getConstSymbol(DecimalFormatSymbols::kOneDigitSymbol).char32At(0); - localizedDigits[2] = getConstSymbol(DecimalFormatSymbols::kTwoDigitSymbol).char32At(0); - localizedDigits[3] = getConstSymbol(DecimalFormatSymbols::kThreeDigitSymbol).char32At(0); - localizedDigits[4] = getConstSymbol(DecimalFormatSymbols::kFourDigitSymbol).char32At(0); - localizedDigits[5] = getConstSymbol(DecimalFormatSymbols::kFiveDigitSymbol).char32At(0); - localizedDigits[6] = getConstSymbol(DecimalFormatSymbols::kSixDigitSymbol).char32At(0); - localizedDigits[7] = getConstSymbol(DecimalFormatSymbols::kSevenDigitSymbol).char32At(0); - localizedDigits[8] = getConstSymbol(DecimalFormatSymbols::kEightDigitSymbol).char32At(0); - localizedDigits[9] = getConstSymbol(DecimalFormatSymbols::kNineDigitSymbol).char32At(0); - - const UnicodeString *grouping ; - if(fCurrencySignCount == fgCurrencySignCountZero) { - grouping = &getConstSymbol(DecimalFormatSymbols::kGroupingSeparatorSymbol); - }else{ - grouping = &getConstSymbol(DecimalFormatSymbols::kMonetaryGroupingSeparatorSymbol); - } - const UnicodeString *decimal; - if(fCurrencySignCount == fgCurrencySignCountZero) { - decimal = &getConstSymbol(DecimalFormatSymbols::kDecimalSeparatorSymbol); - } else { - decimal = &getConstSymbol(DecimalFormatSymbols::kMonetarySeparatorSymbol); - } - UBool useSigDig = areSignificantDigitsUsed(); - int32_t maxIntDig = getMaximumIntegerDigits(); - int32_t minIntDig = getMinimumIntegerDigits(); - - // Appends the prefix. - double doubleValue = digits.getDouble(); - int32_t prefixLen = appendAffix(appendTo, doubleValue, handler, !digits.isPositive(), TRUE); - - if (fUseExponentialNotation) - { - int currentLength = appendTo.length(); - int intBegin = currentLength; - int intEnd = -1; - int fracBegin = -1; - - int32_t minFracDig = 0; - if (useSigDig) { - maxIntDig = minIntDig = 1; - minFracDig = getMinimumSignificantDigits() - 1; - } else { - minFracDig = getMinimumFractionDigits(); - if (maxIntDig > kMaxScientificIntegerDigits) { - maxIntDig = 1; - if (maxIntDig < minIntDig) { - maxIntDig = minIntDig; - } - } - if (maxIntDig > minIntDig) { - minIntDig = 1; - } - } - - // Minimum integer digits are handled in exponential format by - // adjusting the exponent. For example, 0.01234 with 3 minimum - // integer digits is "123.4E-4". - - // Maximum integer digits are interpreted as indicating the - // repeating range. This is useful for engineering notation, in - // which the exponent is restricted to a multiple of 3. For - // example, 0.01234 with 3 maximum integer digits is "12.34e-3". - // If maximum integer digits are defined and are larger than - // minimum integer digits, then minimum integer digits are - // ignored. - digits.reduce(); // Removes trailing zero digits. - int32_t exponent = digits.getDecimalAt(); - if (maxIntDig > 1 && maxIntDig != minIntDig) { - // A exponent increment is defined; adjust to it. - exponent = (exponent > 0) ? (exponent - 1) / maxIntDig - : (exponent / maxIntDig) - 1; - exponent *= maxIntDig; - } else { - // No exponent increment is defined; use minimum integer digits. - // If none is specified, as in "#E0", generate 1 integer digit. - exponent -= (minIntDig > 0 || minFracDig > 0) - ? minIntDig : 1; - } - - // We now output a minimum number of digits, and more if there - // are more digits, up to the maximum number of digits. We - // place the decimal point after the "integer" digits, which - // are the first (decimalAt - exponent) digits. - int32_t minimumDigits = minIntDig + minFracDig; - // The number of integer digits is handled specially if the number - // is zero, since then there may be no digits. - int32_t integerDigits = digits.isZero() ? minIntDig : - digits.getDecimalAt() - exponent; - int32_t totalDigits = digits.getCount(); - if (minimumDigits > totalDigits) - totalDigits = minimumDigits; - if (integerDigits > totalDigits) - totalDigits = integerDigits; - - // totalDigits records total number of digits needs to be processed - int32_t i; - for (i=0; i 0) { - handler.addAttribute(kFractionField, fracBegin, currentLength); - } - - // The exponent is output using the pattern-specified minimum - // exponent digits. There is no maximum limit to the exponent - // digits, since truncating the exponent would appendTo in an - // unacceptable inaccuracy. - appendTo += getConstSymbol(DecimalFormatSymbols::kExponentialSymbol); - - handler.addAttribute(kExponentSymbolField, currentLength, appendTo.length()); - currentLength = appendTo.length(); - - // For zero values, we force the exponent to zero. We - // must do this here, and not earlier, because the value - // is used to determine integer digit count above. - if (digits.isZero()) - exponent = 0; - - if (exponent < 0) { - appendTo += getConstSymbol(DecimalFormatSymbols::kMinusSignSymbol); - handler.addAttribute(kExponentSignField, currentLength, appendTo.length()); - } else if (fExponentSignAlwaysShown) { - appendTo += getConstSymbol(DecimalFormatSymbols::kPlusSignSymbol); - handler.addAttribute(kExponentSignField, currentLength, appendTo.length()); - } - - currentLength = appendTo.length(); - - DigitList expDigits; - expDigits.set(exponent); - { - int expDig = fMinExponentDigits; - if (fUseExponentialNotation && expDig < 1) { - expDig = 1; - } - for (i=expDigits.getDecimalAt(); i 0 && count < digits.getDecimalAt()) { - count = digits.getDecimalAt(); - } - - // Handle the case where getMaximumIntegerDigits() is smaller - // than the real number of integer digits. If this is so, we - // output the least significant max integer digits. For example, - // the value 1997 printed with 2 max integer digits is just "97". - - int32_t digitIndex = 0; // Index into digitList.fDigits[] - if (count > maxIntDig && maxIntDig >= 0) { - count = maxIntDig; - digitIndex = digits.getDecimalAt() - count; - if(fBoolFlags.contains(UNUM_FORMAT_FAIL_IF_MORE_THAN_MAX_DIGITS)) { - status = U_ILLEGAL_ARGUMENT_ERROR; - } - } - - int32_t sizeBeforeIntegerPart = appendTo.length(); - - int32_t i; - for (i=count-1; i>=0; --i) - { - if (i < digits.getDecimalAt() && digitIndex < digits.getCount() && - sigCount < maxSigDig) { - // Output a real digit - appendTo += (UChar32)localizedDigits[digits.getDigitValue(digitIndex++)]; - ++sigCount; - } - else - { - // Output a zero (leading or trailing) - appendTo += localizedDigits[0]; - if (sigCount > 0) { - ++sigCount; - } - } - - // Output grouping separator if necessary. - if (isGroupingPosition(i)) { - currentLength = appendTo.length(); - appendTo.append(*grouping); - handler.addAttribute(kGroupingSeparatorField, currentLength, appendTo.length()); - } - } - - // This handles the special case of formatting 0. For zero only, we count the - // zero to the left of the decimal point as one signficant digit. Ordinarily we - // do not count any leading 0's as significant. If the number we are formatting - // is not zero, then either sigCount or digits.getCount() will be non-zero. - if (sigCount == 0 && digits.getCount() == 0) { - sigCount = 1; - } - - // TODO(dlf): this looks like it was a bug, we marked the int field as ending - // before the zero was generated. - // Record field information for caller. - // if (fieldPosition.getField() == NumberFormat::kIntegerField) - // fieldPosition.setEndIndex(appendTo.length()); - - // Determine whether or not there are any printable fractional - // digits. If we've used up the digits we know there aren't. - UBool fractionPresent = (!isInteger && digitIndex < digits.getCount()) || - (useSigDig ? (sigCount < minSigDig) : (getMinimumFractionDigits() > 0)); - - // If there is no fraction present, and we haven't printed any - // integer digits, then print a zero. Otherwise we won't print - // _any_ digits, and we won't be able to parse this string. - if (!fractionPresent && appendTo.length() == sizeBeforeIntegerPart) - appendTo += localizedDigits[0]; - - currentLength = appendTo.length(); - handler.addAttribute(kIntegerField, intBegin, currentLength); - - // Output the decimal separator if we always do so. - if (fDecimalSeparatorAlwaysShown || fractionPresent) { - appendTo += *decimal; - handler.addAttribute(kDecimalSeparatorField, currentLength, appendTo.length()); - currentLength = appendTo.length(); - } - - int fracBegin = currentLength; - - count = useSigDig ? INT32_MAX : getMaximumFractionDigits(); - if (useSigDig && (sigCount == maxSigDig || - (sigCount >= minSigDig && digitIndex == digits.getCount()))) { - count = 0; - } - - for (i=0; i < count; ++i) { - // Here is where we escape from the loop. We escape - // if we've output the maximum fraction digits - // (specified in the for expression above). We also - // stop when we've output the minimum digits and - // either: we have an integer, so there is no - // fractional stuff to display, or we're out of - // significant digits. - if (!useSigDig && i >= getMinimumFractionDigits() && - (isInteger || digitIndex >= digits.getCount())) { - break; - } - - // Output leading fractional zeros. These are zeros - // that come after the decimal but before any - // significant digits. These are only output if - // abs(number being formatted) < 1.0. - if (-1-i > (digits.getDecimalAt()-1)) { - appendTo += localizedDigits[0]; - continue; - } - - // Output a digit, if we have any precision left, or a - // zero if we don't. We don't want to output noise digits. - if (!isInteger && digitIndex < digits.getCount()) { - appendTo += (UChar32)localizedDigits[digits.getDigitValue(digitIndex++)]; - } else { - appendTo += localizedDigits[0]; - } - - // If we reach the maximum number of significant - // digits, or if we output all the real digits and - // reach the minimum, then we are done. - ++sigCount; - if (useSigDig && - (sigCount == maxSigDig || - (digitIndex == digits.getCount() && sigCount >= minSigDig))) { - break; - } - } - - handler.addAttribute(kFractionField, fracBegin, appendTo.length()); - } - - int32_t suffixLen = appendAffix(appendTo, doubleValue, handler, !digits.isPositive(), FALSE); - - addPadding(appendTo, handler, prefixLen, suffixLen); - return appendTo; -} - -/** - * Inserts the character fPad as needed to expand result to fFormatWidth. - * @param result the string to be padded - */ -void DecimalFormat::addPadding(UnicodeString& appendTo, - FieldPositionHandler& handler, - int32_t prefixLen, - int32_t suffixLen) const -{ - if (fFormatWidth > 0) { - int32_t len = fFormatWidth - appendTo.length(); - if (len > 0) { - UnicodeString padding; - for (int32_t i=0; i currAmt(new CurrencyAmount(parseResult, curbuf, 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: - - // Skip padding characters, if around prefix - if (fFormatWidth > 0 && (fPadPosition == kPadBeforePrefix || - fPadPosition == 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 = &getConstSymbol(DecimalFormatSymbols::kNaNSymbol); - int32_t nanLen = (text.compare(i, nan->length(), *nan) - ? 0 : nan->length()); - if (nanLen) { - i += nanLen; - if (fFormatWidth > 0 && (fPadPosition == kPadBeforeSuffix || - fPadPosition == 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 (fCurrencySignCount != fgCurrencySignCountZero) { - if (!parseForCurrency(text, parsePosition, *digits, - status, currency)) { - return; - } - } else { - if (!subparse(text, - fNegPrefixPattern, fNegSuffixPattern, - fPosPrefixPattern, fPosSuffixPattern, - 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 (fMultiplier != NULL) { - UErrorCode ec = U_ZERO_ERROR; - digits->div(*fMultiplier, ec); - } - - if (fScale != 0) { - DigitList ten; - ten.set((int32_t)10); - if (fScale > 0) { - for (int32_t i = fScale; i > 0; i--) { - UErrorCode ec = U_ZERO_ERROR; - digits->div(ten,ec); - } - } else { - for (int32_t i = 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 { - 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, - fNegPrefixPattern, fNegSuffixPattern, - fPosPrefixPattern, fPosSuffixPattern, - TRUE, UCURR_LONG_NAME, - tmpPos, tmpDigitList, tmpStatus, currency); - } else { - found = subparse(text, - fNegPrefixPattern, fNegSuffixPattern, - fPosPrefixPattern, fPosSuffixPattern, - 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 = -1; - 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, - &fNegativePrefix, &fNegativeSuffix, - &fPositivePrefix, &fPositiveSuffix, - 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 = getConstSymbol(DecimalFormatSymbols::kZeroDigitSymbol).char32At(0); - const UnicodeString *groupingString = &getConstSymbol(fCurrencySignCount == fgCurrencySignCountZero ? - 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); - printf("currencyParsing=%d, fFormatWidth=%d, isParseIntegerOnly=%c text.length=%d negPrefLen=%d\n", currencyParsing, fFormatWidth, (isParseIntegerOnly())?'Y':'N', text.length(), negPrefix!=NULL?negPrefix->length():-1); -#endif - - UBool fastParseOk = false; /* TRUE iff fast parse is OK */ - // UBool fastParseHadDecimal = FALSE; /* true if fast parse saw a decimal point. */ - const DecimalFormatInternal &data = internalData(fReserved); - if((data.fFastParseStatus==kFastpathYES) && - fCurrencySignCount == fgCurrencySignCountZero && - // (negPrefix!=NULL&&negPrefix->isEmpty()) || - 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 = &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 -#ifdef FMT_DEBUG - printf("PP -> %d, good = [%s] digitcount=%d, fGroupingSize=%d fGroupingSize2=%d!\n", j, parsedNum.data(), digitCount, fGroupingSize, fGroupingSize2); -#endif - 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("fFormatWidth=%d ", fFormatWidth); - printf("text.length()=%d ", text.length()); - printf("posPrefix=%p posSuffix=%p ", posPrefix, posSuffix); - - printf("\n"); -#endif - } - - if(!fastParseOk -#if UCONFIG_HAVE_PARSEALLINPUT - && fParseAllInput!=UNUM_YES -#endif - ) - { - // Match padding before prefix - if (fFormatWidth > 0 && fPadPosition == 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 (fFormatWidth > 0 && fPadPosition == kPadAfterPrefix) { - position = skipPadding(text, position); - } - - if (! strictParse) { - position = skipUWhiteSpace(text, position); - } - - // process digits or Inf, find decimal position - const UnicodeString *inf = &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; // where did we last see a grouping separator? - int32_t digitStart = position; - int32_t gs2 = fGroupingSize2 == 0 ? fGroupingSize : fGroupingSize2; - - const UnicodeString *decimalString; - if (fCurrencySignCount != fgCurrencySignCountZero) { - decimalString = &getConstSymbol(DecimalFormatSymbols::kMonetarySeparatorSymbol); - } else { - decimalString = &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; - // Already check above (digit = ch - zero) for ch==zero; the only check we need to do here is: - // if \u3007 is treated as 0 for parsing, \u96F6 should be too. Otherwise check for nonzero digits. - if ( zero!=0x3007 || ch!=0x96F6 ) { - for (digit = 1 ; digit < 10 ; digit++ ) { - if ( 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 && backup - lastGroup - 1 != gs2) || - (lastGroup == -1 && position - digitStart - 1 > gs2)) { - strictFail = TRUE; - break; - } - - lastGroup = backup; - } - - // Cancel out backup setting (see grouping handler below) - 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 && groupingSet!=NULL && u_isWhitespace(ch)) || backup != -1 ) { - // We differ from the ICU4J code by allowing a leading group sep in strict mode (for - // backward compatibility) as long as it is not one of the breaking whitespace characters - // that is only treated as a group separator because of the equivalence set. If we get - // here it is because the leading sep was such a breaking space, or there were multiple - // group separators in a row. Note that the DecimalFormat documentation says - // "During parsing, grouping separators are ignored" and that was for strict parsing, - // so we may need to further revisit this strictParse restriction to ensure compatibility. - // Also note: u_isWhitespace is true for all Zs/Zl/Zp except the no-break ones: 00A0,2007,202F. - // In CLDR, all locales that have space as a group separator use 00A0 (NBSP). - 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. - 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 && position - lastGroup != fGroupingSize + 1)) { - 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 = &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 = &getConstSymbol(DecimalFormatSymbols::kPlusSignSymbol); - if (!text.compare(pos, tmp->length(), *tmp)) - { - pos += tmp->length(); - } - else { - tmp = &getConstSymbol(DecimalFormatSymbols::kMinusSignSymbol); - if (!text.compare(pos, tmp->length(), *tmp)) - { - exponentSign = '-'; - pos += tmp->length(); - } - } - } - - UBool sawExponentDigit = FALSE; - while (pos < textLength) { - ch = text[(int32_t)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; - 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 (backup != -1) - { - position = backup; - } - - if (strictParse && !sawDecimal) { - if (lastGroup != -1 && position - lastGroup != fGroupingSize + 1) { - 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 (fFormatWidth > 0 && fPadPosition == 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 (fFormatWidth > 0 && fPadPosition == 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; - } - 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(fPad); - while (position < text.length() && - text.char32At(position) == fPad) { - 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 (fCurrencyChoice != NULL || currency != NULL || - (fCurrencySignCount != fgCurrencySignCountZero && complexCurrencyParsing)) { - - if (affixPat != NULL) { - return compareComplexAffix(*affixPat, text, pos, type, currency); - } - } - - if (isNegative) { - if (isPrefix) { - patternToCompare = &fNegativePrefix; - } - else { - patternToCompare = &fNegativeSuffix; - } - } - else { - if (isPrefix) { - patternToCompare = &fPositivePrefix; - } - else { - patternToCompare = &fPositiveSuffix; - } - } - 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; - } + if (fields == nullptr) { + // We only get here if an OOM error happend during construction, copy construction, assignment, or modification. + status = U_MEMORY_ALLOCATION_ERROR; + appendTo.setToBogus(); + return appendTo; } - 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); + FormattedNumber output = fields->formatter->formatDecimalQuantity(number, status); + fieldPositionIteratorHelper(output, posIter, appendTo.length(), status); + auto appendable = UnicodeStringAppendable(appendTo); + output.appendTo(appendable, status); + return appendTo; } -/** - * 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); +UnicodeString& +DecimalFormat::format(const DecimalQuantity& number, UnicodeString& appendTo, FieldPosition& pos, + UErrorCode& status) const { + if (U_FAILURE(status)) { + return appendTo; // don't overwrite status if it's already a failure. } - 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); + if (fields == nullptr) { + // We only get here if an OOM error happend during construction, copy construction, assignment, or modification. + status = U_MEMORY_ALLOCATION_ERROR; + appendTo.setToBogus(); + return appendTo; } - return pos; + FormattedNumber output = fields->formatter->formatDecimalQuantity(number, status); + fieldPositionHelper(output, pos, appendTo.length(), status); + auto appendable = UnicodeStringAppendable(appendTo); + output.appendTo(appendable, status); + return appendTo; } -/** - * 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++; +void DecimalFormat::parse(const UnicodeString& text, Formattable& output, + ParsePosition& parsePosition) const { + if (fields == nullptr) { + return; } - 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 || - (fCurrencyChoice != NULL && *getCurrency() != 0) || - fCurrencySignCount != fgCurrencySignCountZero); - - 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); - 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); - - // 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 = &getConstSymbol(DecimalFormatSymbols::kPercentSymbol); - break; - case kPatternPerMill: - affix = &getConstSymbol(DecimalFormatSymbols::kPerMillSymbol); - break; - case kPatternPlus: - affix = &getConstSymbol(DecimalFormatSymbols::kPlusSignSymbol); - break; - case kPatternMinus: - affix = &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); + if (parsePosition.getIndex() < 0 || parsePosition.getIndex() >= text.length()) { + if (parsePosition.getIndex() == text.length()) { + // If there is nothing to parse, it is an error + parsePosition.setErrorIndex(parsePosition.getIndex()); } + return; } - 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; + 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(); + const NumberParserImpl* parser = getParser(status); + if (U_FAILURE(status)) { + return; // unfortunately no way to report back the error. } - 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); + parser->parse(text, startIndex, true, result, status); + if (U_FAILURE(status)) { + return; // unfortunately no way to report back the error. } - 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); + // TODO: Do we need to check for fImpl->properties->parseAllInput (UCONFIG_HAVE_PARSEALLINPUT) here? + if (result.success()) { + parsePosition.setIndex(result.charEnd); + result.populateFormattable(output, parser->getParseFlags()); + } else { + parsePosition.setErrorIndex(startIndex + result.charEnd); } - - 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; - } -} +CurrencyAmount* DecimalFormat::parseCurrency(const UnicodeString& text, ParsePosition& parsePosition) const { + if (fields == nullptr) { + return nullptr; + } + if (parsePosition.getIndex() < 0 || parsePosition.getIndex() >= text.length()) { + return nullptr; + } -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 + 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(); + const NumberParserImpl* parser = getCurrencyParser(status); + if (U_FAILURE(status)) { + return nullptr; + } + parser->parse(text, startIndex, true, result, status); + if (U_FAILURE(status)) { + return nullptr; + } + // TODO: Do we need to check for fImpl->properties->parseAllInput (UCONFIG_HAVE_PARSEALLINPUT) here? + if (result.success()) { + parsePosition.setIndex(result.charEnd); + Formattable formattable; + result.populateFormattable(formattable, parser->getParseFlags()); + LocalPointer currencyAmount( + new CurrencyAmount(formattable, result.currencyCode, status), status); + if (U_FAILURE(status)) { + return nullptr; + } + return currencyAmount.orphan(); } else { - return FALSE; + parsePosition.setErrorIndex(startIndex + result.charEnd); + return nullptr; } } - - -//------------------------------------------------------------------------------ -// Gets the pointer to the localized decimal format symbols - -const DecimalFormatSymbols* -DecimalFormat::getDecimalFormatSymbols() const -{ - return fSymbols; +const DecimalFormatSymbols* DecimalFormat::getDecimalFormatSymbols(void) const { + if (fields == nullptr) { + return nullptr; + } + return fields->symbols.getAlias(); } -//------------------------------------------------------------------------------ -// 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 +void DecimalFormat::adoptDecimalFormatSymbols(DecimalFormatSymbols* symbolsToAdopt) { + if (symbolsToAdopt == nullptr) { + return; // do not allow caller to set fields->symbols to NULL } - - UBool sameSymbols = FALSE; - if (fSymbols != NULL) { - sameSymbols = (UBool)(getConstSymbol(DecimalFormatSymbols::kCurrencySymbol) == - symbolsToAdopt->getConstSymbol(DecimalFormatSymbols::kCurrencySymbol) && - getConstSymbol(DecimalFormatSymbols::kIntlCurrencySymbol) == - symbolsToAdopt->getConstSymbol(DecimalFormatSymbols::kIntlCurrencySymbol)); - delete fSymbols; + // we must take ownership of symbolsToAdopt, even in a failure case. + LocalPointer dfs(symbolsToAdopt); + if (fields == nullptr) { + return; } + fields->symbols.adoptInstead(dfs.orphan()); + touchNoError(); +} - fSymbols = symbolsToAdopt; - if (!sameSymbols) { - // If the currency symbols are the same, there is no need to recalculate. - setCurrencyForSymbols(); +void DecimalFormat::setDecimalFormatSymbols(const DecimalFormatSymbols& symbols) { + if (fields == nullptr) { + return; } - expandAffixes(NULL); -#if UCONFIG_FORMAT_FASTPATHS_49 - handleChanged(); -#endif + UErrorCode status = U_ZERO_ERROR; + LocalPointer dfs(new DecimalFormatSymbols(symbols), status); + if (U_FAILURE(status)) { + // We failed to allocate DecimalFormatSymbols, release fields and its members. + // We must have a fully complete fields object, we cannot have partially populated members. + delete fields; + fields = nullptr; + return; + } + fields->symbols.adoptInstead(dfs.orphan()); + touchNoError(); } -//------------------------------------------------------------------------------ -// Setting the symbols is equlivalent to adopting a newly created localized -// symbols. -void -DecimalFormat::setDecimalFormatSymbols(const DecimalFormatSymbols& symbols) -{ - adoptDecimalFormatSymbols(new DecimalFormatSymbols(symbols)); -#if UCONFIG_FORMAT_FASTPATHS_49 - handleChanged(); -#endif +const CurrencyPluralInfo* DecimalFormat::getCurrencyPluralInfo(void) const { + if (fields == nullptr) { + return nullptr; + } + return fields->properties->currencyPluralInfo.fPtr.getAlias(); } - -const CurrencyPluralInfo* -DecimalFormat::getCurrencyPluralInfo(void) const -{ - return fCurrencyPluralInfo; +void DecimalFormat::adoptCurrencyPluralInfo(CurrencyPluralInfo* toAdopt) { + // TODO: should we guard against nullptr input, like in adoptDecimalFormatSymbols? + // we must take ownership of toAdopt, even in a failure case. + LocalPointer cpi(toAdopt); + if (fields == nullptr) { + return; + } + fields->properties->currencyPluralInfo.fPtr.adoptInstead(cpi.orphan()); + touchNoError(); } - -void -DecimalFormat::adoptCurrencyPluralInfo(CurrencyPluralInfo* toAdopt) -{ - if (toAdopt != NULL) { - delete fCurrencyPluralInfo; - fCurrencyPluralInfo = toAdopt; - // re-set currency affix patterns and currency affixes. - if (fCurrencySignCount != fgCurrencySignCountZero) { - UErrorCode status = U_ZERO_ERROR; - if (fAffixPatternsForCurrency) { - deleteHashForAffixPattern(); - } - setupCurrencyAffixPatterns(status); - if (fCurrencySignCount == fgCurrencySignCountInPluralFormat) { - // only setup the affixes of the plural pattern. - setupCurrencyAffixes(fFormatPattern, FALSE, TRUE, status); - } - } +void DecimalFormat::setCurrencyPluralInfo(const CurrencyPluralInfo& info) { + if (fields == nullptr) { + return; } -#if UCONFIG_FORMAT_FASTPATHS_49 - handleChanged(); -#endif + if (fields->properties->currencyPluralInfo.fPtr.isNull()) { + // Note: clone() can fail with OOM error, but we have no way to report it. :( + fields->properties->currencyPluralInfo.fPtr.adoptInstead(info.clone()); + } else { + *fields->properties->currencyPluralInfo.fPtr = info; // copy-assignment operator + } + touchNoError(); } -void -DecimalFormat::setCurrencyPluralInfo(const CurrencyPluralInfo& info) -{ - adoptCurrencyPluralInfo(info.clone()); -#if UCONFIG_FORMAT_FASTPATHS_49 - handleChanged(); -#endif +UnicodeString& DecimalFormat::getPositivePrefix(UnicodeString& result) const { + if (fields == nullptr) { + result.setToBogus(); + return result; + } + UErrorCode status = U_ZERO_ERROR; + fields->formatter->getAffixImpl(true, false, result, status); + if (U_FAILURE(status)) { result.setToBogus(); } + return result; } - -/** - * Update the currency object to match the symbols. This method - * is used only when the caller has passed in a symbols object - * that may not be the default object for its locale. - */ -void -DecimalFormat::setCurrencyForSymbols() { - /*Bug 4212072 - Update the affix strings accroding to symbols in order to keep - the affix strings up to date. - [Richard/GCL] - */ - - // With the introduction of the Currency object, the currency - // symbols in the DFS object are ignored. For backward - // compatibility, we check any explicitly set DFS object. If it - // is a default symbols object for its locale, we change the - // currency object to one for that locale. If it is custom, - // we set the currency to null. - UErrorCode ec = U_ZERO_ERROR; - const UChar* c = NULL; - const char* loc = fSymbols->getLocale().getName(); - UChar intlCurrencySymbol[4]; - ucurr_forLocale(loc, intlCurrencySymbol, 4, &ec); - UnicodeString currencySymbol; - - uprv_getStaticCurrencyName(intlCurrencySymbol, loc, currencySymbol, ec); - if (U_SUCCESS(ec) - && getConstSymbol(DecimalFormatSymbols::kCurrencySymbol) == currencySymbol - && getConstSymbol(DecimalFormatSymbols::kIntlCurrencySymbol) == UnicodeString(intlCurrencySymbol)) - { - // Trap an error in mapping locale to currency. If we can't - // map, then don't fail and set the currency to "". - c = intlCurrencySymbol; - } - ec = U_ZERO_ERROR; // reset local error code! - setCurrencyInternally(c, ec); -#if UCONFIG_FORMAT_FASTPATHS_49 - handleChanged(); -#endif +void DecimalFormat::setPositivePrefix(const UnicodeString& newValue) { + if (fields == nullptr) { + return; + } + if (newValue == fields->properties->positivePrefix) { return; } + fields->properties->positivePrefix = newValue; + touchNoError(); } - -//------------------------------------------------------------------------------ -// Gets the positive prefix of the number pattern. - -UnicodeString& -DecimalFormat::getPositivePrefix(UnicodeString& result) const -{ - result = fPositivePrefix; +UnicodeString& DecimalFormat::getNegativePrefix(UnicodeString& result) const { + if (fields == nullptr) { + result.setToBogus(); + return result; + } + UErrorCode status = U_ZERO_ERROR; + fields->formatter->getAffixImpl(true, true, result, status); + if (U_FAILURE(status)) { result.setToBogus(); } return result; } -//------------------------------------------------------------------------------ -// Sets the positive prefix of the number pattern. - -void -DecimalFormat::setPositivePrefix(const UnicodeString& newValue) -{ - fPositivePrefix = newValue; - delete fPosPrefixPattern; - fPosPrefixPattern = 0; -#if UCONFIG_FORMAT_FASTPATHS_49 - handleChanged(); -#endif +void DecimalFormat::setNegativePrefix(const UnicodeString& newValue) { + if (fields == nullptr) { + return; + } + if (newValue == fields->properties->negativePrefix) { return; } + fields->properties->negativePrefix = newValue; + touchNoError(); } -//------------------------------------------------------------------------------ -// Gets the negative prefix of the number pattern. - -UnicodeString& -DecimalFormat::getNegativePrefix(UnicodeString& result) const -{ - result = fNegativePrefix; +UnicodeString& DecimalFormat::getPositiveSuffix(UnicodeString& result) const { + if (fields == nullptr) { + result.setToBogus(); + return result; + } + UErrorCode status = U_ZERO_ERROR; + fields->formatter->getAffixImpl(false, false, result, status); + if (U_FAILURE(status)) { result.setToBogus(); } return result; } -//------------------------------------------------------------------------------ -// Gets the negative prefix of the number pattern. - -void -DecimalFormat::setNegativePrefix(const UnicodeString& newValue) -{ - fNegativePrefix = newValue; - delete fNegPrefixPattern; - fNegPrefixPattern = 0; -#if UCONFIG_FORMAT_FASTPATHS_49 - handleChanged(); -#endif +void DecimalFormat::setPositiveSuffix(const UnicodeString& newValue) { + if (fields == nullptr) { + return; + } + if (newValue == fields->properties->positiveSuffix) { return; } + fields->properties->positiveSuffix = newValue; + touchNoError(); } -//------------------------------------------------------------------------------ -// Gets the positive suffix of the number pattern. - -UnicodeString& -DecimalFormat::getPositiveSuffix(UnicodeString& result) const -{ - result = fPositiveSuffix; +UnicodeString& DecimalFormat::getNegativeSuffix(UnicodeString& result) const { + if (fields == nullptr) { + result.setToBogus(); + return result; + } + UErrorCode status = U_ZERO_ERROR; + fields->formatter->getAffixImpl(false, true, result, status); + if (U_FAILURE(status)) { result.setToBogus(); } return result; } -//------------------------------------------------------------------------------ -// Sets the positive suffix of the number pattern. - -void -DecimalFormat::setPositiveSuffix(const UnicodeString& newValue) -{ - fPositiveSuffix = newValue; - delete fPosSuffixPattern; - fPosSuffixPattern = 0; -#if UCONFIG_FORMAT_FASTPATHS_49 - handleChanged(); -#endif +void DecimalFormat::setNegativeSuffix(const UnicodeString& newValue) { + if (fields == nullptr) { + return; + } + if (newValue == fields->properties->negativeSuffix) { return; } + fields->properties->negativeSuffix = newValue; + touchNoError(); } -//------------------------------------------------------------------------------ -// Gets the negative suffix of the number pattern. - -UnicodeString& -DecimalFormat::getNegativeSuffix(UnicodeString& result) const -{ - result = fNegativeSuffix; - return result; +UBool DecimalFormat::isSignAlwaysShown() const { + // Not much we can do to report an error. + if (fields == nullptr) { + return DecimalFormatProperties::getDefault().signAlwaysShown; + } + return fields->properties->signAlwaysShown; } -//------------------------------------------------------------------------------ -// Sets the negative suffix of the number pattern. - -void -DecimalFormat::setNegativeSuffix(const UnicodeString& newValue) -{ - fNegativeSuffix = newValue; - delete fNegSuffixPattern; - fNegSuffixPattern = 0; -#if UCONFIG_FORMAT_FASTPATHS_49 - handleChanged(); -#endif +void DecimalFormat::setSignAlwaysShown(UBool value) { + if (fields == nullptr) { return; } + if (UBOOL_TO_BOOL(value) == fields->properties->signAlwaysShown) { return; } + fields->properties->signAlwaysShown = value; + touchNoError(); } -//------------------------------------------------------------------------------ -// 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 -{ - if (fMultiplier == NULL) { - return 1; +int32_t DecimalFormat::getMultiplier(void) const { + const DecimalFormatProperties *dfp; + // Not much we can do to report an error. + if (fields == nullptr) { + // Fallback to using the default instance of DecimalFormatProperties. + dfp = &(DecimalFormatProperties::getDefault()); } else { - return fMultiplier->getLong(); + dfp = fields->properties.getAlias(); } -} - -//------------------------------------------------------------------------------ -// Sets the multiplier of the number pattern. -void -DecimalFormat::setMultiplier(int32_t newValue) -{ -// if (newValue == 0) { -// throw new IllegalArgumentException("Bad multiplier: " + newValue); -// } - if (newValue == 0) { - newValue = 1; // one being the benign default value for a multiplier. - } - if (newValue == 1) { - delete fMultiplier; - fMultiplier = NULL; + if (dfp->multiplier != 1) { + return dfp->multiplier; + } else if (dfp->magnitudeMultiplier != 0) { + return static_cast(uprv_pow10(dfp->magnitudeMultiplier)); } else { - if (fMultiplier == NULL) { - fMultiplier = new DigitList; - } - if (fMultiplier != NULL) { - fMultiplier->set(newValue); - } + return 1; } -#if UCONFIG_FORMAT_FASTPATHS_49 - handleChanged(); -#endif } -/** - * 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 { - if (fRoundingIncrement == NULL) { - return 0.0; - } else { - return fRoundingIncrement->getDouble(); +void DecimalFormat::setMultiplier(int32_t multiplier) { + if (fields == nullptr) { + return; } -} - -/** - * 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) { - if (newValue > 0.0) { - if (fRoundingIncrement == NULL) { - fRoundingIncrement = new DigitList(); - } - if (fRoundingIncrement != NULL) { - fRoundingIncrement->set(newValue); - return; - } + if (multiplier == 0) { + multiplier = 1; // one being the benign default value for a multiplier. } - // These statements are executed if newValue is less than 0.0 - // or fRoundingIncrement could not be created. - delete fRoundingIncrement; - fRoundingIncrement = NULL; -#if UCONFIG_FORMAT_FASTPATHS_49 - handleChanged(); -#endif -} - -/** - * Get the rounding mode. - * @return A rounding mode - * @see #setRoundingIncrement - * @see #getRoundingIncrement - * @see #setRoundingMode - */ -DecimalFormat::ERoundingMode DecimalFormat::getRoundingMode() const { - return fRoundingMode; -} - -/** - * 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) { - fRoundingMode = roundingMode; -#if UCONFIG_FORMAT_FASTPATHS_49 - handleChanged(); -#endif -} - -/** - * 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 fFormatWidth; -} - -/** - * 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) { - fFormatWidth = (width > 0) ? width : 0; -#if UCONFIG_FORMAT_FASTPATHS_49 - handleChanged(); -#endif -} - -UnicodeString DecimalFormat::getPadCharacterString() const { - return UnicodeString(fPad); -} -void DecimalFormat::setPadCharacter(const UnicodeString &padChar) { - if (padChar.length() > 0) { - fPad = padChar.char32At(0); + // 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; } - else { - fPad = kDefaultPad; + if (delta != -1) { + fields->properties->magnitudeMultiplier = delta; + fields->properties->multiplier = 1; + } else { + fields->properties->magnitudeMultiplier = 0; + fields->properties->multiplier = multiplier; } -#if UCONFIG_FORMAT_FASTPATHS_49 - handleChanged(); -#endif -} - -/** - * 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 fPadPosition; -} - -/** - * 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) { - fPadPosition = padPos; -#if UCONFIG_FORMAT_FASTPATHS_49 - handleChanged(); -#endif -} - -/** - * 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 fUseExponentialNotation; -} - -/** - * 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) { - fUseExponentialNotation = useScientific; -#if UCONFIG_FORMAT_FASTPATHS_49 - handleChanged(); -#endif -} - -/** - * 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 fMinExponentDigits; -} - -/** - * 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) { - fMinExponentDigits = (int8_t)((minExpDig > 0) ? minExpDig : 1); -#if UCONFIG_FORMAT_FASTPATHS_49 - handleChanged(); -#endif -} - -/** - * 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 fExponentSignAlwaysShown; -} - -/** - * 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) { - fExponentSignAlwaysShown = expSignAlways; -#if UCONFIG_FORMAT_FASTPATHS_49 - handleChanged(); -#endif -} - -//------------------------------------------------------------------------------ -// Gets the grouping size of the number pattern. For example, thousand or 10 -// thousand groupings. - -int32_t -DecimalFormat::getGroupingSize() const -{ - return isGroupingUsed() ? fGroupingSize : 0; -} - -//------------------------------------------------------------------------------ -// Gets the grouping size of the number pattern. - -void -DecimalFormat::setGroupingSize(int32_t newValue) -{ - fGroupingSize = newValue; -#if UCONFIG_FORMAT_FASTPATHS_49 - handleChanged(); -#endif + touchNoError(); } -//------------------------------------------------------------------------------ - -int32_t -DecimalFormat::getSecondaryGroupingSize() const -{ - return fGroupingSize2; +int32_t DecimalFormat::getMultiplierScale() const { + // Not much we can do to report an error. + if (fields == nullptr) { + // Fallback to using the default instance of DecimalFormatProperties. + return DecimalFormatProperties::getDefault().multiplierScale; + } + return fields->properties->multiplierScale; } -//------------------------------------------------------------------------------ - -void -DecimalFormat::setSecondaryGroupingSize(int32_t newValue) -{ - fGroupingSize2 = newValue; -#if UCONFIG_FORMAT_FASTPATHS_49 - handleChanged(); -#endif +void DecimalFormat::setMultiplierScale(int32_t newValue) { + if (fields == nullptr) { return; } + if (newValue == fields->properties->multiplierScale) { return; } + fields->properties->multiplierScale = newValue; + touchNoError(); } -//------------------------------------------------------------------------------ -// Checks if to show the decimal separator. - -UBool -DecimalFormat::isDecimalSeparatorAlwaysShown() const -{ - return fDecimalSeparatorAlwaysShown; +double DecimalFormat::getRoundingIncrement(void) const { + // Not much we can do to report an error. + if (fields == nullptr) { + // Fallback to using the default instance of DecimalFormatProperties. + return DecimalFormatProperties::getDefault().roundingIncrement; + } + return fields->exportedProperties->roundingIncrement; } -//------------------------------------------------------------------------------ -// Sets to always show the decimal separator. - -void -DecimalFormat::setDecimalSeparatorAlwaysShown(UBool newValue) -{ - fDecimalSeparatorAlwaysShown = newValue; -#if UCONFIG_FORMAT_FASTPATHS_49 - handleChanged(); -#endif +void DecimalFormat::setRoundingIncrement(double newValue) { + if (fields == nullptr) { return; } + if (newValue == fields->properties->roundingIncrement) { return; } + fields->properties->roundingIncrement = newValue; + touchNoError(); } -//------------------------------------------------------------------------------ -// Emits the pattern of this DecimalFormat instance. - -UnicodeString& -DecimalFormat::toPattern(UnicodeString& result) const -{ - return toPattern(result, FALSE); +ERoundingMode DecimalFormat::getRoundingMode(void) const { + // Not much we can do to report an error. + if (fields == nullptr) { + // Fallback to using the default instance of DecimalFormatProperties. + return static_cast(DecimalFormatProperties::getDefault().roundingMode.getNoError()); + } + // UNumberFormatRoundingMode and ERoundingMode have the same values. + return static_cast(fields->exportedProperties->roundingMode.getNoError()); } -//------------------------------------------------------------------------------ -// Emits the localized pattern this DecimalFormat instance. - -UnicodeString& -DecimalFormat::toLocalizedPattern(UnicodeString& result) const -{ - return toPattern(result, TRUE); +void DecimalFormat::setRoundingMode(ERoundingMode roundingMode) { + if (fields == nullptr) { return; } + auto uRoundingMode = static_cast(roundingMode); + if (!fields->properties->roundingMode.isNull() && uRoundingMode == fields->properties->roundingMode.getNoError()) { + return; + } + NumberFormat::setMaximumIntegerDigits(roundingMode); // to set field for compatibility + fields->properties->roundingMode = uRoundingMode; + touchNoError(); } -//------------------------------------------------------------------------------ -/** - * Expand the affix pattern strings into the expanded affix strings. If any - * affix pattern string is null, do not expand it. This method should be - * called any time the symbols or the affix patterns change in order to keep - * the expanded affix strings up to date. - * This method also will be called before formatting if format currency - * plural names, since the plural name is not a static one, it is - * based on the currency plural count, the affix will be known only - * after the currency plural count is know. - * In which case, the parameter - * 'pluralCount' will be a non-null currency plural count. - * In all other cases, the 'pluralCount' is null, which means it is not needed. - */ -void DecimalFormat::expandAffixes(const UnicodeString* pluralCount) { - FieldPositionHandler none; - if (fPosPrefixPattern != 0) { - expandAffix(*fPosPrefixPattern, fPositivePrefix, 0, none, FALSE, pluralCount); - } - if (fPosSuffixPattern != 0) { - expandAffix(*fPosSuffixPattern, fPositiveSuffix, 0, none, FALSE, pluralCount); - } - if (fNegPrefixPattern != 0) { - expandAffix(*fNegPrefixPattern, fNegativePrefix, 0, none, FALSE, pluralCount); - } - if (fNegSuffixPattern != 0) { - expandAffix(*fNegSuffixPattern, fNegativeSuffix, 0, none, FALSE, pluralCount); - } -#ifdef FMT_DEBUG - UnicodeString s; - s.append(UnicodeString("[")) - .append(DEREFSTR(fPosPrefixPattern)).append((UnicodeString)"|").append(DEREFSTR(fPosSuffixPattern)) - .append((UnicodeString)";") .append(DEREFSTR(fNegPrefixPattern)).append((UnicodeString)"|").append(DEREFSTR(fNegSuffixPattern)) - .append((UnicodeString)"]->[") - .append(fPositivePrefix).append((UnicodeString)"|").append(fPositiveSuffix) - .append((UnicodeString)";") .append(fNegativePrefix).append((UnicodeString)"|").append(fNegativeSuffix) - .append((UnicodeString)"]\n"); - debugout(s); -#endif +int32_t DecimalFormat::getFormatWidth(void) const { + // Not much we can do to report an error. + if (fields == nullptr) { + // Fallback to using the default instance of DecimalFormatProperties. + return DecimalFormatProperties::getDefault().formatWidth; + } + return fields->properties->formatWidth; } -/** - * Expand an affix pattern into an affix string. All characters in the - * pattern are literal unless prefixed by kQuote. The following characters - * after kQuote are recognized: PATTERN_PERCENT, PATTERN_PER_MILLE, - * PATTERN_MINUS, and kCurrencySign. If kCurrencySign is doubled (kQuote + - * kCurrencySign + kCurrencySign), it is interpreted as an international - * currency sign. If CURRENCY_SIGN is tripled, it is interpreted as - * currency plural long names, such as "US Dollars". - * Any other character after a kQuote represents itself. - * kQuote must be followed by another character; kQuote may not occur by - * itself at the end of the pattern. - * - * This method is used in two distinct ways. First, it is used to expand - * the stored affix patterns into actual affixes. For this usage, doFormat - * must be false. Second, it is used to expand the stored affix patterns - * given a specific number (doFormat == true), for those rare cases in - * which a currency format references a ChoiceFormat (e.g., en_IN display - * name for INR). The number itself is taken from digitList. - * - * When used in the first way, this method has a side effect: It sets - * currencyChoice to a ChoiceFormat object, if the currency's display name - * in this locale is a ChoiceFormat pattern (very rare). It only does this - * if currencyChoice is null to start with. - * - * @param pattern the non-null, fPossibly empty pattern - * @param affix string to receive the expanded equivalent of pattern. - * Previous contents are deleted. - * @param doFormat if false, then the pattern will be expanded, and if a - * currency symbol is encountered that expands to a ChoiceFormat, the - * currencyChoice member variable will be initialized if it is null. If - * doFormat is true, then it is assumed that the currencyChoice has been - * created, and it will be used to format the value in digitList. - * @param pluralCount the plural count. It is only used for currency - * plural format. In which case, it is the plural - * count of the currency amount. For example, - * in en_US, it is the singular "one", or the plural - * "other". For all other cases, it is null, and - * is not being used. - */ -void DecimalFormat::expandAffix(const UnicodeString& pattern, - UnicodeString& affix, - double number, - FieldPositionHandler& handler, - UBool doFormat, - const UnicodeString* pluralCount) const { - affix.remove(); - for (int i=0; igetLocale().getName() : - Locale::getDefault().getName(), &isChoiceFormat, - pluralCountChar.data(), &len, &ec); - affix += UnicodeString(s, len); - handler.addAttribute(kCurrencyField, beginIdx, affix.length()); - } else if(intl) { - affix.append(currencyUChars, -1); - handler.addAttribute(kCurrencyField, beginIdx, affix.length()); - } else { - int32_t len; - UBool isChoiceFormat; - // If fSymbols is NULL, use default locale - const UChar* s = ucurr_getName(currencyUChars, - fSymbols != NULL ? fSymbols->getLocale().getName() : Locale::getDefault().getName(), - UCURR_SYMBOL_NAME, &isChoiceFormat, &len, &ec); - if (isChoiceFormat) { - // Two modes here: If doFormat is false, we set up - // currencyChoice. If doFormat is true, we use the - // previously created currencyChoice to format the - // value in digitList. - if (!doFormat) { - // If the currency is handled by a ChoiceFormat, - // then we're not going to use the expanded - // patterns. Instantiate the ChoiceFormat and - // return. - if (fCurrencyChoice == NULL) { - // TODO Replace double-check with proper thread-safe code - ChoiceFormat* fmt = new ChoiceFormat(UnicodeString(s), ec); - if (U_SUCCESS(ec)) { - umtx_lock(NULL); - if (fCurrencyChoice == NULL) { - // Cast away const - ((DecimalFormat*)this)->fCurrencyChoice = fmt; - fmt = NULL; - } - umtx_unlock(NULL); - delete fmt; - } - } - // We could almost return null or "" here, since the - // expanded affixes are almost not used at all - // in this situation. However, one method -- - // toPattern() -- still does use the expanded - // affixes, in order to set up a padding - // pattern. We use the CURRENCY_SIGN as a - // placeholder. - affix.append(kCurrencySign); - } else { - if (fCurrencyChoice != NULL) { - FieldPosition pos(0); // ignored - if (number < 0) { - number = -number; - } - fCurrencyChoice->format(number, affix, pos); - } else { - // We only arrive here if the currency choice - // format in the locale data is INVALID. - affix.append(currencyUChars, -1); - handler.addAttribute(kCurrencyField, beginIdx, affix.length()); - } - } - continue; - } - affix += UnicodeString(s, len); - handler.addAttribute(kCurrencyField, beginIdx, affix.length()); - } - } else { - if(intl) { - affix += getConstSymbol(DecimalFormatSymbols::kIntlCurrencySymbol); - } else { - affix += getConstSymbol(DecimalFormatSymbols::kCurrencySymbol); - } - handler.addAttribute(kCurrencyField, beginIdx, affix.length()); - } - break; - } - case kPatternPercent: - affix += getConstSymbol(DecimalFormatSymbols::kPercentSymbol); - handler.addAttribute(kPercentField, beginIdx, affix.length()); - break; - case kPatternPerMill: - affix += getConstSymbol(DecimalFormatSymbols::kPerMillSymbol); - handler.addAttribute(kPermillField, beginIdx, affix.length()); - break; - case kPatternPlus: - affix += getConstSymbol(DecimalFormatSymbols::kPlusSignSymbol); - handler.addAttribute(kSignField, beginIdx, affix.length()); - break; - case kPatternMinus: - affix += getConstSymbol(DecimalFormatSymbols::kMinusSignSymbol); - handler.addAttribute(kSignField, beginIdx, affix.length()); - break; - default: - affix.append(c); - break; - } - } - else { - affix.append(c); - } - } +void DecimalFormat::setFormatWidth(int32_t width) { + if (fields == nullptr) { return; } + if (width == fields->properties->formatWidth) { return; } + fields->properties->formatWidth = width; + touchNoError(); } -/** - * Append an affix to the given StringBuffer. - * @param buf buffer to append to - * @param isNegative - * @param isPrefix - */ -int32_t DecimalFormat::appendAffix(UnicodeString& buf, double number, - FieldPositionHandler& handler, - UBool isNegative, UBool isPrefix) const { - // plural format precedes choice format - if (fCurrencyChoice != 0 && - fCurrencySignCount != fgCurrencySignCountInPluralFormat) { - const UnicodeString* affixPat; - if (isPrefix) { - affixPat = isNegative ? fNegPrefixPattern : fPosPrefixPattern; - } else { - affixPat = isNegative ? fNegSuffixPattern : fPosSuffixPattern; - } - if (affixPat) { - UnicodeString affixBuf; - expandAffix(*affixPat, affixBuf, number, handler, TRUE, NULL); - buf.append(affixBuf); - return affixBuf.length(); - } - // else someone called a function that reset the pattern. - } - - const UnicodeString* affix; - if (fCurrencySignCount == fgCurrencySignCountInPluralFormat) { - // TODO: get an accurate count of visible fraction digits. - UnicodeString pluralCount; - int32_t minFractionDigits = this->getMinimumFractionDigits(); - if (minFractionDigits > 0) { - FixedDecimal ni(number, this->getMinimumFractionDigits()); - pluralCount = fCurrencyPluralInfo->getPluralRules()->select(ni); - } else { - pluralCount = fCurrencyPluralInfo->getPluralRules()->select(number); - } - AffixesForCurrency* oneSet; - if (fStyle == UNUM_CURRENCY_PLURAL) { - oneSet = (AffixesForCurrency*)fPluralAffixesForCurrency->get(pluralCount); - } else { - oneSet = (AffixesForCurrency*)fAffixesForCurrency->get(pluralCount); - } - if (isPrefix) { - affix = isNegative ? &oneSet->negPrefixForCurrency : - &oneSet->posPrefixForCurrency; - } else { - affix = isNegative ? &oneSet->negSuffixForCurrency : - &oneSet->posSuffixForCurrency; - } +UnicodeString DecimalFormat::getPadCharacterString() const { + if (fields == nullptr || fields->properties->padString.isBogus()) { + // Readonly-alias the static string kFallbackPaddingString + return {TRUE, kFallbackPaddingString, -1}; } else { - if (isPrefix) { - affix = isNegative ? &fNegativePrefix : &fPositivePrefix; - } else { - affix = isNegative ? &fNegativeSuffix : &fPositiveSuffix; - } + return fields->properties->padString; } +} - int32_t begin = (int) buf.length(); - - buf.append(*affix); - - if (handler.isRecording()) { - int32_t offset = (int) (*affix).indexOf(getConstSymbol(DecimalFormatSymbols::kCurrencySymbol)); - if (offset > -1) { - UnicodeString aff = getConstSymbol(DecimalFormatSymbols::kCurrencySymbol); - handler.addAttribute(kCurrencyField, begin + offset, begin + offset + aff.length()); - } - - offset = (int) (*affix).indexOf(getConstSymbol(DecimalFormatSymbols::kIntlCurrencySymbol)); - if (offset > -1) { - UnicodeString aff = getConstSymbol(DecimalFormatSymbols::kIntlCurrencySymbol); - handler.addAttribute(kCurrencyField, begin + offset, begin + offset + aff.length()); - } +void DecimalFormat::setPadCharacter(const UnicodeString& padChar) { + if (fields == nullptr) { return; } + if (padChar == fields->properties->padString) { return; } + if (padChar.length() > 0) { + fields->properties->padString = UnicodeString(padChar.char32At(0)); + } else { + fields->properties->padString.setToBogus(); + } + touchNoError(); +} - offset = (int) (*affix).indexOf(getConstSymbol(DecimalFormatSymbols::kMinusSignSymbol)); - if (offset > -1) { - UnicodeString aff = getConstSymbol(DecimalFormatSymbols::kMinusSignSymbol); - handler.addAttribute(kSignField, begin + offset, begin + offset + aff.length()); - } +EPadPosition DecimalFormat::getPadPosition(void) const { + if (fields == nullptr || fields->properties->padPosition.isNull()) { + return EPadPosition::kPadBeforePrefix; + } else { + // UNumberFormatPadPosition and EPadPosition have the same values. + return static_cast(fields->properties->padPosition.getNoError()); + } +} - offset = (int) (*affix).indexOf(getConstSymbol(DecimalFormatSymbols::kPercentSymbol)); - if (offset > -1) { - UnicodeString aff = getConstSymbol(DecimalFormatSymbols::kPercentSymbol); - handler.addAttribute(kPercentField, begin + offset, begin + offset + aff.length()); - } +void DecimalFormat::setPadPosition(EPadPosition padPos) { + if (fields == nullptr) { return; } + auto uPadPos = static_cast(padPos); + if (!fields->properties->padPosition.isNull() && uPadPos == fields->properties->padPosition.getNoError()) { + return; + } + fields->properties->padPosition = uPadPos; + touchNoError(); +} - offset = (int) (*affix).indexOf(getConstSymbol(DecimalFormatSymbols::kPerMillSymbol)); - if (offset > -1) { - UnicodeString aff = getConstSymbol(DecimalFormatSymbols::kPerMillSymbol); - handler.addAttribute(kPermillField, begin + offset, begin + offset + aff.length()); - } +UBool DecimalFormat::isScientificNotation(void) const { + // Not much we can do to report an error. + if (fields == nullptr) { + // Fallback to using the default instance of DecimalFormatProperties. + return (DecimalFormatProperties::getDefault().minimumExponentDigits != -1); } - return affix->length(); + return (fields->properties->minimumExponentDigits != -1); } -/** - * Appends an affix pattern to the given StringBuffer, quoting special - * characters as needed. Uses the internal affix pattern, if that exists, - * or the literal affix, if the internal affix pattern is null. The - * appended string will generate the same affix pattern (or literal affix) - * when passed to toPattern(). - * - * @param appendTo the affix string is appended to this - * @param affixPattern a pattern such as fPosPrefixPattern; may be null - * @param expAffix a corresponding expanded affix, such as fPositivePrefix. - * Ignored unless affixPattern is null. If affixPattern is null, then - * expAffix is appended as a literal affix. - * @param localized true if the appended pattern should contain localized - * pattern characters; otherwise, non-localized pattern chars are appended - */ -void DecimalFormat::appendAffixPattern(UnicodeString& appendTo, - const UnicodeString* affixPattern, - const UnicodeString& expAffix, - UBool localized) const { - if (affixPattern == 0) { - appendAffixPattern(appendTo, expAffix, localized); +void DecimalFormat::setScientificNotation(UBool useScientific) { + if (fields == nullptr) { return; } + int32_t minExp = useScientific ? 1 : -1; + if (fields->properties->minimumExponentDigits == minExp) { return; } + if (useScientific) { + fields->properties->minimumExponentDigits = 1; } else { - int i; - for (int pos=0; poslength(); pos=i) { - i = affixPattern->indexOf(kQuote, pos); - if (i < 0) { - UnicodeString s; - affixPattern->extractBetween(pos, affixPattern->length(), s); - appendAffixPattern(appendTo, s, localized); - break; - } - if (i > pos) { - UnicodeString s; - affixPattern->extractBetween(pos, i, s); - appendAffixPattern(appendTo, s, localized); - } - UChar32 c = affixPattern->char32At(++i); - ++i; - if (c == kQuote) { - appendTo.append(c).append(c); - // Fall through and append another kQuote below - } else if (c == kCurrencySign && - ilength() && - affixPattern->char32At(i) == kCurrencySign) { - ++i; - appendTo.append(c).append(c); - } else if (localized) { - switch (c) { - case kPatternPercent: - appendTo += getConstSymbol(DecimalFormatSymbols::kPercentSymbol); - break; - case kPatternPerMill: - appendTo += getConstSymbol(DecimalFormatSymbols::kPerMillSymbol); - break; - case kPatternPlus: - appendTo += getConstSymbol(DecimalFormatSymbols::kPlusSignSymbol); - break; - case kPatternMinus: - appendTo += getConstSymbol(DecimalFormatSymbols::kMinusSignSymbol); - break; - default: - appendTo.append(c); - } - } else { - appendTo.append(c); - } - } + fields->properties->minimumExponentDigits = -1; } + touchNoError(); } -/** - * Append an affix to the given StringBuffer, using quotes if - * there are special characters. Single quotes themselves must be - * escaped in either case. - */ -void -DecimalFormat::appendAffixPattern(UnicodeString& appendTo, - const UnicodeString& affix, - UBool localized) const { - UBool needQuote; - if(localized) { - needQuote = affix.indexOf(getConstSymbol(DecimalFormatSymbols::kZeroDigitSymbol)) >= 0 - || affix.indexOf(getConstSymbol(DecimalFormatSymbols::kGroupingSeparatorSymbol)) >= 0 - || affix.indexOf(getConstSymbol(DecimalFormatSymbols::kDecimalSeparatorSymbol)) >= 0 - || affix.indexOf(getConstSymbol(DecimalFormatSymbols::kPercentSymbol)) >= 0 - || affix.indexOf(getConstSymbol(DecimalFormatSymbols::kPerMillSymbol)) >= 0 - || affix.indexOf(getConstSymbol(DecimalFormatSymbols::kDigitSymbol)) >= 0 - || affix.indexOf(getConstSymbol(DecimalFormatSymbols::kPatternSeparatorSymbol)) >= 0 - || affix.indexOf(getConstSymbol(DecimalFormatSymbols::kPlusSignSymbol)) >= 0 - || affix.indexOf(getConstSymbol(DecimalFormatSymbols::kMinusSignSymbol)) >= 0 - || affix.indexOf(kCurrencySign) >= 0; - } - else { - needQuote = affix.indexOf(kPatternZeroDigit) >= 0 - || affix.indexOf(kPatternGroupingSeparator) >= 0 - || affix.indexOf(kPatternDecimalSeparator) >= 0 - || affix.indexOf(kPatternPercent) >= 0 - || affix.indexOf(kPatternPerMill) >= 0 - || affix.indexOf(kPatternDigit) >= 0 - || affix.indexOf(kPatternSeparator) >= 0 - || affix.indexOf(kPatternExponent) >= 0 - || affix.indexOf(kPatternPlus) >= 0 - || affix.indexOf(kPatternMinus) >= 0 - || affix.indexOf(kCurrencySign) >= 0; - } - if (needQuote) - appendTo += (UChar)0x0027 /*'\''*/; - if (affix.indexOf((UChar)0x0027 /*'\''*/) < 0) - appendTo += affix; - else { - for (int32_t j = 0; j < affix.length(); ) { - UChar32 c = affix.char32At(j); - j += U16_LENGTH(c); - appendTo += c; - if (c == 0x0027 /*'\''*/) - appendTo += c; - } +int8_t DecimalFormat::getMinimumExponentDigits(void) const { + // Not much we can do to report an error. + if (fields == nullptr) { + // Fallback to using the default instance of DecimalFormatProperties. + return static_cast(DecimalFormatProperties::getDefault().minimumExponentDigits); } - if (needQuote) - appendTo += (UChar)0x0027 /*'\''*/; + return static_cast(fields->properties->minimumExponentDigits); } -//------------------------------------------------------------------------------ +void DecimalFormat::setMinimumExponentDigits(int8_t minExpDig) { + if (fields == nullptr) { return; } + if (minExpDig == fields->properties->minimumExponentDigits) { return; } + fields->properties->minimumExponentDigits = minExpDig; + touchNoError(); +} -UnicodeString& -DecimalFormat::toPattern(UnicodeString& result, UBool localized) const -{ - if (fStyle == UNUM_CURRENCY_PLURAL) { - // the prefix or suffix pattern might not be defined yet, - // so they can not be synthesized, - // instead, get them directly. - // but it might not be the actual pattern used in formatting. - // the actual pattern used in formatting depends on the - // formatted number's plural count. - result = fFormatPattern; - return result; - } - result.remove(); - UChar32 zero, sigDigit = kPatternSignificantDigit; - UnicodeString digit, group; - int32_t i; - int32_t roundingDecimalPos = 0; // Pos of decimal in roundingDigits - UnicodeString roundingDigits; - int32_t padPos = (fFormatWidth > 0) ? fPadPosition : -1; - UnicodeString padSpec; - UBool useSigDig = areSignificantDigitsUsed(); - - if (localized) { - digit.append(getConstSymbol(DecimalFormatSymbols::kDigitSymbol)); - group.append(getConstSymbol(DecimalFormatSymbols::kGroupingSeparatorSymbol)); - zero = getConstSymbol(DecimalFormatSymbols::kZeroDigitSymbol).char32At(0); - if (useSigDig) { - sigDigit = getConstSymbol(DecimalFormatSymbols::kSignificantDigitSymbol).char32At(0); - } +UBool DecimalFormat::isExponentSignAlwaysShown(void) const { + // Not much we can do to report an error. + if (fields == nullptr) { + // Fallback to using the default instance of DecimalFormatProperties. + return DecimalFormatProperties::getDefault().exponentSignAlwaysShown; } - else { - digit.append((UChar)kPatternDigit); - group.append((UChar)kPatternGroupingSeparator); - zero = (UChar32)kPatternZeroDigit; + return fields->properties->exponentSignAlwaysShown; +} + +void DecimalFormat::setExponentSignAlwaysShown(UBool expSignAlways) { + if (fields == nullptr) { return; } + if (UBOOL_TO_BOOL(expSignAlways) == fields->properties->exponentSignAlwaysShown) { return; } + fields->properties->exponentSignAlwaysShown = expSignAlways; + touchNoError(); +} + +int32_t DecimalFormat::getGroupingSize(void) const { + int32_t groupingSize; + // Not much we can do to report an error. + if (fields == nullptr) { + // Fallback to using the default instance of DecimalFormatProperties. + groupingSize = DecimalFormatProperties::getDefault().groupingSize; + } else { + groupingSize = fields->properties->groupingSize; } - if (fFormatWidth > 0) { - if (localized) { - padSpec.append(getConstSymbol(DecimalFormatSymbols::kPadEscapeSymbol)); - } - else { - padSpec.append((UChar)kPatternPadEscape); - } - padSpec.append(fPad); + if (groupingSize < 0) { + return 0; } - if (fRoundingIncrement != NULL) { - for(i=0; igetCount(); ++i) { - roundingDigits.append(zero+(fRoundingIncrement->getDigitValue(i))); // Convert to Unicode digit - } - roundingDecimalPos = fRoundingIncrement->getDecimalAt(); + return groupingSize; +} + +void DecimalFormat::setGroupingSize(int32_t newValue) { + if (fields == nullptr) { return; } + if (newValue == fields->properties->groupingSize) { return; } + fields->properties->groupingSize = newValue; + touchNoError(); +} + +int32_t DecimalFormat::getSecondaryGroupingSize(void) const { + int32_t grouping2; + // Not much we can do to report an error. + if (fields == nullptr) { + // Fallback to using the default instance of DecimalFormatProperties. + grouping2 = DecimalFormatProperties::getDefault().secondaryGroupingSize; + } else { + grouping2 = fields->properties->secondaryGroupingSize; } - for (int32_t part=0; part<2; ++part) { - if (padPos == kPadBeforePrefix) { - result.append(padSpec); - } - appendAffixPattern(result, - (part==0 ? fPosPrefixPattern : fNegPrefixPattern), - (part==0 ? fPositivePrefix : fNegativePrefix), - localized); - if (padPos == kPadAfterPrefix && ! padSpec.isEmpty()) { - result.append(padSpec); - } - int32_t sub0Start = result.length(); - int32_t g = isGroupingUsed() ? _max(0, fGroupingSize) : 0; - if (g > 0 && fGroupingSize2 > 0 && fGroupingSize2 != fGroupingSize) { - g += fGroupingSize2; - } - int32_t maxDig = 0, minDig = 0, maxSigDig = 0; - if (useSigDig) { - minDig = getMinimumSignificantDigits(); - maxDig = maxSigDig = getMaximumSignificantDigits(); - } else { - minDig = getMinimumIntegerDigits(); - maxDig = getMaximumIntegerDigits(); - } - if (fUseExponentialNotation) { - if (maxDig > kMaxScientificIntegerDigits) { - maxDig = 1; - } - } else if (useSigDig) { - maxDig = _max(maxDig, g+1); - } else { - maxDig = _max(_max(g, getMinimumIntegerDigits()), - roundingDecimalPos) + 1; - } - for (i = maxDig; i > 0; --i) { - if (!fUseExponentialNotation && i maxSigDig or 1 <= pos <= (maxSigDig - minSigDig) - // Use @ if (maxSigDig - minSigDig) < pos <= maxSigDig - if (maxSigDig >= i && i > (maxSigDig - minDig)) { - result.append(sigDigit); - } else { - result.append(digit); - } - } else { - if (! roundingDigits.isEmpty()) { - int32_t pos = roundingDecimalPos - i; - if (pos >= 0 && pos < roundingDigits.length()) { - result.append((UChar) (roundingDigits.char32At(pos) - kPatternZeroDigit + zero)); - continue; - } - } - if (i<=minDig) { - result.append(zero); - } else { - result.append(digit); - } - } - } - if (!useSigDig) { - if (getMaximumFractionDigits() > 0 || fDecimalSeparatorAlwaysShown) { - if (localized) { - result += getConstSymbol(DecimalFormatSymbols::kDecimalSeparatorSymbol); - } - else { - result.append((UChar)kPatternDecimalSeparator); - } - } - int32_t pos = roundingDecimalPos; - for (i = 0; i < getMaximumFractionDigits(); ++i) { - if (! roundingDigits.isEmpty() && pos < roundingDigits.length()) { - if (pos < 0) { - result.append(zero); - } - else { - result.append((UChar)(roundingDigits.char32At(pos) - kPatternZeroDigit + zero)); - } - ++pos; - continue; - } - if (i 0) { - result.insert(sub0Start, digit); - ++maxDig; - --add; - // Only add a grouping separator if we have at least - // 2 additional characters to be added, so we don't - // end up with ",###". - if (add>1 && isGroupingPosition(maxDig)) { - result.insert(sub0Start, group); - --add; - } - } - } - if (fPadPosition == kPadBeforeSuffix && ! padSpec.isEmpty()) { - result.append(padSpec); - } - if (part == 0) { - appendAffixPattern(result, fPosSuffixPattern, fPositiveSuffix, localized); - if (fPadPosition == kPadAfterSuffix && ! padSpec.isEmpty()) { - result.append(padSpec); - } - UBool isDefault = FALSE; - if ((fNegSuffixPattern == fPosSuffixPattern && // both null - fNegativeSuffix == fPositiveSuffix) - || (fNegSuffixPattern != 0 && fPosSuffixPattern != 0 && - *fNegSuffixPattern == *fPosSuffixPattern)) - { - if (fNegPrefixPattern != NULL && fPosPrefixPattern != NULL) - { - int32_t length = fPosPrefixPattern->length(); - isDefault = fNegPrefixPattern->length() == (length+2) && - (*fNegPrefixPattern)[(int32_t)0] == kQuote && - (*fNegPrefixPattern)[(int32_t)1] == kPatternMinus && - fNegPrefixPattern->compare(2, length, *fPosPrefixPattern, 0, length) == 0; - } - if (!isDefault && - fNegPrefixPattern == NULL && fPosPrefixPattern == NULL) - { - int32_t length = fPositivePrefix.length(); - isDefault = fNegativePrefix.length() == (length+1) && - fNegativePrefix.compare(getConstSymbol(DecimalFormatSymbols::kMinusSignSymbol)) == 0 && - fNegativePrefix.compare(1, length, fPositivePrefix, 0, length) == 0; - } - } - if (isDefault) { - break; // Don't output default negative subpattern - } else { - if (localized) { - result += getConstSymbol(DecimalFormatSymbols::kPatternSeparatorSymbol); - } - else { - result.append((UChar)kPatternSeparator); - } - } - } else { - appendAffixPattern(result, fNegSuffixPattern, fNegativeSuffix, localized); - if (fPadPosition == kPadAfterSuffix && ! padSpec.isEmpty()) { - result.append(padSpec); - } - } + if (grouping2 < 0) { + return 0; } + return grouping2; +} - return result; +void DecimalFormat::setSecondaryGroupingSize(int32_t newValue) { + if (fields == nullptr) { return; } + if (newValue == fields->properties->secondaryGroupingSize) { return; } + fields->properties->secondaryGroupingSize = newValue; + touchNoError(); } -//------------------------------------------------------------------------------ +int32_t DecimalFormat::getMinimumGroupingDigits() const { + // Not much we can do to report an error. + if (fields == nullptr) { + // Fallback to using the default instance of DecimalFormatProperties. + return DecimalFormatProperties::getDefault().minimumGroupingDigits; + } + return fields->properties->minimumGroupingDigits; +} -void -DecimalFormat::applyPattern(const UnicodeString& pattern, UErrorCode& status) -{ - UParseError parseError; - applyPattern(pattern, FALSE, parseError, status); +void DecimalFormat::setMinimumGroupingDigits(int32_t newValue) { + if (fields == nullptr) { return; } + if (newValue == fields->properties->minimumGroupingDigits) { return; } + fields->properties->minimumGroupingDigits = newValue; + touchNoError(); } -//------------------------------------------------------------------------------ +UBool DecimalFormat::isDecimalSeparatorAlwaysShown(void) const { + // Not much we can do to report an error. + if (fields == nullptr) { + // Fallback to using the default instance of DecimalFormatProperties. + return DecimalFormatProperties::getDefault().decimalSeparatorAlwaysShown; + } + return fields->properties->decimalSeparatorAlwaysShown; +} -void -DecimalFormat::applyPattern(const UnicodeString& pattern, - UParseError& parseError, - UErrorCode& status) -{ - applyPattern(pattern, FALSE, parseError, status); +void DecimalFormat::setDecimalSeparatorAlwaysShown(UBool newValue) { + if (fields == nullptr) { return; } + if (UBOOL_TO_BOOL(newValue) == fields->properties->decimalSeparatorAlwaysShown) { return; } + fields->properties->decimalSeparatorAlwaysShown = newValue; + touchNoError(); } -//------------------------------------------------------------------------------ -void -DecimalFormat::applyLocalizedPattern(const UnicodeString& pattern, UErrorCode& status) -{ - UParseError parseError; - applyPattern(pattern, TRUE,parseError,status); +UBool DecimalFormat::isDecimalPatternMatchRequired(void) const { + // Not much we can do to report an error. + if (fields == nullptr) { + // Fallback to using the default instance of DecimalFormatProperties. + return DecimalFormatProperties::getDefault().decimalPatternMatchRequired; + } + return fields->properties->decimalPatternMatchRequired; } -//------------------------------------------------------------------------------ +void DecimalFormat::setDecimalPatternMatchRequired(UBool newValue) { + if (fields == nullptr) { return; } + if (UBOOL_TO_BOOL(newValue) == fields->properties->decimalPatternMatchRequired) { return; } + fields->properties->decimalPatternMatchRequired = newValue; + touchNoError(); +} -void -DecimalFormat::applyLocalizedPattern(const UnicodeString& pattern, - UParseError& parseError, - UErrorCode& status) -{ - applyPattern(pattern, TRUE,parseError,status); +UBool DecimalFormat::isParseNoExponent() const { + // Not much we can do to report an error. + if (fields == nullptr) { + // Fallback to using the default instance of DecimalFormatProperties. + return DecimalFormatProperties::getDefault().parseNoExponent; + } + return fields->properties->parseNoExponent; } -//------------------------------------------------------------------------------ +void DecimalFormat::setParseNoExponent(UBool value) { + if (fields == nullptr) { return; } + if (UBOOL_TO_BOOL(value) == fields->properties->parseNoExponent) { return; } + fields->properties->parseNoExponent = value; + touchNoError(); +} -void -DecimalFormat::applyPatternWithoutExpandAffix(const UnicodeString& pattern, - UBool localized, - UParseError& parseError, - UErrorCode& status) -{ - if (U_FAILURE(status)) - { - return; - } - DecimalFormatPatternParser patternParser; - if (localized) { - patternParser.useSymbols(*fSymbols); - } - fFormatPattern = pattern; - DecimalFormatPattern out; - patternParser.applyPatternWithoutExpandAffix( - pattern, - out, - parseError, - status); - if (U_FAILURE(status)) { - return; - } - - setMinimumIntegerDigits(out.fMinimumIntegerDigits); - setMaximumIntegerDigits(out.fMaximumIntegerDigits); - setMinimumFractionDigits(out.fMinimumFractionDigits); - setMaximumFractionDigits(out.fMaximumFractionDigits); - setSignificantDigitsUsed(out.fUseSignificantDigits); - if (out.fUseSignificantDigits) { - setMinimumSignificantDigits(out.fMinimumSignificantDigits); - setMaximumSignificantDigits(out.fMaximumSignificantDigits); - } - fUseExponentialNotation = out.fUseExponentialNotation; - if (out.fUseExponentialNotation) { - fMinExponentDigits = out.fMinExponentDigits; - } - fExponentSignAlwaysShown = out.fExponentSignAlwaysShown; - fCurrencySignCount = out.fCurrencySignCount; - setGroupingUsed(out.fGroupingUsed); - if (out.fGroupingUsed) { - fGroupingSize = out.fGroupingSize; - fGroupingSize2 = out.fGroupingSize2; - } - setMultiplier(out.fMultiplier); - fDecimalSeparatorAlwaysShown = out.fDecimalSeparatorAlwaysShown; - fFormatWidth = out.fFormatWidth; - if (out.fRoundingIncrementUsed) { - if (fRoundingIncrement != NULL) { - *fRoundingIncrement = out.fRoundingIncrement; - } else { - fRoundingIncrement = new DigitList(out.fRoundingIncrement); - /* test for NULL */ - if (fRoundingIncrement == NULL) { - status = U_MEMORY_ALLOCATION_ERROR; - return; - } - } - } else { - setRoundingIncrement(0.0); - } - fPad = out.fPad; - switch (out.fPadPosition) { - case DecimalFormatPattern::kPadBeforePrefix: - fPadPosition = kPadBeforePrefix; - break; - case DecimalFormatPattern::kPadAfterPrefix: - fPadPosition = kPadAfterPrefix; - break; - case DecimalFormatPattern::kPadBeforeSuffix: - fPadPosition = kPadBeforeSuffix; - break; - case DecimalFormatPattern::kPadAfterSuffix: - fPadPosition = kPadAfterSuffix; - break; +UBool DecimalFormat::isParseCaseSensitive() const { + // Not much we can do to report an error. + if (fields == nullptr) { + // Fallback to using the default instance of DecimalFormatProperties. + return DecimalFormatProperties::getDefault().parseCaseSensitive; } - copyString(out.fNegPrefixPattern, out.fNegPatternsBogus, fNegPrefixPattern, status); - copyString(out.fNegSuffixPattern, out.fNegPatternsBogus, fNegSuffixPattern, status); - copyString(out.fPosPrefixPattern, out.fPosPatternsBogus, fPosPrefixPattern, status); - copyString(out.fPosSuffixPattern, out.fPosPatternsBogus, fPosSuffixPattern, status); + return fields->properties->parseCaseSensitive; } +void DecimalFormat::setParseCaseSensitive(UBool value) { + if (fields == nullptr) { return; } + if (UBOOL_TO_BOOL(value) == fields->properties->parseCaseSensitive) { return; } + fields->properties->parseCaseSensitive = value; + touchNoError(); +} -void -DecimalFormat::expandAffixAdjustWidth(const UnicodeString* pluralCount) { - expandAffixes(pluralCount); - if (fFormatWidth > 0) { - // Finish computing format width (see above) - // TODO: how to handle fFormatWidth, - // need to save in f(Plural)AffixesForCurrecy? - fFormatWidth += fPositivePrefix.length() + fPositiveSuffix.length(); +UBool DecimalFormat::isFormatFailIfMoreThanMaxDigits() const { + // Not much we can do to report an error. + if (fields == nullptr) { + // Fallback to using the default instance of DecimalFormatProperties. + return DecimalFormatProperties::getDefault().formatFailIfMoreThanMaxDigits; } + return fields->properties->formatFailIfMoreThanMaxDigits; } +void DecimalFormat::setFormatFailIfMoreThanMaxDigits(UBool value) { + if (fields == nullptr) { return; } + if (UBOOL_TO_BOOL(value) == fields->properties->formatFailIfMoreThanMaxDigits) { return; } + fields->properties->formatFailIfMoreThanMaxDigits = value; + touchNoError(); +} -void -DecimalFormat::applyPattern(const UnicodeString& pattern, - UBool localized, - UParseError& parseError, - UErrorCode& status) -{ - // do the following re-set first. since they change private data by - // apply pattern again. - if (pattern.indexOf(kCurrencySign) != -1) { - if (fCurrencyPluralInfo == NULL) { - // initialize currencyPluralInfo if needed - fCurrencyPluralInfo = new CurrencyPluralInfo(fSymbols->getLocale(), status); - } - if (fAffixPatternsForCurrency == NULL) { - setupCurrencyAffixPatterns(status); - } - if (pattern.indexOf(fgTripleCurrencySign, 3, 0) != -1) { - // only setup the affixes of the current pattern. - setupCurrencyAffixes(pattern, TRUE, FALSE, status); - } +UnicodeString& DecimalFormat::toPattern(UnicodeString& result) const { + if (fields == nullptr) { + // We only get here if an OOM error happend during construction, copy construction, assignment, or modification. + result.setToBogus(); + return result; } - applyPatternWithoutExpandAffix(pattern, localized, parseError, status); - expandAffixAdjustWidth(NULL); -#if UCONFIG_FORMAT_FASTPATHS_49 - handleChanged(); -#endif + // 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(*fields->properties); + bool useCurrency = ( + !tprops.currency.isNull() || + !tprops.currencyPluralInfo.fPtr.isNull() || + !tprops.currencyUsage.isNull() || + AffixUtils::hasCurrencySymbols(tprops.positivePrefixPattern, localStatus) || + AffixUtils::hasCurrencySymbols(tprops.positiveSuffixPattern, localStatus) || + AffixUtils::hasCurrencySymbols(tprops.negativePrefixPattern, localStatus) || + AffixUtils::hasCurrencySymbols(tprops.negativeSuffixPattern, localStatus)); + if (useCurrency) { + tprops.minimumFractionDigits = fields->exportedProperties->minimumFractionDigits; + tprops.maximumFractionDigits = fields->exportedProperties->maximumFractionDigits; + tprops.roundingIncrement = fields->exportedProperties->roundingIncrement; + } + result = PatternStringUtils::propertiesToPatternString(tprops, localStatus); + return result; } +UnicodeString& DecimalFormat::toLocalizedPattern(UnicodeString& result) const { + if (fields == nullptr) { + // We only get here if an OOM error happend during construction, copy construction, assignment, or modification. + result.setToBogus(); + return result; + } + ErrorCode localStatus; + result = toPattern(result); + result = PatternStringUtils::convertLocalized(result, *fields->symbols, true, localStatus); + return result; +} -void -DecimalFormat::applyPatternInternally(const UnicodeString& pluralCount, - const UnicodeString& pattern, - UBool localized, - UParseError& parseError, - UErrorCode& status) { - applyPatternWithoutExpandAffix(pattern, localized, parseError, status); - expandAffixAdjustWidth(&pluralCount); -#if UCONFIG_FORMAT_FASTPATHS_49 - handleChanged(); -#endif +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) { + // don't overwrite status if it's already a failure. + if (U_FAILURE(status)) { return; } + if (fields == nullptr) { + // We only get here if an OOM error happend during construction, copy construction, assignment, or modification. + status = U_MEMORY_ALLOCATION_ERROR; + return; + } + setPropertiesFromPattern(pattern, IGNORE_ROUNDING_NEVER, status); + touch(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) { + // don't overwrite status if it's already a failure. + if (U_FAILURE(status)) { return; } + if (fields == nullptr) { + // We only get here if an OOM error happend during construction, copy construction, assignment, or modification. + status = U_MEMORY_ALLOCATION_ERROR; + return; + } + UnicodeString pattern = PatternStringUtils::convertLocalized( + localizedPattern, *fields->symbols, false, status); + applyPattern(pattern, status); +} -/** - * Sets the maximum number of digits allowed in the integer portion of a - * number. - * @see NumberFormat#setMaximumIntegerDigits - */ void DecimalFormat::setMaximumIntegerDigits(int32_t newValue) { - NumberFormat::setMaximumIntegerDigits(_min(newValue, gDefaultMaxIntegerDigits)); -#if UCONFIG_FORMAT_FASTPATHS_49 - handleChanged(); -#endif + if (fields == nullptr) { return; } + if (newValue == fields->properties->maximumIntegerDigits) { return; } + // For backwards compatibility, conflicting min/max need to keep the most recent setting. + int32_t min = fields->properties->minimumIntegerDigits; + if (min >= 0 && min > newValue) { + fields->properties->minimumIntegerDigits = newValue; + } + fields->properties->maximumIntegerDigits = newValue; + touchNoError(); } -/** - * 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) { - NumberFormat::setMinimumIntegerDigits(_min(newValue, kDoubleIntegerDigits)); -#if UCONFIG_FORMAT_FASTPATHS_49 - handleChanged(); -#endif + if (fields == nullptr) { return; } + if (newValue == fields->properties->minimumIntegerDigits) { return; } + // For backwards compatibility, conflicting min/max need to keep the most recent setting. + int32_t max = fields->properties->maximumIntegerDigits; + if (max >= 0 && max < newValue) { + fields->properties->maximumIntegerDigits = newValue; + } + fields->properties->minimumIntegerDigits = newValue; + touchNoError(); } -/** - * 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) { - NumberFormat::setMaximumFractionDigits(_min(newValue, kDoubleFractionDigits)); -#if UCONFIG_FORMAT_FASTPATHS_49 - handleChanged(); -#endif + if (fields == nullptr) { return; } + if (newValue == fields->properties->maximumFractionDigits) { return; } + // backward compatibility, limit to 340 + if (newValue > 340) { + newValue = 340; + } + // For backwards compatibility, conflicting min/max need to keep the most recent setting. + int32_t min = fields->properties->minimumFractionDigits; + if (min >= 0 && min > newValue) { + fields->properties->minimumFractionDigits = newValue; + } + fields->properties->maximumFractionDigits = newValue; + touchNoError(); } -/** - * 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) { - NumberFormat::setMinimumFractionDigits(_min(newValue, kDoubleFractionDigits)); -#if UCONFIG_FORMAT_FASTPATHS_49 - handleChanged(); -#endif + if (fields == nullptr) { return; } + if (newValue == fields->properties->minimumFractionDigits) { return; } + // For backwards compatibility, conflicting min/max need to keep the most recent setting. + int32_t max = fields->properties->maximumFractionDigits; + if (max >= 0 && max < newValue) { + fields->properties->maximumFractionDigits = newValue; + } + fields->properties->minimumFractionDigits = newValue; + touchNoError(); } int32_t DecimalFormat::getMinimumSignificantDigits() const { - return fMinSignificantDigits; + // Not much we can do to report an error. + if (fields == nullptr) { + // Fallback to using the default instance of DecimalFormatProperties. + return DecimalFormatProperties::getDefault().minimumSignificantDigits; + } + return fields->exportedProperties->minimumSignificantDigits; } int32_t DecimalFormat::getMaximumSignificantDigits() const { - return fMaxSignificantDigits; + // Not much we can do to report an error. + if (fields == nullptr) { + // Fallback to using the default instance of DecimalFormatProperties. + return DecimalFormatProperties::getDefault().maximumSignificantDigits; + } + return fields->exportedProperties->maximumSignificantDigits; } -void DecimalFormat::setMinimumSignificantDigits(int32_t min) { - if (min < 1) { - min = 1; - } - // pin max sig dig to >= min - int32_t max = _max(fMaxSignificantDigits, min); - fMinSignificantDigits = min; - fMaxSignificantDigits = max; - fUseSignificantDigits = TRUE; -#if UCONFIG_FORMAT_FASTPATHS_49 - handleChanged(); -#endif +void DecimalFormat::setMinimumSignificantDigits(int32_t value) { + if (fields == nullptr) { return; } + if (value == fields->properties->minimumSignificantDigits) { return; } + int32_t max = fields->properties->maximumSignificantDigits; + if (max >= 0 && max < value) { + fields->properties->maximumSignificantDigits = value; + } + fields->properties->minimumSignificantDigits = value; + touchNoError(); } -void DecimalFormat::setMaximumSignificantDigits(int32_t max) { - if (max < 1) { - max = 1; - } - // pin min sig dig to 1..max - U_ASSERT(fMinSignificantDigits >= 1); - int32_t min = _min(fMinSignificantDigits, max); - fMinSignificantDigits = min; - fMaxSignificantDigits = max; - fUseSignificantDigits = TRUE; -#if UCONFIG_FORMAT_FASTPATHS_49 - handleChanged(); -#endif +void DecimalFormat::setMaximumSignificantDigits(int32_t value) { + if (fields == nullptr) { return; } + if (value == fields->properties->maximumSignificantDigits) { return; } + int32_t min = fields->properties->minimumSignificantDigits; + if (min >= 0 && min > value) { + fields->properties->minimumSignificantDigits = value; + } + fields->properties->maximumSignificantDigits = value; + touchNoError(); } UBool DecimalFormat::areSignificantDigitsUsed() const { - return fUseSignificantDigits; + const DecimalFormatProperties* dfp; + // Not much we can do to report an error. + if (fields == nullptr) { + // Fallback to using the default instance of DecimalFormatProperties. + dfp = &(DecimalFormatProperties::getDefault()); + } else { + dfp = fields->properties.getAlias(); + } + return dfp->minimumSignificantDigits != -1 || dfp->maximumSignificantDigits != -1; } void DecimalFormat::setSignificantDigitsUsed(UBool useSignificantDigits) { - fUseSignificantDigits = useSignificantDigits; -#if UCONFIG_FORMAT_FASTPATHS_49 - handleChanged(); -#endif + if (fields == nullptr) { return; } + + // These are the default values from the old implementation. + if (useSignificantDigits) { + if (fields->properties->minimumSignificantDigits != -1 || + fields->properties->maximumSignificantDigits != -1) { + return; + } + } else { + if (fields->properties->minimumSignificantDigits == -1 && + fields->properties->maximumSignificantDigits == -1) { + return; + } + } + int32_t minSig = useSignificantDigits ? 1 : -1; + int32_t maxSig = useSignificantDigits ? 6 : -1; + fields->properties->minimumSignificantDigits = minSig; + fields->properties->maximumSignificantDigits = maxSig; + touchNoError(); } -void DecimalFormat::setCurrencyInternally(const UChar* theCurrency, - UErrorCode& ec) { - // If we are a currency format, then modify our affixes to - // encode the currency symbol for the given currency in our - // locale, and adjust the decimal digits and rounding for the - // given currency. +// Group-set several settings used for numbers in date formats. Apple rdar://50064762 +// Equivalent to: +// setGroupingUsed(FALSE); +// setDecimalSeparatorAlwaysShown(FALSE); +// setParseIntegerOnly(TRUE); +// setMinimumFractionDigits(0); +void DecimalFormat::setDateSettings(void) { + if (fields == nullptr) { + return; + } + UBool didChange = FALSE; - // Note: The code is ordered so that this object is *not changed* - // until we are sure we are going to succeed. + if (fields->properties->groupingUsed) { + NumberFormat::setGroupingUsed(FALSE); // to set field for compatibility + fields->properties->groupingUsed = false; + didChange = TRUE; + } - // NULL or empty currency is *legal* and indicates no currency. - UBool isCurr = (theCurrency && *theCurrency); + if (fields->properties->decimalSeparatorAlwaysShown) { + fields->properties->decimalSeparatorAlwaysShown = false; + didChange = TRUE; + } - double rounding = 0.0; - int32_t frac = 0; - if (fCurrencySignCount != fgCurrencySignCountZero && isCurr) { - rounding = ucurr_getRoundingIncrement(theCurrency, &ec); - frac = ucurr_getDefaultFractionDigits(theCurrency, &ec); + if (!fields->properties->parseIntegerOnly) { + NumberFormat::setParseIntegerOnly(TRUE); // to set field for compatibility + fields->properties->parseIntegerOnly = true; + didChange = TRUE; } - NumberFormat::setCurrency(theCurrency, ec); - if (U_FAILURE(ec)) return; + if (fields->properties->minimumFractionDigits != 0) { + fields->properties->minimumFractionDigits = 0; + didChange = TRUE; + } - if (fCurrencySignCount != fgCurrencySignCountZero) { - // NULL or empty currency is *legal* and indicates no currency. - if (isCurr) { - setRoundingIncrement(rounding); - setMinimumFractionDigits(frac); - setMaximumFractionDigits(frac); - } - expandAffixes(NULL); + if (didChange) { + touchNoError(); } -#if UCONFIG_FORMAT_FASTPATHS_49 - handleChanged(); -#endif } -void DecimalFormat::setCurrency(const UChar* theCurrency, UErrorCode& ec) { - // set the currency before compute affixes to get the right currency names - NumberFormat::setCurrency(theCurrency, ec); - if (fFormatPattern.indexOf(fgTripleCurrencySign, 3, 0) != -1) { - UnicodeString savedPtn = fFormatPattern; - setupCurrencyAffixes(fFormatPattern, TRUE, TRUE, ec); - UParseError parseErr; - applyPattern(savedPtn, FALSE, parseErr, ec); - } - // set the currency after apply pattern to get the correct rounding/fraction - setCurrencyInternally(theCurrency, ec); -#if UCONFIG_FORMAT_FASTPATHS_49 - handleChanged(); -#endif +void DecimalFormat::setCurrency(const char16_t* theCurrency, UErrorCode& ec) { + // don't overwrite ec if it's already a failure. + if (U_FAILURE(ec)) { return; } + if (fields == nullptr) { + // We only get here if an OOM error happend during construction, copy construction, assignment, or modification. + ec = U_MEMORY_ALLOCATION_ERROR; + return; + } + // Restore behavior in which empty currency sets locale default + UChar localeCurr[4]; + if (theCurrency==nullptr || theCurrency[0]==0) { + UErrorCode getCurrStatus = U_ZERO_ERROR; + int32_t currLen = ucurr_forLocale(fields->symbols->getLocale().getName(), localeCurr, UPRV_LENGTHOF(localeCurr), &getCurrStatus); + if (U_SUCCESS(getCurrStatus) && currLen==3) { + localeCurr[3] = 0; + theCurrency = localeCurr; + } + } + // + CurrencyUnit currencyUnit(theCurrency, ec); + if (U_FAILURE(ec)) { return; } + if (!fields->properties->currency.isNull() && fields->properties->currency.getNoError() == currencyUnit) { + return; + } + NumberFormat::setCurrency(theCurrency, ec); // to set field for compatibility + fields->properties->currency = currencyUnit; + // TODO: Set values in fields->symbols, too? + touchNoError(); } -// Deprecated variant with no UErrorCode parameter -void DecimalFormat::setCurrency(const UChar* theCurrency) { - UErrorCode ec = U_ZERO_ERROR; - setCurrency(theCurrency, ec); -#if UCONFIG_FORMAT_FASTPATHS_49 - handleChanged(); -#endif +void DecimalFormat::setCurrency(const char16_t* theCurrency) { + ErrorCode localStatus; + setCurrency(theCurrency, localStatus); } -void DecimalFormat::getEffectiveCurrency(UChar* result, UErrorCode& ec) const { - if (fSymbols == NULL) { - ec = U_MEMORY_ALLOCATION_ERROR; +void DecimalFormat::setCurrencyUsage(UCurrencyUsage newUsage, UErrorCode* ec) { + // don't overwrite ec if it's already a failure. + if (U_FAILURE(*ec)) { return; } + if (fields == nullptr) { + // We only get here if an OOM error happend during construction, copy construction, assignment, or modification. + *ec = U_MEMORY_ALLOCATION_ERROR; return; } - ec = U_ZERO_ERROR; - const UChar* c = getCurrency(); - if (*c == 0) { - const UnicodeString &intl = - fSymbols->getConstSymbol(DecimalFormatSymbols::kIntlCurrencySymbol); - c = intl.getBuffer(); // ok for intl to go out of scope + if (!fields->properties->currencyUsage.isNull() && newUsage == fields->properties->currencyUsage.getNoError()) { + return; } - u_strncpy(result, c, 3); - result[3] = 0; + fields->properties->currencyUsage = newUsage; + touch(*ec); } -/** - * Return the number of fraction digits to display, or the total - * number of digits for significant digit formats and exponential - * formats. - */ -int32_t -DecimalFormat::precision() const { - if (areSignificantDigitsUsed()) { - return getMaximumSignificantDigits(); - } else if (fUseExponentialNotation) { - return getMinimumIntegerDigits() + getMaximumFractionDigits(); - } else { - return getMaximumFractionDigits(); +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 (fields == nullptr || fields->properties->currencyUsage.isNull()) { + return UCURR_USAGE_STANDARD; } + return fields->properties->currencyUsage.getNoError(); } +void +DecimalFormat::formatToDecimalQuantity(double number, DecimalQuantity& output, UErrorCode& status) const { + // don't overwrite status if it's already a failure. + if (U_FAILURE(status)) { return; } + if (fields == nullptr) { + // We only get here if an OOM error happend during construction, copy construction, assignment, or modification. + status = U_MEMORY_ALLOCATION_ERROR; + return; + } + fields->formatter->formatDouble(number, status).getDecimalQuantity(output, status); +} -// TODO: template algorithm -Hashtable* -DecimalFormat::initHashForAffix(UErrorCode& status) { - if ( U_FAILURE(status) ) { - return NULL; +void DecimalFormat::formatToDecimalQuantity(const Formattable& number, DecimalQuantity& output, + UErrorCode& status) const { + // don't overwrite status if it's already a failure. + if (U_FAILURE(status)) { return; } + if (fields == nullptr) { + // We only get here if an OOM error happend during construction, copy construction, assignment, or modification. + status = U_MEMORY_ALLOCATION_ERROR; + return; } - Hashtable* hTable; - if ( (hTable = new Hashtable(TRUE, status)) == NULL ) { + UFormattedNumberData obj; + number.populateDecimalQuantity(obj.quantity, status); + fields->formatter->formatImpl(&obj, status); + output = std::move(obj.quantity); +} + +const number::LocalizedNumberFormatter* DecimalFormat::toNumberFormatter(UErrorCode& status) const { + // We sometimes need to return nullptr here (see ICU-20380) + if (U_FAILURE(status)) { return nullptr; } + if (fields == nullptr) { + // We only get here if an OOM error happend during construction, copy construction, assignment, or modification. status = U_MEMORY_ALLOCATION_ERROR; - return NULL; + return nullptr; } - if ( U_FAILURE(status) ) { - delete hTable; - return NULL; + return &*fields->formatter; +} + +const number::LocalizedNumberFormatter& DecimalFormat::toNumberFormatter() const { + UErrorCode localStatus = U_ZERO_ERROR; + return *toNumberFormatter(localStatus); +} + +// Apple +void DecimalFormat::setDFSShallowCopy(UBool shallow) { + if (fields != nullptr && fields->formatter != nullptr) { + fields->formatter->setDFSShallowCopy(shallow); } - hTable->setValueComparator(decimfmtAffixValueComparator); - return hTable; } -Hashtable* -DecimalFormat::initHashForAffixPattern(UErrorCode& status) { - if ( U_FAILURE(status) ) { - return NULL; +/** Rebuilds the formatter object from the property bag. */ +void DecimalFormat::touch(UErrorCode& status) { + if (U_FAILURE(status)) { + return; } - Hashtable* hTable; - if ( (hTable = new Hashtable(TRUE, status)) == NULL ) { + if (fields == nullptr) { + // We only get here if an OOM error happend during construction, copy construction, assignment, or modification. + // For regular construction, the caller should have checked the status variable for errors. + // For copy construction, there is unfortunately nothing to report the error, so we need to guard against + // this possible bad state here and set the status to an error. status = U_MEMORY_ALLOCATION_ERROR; - return NULL; + return; } - if ( U_FAILURE(status) ) { - delete hTable; - return NULL; + + // In C++, fields->symbols is the source of truth for the locale. + Locale locale = fields->symbols->getLocale(); + + // Note: The formatter is relatively cheap to create, and we need it to populate fields->exportedProperties, + // so automatically recompute 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? + + // Since memory has already been allocated for the formatter, we can move assign a stack-allocated object + // and don't need to call new. (Which is slower and could possibly fail). + *fields->formatter = NumberPropertyMapper::create( + *fields->properties, *fields->symbols, fields->warehouse, *fields->exportedProperties, status).locale( + locale); + + // Do this after fields->exportedProperties are set up + setupFastFormat(); + + // Delete the parsers if they were made previously + delete fields->atomicParser.exchange(nullptr); + delete fields->atomicCurrencyParser.exchange(nullptr); + + // In order for the getters to work, we need to populate some fields in NumberFormat. + const UChar* newCurr = u""; + CurrencyUnit currency = fields->exportedProperties->currency.get(status); + if (U_SUCCESS(status)) { + // currency.getISOCurrency() is an inline that just returns a pointer to currency's + // internal field char16_t isoCode[4], cannot be NULL if currency is valid: + newCurr = (const UChar*)currency.getISOCurrency(); + // NumberFormat::getCurrency() just returns a pointer to the superclass's + // internal field char16_t fCurrency[4], cannot be NULL: + const UChar* haveCurr = (const UChar*)NumberFormat::getCurrency(); + if (u_strcmp(newCurr,u"XXX")==0 && u_strcmp(haveCurr,u"XXX")!=0) { // + // We did not get here via DecimalFormat::setCurrency(u"XXX", ...) + newCurr = u""; + } } - hTable->setValueComparator(decimfmtAffixPatternValueComparator); - return hTable; + NumberFormat::setCurrency(newCurr, status); + NumberFormat::setMaximumIntegerDigits(fields->exportedProperties->maximumIntegerDigits); + NumberFormat::setMinimumIntegerDigits(fields->exportedProperties->minimumIntegerDigits); + NumberFormat::setMaximumFractionDigits(fields->exportedProperties->maximumFractionDigits); + NumberFormat::setMinimumFractionDigits(fields->exportedProperties->minimumFractionDigits); + // fImpl->properties, not fields->exportedProperties, since this information comes from the pattern: + NumberFormat::setGroupingUsed(fields->properties->groupingUsed); } -void -DecimalFormat::deleteHashForAffix(Hashtable*& table) -{ - if ( table == NULL ) { - return; - } - int32_t pos = -1; - const UHashElement* element = NULL; - while ( (element = table->nextElement(pos)) != NULL ) { - const UHashTok valueTok = element->value; - const AffixesForCurrency* value = (AffixesForCurrency*)valueTok.pointer; - delete value; +void DecimalFormat::touchNoError() { + UErrorCode localStatus = U_ZERO_ERROR; + touch(localStatus); +} + +void DecimalFormat::setPropertiesFromPattern(const UnicodeString& pattern, int32_t ignoreRounding, + UErrorCode& status) { + if (U_SUCCESS(status)) { + // Cast workaround to get around putting the enum in the public header file + auto actualIgnoreRounding = static_cast(ignoreRounding); + PatternParser::parseToExistingProperties(pattern, *fields->properties, actualIgnoreRounding, status); } - delete table; - table = NULL; } +const numparse::impl::NumberParserImpl* DecimalFormat::getParser(UErrorCode& status) const { + // TODO: Move this into umutex.h? (similar logic also in numrange_fluent.cpp) + // See ICU-20146 + + if (U_FAILURE(status)) { + return nullptr; + } + // First try to get the pre-computed parser + auto* ptr = fields->atomicParser.load(); + if (ptr != nullptr) { + return ptr; + } -void -DecimalFormat::deleteHashForAffixPattern() -{ - if ( fAffixPatternsForCurrency == NULL ) { - return; + // Try computing the parser on our own + auto* temp = NumberParserImpl::createParserFromProperties(*fields->properties, *fields->symbols, false, status); + if (U_FAILURE(status)) { + return nullptr; + } + if (temp == nullptr) { + status = U_MEMORY_ALLOCATION_ERROR; + return nullptr; } - int32_t pos = -1; - const UHashElement* element = NULL; - while ( (element = fAffixPatternsForCurrency->nextElement(pos)) != NULL ) { - const UHashTok valueTok = element->value; - const AffixPatternsForCurrency* value = (AffixPatternsForCurrency*)valueTok.pointer; - delete value; + + // Note: ptr starts as nullptr; during compare_exchange, + // it is set to what is actually stored in the atomic + // if another thread beat us to computing the parser object. + auto* nonConstThis = const_cast(this); + if (!nonConstThis->fields->atomicParser.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; } - delete fAffixPatternsForCurrency; - fAffixPatternsForCurrency = NULL; } +const numparse::impl::NumberParserImpl* DecimalFormat::getCurrencyParser(UErrorCode& status) const { + if (U_FAILURE(status)) { return nullptr; } -void -DecimalFormat::copyHashForAffixPattern(const Hashtable* source, - Hashtable* target, - UErrorCode& status) { - if ( U_FAILURE(status) ) { - return; + // First try to get the pre-computed parser + auto* ptr = fields->atomicCurrencyParser.load(); + if (ptr != nullptr) { + return ptr; } - int32_t pos = -1; - 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; - } - } + + // Try computing the parser on our own + auto* temp = NumberParserImpl::createParserFromProperties(*fields->properties, *fields->symbols, true, status); + if (temp == nullptr) { + status = U_MEMORY_ALLOCATION_ERROR; + // although we may still dereference, call sites should be guarded } -} -// this is only overridden to call handleChanged() for fastpath purposes. -void -DecimalFormat::setGroupingUsed(UBool newValue) { - NumberFormat::setGroupingUsed(newValue); - handleChanged(); + // Note: ptr starts as nullptr; during compare_exchange, it is set to what is actually stored in the + // atomic if another thread beat us to computing the parser object. + auto* nonConstThis = const_cast(this); + if (!nonConstThis->fields->atomicCurrencyParser.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; + } } -// this is only overridden to call handleChanged() for fastpath purposes. void -DecimalFormat::setParseIntegerOnly(UBool newValue) { - NumberFormat::setParseIntegerOnly(newValue); - handleChanged(); +DecimalFormat::fieldPositionHelper(const number::FormattedNumber& formatted, FieldPosition& fieldPosition, + int32_t offset, UErrorCode& status) { + if (U_FAILURE(status)) { return; } + // always return first occurrence: + fieldPosition.setBeginIndex(0); + fieldPosition.setEndIndex(0); + bool found = formatted.nextFieldPosition(fieldPosition, status); + if (found && offset != 0) { + FieldPositionOnlyHandler fpoh(fieldPosition); + fpoh.shiftLast(offset); + } } -// this is only overridden to call handleChanged() for fastpath purposes. -// setContext doesn't affect the fastPath right now, but this is called for completeness void -DecimalFormat::setContext(UDisplayContext value, UErrorCode& status) { - NumberFormat::setContext(value, status); - handleChanged(); +DecimalFormat::fieldPositionIteratorHelper(const number::FormattedNumber& formatted, FieldPositionIterator* fpi, + int32_t offset, UErrorCode& status) { + if (U_SUCCESS(status) && (fpi != nullptr)) { + FieldPositionIteratorHandler fpih(fpi, status); + fpih.setShift(offset); + formatted.getAllFieldPositionsImpl(fpih, status); + } } +// To debug fast-format, change void(x) to printf(x) +#define trace(x) void(x) -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; +void DecimalFormat::setupFastFormat() { + // Check the majority of properties: + if (!fields->properties->equalsDefaultExceptFastFormat()) { + trace("no fast format: equality\n"); + fields->canUseFastFormat = false; + return; + } -#if UCONFIG_HAVE_PARSEALLINPUT - case UNUM_PARSE_ALL_INPUT: - setParseAllInput((UNumberFormatAttributeValue)newValue); - break; -#endif + // Now check the remaining properties. + // Nontrivial affixes: + UBool trivialPP = fields->properties->positivePrefixPattern.isEmpty(); + UBool trivialPS = fields->properties->positiveSuffixPattern.isEmpty(); + UBool trivialNP = fields->properties->negativePrefixPattern.isBogus() || ( + fields->properties->negativePrefixPattern.length() == 1 && + fields->properties->negativePrefixPattern.charAt(0) == u'-'); + UBool trivialNS = fields->properties->negativeSuffixPattern.isEmpty(); + if (!trivialPP || !trivialPS || !trivialNP || !trivialNS) { + trace("no fast format: affixes\n"); + fields->canUseFastFormat = false; + return; + } + + // Grouping (secondary grouping is forbidden in equalsDefaultExceptFastFormat): + bool groupingUsed = fields->properties->groupingUsed; + int32_t groupingSize = fields->properties->groupingSize; + bool unusualGroupingSize = groupingSize > 0 && groupingSize != 3; + const UnicodeString& groupingString = fields->symbols->getConstSymbol(DecimalFormatSymbols::kGroupingSeparatorSymbol); + if (groupingUsed && (unusualGroupingSize || groupingString.length() != 1)) { + trace("no fast format: grouping\n"); + fields->canUseFastFormat = false; + return; + } + + // Integer length: + int32_t minInt = fields->exportedProperties->minimumIntegerDigits; + int32_t maxInt = fields->exportedProperties->maximumIntegerDigits; + // Fastpath supports up to only 10 digits (length of INT32_MIN) + if (minInt > 10) { + trace("no fast format: integer\n"); + fields->canUseFastFormat = false; + return; + } + + // Fraction length (no fraction part allowed in fast path): + int32_t minFrac = fields->exportedProperties->minimumFractionDigits; + if (minFrac > 0) { + trace("no fast format: fraction\n"); + fields->canUseFastFormat = false; + return; + } - /* These are stored in fBoolFlags */ - case UNUM_PARSE_NO_EXPONENT: - case UNUM_FORMAT_FAIL_IF_MORE_THAN_MAX_DIGITS: - if(!fBoolFlags.isValidValue(newValue)) { - status = U_ILLEGAL_ARGUMENT_ERROR; - } else { - fBoolFlags.set(attr, newValue); - } - break; - - case UNUM_SCALE: - fScale = newValue; - break; - - default: - status = U_UNSUPPORTED_ERROR; - break; - } - return *this; + // Other symbols: + const UnicodeString& minusSignString = fields->symbols->getConstSymbol(DecimalFormatSymbols::kMinusSignSymbol); + UChar32 codePointZero = fields->symbols->getCodePointZero(); + if (minusSignString.length() != 1 || U16_LENGTH(codePointZero) != 1) { + trace("no fast format: symbols\n"); + fields->canUseFastFormat = false; + return; + } + + // Good to go! + trace("can use fast format!\n"); + fields->canUseFastFormat = true; + fields->fastData.cpZero = static_cast(codePointZero); + fields->fastData.cpGroupingSeparator = groupingUsed && groupingSize == 3 ? groupingString.charAt(0) : 0; + fields->fastData.cpMinusSign = minusSignString.charAt(0); + fields->fastData.minInt = (minInt < 0 || minInt > 127) ? 0 : static_cast(minInt); + fields->fastData.maxInt = (maxInt < 0 || maxInt > 127) ? 127 : static_cast(maxInt); } -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: - return fBoolFlags.get(attr); - - case UNUM_SCALE: - return fScale; - - default: - status = U_UNSUPPORTED_ERROR; - break; - } - - return -1; /* undefined */ +bool DecimalFormat::fastFormatDouble(double input, UnicodeString& output) const { + if (!fields->canUseFastFormat) { + return false; + } + if (std::isnan(input) + || std::trunc(input) != input + || input <= INT32_MIN + || input > INT32_MAX) { + return false; + } + doFastFormatInt32(static_cast(input), std::signbit(input), output); + return true; } -#if UCONFIG_HAVE_PARSEALLINPUT -void DecimalFormat::setParseAllInput(UNumberFormatAttributeValue value) { - fParseAllInput = value; -#if UCONFIG_FORMAT_FASTPATHS_49 - handleChanged(); -#endif +bool DecimalFormat::fastFormatInt64(int64_t input, UnicodeString& output) const { + if (!fields->canUseFastFormat) { + return false; + } + if (input <= INT32_MIN || input > INT32_MAX) { + return false; + } + doFastFormatInt32(static_cast(input), input < 0, output); + return true; } -#endif -void -DecimalFormat::copyHashForAffix(const Hashtable* source, - Hashtable* target, - UErrorCode& status) { - if ( U_FAILURE(status) ) { - return; +void DecimalFormat::doFastFormatInt32(int32_t input, bool isNegative, UnicodeString& output) const { + U_ASSERT(fields->canUseFastFormat); + if (isNegative) { + output.append(fields->fastData.cpMinusSign); + U_ASSERT(input != INT32_MIN); // handled by callers + input = -input; } - int32_t pos = -1; - 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 AffixesForCurrency* value = (AffixesForCurrency*)valueTok.pointer; - AffixesForCurrency* copy = new AffixesForCurrency( - value->negPrefixForCurrency, - value->negSuffixForCurrency, - value->posPrefixForCurrency, - value->posSuffixForCurrency); - target->put(UnicodeString(*key), copy, status); - if ( U_FAILURE(status) ) { - return; - } + // 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; + int8_t minInt = (fields->fastData.minInt < 1)? 1: fields->fastData.minInt; // rdar://54569257 + for (int8_t i = 0; i < fields->fastData.maxInt && (input != 0 || i < minInt); i++) { + if (group++ == 3 && fields->fastData.cpGroupingSeparator != 0) { + *(--ptr) = fields->fastData.cpGroupingSeparator; + group = 1; } + std::div_t res = std::div(input, 10); + *(--ptr) = static_cast(fields->fastData.cpZero + res.rem); + input = res.quot; } + int32_t len = localCapacity - static_cast(ptr - localBuffer); + output.append(ptr, len); } -U_NAMESPACE_END #endif /* #if !UCONFIG_NO_FORMATTING */ - -//eof