ICU-11276 Adding test cases and more API coverage.

This commit is contained in:
Shane Carr 2018-09-05 23:04:32 -07:00
parent 7155e1fbcf
commit dfd13867b2
No known key found for this signature in database
GPG Key ID: FCED3B24AAB18B5C
9 changed files with 441 additions and 16 deletions

View File

@ -1155,7 +1155,7 @@ const char16_t* DecimalQuantity::checkHealth() const {
bool DecimalQuantity::operator==(const DecimalQuantity& other) const {
// FIXME: Make a faster implementation.
return toString() == other.toString();
return toScientificString() == other.toScientificString();
}
UnicodeString DecimalQuantity::toString() const {

View File

@ -280,10 +280,7 @@ bool ConstantMultiFieldModifier::isStrong() const {
}
bool ConstantMultiFieldModifier::containsField(UNumberFormatFields field) const {
(void)field;
// This method is not currently used.
U_ASSERT(false);
return false;
return fPrefix.containsField(field) || fSuffix.containsField(field);
}
bool ConstantMultiFieldModifier::operator==(const Modifier& other) const {

View File

@ -488,4 +488,13 @@ void NumberStringBuilder::getAllFieldPositions(FieldPositionIteratorHandler& fpi
}
}
bool NumberStringBuilder::containsField(Field field) const {
for (int32_t i = 0; i < fLength; i++) {
if (field == fieldAt(i)) {
return true;
}
}
return false;
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View File

@ -106,6 +106,8 @@ class U_I18N_API NumberStringBuilder : public UMemory {
void getAllFieldPositions(FieldPositionIteratorHandler& fpih, UErrorCode& status) const;
bool containsField(Field field) const;
private:
bool fUsingHeap = false;
ValueOrHeapArray<char16_t> fChars;

View File

@ -114,6 +114,34 @@ Derived NumberRangeFormatterSettings<Derived>::numberFormatterSecond(Unlocalized
return move;
}
template<typename Derived>
Derived NumberRangeFormatterSettings<Derived>::collapse(UNumberRangeCollapse collapse) const& {
Derived copy(*this);
copy.fMacros.collapse = collapse;
return copy;
}
template<typename Derived>
Derived NumberRangeFormatterSettings<Derived>::collapse(UNumberRangeCollapse collapse) && {
Derived move(std::move(*this));
move.fMacros.collapse = collapse;
return move;
}
template<typename Derived>
Derived NumberRangeFormatterSettings<Derived>::identityFallback(UNumberRangeIdentityFallback identityFallback) const& {
Derived copy(*this);
copy.fMacros.identityFallback = identityFallback;
return copy;
}
template<typename Derived>
Derived NumberRangeFormatterSettings<Derived>::identityFallback(UNumberRangeIdentityFallback identityFallback) && {
Derived move(std::move(*this));
move.fMacros.identityFallback = identityFallback;
return move;
}
// Declare all classes that implement NumberRangeFormatterSettings
// See https://stackoverflow.com/a/495056/1407170
template

View File

@ -21,7 +21,7 @@ using namespace icu::number::impl;
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);
}
@ -111,10 +111,18 @@ void NumberRangeFormatterImpl::format(UFormattedNumberRangeData& data, bool equa
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;
}
@ -174,7 +182,8 @@ void NumberRangeFormatterImpl::formatSingleValue(UFormattedNumberRangeData& data
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);
}
@ -186,7 +195,8 @@ void NumberRangeFormatterImpl::formatApproximately (UFormattedNumberRangeData& d
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);
@ -242,8 +252,8 @@ void NumberRangeFormatterImpl::formatRange(UFormattedNumberRangeData& data,
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;
}
}
@ -273,6 +283,8 @@ void NumberRangeFormatterImpl::formatRange(UFormattedNumberRangeData& data,
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)
@ -291,6 +303,7 @@ void NumberRangeFormatterImpl::formatRange(UFormattedNumberRangeData& data,
// 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;

View File

@ -116,7 +116,7 @@ typedef enum UNumberRangeIdentityFallback {
* @draft ICU 63
*/
UNUM_IDENTITY_FALLBACK_RANGE
} UNumberIdentityFallback;
} UNumberRangeIdentityFallback;
/**
* Used in the result class FormattedNumberRange to indicate to the user whether the numbers formatted in the range
@ -199,7 +199,7 @@ struct U_I18N_API RangeMacroProps : public UMemory {
UNumberRangeCollapse collapse = UNUM_RANGE_COLLAPSE_AUTO;
/** @internal */
UNumberIdentityFallback identityFallback = UNUM_IDENTITY_FALLBACK_APPROXIMATELY;
UNumberRangeIdentityFallback identityFallback = UNUM_IDENTITY_FALLBACK_APPROXIMATELY;
/** @internal */
Locale locale;
@ -414,7 +414,7 @@ class U_I18N_API NumberRangeFormatterSettings {
* @return The fluent chain.
* @draft ICU 63
*/
Derived identityFallback(UNumberIdentityFallback identityFallback) const &;
Derived identityFallback(UNumberRangeIdentityFallback identityFallback) const &;
/**
* Overload of identityFallback() for use on an rvalue reference.
@ -425,7 +425,7 @@ class U_I18N_API NumberRangeFormatterSettings {
* @see #identityFallback
* @draft ICU 63
*/
Derived identityFallback(UNumberIdentityFallback identityFallback) &&;
Derived identityFallback(UNumberRangeIdentityFallback identityFallback) &&;
/**
* Sets the UErrorCode if an error occurred in the fluent chain.

View File

@ -253,6 +253,9 @@ class NumberRangeFormatterTest : public IntlTest {
void testSanity();
void testBasic();
void testCollapse();
void testIdentity();
void testDifferentFormatters();
void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0);

View File

@ -44,6 +44,9 @@ void NumberRangeFormatterTest::runIndexedTest(int32_t index, UBool exec, const c
TESTCASE_AUTO_BEGIN;
TESTCASE_AUTO(testSanity);
TESTCASE_AUTO(testBasic);
TESTCASE_AUTO(testCollapse);
TESTCASE_AUTO(testIdentity);
TESTCASE_AUTO(testDifferentFormatters);
TESTCASE_AUTO_END;
}
@ -106,6 +109,376 @@ void NumberRangeFormatterTest::testBasic() {
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"$15",
u"~$5",
u"~$5",
u"$03",
u"~$0",
u"$33,000",
u"$3,0005,000",
u"$4,9995,001",
u"~$5,000",
u"$5,0005,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"$15",
u"~$5",
u"~$5",
u"$03",
u"~$0",
u"$33,000",
u"$3,0005,000",
u"$4,9995,001",
u"~$5,000",
u"$5,0005,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 15",
u"~GBP 5", // TODO: Fix this at some point
u"~GBP 5",
u"GBP 03",
u"~GBP 0",
u"GBP 33,000",
u"GBP 3,0005,000",
u"GBP 4,9995,001",
u"~GBP 5,000",
u"GBP 5,0005,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 15",
u"~GBP 5", // TODO: Fix this at some point
u"~GBP 5",
u"GBP 03",
u"~GBP 0",
u"GBP 33,000",
u"GBP 3,0005,000",
u"GBP 4,9995,001",
u"~GBP 5,000",
u"GBP 5,0005,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 15",
u"~GBP 5", // TODO: Fix this at some point
u"~GBP 5",
u"GBP 03",
u"~GBP 0",
u"GBP 33,000",
u"GBP 3,0005,000",
u"GBP 4,9995,001",
u"~GBP 5,000",
u"GBP 5,0005,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"15 m",
u"~5 m",
u"~5 m",
u"03 m",
u"~0 m",
u"33,000 m",
u"3,0005,000 m",
u"4,9995,001 m",
u"~5,000 m",
u"5,0005,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"15 m",
u"~5 m",
u"~5 m",
u"03 m",
u"~0 m",
u"33,000 m",
u"3,0005,000 m",
u"4,9995,001 m",
u"~5,000 m",
u"5,0005,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"15 m",
u"~5 m",
u"~5 m",
u"03 m",
u"~0 m",
u"33K 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"15 m",
u"~5 m",
u"~5 m",
u"03 m",
u"~0 m",
u"33K 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"15 m",
u"~5 m",
u"~5 m",
u"03 m",
u"~0 m",
u"33K m",
u"35K 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"15",
u"55",
u"55",
u"03",
u"00",
u"33,000",
u"3,0005,000",
u"4,9995,001",
u"5,0005,000",
u"5,0005,000,000");
assertFormatRange(
u"Identity fallback Approximately or Single Value",
NumberRangeFormatter::with().identityFallback(UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE),
Locale("en-us"),
u"15",
u"~5",
u"5",
u"03",
u"0",
u"33,000",
u"3,0005,000",
u"4,9995,001",
u"5,000",
u"5,0005,000,000");
assertFormatRange(
u"Identity fallback Single Value",
NumberRangeFormatter::with().identityFallback(UNUM_IDENTITY_FALLBACK_SINGLE_VALUE),
Locale("en-us"),
u"15",
u"5",
u"5",
u"03",
u"0",
u"33,000",
u"3,0005,000",
u"4,9995,001",
u"5,000",
u"5,0005,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"15",
u"~5",
u"5",
u"03",
u"0",
u"33K",
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"15.0",
u"55.0",
u"55.0",
u"03.0",
u"00.0",
u"33,000",
u"3,0005,000",
u"4,9995,000",
u"5,0005,000", // TODO: Should this one be ~5,000?
u"5,0005,000,000");
}
void NumberRangeFormatterTest::assertFormatRange(
const char16_t* message,
const UnlocalizedNumberRangeFormatter& f,