diff --git a/icu4c/source/i18n/i18n.vcxproj b/icu4c/source/i18n/i18n.vcxproj
index 25b62d36cdb..fe1e71d905c 100644
--- a/icu4c/source/i18n/i18n.vcxproj
+++ b/icu4c/source/i18n/i18n.vcxproj
@@ -554,7 +554,7 @@
-
+
diff --git a/icu4c/source/i18n/i18n.vcxproj.filters b/icu4c/source/i18n/i18n.vcxproj.filters
index ca431e607fc..c8f98451427 100644
--- a/icu4c/source/i18n/i18n.vcxproj.filters
+++ b/icu4c/source/i18n/i18n.vcxproj.filters
@@ -920,7 +920,7 @@
formatting
-
+
formatting
diff --git a/icu4c/source/i18n/i18n_uwp.vcxproj b/icu4c/source/i18n/i18n_uwp.vcxproj
index 3b0b18231ae..bdc42e64a66 100644
--- a/icu4c/source/i18n/i18n_uwp.vcxproj
+++ b/icu4c/source/i18n/i18n_uwp.vcxproj
@@ -659,7 +659,7 @@
-
+
diff --git a/icu4c/source/i18n/number_modifiers.h b/icu4c/source/i18n/number_modifiers.h
index a553100cd92..c1feafa8ecc 100644
--- a/icu4c/source/i18n/number_modifiers.h
+++ b/icu4c/source/i18n/number_modifiers.h
@@ -37,6 +37,10 @@ class U_I18N_API ConstantAffixModifier : public Modifier, public UObject {
bool isStrong() const U_OVERRIDE;
+ bool containsField(UNumberFormatFields field) const U_OVERRIDE;
+
+ bool operator==(const Modifier& other) const U_OVERRIDE;
+
private:
UnicodeString fPrefix;
UnicodeString fSuffix;
@@ -64,6 +68,10 @@ class U_I18N_API SimpleModifier : public Modifier, public UMemory {
bool isStrong() const U_OVERRIDE;
+ bool containsField(UNumberFormatFields field) const U_OVERRIDE;
+
+ bool operator==(const Modifier& other) const U_OVERRIDE;
+
/**
* TODO: This belongs in SimpleFormatterImpl. The only reason I haven't moved it there yet is because
* DoubleSidedStringBuilder is an internal class and SimpleFormatterImpl feels like it should not depend on it.
@@ -122,6 +130,10 @@ class U_I18N_API ConstantMultiFieldModifier : public Modifier, public UMemory {
bool isStrong() const U_OVERRIDE;
+ bool containsField(UNumberFormatFields field) const U_OVERRIDE;
+
+ bool operator==(const Modifier& other) const U_OVERRIDE;
+
protected:
// NOTE: In Java, these are stored as array pointers. In C++, the NumberStringBuilder is stored by
// value and is treated internally as immutable.
@@ -206,6 +218,16 @@ class U_I18N_API EmptyModifier : public Modifier, public UMemory {
return fStrong;
}
+ bool containsField(UNumberFormatFields field) const U_OVERRIDE {
+ (void)field;
+ return false;
+ }
+
+ bool operator==(const Modifier& other) const U_OVERRIDE {
+ UErrorCode status = U_ZERO_ERROR;
+ return other.getCodePointCount(status) == 0;
+ }
+
private:
bool fStrong;
};
diff --git a/icu4c/source/i18n/number_patternmodifier.h b/icu4c/source/i18n/number_patternmodifier.h
index 2854ca056a1..eb1457d92b4 100644
--- a/icu4c/source/i18n/number_patternmodifier.h
+++ b/icu4c/source/i18n/number_patternmodifier.h
@@ -184,6 +184,10 @@ class U_I18N_API MutablePatternModifier
bool isStrong() const U_OVERRIDE;
+ bool containsField(UNumberFormatFields field) const U_OVERRIDE;
+
+ bool operator==(const Modifier& other) const U_OVERRIDE;
+
/**
* Returns the string that substitutes a given symbol type in a pattern.
*/
diff --git a/icu4c/source/i18n/number_scientific.h b/icu4c/source/i18n/number_scientific.h
index 974ab3adb61..4c7cdf36ee8 100644
--- a/icu4c/source/i18n/number_scientific.h
+++ b/icu4c/source/i18n/number_scientific.h
@@ -30,6 +30,10 @@ class U_I18N_API ScientificModifier : public UMemory, public Modifier {
bool isStrong() const U_OVERRIDE;
+ bool containsField(UNumberFormatFields field) const U_OVERRIDE;
+
+ bool operator==(const Modifier& other) const U_OVERRIDE;
+
private:
int32_t fExponent;
const ScientificHandler *fHandler;
diff --git a/icu4c/source/i18n/number_types.h b/icu4c/source/i18n/number_types.h
index 57da72f8aa0..db13a231334 100644
--- a/icu4c/source/i18n/number_types.h
+++ b/icu4c/source/i18n/number_types.h
@@ -127,6 +127,7 @@ class U_I18N_API AffixPatternProvider {
virtual bool hasBody() const = 0;
};
+
/**
* A Modifier is an object that can be passed through the formatting pipeline until it is finally applied to the string
* builder. A Modifier usually contains a prefix and a suffix that are applied, but it could contain something else,
@@ -177,6 +178,16 @@ class U_I18N_API Modifier {
* @return Whether the modifier is strong.
*/
virtual bool isStrong() const = 0;
+
+ /**
+ * Whether the modifier contains at least one occurrence of the given field.
+ */
+ virtual bool containsField(UNumberFormatFields field) const = 0;
+
+ /**
+ * Returns whether the affixes owned by this modifier are equal to the ones owned by the given modifier.
+ */
+ virtual bool operator==(const Modifier& other) const = 0;
};
/**
diff --git a/icu4c/source/i18n/numrange_fluent.cpp b/icu4c/source/i18n/numrange_fluent.cpp
index 5dcd38554c7..6a2704e5fdf 100644
--- a/icu4c/source/i18n/numrange_fluent.cpp
+++ b/icu4c/source/i18n/numrange_fluent.cpp
@@ -9,7 +9,7 @@
// Helpful in toString methods and elsewhere.
#define UNISTR_FROM_STRING_EXPLICIT
-#include "numrange_types.h"
+#include "numrange_impl.h"
#include "util.h"
#include "number_utypes.h"
@@ -241,7 +241,9 @@ FormattedNumberRange LocalizedNumberRangeFormatter::formatFormattableRange(
void LocalizedNumberRangeFormatter::formatImpl(
UFormattedNumberRangeData* results, UErrorCode& status) const {
- // TODO: This is a placeholder implementation.
+
+ MicroProps microsFirst;
+ MicroProps microsSecond;
UFormattedNumberData r1;
r1.quantity = results->quantity1;
diff --git a/icu4c/source/i18n/numrange_impl.cpp b/icu4c/source/i18n/numrange_impl.cpp
index 1b3a6857718..be109e48175 100644
--- a/icu4c/source/i18n/numrange_impl.cpp
+++ b/icu4c/source/i18n/numrange_impl.cpp
@@ -9,14 +9,220 @@
// Helpful in toString methods and elsewhere.
#define UNISTR_FROM_STRING_EXPLICIT
-#include "numrange_types.h"
+#include "numrange_impl.h"
using namespace icu;
using namespace icu::number;
using namespace icu::number::impl;
+namespace {
+
+// Helper function for 2-dimensional switch statement
+constexpr int8_t identity2d(UNumberIdentityFallback a, UNumberRangeIdentityResult b) {
+ return static_cast(a) | (static_cast(b) << 4);
+}
+
+} // namespace
+
+void NumberRangeFormatterImpl::format(UFormattedNumberRangeData& data, bool equalBeforeRounding, UErrorCode& status) const {
+ if (U_FAILURE(status)) {
+ return;
+ }
+
+ // Identity case 1: equal before rounding
+ if (equalBeforeRounding) {
+ }
+
+ MicroProps micros1;
+ MicroProps micros2;
+ formatterImpl1.preProcess(data.quantity1, micros1, status);
+ formatterImpl2.preProcess(data.quantity2, micros2, status);
+ if (U_FAILURE(status)) {
+ return;
+ }
+
+ // Check for identity
+ if (equalBeforeRounding) {
+ data.identityResult = UNUM_IDENTITY_RESULT_EQUAL_BEFORE_ROUNDING;
+ } else if (data.quantity1 == data.quantity2) {
+ data.identityResult = UNUM_IDENTITY_RESULT_EQUAL_AFTER_ROUNDING;
+ } else {
+ data.identityResult = UNUM_IDENTITY_RESULT_NOT_EQUAL;
+ }
+
+ switch (identity2d(fIdentityFallback, data.identityResult)) {
+ case identity2d(UNUM_IDENTITY_FALLBACK_RANGE,
+ UNUM_IDENTITY_RESULT_NOT_EQUAL):
+ case identity2d(UNUM_IDENTITY_FALLBACK_RANGE,
+ UNUM_IDENTITY_RESULT_EQUAL_AFTER_ROUNDING):
+ case identity2d(UNUM_IDENTITY_FALLBACK_RANGE,
+ UNUM_IDENTITY_RESULT_EQUAL_BEFORE_ROUNDING):
+ case identity2d(UNUM_IDENTITY_FALLBACK_APPROXIMATELY,
+ UNUM_IDENTITY_RESULT_NOT_EQUAL):
+ case identity2d(UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE,
+ UNUM_IDENTITY_RESULT_NOT_EQUAL):
+ case identity2d(UNUM_IDENTITY_FALLBACK_SINGLE_VALUE,
+ UNUM_IDENTITY_RESULT_NOT_EQUAL):
+ formatRange(data, micros1, micros2, status);
+ break;
+
+ case identity2d(UNUM_IDENTITY_FALLBACK_APPROXIMATELY,
+ UNUM_IDENTITY_RESULT_EQUAL_AFTER_ROUNDING):
+ case identity2d(UNUM_IDENTITY_FALLBACK_APPROXIMATELY,
+ UNUM_IDENTITY_RESULT_EQUAL_BEFORE_ROUNDING):
+ case identity2d(UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE,
+ UNUM_IDENTITY_RESULT_EQUAL_AFTER_ROUNDING):
+ formatApproximately(data, micros1, micros2, status);
+ break;
+
+ case identity2d(UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE,
+ UNUM_IDENTITY_RESULT_EQUAL_BEFORE_ROUNDING):
+ case identity2d(UNUM_IDENTITY_FALLBACK_SINGLE_VALUE,
+ UNUM_IDENTITY_RESULT_EQUAL_AFTER_ROUNDING):
+ case identity2d(UNUM_IDENTITY_FALLBACK_SINGLE_VALUE,
+ UNUM_IDENTITY_RESULT_EQUAL_BEFORE_ROUNDING):
+ formatSingleValue(data, micros1, micros2, status);
+ break;
+
+ default:
+ U_ASSERT(false);
+ break;
+ }
+}
+void NumberRangeFormatterImpl::formatSingleValue(UFormattedNumberRangeData& data,
+ MicroProps& micros1, MicroProps& micros2,
+ UErrorCode& status) const {
+ if (fSameFormatters) {
+ formatterImpl1.format(data.quantity1, data.string, status);
+ } else {
+ formatRange(data, micros1, micros2, status);
+ }
+}
+
+
+void NumberRangeFormatterImpl::formatApproximately (UFormattedNumberRangeData& data,
+ MicroProps& micros1, MicroProps& micros2,
+ UErrorCode& status) const {
+ if (fSameFormatters) {
+ // FIXME
+ formatterImpl1.format(data.quantity1, data.string, status);
+ data.string.insertCodePoint(0, u'~', UNUM_FIELD_COUNT, status);
+ } else {
+ formatRange(data, micros1, micros2, status);
+ }
+}
+
+
+void NumberRangeFormatterImpl::formatRange(UFormattedNumberRangeData& data,
+ MicroProps& micros1, MicroProps& micros2,
+ UErrorCode& status) const {
+
+ // modInner is always notation (scientific); collapsable in ALL.
+ // modOuter is always units; collapsable in ALL, AUTO, and UNIT.
+ // modMiddle could be either; collapsable in ALL and sometimes AUTO and UNIT.
+ // Never collapse an outer mod but not an inner mod.
+ bool collapseOuter, collapseMiddle, collapseInner;
+ switch (fCollapse) {
+ case UNUM_RANGE_COLLAPSE_ALL:
+ case UNUM_RANGE_COLLAPSE_AUTO:
+ case UNUM_RANGE_COLLAPSE_UNIT:
+ {
+ // OUTER MODIFIER
+ collapseOuter = *micros1.modOuter == *micros2.modOuter;
+
+ if (!collapseOuter) {
+ // Never collapse inner mods if outer mods are not collapsable
+ collapseMiddle = false;
+ collapseInner = false;
+ break;
+ }
+
+ // MIDDLE MODIFIER
+ collapseMiddle = *micros1.modMiddle == *micros2.modMiddle;
+
+ if (!collapseMiddle) {
+ // Never collapse inner mods if outer mods are not collapsable
+ collapseInner = false;
+ break;
+ }
+
+ // MIDDLE MODIFIER HEURISTICS
+ // (could disable collapsing of the middle modifier)
+ // The modifiers are equal by this point, so we can look at just one of them.
+ const Modifier* mm = micros1.modMiddle;
+ if (mm == nullptr) {
+ // pass
+ } else if (fCollapse == UNUM_RANGE_COLLAPSE_UNIT) {
+ // Only collapse if the modifier is a unit.
+ // TODO: Handle case where the modifier has both notation and unit (compact currency)?
+ if (!mm->containsField(UNUM_CURRENCY_FIELD) && !mm->containsField(UNUM_PERCENT_FIELD)) {
+ 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(status) != 1) {
+ collapseMiddle = false;
+ }
+ }
+
+ if (!collapseMiddle || fCollapse != UNUM_RANGE_COLLAPSE_ALL) {
+ collapseInner = false;
+ break;
+ }
+
+ // INNER MODIFIER
+ collapseInner = *micros1.modInner == *micros2.modInner;
+
+ // All done checking for collapsability.
+ break;
+ }
+
+ default:
+ collapseOuter = false;
+ collapseMiddle = false;
+ collapseInner = false;
+ break;
+ }
+
+ NumberStringBuilder& string = data.string;
+ int32_t length1 = 0;
+ int32_t lengthShared = 0;
+ int32_t length2 = 0;
+ #define UPRV_INDEX_0 0
+ #define UPRV_INDEX_1 length1
+ #define UPRV_INDEX_2 length1 + lengthShared
+ #define UPRV_INDEX_3 length1 + lengthShared + length2
+
+ // TODO: Use localized pattern
+ lengthShared += string.insert(UPRV_INDEX_0, u" --- ", UNUM_FIELD_COUNT, status);
+ length1 += NumberFormatterImpl::writeNumber(micros1, data.quantity1, string, UPRV_INDEX_0, status);
+ length2 += NumberFormatterImpl::writeNumber(micros2, data.quantity2, string, UPRV_INDEX_0, status);
+
+ // TODO: Support padding?
+
+ if (collapseInner) {
+ lengthShared += micros1.modInner->apply(string, UPRV_INDEX_0, UPRV_INDEX_3, status);
+ } else {
+ length1 += micros1.modInner->apply(string, UPRV_INDEX_0, UPRV_INDEX_1, status);
+ length2 += micros1.modInner->apply(string, UPRV_INDEX_2, UPRV_INDEX_3, status);
+ }
+
+ if (collapseMiddle) {
+ lengthShared += micros1.modMiddle->apply(string, UPRV_INDEX_0, UPRV_INDEX_3, status);
+ } else {
+ length1 += micros1.modMiddle->apply(string, UPRV_INDEX_0, UPRV_INDEX_1, status);
+ length2 += micros1.modMiddle->apply(string, UPRV_INDEX_2, UPRV_INDEX_3, status);
+ }
+
+ if (collapseOuter) {
+ micros1.modOuter->apply(string, UPRV_INDEX_0, UPRV_INDEX_3, status);
+ } else {
+ micros1.modOuter->apply(string, UPRV_INDEX_0, UPRV_INDEX_1, status);
+ micros1.modOuter->apply(string, UPRV_INDEX_2, UPRV_INDEX_3, status);
+ }
+}
diff --git a/icu4c/source/i18n/numrange_types.h b/icu4c/source/i18n/numrange_impl.h
similarity index 60%
rename from icu4c/source/i18n/numrange_types.h
rename to icu4c/source/i18n/numrange_impl.h
index 2a2820cda24..d05ea850037 100644
--- a/icu4c/source/i18n/numrange_types.h
+++ b/icu4c/source/i18n/numrange_impl.h
@@ -11,6 +11,7 @@
#include "unicode/numberrangeformatter.h"
#include "number_types.h"
#include "number_decimalquantity.h"
+#include "number_formatimpl.h"
#include "number_stringbuilder.h"
U_NAMESPACE_BEGIN namespace number {
@@ -36,12 +37,38 @@ struct UFormattedNumberRangeData : public UMemory {
DecimalQuantity quantity1;
DecimalQuantity quantity2;
NumberStringBuilder string;
- UNumberRangeIdentityResult identityResult = UNUM_IDENTITY_RESULT_EQUAL_BEFORE_ROUNDING;
+ UNumberRangeIdentityResult identityResult = UNUM_IDENTITY_RESULT_COUNT;
// No C conversion methods (no C API yet)
};
+class NumberRangeFormatterImpl : public UMemory {
+ public:
+ void format(UFormattedNumberRangeData& data, bool equalBeforeRounding, UErrorCode& status) const;
+
+ private:
+ NumberFormatterImpl formatterImpl1;
+ NumberFormatterImpl formatterImpl2;
+ bool fSameFormatters;
+
+ UNumberRangeCollapse fCollapse;
+ UNumberRangeIdentityFallback fIdentityFallback;
+
+ void formatSingleValue(UFormattedNumberRangeData& data,
+ MicroProps& micros1, MicroProps& micros2,
+ UErrorCode& status) const;
+
+ void formatApproximately(UFormattedNumberRangeData& data,
+ MicroProps& micros1, MicroProps& micros2,
+ UErrorCode& status) const;
+
+ void formatRange(UFormattedNumberRangeData& data,
+ MicroProps& micros1, MicroProps& micros2,
+ UErrorCode& status) const;
+};
+
+
} // namespace impl
} // namespace number
U_NAMESPACE_END
diff --git a/icu4c/source/i18n/unicode/numberformatter.h b/icu4c/source/i18n/unicode/numberformatter.h
index 64572cbf8c2..57bc1f30345 100644
--- a/icu4c/source/i18n/unicode/numberformatter.h
+++ b/icu4c/source/i18n/unicode/numberformatter.h
@@ -144,6 +144,7 @@ class MultiplierFormatHandler;
class CurrencySymbols;
class GeneratorHelpers;
class DecNum;
+class NumberRangeFormatterImpl;
} // namespace impl
@@ -2188,6 +2189,9 @@ class U_I18N_API UnlocalizedNumberFormatter
// To give NumberFormatter::with() access to this class's constructor:
friend class NumberFormatter;
+
+ // Give NumberRangeFormatter access to the MacroProps
+ friend class NumberRangeFormatterImpl;
};
/**
diff --git a/icu4c/source/i18n/unicode/numberrangeformatter.h b/icu4c/source/i18n/unicode/numberrangeformatter.h
index c5e2c0eb9ac..208e533e22c 100644
--- a/icu4c/source/i18n/unicode/numberrangeformatter.h
+++ b/icu4c/source/i18n/unicode/numberrangeformatter.h
@@ -148,7 +148,16 @@ typedef enum UNumberRangeIdentityResult {
* @draft ICU 63
* @see NumberRangeFormatter
*/
- UNUM_IDENTITY_RESULT_NOT_EQUAL
+ UNUM_IDENTITY_RESULT_NOT_EQUAL,
+
+#ifndef U_HIDE_INTERNAL_API
+ /**
+ * The number of entries in this enum.
+ * @internal
+ */
+ UNUM_IDENTITY_RESULT_COUNT
+#endif
+
} UNumberRangeIdentityResult;
U_NAMESPACE_BEGIN