namespace {
// Helper function for 2-dimensional switch statement
-constexpr int8_t identity2d(UNumberIdentityFallback a, UNumberRangeIdentityResult b) {
+constexpr int8_t identity2d(UNumberRangeIdentityFallback a, UNumberRangeIdentityResult b) {
return static_cast<int8_t>(a) | (static_cast<int8_t>(b) << 4);
}
formatterImpl1.preProcess(data.quantity1, micros1, status);
formatterImpl1.preProcess(data.quantity2, micros2, status);
} else {
- // If the formatters are different, an identity is not possible.
- // Always use formatRange().
formatterImpl1.preProcess(data.quantity1, micros1, status);
formatterImpl2.preProcess(data.quantity2, micros2, status);
+ }
+
+ // If any of the affixes are different, an identity is not possible
+ // and we must use formatRange().
+ // TODO: Write this as MicroProps operator==() ?
+ // TODO: Avoid the redundancy of these equality operations with the
+ // ones in formatRange?
+ if (!(*micros1.modInner == *micros2.modInner)
+ || !(*micros1.modMiddle == *micros2.modMiddle)
+ || !(*micros1.modOuter == *micros2.modOuter)) {
formatRange(data, micros1, micros2, status);
return;
}
UErrorCode& status) const {
if (U_FAILURE(status)) { return; }
if (fSameFormatters) {
- formatterImpl1.format(data.quantity1, data.string, status);
+ int32_t length = formatterImpl1.writeNumber(micros1, data.quantity1, data.string, 0, status);
+ formatterImpl1.writeAffixes(micros1, data.string, 0, length, status);
} else {
formatRange(data, micros1, micros2, status);
}
UErrorCode& status) const {
if (U_FAILURE(status)) { return; }
if (fSameFormatters) {
- int32_t length = formatterImpl1.format(data.quantity1, data.string, status);
+ int32_t length = formatterImpl1.writeNumber(micros1, data.quantity1, data.string, 0, status);
+ length += formatterImpl1.writeAffixes(micros1, data.string, 0, length, status);
fApproximatelyModifier.apply(data.string, 0, length, status);
} else {
formatRange(data, micros1, micros2, status);
collapseMiddle = false;
}
} else if (fCollapse == UNUM_RANGE_COLLAPSE_AUTO) {
- // Heuristic as of ICU 63: collapse only if the modifier is exactly one code point.
- if (mm->getCodePointCount() != 1) {
+ // Heuristic as of ICU 63: collapse only if the modifier is more than one code point.
+ if (mm->getCodePointCount() <= 1) {
collapseMiddle = false;
}
}
int32_t lengthInfix = 0;
int32_t length2 = 0;
int32_t lengthSuffix = 0;
+
+ // Use #define so that these are evaluated at the call site.
#define UPRV_INDEX_0 (lengthPrefix)
#define UPRV_INDEX_1 (lengthPrefix + length1)
#define UPRV_INDEX_2 (lengthPrefix + length1 + lengthInfix)
// SPACING HEURISTIC
// Add spacing unless all modifiers are collapsed.
+ // TODO: add API to control this?
{
bool repeatInner = !collapseInner && micros1.modInner->getCodePointCount() > 0;
bool repeatMiddle = !collapseMiddle && micros1.modMiddle->getCodePointCount() > 0;
TESTCASE_AUTO_BEGIN;
TESTCASE_AUTO(testSanity);
TESTCASE_AUTO(testBasic);
+ TESTCASE_AUTO(testCollapse);
+ TESTCASE_AUTO(testIdentity);
+ TESTCASE_AUTO(testDifferentFormatters);
TESTCASE_AUTO_END;
}
u"5,000 m – 5,000,000 km");
}
+void NumberRangeFormatterTest::testCollapse() {
+ assertFormatRange(
+ u"Default collapse on currency (default rounding)",
+ NumberRangeFormatter::with()
+ .numberFormatterBoth(NumberFormatter::with().unit(USD)),
+ Locale("en-us"),
+ u"$1.00 – $5.00",
+ u"~$5.00",
+ u"~$5.00",
+ u"$0.00 – $3.00",
+ u"~$0.00",
+ u"$3.00 – $3,000.00",
+ u"$3,000.00 – $5,000.00",
+ u"$4,999.00 – $5,001.00",
+ u"~$5,000.00",
+ u"$5,000.00 – $5,000,000.00");
+
+ assertFormatRange(
+ u"Default collapse on currency",
+ NumberRangeFormatter::with()
+ .numberFormatterBoth(NumberFormatter::with().unit(USD).precision(Precision::integer())),
+ Locale("en-us"),
+ u"$1 – $5",
+ u"~$5",
+ u"~$5",
+ u"$0 – $3",
+ u"~$0",
+ u"$3 – $3,000",
+ u"$3,000 – $5,000",
+ u"$4,999 – $5,001",
+ u"~$5,000",
+ u"$5,000 – $5,000,000");
+
+ assertFormatRange(
+ u"No collapse on currency",
+ NumberRangeFormatter::with()
+ .collapse(UNUM_RANGE_COLLAPSE_NONE)
+ .numberFormatterBoth(NumberFormatter::with().unit(USD).precision(Precision::integer())),
+ Locale("en-us"),
+ u"$1 – $5",
+ u"~$5",
+ u"~$5",
+ u"$0 – $3",
+ u"~$0",
+ u"$3 – $3,000",
+ u"$3,000 – $5,000",
+ u"$4,999 – $5,001",
+ u"~$5,000",
+ u"$5,000 – $5,000,000");
+
+ assertFormatRange(
+ u"Unit collapse on currency",
+ NumberRangeFormatter::with()
+ .collapse(UNUM_RANGE_COLLAPSE_UNIT)
+ .numberFormatterBoth(NumberFormatter::with().unit(USD).precision(Precision::integer())),
+ Locale("en-us"),
+ u"$1–5",
+ u"~$5",
+ u"~$5",
+ u"$0–3",
+ u"~$0",
+ u"$3–3,000",
+ u"$3,000–5,000",
+ u"$4,999–5,001",
+ u"~$5,000",
+ u"$5,000–5,000,000");
+
+ assertFormatRange(
+ u"All collapse on currency",
+ NumberRangeFormatter::with()
+ .collapse(UNUM_RANGE_COLLAPSE_ALL)
+ .numberFormatterBoth(NumberFormatter::with().unit(USD).precision(Precision::integer())),
+ Locale("en-us"),
+ u"$1–5",
+ u"~$5",
+ u"~$5",
+ u"$0–3",
+ u"~$0",
+ u"$3–3,000",
+ u"$3,000–5,000",
+ u"$4,999–5,001",
+ u"~$5,000",
+ u"$5,000–5,000,000");
+
+ assertFormatRange(
+ u"Default collapse on currency ISO code",
+ NumberRangeFormatter::with()
+ .numberFormatterBoth(NumberFormatter::with()
+ .unit(GBP)
+ .unitWidth(UNUM_UNIT_WIDTH_ISO_CODE)
+ .precision(Precision::integer())),
+ Locale("en-us"),
+ u"GBP 1–5",
+ u"~GBP 5", // TODO: Fix this at some point
+ u"~GBP 5",
+ u"GBP 0–3",
+ u"~GBP 0",
+ u"GBP 3–3,000",
+ u"GBP 3,000–5,000",
+ u"GBP 4,999–5,001",
+ u"~GBP 5,000",
+ u"GBP 5,000–5,000,000");
+
+ assertFormatRange(
+ u"No collapse on currency ISO code",
+ NumberRangeFormatter::with()
+ .collapse(UNUM_RANGE_COLLAPSE_NONE)
+ .numberFormatterBoth(NumberFormatter::with()
+ .unit(GBP)
+ .unitWidth(UNUM_UNIT_WIDTH_ISO_CODE)
+ .precision(Precision::integer())),
+ Locale("en-us"),
+ u"GBP 1 – GBP 5",
+ u"~GBP 5", // TODO: Fix this at some point
+ u"~GBP 5",
+ u"GBP 0 – GBP 3",
+ u"~GBP 0",
+ u"GBP 3 – GBP 3,000",
+ u"GBP 3,000 – GBP 5,000",
+ u"GBP 4,999 – GBP 5,001",
+ u"~GBP 5,000",
+ u"GBP 5,000 – GBP 5,000,000");
+
+ assertFormatRange(
+ u"Unit collapse on currency ISO code",
+ NumberRangeFormatter::with()
+ .collapse(UNUM_RANGE_COLLAPSE_UNIT)
+ .numberFormatterBoth(NumberFormatter::with()
+ .unit(GBP)
+ .unitWidth(UNUM_UNIT_WIDTH_ISO_CODE)
+ .precision(Precision::integer())),
+ Locale("en-us"),
+ u"GBP 1–5",
+ u"~GBP 5", // TODO: Fix this at some point
+ u"~GBP 5",
+ u"GBP 0–3",
+ u"~GBP 0",
+ u"GBP 3–3,000",
+ u"GBP 3,000–5,000",
+ u"GBP 4,999–5,001",
+ u"~GBP 5,000",
+ u"GBP 5,000–5,000,000");
+
+ assertFormatRange(
+ u"All collapse on currency ISO code",
+ NumberRangeFormatter::with()
+ .collapse(UNUM_RANGE_COLLAPSE_ALL)
+ .numberFormatterBoth(NumberFormatter::with()
+ .unit(GBP)
+ .unitWidth(UNUM_UNIT_WIDTH_ISO_CODE)
+ .precision(Precision::integer())),
+ Locale("en-us"),
+ u"GBP 1–5",
+ u"~GBP 5", // TODO: Fix this at some point
+ u"~GBP 5",
+ u"GBP 0–3",
+ u"~GBP 0",
+ u"GBP 3–3,000",
+ u"GBP 3,000–5,000",
+ u"GBP 4,999–5,001",
+ u"~GBP 5,000",
+ u"GBP 5,000–5,000,000");
+
+ // Default collapse on measurement unit is in testBasic()
+
+ assertFormatRange(
+ u"No collapse on measurement unit",
+ NumberRangeFormatter::with()
+ .collapse(UNUM_RANGE_COLLAPSE_NONE)
+ .numberFormatterBoth(NumberFormatter::with().unit(METER)),
+ Locale("en-us"),
+ u"1 m – 5 m",
+ u"~5 m",
+ u"~5 m",
+ u"0 m – 3 m",
+ u"~0 m",
+ u"3 m – 3,000 m",
+ u"3,000 m – 5,000 m",
+ u"4,999 m – 5,001 m",
+ u"~5,000 m",
+ u"5,000 m – 5,000,000 m");
+
+ assertFormatRange(
+ u"Unit collapse on measurement unit",
+ NumberRangeFormatter::with()
+ .collapse(UNUM_RANGE_COLLAPSE_UNIT)
+ .numberFormatterBoth(NumberFormatter::with().unit(METER)),
+ Locale("en-us"),
+ u"1–5 m",
+ u"~5 m",
+ u"~5 m",
+ u"0–3 m",
+ u"~0 m",
+ u"3–3,000 m",
+ u"3,000–5,000 m",
+ u"4,999–5,001 m",
+ u"~5,000 m",
+ u"5,000–5,000,000 m");
+
+ assertFormatRange(
+ u"All collapse on measurement unit",
+ NumberRangeFormatter::with()
+ .collapse(UNUM_RANGE_COLLAPSE_ALL)
+ .numberFormatterBoth(NumberFormatter::with().unit(METER)),
+ Locale("en-us"),
+ u"1–5 m",
+ u"~5 m",
+ u"~5 m",
+ u"0–3 m",
+ u"~0 m",
+ u"3–3,000 m",
+ u"3,000–5,000 m",
+ u"4,999–5,001 m",
+ u"~5,000 m",
+ u"5,000–5,000,000 m");
+
+ assertFormatRange(
+ u"Default collapse on measurement unit with compact-short notation",
+ NumberRangeFormatter::with()
+ .numberFormatterBoth(NumberFormatter::with().notation(Notation::compactShort()).unit(METER)),
+ Locale("en-us"),
+ u"1–5 m",
+ u"~5 m",
+ u"~5 m",
+ u"0–3 m",
+ u"~0 m",
+ u"3–3K m",
+ u"3K – 5K m",
+ u"~5K m",
+ u"~5K m",
+ u"5K – 5M m");
+
+ assertFormatRange(
+ u"No collapse on measurement unit with compact-short notation",
+ NumberRangeFormatter::with()
+ .collapse(UNUM_RANGE_COLLAPSE_NONE)
+ .numberFormatterBoth(NumberFormatter::with().notation(Notation::compactShort()).unit(METER)),
+ Locale("en-us"),
+ u"1 m – 5 m",
+ u"~5 m",
+ u"~5 m",
+ u"0 m – 3 m",
+ u"~0 m",
+ u"3 m – 3K m",
+ u"3K m – 5K m",
+ u"~5K m",
+ u"~5K m",
+ u"5K m – 5M m");
+
+ assertFormatRange(
+ u"Unit collapse on measurement unit with compact-short notation",
+ NumberRangeFormatter::with()
+ .collapse(UNUM_RANGE_COLLAPSE_UNIT)
+ .numberFormatterBoth(NumberFormatter::with().notation(Notation::compactShort()).unit(METER)),
+ Locale("en-us"),
+ u"1–5 m",
+ u"~5 m",
+ u"~5 m",
+ u"0–3 m",
+ u"~0 m",
+ u"3–3K m",
+ u"3K – 5K m",
+ u"~5K m",
+ u"~5K m",
+ u"5K – 5M m");
+
+ assertFormatRange(
+ u"All collapse on measurement unit with compact-short notation",
+ NumberRangeFormatter::with()
+ .collapse(UNUM_RANGE_COLLAPSE_ALL)
+ .numberFormatterBoth(NumberFormatter::with().notation(Notation::compactShort()).unit(METER)),
+ Locale("en-us"),
+ u"1–5 m",
+ u"~5 m",
+ u"~5 m",
+ u"0–3 m",
+ u"~0 m",
+ u"3–3K m",
+ u"3–5K m", // this one is the key use case for ALL
+ u"~5K m",
+ u"~5K m",
+ u"5K – 5M m");
+
+ // TODO: Test compact currency?
+ // The code is not smart enough to differentiate the notation from the unit.
+}
+
+void NumberRangeFormatterTest::testIdentity() {
+ assertFormatRange(
+ u"Identity fallback Range",
+ NumberRangeFormatter::with().identityFallback(UNUM_IDENTITY_FALLBACK_RANGE),
+ Locale("en-us"),
+ u"1–5",
+ u"5–5",
+ u"5–5",
+ u"0–3",
+ u"0–0",
+ u"3–3,000",
+ u"3,000–5,000",
+ u"4,999–5,001",
+ u"5,000–5,000",
+ u"5,000–5,000,000");
+
+ assertFormatRange(
+ u"Identity fallback Approximately or Single Value",
+ NumberRangeFormatter::with().identityFallback(UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE),
+ Locale("en-us"),
+ u"1–5",
+ u"~5",
+ u"5",
+ u"0–3",
+ u"0",
+ u"3–3,000",
+ u"3,000–5,000",
+ u"4,999–5,001",
+ u"5,000",
+ u"5,000–5,000,000");
+
+ assertFormatRange(
+ u"Identity fallback Single Value",
+ NumberRangeFormatter::with().identityFallback(UNUM_IDENTITY_FALLBACK_SINGLE_VALUE),
+ Locale("en-us"),
+ u"1–5",
+ u"5",
+ u"5",
+ u"0–3",
+ u"0",
+ u"3–3,000",
+ u"3,000–5,000",
+ u"4,999–5,001",
+ u"5,000",
+ u"5,000–5,000,000");
+
+ assertFormatRange(
+ u"Identity fallback Approximately or Single Value with compact notation",
+ NumberRangeFormatter::with()
+ .identityFallback(UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE)
+ .numberFormatterBoth(NumberFormatter::with().notation(Notation::compactShort())),
+ Locale("en-us"),
+ u"1–5",
+ u"~5",
+ u"5",
+ u"0–3",
+ u"0",
+ u"3–3K",
+ u"3K – 5K",
+ u"~5K",
+ u"5K",
+ u"5K – 5M");
+}
+
+void NumberRangeFormatterTest::testDifferentFormatters() {
+ assertFormatRange(
+ u"Different rounding rules",
+ NumberRangeFormatter::with()
+ .numberFormatterFirst(NumberFormatter::with().precision(Precision::integer()))
+ .numberFormatterSecond(NumberFormatter::with().precision(Precision::fixedDigits(2))),
+ Locale("en-us"),
+ u"1–5.0",
+ u"5–5.0",
+ u"5–5.0",
+ u"0–3.0",
+ u"0–0.0",
+ u"3–3,000",
+ u"3,000–5,000",
+ u"4,999–5,000",
+ u"5,000–5,000", // TODO: Should this one be ~5,000?
+ u"5,000–5,000,000");
+}
+
void NumberRangeFormatterTest::assertFormatRange(
const char16_t* message,
const UnlocalizedNumberRangeFormatter& f,