From 161183009ae61a8b60d92b4203db21bcd769e15f Mon Sep 17 00:00:00 2001 From: Travis Keep Date: Wed, 14 Nov 2012 18:49:38 +0000 Subject: [PATCH] ICU-9543 C++ CompactDecimalFormat moved to trunk. X-SVN-Rev: 32819 --- icu4c/source/i18n/Makefile.in | 2 +- icu4c/source/i18n/compactdecimalformat.cpp | 964 ++++++++++++++++++ icu4c/source/i18n/decimfmt.cpp | 122 ++- icu4c/source/i18n/numfmt.cpp | 61 +- icu4c/source/i18n/ucln_in.h | 1 + .../i18n/unicode/compactdecimalformat.h | 330 ++++++ icu4c/source/i18n/unicode/decimfmt.h | 6 + icu4c/source/i18n/unicode/numfmt.h | 14 + icu4c/source/i18n/unicode/unum.h | 12 + icu4c/source/test/intltest/Makefile.in | 2 +- .../intltest/compactdecimalformattest.cpp | 342 +++++++ icu4c/source/test/intltest/intltest.vcxproj | 1 + icu4c/source/test/intltest/itformat.cpp | 10 + 13 files changed, 1794 insertions(+), 73 deletions(-) create mode 100644 icu4c/source/i18n/compactdecimalformat.cpp create mode 100644 icu4c/source/i18n/unicode/compactdecimalformat.h create mode 100644 icu4c/source/test/intltest/compactdecimalformattest.cpp diff --git a/icu4c/source/i18n/Makefile.in b/icu4c/source/i18n/Makefile.in index 6ea83dce865..e2132cc7ad9 100644 --- a/icu4c/source/i18n/Makefile.in +++ b/icu4c/source/i18n/Makefile.in @@ -86,7 +86,7 @@ tmunit.o tmutamt.o tmutfmt.o colldata.o bmsearch.o bms.o currpinf.o \ uspoof.o uspoof_impl.o uspoof_build.o uspoof_conf.o uspoof_wsconf.o decfmtst.o smpdtfst.o \ ztrans.o zrule.o vzone.o fphdlimp.o fpositer.o locdspnm.o \ decNumber.o decContext.o alphaindex.o tznames.o tznames_impl.o tzgnames.o \ -tzfmt.o gender.o +tzfmt.o compactdecimalformat.o gender.o ## Header files to install HEADERS = $(srcdir)/unicode/*.h diff --git a/icu4c/source/i18n/compactdecimalformat.cpp b/icu4c/source/i18n/compactdecimalformat.cpp new file mode 100644 index 00000000000..80abfdcf6ef --- /dev/null +++ b/icu4c/source/i18n/compactdecimalformat.cpp @@ -0,0 +1,964 @@ +/* +******************************************************************************* +* Copyright (C) 1997-2012, International Business Machines Corporation and * +* others. All Rights Reserved. * +******************************************************************************* +* +* File COMPACTDECIMALFORMAT.CPP +* +******************************************************************************** +*/ +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +#include "charstr.h" +#include "cstring.h" +#include "digitlst.h" +#include "mutex.h" +#include "unicode/compactdecimalformat.h" +#include "unicode/numsys.h" +#include "unicode/plurrule.h" +#include "unicode/ures.h" +#include "ucln_in.h" +#include "uhash.h" +#include "umutex.h" +#include "unicode/ures.h" +#include "uresimp.h" + +#define LENGTHOF(array) (int32_t)(sizeof(array) / sizeof((array)[0])) + +// Maps locale name to CDFLocaleData struct. +static UHashtable* gCompactDecimalData = NULL; +static UMutex gCompactDecimalMetaLock = U_MUTEX_INITIALIZER; + +U_NAMESPACE_BEGIN + +static const int32_t MAX_DIGITS = 15; +static const char gOther[] = "other"; +static const char gLatnTag[] = "latn"; +static const char gNumberElementsTag[] = "NumberElements"; +static const char gDecimalFormatTag[] = "decimalFormat"; +static const char gPatternsShort[] = "patternsShort"; +static const char gPatternsLong[] = "patternsLong"; +static const char gRoot[] = "root"; + +static const UChar u_0 = 0x30; +static const UChar u_apos = 0x27; + +static const UChar kZero[] = {u_0}; + +// Used to unescape single quotes. +enum QuoteState { + OUTSIDE, + INSIDE_EMPTY, + INSIDE_FULL +}; + +enum DataLocation { + LOCAL_LOC, + LATIN_LOC, + ROOT_LOC +}; + +enum FallbackFlags { + ANY = 0, + MUST = 1, + NOT_ROOT = 2 + // Next one will be 4 then 6 etc. +}; + + +// CDFUnit represents a prefix-suffix pair for a particular variant +// and log10 value. +struct CDFUnit : public UMemory { + UnicodeString prefix; + UnicodeString suffix; + inline CDFUnit() : prefix(), suffix() { + prefix.setToBogus(); + } + inline ~CDFUnit() {} + inline UBool isSet() const { + return !prefix.isBogus(); + } + inline void markAsSet() { + prefix.remove(); + } +}; + +// CDFLocaleStyleData contains formatting data for a particular locale +// and style. +class CDFLocaleStyleData : public UMemory { + public: + // What to divide by for each log10 value when formatting. These values + // will be powers of 10. For English, would be: + // 1, 1, 1, 1000, 1000, 1000, 1000000, 1000000, 1000000, 1000000000 ... + double divisors[MAX_DIGITS]; + // Maps plural variants to CDFUnit[MAX_DIGITS] arrays. + // To format a number x, + // first compute log10(x). Compute displayNum = (x / divisors[log10(x)]). + // Compute the plural variant for displayNum + // (e.g zero, one, two, few, many, other). + // Compute cdfUnits = unitsByVariant[pluralVariant]. + // Prefix and suffix to use at cdfUnits[log10(x)] + UHashtable* unitsByVariant; + inline CDFLocaleStyleData() : unitsByVariant(NULL) {} + ~CDFLocaleStyleData(); + // Init initializes this object. + void Init(UErrorCode& status); + inline UBool isBogus() const { + return unitsByVariant == NULL; + } + void setToBogus(); + private: + CDFLocaleStyleData(const CDFLocaleStyleData&); + CDFLocaleStyleData& operator=(const CDFLocaleStyleData&); +}; + +// CDFLocaleData contains formatting data for a particular locale. +struct CDFLocaleData : public UMemory { + CDFLocaleStyleData shortData; + CDFLocaleStyleData longData; + inline CDFLocaleData() : shortData(), longData() { } + inline ~CDFLocaleData() { } + // Init initializes this object. + void Init(UErrorCode& status); +}; + +U_NAMESPACE_END + +U_CDECL_BEGIN + +static UBool U_CALLCONV cdf_cleanup(void) { + if (gCompactDecimalData != NULL) { + uhash_close(gCompactDecimalData); + gCompactDecimalData = NULL; + } + return TRUE; +} + +static void U_CALLCONV deleteCDFUnits(void* ptr) { + delete [] (icu::CDFUnit*) ptr; +} + +static void U_CALLCONV deleteCDFLocaleData(void* ptr) { + delete (icu::CDFLocaleData*) ptr; +} + +U_CDECL_END + +U_NAMESPACE_BEGIN + +static UBool divisors_equal(const double* lhs, const double* rhs); +static const CDFLocaleStyleData* getCDFLocaleStyleData(const Locale& inLocale, UNumberCompactStyle style, UErrorCode& status); + +static const CDFLocaleStyleData* extractDataByStyleEnum(const CDFLocaleData& data, UNumberCompactStyle style, UErrorCode& status); +static CDFLocaleData* loadCDFLocaleData(const Locale& inLocale, UErrorCode& status); +static void initCDFLocaleData(const Locale& inLocale, CDFLocaleData* result, UErrorCode& status); +static UResourceBundle* tryGetDecimalFallback(const UResourceBundle* numberSystemResource, const char* style, UResourceBundle** fillIn, FallbackFlags flags, UErrorCode& status); +static UResourceBundle* tryGetByKeyWithFallback(const UResourceBundle* rb, const char* path, UResourceBundle** fillIn, FallbackFlags flags, UErrorCode& status); +static UBool isRoot(const UResourceBundle* rb, UErrorCode& status); +static void initCDFLocaleStyleData(const UResourceBundle* decimalFormatBundle, CDFLocaleStyleData* result, UErrorCode& status); +static void populatePower10(const UResourceBundle* power10Bundle, CDFLocaleStyleData* result, UErrorCode& status); +static int32_t populatePrefixSuffix(const char* variant, int32_t log10Value, const UnicodeString& formatStr, UHashtable* result, UErrorCode& status); +static UBool onlySpaces(UnicodeString u); +static void fixQuotes(UnicodeString& s); +static void fillInMissing(CDFLocaleStyleData* result); +static int32_t computeLog10(double x, UBool inRange); +static CDFUnit* createCDFUnit(const char* variant, int32_t log10Value, UHashtable* table, UErrorCode& status); +static const CDFUnit* getCDFUnitFallback(const UHashtable* table, const UnicodeString& variant, int32_t log10Value); + +UOBJECT_DEFINE_RTTI_IMPLEMENTATION(CompactDecimalFormat) + +CompactDecimalFormat::CompactDecimalFormat( + const DecimalFormat& decimalFormat, + const UHashtable* unitsByVariant, + const double* divisors, + PluralRules* pluralRules) + : DecimalFormat(decimalFormat), _unitsByVariant(unitsByVariant), _divisors(divisors), _pluralRules(pluralRules) { +} + +CompactDecimalFormat::CompactDecimalFormat(const CompactDecimalFormat& source) + : DecimalFormat(source), _unitsByVariant(source._unitsByVariant), _divisors(source._divisors), _pluralRules(source._pluralRules->clone()) { +} + +CompactDecimalFormat* U_EXPORT2 +CompactDecimalFormat::createInstance( + const Locale& inLocale, UNumberCompactStyle style, UErrorCode& status) { + LocalPointer decfmt((DecimalFormat*) NumberFormat::makeInstance(inLocale, UNUM_DECIMAL, TRUE, status)); + if (U_FAILURE(status)) { + return NULL; + } + LocalPointer pluralRules(PluralRules::forLocale(inLocale, status)); + if (U_FAILURE(status)) { + return NULL; + } + const CDFLocaleStyleData* data = getCDFLocaleStyleData(inLocale, style, status); + if (U_FAILURE(status)) { + return NULL; + } + CompactDecimalFormat* result = + new CompactDecimalFormat(*decfmt, data->unitsByVariant, data->divisors, pluralRules.getAlias()); + if (result == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + return NULL; + } + pluralRules.orphan(); + result->setMaximumSignificantDigits(3); + result->setSignificantDigitsUsed(TRUE); + result->setGroupingUsed(FALSE); + return result; +} + +CompactDecimalFormat& +CompactDecimalFormat::operator=(const CompactDecimalFormat& rhs) { + if (this != &rhs) { + DecimalFormat::operator=(rhs); + _unitsByVariant = rhs._unitsByVariant; + _divisors = rhs._divisors; + delete _pluralRules; + _pluralRules = rhs._pluralRules->clone(); + } + return *this; +} + +CompactDecimalFormat::~CompactDecimalFormat() { + delete _pluralRules; +} + + +Format* +CompactDecimalFormat::clone(void) const { + return new CompactDecimalFormat(*this); +} + +UBool +CompactDecimalFormat::operator==(const Format& that) const { + if (this == &that) { + return TRUE; + } + return (DecimalFormat::operator==(that) && eqHelper((const CompactDecimalFormat&) that)); +} + +UBool +CompactDecimalFormat::eqHelper(const CompactDecimalFormat& that) const { + return uhash_equals(_unitsByVariant, that._unitsByVariant) && divisors_equal(_divisors, that._divisors) && (*_pluralRules == *that._pluralRules); +} + +UnicodeString& +CompactDecimalFormat::format( + double number, + UnicodeString& appendTo, + FieldPosition& pos) const { + DigitList orig, rounded; + orig.set(number); + UBool isNegative; + UErrorCode status = U_ZERO_ERROR; + _round(orig, rounded, isNegative, status); + if (U_FAILURE(status)) { + return appendTo; + } + double roundedDouble = rounded.getDouble(); + if (isNegative) { + roundedDouble = -roundedDouble; + } + int32_t baseIdx = computeLog10(roundedDouble, TRUE); + double numberToFormat = roundedDouble / _divisors[baseIdx]; + UnicodeString variant = _pluralRules->select(numberToFormat); + if (isNegative) { + numberToFormat = -numberToFormat; + } + const CDFUnit* unit = getCDFUnitFallback(_unitsByVariant, variant, baseIdx); + appendTo += unit->prefix; + DecimalFormat::format(numberToFormat, appendTo, pos); + appendTo += unit->suffix; + return appendTo; +} + +UnicodeString& +CompactDecimalFormat::format( + double /* number */, + UnicodeString& appendTo, + FieldPositionIterator* /* posIter */, + UErrorCode& status) const { + status = U_UNSUPPORTED_ERROR; + return appendTo; +} + +UnicodeString& +CompactDecimalFormat::format( + int64_t number, + UnicodeString& appendTo, + FieldPosition& pos) const { + return format((double) number, appendTo, pos); +} + +UnicodeString& +CompactDecimalFormat::format( + int64_t /* number */, + UnicodeString& appendTo, + FieldPositionIterator* /* posIter */, + UErrorCode& status) const { + status = U_UNSUPPORTED_ERROR; + return appendTo; +} + +UnicodeString& +CompactDecimalFormat::format( + const StringPiece& /* number */, + UnicodeString& appendTo, + FieldPositionIterator* /* posIter */, + UErrorCode& status) const { + status = U_UNSUPPORTED_ERROR; + return appendTo; +} + +UnicodeString& +CompactDecimalFormat::format( + const DigitList& /* number */, + UnicodeString& appendTo, + FieldPositionIterator* /* posIter */, + UErrorCode& status) const { + status = U_UNSUPPORTED_ERROR; + return appendTo; +} + +UnicodeString& +CompactDecimalFormat::format(const DigitList& /* number */, + UnicodeString& appendTo, + FieldPosition& /* pos */, + UErrorCode& status) const { + status = U_UNSUPPORTED_ERROR; + return appendTo; +} + +void +CompactDecimalFormat::parse( + const UnicodeString& /* text */, + Formattable& /* result */, + ParsePosition& /* parsePosition */) const { +} + +void +CompactDecimalFormat::parse( + const UnicodeString& /* text */, + Formattable& /* result */, + UErrorCode& status) const { + status = U_UNSUPPORTED_ERROR; +} + +CurrencyAmount* +CompactDecimalFormat::parseCurrency( + const UnicodeString& /* text */, + ParsePosition& /* pos */) const { + return NULL; +} + +void CDFLocaleStyleData::Init(UErrorCode& status) { + if (unitsByVariant != NULL) { + return; + } + unitsByVariant = uhash_open(uhash_hashChars, uhash_compareChars, NULL, &status); + if (U_FAILURE(status)) { + return; + } + uhash_setKeyDeleter(unitsByVariant, uprv_free); + uhash_setValueDeleter(unitsByVariant, deleteCDFUnits); +} + +CDFLocaleStyleData::~CDFLocaleStyleData() { + setToBogus(); +} + +void CDFLocaleStyleData::setToBogus() { + if (unitsByVariant != NULL) { + uhash_close(unitsByVariant); + unitsByVariant = NULL; + } +} + +void CDFLocaleData::Init(UErrorCode& status) { + shortData.Init(status); + if (U_FAILURE(status)) { + return; + } + longData.Init(status); +} + +// Helper method for operator= +static UBool divisors_equal(const double* lhs, const double* rhs) { + for (int32_t i = 0; i < MAX_DIGITS; i++) { + if (lhs[i] != rhs[i]) { + return FALSE; + } + } + return TRUE; +} + +// getCDFLocaleStyleData returns pointer to formatting data for given locale and +// style within the global cache. On cache miss, getCDFLocaleStyleData loads +// the data from CLDR into the global cache before returning the pointer. If a +// UNUM_LONG data is requested for a locale, and that locale does not have +// UNUM_LONG data, getCDFLocaleStyleData will fall back to UNUM_SHORT data for +// that locale. +static const CDFLocaleStyleData* getCDFLocaleStyleData(const Locale& inLocale, UNumberCompactStyle style, UErrorCode& status) { + if (U_FAILURE(status)) { + return NULL; + } + CDFLocaleData* result = NULL; + const char* key = inLocale.getName(); + { + Mutex lock(&gCompactDecimalMetaLock); + if (gCompactDecimalData == NULL) { + gCompactDecimalData = uhash_open(uhash_hashChars, uhash_compareChars, NULL, &status); + if (U_FAILURE(status)) { + return NULL; + } + uhash_setKeyDeleter(gCompactDecimalData, uprv_free); + uhash_setValueDeleter(gCompactDecimalData, deleteCDFLocaleData); + ucln_i18n_registerCleanup(UCLN_I18N_CDFINFO, cdf_cleanup); + } else { + result = (CDFLocaleData*) uhash_get(gCompactDecimalData, key); + } + } + if (result != NULL) { + return extractDataByStyleEnum(*result, style, status); + } + + result = loadCDFLocaleData(inLocale, status); + if (U_FAILURE(status)) { + return NULL; + } + + { + Mutex lock(&gCompactDecimalMetaLock); + CDFLocaleData* temp = (CDFLocaleData*) uhash_get(gCompactDecimalData, key); + if (temp != NULL) { + delete result; + result = temp; + } else { + uhash_put(gCompactDecimalData, uprv_strdup(key), (void*) result, &status); + if (U_FAILURE(status)) { + return NULL; + } + } + } + return extractDataByStyleEnum(*result, style, status); +} + +static const CDFLocaleStyleData* extractDataByStyleEnum(const CDFLocaleData& data, UNumberCompactStyle style, UErrorCode& status) { + switch (style) { + case UNUM_SHORT: + return &data.shortData; + case UNUM_LONG: + if (!data.longData.isBogus()) { + return &data.longData; + } + return &data.shortData; + default: + status = U_ILLEGAL_ARGUMENT_ERROR; + return NULL; + } +} + +// loadCDFLocaleData loads formatting data from CLDR for a given locale. The +// caller owns the returned pointer. +static CDFLocaleData* loadCDFLocaleData(const Locale& inLocale, UErrorCode& status) { + if (U_FAILURE(status)) { + return NULL; + } + CDFLocaleData* result = new CDFLocaleData; + if (result == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + return NULL; + } + result->Init(status); + if (U_FAILURE(status)) { + delete result; + return NULL; + } + + initCDFLocaleData(inLocale, result, status); + if (U_FAILURE(status)) { + delete result; + return NULL; + } + return result; +} + +// initCDFLocaleData initializes result with data from CLDR. +// inLocale is the locale, the CLDR data is stored in result. +// First we load the UNUM_SHORT data looking first in local numbering +// system and not including root locale in fallback. Next we try in the latn +// numbering system where we fallback all the way to root. So we find the +// short data in one of 3 places: the local numbering system, the latn +// numbering system non root, latn numbering system root locale. +// Next we look for the UNUM_LONG data in the same way except that if we don't +// find the UNUM_LONG data before we get to where we found the UNUM_SHORT data +// we mark our UNUM_LONG data bogus so that it will fallback to what we have +// for UNUM_SHORT. +static void initCDFLocaleData(const Locale& inLocale, CDFLocaleData* result, UErrorCode& status) { + LocalPointer ns(NumberingSystem::createInstance(inLocale, status)); + if (U_FAILURE(status)) { + return; + } + const char* numberingSystemName = ns->getName(); + UResourceBundle* rb = ures_open(NULL, inLocale.getName(), &status); + rb = ures_getByKeyWithFallback(rb, gNumberElementsTag, rb, &status); + if (U_FAILURE(status)) { + ures_close(rb); + return; + } + LocalUResourceBundlePointer localResource; + LocalUResourceBundlePointer latnResource; + UResourceBundle* dataFillIn = NULL; + UResourceBundle* data = NULL; + + // Look in local numbering system first for UNUM_SHORT if it is not latn + DataLocation shortLocation = LOCAL_LOC; + if (uprv_strcmp(numberingSystemName, gLatnTag) != 0) { + localResource.adoptInstead(tryGetByKeyWithFallback(rb, numberingSystemName, NULL, NOT_ROOT, status)); + data = tryGetDecimalFallback(localResource.getAlias(), gPatternsShort, &dataFillIn, NOT_ROOT, status); + } + // If we haven't found UNUM_SHORT look in latn numbering system. We must + // succeed at finding UNUM_SHORT here. + if (data == NULL) { + latnResource.adoptInstead(tryGetByKeyWithFallback(rb, gLatnTag, NULL, MUST, status)); + data = tryGetDecimalFallback(latnResource.getAlias(), gPatternsShort, &dataFillIn, MUST, status); + shortLocation = isRoot(data, status) ? ROOT_LOC : LATIN_LOC; + } + initCDFLocaleStyleData(data, &result->shortData, status); + if (U_FAILURE(status)) { + ures_close(dataFillIn); + ures_close(rb); + return; + } + data = NULL; + + // Look for UNUM_LONG data in local numbering system first. + data = tryGetDecimalFallback(localResource.getAlias(), gPatternsLong, &dataFillIn, NOT_ROOT, status); + + // If we haven't found UNUM_LONG and we found the UNUM_SHORT data in the latn + // Numbering system, continue. If we find UNUM_LONG in the latin numbering + // system, we have to be sure that we didn't find it after where we found + // UNUM_SHORT. + if (data == NULL && shortLocation != LOCAL_LOC) { + data = tryGetDecimalFallback(latnResource.getAlias(), gPatternsLong, &dataFillIn, ANY, status); + if (data != NULL) { + if (shortLocation == LATIN_LOC && isRoot(data, status)) { + data = NULL; + } + } + } + if (data == NULL) { + result->longData.setToBogus(); + } else { + initCDFLocaleStyleData(data, &result->longData, status); + } + ures_close(dataFillIn); + ures_close(rb); +} + +/** + * tryGetDecimalFallback attempts to fetch the "decimalFormat" resource bundle + * with a particular style. style is either "patternsShort" or "patternsLong." + * FillIn, flags, and status work in the same way as in tryGetByKeyWithFallback. + */ +static UResourceBundle* tryGetDecimalFallback(const UResourceBundle* numberSystemResource, const char* style, UResourceBundle** fillIn, FallbackFlags flags, UErrorCode& status) { + UResourceBundle* first = tryGetByKeyWithFallback(numberSystemResource, style, fillIn, flags, status); + UResourceBundle* second = tryGetByKeyWithFallback(first, gDecimalFormatTag, fillIn, flags, status); + if (fillIn == NULL) { + ures_close(first); + } + return second; +} + +// tryGetByKeyWithFallback returns a sub-resource bundle that matches given +// criteria or NULL if none found. rb is the resource bundle that we are +// searching. If rb == NULL then this function behaves as if no sub-resource +// is found; path is the key of the sub-resource, +// (i.e "foo" but not "foo/bar"); If fillIn is NULL, caller must always call +// ures_close() on returned resource. See below for example when fillIn is +// not NULL. flags is ANY or NOT_ROOT. Optionally, these values +// can be ored with MUST. MUST by itself is the same as ANY | MUST. +// The locale of the returned sub-resource will either match the +// flags or the returned sub-resouce will be NULL. If MUST is included in +// flags, and not suitable sub-resource is found then in addition to returning +// NULL, this function also sets status to U_MISSING_RESOURCE_ERROR. If MUST +// is not included in flags, then this function just returns NULL if no +// such sub-resource is found and will never set status to +// U_MISSING_RESOURCE_ERROR. +// +// Example: This code first searches for "foo/bar" sub-resource without falling +// back to ROOT. Then searches for "baz" sub-resource as last resort. +// +// UResourcebundle* fillIn = NULL; +// UResourceBundle* data = tryGetByKeyWithFallback(rb, "foo", &fillIn, NON_ROOT, status); +// data = tryGetByKeyWithFallback(data, "bar", &fillIn, NON_ROOT, status); +// if (!data) { +// data = tryGetbyKeyWithFallback(rb, "baz", &fillIn, MUST, status); +// } +// if (U_FAILURE(status)) { +// ures_close(fillIn); +// return; +// } +// doStuffWithNonNullSubresource(data); +// +// /* Wrong! don't do the following as it can leak memory if fillIn gets set +// to NULL. */ +// fillIn = tryGetByKeyWithFallback(rb, "wrong", &fillIn, ANY, status); +// +// ures_close(fillIn); +// +static UResourceBundle* tryGetByKeyWithFallback(const UResourceBundle* rb, const char* path, UResourceBundle** fillIn, FallbackFlags flags, UErrorCode& status) { + if (U_FAILURE(status)) { + return NULL; + } + UBool must = (flags & MUST); + if (rb == NULL) { + if (must) { + status = U_MISSING_RESOURCE_ERROR; + } + return NULL; + } + UResourceBundle* result = NULL; + UResourceBundle* ownedByUs = NULL; + if (fillIn == NULL) { + ownedByUs = ures_getByKeyWithFallback(rb, path, NULL, &status); + result = ownedByUs; + } else { + *fillIn = ures_getByKeyWithFallback(rb, path, *fillIn, &status); + result = *fillIn; + } + if (U_FAILURE(status)) { + ures_close(ownedByUs); + if (status == U_MISSING_RESOURCE_ERROR && !must) { + status = U_ZERO_ERROR; + } + return NULL; + } + flags = (FallbackFlags) (flags & ~MUST); + switch (flags) { + case NOT_ROOT: + { + UBool bRoot = isRoot(result, status); + if (bRoot || U_FAILURE(status)) { + ures_close(ownedByUs); + if (must && (status == U_ZERO_ERROR)) { + status = U_MISSING_RESOURCE_ERROR; + } + return NULL; + } + return result; + } + case ANY: + return result; + default: + ures_close(ownedByUs); + status = U_ILLEGAL_ARGUMENT_ERROR; + return NULL; + } +} + +static UBool isRoot(const UResourceBundle* rb, UErrorCode& status) { + const char* actualLocale = ures_getLocaleByType( + rb, ULOC_ACTUAL_LOCALE, &status); + if (U_FAILURE(status)) { + return FALSE; + } + return uprv_strcmp(actualLocale, gRoot) == 0; +} + + +// initCDFLocaleStyleData loads formatting data for a particular style. +// decimalFormatBundle is the "decimalFormat" resource bundle in CLDR. +// Loaded data stored in result. +static void initCDFLocaleStyleData(const UResourceBundle* decimalFormatBundle, CDFLocaleStyleData* result, UErrorCode& status) { + if (U_FAILURE(status)) { + return; + } + // Iterate through all the powers of 10. + int32_t size = ures_getSize(decimalFormatBundle); + UResourceBundle* power10 = NULL; + for (int32_t i = 0; i < size; ++i) { + power10 = ures_getByIndex(decimalFormatBundle, i, power10, &status); + if (U_FAILURE(status)) { + ures_close(power10); + return; + } + populatePower10(power10, result, status); + if (U_FAILURE(status)) { + ures_close(power10); + return; + } + } + ures_close(power10); + fillInMissing(result); +} + +// populatePower10 grabs data for a particular power of 10 from CLDR. +// The loaded data is stored in result. +static void populatePower10(const UResourceBundle* power10Bundle, CDFLocaleStyleData* result, UErrorCode& status) { + if (U_FAILURE(status)) { + return; + } + char* endPtr = NULL; + double power10 = uprv_strtod(ures_getKey(power10Bundle), &endPtr); + if (*endPtr != 0) { + status = U_INTERNAL_PROGRAM_ERROR; + return; + } + int32_t log10Value = computeLog10(power10, FALSE); + // Silently ignore divisors that are too big. + if (log10Value == MAX_DIGITS) { + return; + } + int32_t size = ures_getSize(power10Bundle); + int32_t numZeros = 0; + UBool otherVariantDefined = FALSE; + UResourceBundle* variantBundle = NULL; + // Iterate over all the plural variants for the power of 10 + for (int i = 0; i < size; ++i) { + variantBundle = ures_getByIndex(power10Bundle, i, variantBundle, &status); + if (U_FAILURE(status)) { + ures_close(variantBundle); + return; + } + const char* variant = ures_getKey(variantBundle); + int32_t resLen; + const UChar* formatStrP = ures_getString(variantBundle, &resLen, &status); + if (U_FAILURE(status)) { + ures_close(variantBundle); + return; + } + UnicodeString formatStr(false, formatStrP, resLen); + if (uprv_strcmp(variant, gOther) == 0) { + otherVariantDefined = TRUE; + } + int nz = populatePrefixSuffix( + variant, log10Value, formatStr, result->unitsByVariant, status); + if (U_FAILURE(status)) { + ures_close(variantBundle); + return; + } + if (nz != numZeros) { + // We expect all format strings to have the same number of 0's + // left of the decimal point. + if (numZeros != 0) { + status = U_INTERNAL_PROGRAM_ERROR; + ures_close(variantBundle); + return; + } + numZeros = nz; + } + } + ures_close(variantBundle); + // We expect to find an OTHER variant for each power of 10. + if (!otherVariantDefined) { + status = U_INTERNAL_PROGRAM_ERROR; + return; + } + long divisor = power10; + for (int i = 1; i < numZeros; i++) { + divisor /= 10.0; + } + result->divisors[log10Value] = divisor; +} + +// populatePrefixSuffix Adds a specific prefix-suffix pair to result for a +// given variant and log10 value. +// variant is 'zero', 'one', 'two', 'few', 'many', or 'other'. +// formatStr is the format string from which the prefix and suffix are +// extracted. It is usually of form 'Pefix 000 suffix'. +// populatePrefixSuffix returns the number of 0's found in formatStr +// before the decimal point. +// In the special case that formatStr contains only spaces for prefix +// and suffix, populatePrefixSuffix returns log10Value + 1. +static int32_t populatePrefixSuffix( + const char* variant, int32_t log10Value, const UnicodeString& formatStr, UHashtable* result, UErrorCode& status) { + if (U_FAILURE(status)) { + return 0; + } + int32_t firstIdx = formatStr.indexOf(kZero, LENGTHOF(kZero), 0); + // We must have 0's in format string. + if (firstIdx == -1) { + status = U_INTERNAL_PROGRAM_ERROR; + return 0; + } + int32_t lastIdx = formatStr.lastIndexOf(kZero, LENGTHOF(kZero), firstIdx); + CDFUnit* unit = createCDFUnit(variant, log10Value, result, status); + if (U_FAILURE(status)) { + return 0; + } + // Everything up to first 0 is the prefix + unit->prefix = formatStr.tempSubString(0, firstIdx); + fixQuotes(unit->prefix); + // Everything beyond the last 0 is the suffix + unit->suffix = formatStr.tempSubString(lastIdx + 1); + fixQuotes(unit->suffix); + + // If there is effectively no prefix or suffix, ignore the actual number of + // 0's and act as if the number of 0's matches the size of the number. + if (onlySpaces(unit->prefix) && onlySpaces(unit->suffix)) { + return log10Value + 1; + } + + // Calculate number of zeros before decimal point + int32_t idx = firstIdx + 1; + while (idx <= lastIdx && formatStr.charAt(idx) == u_0) { + ++idx; + } + return (idx - firstIdx); +} + +static UBool onlySpaces(UnicodeString u) { + return u.trim().length() == 0; +} + +// fixQuotes unescapes single quotes. Don''t -> Don't. Letter 'j' -> Letter j. +// Modifies s in place. +static void fixQuotes(UnicodeString& s) { + QuoteState state = OUTSIDE; + int32_t len = s.length(); + int32_t dest = 0; + for (int i = 0; i < len; ++i) { + UChar ch = s.charAt(i); + if (ch == u_apos) { + if (state == INSIDE_EMPTY) { + s.setCharAt(dest, ch); + ++dest; + } + } else { + s.setCharAt(dest, ch); + ++dest; + } + + // Update state + switch (state) { + case OUTSIDE: + state = ch == u_apos ? INSIDE_EMPTY : OUTSIDE; + break; + case INSIDE_EMPTY: + case INSIDE_FULL: + state = ch == u_apos ? OUTSIDE : INSIDE_FULL; + break; + default: + break; + } + } + s.truncate(dest); +} + +// fillInMissing ensures that the data in result is complete. +// result data is complete if for each variant in result, there exists +// a prefix-suffix pair for each log10 value and there also exists +// a divisor for each log10 value. +// +// First this function figures out for which log10 values, the other +// variant already had data. These are the same log10 values defined +// in CLDR. +// +// For each log10 value not defined in CLDR, it uses the divisor for +// the last defined log10 value or 1. +// +// Then for each variant, it does the following. For each log10 +// value not defined in CLDR, copy the prefix-suffix pair from the +// previous log10 value. If log10 value is defined in CLDR but is +// missing from given variant, copy the prefix-suffix pair for that +// log10 value from the 'other' variant. +static void fillInMissing(CDFLocaleStyleData* result) { + const CDFUnit* otherUnits = + (const CDFUnit*) uhash_get(result->unitsByVariant, gOther); + UBool definedInCLDR[MAX_DIGITS]; + double lastDivisor = 1.0; + for (int i = 0; i < MAX_DIGITS; ++i) { + if (!otherUnits[i].isSet()) { + result->divisors[i] = lastDivisor; + definedInCLDR[i] = FALSE; + } else { + lastDivisor = result->divisors[i]; + definedInCLDR[i] = TRUE; + } + } + // Iterate over each variant. + int32_t pos = -1; + const UHashElement* element = uhash_nextElement(result->unitsByVariant, &pos); + for (;element != NULL; element = uhash_nextElement(result->unitsByVariant, &pos)) { + CDFUnit* units = (CDFUnit*) element->value.pointer; + for (int32_t i = 0; i < MAX_DIGITS; ++i) { + if (definedInCLDR[i]) { + if (!units[i].isSet()) { + units[i] = otherUnits[i]; + } + } else { + if (i == 0) { + units[0].markAsSet(); + } else { + units[i] = units[i - 1]; + } + } + } + } +} + +// computeLog10 computes floor(log10(x)). If inRange is TRUE, the biggest +// value computeLog10 will return MAX_DIGITS -1 even for +// numbers > 10^MAX_DIGITS. If inRange is FALSE, computeLog10 will return +// up to MAX_DIGITS. +static int32_t computeLog10(double x, UBool inRange) { + int32_t result = 0; + int32_t max = inRange ? MAX_DIGITS - 1 : MAX_DIGITS; + while (x >= 10.0) { + x /= 10.0; + ++result; + if (result == max) { + break; + } + } + return result; +} + +// createCDFUnit returns a pointer to the prefix-suffix pair for a given +// variant and log10 value within table. If no such prefix-suffix pair is +// stored in table, one is created within table before returning pointer. +static CDFUnit* createCDFUnit(const char* variant, int32_t log10Value, UHashtable* table, UErrorCode& status) { + if (U_FAILURE(status)) { + return NULL; + } + CDFUnit *cdfUnit = (CDFUnit*) uhash_get(table, variant); + if (cdfUnit == NULL) { + cdfUnit = new CDFUnit[MAX_DIGITS]; + if (cdfUnit == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + return NULL; + } + uhash_put(table, uprv_strdup(variant), cdfUnit, &status); + if (U_FAILURE(status)) { + return NULL; + } + } + CDFUnit* result = &cdfUnit[log10Value]; + result->markAsSet(); + return result; +} + +// getCDFUnitFallback returns a pointer to the prefix-suffix pair for a given +// variant and log10 value within table. If the given variant doesn't exist, it +// falls back to the OTHER variant. Therefore, this method will always return +// some non-NULL value. +static const CDFUnit* getCDFUnitFallback(const UHashtable* table, const UnicodeString& variant, int32_t log10Value) { + CharString cvariant; + UErrorCode status = U_ZERO_ERROR; + const CDFUnit *cdfUnit = NULL; + cvariant.appendInvariantChars(variant, status); + if (!U_FAILURE(status)) { + cdfUnit = (const CDFUnit*) uhash_get(table, cvariant.data()); + } + if (cdfUnit == NULL) { + cdfUnit = (const CDFUnit*) uhash_get(table, gOther); + } + return &cdfUnit[log10Value]; +} + +U_NAMESPACE_END +#endif diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp index c4662c9531d..bd9898c2083 100644 --- a/icu4c/source/i18n/decimfmt.cpp +++ b/icu4c/source/i18n/decimfmt.cpp @@ -1319,7 +1319,74 @@ DecimalFormat::format(const DigitList &number, return appendTo; } +DigitList& +DecimalFormat::_round(const DigitList &number, DigitList &adjustedNum, UBool& isNegative, UErrorCode &status) const { + if (U_FAILURE(status)) { + return adjustedNum; + } + 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; + } + } + + /* + * 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); + } + } 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, @@ -1327,6 +1394,10 @@ DecimalFormat::_format(const DigitList &number, 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()) @@ -1340,38 +1411,12 @@ DecimalFormat::_format(const DigitList &number, return appendTo; } - // 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. - - DigitList adjustedNum(number); // Copy, so we do not alter the original. - adjustedNum.setRoundingMode(fRoundingMode); - if (fMultiplier != NULL) { - adjustedNum.mult(*fMultiplier, status); - } - - /* - * 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. - */ - UBool 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 (fRoundingMode == kRoundUnnecessary && (adjustedNum.fContext.status & DEC_Inexact)) { - status = U_FORMAT_INEXACT_ERROR; + DigitList adjustedNum; + UBool isNegative; + _round(number, adjustedNum, isNegative, status); + if (U_FAILURE(status)) { return appendTo; } - // Special case for INFINITE, if (adjustedNum.isInfinite()) { @@ -1387,26 +1432,9 @@ DecimalFormat::_format(const DigitList &number, addPadding(appendTo, handler, prefixLen, suffixLen); return appendTo; } - - if (fUseExponentialNotation || areSignificantDigitsUsed()) { - int32_t sigDigits = precision(); - if (sigDigits > 0) { - adjustedNum.round(sigDigits); - } - } 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 appendTo; - } - return subformat(appendTo, handler, adjustedNum, FALSE, status); } - UnicodeString& DecimalFormat::format( const Formattable& obj, UnicodeString& appendTo, diff --git a/icu4c/source/i18n/numfmt.cpp b/icu4c/source/i18n/numfmt.cpp index b8caf3ac1d2..8c08c1742e5 100644 --- a/icu4c/source/i18n/numfmt.cpp +++ b/icu4c/source/i18n/numfmt.cpp @@ -1140,8 +1140,15 @@ NumberFormat::isStyleSupported(UNumberFormatStyle style) { NumberFormat* NumberFormat::makeInstance(const Locale& desiredLocale, UNumberFormatStyle style, - UErrorCode& status) -{ + UErrorCode& status) { + return makeInstance(desiredLocale, style, false, status); +} + +NumberFormat* +NumberFormat::makeInstance(const Locale& desiredLocale, + UNumberFormatStyle style, + UBool mustBeDecimalFormat, + UErrorCode& status) { if (U_FAILURE(status)) return NULL; if (style < 0 || style >= UNUM_FORMAT_STYLE_COUNT) { @@ -1161,33 +1168,34 @@ NumberFormat::makeInstance(const Locale& desiredLocale, } #if U_PLATFORM_USES_ONLY_WIN32_API - char buffer[8]; - int32_t count = desiredLocale.getKeywordValue("compat", buffer, sizeof(buffer), status); + if (!mustBeDecimalFormat) { + char buffer[8]; + int32_t count = desiredLocale.getKeywordValue("compat", buffer, sizeof(buffer), status); - // if the locale has "@compat=host", create a host-specific NumberFormat - if (U_SUCCESS(status) && count > 0 && uprv_strcmp(buffer, "host") == 0) { - Win32NumberFormat *f = NULL; - UBool curr = TRUE; + // if the locale has "@compat=host", create a host-specific NumberFormat + if (U_SUCCESS(status) && count > 0 && uprv_strcmp(buffer, "host") == 0) { + Win32NumberFormat *f = NULL; + UBool curr = TRUE; - switch (style) { - case UNUM_DECIMAL: - curr = FALSE; - // fall-through + switch (style) { + case UNUM_DECIMAL: + curr = FALSE; + // fall-through - case UNUM_CURRENCY: - case UNUM_CURRENCY_ISO: // do not support plural formatting here - case UNUM_CURRENCY_PLURAL: - f = new Win32NumberFormat(desiredLocale, curr, status); + case UNUM_CURRENCY: + case UNUM_CURRENCY_ISO: // do not support plural formatting here + case UNUM_CURRENCY_PLURAL: + f = new Win32NumberFormat(desiredLocale, curr, status); - if (U_SUCCESS(status)) { - return f; + if (U_SUCCESS(status)) { + return f; + } + + delete f; + break; + default: + break; } - - delete f; - break; - - default: - break; } } #endif @@ -1245,6 +1253,11 @@ NumberFormat::makeInstance(const Locale& desiredLocale, return NULL; } + if (mustBeDecimalFormat && ns->isAlgorithmic()) { + status = U_UNSUPPORTED_ERROR; + return NULL; + } + LocalPointer symbolsToAdopt; UnicodeString pattern; LocalUResourceBundlePointer ownedResource(ures_open(NULL, desiredLocale.getName(), &status)); diff --git a/icu4c/source/i18n/ucln_in.h b/icu4c/source/i18n/ucln_in.h index a8daca589d0..4022f34496e 100644 --- a/icu4c/source/i18n/ucln_in.h +++ b/icu4c/source/i18n/ucln_in.h @@ -51,6 +51,7 @@ typedef enum ECleanupI18NType { UCLN_I18N_COLL_DATA, UCLN_I18N_INDEX_CHARACTERS, UCLN_I18N_GENDERINFO, + UCLN_I18N_CDFINFO, UCLN_I18N_COUNT /* This must be last */ } ECleanupI18NType; diff --git a/icu4c/source/i18n/unicode/compactdecimalformat.h b/icu4c/source/i18n/unicode/compactdecimalformat.h new file mode 100644 index 00000000000..13c39d939a9 --- /dev/null +++ b/icu4c/source/i18n/unicode/compactdecimalformat.h @@ -0,0 +1,330 @@ +/* +******************************************************************************** +* Copyright (C) 2012, International Business Machines +* Corporation and others. All Rights Reserved. +******************************************************************************** +* +* File COMPACTDECIMALFORMAT.H +******************************************************************************** +*/ + +#ifndef __COMPACT_DECIMAL_FORMAT_H__ +#define __COMPACT_DECIMAL_FORMAT_H__ + +#include "unicode/utypes.h" +/** + * \file + * \brief C++ API: Formats decimal numbers in compact form. + */ + +#if !UCONFIG_NO_FORMATTING + +#include "unicode/decimfmt.h" + +struct UHashtable; + +U_NAMESPACE_BEGIN + +class PluralRules; + +/** + * The CompactDecimalFormat produces abbreviated numbers, suitable for display in + * environments will limited real estate. For example, 'Hits: 1.2B' instead of + * 'Hits: 1,200,000,000'. The format will be appropriate for the given language, + * such as "1,2 Mrd." for German. + *

