// © 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 "unicode/testlog.h" #include "unicode/utypes.h" #include "number_asformat.h" #include "number_types.h" #include "number_utils.h" #include "number_utypes.h" #include "number_microprops.h" #include "numbertest.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), TWD(u"TWD", status), TRY(u"TRY", 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; METER_PER_SECOND = *LocalPointer(MeasureUnit::createMeterPerSecond(status)); 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)); POUND_FORCE = *LocalPointer(MeasureUnit::createPoundForce(status)); SQUARE_MILE = *LocalPointer(MeasureUnit::createSquareMile(status)); SQUARE_INCH = *LocalPointer(MeasureUnit::createSquareInch(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(unitArbitraryMeasureUnits); TESTCASE_AUTO(unitSkeletons); TESTCASE_AUTO(unitUsage); TESTCASE_AUTO(unitUsageErrorCodes); TESTCASE_AUTO(unitUsageSkeletons); TESTCASE_AUTO(unitCurrency); TESTCASE_AUTO(unitInflections); TESTCASE_AUTO(unitGender); 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(microPropsInternals); 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 with plural form =1 (ICU-21258)", u"compact-long", u"KK", NumberFormatter::with().notation(Notation::compactLong()), Locale("fr-FR"), 1e3, u"mille"); 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() { IcuTestErrorCode status(*this, "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"); assertFormatDescending( u"Hectometers", u"unit/hectometer", u"unit/hectometer", NumberFormatter::with().unit(MeasureUnit::forIdentifier("hectometer", status)), Locale::getEnglish(), u"87,650 hm", u"8,765 hm", u"876.5 hm", u"87.65 hm", u"8.765 hm", u"0.8765 hm", u"0.08765 hm", u"0.008765 hm", u"0 hm"); // 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"); assertFormatSingle( u"Hubble Constant - usually expressed in km/s/Mpc", u"unit/kilometer-per-megaparsec-second", u"unit/kilometer-per-megaparsec-second", NumberFormatter::with().unit(MeasureUnit::forIdentifier("kilometer-per-second-per-megaparsec", status)), Locale("en"), 74, // Approximate 2019-03-18 measurement u"74 km/Mpc⋅sec"); assertFormatSingle( u"Mixed unit", u"unit/yard-and-foot-and-inch", u"unit/yard-and-foot-and-inch", NumberFormatter::with() .unit(MeasureUnit::forIdentifier("yard-and-foot-and-inch", status)), Locale("en-US"), 3.65, "3 yd, 1 ft, 11.4 in"); assertFormatSingle( u"Mixed unit, Scientific", u"unit/yard-and-foot-and-inch E0", u"unit/yard-and-foot-and-inch E0", NumberFormatter::with() .unit(MeasureUnit::forIdentifier("yard-and-foot-and-inch", status)) .notation(Notation::scientific()), Locale("en-US"), 3.65, "3 yd, 1 ft, 1.14E1 in"); assertFormatSingle( u"Mixed Unit (Narrow Version)", u"unit/metric-ton-and-kilogram-and-gram unit-width-narrow", u"unit/metric-ton-and-kilogram-and-gram unit-width-narrow", NumberFormatter::with() .unit(MeasureUnit::forIdentifier("metric-ton-and-kilogram-and-gram", status)) .unitWidth(UNUM_UNIT_WIDTH_NARROW), Locale("en-US"), 4.28571, u"4t 285kg 710g"); assertFormatSingle( u"Mixed Unit (Short Version)", u"unit/metric-ton-and-kilogram-and-gram unit-width-short", u"unit/metric-ton-and-kilogram-and-gram unit-width-short", NumberFormatter::with() .unit(MeasureUnit::forIdentifier("metric-ton-and-kilogram-and-gram", status)) .unitWidth(UNUM_UNIT_WIDTH_SHORT), Locale("en-US"), 4.28571, u"4 t, 285 kg, 710 g"); assertFormatSingle( u"Mixed Unit (Full Name Version)", u"unit/metric-ton-and-kilogram-and-gram unit-width-full-name", u"unit/metric-ton-and-kilogram-and-gram unit-width-full-name", NumberFormatter::with() .unit(MeasureUnit::forIdentifier("metric-ton-and-kilogram-and-gram", status)) .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME), Locale("en-US"), 4.28571, u"4 metric tons, 285 kilograms, 710 grams"); assertFormatSingle(u"Mixed Unit (Not Sorted) [metric]", // u"unit/gram-and-kilogram unit-width-full-name", // u"unit/gram-and-kilogram unit-width-full-name", // NumberFormatter::with() // .unit(MeasureUnit::forIdentifier("gram-and-kilogram", status)) // .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME), // Locale("en-US"), // 4.28571, // u"285.71 grams, 4 kilograms"); // assertFormatSingle(u"Mixed Unit (Not Sorted) [imperial]", // u"unit/inch-and-yard-and-foot unit-width-full-name", // u"unit/inch-and-yard-and-foot unit-width-full-name", // NumberFormatter::with() // .unit(MeasureUnit::forIdentifier("inch-and-yard-and-foot", status)) // .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME), // Locale("en-US"), // 4.28571, // u"10.28556 inches, 4 yards, 0 feet"); // assertFormatSingle(u"Mixed Unit (Not Sorted) [imperial full]", // u"unit/inch-and-yard-and-foot unit-width-full-name", // u"unit/inch-and-yard-and-foot unit-width-full-name", // NumberFormatter::with() // .unit(MeasureUnit::forIdentifier("inch-and-yard-and-foot", status)) // .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME), // Locale("en-US"), // 4.38571, // u"1.88556 inches, 4 yards, 1 foot"); // assertFormatSingle(u"Mixed Unit (Not Sorted) [imperial full integers]", // u"unit/inch-and-yard-and-foot @# unit-width-full-name", // u"unit/inch-and-yard-and-foot @# unit-width-full-name", // NumberFormatter::with() // .unit(MeasureUnit::forIdentifier("inch-and-yard-and-foot", status)) // .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME) // .precision(Precision::maxSignificantDigits(2)), // Locale("en-US"), // 4.36112, // u"1 inch, 4 yards, 1 foot"); // assertFormatSingle(u"Mixed Unit (Not Sorted) [imperial full] with `And` in the end", // u"unit/inch-and-yard-and-foot unit-width-full-name", // u"unit/inch-and-yard-and-foot unit-width-full-name", // NumberFormatter::with() // .unit(MeasureUnit::forIdentifier("inch-and-yard-and-foot", status)) // .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME), // Locale("fr-FR"), // 4.38571, // u"1,88556\u00A0pouce, 4\u00A0yards et 1\u00A0pied"); // assertFormatSingle(u"Mixed unit, Scientific [Not in Order]", // u"unit/foot-and-inch-and-yard E0", // u"unit/foot-and-inch-and-yard E0", // NumberFormatter::with() // .unit(MeasureUnit::forIdentifier("foot-and-inch-and-yard", status)) // .notation(Notation::scientific()), // Locale("en-US"), // 3.65, // "1 ft, 1.14E1 in, 3 yd"); // assertFormatSingle( u"Testing \"1 foot 12 inches\"", u"unit/foot-and-inch @### unit-width-full-name", u"unit/foot-and-inch @### unit-width-full-name", NumberFormatter::with() .unit(MeasureUnit::forIdentifier("foot-and-inch", status)) .precision(Precision::maxSignificantDigits(4)) .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME), Locale("en-US"), 1.9999, u"2 feet, 0 inches"); assertFormatSingle( u"Negative numbers: temperature", u"measure-unit/temperature-celsius", u"unit/celsius", NumberFormatter::with().unit(MeasureUnit::forIdentifier("celsius", status)), Locale("nl-NL"), -6.5, u"-6,5°C"); assertFormatSingle( u"Negative numbers: time", u"unit/hour-and-minute-and-second", u"unit/hour-and-minute-and-second", NumberFormatter::with().unit(MeasureUnit::forIdentifier("hour-and-minute-and-second", status)), Locale("de-DE"), -1.24, u"-1 Std., 14 Min. und 24 Sek."); assertFormatSingle( u"Zero out the unit field", u"", u"", NumberFormatter::with().unit(KELVIN).unit(MeasureUnit()), Locale("en"), 100, u"100"); // TODO: desired behaviour for this "pathological" case? // Since this is pointless, we don't test that its behaviour doesn't change. // As of January 2021, the produced result has a missing sign: 23.5 Kelvin // is "23 Kelvin and -272.65 degrees Celsius": // assertFormatSingle( // u"Meaningless: kelvin-and-celcius", // u"unit/kelvin-and-celsius", // u"unit/kelvin-and-celsius", // NumberFormatter::with().unit(MeasureUnit::forIdentifier("kelvin-and-celsius", status)), // Locale("en"), // 23.5, // u"23 K, 272.65°C"); if (uprv_getNaN() != 0.0) { assertFormatSingle( u"Measured -Inf", u"measure-unit/electric-ampere", u"unit/ampere", NumberFormatter::with().unit(MeasureUnit::getAmpere()), Locale("en"), -uprv_getInfinity(), u"-∞ A"); assertFormatSingle( u"Measured NaN", u"measure-unit/temperature-celsius", u"unit/celsius", NumberFormatter::with().unit(MeasureUnit::forIdentifier("celsius", status)), Locale("en"), uprv_getNaN(), u"NaN°C"); } } void NumberFormatterApiTest::unitCompoundMeasure() { IcuTestErrorCode status(*this, "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"Meters Per Second Short, built-in m/s", u"measure-unit/speed-meter-per-second", u"unit/meter-per-second", NumberFormatter::with().unit(METER_PER_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"); 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"); assertFormatDescending( u"Pounds per Square Inch: composed", u"measure-unit/force-pound-force per-measure-unit/area-square-inch", u"unit/pound-force-per-square-inch", NumberFormatter::with().unit(POUND_FORCE).perUnit(SQUARE_INCH), Locale::getEnglish(), u"87,650 psi", u"8,765 psi", u"876.5 psi", u"87.65 psi", u"8.765 psi", u"0.8765 psi", u"0.08765 psi", u"0.008765 psi", u"0 psi"); assertFormatDescending( u"Pounds per Square Inch: built-in", u"measure-unit/force-pound-force per-measure-unit/area-square-inch", u"unit/pound-force-per-square-inch", NumberFormatter::with().unit(MeasureUnit::getPoundPerSquareInch()), Locale::getEnglish(), u"87,650 psi", u"8,765 psi", u"876.5 psi", u"87.65 psi", u"8.765 psi", u"0.8765 psi", u"0.08765 psi", u"0.008765 psi", u"0 psi"); assertFormatSingle( u"m/s/s simplifies to m/s^2", u"measure-unit/speed-meter-per-second per-measure-unit/duration-second", u"unit/meter-per-square-second", NumberFormatter::with().unit(METER_PER_SECOND).perUnit(SECOND), Locale("en-GB"), 2.4, u"2.4 m/s\u00B2"); assertFormatSingle( u"Negative numbers: acceleration", u"measure-unit/acceleration-meter-per-square-second", u"unit/meter-per-second-second", NumberFormatter::with().unit(MeasureUnit::forIdentifier("meter-per-pow2-second", status)), Locale("af-ZA"), -9.81, u"-9,81 m/s\u00B2"); // Testing the rejection of invalid specifications // If .unit() is not given a built-in type, .perUnit() is not allowed // (because .unit is now flexible enough to handle compound units, // .perUnit() is supported for backward compatibility). LocalizedNumberFormatter nf = NumberFormatter::with() .unit(MeasureUnit::forIdentifier("furlong-pascal", status)) .perUnit(METER) .locale("en-GB"); status.assertSuccess(); // Error is only returned once we try to format. FormattedNumber num = nf.formatDouble(2.4, status); if (!status.expectErrorAndReset(U_UNSUPPORTED_ERROR)) { errln(UnicodeString("Expected failure for unit/furlong-pascal per-unit/length-meter, got: \"") + nf.formatDouble(2.4, status).toString(status) + "\"."); status.assertSuccess(); } // .perUnit() may only be passed a built-in type, or something that combines // to a built-in type together with .unit(). MeasureUnit SQUARE_SECOND = MeasureUnit::forIdentifier("square-second", status); nf = NumberFormatter::with().unit(FURLONG).perUnit(SQUARE_SECOND).locale("en-GB"); status.assertSuccess(); // Error is only returned once we try to format. num = nf.formatDouble(2.4, status); if (!status.expectErrorAndReset(U_UNSUPPORTED_ERROR)) { errln(UnicodeString("Expected failure, got: \"") + nf.formatDouble(2.4, status).toString(status) + "\"."); status.assertSuccess(); } // As above, "square-second" is not a built-in type, however this time, // meter-per-square-second is a built-in type. assertFormatSingle( u"meter per square-second works as a composed unit", u"measure-unit/speed-meter-per-second per-measure-unit/duration-second", u"unit/meter-per-square-second", NumberFormatter::with().unit(METER).perUnit(SQUARE_SECOND), Locale("en-GB"), 2.4, u"2.4 m/s\u00B2"); } void NumberFormatterApiTest::unitArbitraryMeasureUnits() { IcuTestErrorCode status(*this, "unitArbitraryMeasureUnits()"); // TODO: fix after data bug is resolved? See CLDR-14510. // assertFormatSingle( // u"Binary unit prefix: kibibyte", // u"unit/kibibyte", // u"unit/kibibyte", // NumberFormatter::with().unit(MeasureUnit::forIdentifier("kibibyte", status)), // Locale("en-GB"), // 2.4, // u"2.4 KiB"); assertFormatSingle( u"Binary unit prefix: kibibyte full-name", u"unit/kibibyte unit-width-full-name", u"unit/kibibyte unit-width-full-name", NumberFormatter::with() .unit(MeasureUnit::forIdentifier("kibibyte", status)) .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME), Locale("en-GB"), 2.4, u"2.4 kibibytes"); assertFormatSingle( u"Binary unit prefix: kibibyte full-name", u"unit/kibibyte unit-width-full-name", u"unit/kibibyte unit-width-full-name", NumberFormatter::with() .unit(MeasureUnit::forIdentifier("kibibyte", status)) .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME), Locale("de"), 2.4, u"2,4 Kibibyte"); assertFormatSingle( u"Binary prefix for non-digital units: kibimeter", u"unit/kibimeter", u"unit/kibimeter", NumberFormatter::with() .unit(MeasureUnit::forIdentifier("kibimeter", status)), Locale("en-GB"), 2.4, u"2.4 Kim"); assertFormatSingle( u"SI prefix falling back to root: microohm", u"unit/microohm", u"unit/microohm", NumberFormatter::with() .unit(MeasureUnit::forIdentifier("microohm", status)), Locale("de-CH"), 2.4, u"2.4 μΩ"); assertFormatSingle( u"de-CH fallback to de: microohm unit-width-full-name", u"unit/microohm unit-width-full-name", u"unit/microohm unit-width-full-name", NumberFormatter::with() .unit(MeasureUnit::forIdentifier("microohm", status)) .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME), Locale("de-CH"), 2.4, u"2.4\u00A0Mikroohm"); assertFormatSingle( u"No prefixes, 'times' pattern: joule-furlong", u"unit/joule-furlong", u"unit/joule-furlong", NumberFormatter::with() .unit(MeasureUnit::forIdentifier("joule-furlong", status)), Locale("en"), 2.4, u"2.4 J⋅fur"); assertFormatSingle( u"No numeratorUnitString: per-second", u"unit/per-second", u"unit/per-second", NumberFormatter::with() .unit(MeasureUnit::forIdentifier("per-second", status)), Locale("de-CH"), 2.4, u"2.4/s"); assertFormatSingle( u"No numeratorUnitString: per-second unit-width-full-name", u"unit/per-second unit-width-full-name", u"unit/per-second unit-width-full-name", NumberFormatter::with() .unit(MeasureUnit::forIdentifier("per-second", status)) .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME), Locale("de-CH"), 2.4, u"2.4 pro Sekunde"); assertFormatSingle( u"Prefix in the denominator: nanogram-per-picobarrel", u"unit/nanogram-per-picobarrel", u"unit/nanogram-per-picobarrel", NumberFormatter::with() .unit(MeasureUnit::forIdentifier("nanogram-per-picobarrel", status)), Locale("en-ZA"), 2.4, u"2,4 ng/pbbl"); assertFormatSingle( u"Prefix in the denominator: nanogram-per-picobarrel unit-width-full-name", u"unit/nanogram-per-picobarrel unit-width-full-name", u"unit/nanogram-per-picobarrel unit-width-full-name", NumberFormatter::with() .unit(MeasureUnit::forIdentifier("nanogram-per-picobarrel", status)) .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME), Locale("en-ZA"), 2.4, u"2,4 nanograms per picobarrel"); // Valid MeasureUnit, but unformattable, because we only have patterns for // pow2 and pow3 at this time: LocalizedNumberFormatter lnf = NumberFormatter::with() .unit(MeasureUnit::forIdentifier("pow4-mile", status)) .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME) .locale("en-ZA"); lnf.operator=(lnf); // self-assignment should be a no-op lnf.formatInt(1, status); status.expectErrorAndReset(U_RESOURCE_TYPE_MISMATCH); assertFormatSingle( u"kibijoule-foot-per-cubic-gigafurlong-square-second unit-width-full-name", u"unit/kibijoule-foot-per-cubic-gigafurlong-square-second unit-width-full-name", u"unit/kibijoule-foot-per-cubic-gigafurlong-square-second unit-width-full-name", NumberFormatter::with() .unit(MeasureUnit::forIdentifier("kibijoule-foot-per-cubic-gigafurlong-square-second", status)) .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME), Locale("en-ZA"), 2.4, u"2,4 kibijoule-feet per cubic gigafurlong-square second"); assertFormatSingle( u"kibijoule-foot-per-cubic-gigafurlong-square-second unit-width-full-name", u"unit/kibijoule-foot-per-cubic-gigafurlong-square-second unit-width-full-name", u"unit/kibijoule-foot-per-cubic-gigafurlong-square-second unit-width-full-name", NumberFormatter::with() .unit(MeasureUnit::forIdentifier("kibijoule-foot-per-cubic-gigafurlong-square-second", status)) .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME), Locale("de-CH"), 2.4, u"2.4\u00A0Kibijoule⋅Fuss pro Kubikgigafurlong⋅Quadratsekunde"); // TODO(ICU-21504): We want to be able to format this, but "100-kilometer" // is not yet supported when it's not part of liter-per-100-kilometer: lnf = NumberFormatter::with() .unit(MeasureUnit::forIdentifier("kilowatt-hour-per-100-kilometer", status)) .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME) .locale("en-ZA"); lnf.formatInt(1, status); status.expectErrorAndReset(U_UNSUPPORTED_ERROR); } // TODO: merge these tests into numbertest_skeletons.cpp instead of here: void NumberFormatterApiTest::unitSkeletons() { const struct TestCase { const char *msg; const char16_t *inputSkeleton; const char16_t *normalizedSkeleton; } cases[] = { {"old-form built-in compound unit", // u"measure-unit/speed-meter-per-second", // u"unit/meter-per-second"}, {"old-form compound construction, converts to built-in", // u"measure-unit/length-meter per-measure-unit/duration-second", // u"unit/meter-per-second"}, {"old-form compound construction which does not simplify to a built-in", // u"measure-unit/energy-joule per-measure-unit/length-meter", // u"unit/joule-per-meter"}, {"old-form compound-compound ugliness resolves neatly", // u"measure-unit/speed-meter-per-second per-measure-unit/duration-second", // u"unit/meter-per-square-second"}, {"short-form built-in units stick with the built-in", // u"unit/meter-per-second", // u"unit/meter-per-second"}, {"short-form compound units stay as is", // u"unit/square-meter-per-square-meter", // u"unit/square-meter-per-square-meter"}, {"short-form compound units stay as is", // u"unit/joule-per-furlong", // u"unit/joule-per-furlong"}, {"short-form that doesn't consist of built-in units", // u"unit/hectometer-per-second", // u"unit/hectometer-per-second"}, {"short-form that doesn't consist of built-in units", // u"unit/meter-per-hectosecond", // u"unit/meter-per-hectosecond"}, {"percent compound skeletons handled correctly", // u"unit/percent-per-meter", // u"unit/percent-per-meter"}, {"permille compound skeletons handled correctly", // u"measure-unit/concentr-permille per-measure-unit/length-meter", // u"unit/permille-per-meter"}, {"percent simple unit is not actually considered a unit", // u"unit/percent", // u"percent"}, {"permille simple unit is not actually considered a unit", // u"measure-unit/concentr-permille", // u"permille"}, {"Round-trip example from icu-units#35", // u"unit/kibijoule-per-furlong", // u"unit/kibijoule-per-furlong"}, }; for (auto &cas : cases) { IcuTestErrorCode status(*this, cas.msg); auto nf = NumberFormatter::forSkeleton(cas.inputSkeleton, status); if (status.errIfFailureAndReset("NumberFormatter::forSkeleton failed")) { continue; } assertEquals( // UnicodeString(TRUE, cas.inputSkeleton, -1) + u" normalization", // cas.normalizedSkeleton, // nf.toSkeleton(status)); status.errIfFailureAndReset("NumberFormatter::toSkeleton failed"); } const struct FailCase { const char *msg; const char16_t *inputSkeleton; UErrorCode expectedForSkelStatus; UErrorCode expectedToSkelStatus; } failCases[] = { {"Parsing measure-unit/* results in failure if not built-in unit", u"measure-unit/hectometer", // U_NUMBER_SKELETON_SYNTAX_ERROR, // U_ZERO_ERROR}, {"Parsing per-measure-unit/* results in failure if not built-in unit", u"measure-unit/meter per-measure-unit/hectosecond", // U_NUMBER_SKELETON_SYNTAX_ERROR, // U_ZERO_ERROR}, {"\"currency/EUR measure-unit/length-meter\" fails, conflicting skeleton.", u"currency/EUR measure-unit/length-meter", // U_NUMBER_SKELETON_SYNTAX_ERROR, // U_ZERO_ERROR}, {"\"measure-unit/length-meter currency/EUR\" fails, conflicting skeleton.", u"measure-unit/length-meter currency/EUR", // U_NUMBER_SKELETON_SYNTAX_ERROR, // U_ZERO_ERROR}, {"\"currency/EUR per-measure-unit/meter\" fails, conflicting skeleton.", u"currency/EUR per-measure-unit/length-meter", // U_NUMBER_SKELETON_SYNTAX_ERROR, // U_ZERO_ERROR}, }; for (auto &cas : failCases) { IcuTestErrorCode status(*this, cas.msg); auto nf = NumberFormatter::forSkeleton(cas.inputSkeleton, status); if (status.expectErrorAndReset(cas.expectedForSkelStatus, cas.msg)) { continue; } nf.toSkeleton(status); status.expectErrorAndReset(cas.expectedToSkelStatus, cas.msg); } IcuTestErrorCode status(*this, "unitSkeletons"); assertEquals( // ".unit(METER_PER_SECOND) normalization", // u"unit/meter-per-second", // NumberFormatter::with().unit(METER_PER_SECOND).toSkeleton(status)); assertEquals( // ".unit(METER).perUnit(SECOND) normalization", // u"unit/meter-per-second", NumberFormatter::with().unit(METER).perUnit(SECOND).toSkeleton(status)); assertEquals( // ".unit(MeasureUnit::forIdentifier(\"hectometer\", status)) normalization", // u"unit/hectometer", NumberFormatter::with() .unit(MeasureUnit::forIdentifier("hectometer", status)) .toSkeleton(status)); assertEquals( // ".unit(MeasureUnit::forIdentifier(\"hectometer\", status)) normalization", // u"unit/meter-per-hectosecond", NumberFormatter::with() .unit(METER) .perUnit(MeasureUnit::forIdentifier("hectosecond", status)) .toSkeleton(status)); status.assertSuccess(); assertEquals( // ".unit(CURRENCY) produces a currency/CURRENCY skeleton", // u"currency/GBP", // NumberFormatter::with().unit(GBP).toSkeleton(status)); status.assertSuccess(); // .unit(CURRENCY).perUnit(ANYTHING) is not supported. NumberFormatter::with().unit(GBP).perUnit(METER).toSkeleton(status); status.expectErrorAndReset(U_UNSUPPORTED_ERROR); } void NumberFormatterApiTest::unitUsage() { IcuTestErrorCode status(*this, "unitUsage()"); UnlocalizedNumberFormatter unloc_formatter; LocalizedNumberFormatter formatter; FormattedNumber formattedNum; UnicodeString uTestCase; status.assertSuccess(); formattedNum = NumberFormatter::with().usage("road").locale(Locale::getEnglish()).formatInt(1, status); status.expectErrorAndReset(U_ILLEGAL_ARGUMENT_ERROR); unloc_formatter = NumberFormatter::with().usage("road").unit(MeasureUnit::getMeter()); uTestCase = u"unitUsage() en-ZA road"; formatter = unloc_formatter.locale("en-ZA"); formattedNum = formatter.formatDouble(321, status); status.errIfFailureAndReset("unitUsage() en-ZA road formatDouble"); assertTrue( uTestCase + u", got outputUnit: \"" + formattedNum.getOutputUnit(status).getIdentifier() + "\"", MeasureUnit::getMeter() == formattedNum.getOutputUnit(status)); assertEquals(uTestCase, "300 m", formattedNum.toString(status)); { static const UFieldPosition expectedFieldPositions[] = { {UNUM_INTEGER_FIELD, 0, 3}, {UNUM_MEASURE_UNIT_FIELD, 4, 5}}; assertNumberFieldPositions( (uTestCase + u" field positions").getTerminatedBuffer(), formattedNum, expectedFieldPositions, UPRV_LENGTHOF(expectedFieldPositions)); } assertFormatDescendingBig( uTestCase.getTerminatedBuffer(), u"measure-unit/length-meter usage/road", u"unit/meter usage/road", unloc_formatter, Locale("en-ZA"), u"87\u00A0650 km", u"8\u00A0765 km", u"876 km", // 6.5 rounds down, 7.5 rounds up. u"88 km", u"8,8 km", u"900 m", u"90 m", u"9 m", u"0 m"); uTestCase = u"unitUsage() en-GB road"; formatter = unloc_formatter.locale("en-GB"); formattedNum = formatter.formatDouble(321, status); status.errIfFailureAndReset("unitUsage() en-GB road, formatDouble(...)"); assertTrue( uTestCase + u", got outputUnit: \"" + formattedNum.getOutputUnit(status).getIdentifier() + "\"", MeasureUnit::getYard() == formattedNum.getOutputUnit(status)); status.errIfFailureAndReset("unitUsage() en-GB road, getOutputUnit(...)"); assertEquals(uTestCase, "350 yd", formattedNum.toString(status)); status.errIfFailureAndReset("unitUsage() en-GB road, toString(...)"); { static const UFieldPosition expectedFieldPositions[] = { {UNUM_INTEGER_FIELD, 0, 3}, {UNUM_MEASURE_UNIT_FIELD, 4, 6}}; assertNumberFieldPositions( (uTestCase + u" field positions").getTerminatedBuffer(), formattedNum, expectedFieldPositions, UPRV_LENGTHOF(expectedFieldPositions)); } assertFormatDescendingBig( uTestCase.getTerminatedBuffer(), u"measure-unit/length-meter usage/road", u"unit/meter usage/road", unloc_formatter, Locale("en-GB"), u"54,463 mi", u"5,446 mi", u"545 mi", u"54 mi", u"5.4 mi", u"0.54 mi", u"100 yd", u"10 yd", u"0 yd"); uTestCase = u"unitUsage() en-US road"; formatter = unloc_formatter.locale("en-US"); formattedNum = formatter.formatDouble(321, status); status.errIfFailureAndReset("unitUsage() en-US road, formatDouble(...)"); assertTrue( uTestCase + u", got outputUnit: \"" + formattedNum.getOutputUnit(status).getIdentifier() + "\"", MeasureUnit::getFoot() == formattedNum.getOutputUnit(status)); status.errIfFailureAndReset("unitUsage() en-US road, getOutputUnit(...)"); assertEquals(uTestCase, "1,050 ft", formattedNum.toString(status)); status.errIfFailureAndReset("unitUsage() en-US road, toString(...)"); { static const UFieldPosition expectedFieldPositions[] = { {UNUM_GROUPING_SEPARATOR_FIELD, 1, 2}, {UNUM_INTEGER_FIELD, 0, 5}, {UNUM_MEASURE_UNIT_FIELD, 6, 8}}; assertNumberFieldPositions( (uTestCase + u" field positions").getTerminatedBuffer(), formattedNum, expectedFieldPositions, UPRV_LENGTHOF(expectedFieldPositions)); } assertFormatDescendingBig( uTestCase.getTerminatedBuffer(), u"measure-unit/length-meter usage/road", u"unit/meter usage/road", unloc_formatter, Locale("en-US"), u"54,463 mi", u"5,446 mi", u"545 mi", u"54 mi", u"5.4 mi", u"0.54 mi", u"300 ft", u"30 ft", u"0 ft"); unloc_formatter = NumberFormatter::with().usage("person").unit(MeasureUnit::getKilogram()); uTestCase = u"unitUsage() en-GB person"; formatter = unloc_formatter.locale("en-GB"); formattedNum = formatter.formatDouble(80, status); status.errIfFailureAndReset("unitUsage() en-GB person formatDouble"); assertTrue( uTestCase + ", got outputUnit: \"" + formattedNum.getOutputUnit(status).getIdentifier() + "\"", MeasureUnit::forIdentifier("stone-and-pound", status) == formattedNum.getOutputUnit(status)); status.errIfFailureAndReset("unitUsage() en-GB person - formattedNum.getOutputUnit(status)"); assertEquals(uTestCase, "12 st, 8.4 lb", formattedNum.toString(status)); status.errIfFailureAndReset("unitUsage() en-GB person, toString(...)"); { static const UFieldPosition expectedFieldPositions[] = { // // Desired output: TODO(icu-units#67) // {UNUM_INTEGER_FIELD, 0, 2}, // {UNUM_MEASURE_UNIT_FIELD, 3, 5}, // {ULISTFMT_LITERAL_FIELD, 5, 6}, // {UNUM_INTEGER_FIELD, 7, 8}, // {UNUM_DECIMAL_SEPARATOR_FIELD, 8, 9}, // {UNUM_FRACTION_FIELD, 9, 10}, // {UNUM_MEASURE_UNIT_FIELD, 11, 13}}; // Current output: rather no fields than wrong fields {UNUM_INTEGER_FIELD, 7, 8}, {UNUM_DECIMAL_SEPARATOR_FIELD, 8, 9}, {UNUM_FRACTION_FIELD, 9, 10}, }; assertNumberFieldPositions( (uTestCase + u" field positions").getTerminatedBuffer(), formattedNum, expectedFieldPositions, UPRV_LENGTHOF(expectedFieldPositions)); } assertFormatDescending( uTestCase.getTerminatedBuffer(), u"measure-unit/mass-kilogram usage/person", u"unit/kilogram usage/person", unloc_formatter, Locale("en-GB"), u"13,802 st, 7.2 lb", u"1,380 st, 3.5 lb", u"138 st, 0.35 lb", u"13 st, 11 lb", u"1 st, 5.3 lb", u"1 lb, 15 oz", u"0 lb, 3.1 oz", u"0 lb, 0.31 oz", u"0 lb, 0 oz"); assertFormatDescending( uTestCase.getTerminatedBuffer(), u"usage/person unit-width-narrow measure-unit/mass-kilogram", u"usage/person unit-width-narrow unit/kilogram", unloc_formatter.unitWidth(UNUM_UNIT_WIDTH_NARROW), Locale("en-GB"), u"13,802st 7.2lb", u"1,380st 3.5lb", u"138st 0.35lb", u"13st 11lb", u"1st 5.3lb", u"1lb 15oz", u"0lb 3.1oz", u"0lb 0.31oz", u"0lb 0oz"); assertFormatDescending( uTestCase.getTerminatedBuffer(), u"usage/person unit-width-short measure-unit/mass-kilogram", u"usage/person unit-width-short unit/kilogram", unloc_formatter.unitWidth(UNUM_UNIT_WIDTH_SHORT), Locale("en-GB"), u"13,802 st, 7.2 lb", u"1,380 st, 3.5 lb", u"138 st, 0.35 lb", u"13 st, 11 lb", u"1 st, 5.3 lb", u"1 lb, 15 oz", u"0 lb, 3.1 oz", u"0 lb, 0.31 oz", u"0 lb, 0 oz"); assertFormatDescending( uTestCase.getTerminatedBuffer(), u"usage/person unit-width-full-name measure-unit/mass-kilogram", u"usage/person unit-width-full-name unit/kilogram", unloc_formatter.unitWidth(UNUM_UNIT_WIDTH_FULL_NAME), Locale("en-GB"), u"13,802 stone, 7.2 pounds", u"1,380 stone, 3.5 pounds", u"138 stone, 0.35 pounds", u"13 stone, 11 pounds", u"1 stone, 5.3 pounds", u"1 pound, 15 ounces", u"0 pounds, 3.1 ounces", u"0 pounds, 0.31 ounces", u"0 pounds, 0 ounces"); assertFormatDescendingBig( u"Scientific notation with Usage: possible when using a reasonable Precision", u"scientific @### usage/default measure-unit/area-square-meter unit-width-full-name", u"scientific @### usage/default unit/square-meter unit-width-full-name", NumberFormatter::with() .unit(SQUARE_METER) .usage("default") .notation(Notation::scientific()) .precision(Precision::minMaxSignificantDigits(1, 4)) .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME), Locale("en-ZA"), u"8,765E1 square kilometres", u"8,765E0 square kilometres", u"8,765E1 hectares", u"8,765E0 hectares", u"8,765E3 square metres", u"8,765E2 square metres", u"8,765E1 square metres", u"8,765E0 square metres", u"0E0 square centimetres"); assertFormatSingle( u"Negative Infinity with Unit Preferences", u"measure-unit/area-acre usage/default", u"unit/acre usage/default", NumberFormatter::with().unit(MeasureUnit::getAcre()).usage("default"), Locale::getEnglish(), -uprv_getInfinity(), u"-∞ km²"); // // TODO(icu-units#131): do we care about NaN? // // TODO: on some platforms with MSVC, "-NaN sec" is returned. // assertFormatSingle( // u"NaN with Unit Preferences", // u"measure-unit/area-acre usage/default", // u"unit/acre usage/default", // NumberFormatter::with().unit(MeasureUnit::getAcre()).usage("default"), // Locale::getEnglish(), // uprv_getNaN(), // u"NaN cm²"); assertFormatSingle( u"Negative numbers: minute-and-second", u"measure-unit/duration-second usage/media", u"unit/second usage/media", NumberFormatter::with().unit(SECOND).usage("media"), Locale("nl-NL"), -77.7, u"-1 min, 18 sec"); assertFormatSingle( u"Negative numbers: media seconds", u"measure-unit/duration-second usage/media", u"unit/second usage/media", NumberFormatter::with().unit(SECOND).usage("media"), Locale("nl-NL"), -2.7, u"-2,7 sec"); // // TODO: on some platforms with MSVC, "-NaN sec" is returned. // assertFormatSingle( // u"NaN minute-and-second", // u"measure-unit/duration-second usage/media", // u"unit/second usage/media", // NumberFormatter::with().unit(SECOND).usage("media"), // Locale("nl-NL"), // uprv_getNaN(), // u"NaN sec"); assertFormatSingle( u"NaN meter-and-centimeter", u"measure-unit/length-meter usage/person-height", u"unit/meter usage/person-height", NumberFormatter::with().unit(METER).usage("person-height"), Locale("sv-SE"), uprv_getNaN(), u"0 m, NaN cm"); assertFormatSingle( u"Rounding Mode propagates: rounding down", u"usage/road measure-unit/length-centimeter rounding-mode-floor", u"usage/road unit/centimeter rounding-mode-floor", NumberFormatter::with() .unit(MeasureUnit::forIdentifier("centimeter", status)) .usage("road") .roundingMode(UNUM_ROUND_FLOOR), Locale("en-ZA"), 34500, u"300 m"); assertFormatSingle( u"Rounding Mode propagates: rounding up", u"usage/road measure-unit/length-centimeter rounding-mode-ceiling", u"usage/road unit/centimeter rounding-mode-ceiling", NumberFormatter::with() .unit(MeasureUnit::forIdentifier("centimeter", status)) .usage("road") .roundingMode(UNUM_ROUND_CEILING), Locale("en-ZA"), 30500, u"350 m"); // Test calling `.usage("")` should unset the existing usage. // First: without usage assertFormatSingle(u"Rounding Mode propagates: rounding up", u"measure-unit/length-centimeter rounding-mode-ceiling", u"unit/centimeter rounding-mode-ceiling", NumberFormatter::with() .unit(MeasureUnit::forIdentifier("centimeter", status)) .roundingMode(UNUM_ROUND_CEILING), Locale("en-US"), // 3048, // u"3,048 cm"); // Second: with "road" usage assertFormatSingle(u"Rounding Mode propagates: rounding up", u"usage/road measure-unit/length-centimeter rounding-mode-ceiling", u"usage/road unit/centimeter rounding-mode-ceiling", NumberFormatter::with() .unit(MeasureUnit::forIdentifier("centimeter", status)) .usage("road") .roundingMode(UNUM_ROUND_CEILING), Locale("en-US"), // 3048, // u"100 ft"); // Third: with "road" usage, then the usage unsetted by calling .usage("") assertFormatSingle(u"Rounding Mode propagates: rounding up", u"measure-unit/length-centimeter rounding-mode-ceiling", u"unit/centimeter rounding-mode-ceiling", NumberFormatter::with() .unit(MeasureUnit::forIdentifier("centimeter", status)) .usage("road") .roundingMode(UNUM_ROUND_CEILING) .usage(""), // unset Locale("en-US"), // 3048, // u"3,048 cm"); // TODO(icu-units#38): improve unit testing coverage. E.g. add vehicle-fuel // triggering inversion conversion code. Test with 0 too, to see // divide-by-zero behaviour. } void NumberFormatterApiTest::unitUsageErrorCodes() { IcuTestErrorCode status(*this, "unitUsageErrorCodes()"); UnlocalizedNumberFormatter unloc_formatter; unloc_formatter = NumberFormatter::forSkeleton(u"unit/foobar", status); // This gives an error, because foobar is an invalid unit: status.expectErrorAndReset(U_NUMBER_SKELETON_SYNTAX_ERROR); unloc_formatter = NumberFormatter::forSkeleton(u"usage/foobar", status); // This does not give an error, because usage is not looked up yet. status.errIfFailureAndReset("Expected behaviour: no immediate error for invalid usage"); unloc_formatter.locale("en-GB").formatInt(1, status); // Lacking a unit results in a failure. The skeleton is "incomplete", but we // support adding the unit via the fluent API, so it is not an error until // we build the formatting pipeline itself. status.expectErrorAndReset(U_ILLEGAL_ARGUMENT_ERROR); // Adding the unit as part of the fluent chain leads to success. unloc_formatter.unit(MeasureUnit::getMeter()).locale("en-GB").formatInt(1, status); status.assertSuccess(); // Setting unit to the "base dimensionless unit" is like clearing unit. unloc_formatter = NumberFormatter::with().unit(MeasureUnit()).usage("default"); // This does not give an error, because usage-vs-unit isn't resolved yet. status.errIfFailureAndReset("Expected behaviour: no immediate error for invalid unit"); unloc_formatter.locale("en-GB").formatInt(1, status); status.expectErrorAndReset(U_ILLEGAL_ARGUMENT_ERROR); } // Tests for the "skeletons" field in unitPreferenceData, as well as precision // and notation overrides. void NumberFormatterApiTest::unitUsageSkeletons() { IcuTestErrorCode status(*this, "unitUsageSkeletons()"); assertFormatSingle( u"Default >300m road preference skeletons round to 50m", u"usage/road measure-unit/length-meter", u"usage/road unit/meter", NumberFormatter::with().unit(METER).usage("road"), Locale("en-ZA"), 321, u"300 m"); assertFormatSingle( u"Precision can be overridden: override takes precedence", u"usage/road measure-unit/length-meter @#", u"usage/road unit/meter @#", NumberFormatter::with() .unit(METER) .usage("road") .precision(Precision::maxSignificantDigits(2)), Locale("en-ZA"), 321, u"320 m"); assertFormatSingle( u"Compact notation with Usage: bizarre, but possible (short)", u"compact-short usage/road measure-unit/length-meter", u"compact-short usage/road unit/meter", NumberFormatter::with() .unit(METER) .usage("road") .notation(Notation::compactShort()), Locale("en-ZA"), 987654321, u"988K km"); assertFormatSingle( u"Compact notation with Usage: bizarre, but possible (short, precision override)", u"compact-short usage/road measure-unit/length-meter @#", u"compact-short usage/road unit/meter @#", NumberFormatter::with() .unit(METER) .usage("road") .notation(Notation::compactShort()) .precision(Precision::maxSignificantDigits(2)), Locale("en-ZA"), 987654321, u"990K km"); assertFormatSingle( u"Compact notation with Usage: unusual but possible (long)", u"compact-long usage/road measure-unit/length-meter @#", u"compact-long usage/road unit/meter @#", NumberFormatter::with() .unit(METER) .usage("road") .notation(Notation::compactLong()) .precision(Precision::maxSignificantDigits(2)), Locale("en-ZA"), 987654321, u"990 thousand km"); assertFormatSingle( u"Compact notation with Usage: unusual but possible (long, precision override)", u"compact-long usage/road measure-unit/length-meter @#", u"compact-long usage/road unit/meter @#", NumberFormatter::with() .unit(METER) .usage("road") .notation(Notation::compactLong()) .precision(Precision::maxSignificantDigits(2)), Locale("en-ZA"), 987654321, u"990 thousand km"); assertFormatSingle( u"Scientific notation, not recommended, requires precision override for road", u"scientific usage/road measure-unit/length-meter", u"scientific usage/road unit/meter", NumberFormatter::with().unit(METER).usage("road").notation(Notation::scientific()), Locale("en-ZA"), 321.45, // Rounding to the nearest "50" is not exponent-adjusted in scientific notation: u"0E2 m"); assertFormatSingle( u"Scientific notation with Usage: possible when using a reasonable Precision", u"scientific usage/road measure-unit/length-meter @###", u"scientific usage/road unit/meter @###", NumberFormatter::with() .unit(METER) .usage("road") .notation(Notation::scientific()) .precision(Precision::maxSignificantDigits(4)), Locale("en-ZA"), 321.45, // 0.45 rounds down, 0.55 rounds up. u"3,214E2 m"); assertFormatSingle( u"Scientific notation with Usage: possible when using a reasonable Precision", u"scientific usage/default measure-unit/length-astronomical-unit unit-width-full-name", u"scientific usage/default unit/astronomical-unit unit-width-full-name", NumberFormatter::with() .unit(MeasureUnit::forIdentifier("astronomical-unit", status)) .usage("default") .notation(Notation::scientific()) .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME), Locale("en-ZA"), 1e20, u"1,5E28 kilometres"); status.assertSuccess(); } 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 Difference between Formal and Short (Formal Version)", u"currency/TWD unit-width-formal", u"currency/TWD unit-width-formal", NumberFormatter::with().unit(TWD).unitWidth(UNUM_UNIT_WIDTH_FORMAL), Locale("zh-TW"), 5.43, u"NT$5.43"); assertFormatSingle( u"Currency Difference between Formal and Short (Short Version)", u"currency/TWD unit-width-short", u"currency/TWD unit-width-short", NumberFormatter::with().unit(TWD).unitWidth(UNUM_UNIT_WIDTH_SHORT), Locale("zh-TW"), 5.43, u"$5.43"); assertFormatSingle( u"Currency Difference between Variant and Short (Formal Version)", u"currency/TRY unit-width-variant", u"currency/TRY unit-width-variant", NumberFormatter::with().unit(TRY).unitWidth(UNUM_UNIT_WIDTH_VARIANT), Locale("tr-TR"), 5.43, u"TL\u00A05,43"); assertFormatSingle( u"Currency Difference between Variant and Short (Short Version)", u"currency/TRY unit-width-short", u"currency/TRY unit-width-short", NumberFormatter::with().unit(TRY).unitWidth(UNUM_UNIT_WIDTH_SHORT), Locale("tr-TR"), 5.43, u"₺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::runUnitInflectionsTestCases(UnlocalizedNumberFormatter unf, UnicodeString skeleton, const UnitInflectionTestCase *cases, int32_t numCases, IcuTestErrorCode &status) { for (int32_t i = 0; i < numCases; i++) { UnitInflectionTestCase t = cases[i]; status.assertSuccess(); MeasureUnit mu = MeasureUnit::forIdentifier(t.unitIdentifier, status); if (status.errIfFailureAndReset("MeasureUnit::forIdentifier(\"%s\", ...) failed", t.unitIdentifier)) { continue; }; UnicodeString skelString = UnicodeString("unit/") + t.unitIdentifier + u" " + skeleton; const UChar *skel; if (t.unitDisplayCase == nullptr || t.unitDisplayCase[0] == 0) { unf = unf.unit(mu).unitDisplayCase(""); skel = skelString.getTerminatedBuffer(); } else { unf = unf.unit(mu).unitDisplayCase(t.unitDisplayCase); // No skeleton support for unitDisplayCase yet. skel = nullptr; } assertFormatSingle((UnicodeString("Unit: \"") + t.unitIdentifier + ("\", \"") + skeleton + u"\", locale=\"" + t.locale + u"\", case=\"" + (t.unitDisplayCase ? t.unitDisplayCase : "") + u"\", value=" + t.value) .getTerminatedBuffer(), skel, skel, unf, Locale(t.locale), t.value, t.expected); status.assertSuccess(); } } void NumberFormatterApiTest::unitInflections() { IcuTestErrorCode status(*this, "unitInflections"); UnlocalizedNumberFormatter unf; const UChar *skeleton; { // Simple inflected form test - test case based on the example in CLDR's // grammaticalFeatures.xml unf = NumberFormatter::with().unitWidth(UNUM_UNIT_WIDTH_FULL_NAME); skeleton = u"unit-width-full-name"; const UnitInflectionTestCase percentCases[] = { {"percent", "ru", nullptr, 10, u"10 процентов"}, // many {"percent", "ru", "genitive", 10, u"10 процентов"}, // many {"percent", "ru", nullptr, 33, u"33 процента"}, // few {"percent", "ru", "genitive", 33, u"33 процентов"}, // few {"percent", "ru", nullptr, 1, u"1 процент"}, // one {"percent", "ru", "genitive", 1, u"1 процента"}, // one }; runUnitInflectionsTestCases(unf, skeleton, percentCases, UPRV_LENGTHOF(percentCases), status); } { // General testing of inflection rules unf = NumberFormatter::with().unitWidth(UNUM_UNIT_WIDTH_FULL_NAME); skeleton = u"unit-width-full-name"; const UnitInflectionTestCase testCases[] = { // Check up on the basic values that the compound patterns below are // derived from: {"meter", "de", nullptr, 1, u"1 Meter"}, {"meter", "de", "genitive", 1, u"1 Meters"}, {"meter", "de", nullptr, 2, u"2 Meter"}, {"meter", "de", "dative", 2, u"2 Metern"}, {"mile", "de", nullptr, 1, u"1 Meile"}, {"mile", "de", nullptr, 2, u"2 Meilen"}, {"day", "de", nullptr, 1, u"1 Tag"}, {"day", "de", "genitive", 1, u"1 Tages"}, {"day", "de", nullptr, 2, u"2 Tage"}, {"day", "de", "dative", 2, u"2 Tagen"}, {"decade", "de", nullptr, 1, u"1\u00A0Jahrzehnt"}, {"decade", "de", nullptr, 2, u"2\u00A0Jahrzehnte"}, // Testing de "per" rules: // // // per-patterns use accusative, but since the accusative form // matches the nominative form, we're not effectively testing value1 // in the "case & per" rule above. // We have a perUnitPattern for "day" in de, so "per" rules are not // applied for these: {"meter-per-day", "de", nullptr, 1, u"1 Meter pro Tag"}, {"meter-per-day", "de", "genitive", 1, u"1 Meters pro Tag"}, {"meter-per-day", "de", nullptr, 2, u"2 Meter pro Tag"}, {"meter-per-day", "de", "dative", 2, u"2 Metern pro Tag"}, // testing code path that falls back to "root" grammaticalFeatures // but does not inflect: {"meter-per-day", "af", nullptr, 1, u"1 meter per dag"}, {"meter-per-day", "af", "dative", 1, u"1 meter per dag"}, // Decade does not have a perUnitPattern at this time (CLDR 39 / ICU // 69), so we can use it to test for selection of correct plural form. // - Note: fragile test cases, these cases will break when // whitespace is more consistently applied. {"parsec-per-decade", "de", nullptr, 1, u"1\u00A0Parsec pro Jahrzehnt"}, {"parsec-per-decade", "de", "genitive", 1, u"1 Parsec pro Jahrzehnt"}, {"parsec-per-decade", "de", nullptr, 2, u"2\u00A0Parsec pro Jahrzehnt"}, {"parsec-per-decade", "de", "dative", 2, u"2 Parsec pro Jahrzehnt"}, // Testing de "times", "power" and "prefix" rules: // // // // // // // // Prefixes in German don't change with plural or case, so these // tests can't test value0 of the following two rules: // // {"square-decimeter-dekameter", "de", nullptr, 1, u"1 Quadratdezimeter⋅Dekameter"}, {"square-decimeter-dekameter", "de", "genitive", 1, u"1 Quadratdezimeter⋅Dekameters"}, {"square-decimeter-dekameter", "de", nullptr, 2, u"2 Quadratdezimeter⋅Dekameter"}, {"square-decimeter-dekameter", "de", "dative", 2, u"2 Quadratdezimeter⋅Dekametern"}, // Feminine "Meile" better demonstrates singular-vs-plural form: {"cubic-mile-dekamile", "de", nullptr, 1, u"1 Kubikmeile⋅Dekameile"}, {"cubic-mile-dekamile", "de", nullptr, 2, u"2 Kubikmeile⋅Dekameilen"}, // French handles plural "times" and "power" structures differently: // plural form impacts all "numerator" units (denominator remains // singular like German), and "pow2" prefixes have different forms // // {"square-decimeter-square-second", "fr", nullptr, 1, u"1\u00A0décimètre carré-seconde carrée"}, {"square-decimeter-square-second", "fr", nullptr, 2, u"2\u00A0décimètres carrés-secondes carrées"}, }; runUnitInflectionsTestCases(unf, skeleton, testCases, UPRV_LENGTHOF(testCases), status); } { // Testing inflection of mixed units: unf = NumberFormatter::with().unitWidth(UNUM_UNIT_WIDTH_FULL_NAME); skeleton = u"unit-width-full-name"; const UnitInflectionTestCase testCases[] = { {"meter", "de", nullptr, 1, u"1 Meter"}, {"meter", "de", "genitive", 1, u"1 Meters"}, {"meter", "de", "dative", 2, u"2 Metern"}, {"centimeter", "de", nullptr, 1, u"1 Zentimeter"}, {"centimeter", "de", "genitive", 1, u"1 Zentimeters"}, {"centimeter", "de", "dative", 10, u"10 Zentimetern"}, // TODO(CLDR-14502): check that these inflections are correct, and // whether CLDR needs any rules for them (presumably CLDR spec // should mention it, if it's a consistent rule): {"meter-and-centimeter", "de", nullptr, 1.01, u"1 Meter, 1 Zentimeter"}, {"meter-and-centimeter", "de", "genitive", 1.01, u"1 Meters, 1 Zentimeters"}, {"meter-and-centimeter", "de", "genitive", 1.1, u"1 Meters, 10 Zentimeter"}, {"meter-and-centimeter", "de", "dative", 1.1, u"1 Meter, 10 Zentimetern"}, {"meter-and-centimeter", "de", "dative", 2.1, u"2 Metern, 10 Zentimetern"}, }; runUnitInflectionsTestCases(unf, skeleton, testCases, UPRV_LENGTHOF(testCases), status); } // TODO: add a usage case that selects between preferences with different // genders (e.g. year, month, day, hour). // TODO: look at "↑↑↑" cases: check that inheritance is done right. } void NumberFormatterApiTest::unitGender() { IcuTestErrorCode status(*this, "unitGender"); const struct TestCase { const char *locale; const char *unitIdentifier; const char *expectedGender; } cases[] = { {"de", "inch", "masculine"}, {"de", "yard", "neuter"}, {"de", "meter", "masculine"}, {"de", "liter", "masculine"}, {"de", "second", "feminine"}, {"de", "minute", "feminine"}, {"de", "hour", "feminine"}, {"de", "day", "masculine"}, {"de", "year", "neuter"}, {"de", "gram", "neuter"}, {"de", "watt", "neuter"}, {"de", "bit", "neuter"}, {"de", "byte", "neuter"}, {"fr", "inch", "masculine"}, {"fr", "yard", "masculine"}, {"fr", "meter", "masculine"}, {"fr", "liter", "masculine"}, {"fr", "second", "feminine"}, {"fr", "minute", "feminine"}, {"fr", "hour", "feminine"}, {"fr", "day", "masculine"}, {"fr", "year", "masculine"}, {"fr", "gram", "masculine"}, // grammaticalFeatures deriveCompound "per" rule takes the gender of the // numerator unit: {"de", "meter-per-hour", "masculine"}, {"fr", "meter-per-hour", "masculine"}, {"af", "meter-per-hour", ""}, // ungendered language // French "times" takes gender from first value, German takes the // second. Prefix and power does not have impact on gender for these // languages: {"de", "square-decimeter-square-second", "feminine"}, {"fr", "square-decimeter-square-second", "masculine"}, // TODO(icu-units#149): percent and permille bypasses LongNameHandler // when unitWidth is not FULL_NAME: // // Gender of per-second might be that of percent? TODO(icu-units#28) // {"de", "percent", "neuter"}, // {"fr", "percent", "masculine"}, // Built-in units whose simple units lack gender in the CLDR data file {"de", "kilopascal", "neuter"}, {"fr", "kilopascal", "masculine"}, // {"de", "pascal", ""}, // {"fr", "pascal", ""}, // Built-in units that lack gender in the CLDR data file // {"de", "revolution", ""}, // {"de", "radian", ""}, // {"de", "arc-minute", ""}, // {"de", "arc-second", ""}, {"de", "square-yard", "neuter"}, // POWER {"de", "square-inch", "masculine"}, // POWER // {"de", "dunam", ""}, // {"de", "karat", ""}, // {"de", "milligram-ofglucose-per-deciliter", ""}, // COMPOUND, ofglucose // {"de", "millimole-per-liter", ""}, // COMPOUND, mole // {"de", "permillion", ""}, // {"de", "permille", ""}, // {"de", "permyriad", ""}, // {"de", "mole", ""}, {"de", "liter-per-kilometer", "masculine"}, // COMPOUND {"de", "petabyte", "neuter"}, // PREFIX {"de", "terabit", "neuter"}, // PREFIX // {"de", "century", ""}, // {"de", "decade", ""}, {"de", "millisecond", "feminine"}, // PREFIX {"de", "microsecond", "feminine"}, // PREFIX {"de", "nanosecond", "feminine"}, // PREFIX // {"de", "ampere", ""}, // {"de", "milliampere", ""}, // PREFIX, ampere // {"de", "ohm", ""}, // {"de", "calorie", ""}, // {"de", "kilojoule", ""}, // PREFIX, joule // {"de", "joule", ""}, {"de", "kilowatt-hour", "feminine"}, // COMPOUND // {"de", "electronvolt", ""}, // {"de", "british-thermal-unit", ""}, // {"de", "therm-us", ""}, // {"de", "pound-force", ""}, // {"de", "newton", ""}, // {"de", "gigahertz", ""}, // PREFIX, hertz // {"de", "megahertz", ""}, // PREFIX, hertz // {"de", "kilohertz", ""}, // PREFIX, hertz // {"de", "hertz", ""}, // {"de", "em", ""}, // {"de", "pixel", ""}, // {"de", "megapixel", ""}, // {"de", "pixel-per-centimeter", ""}, // COMPOUND, pixel // {"de", "pixel-per-inch", ""}, // COMPOUND, pixel // {"de", "dot-per-centimeter", ""}, // COMPOUND, dot // {"de", "dot-per-inch", ""}, // COMPOUND, dot // {"de", "dot", ""}, // {"de", "earth-radius", ""}, {"de", "decimeter", "masculine"}, // PREFIX {"de", "micrometer", "masculine"}, // PREFIX {"de", "nanometer", "masculine"}, // PREFIX // {"de", "light-year", ""}, // {"de", "astronomical-unit", ""}, // {"de", "furlong", ""}, // {"de", "fathom", ""}, // {"de", "nautical-mile", ""}, // {"de", "mile-scandinavian", ""}, // {"de", "point", ""}, // {"de", "lux", ""}, // {"de", "candela", ""}, // {"de", "lumen", ""}, // {"de", "metric-ton", ""}, // {"de", "microgram", "neuter"}, // PREFIX // {"de", "ton", ""}, // {"de", "stone", ""}, // {"de", "ounce-troy", ""}, // {"de", "carat", ""}, {"de", "gigawatt", "neuter"}, // PREFIX {"de", "milliwatt", "neuter"}, // PREFIX // {"de", "horsepower", ""}, // {"de", "millimeter-ofhg", ""}, // {"de", "pound-force-per-square-inch", ""}, // COMPOUND, pound-force // {"de", "inch-ofhg", ""}, // {"de", "bar", ""}, // {"de", "millibar", ""}, // PREFIX, bar // {"de", "atmosphere", ""}, // {"de", "pascal", ""}, // PREFIX, kilopascal? neuter? // {"de", "hectopascal", ""}, // PREFIX, pascal, neuter? // {"de", "megapascal", ""}, // PREFIX, pascal, neuter? // {"de", "knot", ""}, {"de", "pound-force-foot", "masculine"}, // COMPOUND {"de", "newton-meter", "masculine"}, // COMPOUND {"de", "cubic-kilometer", "masculine"}, // POWER {"de", "cubic-yard", "neuter"}, // POWER {"de", "cubic-inch", "masculine"}, // POWER {"de", "megaliter", "masculine"}, // PREFIX {"de", "hectoliter", "masculine"}, // PREFIX // {"de", "pint-metric", ""}, // {"de", "cup-metric", ""}, {"de", "acre-foot", "masculine"}, // COMPOUND // {"de", "bushel", ""}, // {"de", "barrel", ""}, // Units missing gender in German also misses gender in French: // {"fr", "revolution", ""}, // {"fr", "radian", ""}, // {"fr", "arc-minute", ""}, // {"fr", "arc-second", ""}, {"fr", "square-yard", "masculine"}, // POWER {"fr", "square-inch", "masculine"}, // POWER // {"fr", "dunam", ""}, // {"fr", "karat", ""}, {"fr", "milligram-ofglucose-per-deciliter", "masculine"}, // COMPOUND // {"fr", "millimole-per-liter", ""}, // COMPOUND, mole // {"fr", "permillion", ""}, // {"fr", "permille", ""}, // {"fr", "permyriad", ""}, // {"fr", "mole", ""}, {"fr", "liter-per-kilometer", "masculine"}, // COMPOUND // {"fr", "petabyte", ""}, // PREFIX // {"fr", "terabit", ""}, // PREFIX // {"fr", "century", ""}, // {"fr", "decade", ""}, {"fr", "millisecond", "feminine"}, // PREFIX {"fr", "microsecond", "feminine"}, // PREFIX {"fr", "nanosecond", "feminine"}, // PREFIX // {"fr", "ampere", ""}, // {"fr", "milliampere", ""}, // PREFIX, ampere // {"fr", "ohm", ""}, // {"fr", "calorie", ""}, // {"fr", "kilojoule", ""}, // PREFIX, joule // {"fr", "joule", ""}, // {"fr", "kilowatt-hour", ""}, // COMPOUND // {"fr", "electronvolt", ""}, // {"fr", "british-thermal-unit", ""}, // {"fr", "therm-us", ""}, // {"fr", "pound-force", ""}, // {"fr", "newton", ""}, // {"fr", "gigahertz", ""}, // PREFIX, hertz // {"fr", "megahertz", ""}, // PREFIX, hertz // {"fr", "kilohertz", ""}, // PREFIX, hertz // {"fr", "hertz", ""}, // {"fr", "em", ""}, // {"fr", "pixel", ""}, // {"fr", "megapixel", ""}, // {"fr", "pixel-per-centimeter", ""}, // COMPOUND, pixel // {"fr", "pixel-per-inch", ""}, // COMPOUND, pixel // {"fr", "dot-per-centimeter", ""}, // COMPOUND, dot // {"fr", "dot-per-inch", ""}, // COMPOUND, dot // {"fr", "dot", ""}, // {"fr", "earth-radius", ""}, {"fr", "decimeter", "masculine"}, // PREFIX {"fr", "micrometer", "masculine"}, // PREFIX {"fr", "nanometer", "masculine"}, // PREFIX // {"fr", "light-year", ""}, // {"fr", "astronomical-unit", ""}, // {"fr", "furlong", ""}, // {"fr", "fathom", ""}, // {"fr", "nautical-mile", ""}, // {"fr", "mile-scandinavian", ""}, // {"fr", "point", ""}, // {"fr", "lux", ""}, // {"fr", "candela", ""}, // {"fr", "lumen", ""}, // {"fr", "metric-ton", ""}, // {"fr", "microgram", "masculine"}, // PREFIX // {"fr", "ton", ""}, // {"fr", "stone", ""}, // {"fr", "ounce-troy", ""}, // {"fr", "carat", ""}, // {"fr", "gigawatt", ""}, // PREFIX // {"fr", "milliwatt", ""}, // {"fr", "horsepower", ""}, {"fr", "millimeter-ofhg", "masculine"}, // {"fr", "pound-force-per-square-inch", ""}, // COMPOUND, pound-force {"fr", "inch-ofhg", "masculine"}, // {"fr", "bar", ""}, // {"fr", "millibar", ""}, // PREFIX, bar // {"fr", "atmosphere", ""}, // {"fr", "pascal", ""}, // PREFIX, kilopascal? // {"fr", "hectopascal", ""}, // PREFIX, pascal // {"fr", "megapascal", ""}, // PREFIX, pascal // {"fr", "knot", ""}, // {"fr", "pound-force-foot", ""}, // {"fr", "newton-meter", ""}, {"fr", "cubic-kilometer", "masculine"}, // POWER {"fr", "cubic-yard", "masculine"}, // POWER {"fr", "cubic-inch", "masculine"}, // POWER {"fr", "megaliter", "masculine"}, // PREFIX {"fr", "hectoliter", "masculine"}, // PREFIX // {"fr", "pint-metric", ""}, // {"fr", "cup-metric", ""}, {"fr", "acre-foot", "feminine"}, // COMPOUND // {"fr", "bushel", ""}, // {"fr", "barrel", ""}, // Some more French units missing gender: // {"fr", "degree", ""}, {"fr", "square-meter", "masculine"}, // POWER // {"fr", "terabyte", ""}, // PREFIX, byte // {"fr", "gigabyte", ""}, // PREFIX, byte // {"fr", "gigabit", ""}, // PREFIX, bit // {"fr", "megabyte", ""}, // PREFIX, byte // {"fr", "megabit", ""}, // PREFIX, bit // {"fr", "kilobyte", ""}, // PREFIX, byte // {"fr", "kilobit", ""}, // PREFIX, bit // {"fr", "byte", ""}, // {"fr", "bit", ""}, // {"fr", "volt", ""}, // {"fr", "watt", ""}, {"fr", "cubic-meter", "masculine"}, // POWER // gender-lacking builtins within compound units {"de", "newton-meter-per-second", "masculine"}, // TODO(ICU-21494): determine whether list genders behave as follows, // and implement proper getListGender support (covering more than just // two genders): // // gender rule for lists of people: de "neutral", fr "maleTaints" // {"de", "day-and-hour-and-minute", "neuter"}, // {"de", "hour-and-minute", "feminine"}, // {"fr", "day-and-hour-and-minute", "masculine"}, // {"fr", "hour-and-minute", "feminine"}, }; LocalizedNumberFormatter formatter; FormattedNumber fn; for (const TestCase &t : cases) { formatter = NumberFormatter::with() .unit(MeasureUnit::forIdentifier(t.unitIdentifier, status)) .locale(Locale(t.locale)); fn = formatter.formatDouble(1.1, status); assertEquals(UnicodeString("Testing gender with default width, unit: ") + t.unitIdentifier + ", locale: " + t.locale, t.expectedGender, fn.getGender(status)); status.assertSuccess(); formatter = NumberFormatter::with() .unit(MeasureUnit::forIdentifier(t.unitIdentifier, status)) .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME) .locale(Locale(t.locale)); fn = formatter.formatDouble(1.1, status); assertEquals(UnicodeString("Testing gender with UNUM_UNIT_WIDTH_FULL_NAME, unit: ") + t.unitIdentifier + ", locale: " + t.locale, t.expectedGender, fn.getGender(status)); status.assertSuccess(); } // Make sure getGender does not return garbage for genderless languages formatter = NumberFormatter::with().locale(Locale::getEnglish()); fn = formatter.formatDouble(1.1, status); status.assertSuccess(); assertEquals("getGender for a genderless language", "", fn.getGender(status)); } 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%"); // ICU-20923 assertFormatDescendingBig( u"Compact Percent", u"compact-short percent", u"K %", NumberFormatter::with() .notation(Notation::compactShort()) .unit(NoUnit::percent()), Locale::getEnglish(), u"88M%", u"8.8M%", u"876K%", u"88K%", u"8.8K%", u"876%", u"88%", u"8.8%", u"0%"); // ICU-20923 assertFormatDescendingBig( u"Compact Percent with Scale", u"compact-short percent scale/100", u"K %x100", NumberFormatter::with() .notation(Notation::compactShort()) .unit(NoUnit::percent()) .scale(Scale::powerOfTen(2)), Locale::getEnglish(), u"8.8B%", u"876M%", u"88M%", u"8.8M%", u"876K%", u"88K%", u"8.8K%", u"876%", u"0%"); // ICU-20923 assertFormatDescendingBig( u"Compact Percent Long Name", u"compact-short percent unit-width-full-name", u"K % unit-width-full-name", NumberFormatter::with() .notation(Notation::compactShort()) .unit(NoUnit::percent()) .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME), Locale::getEnglish(), u"88M percent", u"8.8M percent", u"876K percent", u"88K percent", u"8.8K percent", u"876 percent", u"88 percent", u"8.8 percent", u"0 percent"); assertFormatSingle( u"Per Percent", u"measure-unit/length-meter per-measure-unit/concentr-percent unit-width-full-name", u"measure-unit/length-meter per-measure-unit/concentr-percent unit-width-full-name", NumberFormatter::with() .unit(MeasureUnit::getMeter()) .perUnit(MeasureUnit::getPercent()) .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME), Locale::getEnglish(), 50, u"50 meters per percent"); } 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::microPropsInternals() { // Verify copy construction and assignment operators. int64_t testValues[2] = {4, 61}; MicroProps mp; assertEquals("capacity", 2, mp.mixedMeasures.getCapacity()); mp.mixedMeasures[0] = testValues[0]; mp.mixedMeasures[1] = testValues[1]; MicroProps copyConstructed(mp); MicroProps copyAssigned; int64_t *resizeResult = mp.mixedMeasures.resize(4, 4); assertTrue("Resize success", resizeResult != NULL); copyAssigned = mp; assertTrue("MicroProps success status", U_SUCCESS(mp.mixedMeasures.status)); assertTrue("Copy Constructed success status", U_SUCCESS(copyConstructed.mixedMeasures.status)); assertTrue("Copy Assigned success status", U_SUCCESS(copyAssigned.mixedMeasures.status)); assertEquals("Original values[0]", testValues[0], mp.mixedMeasures[0]); assertEquals("Original values[1]", testValues[1], mp.mixedMeasures[1]); assertEquals("Copy Constructed[0]", testValues[0], copyConstructed.mixedMeasures[0]); assertEquals("Copy Constructed[1]", testValues[1], copyConstructed.mixedMeasures[1]); assertEquals("Copy Assigned[0]", testValues[0], copyAssigned.mixedMeasures[0]); assertEquals("Copy Assigned[1]", testValues[1], copyAssigned.mixedMeasures[1]); assertEquals("Original capacity", 4, mp.mixedMeasures.getCapacity()); assertEquals("Copy Constructed capacity", 2, copyConstructed.mixedMeasures.getCapacity()); assertEquals("Copy Assigned capacity", 4, copyAssigned.mixedMeasures.getCapacity()); } /* For skeleton comparisons: this checks the toSkeleton output for `f` and for * `conciseSkeleton` against the normalized version of `uskeleton` - this does * not round-trip uskeleton itself. * * If `conciseSkeleton` starts with a "~", its round-trip check is skipped. * * If `uskeleton` is nullptr, toSkeleton is expected to return an * U_UNSUPPORTED_ERROR. */ 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); } } /* For skeleton comparisons: this checks the toSkeleton output for `f` and for * `conciseSkeleton` against the normalized version of `uskeleton` - this does * not round-trip uskeleton itself. * * If `conciseSkeleton` starts with a "~", its round-trip check is skipped. * * If `uskeleton` is nullptr, toSkeleton is expected to return an * U_UNSUPPORTED_ERROR. */ 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); } } /* For skeleton comparisons: this checks the toSkeleton output for `f` and for * `conciseSkeleton` against the normalized version of `uskeleton` - this does * not round-trip uskeleton itself. * * If `conciseSkeleton` starts with a "~", its round-trip check is skipped. * * If `uskeleton` is nullptr, toSkeleton is expected to return an * U_UNSUPPORTED_ERROR. */ 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 */