| // © 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 <cstdarg> |
| #include <cmath> |
| #include <memory> |
| #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<MeasureUnit> 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>(MeasureUnit::createMeterPerSecond(status)); |
| DAY = *LocalPointer<MeasureUnit>(MeasureUnit::createDay(status)); |
| SQUARE_METER = *LocalPointer<MeasureUnit>(MeasureUnit::createSquareMeter(status)); |
| FAHRENHEIT = *LocalPointer<MeasureUnit>(MeasureUnit::createFahrenheit(status)); |
| SECOND = *LocalPointer<MeasureUnit>(MeasureUnit::createSecond(status)); |
| POUND = *LocalPointer<MeasureUnit>(MeasureUnit::createPound(status)); |
| POUND_FORCE = *LocalPointer<MeasureUnit>(MeasureUnit::createPoundForce(status)); |
| SQUARE_MILE = *LocalPointer<MeasureUnit>(MeasureUnit::createSquareMile(status)); |
| SQUARE_INCH = *LocalPointer<MeasureUnit>(MeasureUnit::createSquareInch(status)); |
| JOULE = *LocalPointer<MeasureUnit>(MeasureUnit::createJoule(status)); |
| FURLONG = *LocalPointer<MeasureUnit>(MeasureUnit::createFurlong(status)); |
| KELVIN = *LocalPointer<MeasureUnit>(MeasureUnit::createKelvin(status)); |
| |
| MATHSANB = *LocalPointer<NumberingSystem>(NumberingSystem::createInstanceByName("mathsanb", status)); |
| LATN = *LocalPointer<NumberingSystem>(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: |
| // <deriveComponent feature="case" structure="per" value0="compound" value1="accusative"/> |
| // <deriveComponent feature="plural" structure="per" value0="compound" value1="one"/> |
| // 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: |
| // |
| // <deriveComponent feature="plural" structure="times" value0="one" value1="compound"/> |
| // <deriveComponent feature="case" structure="times" value0="nominative" value1="compound"/> |
| // |
| // <deriveComponent feature="plural" structure="prefix" value0="one" value1="compound"/> |
| // <deriveComponent feature="case" structure="prefix" value0="nominative" value1="compound"/> |
| // |
| // Prefixes in German don't change with plural or case, so these |
| // tests can't test value0 of the following two rules: |
| // <deriveComponent feature="plural" structure="power" value0="one" value1="compound"/> |
| // <deriveComponent feature="case" structure="power" value0="nominative" value1="compound"/> |
| {"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 |
| // <deriveComponent feature="plural" structure="times" value0="compound" value1="compound"/> |
| // <deriveComponent feature="plural" structure="power" value0="compound" value1="compound"/> |
| {"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<localeCount; i++) { |
| auto& locale = locales[i]; |
| UnicodeString sNoUnitPercent = uNoUnitPercent.locale(locale) |
| .formatDouble(50, status).toString(status); |
| UnicodeString sNoUnitPermille = uNoUnitPermille.locale(locale) |
| .formatDouble(50, status).toString(status); |
| UnicodeString sMeasurePercent = uMeasurePercent.locale(locale) |
| .formatDouble(50, status).toString(status); |
| UnicodeString sMeasurePermille = uMeasurePermille.locale(locale) |
| .formatDouble(50, status).toString(status); |
| |
| assertEquals(u"Percent, locale " + UnicodeString(locale.getName()), |
| sNoUnitPercent, sMeasurePercent); |
| assertEquals(u"Permille, locale " + UnicodeString(locale.getName()), |
| sNoUnitPermille, sMeasurePermille); |
| } |
| } |
| |
| void NumberFormatterApiTest::roundingFraction() { |
| assertFormatDescending( |
| u"Integer", |
| u"precision-integer", |
| u".", |
| NumberFormatter::with().precision(Precision::integer()), |
| Locale::getEnglish(), |
| u"87,650", |
| u"8,765", |
| u"876", |
| u"88", |
| u"9", |
| u"1", |
| u"0", |
| u"0", |
| u"0"); |
| |
| assertFormatDescending( |
| u"Fixed Fraction", |
| u".000", |
| u".000", |
| NumberFormatter::with().precision(Precision::fixedFraction(3)), |
| Locale::getEnglish(), |
| u"87,650.000", |
| u"8,765.000", |
| u"876.500", |
| u"87.650", |
| u"8.765", |
| u"0.876", |
| u"0.088", |
| u"0.009", |
| u"0.000"); |
| |
| assertFormatDescending( |
| u"Min Fraction", |
| u".0*", |
| u".0+", |
| NumberFormatter::with().precision(Precision::minFraction(1)), |
| Locale::getEnglish(), |
| u"87,650.0", |
| u"8,765.0", |
| u"876.5", |
| u"87.65", |
| u"8.765", |
| u"0.8765", |
| u"0.08765", |
| u"0.008765", |
| u"0.0"); |
| |
| assertFormatDescending( |
| u"Max Fraction", |
| u".#", |
| u".#", |
| NumberFormatter::with().precision(Precision::maxFraction(1)), |
| Locale::getEnglish(), |
| u"87,650", |
| u"8,765", |
| u"876.5", |
| u"87.6", |
| u"8.8", |
| u"0.9", |
| u"0.1", |
| u"0", |
| u"0"); |
| |
| assertFormatDescending( |
| u"Min/Max Fraction", |
| u".0##", |
| u".0##", |
| NumberFormatter::with().precision(Precision::minMaxFraction(1, 3)), |
| Locale::getEnglish(), |
| u"87,650.0", |
| u"8,765.0", |
| u"876.5", |
| u"87.65", |
| u"8.765", |
| u"0.876", |
| u"0.088", |
| u"0.009", |
| u"0.0"); |
| |
| assertFormatSingle( |
| u"Hide If Whole A", |
| u".00/w", |
| u".00/w", |
| NumberFormatter::with().precision(Precision::fixedFraction(2) |
| .trailingZeroDisplay(UNUM_TRAILING_ZERO_HIDE_IF_WHOLE)), |
| Locale::getEnglish(), |
| 1.2, |
| "1.20"); |
| |
| assertFormatSingle( |
| u"Hide If Whole B", |
| u".00/w", |
| u".00/w", |
| NumberFormatter::with().precision(Precision::fixedFraction(2) |
| .trailingZeroDisplay(UNUM_TRAILING_ZERO_HIDE_IF_WHOLE)), |
| Locale::getEnglish(), |
| 1, |
| "1"); |
| } |
| |
| void NumberFormatterApiTest::roundingFigures() { |
| assertFormatSingle( |
| u"Fixed Significant", |
| u"@@@", |
| u"@@@", |
| NumberFormatter::with().precision(Precision::fixedSignificantDigits(3)), |
| Locale::getEnglish(), |
| -98, |
| u"-98.0"); |
| |
| assertFormatSingle( |
| u"Fixed Significant Rounding", |
| u"@@@", |
| u"@@@", |
| NumberFormatter::with().precision(Precision::fixedSignificantDigits(3)), |
| Locale::getEnglish(), |
| -98.7654321, |
| u"-98.8"); |
| |
| assertFormatSingle( |
| u"Fixed Significant Zero", |
| u"@@@", |
| u"@@@", |
| NumberFormatter::with().precision(Precision::fixedSignificantDigits(3)), |
| Locale::getEnglish(), |
| 0, |
| u"0.00"); |
| |
| assertFormatSingle( |
| u"Min Significant", |
| u"@@*", |
| u"@@+", |
| NumberFormatter::with().precision(Precision::minSignificantDigits(2)), |
| Locale::getEnglish(), |
| -9, |
| u"-9.0"); |
| |
| assertFormatSingle( |
| u"Max Significant", |
| u"@###", |
| u"@###", |
| NumberFormatter::with().precision(Precision::maxSignificantDigits(4)), |
| Locale::getEnglish(), |
| 98.7654321, |
| u"98.77"); |
| |
| assertFormatSingle( |
| u"Min/Max Significant", |
| u"@@@#", |
| u"@@@#", |
| NumberFormatter::with().precision(Precision::minMaxSignificantDigits(3, 4)), |
| Locale::getEnglish(), |
| 9.99999, |
| u"10.0"); |
| |
| assertFormatSingle( |
| u"Fixed Significant on zero with lots of integer width", |
| u"@ integer-width/+000", |
| u"@ 000", |
| NumberFormatter::with().precision(Precision::fixedSignificantDigits(1)) |
| .integerWidth(IntegerWidth::zeroFillTo(3)), |
| Locale::getEnglish(), |
| 0, |
| "000"); |
| |
| assertFormatSingle( |
| u"Fixed Significant on zero with zero integer width", |
| u"@ integer-width/*", |
| u"@ integer-width/+", |
| NumberFormatter::with().precision(Precision::fixedSignificantDigits(1)) |
| .integerWidth(IntegerWidth::zeroFillTo(0)), |
| Locale::getEnglish(), |
| 0, |
| "0"); |
| } |
| |
| void NumberFormatterApiTest::roundingFractionFigures() { |
| assertFormatDescending( |
| u"Basic Significant", // for comparison |
| u"@#", |
| u"@#", |
| NumberFormatter::with().precision(Precision::maxSignificantDigits(2)), |
| Locale::getEnglish(), |
| u"88,000", |
| u"8,800", |
| u"880", |
| u"88", |
| u"8.8", |
| u"0.88", |
| u"0.088", |
| u"0.0088", |
| u"0"); |
| |
| assertFormatDescending( |
| u"FracSig minMaxFrac minSig", |
| u".0#/@@@*", |
| u".0#/@@@+", |
| NumberFormatter::with().precision(Precision::minMaxFraction(1, 2).withMinDigits(3)), |
| Locale::getEnglish(), |
| u"87,650.0", |
| u"8,765.0", |
| u"876.5", |
| u"87.65", |
| u"8.76", |
| u"0.876", // minSig beats maxFrac |
| u"0.0876", // minSig beats maxFrac |
| u"0.00876", // minSig beats maxFrac |
| u"0.0"); |
| |
| assertFormatDescending( |
| u"FracSig minMaxFrac maxSig A", |
| u".0##/@#", |
| u".0##/@#", |
| NumberFormatter::with().precision(Precision::minMaxFraction(1, 3).withMaxDigits(2)), |
| Locale::getEnglish(), |
| u"88,000.0", // maxSig beats maxFrac |
| u"8,800.0", // maxSig beats maxFrac |
| u"880.0", // maxSig beats maxFrac |
| u"88.0", // maxSig beats maxFrac |
| u"8.8", // maxSig beats maxFrac |
| u"0.88", // maxSig beats maxFrac |
| u"0.088", |
| u"0.009", |
| u"0.0"); |
| |
| assertFormatDescending( |
| u"FracSig minMaxFrac maxSig B", |
| u".00/@#", |
| u".00/@#", |
| NumberFormatter::with().precision(Precision::fixedFraction(2).withMaxDigits(2)), |
| Locale::getEnglish(), |
| u"88,000.00", // maxSig beats maxFrac |
| u"8,800.00", // maxSig beats maxFrac |
| u"880.00", // maxSig beats maxFrac |
| u"88.00", // maxSig beats maxFrac |
| u"8.80", // maxSig beats maxFrac |
| u"0.88", |
| u"0.09", |
| u"0.01", |
| u"0.00"); |
| |
| assertFormatSingle( |
| u"FracSig with trailing zeros A", |
| u".00/@@@*", |
| u".00/@@@+", |
| NumberFormatter::with().precision(Precision::fixedFraction(2).withMinDigits(3)), |
| Locale::getEnglish(), |
| 0.1, |
| u"0.10"); |
| |
| assertFormatSingle( |
| u"FracSig with trailing zeros B", |
| u".00/@@@*", |
| u".00/@@@+", |
| NumberFormatter::with().precision(Precision::fixedFraction(2).withMinDigits(3)), |
| Locale::getEnglish(), |
| 0.0999999, |
| u"0.10"); |
| |
| assertFormatDescending( |
| u"FracSig withSignificantDigits RELAXED", |
| u"precision-integer/@#r", |
| u"./@#r", |
| NumberFormatter::with().precision(Precision::maxFraction(0) |
| .withSignificantDigits(1, 2, UNUM_ROUNDING_PRIORITY_RELAXED)), |
| Locale::getEnglish(), |
| u"87,650", |
| u"8,765", |
| u"876", |
| u"88", |
| u"8.8", |
| u"0.88", |
| u"0.088", |
| u"0.0088", |
| u"0"); |
| |
| assertFormatDescending( |
| u"FracSig withSignificantDigits STRICT", |
| u"precision-integer/@#s", |
| u"./@#", |
| NumberFormatter::with().precision(Precision::maxFraction(0) |
| .withSignificantDigits(1, 2, UNUM_ROUNDING_PRIORITY_STRICT)), |
| Locale::getEnglish(), |
| u"88,000", |
| u"8,800", |
| u"880", |
| u"88", |
| u"9", |
| u"1", |
| u"0", |
| u"0", |
| u"0"); |
| |
| assertFormatSingle( |
| u"FracSig withSignificantDigits Trailing Zeros RELAXED", |
| u".0/@@@r", |
| u".0/@@@r", |
| NumberFormatter::with().precision(Precision::fixedFraction(1) |
| .withSignificantDigits(3, 3, UNUM_ROUNDING_PRIORITY_RELAXED)), |
| Locale::getEnglish(), |
| 1, |
| u"1.00"); |
| |
| // Trailing zeros are always retained: |
| assertFormatSingle( |
| u"FracSig withSignificantDigits Trailing Zeros STRICT", |
| u".0/@@@s", |
| u".0/@@@s", |
| NumberFormatter::with().precision(Precision::fixedFraction(1) |
| .withSignificantDigits(3, 3, UNUM_ROUNDING_PRIORITY_STRICT)), |
| Locale::getEnglish(), |
| 1, |
| u"1.00"); |
| |
| assertFormatSingle( |
| u"FracSig withSignificantDigits at rounding boundary", |
| u"precision-integer/@@@s", |
| u"./@@@s", |
| NumberFormatter::with().precision(Precision::fixedFraction(0) |
| .withSignificantDigits(3, 3, UNUM_ROUNDING_PRIORITY_STRICT)), |
| Locale::getEnglish(), |
| 9.99, |
| u"10.0"); |
| |
| assertFormatSingle( |
| u"FracSig with Trailing Zero Display", |
| u".00/@@@*/w", |
| u".00/@@@+/w", |
| NumberFormatter::with().precision(Precision::fixedFraction(2).withMinDigits(3) |
| .trailingZeroDisplay(UNUM_TRAILING_ZERO_HIDE_IF_WHOLE)), |
| Locale::getEnglish(), |
| 1, |
| u"1"); |
| } |
| |
| void NumberFormatterApiTest::roundingOther() { |
| assertFormatDescending( |
| u"Rounding None", |
| u"precision-unlimited", |
| u".+", |
| NumberFormatter::with().precision(Precision::unlimited()), |
| 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"Increment", |
| u"precision-increment/0.5", |
| u"precision-increment/0.5", |
| NumberFormatter::with().precision(Precision::increment(0.5).withMinFraction(1)), |
| Locale::getEnglish(), |
| u"87,650.0", |
| u"8,765.0", |
| u"876.5", |
| u"87.5", |
| u"9.0", |
| u"1.0", |
| u"0.0", |
| u"0.0", |
| u"0.0"); |
| |
| assertFormatDescending( |
| u"Increment with Min Fraction", |
| u"precision-increment/0.50", |
| u"precision-increment/0.50", |
| NumberFormatter::with().precision(Precision::increment(0.5).withMinFraction(2)), |
| Locale::getEnglish(), |
| u"87,650.00", |
| u"8,765.00", |
| u"876.50", |
| u"87.50", |
| u"9.00", |
| u"1.00", |
| u"0.00", |
| u"0.00", |
| u"0.00"); |
| |
| assertFormatDescending( |
| u"Strange Increment", |
| u"precision-increment/3.140", |
| u"precision-increment/3.140", |
| NumberFormatter::with().precision(Precision::increment(3.14).withMinFraction(3)), |
| Locale::getEnglish(), |
| u"87,649.960", |
| u"8,763.740", |
| u"876.060", |
| u"87.920", |
| u"9.420", |
| u"0.000", |
| u"0.000", |
| u"0.000", |
| u"0.000"); |
| |
| assertFormatDescending( |
| u"Increment Resolving to Power of 10", |
| u"precision-increment/0.010", |
| u"precision-increment/0.010", |
| NumberFormatter::with().precision(Precision::increment(0.01).withMinFraction(3)), |
| Locale::getEnglish(), |
| u"87,650.000", |
| u"8,765.000", |
| u"876.500", |
| u"87.650", |
| u"8.760", |
| u"0.880", |
| u"0.090", |
| u"0.010", |
| u"0.000"); |
| |
| assertFormatDescending( |
| u"Currency Standard", |
| u"currency/CZK precision-currency-standard", |
| u"currency/CZK precision-currency-standard", |
| NumberFormatter::with().precision(Precision::currency(UCurrencyUsage::UCURR_USAGE_STANDARD)) |
| .unit(CZK), |
| Locale::getEnglish(), |
| u"CZK 87,650.00", |
| u"CZK 8,765.00", |
| u"CZK 876.50", |
| u"CZK 87.65", |
| u"CZK 8.76", |
| u"CZK 0.88", |
| u"CZK 0.09", |
| u"CZK 0.01", |
| u"CZK 0.00"); |
| |
| assertFormatDescending( |
| u"Currency Cash", |
| u"currency/CZK precision-currency-cash", |
| u"currency/CZK precision-currency-cash", |
| NumberFormatter::with().precision(Precision::currency(UCurrencyUsage::UCURR_USAGE_CASH)) |
| .unit(CZK), |
| Locale::getEnglish(), |
| u"CZK 87,650", |
| u"CZK 8,765", |
| u"CZK 876", |
| u"CZK 88", |
| u"CZK 9", |
| u"CZK 1", |
| u"CZK 0", |
| u"CZK 0", |
| u"CZK 0"); |
| |
| assertFormatDescending( |
| u"Currency Standard with Trailing Zero Display", |
| u"currency/CZK precision-currency-standard/w", |
| u"currency/CZK precision-currency-standard/w", |
| NumberFormatter::with().precision( |
| Precision::currency(UCurrencyUsage::UCURR_USAGE_STANDARD) |
| .trailingZeroDisplay(UNUM_TRAILING_ZERO_HIDE_IF_WHOLE)) |
| .unit(CZK), |
| Locale::getEnglish(), |
| u"CZK 87,650", |
| u"CZK 8,765", |
| u"CZK 876.50", |
| u"CZK 87.65", |
| u"CZK 8.76", |
| u"CZK 0.88", |
| u"CZK 0.09", |
| u"CZK 0.01", |
| u"CZK 0"); |
| |
| assertFormatDescending( |
| u"Currency Cash with Nickel Rounding", |
| u"currency/CAD precision-currency-cash", |
| u"currency/CAD precision-currency-cash", |
| NumberFormatter::with().precision(Precision::currency(UCurrencyUsage::UCURR_USAGE_CASH)) |
| .unit(CAD), |
| Locale::getEnglish(), |
| u"CA$87,650.00", |
| u"CA$8,765.00", |
| u"CA$876.50", |
| u"CA$87.65", |
| u"CA$8.75", |
| u"CA$0.90", |
| u"CA$0.10", |
| u"CA$0.00", |
| u"CA$0.00"); |
| |
| assertFormatDescending( |
| u"Currency not in top-level fluent chain", |
| u"precision-integer", // calling .withCurrency() applies currency rounding rules immediately |
| u".", |
| NumberFormatter::with().precision( |
| Precision::currency(UCurrencyUsage::UCURR_USAGE_CASH).withCurrency(CZK)), |
| Locale::getEnglish(), |
| u"87,650", |
| u"8,765", |
| u"876", |
| u"88", |
| u"9", |
| u"1", |
| u"0", |
| u"0", |
| u"0"); |
| |
| // NOTE: Other tests cover the behavior of the other rounding modes. |
| assertFormatDescending( |
| u"Rounding Mode CEILING", |
| u"precision-integer rounding-mode-ceiling", |
| u". rounding-mode-ceiling", |
| NumberFormatter::with().precision(Precision::integer()).roundingMode(UNUM_ROUND_CEILING), |
| Locale::getEnglish(), |
| u"87,650", |
| u"8,765", |
| u"877", |
| u"88", |
| u"9", |
| u"1", |
| u"1", |
| u"1", |
| u"0"); |
| |
| assertFormatSingle( |
| u"ICU-20974 Double.MIN_NORMAL", |
| u"scientific", |
| u"E0", |
| NumberFormatter::with().notation(Notation::scientific()), |
| Locale::getEnglish(), |
| DBL_MIN, |
| u"2.225074E-308"); |
| |
| #ifndef DBL_TRUE_MIN |
| #define DBL_TRUE_MIN 4.9E-324 |
| #endif |
| |
| // Note: this behavior is intentionally different from Java; see |
| // https://github.com/google/double-conversion/issues/126 |
| assertFormatSingle( |
| u"ICU-20974 Double.MIN_VALUE", |
| u"scientific", |
| u"E0", |
| NumberFormatter::with().notation(Notation::scientific()), |
| Locale::getEnglish(), |
| DBL_TRUE_MIN, |
| u"5E-324"); |
| } |
| |
| void NumberFormatterApiTest::grouping() { |
| assertFormatDescendingBig( |
| u"Western Grouping", |
| u"group-auto", |
| u"", |
| NumberFormatter::with().grouping(UNUM_GROUPING_AUTO), |
| 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"); |
| |
| assertFormatDescendingBig( |
| u"Indic Grouping", |
| u"group-auto", |
| u"", |
| NumberFormatter::with().grouping(UNUM_GROUPING_AUTO), |
| Locale("en-IN"), |
| u"8,76,50,000", |
| u"87,65,000", |
| u"8,76,500", |
| u"87,650", |
| u"8,765", |
| u"876.5", |
| u"87.65", |
| u"8.765", |
| u"0"); |
| |
| assertFormatDescendingBig( |
| u"Western Grouping, Min 2", |
| u"group-min2", |
| u",?", |
| NumberFormatter::with().grouping(UNUM_GROUPING_MIN2), |
| Locale::getEnglish(), |
| u"87,650,000", |
| u"8,765,000", |
| u"876,500", |
| u"87,650", |
| u"8765", |
| u"876.5", |
| u"87.65", |
| u"8.765", |
| u"0"); |
| |
| assertFormatDescendingBig( |
| u"Indic Grouping, Min 2", |
| u"group-min2", |
| u",?", |
| NumberFormatter::with().grouping(UNUM_GROUPING_MIN2), |
| Locale("en-IN"), |
| u"8,76,50,000", |
| u"87,65,000", |
| u"8,76,500", |
| u"87,650", |
| u"8765", |
| u"876.5", |
| u"87.65", |
| u"8.765", |
| u"0"); |
| |
| assertFormatDescendingBig( |
| u"No Grouping", |
| u"group-off", |
| u",_", |
| NumberFormatter::with().grouping(UNUM_GROUPING_OFF), |
| Locale("en-IN"), |
| u"87650000", |
| u"8765000", |
| u"876500", |
| u"87650", |
| u"8765", |
| u"876.5", |
| u"87.65", |
| u"8.765", |
| u"0"); |
| |
| assertFormatDescendingBig( |
| u"Indic locale with THOUSANDS grouping", |
| u"group-thousands", |
| u"group-thousands", |
| NumberFormatter::with().grouping(UNUM_GROUPING_THOUSANDS), |
| Locale("en-IN"), |
| 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"); |
| |
| // NOTE: Polish is interesting because it has minimumGroupingDigits=2 in locale data |
| // (Most locales have either 1 or 2) |
| // If this test breaks due to data changes, find another locale that has minimumGroupingDigits. |
| assertFormatDescendingBig( |
| u"Polish Grouping", |
| u"group-auto", |
| u"", |
| NumberFormatter::with().grouping(UNUM_GROUPING_AUTO), |
| Locale("pl"), |
| u"87 650 000", |
| u"8 765 000", |
| u"876 500", |
| u"87 650", |
| u"8765", |
| u"876,5", |
| u"87,65", |
| u"8,765", |
| u"0"); |
| |
| assertFormatDescendingBig( |
| u"Polish Grouping, Min 2", |
| u"group-min2", |
| u",?", |
| NumberFormatter::with().grouping(UNUM_GROUPING_MIN2), |
| Locale("pl"), |
| u"87 650 000", |
| u"8 765 000", |
| u"876 500", |
| u"87 650", |
| u"8765", |
| u"876,5", |
| u"87,65", |
| u"8,765", |
| u"0"); |
| |
| assertFormatDescendingBig( |
| u"Polish Grouping, Always", |
| u"group-on-aligned", |
| u",!", |
| NumberFormatter::with().grouping(UNUM_GROUPING_ON_ALIGNED), |
| Locale("pl"), |
| 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"); |
| |
| // NOTE: Bulgarian is interesting because it has no grouping in the default currency format. |
| // If this test breaks due to data changes, find another locale that has no default grouping. |
| assertFormatDescendingBig( |
| u"Bulgarian Currency Grouping", |
| u"currency/USD group-auto", |
| u"currency/USD", |
| NumberFormatter::with().grouping(UNUM_GROUPING_AUTO).unit(USD), |
| Locale("bg"), |
| u"87650000,00 щ.д.", |
| u"8765000,00 щ.д.", |
| u"876500,00 щ.д.", |
| u"87650,00 щ.д.", |
| u"8765,00 щ.д.", |
| u"876,50 щ.д.", |
| u"87,65 щ.д.", |
| u"8,76 щ.д.", |
| u"0,00 щ.д."); |
| |
| assertFormatDescendingBig( |
| u"Bulgarian Currency Grouping, Always", |
| u"currency/USD group-on-aligned", |
| u"currency/USD ,!", |
| NumberFormatter::with().grouping(UNUM_GROUPING_ON_ALIGNED).unit(USD), |
| Locale("bg"), |
| u"87 650 000,00 щ.д.", |
| u"8 765 000,00 щ.д.", |
| u"876 500,00 щ.д.", |
| u"87 650,00 щ.д.", |
| u"8 765,00 щ.д.", |
| u"876,50 щ.д.", |
| u"87,65 щ.д.", |
| u"8,76 щ.д.", |
| u"0,00 щ.д."); |
| |
| MacroProps macros; |
| macros.grouper = Grouper(4, 1, 3, UNUM_GROUPING_COUNT); |
| assertFormatDescendingBig( |
| u"Custom Grouping via Internal API", |
| nullptr, |
| nullptr, |
| NumberFormatter::with().macros(macros), |
| Locale::getEnglish(), |
| u"8,7,6,5,0000", |
| u"8,7,6,5000", |
| u"876500", |
| u"87650", |
| u"8765", |
| u"876.5", |
| u"87.65", |
| u"8.765", |
| u"0"); |
| } |
| |
| void NumberFormatterApiTest::padding() { |
| assertFormatDescending( |
| u"Padding", |
| nullptr, |
| nullptr, |
| NumberFormatter::with().padding(Padder::none()), |
| 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"Padding", |
| nullptr, |
| nullptr, |
| NumberFormatter::with().padding( |
| Padder::codePoints( |
| '*', 8, PadPosition::UNUM_PAD_AFTER_PREFIX)), |
| 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"Padding with code points", |
| nullptr, |
| nullptr, |
| NumberFormatter::with().padding( |
| Padder::codePoints( |
| 0x101E4, 8, PadPosition::UNUM_PAD_AFTER_PREFIX)), |
| 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"Padding with wide digits", |
| nullptr, |
| nullptr, |
| NumberFormatter::with().padding( |
| Padder::codePoints( |
| '*', 8, PadPosition::UNUM_PAD_AFTER_PREFIX)) |
| .adoptSymbols(new NumberingSystem(MATHSANB)), |
| Locale::getEnglish(), |
| u"**𝟴𝟳,𝟲𝟱𝟬", |
| u"***𝟴,𝟳𝟲𝟱", |
| u"***𝟴𝟳𝟲.𝟱", |
| u"***𝟴𝟳.𝟲𝟱", |
| u"***𝟴.𝟳𝟲𝟱", |
| u"**𝟬.𝟴𝟳𝟲𝟱", |
| u"*𝟬.𝟬𝟴𝟳𝟲𝟱", |
| u"𝟬.𝟬𝟬𝟴𝟳𝟲𝟱", |
| u"*******𝟬"); |
| |
| assertFormatDescending( |
| u"Padding with currency spacing", |
| nullptr, |
| nullptr, |
| NumberFormatter::with().padding( |
| Padder::codePoints( |
| '*', 10, PadPosition::UNUM_PAD_AFTER_PREFIX)) |
| .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"); |
| |
| assertFormatSingle( |
| u"Pad Before Prefix", |
| nullptr, |
| nullptr, |
| NumberFormatter::with().padding( |
| Padder::codePoints( |
| '*', 8, PadPosition::UNUM_PAD_BEFORE_PREFIX)), |
| Locale::getEnglish(), |
| -88.88, |
| u"**-88.88"); |
| |
| assertFormatSingle( |
| u"Pad After Prefix", |
| nullptr, |
| nullptr, |
| NumberFormatter::with().padding( |
| Padder::codePoints( |
| '*', 8, PadPosition::UNUM_PAD_AFTER_PREFIX)), |
| Locale::getEnglish(), |
| -88.88, |
| u"-**88.88"); |
| |
| assertFormatSingle( |
| u"Pad Before Suffix", |
| nullptr, |
| nullptr, |
| NumberFormatter::with().padding( |
| Padder::codePoints( |
| '*', 8, PadPosition::UNUM_PAD_BEFORE_SUFFIX)).unit(NoUnit::percent()), |
| Locale::getEnglish(), |
| 88.88, |
| u"88.88**%"); |
| |
| assertFormatSingle( |
| u"Pad After Suffix", |
| nullptr, |
| nullptr, |
| NumberFormatter::with().padding( |
| Padder::codePoints( |
| '*', 8, PadPosition::UNUM_PAD_AFTER_SUFFIX)).unit(NoUnit::percent()), |
| Locale::getEnglish(), |
| 88.88, |
| u"88.88%**"); |
| |
| assertFormatSingle( |
| u"Currency Spacing with Zero Digit Padding Broken", |
| nullptr, |
| nullptr, |
| NumberFormatter::with().padding( |
| Padder::codePoints( |
| '0', 12, PadPosition::UNUM_PAD_AFTER_PREFIX)) |
| .unit(GBP) |
| .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_ISO_CODE), |
| Locale::getEnglish(), |
| 514.23, |
| u"GBP 000514.23"); // TODO: This is broken; it renders too wide (13 instead of 12). |
| } |
| |
| void NumberFormatterApiTest::integerWidth() { |
| assertFormatDescending( |
| u"Integer Width Default", |
| u"integer-width/+0", |
| u"0", |
| NumberFormatter::with().integerWidth(IntegerWidth::zeroFillTo(1)), |
| 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"Integer Width Zero Fill 0", |
| u"integer-width/*", |
| u"integer-width/+", |
| NumberFormatter::with().integerWidth(IntegerWidth::zeroFillTo(0)), |
| Locale::getEnglish(), |
| u"87,650", |
| u"8,765", |
| u"876.5", |
| u"87.65", |
| u"8.765", |
| u".8765", |
| u".08765", |
| u".008765", |
| u"0"); // see ICU-20844 |
| |
| assertFormatDescending( |
| u"Integer Width Zero Fill 3", |
| u"integer-width/+000", |
| u"000", |
| NumberFormatter::with().integerWidth(IntegerWidth::zeroFillTo(3)), |
| Locale::getEnglish(), |
| u"87,650", |
| u"8,765", |
| u"876.5", |
| u"087.65", |
| u"008.765", |
| u"000.8765", |
| u"000.08765", |
| u"000.008765", |
| u"000"); |
| |
| assertFormatDescending( |
| u"Integer Width Max 3", |
| u"integer-width/##0", |
| u"integer-width/##0", |
| NumberFormatter::with().integerWidth(IntegerWidth::zeroFillTo(1).truncateAt(3)), |
| Locale::getEnglish(), |
| u"650", |
| u"765", |
| u"876.5", |
| u"87.65", |
| u"8.765", |
| u"0.8765", |
| u"0.08765", |
| u"0.008765", |
| u"0"); |
| |
| assertFormatDescending( |
| u"Integer Width Fixed 2", |
| u"integer-width/00", |
| u"integer-width/00", |
| NumberFormatter::with().integerWidth(IntegerWidth::zeroFillTo(2).truncateAt(2)), |
| Locale::getEnglish(), |
| u"50", |
| u"65", |
| u"76.5", |
| u"87.65", |
| u"08.765", |
| u"00.8765", |
| u"00.08765", |
| u"00.008765", |
| u"00"); |
| |
| assertFormatDescending( |
| u"Integer Width Compact", |
| u"compact-short integer-width/000", |
| u"compact-short integer-width/000", |
| NumberFormatter::with() |
| .notation(Notation::compactShort()) |
| .integerWidth(IntegerWidth::zeroFillTo(3).truncateAt(3)), |
| Locale::getEnglish(), |
| u"088K", |
| u"008.8K", |
| u"876", |
| u"088", |
| u"008.8", |
| u"000.88", |
| u"000.088", |
| u"000.0088", |
| u"000"); |
| |
| assertFormatDescending( |
| u"Integer Width Scientific", |
| u"scientific integer-width/000", |
| u"scientific integer-width/000", |
| NumberFormatter::with() |
| .notation(Notation::scientific()) |
| .integerWidth(IntegerWidth::zeroFillTo(3).truncateAt(3)), |
| Locale::getEnglish(), |
| u"008.765E4", |
| u"008.765E3", |
| u"008.765E2", |
| u"008.765E1", |
| u"008.765E0", |
| u"008.765E-1", |
| u"008.765E-2", |
| u"008.765E-3", |
| u"000E0"); |
| |
| assertFormatDescending( |
| u"Integer Width Engineering", |
| u"engineering integer-width/000", |
| u"engineering integer-width/000", |
| NumberFormatter::with() |
| .notation(Notation::engineering()) |
| .integerWidth(IntegerWidth::zeroFillTo(3).truncateAt(3)), |
| Locale::getEnglish(), |
| u"087.65E3", |
| u"008.765E3", |
| u"876.5E0", |
| u"087.65E0", |
| u"008.765E0", |
| u"876.5E-3", |
| u"087.65E-3", |
| u"008.765E-3", |
| u"000E0"); |
| |
| assertFormatSingle( |
| u"Integer Width Remove All A", |
| u"integer-width/00", |
| u"integer-width/00", |
| NumberFormatter::with().integerWidth(IntegerWidth::zeroFillTo(2).truncateAt(2)), |
| "en", |
| 2500, |
| u"00"); |
| |
| assertFormatSingle( |
| u"Integer Width Remove All B", |
| u"integer-width/00", |
| u"integer-width/00", |
| NumberFormatter::with().integerWidth(IntegerWidth::zeroFillTo(2).truncateAt(2)), |
| "en", |
| 25000, |
| u"00"); |
| |
| assertFormatSingle( |
| u"Integer Width Remove All B, Bytes Mode", |
| u"integer-width/00", |
| u"integer-width/00", |
| NumberFormatter::with().integerWidth(IntegerWidth::zeroFillTo(2).truncateAt(2)), |
| "en", |
| // Note: this double produces all 17 significant digits |
| 10000000000000002000.0, |
| u"00"); |
| } |
| |
| void NumberFormatterApiTest::symbols() { |
| assertFormatDescending( |
| u"French Symbols with Japanese Data 1", |
| nullptr, |
| nullptr, |
| NumberFormatter::with().symbols(FRENCH_SYMBOLS), |
| Locale::getJapan(), |
| u"87\u202F650", |
| u"8\u202F765", |
| u"876,5", |
| u"87,65", |
| u"8,765", |
| u"0,8765", |
| u"0,08765", |
| u"0,008765", |
| u"0"); |
| |
| assertFormatSingle( |
| u"French Symbols with Japanese Data 2", |
| nullptr, |
| nullptr, |
| NumberFormatter::with().notation(Notation::compactShort()).symbols(FRENCH_SYMBOLS), |
| Locale::getJapan(), |
| 12345, |
| u"1,2\u4E07"); |
| |
| assertFormatDescending( |
| u"Latin Numbering System with Arabic Data", |
| u"currency/USD latin", |
| u"currency/USD latin", |
| NumberFormatter::with().adoptSymbols(new NumberingSystem(LATN)).unit(USD), |
| Locale("ar"), |
| u"US$ 87,650.00", |
| u"US$ 8,765.00", |
| u"US$ 876.50", |
| u"US$ 87.65", |
| u"US$ 8.76", |
| u"US$ 0.88", |
| u"US$ 0.09", |
| u"US$ 0.01", |
| u"US$ 0.00"); |
| |
| assertFormatDescending( |
| u"Math Numbering System with French Data", |
| u"numbering-system/mathsanb", |
| u"numbering-system/mathsanb", |
| NumberFormatter::with().adoptSymbols(new NumberingSystem(MATHSANB)), |
| Locale::getFrench(), |
| u"𝟴𝟳\u202F𝟲𝟱𝟬", |
| u"𝟴\u202F𝟳𝟲𝟱", |
| u"𝟴𝟳𝟲,𝟱", |
| u"𝟴𝟳,𝟲𝟱", |
| u"𝟴,𝟳𝟲𝟱", |
| u"𝟬,𝟴𝟳𝟲𝟱", |
| u"𝟬,𝟬𝟴𝟳𝟲𝟱", |
| u"𝟬,𝟬𝟬𝟴𝟳𝟲𝟱", |
| u"𝟬"); |
| |
| assertFormatSingle( |
| u"Swiss Symbols (used in documentation)", |
| nullptr, |
| nullptr, |
| NumberFormatter::with().symbols(SWISS_SYMBOLS), |
| Locale::getEnglish(), |
| 12345.67, |
| u"12’345.67"); |
| |
| assertFormatSingle( |
| u"Myanmar Symbols (used in documentation)", |
| nullptr, |
| nullptr, |
| NumberFormatter::with().symbols(MYANMAR_SYMBOLS), |
| Locale::getEnglish(), |
| 12345.67, |
| u"\u1041\u1042,\u1043\u1044\u1045.\u1046\u1047"); |
| |
| // NOTE: Locale ar puts ¤ after the number in NS arab but before the number in NS latn. |
| |
| assertFormatSingle( |
| u"Currency symbol should precede number in ar with NS latn", |
| u"currency/USD latin", |
| u"currency/USD latin", |
| NumberFormatter::with().adoptSymbols(new NumberingSystem(LATN)).unit(USD), |
| Locale("ar"), |
| 12345.67, |
| u"US$ 12,345.67"); |
| |
| assertFormatSingle( |
| u"Currency symbol should precede number in ar@numbers=latn", |
| u"currency/USD", |
| u"currency/USD", |
| NumberFormatter::with().unit(USD), |
| Locale("ar@numbers=latn"), |
| 12345.67, |
| u"US$ 12,345.67"); |
| |
| assertFormatSingle( |
| u"Currency symbol should follow number in ar-EG with NS arab", |
| u"currency/USD", |
| u"currency/USD", |
| NumberFormatter::with().unit(USD), |
| Locale("ar-EG"), |
| 12345.67, |
| u"١٢٬٣٤٥٫٦٧ US$"); |
| |
| assertFormatSingle( |
| u"Currency symbol should follow number in ar@numbers=arab", |
| u"currency/USD", |
| u"currency/USD", |
| NumberFormatter::with().unit(USD), |
| Locale("ar@numbers=arab"), |
| 12345.67, |
| u"١٢٬٣٤٥٫٦٧ US$"); |
| |
| assertFormatSingle( |
| u"NumberingSystem in API should win over @numbers keyword", |
| u"currency/USD latin", |
| u"currency/USD latin", |
| NumberFormatter::with().adoptSymbols(new NumberingSystem(LATN)).unit(USD), |
| Locale("ar@numbers=arab"), |
| 12345.67, |
| u"US$ 12,345.67"); |
| |
| UErrorCode status = U_ZERO_ERROR; |
| assertEquals( |
| "NumberingSystem in API should win over @numbers keyword in reverse order", |
| u"US$ 12,345.67", |
| NumberFormatter::withLocale(Locale("ar@numbers=arab")).adoptSymbols(new NumberingSystem(LATN)) |
| .unit(USD) |
| .formatDouble(12345.67, status) |
| .toString(status)); |
| |
| DecimalFormatSymbols symbols = SWISS_SYMBOLS; |
| UnlocalizedNumberFormatter f = NumberFormatter::with().symbols(symbols); |
| symbols.setSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kGroupingSeparatorSymbol, u"!", status); |
| assertFormatSingle( |
| u"Symbols object should be copied", |
| nullptr, |
| nullptr, |
| f, |
| Locale::getEnglish(), |
| 12345.67, |
| u"12’345.67"); |
| |
| assertFormatSingle( |
| u"The last symbols setter wins", |
| u"latin", |
| u"latin", |
| NumberFormatter::with().symbols(symbols).adoptSymbols(new NumberingSystem(LATN)), |
| Locale::getEnglish(), |
| 12345.67, |
| u"12,345.67"); |
| |
| assertFormatSingle( |
| u"The last symbols setter wins", |
| nullptr, |
| nullptr, |
| NumberFormatter::with().adoptSymbols(new NumberingSystem(LATN)).symbols(symbols), |
| Locale::getEnglish(), |
| 12345.67, |
| u"12!345.67"); |
| } |
| |
| // TODO: Enable if/when currency symbol override is added. |
| //void NumberFormatterTest::symbolsOverride() { |
| // DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(Locale::getEnglish()); |
| // dfs.setCurrencySymbol("@"); |
| // dfs.setInternationalCurrencySymbol("foo"); |
| // assertFormatSingle( |
| // u"Custom Short Currency Symbol", |
| // NumberFormatter::with().unit(Currency.getInstance("XXX")).symbols(dfs), |
| // Locale::getEnglish(), |
| // 12.3, |
| // u"@ 12.30"); |
| //} |
| |
| void NumberFormatterApiTest::sign() { |
| assertFormatSingle( |
| u"Sign Auto Positive", |
| u"sign-auto", |
| u"", |
| NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_AUTO), |
| Locale::getEnglish(), |
| 444444, |
| u"444,444"); |
| |
| assertFormatSingle( |
| u"Sign Auto Negative", |
| u"sign-auto", |
| u"", |
| NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_AUTO), |
| Locale::getEnglish(), |
| -444444, |
| u"-444,444"); |
| |
| assertFormatSingle( |
| u"Sign Auto Zero", |
| u"sign-auto", |
| u"", |
| NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_AUTO), |
| Locale::getEnglish(), |
| 0, |
| u"0"); |
| |
| assertFormatSingle( |
| u"Sign Always Positive", |
| u"sign-always", |
| u"+!", |
| NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ALWAYS), |
| Locale::getEnglish(), |
| 444444, |
| u"+444,444"); |
| |
| assertFormatSingle( |
| u"Sign Always Negative", |
| u"sign-always", |
| u"+!", |
| NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ALWAYS), |
| Locale::getEnglish(), |
| -444444, |
| u"-444,444"); |
| |
| assertFormatSingle( |
| u"Sign Always Zero", |
| u"sign-always", |
| u"+!", |
| NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ALWAYS), |
| Locale::getEnglish(), |
| 0, |
| u"+0"); |
| |
| assertFormatSingle( |
| u"Sign Never Positive", |
| u"sign-never", |
| u"+_", |
| NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_NEVER), |
| Locale::getEnglish(), |
| 444444, |
| u"444,444"); |
| |
| assertFormatSingle( |
| u"Sign Never Negative", |
| u"sign-never", |
| u"+_", |
| NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_NEVER), |
| Locale::getEnglish(), |
| -444444, |
| u"444,444"); |
| |
| assertFormatSingle( |
| u"Sign Never Zero", |
| u"sign-never", |
| u"+_", |
| NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_NEVER), |
| Locale::getEnglish(), |
| 0, |
| u"0"); |
| |
| assertFormatSingle( |
| u"Sign Accounting Positive", |
| u"currency/USD sign-accounting", |
| u"currency/USD ()", |
| NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING).unit(USD), |
| Locale::getEnglish(), |
| 444444, |
| u"$444,444.00"); |
| |
| assertFormatSingle( |
| u"Sign Accounting Negative", |
| u"currency/USD sign-accounting", |
| u"currency/USD ()", |
| NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING).unit(USD), |
| Locale::getEnglish(), |
| -444444, |
| u"($444,444.00)"); |
| |
| assertFormatSingle( |
| u"Sign Accounting Zero", |
| u"currency/USD sign-accounting", |
| u"currency/USD ()", |
| NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING).unit(USD), |
| Locale::getEnglish(), |
| 0, |
| u"$0.00"); |
| |
| assertFormatSingle( |
| u"Sign Accounting-Always Positive", |
| u"currency/USD sign-accounting-always", |
| u"currency/USD ()!", |
| NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_ALWAYS).unit(USD), |
| Locale::getEnglish(), |
| 444444, |
| u"+$444,444.00"); |
| |
| assertFormatSingle( |
| u"Sign Accounting-Always Negative", |
| u"currency/USD sign-accounting-always", |
| u"currency/USD ()!", |
| NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_ALWAYS).unit(USD), |
| Locale::getEnglish(), |
| -444444, |
| u"($444,444.00)"); |
| |
| assertFormatSingle( |
| u"Sign Accounting-Always Zero", |
| u"currency/USD sign-accounting-always", |
| u"currency/USD ()!", |
| NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_ALWAYS).unit(USD), |
| Locale::getEnglish(), |
| 0, |
| u"+$0.00"); |
| |
| assertFormatSingle( |
| u"Sign Except-Zero Positive", |
| u"sign-except-zero", |
| u"+?", |
| NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_EXCEPT_ZERO), |
| Locale::getEnglish(), |
| 444444, |
| u"+444,444"); |
| |
| assertFormatSingle( |
| u"Sign Except-Zero Negative", |
| u"sign-except-zero", |
| u"+?", |
| NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_EXCEPT_ZERO), |
| Locale::getEnglish(), |
| -444444, |
| u"-444,444"); |
| |
| assertFormatSingle( |
| u"Sign Except-Zero Zero", |
| u"sign-except-zero", |
| u"+?", |
| NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_EXCEPT_ZERO), |
| Locale::getEnglish(), |
| 0, |
| u"0"); |
| |
| assertFormatSingle( |
| u"Sign Accounting-Except-Zero Positive", |
| u"currency/USD sign-accounting-except-zero", |
| u"currency/USD ()?", |
| NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO).unit(USD), |
| Locale::getEnglish(), |
| 444444, |
| u"+$444,444.00"); |
| |
| assertFormatSingle( |
| u"Sign Accounting-Except-Zero Negative", |
| u"currency/USD sign-accounting-except-zero", |
| u"currency/USD ()?", |
| NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO).unit(USD), |
| Locale::getEnglish(), |
| -444444, |
| u"($444,444.00)"); |
| |
| assertFormatSingle( |
| u"Sign Accounting-Except-Zero Zero", |
| u"currency/USD sign-accounting-except-zero", |
| u"currency/USD ()?", |
| NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO).unit(USD), |
| Locale::getEnglish(), |
| 0, |
| u"$0.00"); |
| |
| assertFormatSingle( |
| u"Sign Negative Positive", |
| u"sign-negative", |
| u"+-", |
| NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_NEGATIVE), |
| Locale::getEnglish(), |
| 444444, |
| u"444,444"); |
| |
| assertFormatSingle( |
| u"Sign Negative Negative", |
| u"sign-negative", |
| u"+-", |
| NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_NEGATIVE), |
| Locale::getEnglish(), |
| -444444, |
| u"-444,444"); |
| |
| assertFormatSingle( |
| u"Sign Negative Negative Zero", |
| u"sign-negative", |
| u"+-", |
| NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_NEGATIVE), |
| Locale::getEnglish(), |
| -0.0000001, |
| u"0"); |
| |
| assertFormatSingle( |
| u"Sign Accounting-Negative Positive", |
| u"currency/USD sign-accounting-negative", |
| u"currency/USD ()-", |
| NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_NEGATIVE).unit(USD), |
| Locale::getEnglish(), |
| 444444, |
| u"$444,444.00"); |
| |
| assertFormatSingle( |
| u"Sign Accounting-Negative Negative", |
| u"currency/USD sign-accounting-negative", |
| u"currency/USD ()-", |
| NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_NEGATIVE).unit(USD), |
| Locale::getEnglish(), |
| -444444, |
| "($444,444.00)"); |
| |
| assertFormatSingle( |
| u"Sign Accounting-Negative Negative Zero", |
| u"currency/USD sign-accounting-negative", |
| u"currency/USD ()-", |
| NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_NEGATIVE).unit(USD), |
| Locale::getEnglish(), |
| -0.0000001, |
| u"$0.00"); |
| |
| assertFormatSingle( |
| u"Sign Accounting Negative Hidden", |
| u"currency/USD unit-width-hidden sign-accounting", |
| u"currency/USD unit-width-hidden ()", |
| NumberFormatter::with() |
| .sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING) |
| .unit(USD) |
| .unitWidth(UNUM_UNIT_WIDTH_HIDDEN), |
| Locale::getEnglish(), |
| -444444, |
| u"(444,444.00)"); |
| |
| assertFormatSingle( |
| u"Sign Accounting Negative Narrow", |
| u"currency/USD unit-width-narrow sign-accounting", |
| u"currency/USD unit-width-narrow ()", |
| NumberFormatter::with() |
| .sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING) |
| .unit(USD) |
| .unitWidth(UNUM_UNIT_WIDTH_NARROW), |
| Locale::getCanada(), |
| -444444, |
| u"($444,444.00)"); |
| |
| assertFormatSingle( |
| u"Sign Accounting Negative Short", |
| u"currency/USD sign-accounting", |
| u"currency/USD ()", |
| NumberFormatter::with() |
| .sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING) |
| .unit(USD) |
| .unitWidth(UNUM_UNIT_WIDTH_SHORT), |
| Locale::getCanada(), |
| -444444, |
| u"(US$444,444.00)"); |
| |
| assertFormatSingle( |
| u"Sign Accounting Negative Iso Code", |
| u"currency/USD unit-width-iso-code sign-accounting", |
| u"currency/USD unit-width-iso-code ()", |
| NumberFormatter::with() |
| .sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING) |
| .unit(USD) |
| .unitWidth(UNUM_UNIT_WIDTH_ISO_CODE), |
| Locale::getCanada(), |
| -444444, |
| u"(USD 444,444.00)"); |
| |
| // Note: CLDR does not provide an accounting pattern for long name currency. |
| // We fall back to normal currency format. This may change in the future. |
| assertFormatSingle( |
| u"Sign Accounting Negative Full Name", |
| u"currency/USD unit-width-full-name sign-accounting", |
| u"currency/USD unit-width-full-name ()", |
| NumberFormatter::with() |
| .sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING) |
| .unit(USD) |
| .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME), |
| Locale::getCanada(), |
| -444444, |
| u"-444,444.00 US dollars"); |
| } |
| |
| void NumberFormatterApiTest::signNearZero() { |
| // https://unicode-org.atlassian.net/browse/ICU-20709 |
| IcuTestErrorCode status(*this, "signNearZero"); |
| const struct TestCase { |
| UNumberSignDisplay sign; |
| double input; |
| const char16_t* expected; |
| } cases[] = { |
| { UNUM_SIGN_AUTO, 1.1, u"1" }, |
| { UNUM_SIGN_AUTO, 0.9, u"1" }, |
| { UNUM_SIGN_AUTO, 0.1, u"0" }, |
| { UNUM_SIGN_AUTO, -0.1, u"-0" }, // interesting case |
| { UNUM_SIGN_AUTO, -0.9, u"-1" }, |
| { UNUM_SIGN_AUTO, -1.1, u"-1" }, |
| { UNUM_SIGN_ALWAYS, 1.1, u"+1" }, |
| { UNUM_SIGN_ALWAYS, 0.9, u"+1" }, |
| { UNUM_SIGN_ALWAYS, 0.1, u"+0" }, |
| { UNUM_SIGN_ALWAYS, -0.1, u"-0" }, |
| { UNUM_SIGN_ALWAYS, -0.9, u"-1" }, |
| { UNUM_SIGN_ALWAYS, -1.1, u"-1" }, |
| { UNUM_SIGN_EXCEPT_ZERO, 1.1, u"+1" }, |
| { UNUM_SIGN_EXCEPT_ZERO, 0.9, u"+1" }, |
| { UNUM_SIGN_EXCEPT_ZERO, 0.1, u"0" }, // interesting case |
| { UNUM_SIGN_EXCEPT_ZERO, -0.1, u"0" }, // interesting case |
| { UNUM_SIGN_EXCEPT_ZERO, -0.9, u"-1" }, |
| { UNUM_SIGN_EXCEPT_ZERO, -1.1, u"-1" }, |
| { UNUM_SIGN_NEGATIVE, 1.1, u"1" }, |
| { UNUM_SIGN_NEGATIVE, 0.9, u"1" }, |
| { UNUM_SIGN_NEGATIVE, 0.1, u"0" }, |
| { UNUM_SIGN_NEGATIVE, -0.1, u"0" }, // interesting case |
| { UNUM_SIGN_NEGATIVE, -0.9, u"-1" }, |
| { UNUM_SIGN_NEGATIVE, -1.1, u"-1" }, |
| }; |
| for (auto& cas : cases) { |
| auto sign = cas.sign; |
| auto input = cas.input; |
| auto expected = cas.expected; |
| auto actual = NumberFormatter::with() |
| .sign(sign) |
| .precision(Precision::integer()) |
| .locale(Locale::getUS()) |
| .formatDouble(input, status) |
| .toString(status); |
| assertEquals( |
| DoubleToUnicodeString(input) + " @ SignDisplay " + Int64ToUnicodeString(sign), |
| expected, actual); |
| } |
| } |
| |
| void NumberFormatterApiTest::signCoverage() { |
| // https://unicode-org.atlassian.net/browse/ICU-20708 |
| IcuTestErrorCode status(*this, "signCoverage"); |
| const struct TestCase { |
| UNumberSignDisplay sign; |
| const char16_t* expectedStrings[8]; |
| } cases[] = { |
| { UNUM_SIGN_AUTO, { u"-∞", u"-1", u"-0", u"0", u"1", u"∞", u"NaN", u"-NaN" } }, |
| { UNUM_SIGN_ALWAYS, { u"-∞", u"-1", u"-0", u"+0", u"+1", u"+∞", u"+NaN", u"-NaN" } }, |
| { UNUM_SIGN_NEVER, { u"∞", u"1", u"0", u"0", u"1", u"∞", u"NaN", u"NaN" } }, |
| { UNUM_SIGN_EXCEPT_ZERO, { u"-∞", u"-1", u"0", u"0", u"+1", u"+∞", u"NaN", u"NaN" } }, |
| }; |
| double negNaN = std::copysign(uprv_getNaN(), -0.0); |
| const double inputs[] = { |
| -uprv_getInfinity(), -1, -0.0, 0, 1, uprv_getInfinity(), uprv_getNaN(), negNaN |
| }; |
| for (auto& cas : cases) { |
| auto sign = cas.sign; |
| for (int32_t i = 0; i < UPRV_LENGTHOF(inputs); i++) { |
| auto input = inputs[i]; |
| auto expected = cas.expectedStrings[i]; |
| auto actual = NumberFormatter::with() |
| .sign(sign) |
| .locale(Locale::getUS()) |
| .formatDouble(input, status) |
| .toString(status); |
| assertEquals( |
| DoubleToUnicodeString(input) + " " + Int64ToUnicodeString(sign), |
| expected, actual); |
| } |
| } |
| } |
| |
| void NumberFormatterApiTest::decimal() { |
| assertFormatDescending( |
| u"Decimal Default", |
| u"decimal-auto", |
| u"", |
| NumberFormatter::with().decimal(UNumberDecimalSeparatorDisplay::UNUM_DECIMAL_SEPARATOR_AUTO), |
| 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"Decimal Always Shown", |
| u"decimal-always", |
| u"decimal-always", |
| NumberFormatter::with().decimal(UNumberDecimalSeparatorDisplay::UNUM_DECIMAL_SEPARATOR_ALWAYS), |
| 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."); |
| } |
| |
| void NumberFormatterApiTest::scale() { |
| assertFormatDescending( |
| u"Multiplier None", |
| u"scale/1", |
| u"", |
| NumberFormatter::with().scale(Scale::none()), |
| 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"Multiplier Power of Ten", |
| u"scale/1000000", |
| u"scale/1E6", |
| NumberFormatter::with().scale(Scale::powerOfTen(6)), |
| Locale::getEnglish(), |
| u"87,650,000,000", |
| u"8,765,000,000", |
| u"876,500,000", |
| u"87,650,000", |
| u"8,765,000", |
| u"876,500", |
| u"87,650", |
| u"8,765", |
| u"0"); |
| |
| assertFormatDescending( |
| u"Multiplier Arbitrary Double", |
| u"scale/5.2", |
| u"scale/5.2", |
| NumberFormatter::with().scale(Scale::byDouble(5.2)), |
| Locale::getEnglish(), |
| u"455,780", |
| u"45,578", |
| u"4,557.8", |
| u"455.78", |
| u"45.578", |
| u"4.5578", |
| u"0.45578", |
| u"0.045578", |
| u"0"); |
| |
| assertFormatDescending( |
| u"Multiplier Arbitrary BigDecimal", |
| u"scale/5.2", |
| u"scale/5.2", |
| NumberFormatter::with().scale(Scale::byDecimal({"5.2", -1})), |
| Locale::getEnglish(), |
| u"455,780", |
| u"45,578", |
| u"4,557.8", |
| u"455.78", |
| u"45.578", |
| u"4.5578", |
| u"0.45578", |
| u"0.045578", |
| u"0"); |
| |
| assertFormatDescending( |
| u"Multiplier Arbitrary Double And Power Of Ten", |
| u"scale/5200", |
| u"scale/5200", |
| NumberFormatter::with().scale(Scale::byDoubleAndPowerOfTen(5.2, 3)), |
| Locale::getEnglish(), |
| u"455,780,000", |
| u"45,578,000", |
| u"4,557,800", |
| u"455,780", |
| u"45,578", |
| u"4,557.8", |
| u"455.78", |
| u"45.578", |
| u"0"); |
| |
| assertFormatDescending( |
| u"Multiplier Zero", |
| u"scale/0", |
| u"scale/0", |
| NumberFormatter::with().scale(Scale::byDouble(0)), |
| Locale::getEnglish(), |
| u"0", |
| u"0", |
| u"0", |
| u"0", |
| u"0", |
| u"0", |
| u"0", |
| u"0", |
| u"0"); |
| |
| assertFormatSingle( |
| u"Multiplier Skeleton Scientific Notation and Percent", |
| u"percent scale/1E2", |
| u"%x100", |
| NumberFormatter::with().unit(NoUnit::percent()).scale(Scale::powerOfTen(2)), |
| Locale::getEnglish(), |
| 0.5, |
| u"50%"); |
| |
| assertFormatSingle( |
| u"Negative Multiplier", |
| u"scale/-5.2", |
| u"scale/-5.2", |
| NumberFormatter::with().scale(Scale::byDouble(-5.2)), |
| Locale::getEnglish(), |
| 2, |
| u"-10.4"); |
| |
| assertFormatSingle( |
| u"Negative One Multiplier", |
| u"scale/-1", |
| u"scale/-1", |
| NumberFormatter::with().scale(Scale::byDouble(-1)), |
| Locale::getEnglish(), |
| 444444, |
| u"-444,444"); |
| |
| assertFormatSingle( |
| u"Two-Type Multiplier with Overlap", |
| u"scale/10000", |
| u"scale/1E4", |
| NumberFormatter::with().scale(Scale::byDoubleAndPowerOfTen(100, 2)), |
| Locale::getEnglish(), |
| 2, |
| u"20,000"); |
| } |
| |
| void NumberFormatterApiTest::locale() { |
| // Coverage for the locale setters. |
| UErrorCode status = U_ZERO_ERROR; |
| UnicodeString actual = NumberFormatter::withLocale(Locale::getFrench()).formatInt(1234, status) |
| .toString(status); |
| assertEquals("Locale withLocale()", u"1\u202f234", actual); |
| } |
| |
| void NumberFormatterApiTest::skeletonUserGuideExamples() { |
| IcuTestErrorCode status(*this, "skeletonUserGuideExamples"); |
| |
| // Test the skeleton examples in userguide/format_parse/numbers/skeletons.md |
| struct TestCase { |
| const char16_t* skeleton; |
| const char16_t* conciseSkeleton; |
| double input; |
| const char16_t* expected; |
| } cases[] = { |
| {u"percent", u"%", 25, u"25%"}, |
| {u".00", u".00", 25, u"25.00"}, |
| {u"percent .00", u"% .00", 25, u"25.00%"}, |
| {u"scale/100", u"scale/100", 0.3, u"30"}, |
| {u"percent scale/100", u"%x100", 0.3, u"30%"}, |
| {u"measure-unit/length-meter", u"unit/meter", 5, u"5 m"}, |
| {u"measure-unit/length-meter unit-width-full-name", u"unit/meter unit-width-full-name", 5, u"5 meters"}, |
| {u"currency/CAD", u"currency/CAD", 10, u"CA$10.00"}, |
| {u"currency/CAD unit-width-narrow", u"currency/CAD unit-width-narrow", 10, u"$10.00"}, |
| {u"compact-short", u"K", 5000, u"5K"}, |
| {u"compact-long", u"KK", 5000, u"5 thousand"}, |
| {u"compact-short currency/CAD", u"K currency/CAD", 5000, u"CA$5K"}, |
| {u"", u"", 5000, u"5,000"}, |
| {u"group-min2", u",?", 5000, u"5000"}, |
| {u"group-min2", u",?", 15000, u"15,000"}, |
| {u"sign-always", u"+!", 60, u"+60"}, |
| {u"sign-always", u"+!", 0, u"+0"}, |
| {u"sign-except-zero", u"+?", 60, u"+60"}, |
| {u"sign-except-zero", u"+?", 0, u"0"}, |
| {u"sign-accounting currency/CAD", u"() currency/CAD", -40, u"(CA$40.00)"} |
| }; |
| |
| for (const auto& cas : cases) { |
| status.setScope(cas.skeleton); |
| FormattedNumber actual = NumberFormatter::forSkeleton(cas.skeleton, status) |
| .locale("en-US") |
| .formatDouble(cas.input, status); |
| assertEquals(cas.skeleton, cas.expected, actual.toTempString(status)); |
| status.errIfFailureAndReset(); |
| FormattedNumber actualConcise = NumberFormatter::forSkeleton(cas.conciseSkeleton, status) |
| .locale("en-US") |
| .formatDouble(cas.input, status); |
| assertEquals(cas.conciseSkeleton, cas.expected, actualConcise.toTempString(status)); |
| status.errIfFailureAndReset(); |
| } |
| } |
| |
| void NumberFormatterApiTest::formatTypes() { |
| UErrorCode status = U_ZERO_ERROR; |
| LocalizedNumberFormatter formatter = NumberFormatter::withLocale(Locale::getEnglish()); |
| |
| // Double |
| assertEquals("Format double", "514.23", formatter.formatDouble(514.23, status).toString(status)); |
| |
| // Int64 |
| assertEquals("Format int64", "51,423", formatter.formatDouble(51423L, status).toString(status)); |
| |
| // decNumber |
| UnicodeString actual = formatter.formatDecimal("98765432123456789E1", status).toString(status); |
| assertEquals("Format decNumber", u"987,654,321,234,567,890", actual); |
| |
| // Also test proper DecimalQuantity bytes storage when all digits are in the fraction. |
| // The number needs to have exactly 40 digits, which is the size of the default buffer. |
| // (issue discovered by the address sanitizer in C++) |
| static const char* str = "0.009876543210987654321098765432109876543211"; |
| actual = formatter.precision(Precision::unlimited()).formatDecimal(str, status).toString(status); |
| assertEquals("Format decNumber to 40 digits", str, actual); |
| } |
| |
| void NumberFormatterApiTest::fieldPositionLogic() { |
| IcuTestErrorCode status(*this, "fieldPositionLogic"); |
| |
| const char16_t* message = u"Field position logic test"; |
| |
| FormattedNumber fmtd = assertFormatSingle( |
| message, |
| u"", |
| u"", |
| NumberFormatter::with(), |
| Locale::getEnglish(), |
| -9876543210.12, |
| u"-9,876,543,210.12"); |
| |
| static const UFieldPosition expectedFieldPositions[] = { |
| // field, begin index, end index |
| {UNUM_SIGN_FIELD, 0, 1}, |
| {UNUM_GROUPING_SEPARATOR_FIELD, 2, 3}, |
| {UNUM_GROUPING_SEPARATOR_FIELD, 6, 7}, |
| {UNUM_GROUPING_SEPARATOR_FIELD, 10, 11}, |
| {UNUM_INTEGER_FIELD, 1, 14}, |
| {UNUM_DECIMAL_SEPARATOR_FIELD, 14, 15}, |
| {UNUM_FRACTION_FIELD, 15, 17}}; |
| |
| assertNumberFieldPositions( |
| message, |
| fmtd, |
| expectedFieldPositions, |
| UPRV_LENGTHOF(expectedFieldPositions)); |
| |
| // Test the iteration functionality of nextFieldPosition |
| ConstrainedFieldPosition actual; |
| actual.constrainField(UFIELD_CATEGORY_NUMBER, UNUM_GROUPING_SEPARATOR_FIELD); |
| int32_t i = 1; |
| while (fmtd.nextPosition(actual, status)) { |
| UFieldPosition expected = expectedFieldPositions[i++]; |
| assertEquals( |
| UnicodeString(u"Next for grouping, field, case #") + Int64ToUnicodeString(i), |
| expected.field, |
| actual.getField()); |
| assertEquals( |
| UnicodeString(u"Next for grouping, begin index, case #") + Int64ToUnicodeString(i), |
| expected.beginIndex, |
| actual.getStart()); |
| assertEquals( |
| UnicodeString(u"Next for grouping, end index, case #") + Int64ToUnicodeString(i), |
| expected.endIndex, |
| actual.getLimit()); |
| } |
| assertEquals(u"Should have seen all grouping separators", 4, i); |
| |
| // Make sure strings without fraction do not contain fraction field |
| actual.reset(); |
| actual.constrainField(UFIELD_CATEGORY_NUMBER, UNUM_FRACTION_FIELD); |
| fmtd = NumberFormatter::withLocale("en").formatInt(5, status); |
| assertFalse(u"No fraction part in an integer", fmtd.nextPosition(actual, status)); |
| } |
| |
| void NumberFormatterApiTest::fieldPositionCoverage() { |
| IcuTestErrorCode status(*this, "fieldPositionCoverage"); |
| |
| { |
| const char16_t* message = u"Measure unit field position basic"; |
| FormattedNumber result = assertFormatSingle( |
| message, |
| u"measure-unit/temperature-fahrenheit", |
| u"unit/fahrenheit", |
| NumberFormatter::with().unit(FAHRENHEIT), |
| Locale::getEnglish(), |
| 68, |
| u"68°F"); |
| static const UFieldPosition expectedFieldPositions[] = { |
| // field, begin index, end index |
| {UNUM_INTEGER_FIELD, 0, 2}, |
| {UNUM_MEASURE_UNIT_FIELD, 2, 4}}; |
| assertNumberFieldPositions( |
| message, |
| result, |
| expectedFieldPositions, |
| UPRV_LENGTHOF(expectedFieldPositions)); |
| } |
| |
| { |
| const char16_t* message = u"Measure unit field position with compound unit"; |
| FormattedNumber result = assertFormatSingle( |
| message, |
| u"measure-unit/temperature-fahrenheit per-measure-unit/duration-day", |
| u"unit/fahrenheit-per-day", |
| NumberFormatter::with().unit(FAHRENHEIT).perUnit(DAY), |
| Locale::getEnglish(), |
| 68, |
| u"68°F/d"); |
| static const UFieldPosition expectedFieldPositions[] = { |
| // field, begin index, end index |
| {UNUM_INTEGER_FIELD, 0, 2}, |
| // coverage for old enum: |
| {DecimalFormat::kMeasureUnitField, 2, 6}}; |
| assertNumberFieldPositions( |
| message, |
| result, |
| expectedFieldPositions, |
| UPRV_LENGTHOF(expectedFieldPositions)); |
| } |
| |
| { |
| const char16_t* message = u"Measure unit field position with spaces"; |
| FormattedNumber result = assertFormatSingle( |
| message, |
| u"measure-unit/length-meter unit-width-full-name", |
| u"unit/meter unit-width-full-name", |
| NumberFormatter::with().unit(METER).unitWidth(UNUM_UNIT_WIDTH_FULL_NAME), |
| Locale::getEnglish(), |
| 68, |
| u"68 meters"); |
| static const UFieldPosition expectedFieldPositions[] = { |
| // field, begin index, end index |
| {UNUM_INTEGER_FIELD, 0, 2}, |
| // note: field starts after the space |
| {UNUM_MEASURE_UNIT_FIELD, 3, 9}}; |
| assertNumberFieldPositions( |
| message, |
| result, |
| expectedFieldPositions, |
| UPRV_LENGTHOF(expectedFieldPositions)); |
| } |
| |
| { |
| const char16_t* message = u"Measure unit field position with prefix and suffix, composed m/s"; |
| FormattedNumber result = assertFormatSingle( |
| message, |
| u"measure-unit/length-meter per-measure-unit/duration-second unit-width-full-name", |
| u"measure-unit/length-meter per-measure-unit/duration-second unit-width-full-name", |
| NumberFormatter::with().unit(METER).perUnit(SECOND).unitWidth(UNUM_UNIT_WIDTH_FULL_NAME), |
| "ky", // locale with the interesting data |
| 68, |
| u"секундасына 68 метр"); |
| static const UFieldPosition expectedFieldPositions[] = { |
| // field, begin index, end index |
| {UNUM_MEASURE_UNIT_FIELD, 0, 11}, |
| {UNUM_INTEGER_FIELD, 12, 14}, |
| {UNUM_MEASURE_UNIT_FIELD, 15, 19}}; |
| assertNumberFieldPositions( |
| message, |
| result, |
| expectedFieldPositions, |
| UPRV_LENGTHOF(expectedFieldPositions)); |
| } |
| |
| { |
| const char16_t* message = u"Measure unit field position with prefix and suffix, built-in m/s"; |
| FormattedNumber result = assertFormatSingle( |
| message, |
| u"measure-unit/speed-meter-per-second unit-width-full-name", |
| u"unit/meter-per-second unit-width-full-name", |
| NumberFormatter::with().unit(METER_PER_SECOND).unitWidth(UNUM_UNIT_WIDTH_FULL_NAME), |
| "ky", // locale with the interesting data |
| 68, |
| u"секундасына 68 метр"); |
| static const UFieldPosition expectedFieldPositions[] = { |
| // field, begin index, end index |
| {UNUM_MEASURE_UNIT_FIELD, 0, 11}, |
| {UNUM_INTEGER_FIELD, 12, 14}, |
| {UNUM_MEASURE_UNIT_FIELD, 15, 19}}; |
| assertNumberFieldPositions( |
| message, |
| result, |
| expectedFieldPositions, |
| UPRV_LENGTHOF(expectedFieldPositions)); |
| } |
| |
| { |
| const char16_t* message = u"Measure unit field position with inner spaces"; |
| FormattedNumber result = assertFormatSingle( |
| message, |
| u"measure-unit/temperature-fahrenheit unit-width-full-name", |
| u"unit/fahrenheit unit-width-full-name", |
| NumberFormatter::with().unit(FAHRENHEIT).unitWidth(UNUM_UNIT_WIDTH_FULL_NAME), |
| "vi", // locale with the interesting data |
| 68, |
| u"68 độ F"); |
| static const UFieldPosition expectedFieldPositions[] = { |
| // field, begin index, end index |
| {UNUM_INTEGER_FIELD, 0, 2}, |
| // Should trim leading/trailing spaces, but not inner spaces: |
| {UNUM_MEASURE_UNIT_FIELD, 3, 7}}; |
| assertNumberFieldPositions( |
| message, |
| result, |
| expectedFieldPositions, |
| UPRV_LENGTHOF(expectedFieldPositions)); |
| } |
| |
| { |
| // Data: other{"{0} K"} == "\u200E{0} K" |
| // If that data changes, try to find another example of a non-empty unit prefix/suffix |
| // that is also all ignorables (whitespace and bidi control marks). |
| const char16_t* message = u"Measure unit field position with fully ignorable prefix"; |
| FormattedNumber result = assertFormatSingle( |
| message, |
| u"measure-unit/temperature-kelvin", |
| u"unit/kelvin", |
| NumberFormatter::with().unit(KELVIN), |
| "fa", // locale with the interesting data |
| 68, |
| u"۶۸ K"); |
| static const UFieldPosition expectedFieldPositions[] = { |
| // field, begin index, end index |
| {UNUM_INTEGER_FIELD, 1, 3}, |
| {UNUM_MEASURE_UNIT_FIELD, 4, 5}}; |
| assertNumberFieldPositions( |
| message, |
| result, |
| expectedFieldPositions, |
| UPRV_LENGTHOF(expectedFieldPositions)); |
| } |
| |
| { |
| const char16_t* message = u"Compact field basic"; |
| FormattedNumber result = assertFormatSingle( |
| message, |
| u"compact-short", |
| u"K", |
| NumberFormatter::with().notation(Notation::compactShort()), |
| Locale::getUS(), |
| 65000, |
| u"65K"); |
| static const UFieldPosition expectedFieldPositions[] = { |
| // field, begin index, end index |
| {UNUM_INTEGER_FIELD, 0, 2}, |
| {UNUM_COMPACT_FIELD, 2, 3}}; |
| assertNumberFieldPositions( |
| message, |
| result, |
| expectedFieldPositions, |
| UPRV_LENGTHOF(expectedFieldPositions)); |
| } |
| |
| { |
| const char16_t* message = u"Compact field with spaces"; |
| FormattedNumber result = assertFormatSingle( |
| message, |
| u"compact-long", |
| u"KK", |
| NumberFormatter::with().notation(Notation::compactLong()), |
| Locale::getUS(), |
| 65000, |
| u"65 thousand"); |
| static const UFieldPosition expectedFieldPositions[] = { |
| // field, begin index, end index |
| {UNUM_INTEGER_FIELD, 0, 2}, |
| {UNUM_COMPACT_FIELD, 3, 11}}; |
| assertNumberFieldPositions( |
| message, |
| result, |
| expectedFieldPositions, |
| UPRV_LENGTHOF(expectedFieldPositions)); |
| } |
| |
| { |
| const char16_t* message = u"Compact field with inner space"; |
| FormattedNumber result = assertFormatSingle( |
| message, |
| u"compact-long", |
| u"KK", |
| NumberFormatter::with().notation(Notation::compactLong()), |
| "fil", // locale with interesting data |
| 6000, |
| u"6 na libo"); |
| static const UFieldPosition expectedFieldPositions[] = { |
| // field, begin index, end index |
| {UNUM_INTEGER_FIELD, 0, 1}, |
| {UNUM_COMPACT_FIELD, 2, 9}}; |
| assertNumberFieldPositions( |
| message, |
| result, |
| expectedFieldPositions, |
| UPRV_LENGTHOF(expectedFieldPositions)); |
| } |
| |
| { |
| const char16_t* message = u"Compact field with bidi mark"; |
| FormattedNumber result = assertFormatSingle( |
| message, |
| u"compact-long", |
| u"KK", |
| NumberFormatter::with().notation(Notation::compactLong()), |
| "he", // locale with interesting data |
| 6000, |
| u"\u200F6 אלף"); |
| static const UFieldPosition expectedFieldPositions[] = { |
| // field, begin index, end index |
| {UNUM_INTEGER_FIELD, 1, 2}, |
| {UNUM_COMPACT_FIELD, 3, 6}}; |
| assertNumberFieldPositions( |
| message, |
| result, |
| expectedFieldPositions, |
| UPRV_LENGTHOF(expectedFieldPositions)); |
| } |
| |
| { |
| const char16_t* message = u"Compact with currency fields"; |
| FormattedNumber result = assertFormatSingle( |
| message, |
| u"compact-short currency/USD", |
| u"K currency/USD", |
| NumberFormatter::with().notation(Notation::compactShort()).unit(USD), |
| "sr_Latn", // locale with interesting data |
| 65000, |
| u"65 hilj. US$"); |
| static const UFieldPosition expectedFieldPositions[] = { |
| // field, begin index, end index |
| {UNUM_INTEGER_FIELD, 0, 2}, |
| {UNUM_COMPACT_FIELD, 3, 8}, |
| {UNUM_CURRENCY_FIELD, 9, 12}}; |
| assertNumberFieldPositions( |
| message, |
| result, |
| expectedFieldPositions, |
| UPRV_LENGTHOF(expectedFieldPositions)); |
| } |
| |
| { |
| const char16_t* message = u"Currency long name fields"; |
| FormattedNumber result = assertFormatSingle( |
| message, |
| u"currency/USD unit-width-full-name", |
| u"currency/USD unit-width-full-name", |
| NumberFormatter::with().unit(USD) |
| .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME), |
| "en", |
| 12345, |
| u"12,345.00 US dollars"); |
| static const UFieldPosition expectedFieldPositions[] = { |
| // field, begin index, end index |
| {UNUM_GROUPING_SEPARATOR_FIELD, 2, 3}, |
| {UNUM_INTEGER_FIELD, 0, 6}, |
| {UNUM_DECIMAL_SEPARATOR_FIELD, 6, 7}, |
| {UNUM_FRACTION_FIELD, 7, 9}, |
| {UNUM_CURRENCY_FIELD, 10, 20}}; |
| assertNumberFieldPositions( |
| message, |
| result, |
| expectedFieldPositions, |
| UPRV_LENGTHOF(expectedFieldPositions)); |
| } |
| |
| { |
| const char16_t* message = u"Compact with measure unit fields"; |
| FormattedNumber result = assertFormatSingle( |
| message, |
| 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(UNUM_UNIT_WIDTH_FULL_NAME), |
| Locale::getUS(), |
| 65000, |
| u"65 thousand meters"); |
| static const UFieldPosition expectedFieldPositions[] = { |
| // field, begin index, end index |
| {UNUM_INTEGER_FIELD, 0, 2}, |
| {UNUM_COMPACT_FIELD, 3, 11}, |
| {UNUM_MEASURE_UNIT_FIELD, 12, 18}}; |
| assertNumberFieldPositions( |
| message, |
| result, |
| expectedFieldPositions, |
| UPRV_LENGTHOF(expectedFieldPositions)); |
| } |
| } |
| |
| void NumberFormatterApiTest::toFormat() { |
| IcuTestErrorCode status(*this, "icuFormat"); |
| LocalizedNumberFormatter lnf = NumberFormatter::withLocale("fr") |
| .precision(Precision::fixedFraction(3)); |
| LocalPointer<Format> 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<LocalizedNumberFormatterAsFormat*>(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<LocalizedNumberFormatter> 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<LocalizedNumberFormatter> 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<LocalizedNumberFormatter> lnf( |
| NumberFormatter::withLocale("en") |
| .precision(Precision::fixedFraction(2)) |
| .clone()); |
| assertTrue("should create successfully, unique_ptr", static_cast<bool>(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<LocalizedNumberFormatter> lnf = |
| NumberFormatter::withLocale("en") |
| .precision(Precision::fixedFraction(2)) |
| .clone(); |
| assertTrue("should create successfully, unique_ptr B", static_cast<bool>(lnf)); |
| assertEquals("object API test, unique_ptr B", u"1,000.00", |
| lnf->formatDouble(1000, status).toString(status)); |
| } |
| |
| // to LocalPointer via assignment |
| { |
| LocalPointer<UnlocalizedNumberFormatter> 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<std::string>(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<const FormattedValue&>(formattedNumber), |
| formattedNumber.toString(status), |
| UFIELD_CATEGORY_NUMBER, |
| expectedFieldPositions, |
| length); |
| } |
| |
| |
| #endif /* #if !UCONFIG_NO_FORMATTING */ |