+ * For numbers under 1000 trillion (under 10^15, such as 123,456,789,012,345), + * the result will be short for supported languages. However, the result may + * sometimes exceed 7 characters, such as when there are combining marks or thin + * characters. In such cases, the visual width in fonts should still be short. + *

+ * By default, there are 3 significant digits. After creation, if more than + * three significant digits are set (with setMaximumSignificantDigits), or if a + * fixed number of digits are set (with setMaximumIntegerDigits or + * setMaximumFractionDigits), then result may be wider. + *

+ * At this time, parsing is not supported, and will produce a U_UNSUPPORTED_ERROR. + * Resetting the pattern prefixes or suffixes is not supported; the method calls + * are ignored. + *

+ */ +class U_I18N_API CompactDecimalFormat : public DecimalFormat { +public: + + /** + * Returns a compact decimal instance for specified locale. + * @param inLocale the given locale. + * @param style whether to use short or long style. + * @param status error code returned here. + * @draft ICU 51 + */ + static CompactDecimalFormat* U_EXPORT2 createInstance( + const Locale& inLocale, UNumberCompactStyle style, UErrorCode& status); + + /** + * Copy constructor. + * + * @param source the DecimalFormat object to be copied from. + * @draft ICU 51 + */ + CompactDecimalFormat(const CompactDecimalFormat& source); + + /** + * Destructor. + * @draft ICU 51 + */ + virtual ~CompactDecimalFormat(); + + /** + * Assignment operator. + * + * @param rhs the DecimalFormat object to be copied. + * @draft ICU 51 + */ + CompactDecimalFormat& operator=(const CompactDecimalFormat& rhs); + + /** + * Clone this Format object polymorphically. The caller owns the + * result and should delete it when done. + * + * @return a polymorphic copy of this CompactDecimalFormat. + * @draft ICU 51 + */ + virtual Format* clone() const; + + /** + * Return TRUE if the given Format objects are semantically equal. + * Objects of different subclasses are considered unequal. + * + * @param other the object to be compared with. + * @return TRUE if the given Format objects are semantically equal. + * @draft ICU 51 + */ + virtual UBool operator==(const Format& other) const; + + + using DecimalFormat::format; + + /** + * Format a double or long number using base-10 representation. + * + * @param number The value to be formatted. + * @param appendTo Output parameter to receive result. + * Result is appended to existing contents. + * @param pos On input: an alignment field, if desired. + * On output: the offsets of the alignment field. + * @return Reference to 'appendTo' parameter. + * @draft ICU 51 + */ + virtual UnicodeString& format(double number, + UnicodeString& appendTo, + FieldPosition& pos) const; + + /** + * Format a double or long number using base-10 representation. + * Currently sets status to U_UNSUPPORTED_ERROR. + * + * @param number The value to be formatted. + * @param appendTo Output parameter to receive result. + * Result is appended to existing contents. + * @param posIter On return, can be used to iterate over positions + * of fields generated by this format call. + * Can be NULL. + * @param status Output param filled with success/failure status. + * @return Reference to 'appendTo' parameter. + * @internal + */ + virtual UnicodeString& format(double number, + UnicodeString& appendTo, + FieldPositionIterator* posIter, + UErrorCode& status) const; + + /** + * Format an int64 number using base-10 representation. + * + * @param number The value to be formatted. + * @param appendTo Output parameter to receive result. + * Result is appended to existing contents. + * @param pos On input: an alignment field, if desired. + * On output: the offsets of the alignment field. + * @return Reference to 'appendTo' parameter. + * @draft ICU 51 + */ + virtual UnicodeString& format(int64_t number, + UnicodeString& appendTo, + FieldPosition& pos) const; + + /** + * Format an int64 number using base-10 representation. + * Currently sets status to U_UNSUPPORTED_ERROR + * + * @param number The value to be formatted. + * @param appendTo Output parameter to receive result. + * Result is appended to existing contents. + * @param posIter On return, can be used to iterate over positions + * of fields generated by this format call. + * Can be NULL. + * @param status Output param filled with success/failure status. + * @return Reference to 'appendTo' parameter. + * @internal + */ + virtual UnicodeString& format(int64_t number, + UnicodeString& appendTo, + FieldPositionIterator* posIter, + UErrorCode& status) const; + + /** + * Format a decimal number. Currently sets status to U_UNSUPPORTED_ERROR + * The syntax of the unformatted number is a "numeric string" + * as defined in the Decimal Arithmetic Specification, available at + * http://speleotrove.com/decimal + * + * @param number The unformatted number, as a string. + * @param appendTo Output parameter to receive result. + * Result is appended to existing contents. + * @param posIter On return, can be used to iterate over positions + * of fields generated by this format call. + * Can be NULL. + * @param status Output param filled with success/failure status. + * @return Reference to 'appendTo' parameter. + * @internal + */ + virtual UnicodeString& format(const StringPiece &number, + UnicodeString& appendTo, + FieldPositionIterator* posIter, + UErrorCode& status) const; + + /** + * Format a decimal number. Currently sets status to U_UNSUPPORTED_ERROR + * The number is a DigitList wrapper onto a floating point decimal number. + * The default implementation in NumberFormat converts the decimal number + * to a double and formats that. + * + * @param number The number, a DigitList format Decimal Floating Point. + * @param appendTo Output parameter to receive result. + * Result is appended to existing contents. + * @param posIter On return, can be used to iterate over positions + * of fields generated by this format call. + * @param status Output param filled with success/failure status. + * @return Reference to 'appendTo' parameter. + * @internal + */ + virtual UnicodeString& format(const DigitList &number, + UnicodeString& appendTo, + FieldPositionIterator* posIter, + UErrorCode& status) const; + + /** + * Format a decimal number. Currently sets status to U_UNSUPPORTED_ERROR. + * The number is a DigitList wrapper onto a floating point decimal number. + * The default implementation in NumberFormat converts the decimal number + * to a double and formats that. + * + * @param number The number, a DigitList format Decimal Floating Point. + * @param appendTo Output parameter to receive result. + * Result is appended to existing contents. + * @param pos On input: an alignment field, if desired. + * On output: the offsets of the alignment field. + * @param status Output param filled with success/failure status. + * @return Reference to 'appendTo' parameter. + * @internal + */ + virtual UnicodeString& format(const DigitList &number, + UnicodeString& appendTo, + FieldPosition& pos, + UErrorCode& status) const; + + /** + * CompactDecimalFormat does not support parsing. This implementation + * does nothing. + * @param text Unused. + * @param result Does not change. + * @param parsePosition Does not change. + * @see Formattable + * @draft ICU 51 + */ + virtual void parse(const UnicodeString& text, + Formattable& result, + ParsePosition& parsePosition) const; + + /** + * CompactDecimalFormat does not support parsing. This implementation + * sets status to U_UNSUPPORTED_ERROR + * + * @param text Unused. + * @param result Does not change. + * @param status Always set to U_UNSUPPORTED_ERROR. + * @draft ICU 51 + */ + virtual void parse(const UnicodeString& text, + Formattable& result, + UErrorCode& status) const; + +/* Cannot use #ifndef U_HIDE_DRAFT_API for the following draft method since it is virtual */ + /** + * Parses text from the given string as a currency amount. Unlike + * the parse() method, this method will attempt to parse a generic + * currency name, searching for a match of this object's locale's + * currency display names, or for a 3-letter ISO currency code. + * This method will fail if this format is not a currency format, + * that is, if it does not contain the currency pattern symbol + * (U+00A4) in its prefix or suffix. This implementation always returns + * NULL. + * + * @param text the string to parse + * @param pos input-output position; on input, the position within text + * to match; must have 0 <= pos.getIndex() < text.length(); + * on output, the position after the last matched character. + * If the parse fails, the position in unchanged upon output. + * @return if parse succeeds, a pointer to a newly-created CurrencyAmount + * object (owned by the caller) containing information about + * the parsed currency; if parse fails, this is NULL. + * @internal + */ + virtual CurrencyAmount* parseCurrency(const UnicodeString& text, + ParsePosition& pos) const; + + /** + * Return the class ID for this class. This is useful only for + * comparing to a return value from getDynamicClassID(). For example: + *

+     * .      Base* polymorphic_pointer = createPolymorphicObject();
+     * .      if (polymorphic_pointer->getDynamicClassID() ==
+     * .          Derived::getStaticClassID()) ...
+     * 
+ * @return The class ID for all objects of this class. + * @draft ICU 51 + */ + static UClassID U_EXPORT2 getStaticClassID(); + + /** + * Returns a unique class ID POLYMORPHICALLY. Pure virtual override. + * This method is to implement a simple version of RTTI, since not all + * C++ compilers support genuine RTTI. Polymorphic operator==() and + * clone() methods call this method. + * + * @return The class ID for this object. All objects of a + * given class have the same class ID. Objects of + * other classes have different class IDs. + * @draft ICU 51 + */ + virtual UClassID getDynamicClassID() const; + +private: + + const UHashtable* _unitsByVariant; + const double* _divisors; + PluralRules* _pluralRules; + + // Default constructor not implemented. + CompactDecimalFormat(const DecimalFormat &, const UHashtable* unitsByVariant, const double* divisors, PluralRules* pluralRules); + + UBool eqHelper(const CompactDecimalFormat& that) const; +}; + +U_NAMESPACE_END + +#endif /* #if !UCONFIG_NO_FORMATTING */ + +#endif // __COMPACT_DECIMAL_FORMAT_H__ +//eof diff --git a/icu4c/source/i18n/unicode/decimfmt.h b/icu4c/source/i18n/unicode/decimfmt.h index b4f53faea88..0c55bfedd4c 100644 --- a/icu4c/source/i18n/unicode/decimfmt.h +++ b/icu4c/source/i18n/unicode/decimfmt.h @@ -2403,6 +2403,12 @@ private: protected: + /** + * Rounds a value according to the rules of this object. + * @internal + */ + DigitList& _round(const DigitList& number, DigitList& adjustedNum, UBool& isNegative, UErrorCode& status) const; + /** * Returns the currency in effect for this formatter. Subclasses * should override this method as needed. Unlike getCurrency(), diff --git a/icu4c/source/i18n/unicode/numfmt.h b/icu4c/source/i18n/unicode/numfmt.h index 235128e0c35..32bae4f63b6 100644 --- a/icu4c/source/i18n/unicode/numfmt.h +++ b/icu4c/source/i18n/unicode/numfmt.h @@ -71,6 +71,9 @@ class StringEnumeration; * cout << " Example 1: " << myString << endl; * \endcode * + * Note that there are additional factory methods within subclasses of + * NumberFormat. + *

* If you are formatting multiple numbers, it is more efficient to get * the format and use it multiple times so that the system doesn't * have to fetch the information about the local language and country @@ -983,6 +986,17 @@ protected: */ virtual void getEffectiveCurrency(UChar* result, UErrorCode& ec) const; + /** + * Creates the specified number format style of the desired locale. + * If mustBeDecimalFormat is TRUE, then the returned pointer is + * either a DecimalFormat or it is NULL. + * @internal + */ + static NumberFormat* makeInstance(const Locale& desiredLocale, + UNumberFormatStyle style, + UBool mustBeDecimalFormat, + UErrorCode& errorCode); + private: static UBool isStyleSupported(UNumberFormatStyle style); diff --git a/icu4c/source/i18n/unicode/unum.h b/icu4c/source/i18n/unicode/unum.h index d8c092e6526..f3132f18e65 100644 --- a/icu4c/source/i18n/unicode/unum.h +++ b/icu4c/source/i18n/unicode/unum.h @@ -250,6 +250,18 @@ typedef enum UNumberFormatPadPosition { UNUM_PAD_AFTER_SUFFIX } UNumberFormatPadPosition; +/** + * Constants for specifying short or long format. + * @draft ICU 51 + */ +typedef enum UNumberCompactStyle { + /** @draft ICU 51 */ + UNUM_SHORT, + /** @draft ICU 51 */ + UNUM_LONG + /** @draft ICU 51 */ +} UNumberCompactStyle; + /** * Constants for specifying currency spacing * @stable ICU 4.8 diff --git a/icu4c/source/test/intltest/Makefile.in b/icu4c/source/test/intltest/Makefile.in index 099f9e1929c..31fc8b382d0 100644 --- a/icu4c/source/test/intltest/Makefile.in +++ b/icu4c/source/test/intltest/Makefile.in @@ -55,7 +55,7 @@ itrbnf.o itrbnfrt.o itrbnfp.o ucaconf.o icusvtst.o \ uobjtest.o idnaref.o idnaconf.o nptrans.o punyref.o testidn.o testidna.o uts46test.o \ incaltst.o calcasts.o v32test.o uvectest.o textfile.o tokiter.o utxttest.o \ windttst.o winnmtst.o winutil.o csdetest.o tzrulets.o tzoffloc.o tzfmttst.o ssearch.o dtifmtts.o \ -tufmtts.o itspoof.o simplethread.o bidiconf.o locnmtst.o dcfmtest.o alphaindextst.o listformattertest.o genderinfotest.o +tufmtts.o itspoof.o simplethread.o bidiconf.o locnmtst.o dcfmtest.o alphaindextst.o listformattertest.o genderinfotest.o compactdecimalformattest.o DEPS = $(OBJECTS:.o=.d) diff --git a/icu4c/source/test/intltest/compactdecimalformattest.cpp b/icu4c/source/test/intltest/compactdecimalformattest.cpp new file mode 100644 index 00000000000..373965295a5 --- /dev/null +++ b/icu4c/source/test/intltest/compactdecimalformattest.cpp @@ -0,0 +1,342 @@ +/* +******************************************************************************* +* Copyright (C) 1997-2012, International Business Machines Corporation and * +* others. All Rights Reserved. * +******************************************************************************* +* +* File COMPACTDECIMALFORMATTEST.CPP +* +******************************************************************************** +*/ +#include +#include + +#include "intltest.h" +#include "unicode/compactdecimalformat.h" +#include "unicode/unum.h" + +#define LENGTHOF(array) (int32_t)(sizeof(array) / sizeof((array)[0])) + +typedef struct ExpectedResult { + double value; + const char *expected; +} ExpectedResult; + +static const char *kShortStr = "Short"; +static const char *kLongStr = "Long"; + +static ExpectedResult kEnglishShort[] = { + {0.0, "0.0"}, + {0.17, "0.17"}, + {1, "1"}, + {1234, "1.2K"}, + {12345, "12K"}, + {123456, "120K"}, + {1234567, "1.2M"}, + {12345678, "12M"}, + {123456789, "120M"}, + {1234567890, "1.2B"}, + {12345678901, "12B"}, + {123456789012, "120B"}, + {1234567890123, "1.2T"}, + {12345678901234, "12T"}, + {123456789012345, "120T"}, + {1234567890123456, "1200T"}}; + +static ExpectedResult kSerbianShort[] = { + {1234, "1200"}, + {12345, "12K"}, + {20789, "21\\u00a0\\u0445\\u0438\\u0459"}, + {123456, "120\\u00a0\\u0445\\u0438\\u0459"}, + {1234567, "1,2\\u00A0\\u043C\\u0438\\u043B"}, + {12345678, "12\\u00A0\\u043C\\u0438\\u043B"}, + {123456789, "120\\u00A0\\u043C\\u0438\\u043B"}, + {1234567890, "1,2\\u00A0\\u043C\\u043B\\u0440\\u0434"}, + {12345678901, "12\\u00A0\\u043C\\u043B\\u0440\\u0434"}, + {123456789012, "120\\u00A0\\u043C\\u043B\\u0440\\u0434"}, + {1234567890123, "1,2\\u00A0\\u0431\\u0438\\u043B"}, + {12345678901234, "12\\u00A0\\u0431\\u0438\\u043B"}, + {123456789012345, "120\\u00A0\\u0431\\u0438\\u043B"}, + {1234567890123456, "1200\\u00A0\\u0431\\u0438\\u043B"}}; + +static ExpectedResult kSerbianLong[] = { + {1234, "1,2 \\u0445\\u0438\\u0459\\u0430\\u0434\\u0430"}, + {12345, "12 \\u0445\\u0438\\u0459\\u0430\\u0434\\u0430"}, + {21789, "22 \\u0445\\u0438\\u0459\\u0430\\u0434\\u0435"}, + {123456, "120 \\u0445\\u0438\\u0459\\u0430\\u0434\\u0430"}, + {999999, "1 \\u043C\\u0438\\u043B\\u0438\\u043E\\u043D"}, + {1234567, "1,2 \\u043C\\u0438\\u043B\\u0438\\u043E\\u043D\\u0430"}, + {12345678, "12 \\u043C\\u0438\\u043B\\u0438\\u043E\\u043D\\u0430"}, + {123456789, "120 \\u043C\\u0438\\u043B\\u0438\\u043E\\u043D\\u0430"}, + {1234567890, "1,2 \\u043C\\u0438\\u043B\\u0438\\u0458\\u0430\\u0440\\u0434\\u0438"}, + {12345678901, "12 \\u043C\\u0438\\u043B\\u0438\\u0458\\u0430\\u0440\\u0434\\u0438"}, + {20890123456, "21 \\u043C\\u0438\\u043B\\u0438\\u0458\\u0430\\u0440\\u0434\\u0430"}, + {21890123456, "22 \\u043C\\u0438\\u043B\\u0438\\u0458\\u0430\\u0440\\u0434\\u0435"}, + {123456789012, "120 \\u043C\\u0438\\u043B\\u0438\\u0458\\u0430\\u0440\\u0434\\u0438"}, + {1234567890123, "1,2 \\u0442\\u0440\\u0438\\u043B\\u0438\\u043E\\u043D\\u0430"}, + {12345678901234, "12 \\u0442\\u0440\\u0438\\u043B\\u0438\\u043E\\u043D\\u0430"}, + {123456789012345, "120 \\u0442\\u0440\\u0438\\u043B\\u0438\\u043E\\u043D\\u0430"}, + {1234567890123456, "1200 \\u0442\\u0440\\u0438\\u043B\\u0438\\u043E\\u043D\\u0430"}}; + +static ExpectedResult kSerbianLongNegative[] = { + {-1234, "-1,2 \\u0445\\u0438\\u0459\\u0430\\u0434\\u0430"}, + {-12345, "-12 \\u0445\\u0438\\u0459\\u0430\\u0434\\u0430"}, + {-21789, "-22 \\u0445\\u0438\\u0459\\u0430\\u0434\\u0435"}, + {-123456, "-120 \\u0445\\u0438\\u0459\\u0430\\u0434\\u0430"}, + {-999999, "-1 \\u043C\\u0438\\u043B\\u0438\\u043E\\u043D"}, + {-1234567, "-1,2 \\u043C\\u0438\\u043B\\u0438\\u043E\\u043D\\u0430"}, + {-12345678, "-12 \\u043C\\u0438\\u043B\\u0438\\u043E\\u043D\\u0430"}, + {-123456789, "-120 \\u043C\\u0438\\u043B\\u0438\\u043E\\u043D\\u0430"}, + {-1234567890, "-1,2 \\u043C\\u0438\\u043B\\u0438\\u0458\\u0430\\u0440\\u0434\\u0438"}, + {-12345678901, "-12 \\u043C\\u0438\\u043B\\u0438\\u0458\\u0430\\u0440\\u0434\\u0438"}, + {-20890123456, "-21 \\u043C\\u0438\\u043B\\u0438\\u0458\\u0430\\u0440\\u0434\\u0430"}, + {-21890123456, "-22 \\u043C\\u0438\\u043B\\u0438\\u0458\\u0430\\u0440\\u0434\\u0435"}, + {-123456789012, "-120 \\u043C\\u0438\\u043B\\u0438\\u0458\\u0430\\u0440\\u0434\\u0438"}, + {-1234567890123, "-1,2 \\u0442\\u0440\\u0438\\u043B\\u0438\\u043E\\u043D\\u0430"}, + {-12345678901234, "-12 \\u0442\\u0440\\u0438\\u043B\\u0438\\u043E\\u043D\\u0430"}, + {-123456789012345, "-120 \\u0442\\u0440\\u0438\\u043B\\u0438\\u043E\\u043D\\u0430"}, + {-1234567890123456, "-1200 \\u0442\\u0440\\u0438\\u043B\\u0438\\u043E\\u043D\\u0430"}}; + +static ExpectedResult kJapaneseShort[] = { + {1234, "1.2\\u5343"}, + {12345, "1.2\\u4E07"}, + {123456, "12\\u4E07"}, + {1234567, "120\\u4E07"}, + {12345678, "1200\\u4E07"}, + {123456789, "1.2\\u5104"}, + {1234567890, "12\\u5104"}, + {12345678901, "120\\u5104"}, + {123456789012, "1200\\u5104"}, + {1234567890123, "1.2\\u5146"}, + {12345678901234, "12\\u5146"}, + {123456789012345, "120\\u5146"}}; + +static ExpectedResult kSwahiliShort[] = { + {1234, "elfu\\u00a01.2"}, + {12345, "elfu\\u00a012"}, + {123456, "laki1.2"}, + {1234567, "M1.2"}, + {12345678, "M12"}, + {123456789, "M120"}, + {1234567890, "B1.2"}, + {12345678901, "B12"}, + {123456789012, "B120"}, + {1234567890123, "T1.2"}, + {12345678901234, "T12"}, + {1234567890123456, "T1200"}}; + +static ExpectedResult kCsShort[] = { + {1000, "1\\u00a0tis."}, + {1500, "1,5\\u00a0tis."}, + {5000, "5\\u00a0tis."}, + {23000, "23\\u00a0tis."}, + {127123, "130\\u00a0tis."}, + {1271234, "1,3\\u00a0mil."}, + {12712345, "13\\u00a0mil."}, + {127123456, "130\\u00a0mil."}, + {1271234567, "1,3\\u00a0mld."}, + {12712345678, "13\\u00a0mld."}, + {127123456789, "130\\u00a0mld."}, + {1271234567890, "1,3\\u00a0bil."}, + {12712345678901, "13\\u00a0bil."}, + {127123456789012, "130\\u00a0bil."}}; + +static ExpectedResult kSkLong[] = { + {1000, "1 tis\\u00edc"}, + {1572, "1,6 tis\\u00edc"}, + {5184, "5,2 tis\\u00edc"}}; + +static ExpectedResult kSwahiliShortNegative[] = { + {-1234, "elfu\\u00a0-1.2"}, + {-12345, "elfu\\u00a0-12"}, + {-123456, "laki-1.2"}, + {-1234567, "M-1.2"}, + {-12345678, "M-12"}, + {-123456789, "M-120"}, + {-1234567890, "B-1.2"}, + {-12345678901, "B-12"}, + {-123456789012, "B-120"}, + {-1234567890123, "T-1.2"}, + {-12345678901234, "T-12"}, + {-1234567890123456, "T-1200"}}; + +static ExpectedResult kArabicLong[] = { + {-5300, "\\u0665\\u066B\\u0663- \\u0623\\u0644\\u0641"}}; + + +class CompactDecimalFormatTest : public IntlTest { +public: + CompactDecimalFormatTest() { + } + + void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par=0); +private: + void TestEnglishShort(); + void TestSerbianShort(); + void TestSerbianLong(); + void TestSerbianLongNegative(); + void TestJapaneseShort(); + void TestSwahiliShort(); + void TestCsShort(); + void TestSkLong(); + void TestSwahiliShortNegative(); + void TestArabicLong(); + void TestFieldPosition(); + void TestSignificantDigits(); + void CheckLocale( + const Locale& locale, UNumberCompactStyle style, + const ExpectedResult* expectedResult, int32_t expectedResultLength); + void CheckExpectedResult( + const CompactDecimalFormat* cdf, const ExpectedResult* expectedResult, + const char* description); + CompactDecimalFormat* createCDFInstance(const Locale& locale, UNumberCompactStyle style, UErrorCode& status); + static const char *StyleStr(UNumberCompactStyle style); +}; + +void CompactDecimalFormatTest::runIndexedTest( + int32_t index, UBool exec, const char *&name, char *) { + if (exec) { + logln("TestSuite CompactDecimalFormatTest: "); + } + TESTCASE_AUTO_BEGIN; + TESTCASE_AUTO(TestEnglishShort); + TESTCASE_AUTO(TestSerbianShort); + TESTCASE_AUTO(TestSerbianLong); + TESTCASE_AUTO(TestSerbianLongNegative); + TESTCASE_AUTO(TestJapaneseShort); + TESTCASE_AUTO(TestSwahiliShort); + TESTCASE_AUTO(TestCsShort); + TESTCASE_AUTO(TestSkLong); + TESTCASE_AUTO(TestSwahiliShortNegative); + TESTCASE_AUTO(TestArabicLong); + TESTCASE_AUTO(TestFieldPosition); + TESTCASE_AUTO(TestSignificantDigits); + TESTCASE_AUTO_END; +} + +void CompactDecimalFormatTest::TestEnglishShort() { + CheckLocale("en", UNUM_SHORT, kEnglishShort, LENGTHOF(kEnglishShort)); +} + +void CompactDecimalFormatTest::TestSerbianShort() { + CheckLocale("sr", UNUM_SHORT, kSerbianShort, LENGTHOF(kSerbianShort)); +} + +void CompactDecimalFormatTest::TestSerbianLong() { + CheckLocale("sr", UNUM_LONG, kSerbianLong, LENGTHOF(kSerbianLong)); +} + +void CompactDecimalFormatTest::TestSerbianLongNegative() { + CheckLocale("sr", UNUM_LONG, kSerbianLongNegative, LENGTHOF(kSerbianLongNegative)); +} + +void CompactDecimalFormatTest::TestJapaneseShort() { + CheckLocale(Locale::getJapan(), UNUM_SHORT, kJapaneseShort, LENGTHOF(kJapaneseShort)); +} + +void CompactDecimalFormatTest::TestSwahiliShort() { + CheckLocale("sw", UNUM_SHORT, kSwahiliShort, LENGTHOF(kSwahiliShort)); +} + +void CompactDecimalFormatTest::TestFieldPosition() { + // Swahili uses prefixes which forces offsets in field position to change + UErrorCode status = U_ZERO_ERROR; + LocalPointer cdf(createCDFInstance("sw", UNUM_SHORT, status)); + if (U_FAILURE(status)) { + errln("Unable to create format object - %s", u_errorName(status)); + } + FieldPosition fp(UNUM_INTEGER_FIELD); + UnicodeString result; + cdf->format(1234567.0, result, fp); + UnicodeString subString = result.tempSubString(fp.getBeginIndex(), fp.getEndIndex() - fp.getBeginIndex()); + if (subString != UnicodeString("1", -1, US_INV)) { + errln(UnicodeString("Expected 1, got ") + subString); + } +} + +void CompactDecimalFormatTest::TestCsShort() { + CheckLocale("cs", UNUM_SHORT, kCsShort, LENGTHOF(kCsShort)); +} + +void CompactDecimalFormatTest::TestSkLong() { + // In CLDR we have: + // 1000 { + // few{"0"} + // one{"0"} + // other{"0"} + CheckLocale("sk", UNUM_LONG, kSkLong, LENGTHOF(kSkLong)); +} + +void CompactDecimalFormatTest::TestSwahiliShortNegative() { + CheckLocale("sw", UNUM_SHORT, kSwahiliShortNegative, LENGTHOF(kSwahiliShortNegative)); +} + +void CompactDecimalFormatTest::TestArabicLong() { + CheckLocale("ar", UNUM_LONG, kArabicLong, LENGTHOF(kArabicLong)); +} + +void CompactDecimalFormatTest::TestSignificantDigits() { + UErrorCode status = U_ZERO_ERROR; + LocalPointer cdf(CompactDecimalFormat::createInstance("en", UNUM_SHORT, status)); + if (U_FAILURE(status)) { + errln("Unable to create format object - %s", u_errorName(status)); + return; + } + UnicodeString actual; + cdf->format(123456.0, actual); + // We expect 3 significant digits by default + UnicodeString expected("123K", -1, US_INV); + if (actual != expected) { + errln(UnicodeString("Fail: Expected: ") + expected + UnicodeString(" Got: ") + actual); + } +} + +void CompactDecimalFormatTest::CheckLocale(const Locale& locale, UNumberCompactStyle style, const ExpectedResult* expectedResults, int32_t expectedResultLength) { + UErrorCode status = U_ZERO_ERROR; + LocalPointer cdf(createCDFInstance(locale, style, status)); + if (U_FAILURE(status)) { + errln("Unable to create format object - %s", u_errorName(status)); + return; + } + char description[256]; + sprintf(description,"%s - %s", locale.getName(), StyleStr(style)); + for (int32_t i = 0; i < expectedResultLength; i++) { + CheckExpectedResult(cdf.getAlias(), &expectedResults[i], description); + } +} + +void CompactDecimalFormatTest::CheckExpectedResult( + const CompactDecimalFormat* cdf, const ExpectedResult* expectedResult, const char* description) { + UnicodeString actual; + cdf->format(expectedResult->value, actual); + UnicodeString expected(expectedResult->expected, -1, US_INV); + expected = expected.unescape(); + if (actual != expected) { + errln(UnicodeString("Fail: Expected: ") + expected + + UnicodeString(" Got: ") + actual + + UnicodeString(" for: ") + UnicodeString(description)); + } +} + +CompactDecimalFormat* +CompactDecimalFormatTest::createCDFInstance(const Locale& locale, UNumberCompactStyle style, UErrorCode& status) { + CompactDecimalFormat* result = CompactDecimalFormat::createInstance(locale, style, status); + if (U_FAILURE(status)) { + return NULL; + } + // All tests are written for two significant digits, so we explicitly set here + // in case default significant digits change. + result->setMaximumSignificantDigits(2); + return result; +} + +const char *CompactDecimalFormatTest::StyleStr(UNumberCompactStyle style) { + if (style == UNUM_SHORT) { + return kShortStr; + } + return kLongStr; +} + +extern IntlTest *createCompactDecimalFormatTest() { + return new CompactDecimalFormatTest(); +} diff --git a/icu4c/source/test/intltest/intltest.vcxproj b/icu4c/source/test/intltest/intltest.vcxproj index ce194ac4a26..f93621b1d5f 100644 --- a/icu4c/source/test/intltest/intltest.vcxproj +++ b/icu4c/source/test/intltest/intltest.vcxproj @@ -281,6 +281,7 @@ + diff --git a/icu4c/source/test/intltest/itformat.cpp b/icu4c/source/test/intltest/itformat.cpp index a4018ffdc47..d5c61107ec8 100644 --- a/icu4c/source/test/intltest/itformat.cpp +++ b/icu4c/source/test/intltest/itformat.cpp @@ -58,6 +58,7 @@ #include "dcfmtest.h" // DecimalFormatTest #include "listformattertest.h" // ListFormatterTest +extern IntlTest *createCompactDecimalFormatTest(); extern IntlTest *createGenderInfoTest(); #define TESTCLASS(id, TestClass) \ @@ -145,6 +146,15 @@ void IntlTestFormat::runIndexedTest( int32_t index, UBool exec, const char* &nam callTest(*test, par); } break; + case 44: + name = "CompactDecimalFormatTest"; + if (exec) { + logln("CompactDecimalFormatTest test---"); + logln((UnicodeString)""); + LocalPointer test(createCompactDecimalFormatTest()); + callTest(*test, par); + } + break; default: name = ""; break; //needed to end loop } if (exec) {