// © 2017 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html #include "unicode/utypes.h" #if !UCONFIG_NO_FORMATTING #include "charstr.h" #include #include #include #include "unicode/unum.h" #include "unicode/numberformatter.h" #include "number_asformat.h" #include "number_types.h" #include "number_utils.h" #include "numbertest.h" #include "unicode/utypes.h" #include "number_utypes.h" using number::impl::UFormattedNumberData; // Horrible workaround for the lack of a status code in the constructor... // (Also affects numbertest_range.cpp) UErrorCode globalNumberFormatterApiTestStatus = U_ZERO_ERROR; NumberFormatterApiTest::NumberFormatterApiTest() : NumberFormatterApiTest(globalNumberFormatterApiTestStatus) { } NumberFormatterApiTest::NumberFormatterApiTest(UErrorCode& status) : USD(u"USD", status), GBP(u"GBP", status), CZK(u"CZK", status), CAD(u"CAD", status), ESP(u"ESP", status), PTE(u"PTE", status), RON(u"RON", status), CNY(u"CNY", status), FRENCH_SYMBOLS(Locale::getFrench(), status), SWISS_SYMBOLS(Locale("de-CH"), status), MYANMAR_SYMBOLS(Locale("my"), status) { // Check for error on the first MeasureUnit in case there is no data LocalPointer unit(MeasureUnit::createMeter(status)); if (U_FAILURE(status)) { dataerrln("%s %d status = %s", __FILE__, __LINE__, u_errorName(status)); return; } METER = *unit; DAY = *LocalPointer(MeasureUnit::createDay(status)); SQUARE_METER = *LocalPointer(MeasureUnit::createSquareMeter(status)); FAHRENHEIT = *LocalPointer(MeasureUnit::createFahrenheit(status)); SECOND = *LocalPointer(MeasureUnit::createSecond(status)); POUND = *LocalPointer(MeasureUnit::createPound(status)); SQUARE_MILE = *LocalPointer(MeasureUnit::createSquareMile(status)); JOULE = *LocalPointer(MeasureUnit::createJoule(status)); FURLONG = *LocalPointer(MeasureUnit::createFurlong(status)); KELVIN = *LocalPointer(MeasureUnit::createKelvin(status)); MATHSANB = *LocalPointer(NumberingSystem::createInstanceByName("mathsanb", status)); LATN = *LocalPointer(NumberingSystem::createInstanceByName("latn", status)); } void NumberFormatterApiTest::runIndexedTest(int32_t index, UBool exec, const char*& name, char*) { if (exec) { logln("TestSuite NumberFormatterApiTest: "); } TESTCASE_AUTO_BEGIN; TESTCASE_AUTO(notationSimple); TESTCASE_AUTO(notationScientific); TESTCASE_AUTO(notationCompact); TESTCASE_AUTO(unitMeasure); TESTCASE_AUTO(unitCompoundMeasure); TESTCASE_AUTO(unitCurrency); TESTCASE_AUTO(unitPercent); if (!quick) { // Slow test: run in exhaustive mode only TESTCASE_AUTO(percentParity); } TESTCASE_AUTO(roundingFraction); TESTCASE_AUTO(roundingFigures); TESTCASE_AUTO(roundingFractionFigures); TESTCASE_AUTO(roundingOther); TESTCASE_AUTO(grouping); TESTCASE_AUTO(padding); TESTCASE_AUTO(integerWidth); TESTCASE_AUTO(symbols); // TODO: Add this method if currency symbols override support is added. //TESTCASE_AUTO(symbolsOverride); TESTCASE_AUTO(sign); TESTCASE_AUTO(signNearZero); TESTCASE_AUTO(signCoverage); TESTCASE_AUTO(decimal); TESTCASE_AUTO(scale); TESTCASE_AUTO(locale); TESTCASE_AUTO(skeletonUserGuideExamples); TESTCASE_AUTO(formatTypes); TESTCASE_AUTO(fieldPositionLogic); TESTCASE_AUTO(fieldPositionCoverage); TESTCASE_AUTO(toFormat); TESTCASE_AUTO(errors); if (!quick) { // Slow test: run in exhaustive mode only // (somewhat slow to check all permutations of settings) TESTCASE_AUTO(validRanges); } TESTCASE_AUTO(copyMove); TESTCASE_AUTO(localPointerCAPI); TESTCASE_AUTO(toObject); TESTCASE_AUTO(toDecimalNumber); TESTCASE_AUTO_END; } void NumberFormatterApiTest::notationSimple() { assertFormatDescending( u"Basic", u"", u"", NumberFormatter::with(), Locale::getEnglish(), u"87,650", u"8,765", u"876.5", u"87.65", u"8.765", u"0.8765", u"0.08765", u"0.008765", u"0"); assertFormatDescendingBig( u"Big Simple", u"notation-simple", u"", NumberFormatter::with().notation(Notation::simple()), Locale::getEnglish(), u"87,650,000", u"8,765,000", u"876,500", u"87,650", u"8,765", u"876.5", u"87.65", u"8.765", u"0"); assertFormatSingle( u"Basic with Negative Sign", u"", u"", NumberFormatter::with(), Locale::getEnglish(), -9876543.21, u"-9,876,543.21"); } void NumberFormatterApiTest::notationScientific() { assertFormatDescending( u"Scientific", u"scientific", u"E0", NumberFormatter::with().notation(Notation::scientific()), Locale::getEnglish(), u"8.765E4", u"8.765E3", u"8.765E2", u"8.765E1", u"8.765E0", u"8.765E-1", u"8.765E-2", u"8.765E-3", u"0E0"); assertFormatDescending( u"Engineering", u"engineering", u"EE0", NumberFormatter::with().notation(Notation::engineering()), Locale::getEnglish(), u"87.65E3", u"8.765E3", u"876.5E0", u"87.65E0", u"8.765E0", u"876.5E-3", u"87.65E-3", u"8.765E-3", u"0E0"); assertFormatDescending( u"Scientific sign always shown", u"scientific/sign-always", u"E+!0", NumberFormatter::with().notation( Notation::scientific().withExponentSignDisplay(UNumberSignDisplay::UNUM_SIGN_ALWAYS)), Locale::getEnglish(), u"8.765E+4", u"8.765E+3", u"8.765E+2", u"8.765E+1", u"8.765E+0", u"8.765E-1", u"8.765E-2", u"8.765E-3", u"0E+0"); assertFormatDescending( u"Scientific min exponent digits", u"scientific/*ee", u"E00", NumberFormatter::with().notation(Notation::scientific().withMinExponentDigits(2)), Locale::getEnglish(), u"8.765E04", u"8.765E03", u"8.765E02", u"8.765E01", u"8.765E00", u"8.765E-01", u"8.765E-02", u"8.765E-03", u"0E00"); assertFormatSingle( u"Scientific Negative", u"scientific", u"E0", NumberFormatter::with().notation(Notation::scientific()), Locale::getEnglish(), -1000000, u"-1E6"); assertFormatSingle( u"Scientific Infinity", u"scientific", u"E0", NumberFormatter::with().notation(Notation::scientific()), Locale::getEnglish(), -uprv_getInfinity(), u"-∞"); assertFormatSingle( u"Scientific NaN", u"scientific", u"E0", NumberFormatter::with().notation(Notation::scientific()), Locale::getEnglish(), uprv_getNaN(), u"NaN"); } void NumberFormatterApiTest::notationCompact() { assertFormatDescending( u"Compact Short", u"compact-short", u"K", NumberFormatter::with().notation(Notation::compactShort()), Locale::getEnglish(), u"88K", u"8.8K", u"876", u"88", u"8.8", u"0.88", u"0.088", u"0.0088", u"0"); assertFormatDescending( u"Compact Long", u"compact-long", u"KK", NumberFormatter::with().notation(Notation::compactLong()), Locale::getEnglish(), u"88 thousand", u"8.8 thousand", u"876", u"88", u"8.8", u"0.88", u"0.088", u"0.0088", u"0"); assertFormatDescending( u"Compact Short Currency", u"compact-short currency/USD", u"K currency/USD", NumberFormatter::with().notation(Notation::compactShort()).unit(USD), Locale::getEnglish(), u"$88K", u"$8.8K", u"$876", u"$88", u"$8.8", u"$0.88", u"$0.088", u"$0.0088", u"$0"); assertFormatDescending( u"Compact Short with ISO Currency", u"compact-short currency/USD unit-width-iso-code", u"K currency/USD unit-width-iso-code", NumberFormatter::with().notation(Notation::compactShort()) .unit(USD) .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_ISO_CODE), Locale::getEnglish(), u"USD 88K", u"USD 8.8K", u"USD 876", u"USD 88", u"USD 8.8", u"USD 0.88", u"USD 0.088", u"USD 0.0088", u"USD 0"); assertFormatDescending( u"Compact Short with Long Name Currency", u"compact-short currency/USD unit-width-full-name", u"K currency/USD unit-width-full-name", NumberFormatter::with().notation(Notation::compactShort()) .unit(USD) .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME), Locale::getEnglish(), u"88K US dollars", u"8.8K US dollars", u"876 US dollars", u"88 US dollars", u"8.8 US dollars", u"0.88 US dollars", u"0.088 US dollars", u"0.0088 US dollars", u"0 US dollars"); // Note: Most locales don't have compact long currency, so this currently falls back to short. // This test case should be fixed when proper compact long currency patterns are added. assertFormatDescending( u"Compact Long Currency", u"compact-long currency/USD", u"KK currency/USD", NumberFormatter::with().notation(Notation::compactLong()).unit(USD), Locale::getEnglish(), u"$88K", // should be something like "$88 thousand" u"$8.8K", u"$876", u"$88", u"$8.8", u"$0.88", u"$0.088", u"$0.0088", u"$0"); // Note: Most locales don't have compact long currency, so this currently falls back to short. // This test case should be fixed when proper compact long currency patterns are added. assertFormatDescending( u"Compact Long with ISO Currency", u"compact-long currency/USD unit-width-iso-code", u"KK currency/USD unit-width-iso-code", NumberFormatter::with().notation(Notation::compactLong()) .unit(USD) .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_ISO_CODE), Locale::getEnglish(), u"USD 88K", // should be something like "USD 88 thousand" u"USD 8.8K", u"USD 876", u"USD 88", u"USD 8.8", u"USD 0.88", u"USD 0.088", u"USD 0.0088", u"USD 0"); // TODO: This behavior could be improved and should be revisited. assertFormatDescending( u"Compact Long with Long Name Currency", u"compact-long currency/USD unit-width-full-name", u"KK currency/USD unit-width-full-name", NumberFormatter::with().notation(Notation::compactLong()) .unit(USD) .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME), Locale::getEnglish(), u"88 thousand US dollars", u"8.8 thousand US dollars", u"876 US dollars", u"88 US dollars", u"8.8 US dollars", u"0.88 US dollars", u"0.088 US dollars", u"0.0088 US dollars", u"0 US dollars"); assertFormatSingle( u"Compact Plural One", u"compact-long", u"KK", NumberFormatter::with().notation(Notation::compactLong()), Locale::createFromName("es"), 1000000, u"1 millón"); assertFormatSingle( u"Compact Plural Other", u"compact-long", u"KK", NumberFormatter::with().notation(Notation::compactLong()), Locale::createFromName("es"), 2000000, u"2 millones"); assertFormatSingle( u"Compact with Negative Sign", u"compact-short", u"K", NumberFormatter::with().notation(Notation::compactShort()), Locale::getEnglish(), -9876543.21, u"-9.9M"); assertFormatSingle( u"Compact Rounding", u"compact-short", u"K", NumberFormatter::with().notation(Notation::compactShort()), Locale::getEnglish(), 990000, u"990K"); assertFormatSingle( u"Compact Rounding", u"compact-short", u"K", NumberFormatter::with().notation(Notation::compactShort()), Locale::getEnglish(), 999000, u"999K"); assertFormatSingle( u"Compact Rounding", u"compact-short", u"K", NumberFormatter::with().notation(Notation::compactShort()), Locale::getEnglish(), 999900, u"1M"); assertFormatSingle( u"Compact Rounding", u"compact-short", u"K", NumberFormatter::with().notation(Notation::compactShort()), Locale::getEnglish(), 9900000, u"9.9M"); assertFormatSingle( u"Compact Rounding", u"compact-short", u"K", NumberFormatter::with().notation(Notation::compactShort()), Locale::getEnglish(), 9990000, u"10M"); assertFormatSingle( u"Compact in zh-Hant-HK", u"compact-short", u"K", NumberFormatter::with().notation(Notation::compactShort()), Locale("zh-Hant-HK"), 1e7, u"10M"); assertFormatSingle( u"Compact in zh-Hant", u"compact-short", u"K", NumberFormatter::with().notation(Notation::compactShort()), Locale("zh-Hant"), 1e7, u"1000\u842C"); assertFormatSingle( u"Compact Infinity", u"compact-short", u"K", NumberFormatter::with().notation(Notation::compactShort()), Locale::getEnglish(), -uprv_getInfinity(), u"-∞"); assertFormatSingle( u"Compact NaN", u"compact-short", u"K", NumberFormatter::with().notation(Notation::compactShort()), Locale::getEnglish(), uprv_getNaN(), u"NaN"); // NOTE: There is no API for compact custom data in C++ // and thus no "Compact Somali No Figure" test } void NumberFormatterApiTest::unitMeasure() { assertFormatDescending( u"Meters Short and unit() method", u"measure-unit/length-meter", u"unit/meter", NumberFormatter::with().unit(MeasureUnit::getMeter()), Locale::getEnglish(), u"87,650 m", u"8,765 m", u"876.5 m", u"87.65 m", u"8.765 m", u"0.8765 m", u"0.08765 m", u"0.008765 m", u"0 m"); assertFormatDescending( u"Meters Long and adoptUnit() method", u"measure-unit/length-meter unit-width-full-name", u"unit/meter unit-width-full-name", NumberFormatter::with().adoptUnit(new MeasureUnit(METER)) .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME), Locale::getEnglish(), u"87,650 meters", u"8,765 meters", u"876.5 meters", u"87.65 meters", u"8.765 meters", u"0.8765 meters", u"0.08765 meters", u"0.008765 meters", u"0 meters"); assertFormatDescending( u"Compact Meters Long", u"compact-long measure-unit/length-meter unit-width-full-name", u"KK unit/meter unit-width-full-name", NumberFormatter::with().notation(Notation::compactLong()) .unit(METER) .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME), Locale::getEnglish(), u"88 thousand meters", u"8.8 thousand meters", u"876 meters", u"88 meters", u"8.8 meters", u"0.88 meters", u"0.088 meters", u"0.0088 meters", u"0 meters"); // TODO: Implement Measure in C++ // assertFormatSingleMeasure( // u"Meters with Measure Input", // NumberFormatter::with().unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME), // Locale::getEnglish(), // new Measure(5.43, new MeasureUnit(METER)), // u"5.43 meters"); // TODO: Implement Measure in C++ // assertFormatSingleMeasure( // u"Measure format method takes precedence over fluent chain", // NumberFormatter::with().unit(METER), // Locale::getEnglish(), // new Measure(5.43, USD), // u"$5.43"); assertFormatSingle( u"Meters with Negative Sign", u"measure-unit/length-meter", u"unit/meter", NumberFormatter::with().unit(METER), Locale::getEnglish(), -9876543.21, u"-9,876,543.21 m"); // The locale string "सान" appears only in brx.txt: assertFormatSingle( u"Interesting Data Fallback 1", u"measure-unit/duration-day unit-width-full-name", u"unit/day unit-width-full-name", NumberFormatter::with().unit(DAY).unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME), Locale::createFromName("brx"), 5.43, u"5.43 सान"); // Requires following the alias from unitsNarrow to unitsShort: assertFormatSingle( u"Interesting Data Fallback 2", u"measure-unit/duration-day unit-width-narrow", u"unit/day unit-width-narrow", NumberFormatter::with().unit(DAY).unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW), Locale::createFromName("brx"), 5.43, u"5.43 d"); // en_001.txt has a unitsNarrow/area/square-meter table, but table does not contain the OTHER unit, // requiring fallback to the root. assertFormatSingle( u"Interesting Data Fallback 3", u"measure-unit/area-square-meter unit-width-narrow", u"unit/square-meter unit-width-narrow", NumberFormatter::with().unit(SQUARE_METER).unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW), Locale::createFromName("en-GB"), 5.43, u"5.43m²"); // Try accessing a narrow unit directly from root. assertFormatSingle( u"Interesting Data Fallback 4", u"measure-unit/area-square-meter unit-width-narrow", u"unit/square-meter unit-width-narrow", NumberFormatter::with().unit(SQUARE_METER).unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW), Locale::createFromName("root"), 5.43, u"5.43 m²"); // es_US has "{0}°" for unitsNarrow/temperature/FAHRENHEIT. // NOTE: This example is in the documentation. assertFormatSingle( u"Difference between Narrow and Short (Narrow Version)", u"measure-unit/temperature-fahrenheit unit-width-narrow", u"unit/fahrenheit unit-width-narrow", NumberFormatter::with().unit(FAHRENHEIT).unitWidth(UNUM_UNIT_WIDTH_NARROW), Locale("es-US"), 5.43, u"5.43°"); assertFormatSingle( u"Difference between Narrow and Short (Short Version)", u"measure-unit/temperature-fahrenheit unit-width-short", u"unit/fahrenheit unit-width-short", NumberFormatter::with().unit(FAHRENHEIT).unitWidth(UNUM_UNIT_WIDTH_SHORT), Locale("es-US"), 5.43, u"5.43 °F"); assertFormatSingle( u"MeasureUnit form without {0} in CLDR pattern", u"measure-unit/temperature-kelvin unit-width-full-name", u"unit/kelvin unit-width-full-name", NumberFormatter::with().unit(KELVIN).unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME), Locale("es-MX"), 1, u"kelvin"); assertFormatSingle( u"MeasureUnit form without {0} in CLDR pattern and wide base form", u"measure-unit/temperature-kelvin .00000000000000000000 unit-width-full-name", u"unit/kelvin .00000000000000000000 unit-width-full-name", NumberFormatter::with().precision(Precision::fixedFraction(20)) .unit(KELVIN) .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME), Locale("es-MX"), 1, u"kelvin"); assertFormatSingle( u"Person unit not in short form", u"measure-unit/duration-year-person unit-width-full-name", u"unit/year-person unit-width-full-name", NumberFormatter::with().unit(MeasureUnit::getYearPerson()) .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME), Locale("es-MX"), 5, u"5 a\u00F1os"); } void NumberFormatterApiTest::unitCompoundMeasure() { assertFormatDescending( u"Meters Per Second Short (unit that simplifies) and perUnit method", u"measure-unit/length-meter per-measure-unit/duration-second", u"unit/meter-per-second", NumberFormatter::with().unit(METER).perUnit(SECOND), Locale::getEnglish(), u"87,650 m/s", u"8,765 m/s", u"876.5 m/s", u"87.65 m/s", u"8.765 m/s", u"0.8765 m/s", u"0.08765 m/s", u"0.008765 m/s", u"0 m/s"); assertFormatDescending( u"Pounds Per Square Mile Short (secondary unit has per-format) and adoptPerUnit method", u"measure-unit/mass-pound per-measure-unit/area-square-mile", u"unit/pound-per-square-mile", NumberFormatter::with().unit(POUND).adoptPerUnit(new MeasureUnit(SQUARE_MILE)), Locale::getEnglish(), u"87,650 lb/mi²", u"8,765 lb/mi²", u"876.5 lb/mi²", u"87.65 lb/mi²", u"8.765 lb/mi²", u"0.8765 lb/mi²", u"0.08765 lb/mi²", u"0.008765 lb/mi²", u"0 lb/mi²"); assertFormatDescending( u"Joules Per Furlong Short (unit with no simplifications or special patterns)", u"measure-unit/energy-joule per-measure-unit/length-furlong", u"unit/joule-per-furlong", NumberFormatter::with().unit(JOULE).perUnit(FURLONG), Locale::getEnglish(), u"87,650 J/fur", u"8,765 J/fur", u"876.5 J/fur", u"87.65 J/fur", u"8.765 J/fur", u"0.8765 J/fur", u"0.08765 J/fur", u"0.008765 J/fur", u"0 J/fur"); // TODO(ICU-20941): Support constructions such as this one. // assertFormatDescending( // u"Joules Per Furlong Short with unit identifier via API", // u"measure-unit/energy-joule per-measure-unit/length-furlong", // u"unit/joule-per-furlong", // NumberFormatter::with().unit(MeasureUnit::forIdentifier("joule-per-furlong", status)), // Locale::getEnglish(), // u"87,650 J/fur", // u"8,765 J/fur", // u"876.5 J/fur", // u"87.65 J/fur", // u"8.765 J/fur", // u"0.8765 J/fur", // u"0.08765 J/fur", // u"0.008765 J/fur", // u"0 J/fur"); } void NumberFormatterApiTest::unitCurrency() { assertFormatDescending( u"Currency", u"currency/GBP", u"currency/GBP", NumberFormatter::with().unit(GBP), Locale::getEnglish(), u"£87,650.00", u"£8,765.00", u"£876.50", u"£87.65", u"£8.76", u"£0.88", u"£0.09", u"£0.01", u"£0.00"); assertFormatDescending( u"Currency ISO", u"currency/GBP unit-width-iso-code", u"currency/GBP unit-width-iso-code", NumberFormatter::with().unit(GBP).unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_ISO_CODE), Locale::getEnglish(), u"GBP 87,650.00", u"GBP 8,765.00", u"GBP 876.50", u"GBP 87.65", u"GBP 8.76", u"GBP 0.88", u"GBP 0.09", u"GBP 0.01", u"GBP 0.00"); assertFormatDescending( u"Currency Long Name", u"currency/GBP unit-width-full-name", u"currency/GBP unit-width-full-name", NumberFormatter::with().unit(GBP).unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME), Locale::getEnglish(), u"87,650.00 British pounds", u"8,765.00 British pounds", u"876.50 British pounds", u"87.65 British pounds", u"8.76 British pounds", u"0.88 British pounds", u"0.09 British pounds", u"0.01 British pounds", u"0.00 British pounds"); assertFormatDescending( u"Currency Hidden", u"currency/GBP unit-width-hidden", u"currency/GBP unit-width-hidden", NumberFormatter::with().unit(GBP).unitWidth(UNUM_UNIT_WIDTH_HIDDEN), Locale::getEnglish(), u"87,650.00", u"8,765.00", u"876.50", u"87.65", u"8.76", u"0.88", u"0.09", u"0.01", u"0.00"); // TODO: Implement Measure in C++ // assertFormatSingleMeasure( // u"Currency with CurrencyAmount Input", // NumberFormatter::with(), // Locale::getEnglish(), // new CurrencyAmount(5.43, GBP), // u"£5.43"); // TODO: Enable this test when DecimalFormat wrapper is done. // assertFormatSingle( // u"Currency Long Name from Pattern Syntax", NumberFormatter.fromDecimalFormat( // PatternStringParser.parseToProperties("0 ¤¤¤"), // DecimalFormatSymbols.getInstance(Locale::getEnglish()), // null).unit(GBP), Locale::getEnglish(), 1234567.89, u"1234568 British pounds"); assertFormatSingle( u"Currency with Negative Sign", u"currency/GBP", u"currency/GBP", NumberFormatter::with().unit(GBP), Locale::getEnglish(), -9876543.21, u"-£9,876,543.21"); // The full currency symbol is not shown in NARROW format. // NOTE: This example is in the documentation. assertFormatSingle( u"Currency Difference between Narrow and Short (Narrow Version)", u"currency/USD unit-width-narrow", u"currency/USD unit-width-narrow", NumberFormatter::with().unit(USD).unitWidth(UNUM_UNIT_WIDTH_NARROW), Locale("en-CA"), 5.43, u"$5.43"); assertFormatSingle( u"Currency Difference between Narrow and Short (Short Version)", u"currency/USD unit-width-short", u"currency/USD unit-width-short", NumberFormatter::with().unit(USD).unitWidth(UNUM_UNIT_WIDTH_SHORT), Locale("en-CA"), 5.43, u"US$5.43"); assertFormatSingle( u"Currency-dependent format (Control)", u"currency/USD unit-width-short", u"currency/USD unit-width-short", NumberFormatter::with().unit(USD).unitWidth(UNUM_UNIT_WIDTH_SHORT), Locale("ca"), 444444.55, u"444.444,55 USD"); assertFormatSingle( u"Currency-dependent format (Test)", u"currency/ESP unit-width-short", u"currency/ESP unit-width-short", NumberFormatter::with().unit(ESP).unitWidth(UNUM_UNIT_WIDTH_SHORT), Locale("ca"), 444444.55, u"₧ 444.445"); assertFormatSingle( u"Currency-dependent symbols (Control)", u"currency/USD unit-width-short", u"currency/USD unit-width-short", NumberFormatter::with().unit(USD).unitWidth(UNUM_UNIT_WIDTH_SHORT), Locale("pt-PT"), 444444.55, u"444 444,55 US$"); // NOTE: This is a bit of a hack on CLDR's part. They set the currency symbol to U+200B (zero- // width space), and they set the decimal separator to the $ symbol. assertFormatSingle( u"Currency-dependent symbols (Test Short)", u"currency/PTE unit-width-short", u"currency/PTE unit-width-short", NumberFormatter::with().unit(PTE).unitWidth(UNUM_UNIT_WIDTH_SHORT), Locale("pt-PT"), 444444.55, u"444,444$55 \u200B"); assertFormatSingle( u"Currency-dependent symbols (Test Narrow)", u"currency/PTE unit-width-narrow", u"currency/PTE unit-width-narrow", NumberFormatter::with().unit(PTE).unitWidth(UNUM_UNIT_WIDTH_NARROW), Locale("pt-PT"), 444444.55, u"444,444$55 \u200B"); assertFormatSingle( u"Currency-dependent symbols (Test ISO Code)", u"currency/PTE unit-width-iso-code", u"currency/PTE unit-width-iso-code", NumberFormatter::with().unit(PTE).unitWidth(UNUM_UNIT_WIDTH_ISO_CODE), Locale("pt-PT"), 444444.55, u"444,444$55 PTE"); assertFormatSingle( u"Plural form depending on visible digits (ICU-20499)", u"currency/RON unit-width-full-name", u"currency/RON unit-width-full-name", NumberFormatter::with().unit(RON).unitWidth(UNUM_UNIT_WIDTH_FULL_NAME), Locale("ro-RO"), 24, u"24,00 lei românești"); assertFormatSingle( u"Currency spacing in suffix (ICU-20954)", u"currency/CNY", u"currency/CNY", NumberFormatter::with().unit(CNY), Locale("lu"), 123.12, u"123,12 CN¥"); } void NumberFormatterApiTest::unitPercent() { assertFormatDescending( u"Percent", u"percent", u"%", NumberFormatter::with().unit(NoUnit::percent()), Locale::getEnglish(), u"87,650%", u"8,765%", u"876.5%", u"87.65%", u"8.765%", u"0.8765%", u"0.08765%", u"0.008765%", u"0%"); assertFormatDescending( u"Permille", u"permille", u"permille", NumberFormatter::with().unit(NoUnit::permille()), Locale::getEnglish(), u"87,650‰", u"8,765‰", u"876.5‰", u"87.65‰", u"8.765‰", u"0.8765‰", u"0.08765‰", u"0.008765‰", u"0‰"); assertFormatSingle( u"NoUnit Base", u"base-unit", u"", NumberFormatter::with().unit(NoUnit::base()), Locale::getEnglish(), 51423, u"51,423"); assertFormatSingle( u"Percent with Negative Sign", u"percent", u"%", NumberFormatter::with().unit(NoUnit::percent()), Locale::getEnglish(), -98.7654321, u"-98.765432%"); } void NumberFormatterApiTest::percentParity() { IcuTestErrorCode status(*this, "percentParity"); UnlocalizedNumberFormatter uNoUnitPercent = NumberFormatter::with().unit(NoUnit::percent()); UnlocalizedNumberFormatter uNoUnitPermille = NumberFormatter::with().unit(NoUnit::permille()); UnlocalizedNumberFormatter uMeasurePercent = NumberFormatter::with().unit(MeasureUnit::getPercent()); UnlocalizedNumberFormatter uMeasurePermille = NumberFormatter::with().unit(MeasureUnit::getPermille()); int32_t localeCount; auto locales = Locale::getAvailableLocales(localeCount); for (int32_t i=0; i format(lnf.toFormat(status), status); FieldPosition fpos(UNUM_DECIMAL_SEPARATOR_FIELD); UnicodeString sb; format->format(514.23, sb, fpos, status); assertEquals("Should correctly format number", u"514,230", sb); assertEquals("Should find decimal separator", 3, fpos.getBeginIndex()); assertEquals("Should find end of decimal separator", 4, fpos.getEndIndex()); assertEquals( "ICU Format should round-trip", lnf.toSkeleton(status), dynamic_cast(format.getAlias())->getNumberFormatter() .toSkeleton(status)); UFormattedNumberData result; result.quantity.setToDouble(514.23); lnf.formatImpl(&result, status); FieldPositionIterator fpi1; { FieldPositionIteratorHandler fpih(&fpi1, status); result.getAllFieldPositions(fpih, status); } FieldPositionIterator fpi2; format->format(514.23, sb.remove(), &fpi2, status); assertTrue("Should produce same field position iterator", fpi1 == fpi2); } void NumberFormatterApiTest::errors() { LocalizedNumberFormatter lnf = NumberFormatter::withLocale(Locale::getEnglish()).precision( Precision::fixedFraction( -1)); // formatInt UErrorCode status = U_ZERO_ERROR; FormattedNumber fn = lnf.formatInt(1, status); assertEquals( "Should fail in formatInt method with error code for rounding", U_NUMBER_ARG_OUTOFBOUNDS_ERROR, status); // formatDouble status = U_ZERO_ERROR; fn = lnf.formatDouble(1.0, status); assertEquals( "Should fail in formatDouble method with error code for rounding", U_NUMBER_ARG_OUTOFBOUNDS_ERROR, status); // formatDecimal (decimal error) status = U_ZERO_ERROR; fn = NumberFormatter::withLocale("en").formatDecimal("1x2", status); assertEquals( "Should fail in formatDecimal method with error code for decimal number syntax", U_DECIMAL_NUMBER_SYNTAX_ERROR, status); // formatDecimal (setting error) status = U_ZERO_ERROR; fn = lnf.formatDecimal("1.0", status); assertEquals( "Should fail in formatDecimal method with error code for rounding", U_NUMBER_ARG_OUTOFBOUNDS_ERROR, status); // Skeleton string status = U_ZERO_ERROR; UnicodeString output = lnf.toSkeleton(status); assertEquals( "Should fail on toSkeleton terminal method with correct error code", U_NUMBER_ARG_OUTOFBOUNDS_ERROR, status); assertTrue( "Terminal toSkeleton on error object should be bogus", output.isBogus()); // FieldPosition (constrained category) status = U_ZERO_ERROR; ConstrainedFieldPosition fp; fp.constrainCategory(UFIELD_CATEGORY_NUMBER); fn.nextPosition(fp, status); assertEquals( "Should fail on FieldPosition terminal method with correct error code", U_NUMBER_ARG_OUTOFBOUNDS_ERROR, status); // FieldPositionIterator (no constraints) status = U_ZERO_ERROR; fp.reset(); fn.nextPosition(fp, status); assertEquals( "Should fail on FieldPositoinIterator terminal method with correct error code", U_NUMBER_ARG_OUTOFBOUNDS_ERROR, status); // Appendable status = U_ZERO_ERROR; UnicodeStringAppendable appendable(output.remove()); fn.appendTo(appendable, status); assertEquals( "Should fail on Appendable terminal method with correct error code", U_NUMBER_ARG_OUTOFBOUNDS_ERROR, status); // UnicodeString status = U_ZERO_ERROR; output = fn.toString(status); assertEquals( "Should fail on UnicodeString terminal method with correct error code", U_NUMBER_ARG_OUTOFBOUNDS_ERROR, status); assertTrue( "Terminal UnicodeString on error object should be bogus", output.isBogus()); // CopyErrorTo status = U_ZERO_ERROR; lnf.copyErrorTo(status); assertEquals( "Should fail since rounder is not legal with correct error code", U_NUMBER_ARG_OUTOFBOUNDS_ERROR, status); } void NumberFormatterApiTest::validRanges() { #define EXPECTED_MAX_INT_FRAC_SIG 999 #define VALID_RANGE_ASSERT(status, method, lowerBound, argument) UPRV_BLOCK_MACRO_BEGIN { \ UErrorCode expectedStatus = ((lowerBound <= argument) && (argument <= EXPECTED_MAX_INT_FRAC_SIG)) \ ? U_ZERO_ERROR \ : U_NUMBER_ARG_OUTOFBOUNDS_ERROR; \ assertEquals( \ UnicodeString(u"Incorrect status for " #method " on input ") \ + Int64ToUnicodeString(argument), \ expectedStatus, \ status); \ } UPRV_BLOCK_MACRO_END #define VALID_RANGE_ONEARG(setting, method, lowerBound) UPRV_BLOCK_MACRO_BEGIN { \ for (int32_t argument = -2; argument <= EXPECTED_MAX_INT_FRAC_SIG + 2; argument++) { \ UErrorCode status = U_ZERO_ERROR; \ NumberFormatter::with().setting(method(argument)).copyErrorTo(status); \ VALID_RANGE_ASSERT(status, method, lowerBound, argument); \ } \ } UPRV_BLOCK_MACRO_END #define VALID_RANGE_TWOARGS(setting, method, lowerBound) UPRV_BLOCK_MACRO_BEGIN { \ for (int32_t argument = -2; argument <= EXPECTED_MAX_INT_FRAC_SIG + 2; argument++) { \ UErrorCode status = U_ZERO_ERROR; \ /* Pass EXPECTED_MAX_INT_FRAC_SIG as the second argument so arg1 <= arg2 in expected cases */ \ NumberFormatter::with().setting(method(argument, EXPECTED_MAX_INT_FRAC_SIG)).copyErrorTo(status); \ VALID_RANGE_ASSERT(status, method, lowerBound, argument); \ status = U_ZERO_ERROR; \ /* Pass lowerBound as the first argument so arg1 <= arg2 in expected cases */ \ NumberFormatter::with().setting(method(lowerBound, argument)).copyErrorTo(status); \ VALID_RANGE_ASSERT(status, method, lowerBound, argument); \ /* Check that first argument must be less than or equal to second argument */ \ NumberFormatter::with().setting(method(argument, argument - 1)).copyErrorTo(status); \ assertEquals("Incorrect status for " #method " on max < min input", \ U_NUMBER_ARG_OUTOFBOUNDS_ERROR, \ status); \ } \ } UPRV_BLOCK_MACRO_END VALID_RANGE_ONEARG(precision, Precision::fixedFraction, 0); VALID_RANGE_ONEARG(precision, Precision::minFraction, 0); VALID_RANGE_ONEARG(precision, Precision::maxFraction, 0); VALID_RANGE_TWOARGS(precision, Precision::minMaxFraction, 0); VALID_RANGE_ONEARG(precision, Precision::fixedSignificantDigits, 1); VALID_RANGE_ONEARG(precision, Precision::minSignificantDigits, 1); VALID_RANGE_ONEARG(precision, Precision::maxSignificantDigits, 1); VALID_RANGE_TWOARGS(precision, Precision::minMaxSignificantDigits, 1); VALID_RANGE_ONEARG(precision, Precision::fixedFraction(1).withMinDigits, 1); VALID_RANGE_ONEARG(precision, Precision::fixedFraction(1).withMaxDigits, 1); VALID_RANGE_ONEARG(notation, Notation::scientific().withMinExponentDigits, 1); VALID_RANGE_ONEARG(integerWidth, IntegerWidth::zeroFillTo, 0); VALID_RANGE_ONEARG(integerWidth, IntegerWidth::zeroFillTo(0).truncateAt, -1); } void NumberFormatterApiTest::copyMove() { IcuTestErrorCode status(*this, "copyMove"); // Default constructors LocalizedNumberFormatter l1; assertEquals("Initial behavior", u"10", l1.formatInt(10, status).toString(status), true); if (status.errDataIfFailureAndReset()) { return; } assertEquals("Initial call count", 1, l1.getCallCount()); assertTrue("Initial compiled", l1.getCompiled() == nullptr); // Setup l1 = NumberFormatter::withLocale("en").unit(NoUnit::percent()).threshold(3); assertEquals("Initial behavior", u"10%", l1.formatInt(10, status).toString(status)); assertEquals("Initial call count", 1, l1.getCallCount()); assertTrue("Initial compiled", l1.getCompiled() == nullptr); l1.formatInt(123, status); assertEquals("Still not compiled", 2, l1.getCallCount()); assertTrue("Still not compiled", l1.getCompiled() == nullptr); l1.formatInt(123, status); assertEquals("Compiled", u"10%", l1.formatInt(10, status).toString(status)); assertEquals("Compiled", INT32_MIN, l1.getCallCount()); assertTrue("Compiled", l1.getCompiled() != nullptr); // Copy constructor LocalizedNumberFormatter l2 = l1; assertEquals("[constructor] Copy behavior", u"10%", l2.formatInt(10, status).toString(status)); assertEquals("[constructor] Copy should not have compiled state", 1, l2.getCallCount()); assertTrue("[constructor] Copy should not have compiled state", l2.getCompiled() == nullptr); // Move constructor LocalizedNumberFormatter l3 = std::move(l1); assertEquals("[constructor] Move behavior", u"10%", l3.formatInt(10, status).toString(status)); assertEquals("[constructor] Move *should* have compiled state", INT32_MIN, l3.getCallCount()); assertTrue("[constructor] Move *should* have compiled state", l3.getCompiled() != nullptr); assertEquals("[constructor] Source should be reset after move", 0, l1.getCallCount()); assertTrue("[constructor] Source should be reset after move", l1.getCompiled() == nullptr); // Reset l1 and l2 to check for macro-props copying for behavior testing // Make the test more interesting: also warm them up with a compiled formatter. l1 = NumberFormatter::withLocale("en"); l1.formatInt(1, status); l1.formatInt(1, status); l1.formatInt(1, status); l2 = NumberFormatter::withLocale("en"); l2.formatInt(1, status); l2.formatInt(1, status); l2.formatInt(1, status); // Copy assignment l1 = l3; assertEquals("[assignment] Copy behavior", u"10%", l1.formatInt(10, status).toString(status)); assertEquals("[assignment] Copy should not have compiled state", 1, l1.getCallCount()); assertTrue("[assignment] Copy should not have compiled state", l1.getCompiled() == nullptr); // Move assignment l2 = std::move(l3); assertEquals("[assignment] Move behavior", u"10%", l2.formatInt(10, status).toString(status)); assertEquals("[assignment] Move *should* have compiled state", INT32_MIN, l2.getCallCount()); assertTrue("[assignment] Move *should* have compiled state", l2.getCompiled() != nullptr); assertEquals("[assignment] Source should be reset after move", 0, l3.getCallCount()); assertTrue("[assignment] Source should be reset after move", l3.getCompiled() == nullptr); // Coverage tests for UnlocalizedNumberFormatter UnlocalizedNumberFormatter u1; assertEquals("Default behavior", u"10", u1.locale("en").formatInt(10, status).toString(status)); u1 = u1.unit(NoUnit::percent()); assertEquals("Copy assignment", u"10%", u1.locale("en").formatInt(10, status).toString(status)); UnlocalizedNumberFormatter u2 = u1; assertEquals("Copy constructor", u"10%", u2.locale("en").formatInt(10, status).toString(status)); UnlocalizedNumberFormatter u3 = std::move(u1); assertEquals("Move constructor", u"10%", u3.locale("en").formatInt(10, status).toString(status)); u1 = NumberFormatter::with(); u1 = std::move(u2); assertEquals("Move assignment", u"10%", u1.locale("en").formatInt(10, status).toString(status)); // FormattedNumber move operators FormattedNumber result = l1.formatInt(10, status); assertEquals("FormattedNumber move constructor", u"10%", result.toString(status)); result = l1.formatInt(20, status); assertEquals("FormattedNumber move assignment", u"20%", result.toString(status)); } void NumberFormatterApiTest::localPointerCAPI() { // NOTE: This is also the sample code in unumberformatter.h UErrorCode ec = U_ZERO_ERROR; // Setup: LocalUNumberFormatterPointer uformatter(unumf_openForSkeletonAndLocale(u"percent", -1, "en", &ec)); LocalUFormattedNumberPointer uresult(unumf_openResult(&ec)); if (!assertSuccess("", ec, true, __FILE__, __LINE__)) { return; } // Format a decimal number: unumf_formatDecimal(uformatter.getAlias(), "9.87E-3", -1, uresult.getAlias(), &ec); if (!assertSuccess("", ec, true, __FILE__, __LINE__)) { return; } // Get the location of the percent sign: UFieldPosition ufpos = {UNUM_PERCENT_FIELD, 0, 0}; unumf_resultNextFieldPosition(uresult.getAlias(), &ufpos, &ec); assertEquals("Percent sign location within '0.00987%'", 7, ufpos.beginIndex); assertEquals("Percent sign location within '0.00987%'", 8, ufpos.endIndex); // No need to do any cleanup since we are using LocalPointer. } void NumberFormatterApiTest::toObject() { IcuTestErrorCode status(*this, "toObject"); // const lvalue version { LocalizedNumberFormatter lnf = NumberFormatter::withLocale("en") .precision(Precision::fixedFraction(2)); LocalPointer lnf2(lnf.clone()); assertFalse("should create successfully, const lvalue", lnf2.isNull()); assertEquals("object API test, const lvalue", u"1,000.00", lnf2->formatDouble(1000, status).toString(status)); } // rvalue reference version { LocalPointer lnf( NumberFormatter::withLocale("en") .precision(Precision::fixedFraction(2)) .clone()); assertFalse("should create successfully, rvalue reference", lnf.isNull()); assertEquals("object API test, rvalue reference", u"1,000.00", lnf->formatDouble(1000, status).toString(status)); } // to std::unique_ptr via constructor { std::unique_ptr lnf( NumberFormatter::withLocale("en") .precision(Precision::fixedFraction(2)) .clone()); assertTrue("should create successfully, unique_ptr", static_cast(lnf)); assertEquals("object API test, unique_ptr", u"1,000.00", lnf->formatDouble(1000, status).toString(status)); } // to std::unique_ptr via assignment { std::unique_ptr lnf = NumberFormatter::withLocale("en") .precision(Precision::fixedFraction(2)) .clone(); assertTrue("should create successfully, unique_ptr B", static_cast(lnf)); assertEquals("object API test, unique_ptr B", u"1,000.00", lnf->formatDouble(1000, status).toString(status)); } // to LocalPointer via assignment { LocalPointer f = NumberFormatter::with().clone(); } // make sure no memory leaks { NumberFormatter::with().clone(); } } void NumberFormatterApiTest::toDecimalNumber() { IcuTestErrorCode status(*this, "toDecimalNumber"); FormattedNumber fn = NumberFormatter::withLocale("bn-BD") .scale(Scale::powerOfTen(2)) .precision(Precision::maxSignificantDigits(5)) .formatDouble(9.87654321e12, status); assertEquals("Should have expected localized string result", u"৯৮,৭৬,৫০,০০,০০,০০,০০০", fn.toString(status)); assertEquals(u"Should have expected toDecimalNumber string result", "9.8765E+14", fn.toDecimalNumber(status).c_str()); } void NumberFormatterApiTest::assertFormatDescending( const char16_t* umessage, const char16_t* uskeleton, const char16_t* conciseSkeleton, const UnlocalizedNumberFormatter& f, Locale locale, ...) { va_list args; va_start(args, locale); UnicodeString message(TRUE, umessage, -1); static double inputs[] = {87650, 8765, 876.5, 87.65, 8.765, 0.8765, 0.08765, 0.008765, 0}; const LocalizedNumberFormatter l1 = f.threshold(0).locale(locale); // no self-regulation const LocalizedNumberFormatter l2 = f.threshold(1).locale(locale); // all self-regulation IcuTestErrorCode status(*this, "assertFormatDescending"); status.setScope(message); UnicodeString expecteds[10]; for (int16_t i = 0; i < 9; i++) { char16_t caseNumber = u'0' + i; double d = inputs[i]; UnicodeString expected = va_arg(args, const char16_t*); expecteds[i] = expected; UnicodeString actual1 = l1.formatDouble(d, status).toString(status); assertSuccess(message + u": Unsafe Path: " + caseNumber, status); assertEquals(message + u": Unsafe Path: " + caseNumber, expected, actual1); UnicodeString actual2 = l2.formatDouble(d, status).toString(status); assertSuccess(message + u": Safe Path: " + caseNumber, status); assertEquals(message + u": Safe Path: " + caseNumber, expected, actual2); } if (uskeleton != nullptr) { // if null, skeleton is declared as undefined. UnicodeString skeleton(TRUE, uskeleton, -1); // Only compare normalized skeletons: the tests need not provide the normalized forms. // Use the normalized form to construct the testing formatter to guarantee no loss of info. UnicodeString normalized = NumberFormatter::forSkeleton(skeleton, status).toSkeleton(status); assertEquals(message + ": Skeleton:", normalized, f.toSkeleton(status)); LocalizedNumberFormatter l3 = NumberFormatter::forSkeleton(normalized, status).locale(locale); for (int32_t i = 0; i < 9; i++) { double d = inputs[i]; UnicodeString actual3 = l3.formatDouble(d, status).toString(status); assertEquals(message + ": Skeleton Path: '" + normalized + "': " + d, expecteds[i], actual3); } // Concise skeletons should have same output, and usually round-trip to the normalized skeleton. // If the concise skeleton starts with '~', disable the round-trip check. bool shouldRoundTrip = true; if (conciseSkeleton[0] == u'~') { conciseSkeleton++; shouldRoundTrip = false; } LocalizedNumberFormatter l4 = NumberFormatter::forSkeleton(conciseSkeleton, status).locale(locale); if (shouldRoundTrip) { assertEquals(message + ": Concise Skeleton:", normalized, l4.toSkeleton(status)); } for (int32_t i = 0; i < 9; i++) { double d = inputs[i]; UnicodeString actual4 = l4.formatDouble(d, status).toString(status); assertEquals(message + ": Concise Skeleton Path: '" + normalized + "': " + d, expecteds[i], actual4); } } else { assertUndefinedSkeleton(f); } } void NumberFormatterApiTest::assertFormatDescendingBig( const char16_t* umessage, const char16_t* uskeleton, const char16_t* conciseSkeleton, const UnlocalizedNumberFormatter& f, Locale locale, ...) { va_list args; va_start(args, locale); UnicodeString message(TRUE, umessage, -1); static double inputs[] = {87650000, 8765000, 876500, 87650, 8765, 876.5, 87.65, 8.765, 0}; const LocalizedNumberFormatter l1 = f.threshold(0).locale(locale); // no self-regulation const LocalizedNumberFormatter l2 = f.threshold(1).locale(locale); // all self-regulation IcuTestErrorCode status(*this, "assertFormatDescendingBig"); status.setScope(message); UnicodeString expecteds[10]; for (int16_t i = 0; i < 9; i++) { char16_t caseNumber = u'0' + i; double d = inputs[i]; UnicodeString expected = va_arg(args, const char16_t*); expecteds[i] = expected; UnicodeString actual1 = l1.formatDouble(d, status).toString(status); assertSuccess(message + u": Unsafe Path: " + caseNumber, status); assertEquals(message + u": Unsafe Path: " + caseNumber, expected, actual1); UnicodeString actual2 = l2.formatDouble(d, status).toString(status); assertSuccess(message + u": Safe Path: " + caseNumber, status); assertEquals(message + u": Safe Path: " + caseNumber, expected, actual2); } if (uskeleton != nullptr) { // if null, skeleton is declared as undefined. UnicodeString skeleton(TRUE, uskeleton, -1); // Only compare normalized skeletons: the tests need not provide the normalized forms. // Use the normalized form to construct the testing formatter to guarantee no loss of info. UnicodeString normalized = NumberFormatter::forSkeleton(skeleton, status).toSkeleton(status); assertEquals(message + ": Skeleton:", normalized, f.toSkeleton(status)); LocalizedNumberFormatter l3 = NumberFormatter::forSkeleton(normalized, status).locale(locale); for (int32_t i = 0; i < 9; i++) { double d = inputs[i]; UnicodeString actual3 = l3.formatDouble(d, status).toString(status); assertEquals(message + ": Skeleton Path: '" + normalized + "': " + d, expecteds[i], actual3); } // Concise skeletons should have same output, and usually round-trip to the normalized skeleton. // If the concise skeleton starts with '~', disable the round-trip check. bool shouldRoundTrip = true; if (conciseSkeleton[0] == u'~') { conciseSkeleton++; shouldRoundTrip = false; } LocalizedNumberFormatter l4 = NumberFormatter::forSkeleton(conciseSkeleton, status).locale(locale); if (shouldRoundTrip) { assertEquals(message + ": Concise Skeleton:", normalized, l4.toSkeleton(status)); } for (int32_t i = 0; i < 9; i++) { double d = inputs[i]; UnicodeString actual4 = l4.formatDouble(d, status).toString(status); assertEquals(message + ": Concise Skeleton Path: '" + normalized + "': " + d, expecteds[i], actual4); } } else { assertUndefinedSkeleton(f); } } FormattedNumber NumberFormatterApiTest::assertFormatSingle( const char16_t* umessage, const char16_t* uskeleton, const char16_t* conciseSkeleton, const UnlocalizedNumberFormatter& f, Locale locale, double input, const UnicodeString& expected) { UnicodeString message(TRUE, umessage, -1); const LocalizedNumberFormatter l1 = f.threshold(0).locale(locale); // no self-regulation const LocalizedNumberFormatter l2 = f.threshold(1).locale(locale); // all self-regulation IcuTestErrorCode status(*this, "assertFormatSingle"); status.setScope(message); FormattedNumber result1 = l1.formatDouble(input, status); UnicodeString actual1 = result1.toString(status); assertSuccess(message + u": Unsafe Path", status); assertEquals(message + u": Unsafe Path", expected, actual1); UnicodeString actual2 = l2.formatDouble(input, status).toString(status); assertSuccess(message + u": Safe Path", status); assertEquals(message + u": Safe Path", expected, actual2); if (uskeleton != nullptr) { // if null, skeleton is declared as undefined. UnicodeString skeleton(TRUE, uskeleton, -1); // Only compare normalized skeletons: the tests need not provide the normalized forms. // Use the normalized form to construct the testing formatter to ensure no loss of info. UnicodeString normalized = NumberFormatter::forSkeleton(skeleton, status).toSkeleton(status); assertEquals(message + ": Skeleton:", normalized, f.toSkeleton(status)); LocalizedNumberFormatter l3 = NumberFormatter::forSkeleton(normalized, status).locale(locale); UnicodeString actual3 = l3.formatDouble(input, status).toString(status); assertEquals(message + ": Skeleton Path: '" + normalized + "': " + input, expected, actual3); // Concise skeletons should have same output, and usually round-trip to the normalized skeleton. // If the concise skeleton starts with '~', disable the round-trip check. bool shouldRoundTrip = true; if (conciseSkeleton[0] == u'~') { conciseSkeleton++; shouldRoundTrip = false; } LocalizedNumberFormatter l4 = NumberFormatter::forSkeleton(conciseSkeleton, status).locale(locale); if (shouldRoundTrip) { assertEquals(message + ": Concise Skeleton:", normalized, l4.toSkeleton(status)); } UnicodeString actual4 = l4.formatDouble(input, status).toString(status); assertEquals(message + ": Concise Skeleton Path: '" + normalized + "': " + input, expected, actual4); } else { assertUndefinedSkeleton(f); } return result1; } void NumberFormatterApiTest::assertUndefinedSkeleton(const UnlocalizedNumberFormatter& f) { UErrorCode status = U_ZERO_ERROR; UnicodeString skeleton = f.toSkeleton(status); assertEquals( u"Expect toSkeleton to fail, but passed, producing: " + skeleton, U_UNSUPPORTED_ERROR, status); } void NumberFormatterApiTest::assertNumberFieldPositions( const char16_t* message, const FormattedNumber& formattedNumber, const UFieldPosition* expectedFieldPositions, int32_t length) { IcuTestErrorCode status(*this, "assertNumberFieldPositions"); // Check FormattedValue functions checkFormattedValue( message, static_cast(formattedNumber), formattedNumber.toString(status), UFIELD_CATEGORY_NUMBER, expectedFieldPositions, length); } #endif /* #if !UCONFIG_NO_FORMATTING */