blob: 75b0d006569ace63e4ca63615c111f4b499e6392 [file] [log] [blame]
// © 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 "number_asformat.h"
#include "number_types.h"
#include "number_utils.h"
#include "numbertest.h"
#include "unicode/utypes.h"
#include "number_utypes.h"
using number::impl::UFormattedNumberData;
// Horrible workaround for the lack of a status code in the constructor...
// (Also affects numbertest_range.cpp)
UErrorCode globalNumberFormatterApiTestStatus = U_ZERO_ERROR;
NumberFormatterApiTest::NumberFormatterApiTest()
: NumberFormatterApiTest(globalNumberFormatterApiTestStatus) {
}
NumberFormatterApiTest::NumberFormatterApiTest(UErrorCode& status)
: USD(u"USD", status),
GBP(u"GBP", status),
CZK(u"CZK", status),
CAD(u"CAD", status),
ESP(u"ESP", status),
PTE(u"PTE", status),
RON(u"RON", status),
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;
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));
SQUARE_MILE = *LocalPointer<MeasureUnit>(MeasureUnit::createSquareMile(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(unitCurrency);
TESTCASE_AUTO(unitPercent);
if (!quick) {
// Slow test: run in exhaustive mode only
TESTCASE_AUTO(percentParity);
}
TESTCASE_AUTO(roundingFraction);
TESTCASE_AUTO(roundingFigures);
TESTCASE_AUTO(roundingFractionFigures);
TESTCASE_AUTO(roundingOther);
TESTCASE_AUTO(grouping);
TESTCASE_AUTO(padding);
TESTCASE_AUTO(integerWidth);
TESTCASE_AUTO(symbols);
// TODO: Add this method if currency symbols override support is added.
//TESTCASE_AUTO(symbolsOverride);
TESTCASE_AUTO(sign);
TESTCASE_AUTO(signNearZero);
TESTCASE_AUTO(signCoverage);
TESTCASE_AUTO(decimal);
TESTCASE_AUTO(scale);
TESTCASE_AUTO(locale);
TESTCASE_AUTO(skeletonUserGuideExamples);
TESTCASE_AUTO(formatTypes);
TESTCASE_AUTO(fieldPositionLogic);
TESTCASE_AUTO(fieldPositionCoverage);
TESTCASE_AUTO(toFormat);
TESTCASE_AUTO(errors);
if (!quick) {
// Slow test: run in exhaustive mode only
// (somewhat slow to check all permutations of settings)
TESTCASE_AUTO(validRanges);
}
TESTCASE_AUTO(copyMove);
TESTCASE_AUTO(localPointerCAPI);
TESTCASE_AUTO(toObject);
TESTCASE_AUTO(toDecimalNumber);
TESTCASE_AUTO_END;
}
void NumberFormatterApiTest::notationSimple() {
assertFormatDescending(
u"Basic",
u"",
u"",
NumberFormatter::with(),
Locale::getEnglish(),
u"87,650",
u"8,765",
u"876.5",
u"87.65",
u"8.765",
u"0.8765",
u"0.08765",
u"0.008765",
u"0");
assertFormatDescendingBig(
u"Big Simple",
u"notation-simple",
u"",
NumberFormatter::with().notation(Notation::simple()),
Locale::getEnglish(),
u"87,650,000",
u"8,765,000",
u"876,500",
u"87,650",
u"8,765",
u"876.5",
u"87.65",
u"8.765",
u"0");
assertFormatSingle(
u"Basic with Negative Sign",
u"",
u"",
NumberFormatter::with(),
Locale::getEnglish(),
-9876543.21,
u"-9,876,543.21");
}
void NumberFormatterApiTest::notationScientific() {
assertFormatDescending(
u"Scientific",
u"scientific",
u"E0",
NumberFormatter::with().notation(Notation::scientific()),
Locale::getEnglish(),
u"8.765E4",
u"8.765E3",
u"8.765E2",
u"8.765E1",
u"8.765E0",
u"8.765E-1",
u"8.765E-2",
u"8.765E-3",
u"0E0");
assertFormatDescending(
u"Engineering",
u"engineering",
u"EE0",
NumberFormatter::with().notation(Notation::engineering()),
Locale::getEnglish(),
u"87.65E3",
u"8.765E3",
u"876.5E0",
u"87.65E0",
u"8.765E0",
u"876.5E-3",
u"87.65E-3",
u"8.765E-3",
u"0E0");
assertFormatDescending(
u"Scientific sign always shown",
u"scientific/sign-always",
u"E+!0",
NumberFormatter::with().notation(
Notation::scientific().withExponentSignDisplay(UNumberSignDisplay::UNUM_SIGN_ALWAYS)),
Locale::getEnglish(),
u"8.765E+4",
u"8.765E+3",
u"8.765E+2",
u"8.765E+1",
u"8.765E+0",
u"8.765E-1",
u"8.765E-2",
u"8.765E-3",
u"0E+0");
assertFormatDescending(
u"Scientific min exponent digits",
u"scientific/*ee",
u"E00",
NumberFormatter::with().notation(Notation::scientific().withMinExponentDigits(2)),
Locale::getEnglish(),
u"8.765E04",
u"8.765E03",
u"8.765E02",
u"8.765E01",
u"8.765E00",
u"8.765E-01",
u"8.765E-02",
u"8.765E-03",
u"0E00");
assertFormatSingle(
u"Scientific Negative",
u"scientific",
u"E0",
NumberFormatter::with().notation(Notation::scientific()),
Locale::getEnglish(),
-1000000,
u"-1E6");
assertFormatSingle(
u"Scientific Infinity",
u"scientific",
u"E0",
NumberFormatter::with().notation(Notation::scientific()),
Locale::getEnglish(),
-uprv_getInfinity(),
u"-∞");
assertFormatSingle(
u"Scientific NaN",
u"scientific",
u"E0",
NumberFormatter::with().notation(Notation::scientific()),
Locale::getEnglish(),
uprv_getNaN(),
u"NaN");
}
void NumberFormatterApiTest::notationCompact() {
assertFormatDescending(
u"Compact Short",
u"compact-short",
u"K",
NumberFormatter::with().notation(Notation::compactShort()),
Locale::getEnglish(),
u"88K",
u"8.8K",
u"876",
u"88",
u"8.8",
u"0.88",
u"0.088",
u"0.0088",
u"0");
assertFormatDescending(
u"Compact Long",
u"compact-long",
u"KK",
NumberFormatter::with().notation(Notation::compactLong()),
Locale::getEnglish(),
u"88 thousand",
u"8.8 thousand",
u"876",
u"88",
u"8.8",
u"0.88",
u"0.088",
u"0.0088",
u"0");
assertFormatDescending(
u"Compact Short Currency",
u"compact-short currency/USD",
u"K currency/USD",
NumberFormatter::with().notation(Notation::compactShort()).unit(USD),
Locale::getEnglish(),
u"$88K",
u"$8.8K",
u"$876",
u"$88",
u"$8.8",
u"$0.88",
u"$0.088",
u"$0.0088",
u"$0");
assertFormatDescending(
u"Compact Short with ISO Currency",
u"compact-short currency/USD unit-width-iso-code",
u"K currency/USD unit-width-iso-code",
NumberFormatter::with().notation(Notation::compactShort())
.unit(USD)
.unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_ISO_CODE),
Locale::getEnglish(),
u"USD 88K",
u"USD 8.8K",
u"USD 876",
u"USD 88",
u"USD 8.8",
u"USD 0.88",
u"USD 0.088",
u"USD 0.0088",
u"USD 0");
assertFormatDescending(
u"Compact Short with Long Name Currency",
u"compact-short currency/USD unit-width-full-name",
u"K currency/USD unit-width-full-name",
NumberFormatter::with().notation(Notation::compactShort())
.unit(USD)
.unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
Locale::getEnglish(),
u"88K US dollars",
u"8.8K US dollars",
u"876 US dollars",
u"88 US dollars",
u"8.8 US dollars",
u"0.88 US dollars",
u"0.088 US dollars",
u"0.0088 US dollars",
u"0 US dollars");
// Note: Most locales don't have compact long currency, so this currently falls back to short.
// This test case should be fixed when proper compact long currency patterns are added.
assertFormatDescending(
u"Compact Long Currency",
u"compact-long currency/USD",
u"KK currency/USD",
NumberFormatter::with().notation(Notation::compactLong()).unit(USD),
Locale::getEnglish(),
u"$88K", // should be something like "$88 thousand"
u"$8.8K",
u"$876",
u"$88",
u"$8.8",
u"$0.88",
u"$0.088",
u"$0.0088",
u"$0");
// Note: Most locales don't have compact long currency, so this currently falls back to short.
// This test case should be fixed when proper compact long currency patterns are added.
assertFormatDescending(
u"Compact Long with ISO Currency",
u"compact-long currency/USD unit-width-iso-code",
u"KK currency/USD unit-width-iso-code",
NumberFormatter::with().notation(Notation::compactLong())
.unit(USD)
.unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_ISO_CODE),
Locale::getEnglish(),
u"USD 88K", // should be something like "USD 88 thousand"
u"USD 8.8K",
u"USD 876",
u"USD 88",
u"USD 8.8",
u"USD 0.88",
u"USD 0.088",
u"USD 0.0088",
u"USD 0");
// TODO: This behavior could be improved and should be revisited.
assertFormatDescending(
u"Compact Long with Long Name Currency",
u"compact-long currency/USD unit-width-full-name",
u"KK currency/USD unit-width-full-name",
NumberFormatter::with().notation(Notation::compactLong())
.unit(USD)
.unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
Locale::getEnglish(),
u"88 thousand US dollars",
u"8.8 thousand US dollars",
u"876 US dollars",
u"88 US dollars",
u"8.8 US dollars",
u"0.88 US dollars",
u"0.088 US dollars",
u"0.0088 US dollars",
u"0 US dollars");
assertFormatSingle(
u"Compact Plural One",
u"compact-long",
u"KK",
NumberFormatter::with().notation(Notation::compactLong()),
Locale::createFromName("es"),
1000000,
u"1 millón");
assertFormatSingle(
u"Compact Plural Other",
u"compact-long",
u"KK",
NumberFormatter::with().notation(Notation::compactLong()),
Locale::createFromName("es"),
2000000,
u"2 millones");
assertFormatSingle(
u"Compact with Negative Sign",
u"compact-short",
u"K",
NumberFormatter::with().notation(Notation::compactShort()),
Locale::getEnglish(),
-9876543.21,
u"-9.9M");
assertFormatSingle(
u"Compact Rounding",
u"compact-short",
u"K",
NumberFormatter::with().notation(Notation::compactShort()),
Locale::getEnglish(),
990000,
u"990K");
assertFormatSingle(
u"Compact Rounding",
u"compact-short",
u"K",
NumberFormatter::with().notation(Notation::compactShort()),
Locale::getEnglish(),
999000,
u"999K");
assertFormatSingle(
u"Compact Rounding",
u"compact-short",
u"K",
NumberFormatter::with().notation(Notation::compactShort()),
Locale::getEnglish(),
999900,
u"1M");
assertFormatSingle(
u"Compact Rounding",
u"compact-short",
u"K",
NumberFormatter::with().notation(Notation::compactShort()),
Locale::getEnglish(),
9900000,
u"9.9M");
assertFormatSingle(
u"Compact Rounding",
u"compact-short",
u"K",
NumberFormatter::with().notation(Notation::compactShort()),
Locale::getEnglish(),
9990000,
u"10M");
assertFormatSingle(
u"Compact in zh-Hant-HK",
u"compact-short",
u"K",
NumberFormatter::with().notation(Notation::compactShort()),
Locale("zh-Hant-HK"),
1e7,
u"10M");
assertFormatSingle(
u"Compact in zh-Hant",
u"compact-short",
u"K",
NumberFormatter::with().notation(Notation::compactShort()),
Locale("zh-Hant"),
1e7,
u"1000\u842C");
assertFormatSingle(
u"Compact Infinity",
u"compact-short",
u"K",
NumberFormatter::with().notation(Notation::compactShort()),
Locale::getEnglish(),
-uprv_getInfinity(),
u"-∞");
assertFormatSingle(
u"Compact NaN",
u"compact-short",
u"K",
NumberFormatter::with().notation(Notation::compactShort()),
Locale::getEnglish(),
uprv_getNaN(),
u"NaN");
// NOTE: There is no API for compact custom data in C++
// and thus no "Compact Somali No Figure" test
}
void NumberFormatterApiTest::unitMeasure() {
assertFormatDescending(
u"Meters Short and unit() method",
u"measure-unit/length-meter",
u"unit/meter",
NumberFormatter::with().unit(MeasureUnit::getMeter()),
Locale::getEnglish(),
u"87,650 m",
u"8,765 m",
u"876.5 m",
u"87.65 m",
u"8.765 m",
u"0.8765 m",
u"0.08765 m",
u"0.008765 m",
u"0 m");
assertFormatDescending(
u"Meters Long and adoptUnit() method",
u"measure-unit/length-meter unit-width-full-name",
u"unit/meter unit-width-full-name",
NumberFormatter::with().adoptUnit(new MeasureUnit(METER))
.unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
Locale::getEnglish(),
u"87,650 meters",
u"8,765 meters",
u"876.5 meters",
u"87.65 meters",
u"8.765 meters",
u"0.8765 meters",
u"0.08765 meters",
u"0.008765 meters",
u"0 meters");
assertFormatDescending(
u"Compact Meters Long",
u"compact-long measure-unit/length-meter unit-width-full-name",
u"KK unit/meter unit-width-full-name",
NumberFormatter::with().notation(Notation::compactLong())
.unit(METER)
.unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
Locale::getEnglish(),
u"88 thousand meters",
u"8.8 thousand meters",
u"876 meters",
u"88 meters",
u"8.8 meters",
u"0.88 meters",
u"0.088 meters",
u"0.0088 meters",
u"0 meters");
// TODO: Implement Measure in C++
// assertFormatSingleMeasure(
// u"Meters with Measure Input",
// NumberFormatter::with().unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
// Locale::getEnglish(),
// new Measure(5.43, new MeasureUnit(METER)),
// u"5.43 meters");
// TODO: Implement Measure in C++
// assertFormatSingleMeasure(
// u"Measure format method takes precedence over fluent chain",
// NumberFormatter::with().unit(METER),
// Locale::getEnglish(),
// new Measure(5.43, USD),
// u"$5.43");
assertFormatSingle(
u"Meters with Negative Sign",
u"measure-unit/length-meter",
u"unit/meter",
NumberFormatter::with().unit(METER),
Locale::getEnglish(),
-9876543.21,
u"-9,876,543.21 m");
// The locale string "सान" appears only in brx.txt:
assertFormatSingle(
u"Interesting Data Fallback 1",
u"measure-unit/duration-day unit-width-full-name",
u"unit/day unit-width-full-name",
NumberFormatter::with().unit(DAY).unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
Locale::createFromName("brx"),
5.43,
u"5.43 सान");
// Requires following the alias from unitsNarrow to unitsShort:
assertFormatSingle(
u"Interesting Data Fallback 2",
u"measure-unit/duration-day unit-width-narrow",
u"unit/day unit-width-narrow",
NumberFormatter::with().unit(DAY).unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW),
Locale::createFromName("brx"),
5.43,
u"5.43 d");
// en_001.txt has a unitsNarrow/area/square-meter table, but table does not contain the OTHER unit,
// requiring fallback to the root.
assertFormatSingle(
u"Interesting Data Fallback 3",
u"measure-unit/area-square-meter unit-width-narrow",
u"unit/square-meter unit-width-narrow",
NumberFormatter::with().unit(SQUARE_METER).unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW),
Locale::createFromName("en-GB"),
5.43,
u"5.43m²");
// Try accessing a narrow unit directly from root.
assertFormatSingle(
u"Interesting Data Fallback 4",
u"measure-unit/area-square-meter unit-width-narrow",
u"unit/square-meter unit-width-narrow",
NumberFormatter::with().unit(SQUARE_METER).unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW),
Locale::createFromName("root"),
5.43,
u"5.43 m²");
// es_US has "{0}°" for unitsNarrow/temperature/FAHRENHEIT.
// NOTE: This example is in the documentation.
assertFormatSingle(
u"Difference between Narrow and Short (Narrow Version)",
u"measure-unit/temperature-fahrenheit unit-width-narrow",
u"unit/fahrenheit unit-width-narrow",
NumberFormatter::with().unit(FAHRENHEIT).unitWidth(UNUM_UNIT_WIDTH_NARROW),
Locale("es-US"),
5.43,
u"5.43°");
assertFormatSingle(
u"Difference between Narrow and Short (Short Version)",
u"measure-unit/temperature-fahrenheit unit-width-short",
u"unit/fahrenheit unit-width-short",
NumberFormatter::with().unit(FAHRENHEIT).unitWidth(UNUM_UNIT_WIDTH_SHORT),
Locale("es-US"),
5.43,
u"5.43 °F");
assertFormatSingle(
u"MeasureUnit form without {0} in CLDR pattern",
u"measure-unit/temperature-kelvin unit-width-full-name",
u"unit/kelvin unit-width-full-name",
NumberFormatter::with().unit(KELVIN).unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
Locale("es-MX"),
1,
u"kelvin");
assertFormatSingle(
u"MeasureUnit form without {0} in CLDR pattern and wide base form",
u"measure-unit/temperature-kelvin .00000000000000000000 unit-width-full-name",
u"unit/kelvin .00000000000000000000 unit-width-full-name",
NumberFormatter::with().precision(Precision::fixedFraction(20))
.unit(KELVIN)
.unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
Locale("es-MX"),
1,
u"kelvin");
assertFormatSingle(
u"Person unit not in short form",
u"measure-unit/duration-year-person unit-width-full-name",
u"unit/year-person unit-width-full-name",
NumberFormatter::with().unit(MeasureUnit::getYearPerson())
.unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
Locale("es-MX"),
5,
u"5 a\u00F1os");
}
void NumberFormatterApiTest::unitCompoundMeasure() {
assertFormatDescending(
u"Meters Per Second Short (unit that simplifies) and perUnit method",
u"measure-unit/length-meter per-measure-unit/duration-second",
u"unit/meter-per-second",
NumberFormatter::with().unit(METER).perUnit(SECOND),
Locale::getEnglish(),
u"87,650 m/s",
u"8,765 m/s",
u"876.5 m/s",
u"87.65 m/s",
u"8.765 m/s",
u"0.8765 m/s",
u"0.08765 m/s",
u"0.008765 m/s",
u"0 m/s");
assertFormatDescending(
u"Pounds Per Square Mile Short (secondary unit has per-format) and adoptPerUnit method",
u"measure-unit/mass-pound per-measure-unit/area-square-mile",
u"unit/pound-per-square-mile",
NumberFormatter::with().unit(POUND).adoptPerUnit(new MeasureUnit(SQUARE_MILE)),
Locale::getEnglish(),
u"87,650 lb/mi²",
u"8,765 lb/mi²",
u"876.5 lb/mi²",
u"87.65 lb/mi²",
u"8.765 lb/mi²",
u"0.8765 lb/mi²",
u"0.08765 lb/mi²",
u"0.008765 lb/mi²",
u"0 lb/mi²");
assertFormatDescending(
u"Joules Per Furlong Short (unit with no simplifications or special patterns)",
u"measure-unit/energy-joule per-measure-unit/length-furlong",
u"unit/joule-per-furlong",
NumberFormatter::with().unit(JOULE).perUnit(FURLONG),
Locale::getEnglish(),
u"87,650 J/fur",
u"8,765 J/fur",
u"876.5 J/fur",
u"87.65 J/fur",
u"8.765 J/fur",
u"0.8765 J/fur",
u"0.08765 J/fur",
u"0.008765 J/fur",
u"0 J/fur");
// TODO(ICU-20941): Support constructions such as this one.
// assertFormatDescending(
// u"Joules Per Furlong Short with unit identifier via API",
// u"measure-unit/energy-joule per-measure-unit/length-furlong",
// u"unit/joule-per-furlong",
// NumberFormatter::with().unit(MeasureUnit::forIdentifier("joule-per-furlong", status)),
// Locale::getEnglish(),
// u"87,650 J/fur",
// u"8,765 J/fur",
// u"876.5 J/fur",
// u"87.65 J/fur",
// u"8.765 J/fur",
// u"0.8765 J/fur",
// u"0.08765 J/fur",
// u"0.008765 J/fur",
// u"0 J/fur");
}
void NumberFormatterApiTest::unitCurrency() {
assertFormatDescending(
u"Currency",
u"currency/GBP",
u"currency/GBP",
NumberFormatter::with().unit(GBP),
Locale::getEnglish(),
u"£87,650.00",
u"£8,765.00",
u"£876.50",
u"£87.65",
u"£8.76",
u"£0.88",
u"£0.09",
u"£0.01",
u"£0.00");
assertFormatDescending(
u"Currency ISO",
u"currency/GBP unit-width-iso-code",
u"currency/GBP unit-width-iso-code",
NumberFormatter::with().unit(GBP).unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_ISO_CODE),
Locale::getEnglish(),
u"GBP 87,650.00",
u"GBP 8,765.00",
u"GBP 876.50",
u"GBP 87.65",
u"GBP 8.76",
u"GBP 0.88",
u"GBP 0.09",
u"GBP 0.01",
u"GBP 0.00");
assertFormatDescending(
u"Currency Long Name",
u"currency/GBP unit-width-full-name",
u"currency/GBP unit-width-full-name",
NumberFormatter::with().unit(GBP).unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
Locale::getEnglish(),
u"87,650.00 British pounds",
u"8,765.00 British pounds",
u"876.50 British pounds",
u"87.65 British pounds",
u"8.76 British pounds",
u"0.88 British pounds",
u"0.09 British pounds",
u"0.01 British pounds",
u"0.00 British pounds");
assertFormatDescending(
u"Currency Hidden",
u"currency/GBP unit-width-hidden",
u"currency/GBP unit-width-hidden",
NumberFormatter::with().unit(GBP).unitWidth(UNUM_UNIT_WIDTH_HIDDEN),
Locale::getEnglish(),
u"87,650.00",
u"8,765.00",
u"876.50",
u"87.65",
u"8.76",
u"0.88",
u"0.09",
u"0.01",
u"0.00");
// TODO: Implement Measure in C++
// assertFormatSingleMeasure(
// u"Currency with CurrencyAmount Input",
// NumberFormatter::with(),
// Locale::getEnglish(),
// new CurrencyAmount(5.43, GBP),
// u"£5.43");
// TODO: Enable this test when DecimalFormat wrapper is done.
// assertFormatSingle(
// u"Currency Long Name from Pattern Syntax", NumberFormatter.fromDecimalFormat(
// PatternStringParser.parseToProperties("0 ¤¤¤"),
// DecimalFormatSymbols.getInstance(Locale::getEnglish()),
// null).unit(GBP), Locale::getEnglish(), 1234567.89, u"1234568 British pounds");
assertFormatSingle(
u"Currency with Negative Sign",
u"currency/GBP",
u"currency/GBP",
NumberFormatter::with().unit(GBP),
Locale::getEnglish(),
-9876543.21,
u"-£9,876,543.21");
// The full currency symbol is not shown in NARROW format.
// NOTE: This example is in the documentation.
assertFormatSingle(
u"Currency Difference between Narrow and Short (Narrow Version)",
u"currency/USD unit-width-narrow",
u"currency/USD unit-width-narrow",
NumberFormatter::with().unit(USD).unitWidth(UNUM_UNIT_WIDTH_NARROW),
Locale("en-CA"),
5.43,
u"$5.43");
assertFormatSingle(
u"Currency Difference between Narrow and Short (Short Version)",
u"currency/USD unit-width-short",
u"currency/USD unit-width-short",
NumberFormatter::with().unit(USD).unitWidth(UNUM_UNIT_WIDTH_SHORT),
Locale("en-CA"),
5.43,
u"US$5.43");
assertFormatSingle(
u"Currency 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::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%");
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");
}
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");
}
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 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 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" },
};
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";
FormattedNumber result = assertFormatSingle(
message,
u"measure-unit/length-meter per-measure-unit/duration-second unit-width-full-name",
u"unit/meter-per-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 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::assertFormatDescending(
const char16_t* umessage,
const char16_t* uskeleton,
const char16_t* conciseSkeleton,
const UnlocalizedNumberFormatter& f,
Locale locale,
...) {
va_list args;
va_start(args, locale);
UnicodeString message(TRUE, umessage, -1);
static double inputs[] = {87650, 8765, 876.5, 87.65, 8.765, 0.8765, 0.08765, 0.008765, 0};
const LocalizedNumberFormatter l1 = f.threshold(0).locale(locale); // no self-regulation
const LocalizedNumberFormatter l2 = f.threshold(1).locale(locale); // all self-regulation
IcuTestErrorCode status(*this, "assertFormatDescending");
status.setScope(message);
UnicodeString expecteds[10];
for (int16_t i = 0; i < 9; i++) {
char16_t caseNumber = u'0' + i;
double d = inputs[i];
UnicodeString expected = va_arg(args, const char16_t*);
expecteds[i] = expected;
UnicodeString actual1 = l1.formatDouble(d, status).toString(status);
assertSuccess(message + u": Unsafe Path: " + caseNumber, status);
assertEquals(message + u": Unsafe Path: " + caseNumber, expected, actual1);
UnicodeString actual2 = l2.formatDouble(d, status).toString(status);
assertSuccess(message + u": Safe Path: " + caseNumber, status);
assertEquals(message + u": Safe Path: " + caseNumber, expected, actual2);
}
if (uskeleton != nullptr) { // if null, skeleton is declared as undefined.
UnicodeString skeleton(TRUE, uskeleton, -1);
// Only compare normalized skeletons: the tests need not provide the normalized forms.
// Use the normalized form to construct the testing formatter to guarantee no loss of info.
UnicodeString normalized = NumberFormatter::forSkeleton(skeleton, status).toSkeleton(status);
assertEquals(message + ": Skeleton:", normalized, f.toSkeleton(status));
LocalizedNumberFormatter l3 = NumberFormatter::forSkeleton(normalized, status).locale(locale);
for (int32_t i = 0; i < 9; i++) {
double d = inputs[i];
UnicodeString actual3 = l3.formatDouble(d, status).toString(status);
assertEquals(message + ": Skeleton Path: '" + normalized + "': " + d, expecteds[i], actual3);
}
// Concise skeletons should have same output, and usually round-trip to the normalized skeleton.
// If the concise skeleton starts with '~', disable the round-trip check.
bool shouldRoundTrip = true;
if (conciseSkeleton[0] == u'~') {
conciseSkeleton++;
shouldRoundTrip = false;
}
LocalizedNumberFormatter l4 = NumberFormatter::forSkeleton(conciseSkeleton, status).locale(locale);
if (shouldRoundTrip) {
assertEquals(message + ": Concise Skeleton:", normalized, l4.toSkeleton(status));
}
for (int32_t i = 0; i < 9; i++) {
double d = inputs[i];
UnicodeString actual4 = l4.formatDouble(d, status).toString(status);
assertEquals(message + ": Concise Skeleton Path: '" + normalized + "': " + d, expecteds[i], actual4);
}
} else {
assertUndefinedSkeleton(f);
}
}
void NumberFormatterApiTest::assertFormatDescendingBig(
const char16_t* umessage,
const char16_t* uskeleton,
const char16_t* conciseSkeleton,
const UnlocalizedNumberFormatter& f,
Locale locale,
...) {
va_list args;
va_start(args, locale);
UnicodeString message(TRUE, umessage, -1);
static double inputs[] = {87650000, 8765000, 876500, 87650, 8765, 876.5, 87.65, 8.765, 0};
const LocalizedNumberFormatter l1 = f.threshold(0).locale(locale); // no self-regulation
const LocalizedNumberFormatter l2 = f.threshold(1).locale(locale); // all self-regulation
IcuTestErrorCode status(*this, "assertFormatDescendingBig");
status.setScope(message);
UnicodeString expecteds[10];
for (int16_t i = 0; i < 9; i++) {
char16_t caseNumber = u'0' + i;
double d = inputs[i];
UnicodeString expected = va_arg(args, const char16_t*);
expecteds[i] = expected;
UnicodeString actual1 = l1.formatDouble(d, status).toString(status);
assertSuccess(message + u": Unsafe Path: " + caseNumber, status);
assertEquals(message + u": Unsafe Path: " + caseNumber, expected, actual1);
UnicodeString actual2 = l2.formatDouble(d, status).toString(status);
assertSuccess(message + u": Safe Path: " + caseNumber, status);
assertEquals(message + u": Safe Path: " + caseNumber, expected, actual2);
}
if (uskeleton != nullptr) { // if null, skeleton is declared as undefined.
UnicodeString skeleton(TRUE, uskeleton, -1);
// Only compare normalized skeletons: the tests need not provide the normalized forms.
// Use the normalized form to construct the testing formatter to guarantee no loss of info.
UnicodeString normalized = NumberFormatter::forSkeleton(skeleton, status).toSkeleton(status);
assertEquals(message + ": Skeleton:", normalized, f.toSkeleton(status));
LocalizedNumberFormatter l3 = NumberFormatter::forSkeleton(normalized, status).locale(locale);
for (int32_t i = 0; i < 9; i++) {
double d = inputs[i];
UnicodeString actual3 = l3.formatDouble(d, status).toString(status);
assertEquals(message + ": Skeleton Path: '" + normalized + "': " + d, expecteds[i], actual3);
}
// Concise skeletons should have same output, and usually round-trip to the normalized skeleton.
// If the concise skeleton starts with '~', disable the round-trip check.
bool shouldRoundTrip = true;
if (conciseSkeleton[0] == u'~') {
conciseSkeleton++;
shouldRoundTrip = false;
}
LocalizedNumberFormatter l4 = NumberFormatter::forSkeleton(conciseSkeleton, status).locale(locale);
if (shouldRoundTrip) {
assertEquals(message + ": Concise Skeleton:", normalized, l4.toSkeleton(status));
}
for (int32_t i = 0; i < 9; i++) {
double d = inputs[i];
UnicodeString actual4 = l4.formatDouble(d, status).toString(status);
assertEquals(message + ": Concise Skeleton Path: '" + normalized + "': " + d, expecteds[i], actual4);
}
} else {
assertUndefinedSkeleton(f);
}
}
FormattedNumber
NumberFormatterApiTest::assertFormatSingle(
const char16_t* umessage,
const char16_t* uskeleton,
const char16_t* conciseSkeleton,
const UnlocalizedNumberFormatter& f,
Locale locale,
double input,
const UnicodeString& expected) {
UnicodeString message(TRUE, umessage, -1);
const LocalizedNumberFormatter l1 = f.threshold(0).locale(locale); // no self-regulation
const LocalizedNumberFormatter l2 = f.threshold(1).locale(locale); // all self-regulation
IcuTestErrorCode status(*this, "assertFormatSingle");
status.setScope(message);
FormattedNumber result1 = l1.formatDouble(input, status);
UnicodeString actual1 = result1.toString(status);
assertSuccess(message + u": Unsafe Path", status);
assertEquals(message + u": Unsafe Path", expected, actual1);
UnicodeString actual2 = l2.formatDouble(input, status).toString(status);
assertSuccess(message + u": Safe Path", status);
assertEquals(message + u": Safe Path", expected, actual2);
if (uskeleton != nullptr) { // if null, skeleton is declared as undefined.
UnicodeString skeleton(TRUE, uskeleton, -1);
// Only compare normalized skeletons: the tests need not provide the normalized forms.
// Use the normalized form to construct the testing formatter to ensure no loss of info.
UnicodeString normalized = NumberFormatter::forSkeleton(skeleton, status).toSkeleton(status);
assertEquals(message + ": Skeleton:", normalized, f.toSkeleton(status));
LocalizedNumberFormatter l3 = NumberFormatter::forSkeleton(normalized, status).locale(locale);
UnicodeString actual3 = l3.formatDouble(input, status).toString(status);
assertEquals(message + ": Skeleton Path: '" + normalized + "': " + input, expected, actual3);
// Concise skeletons should have same output, and usually round-trip to the normalized skeleton.
// If the concise skeleton starts with '~', disable the round-trip check.
bool shouldRoundTrip = true;
if (conciseSkeleton[0] == u'~') {
conciseSkeleton++;
shouldRoundTrip = false;
}
LocalizedNumberFormatter l4 = NumberFormatter::forSkeleton(conciseSkeleton, status).locale(locale);
if (shouldRoundTrip) {
assertEquals(message + ": Concise Skeleton:", normalized, l4.toSkeleton(status));
}
UnicodeString actual4 = l4.formatDouble(input, status).toString(status);
assertEquals(message + ": Concise Skeleton Path: '" + normalized + "': " + input, expected, actual4);
} else {
assertUndefinedSkeleton(f);
}
return result1;
}
void NumberFormatterApiTest::assertUndefinedSkeleton(const UnlocalizedNumberFormatter& f) {
UErrorCode status = U_ZERO_ERROR;
UnicodeString skeleton = f.toSkeleton(status);
assertEquals(
u"Expect toSkeleton to fail, but passed, producing: " + skeleton,
U_UNSUPPORTED_ERROR,
status);
}
void NumberFormatterApiTest::assertNumberFieldPositions(
const char16_t* message,
const FormattedNumber& formattedNumber,
const UFieldPosition* expectedFieldPositions,
int32_t length) {
IcuTestErrorCode status(*this, "assertNumberFieldPositions");
// Check FormattedValue functions
checkFormattedValue(
message,
static_cast<const FormattedValue&>(formattedNumber),
formattedNumber.toString(status),
UFIELD_CATEGORY_NUMBER,
expectedFieldPositions,
length);
}
#endif /* #if !UCONFIG_NO_FORMATTING */