diff --git a/icu4c/source/i18n/i18n.vcxproj b/icu4c/source/i18n/i18n.vcxproj
index cef0ad344ee..9df696fc72f 100644
--- a/icu4c/source/i18n/i18n.vcxproj
+++ b/icu4c/source/i18n/i18n.vcxproj
@@ -219,6 +219,7 @@
+
@@ -487,13 +488,14 @@
-
+
+
diff --git a/icu4c/source/i18n/i18n.vcxproj.filters b/icu4c/source/i18n/i18n.vcxproj.filters
index 4e65f29a2c4..83409f013d2 100644
--- a/icu4c/source/i18n/i18n.vcxproj.filters
+++ b/icu4c/source/i18n/i18n.vcxproj.filters
@@ -591,6 +591,9 @@
formatting
+
+ formatting
+
formatting
@@ -606,6 +609,9 @@
formatting
+
+ formatting
+
formatting
@@ -917,6 +923,9 @@
formatting
+
+ formatting
+
formatting
@@ -932,6 +941,9 @@
formatting
+
+ formatting
+
formatting
diff --git a/icu4c/source/i18n/number_fluent.cpp b/icu4c/source/i18n/number_fluent.cpp
index 579f3062142..8569a36e5b2 100644
--- a/icu4c/source/i18n/number_fluent.cpp
+++ b/icu4c/source/i18n/number_fluent.cpp
@@ -520,123 +520,6 @@ LocalizedNumberFormatter UnlocalizedNumberFormatter::locale(const Locale& locale
return LocalizedNumberFormatter(std::move(fMacros), locale);
}
-SymbolsWrapper::SymbolsWrapper(const SymbolsWrapper& other) {
- doCopyFrom(other);
-}
-
-SymbolsWrapper::SymbolsWrapper(SymbolsWrapper&& src) U_NOEXCEPT {
- doMoveFrom(std::move(src));
-}
-
-SymbolsWrapper& SymbolsWrapper::operator=(const SymbolsWrapper& other) {
- if (this == &other) {
- return *this;
- }
- doCleanup();
- doCopyFrom(other);
- return *this;
-}
-
-SymbolsWrapper& SymbolsWrapper::operator=(SymbolsWrapper&& src) U_NOEXCEPT {
- if (this == &src) {
- return *this;
- }
- doCleanup();
- doMoveFrom(std::move(src));
- return *this;
-}
-
-SymbolsWrapper::~SymbolsWrapper() {
- doCleanup();
-}
-
-void SymbolsWrapper::setTo(const DecimalFormatSymbols& dfs) {
- doCleanup();
- fType = SYMPTR_DFS;
- fPtr.dfs = new DecimalFormatSymbols(dfs);
-}
-
-void SymbolsWrapper::setTo(const NumberingSystem* ns) {
- doCleanup();
- fType = SYMPTR_NS;
- fPtr.ns = ns;
-}
-
-void SymbolsWrapper::doCopyFrom(const SymbolsWrapper& other) {
- fType = other.fType;
- switch (fType) {
- case SYMPTR_NONE:
- // No action necessary
- break;
- case SYMPTR_DFS:
- // Memory allocation failures are exposed in copyErrorTo()
- if (other.fPtr.dfs != nullptr) {
- fPtr.dfs = new DecimalFormatSymbols(*other.fPtr.dfs);
- } else {
- fPtr.dfs = nullptr;
- }
- break;
- case SYMPTR_NS:
- // Memory allocation failures are exposed in copyErrorTo()
- if (other.fPtr.ns != nullptr) {
- fPtr.ns = new NumberingSystem(*other.fPtr.ns);
- } else {
- fPtr.ns = nullptr;
- }
- break;
- }
-}
-
-void SymbolsWrapper::doMoveFrom(SymbolsWrapper&& src) {
- fType = src.fType;
- switch (fType) {
- case SYMPTR_NONE:
- // No action necessary
- break;
- case SYMPTR_DFS:
- fPtr.dfs = src.fPtr.dfs;
- src.fPtr.dfs = nullptr;
- break;
- case SYMPTR_NS:
- fPtr.ns = src.fPtr.ns;
- src.fPtr.ns = nullptr;
- break;
- }
-}
-
-void SymbolsWrapper::doCleanup() {
- switch (fType) {
- case SYMPTR_NONE:
- // No action necessary
- break;
- case SYMPTR_DFS:
- delete fPtr.dfs;
- break;
- case SYMPTR_NS:
- delete fPtr.ns;
- break;
- }
-}
-
-bool SymbolsWrapper::isDecimalFormatSymbols() const {
- return fType == SYMPTR_DFS;
-}
-
-bool SymbolsWrapper::isNumberingSystem() const {
- return fType == SYMPTR_NS;
-}
-
-const DecimalFormatSymbols* SymbolsWrapper::getDecimalFormatSymbols() const {
- U_ASSERT(fType == SYMPTR_DFS);
- return fPtr.dfs;
-}
-
-const NumberingSystem* SymbolsWrapper::getNumberingSystem() const {
- U_ASSERT(fType == SYMPTR_NS);
- return fPtr.ns;
-}
-
-
FormattedNumber LocalizedNumberFormatter::formatInt(int64_t value, UErrorCode& status) const {
if (U_FAILURE(status)) { return FormattedNumber(U_ILLEGAL_ARGUMENT_ERROR); }
auto results = new UFormattedNumberData();
diff --git a/icu4c/source/i18n/number_formatimpl.cpp b/icu4c/source/i18n/number_formatimpl.cpp
index b7aee30225f..3f2a00f7e7c 100644
--- a/icu4c/source/i18n/number_formatimpl.cpp
+++ b/icu4c/source/i18n/number_formatimpl.cpp
@@ -25,9 +25,6 @@ using namespace icu::number;
using namespace icu::number::impl;
-MicroPropsGenerator::~MicroPropsGenerator() = default;
-
-
NumberFormatterImpl::NumberFormatterImpl(const MacroProps& macros, UErrorCode& status)
: NumberFormatterImpl(macros, true, status) {
}
@@ -255,16 +252,14 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe,
precision = Precision::integer().withMinDigits(2);
} else if (isCurrency) {
precision = Precision::currency(UCURR_USAGE_STANDARD);
+ } else if (macros.usage.isSet()) {
+ // Bogus Precision - it will get set in the UsagePrefsHandler instead
+ precision = Precision();
} else {
precision = Precision::maxFraction(6);
}
UNumberFormatRoundingMode roundingMode;
- if (macros.roundingMode != kDefaultMode) {
- roundingMode = macros.roundingMode;
- } else {
- // Temporary until ICU 64
- roundingMode = precision.fRoundingMode;
- }
+ roundingMode = macros.roundingMode;
fMicros.rounder = {precision, roundingMode, currency, status};
if (U_FAILURE(status)) {
return nullptr;
diff --git a/icu4c/source/i18n/number_integerwidth.cpp b/icu4c/source/i18n/number_integerwidth.cpp
index d62aef444dc..10b853423c8 100644
--- a/icu4c/source/i18n/number_integerwidth.cpp
+++ b/icu4c/source/i18n/number_integerwidth.cpp
@@ -40,6 +40,9 @@ IntegerWidth IntegerWidth::truncateAt(int32_t maxInt) {
}
void IntegerWidth::apply(impl::DecimalQuantity& quantity, UErrorCode& status) const {
+ if (U_FAILURE(status)) {
+ return;
+ }
if (fHasError) {
status = U_ILLEGAL_ARGUMENT_ERROR;
} else if (fUnion.minMaxInt.fMaxInt == -1) {
diff --git a/icu4c/source/i18n/number_longnames.cpp b/icu4c/source/i18n/number_longnames.cpp
index 9d58895c6dd..32a5be9d591 100644
--- a/icu4c/source/i18n/number_longnames.cpp
+++ b/icu4c/source/i18n/number_longnames.cpp
@@ -426,6 +426,9 @@ void LongNameMultiplexer::processQuantity(DecimalQuantity &quantity, MicroProps
return;
}
}
+ if (U_FAILURE(status)) {
+ return;
+ }
// We shouldn't receive any outputUnit for which we haven't already got a
// LongNameHandler:
status = U_INTERNAL_PROGRAM_ERROR;
diff --git a/icu4c/source/i18n/number_mapper.cpp b/icu4c/source/i18n/number_mapper.cpp
index ec617438c9a..e2a0d284b7c 100644
--- a/icu4c/source/i18n/number_mapper.cpp
+++ b/icu4c/source/i18n/number_mapper.cpp
@@ -92,6 +92,8 @@ MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& propert
int32_t minSig = properties.minimumSignificantDigits;
int32_t maxSig = properties.maximumSignificantDigits;
double roundingIncrement = properties.roundingIncrement;
+ // Not assigning directly to macros.roundingMode here: we change
+ // roundingMode if and when we also change macros.precision.
RoundingMode roundingMode = properties.roundingMode.getOrDefault(UNUM_ROUND_HALFEVEN);
bool explicitMinMaxFrac = minFrac != -1 || maxFrac != -1;
bool explicitMinMaxSig = minSig != -1 || maxSig != -1;
@@ -145,7 +147,7 @@ MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& propert
precision = Precision::constructCurrency(currencyUsage);
}
if (!precision.isBogus()) {
- precision.fRoundingMode = roundingMode;
+ macros.roundingMode = roundingMode;
macros.precision = precision;
}
@@ -239,7 +241,7 @@ MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& propert
// TODO: Reset maxSig_ = 1 + minFrac_ to follow the spec.
macros.precision = Precision::constructSignificant(minSig_, maxSig_);
}
- macros.precision.fRoundingMode = roundingMode;
+ macros.roundingMode = roundingMode;
}
}
diff --git a/icu4c/source/i18n/number_rounding.cpp b/icu4c/source/i18n/number_rounding.cpp
index 3ffce673ad0..14896679696 100644
--- a/icu4c/source/i18n/number_rounding.cpp
+++ b/icu4c/source/i18n/number_rounding.cpp
@@ -5,13 +5,16 @@
#if !UCONFIG_NO_FORMATTING
+#include "charstr.h"
#include "uassert.h"
#include "unicode/numberformatter.h"
#include "number_types.h"
#include "number_decimalquantity.h"
#include "double-conversion.h"
#include "number_roundingutils.h"
+#include "number_skeletons.h"
#include "putilimp.h"
+#include "string_segment.h"
using namespace icu;
using namespace icu::number;
@@ -19,6 +22,40 @@ using namespace icu::number::impl;
using double_conversion::DoubleToStringConverter;
+using icu::StringSegment;
+
+// Most blueprint_helpers live in number_skeletons.cpp. This one is in
+// number_rounding.cpp for dependency reasons.
+void blueprint_helpers::parseIncrementOption(const StringSegment &segment, MacroProps ¯os,
+ UErrorCode &status) {
+ // Need to do char <-> UChar conversion...
+ U_ASSERT(U_SUCCESS(status));
+ CharString buffer;
+ SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status);
+
+ // Utilize DecimalQuantity/decNumber to parse this for us.
+ DecimalQuantity dq;
+ UErrorCode localStatus = U_ZERO_ERROR;
+ dq.setToDecNumber({buffer.data(), buffer.length()}, localStatus);
+ if (U_FAILURE(localStatus)) {
+ // throw new SkeletonSyntaxException("Invalid rounding increment", segment, e);
+ status = U_NUMBER_SKELETON_SYNTAX_ERROR;
+ return;
+ }
+ double increment = dq.toDouble();
+
+ // We also need to figure out how many digits. Do a brute force string operation.
+ int decimalOffset = 0;
+ while (decimalOffset < segment.length() && segment.charAt(decimalOffset) != '.') {
+ decimalOffset++;
+ }
+ if (decimalOffset == segment.length()) {
+ macros.precision = Precision::increment(increment);
+ } else {
+ int32_t fractionLength = segment.length() - decimalOffset - 1;
+ macros.precision = Precision::increment(increment).withMinFraction(fractionLength);
+ }
+}
namespace {
@@ -84,7 +121,7 @@ digits_t roundingutils::doubleFractionLength(double input, int8_t* singleDigit)
Precision Precision::unlimited() {
- return Precision(RND_NONE, {}, kDefaultMode);
+ return Precision(RND_NONE, {});
}
FractionPrecision Precision::integer() {
@@ -229,7 +266,7 @@ FractionPrecision Precision::constructFraction(int32_t minFrac, int32_t maxFrac)
settings.fMaxSig = -1;
PrecisionUnion union_;
union_.fracSig = settings;
- return {RND_FRACTION, union_, kDefaultMode};
+ return {RND_FRACTION, union_};
}
Precision Precision::constructSignificant(int32_t minSig, int32_t maxSig) {
@@ -240,7 +277,7 @@ Precision Precision::constructSignificant(int32_t minSig, int32_t maxSig) {
settings.fMaxSig = static_cast(maxSig);
PrecisionUnion union_;
union_.fracSig = settings;
- return {RND_SIGNIFICANT, union_, kDefaultMode};
+ return {RND_SIGNIFICANT, union_};
}
Precision
@@ -250,7 +287,7 @@ Precision::constructFractionSignificant(const FractionPrecision &base, int32_t m
settings.fMaxSig = static_cast(maxSig);
PrecisionUnion union_;
union_.fracSig = settings;
- return {RND_FRACTION_SIGNIFICANT, union_, kDefaultMode};
+ return {RND_FRACTION_SIGNIFICANT, union_};
}
IncrementPrecision Precision::constructIncrement(double increment, int32_t minFrac) {
@@ -270,18 +307,18 @@ IncrementPrecision Precision::constructIncrement(double increment, int32_t minFr
// NOTE: In C++, we must return the correct value type with the correct union.
// It would be invalid to return a RND_FRACTION here because the methods on the
// IncrementPrecision type assume that the union is backed by increment data.
- return {RND_INCREMENT_ONE, union_, kDefaultMode};
+ return {RND_INCREMENT_ONE, union_};
} else if (singleDigit == 5) {
- return {RND_INCREMENT_FIVE, union_, kDefaultMode};
+ return {RND_INCREMENT_FIVE, union_};
} else {
- return {RND_INCREMENT, union_, kDefaultMode};
+ return {RND_INCREMENT, union_};
}
}
CurrencyPrecision Precision::constructCurrency(UCurrencyUsage usage) {
PrecisionUnion union_;
union_.currencyUsage = usage;
- return {RND_CURRENCY, union_, kDefaultMode};
+ return {RND_CURRENCY, union_};
}
@@ -341,6 +378,9 @@ RoundingImpl::chooseMultiplierAndApply(impl::DecimalQuantity &input, const impl:
/** This is the method that contains the actual rounding logic. */
void RoundingImpl::apply(impl::DecimalQuantity &value, UErrorCode& status) const {
+ if (U_FAILURE(status)) {
+ return;
+ }
if (fPassThrough) {
return;
}
diff --git a/icu4c/source/i18n/number_roundingutils.h b/icu4c/source/i18n/number_roundingutils.h
index 3e37f319540..841854fab54 100644
--- a/icu4c/source/i18n/number_roundingutils.h
+++ b/icu4c/source/i18n/number_roundingutils.h
@@ -44,6 +44,9 @@ enum Section {
inline bool
getRoundingDirection(bool isEven, bool isNegative, Section section, RoundingMode roundingMode,
UErrorCode &status) {
+ if (U_FAILURE(status)) {
+ return false;
+ }
switch (roundingMode) {
case RoundingMode::UNUM_ROUND_UP:
// round away from zero
@@ -187,6 +190,9 @@ class RoundingImpl {
Precision fPrecision;
UNumberFormatRoundingMode fRoundingMode;
bool fPassThrough = true; // default value
+
+ // Permits access to fPrecision.
+ friend class UsagePrefsHandler;
};
diff --git a/icu4c/source/i18n/number_skeletons.cpp b/icu4c/source/i18n/number_skeletons.cpp
index 46c2125f1d2..207fc05daac 100644
--- a/icu4c/source/i18n/number_skeletons.cpp
+++ b/icu4c/source/i18n/number_skeletons.cpp
@@ -152,21 +152,6 @@ UPRV_BLOCK_MACRO_BEGIN { \
} UPRV_BLOCK_MACRO_END
-#define SKELETON_UCHAR_TO_CHAR(dest, src, start, end, status) (void)(dest); \
-UPRV_BLOCK_MACRO_BEGIN { \
- UErrorCode conversionStatus = U_ZERO_ERROR; \
- (dest).appendInvariantChars({FALSE, (src).getBuffer() + (start), (end) - (start)}, conversionStatus); \
- if (conversionStatus == U_INVARIANT_CONVERSION_ERROR) { \
- /* Don't propagate the invariant conversion error; it is a skeleton syntax error */ \
- (status) = U_NUMBER_SKELETON_SYNTAX_ERROR; \
- return; \
- } else if (U_FAILURE(conversionStatus)) { \
- (status) = conversionStatus; \
- return; \
- } \
-} UPRV_BLOCK_MACRO_END
-
-
} // anonymous namespace
@@ -483,6 +468,7 @@ UnicodeString skeleton::generate(const MacroProps& macros, UErrorCode& status) {
MacroProps skeleton::parseSkeleton(
const UnicodeString& skeletonString, int32_t& errOffset, UErrorCode& status) {
U_ASSERT(U_SUCCESS(status));
+ U_ASSERT(kSerializedStemTrie != nullptr);
// Add a trailing whitespace to the end of the skeleton string to make code cleaner.
UnicodeString tempSkeletonString(skeletonString);
@@ -589,6 +575,8 @@ MacroProps skeleton::parseSkeleton(
ParseState
skeleton::parseStem(const StringSegment& segment, const UCharsTrie& stemTrie, SeenMacroProps& seen,
MacroProps& macros, UErrorCode& status) {
+ U_ASSERT(U_SUCCESS(status));
+
// First check for "blueprint" stems, which start with a "signal char"
switch (segment.charAt(0)) {
case u'.':
@@ -767,6 +755,7 @@ skeleton::parseStem(const StringSegment& segment, const UCharsTrie& stemTrie, Se
ParseState skeleton::parseOption(ParseState stem, const StringSegment& segment, MacroProps& macros,
UErrorCode& status) {
+ U_ASSERT(U_SUCCESS(status));
///// Required options: /////
@@ -995,6 +984,7 @@ blueprint_helpers::generateCurrencyOption(const CurrencyUnit& currency, UnicodeS
void blueprint_helpers::parseMeasureUnitOption(const StringSegment& segment, MacroProps& macros,
UErrorCode& status) {
+ U_ASSERT(U_SUCCESS(status));
const UnicodeString stemString = segment.toTempUnicodeString();
// NOTE: The category (type) of the unit is guaranteed to be a valid subtag (alphanumeric)
@@ -1010,7 +1000,6 @@ void blueprint_helpers::parseMeasureUnitOption(const StringSegment& segment, Mac
}
// Need to do char <-> UChar conversion...
- U_ASSERT(U_SUCCESS(status));
CharString type;
SKELETON_UCHAR_TO_CHAR(type, stemString, 0, firstHyphen, status);
CharString subType;
@@ -1339,36 +1328,8 @@ bool blueprint_helpers::parseFracSigOption(const StringSegment& segment, MacroPr
return true;
}
-void blueprint_helpers::parseIncrementOption(const StringSegment& segment, MacroProps& macros,
- UErrorCode& status) {
- // Need to do char <-> UChar conversion...
- U_ASSERT(U_SUCCESS(status));
- CharString buffer;
- SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status);
-
- // Utilize DecimalQuantity/decNumber to parse this for us.
- DecimalQuantity dq;
- UErrorCode localStatus = U_ZERO_ERROR;
- dq.setToDecNumber({buffer.data(), buffer.length()}, localStatus);
- if (U_FAILURE(localStatus)) {
- // throw new SkeletonSyntaxException("Invalid rounding increment", segment, e);
- status = U_NUMBER_SKELETON_SYNTAX_ERROR;
- return;
- }
- double increment = dq.toDouble();
-
- // We also need to figure out how many digits. Do a brute force string operation.
- int decimalOffset = 0;
- while (decimalOffset < segment.length() && segment.charAt(decimalOffset) != '.') {
- decimalOffset++;
- }
- if (decimalOffset == segment.length()) {
- macros.precision = Precision::increment(increment);
- } else {
- int32_t fractionLength = segment.length() - decimalOffset - 1;
- macros.precision = Precision::increment(increment).withMinFraction(fractionLength);
- }
-}
+// blueprint_helpers::parseIncrementOption lives in number_rounding.cpp for
+// dependencies reasons.
void blueprint_helpers::generateIncrementOption(double increment, int32_t trailingZeros, UnicodeString& sb,
UErrorCode&) {
@@ -1583,7 +1544,7 @@ bool GeneratorHelpers::perUnit(const MacroProps& macros, UnicodeString& sb, UErr
}
}
-bool GeneratorHelpers::usage(const MacroProps& macros, UnicodeString& sb, UErrorCode&) {
+bool GeneratorHelpers::usage(const MacroProps& macros, UnicodeString& sb, UErrorCode& /* status */) {
if (macros.usage.fLength > 0) {
sb.append(u"usage/", -1);
sb.append(UnicodeString(macros.usage.fUsage, -1, US_INV));
diff --git a/icu4c/source/i18n/number_skeletons.h b/icu4c/source/i18n/number_skeletons.h
index 313c7ac54c2..b29b7f9960b 100644
--- a/icu4c/source/i18n/number_skeletons.h
+++ b/icu4c/source/i18n/number_skeletons.h
@@ -355,6 +355,24 @@ struct SeenMacroProps {
bool scale = false;
};
+namespace {
+
+#define SKELETON_UCHAR_TO_CHAR(dest, src, start, end, status) (void)(dest); \
+UPRV_BLOCK_MACRO_BEGIN { \
+ UErrorCode conversionStatus = U_ZERO_ERROR; \
+ (dest).appendInvariantChars({FALSE, (src).getBuffer() + (start), (end) - (start)}, conversionStatus); \
+ if (conversionStatus == U_INVARIANT_CONVERSION_ERROR) { \
+ /* Don't propagate the invariant conversion error; it is a skeleton syntax error */ \
+ (status) = U_NUMBER_SKELETON_SYNTAX_ERROR; \
+ return; \
+ } else if (U_FAILURE(conversionStatus)) { \
+ (status) = conversionStatus; \
+ return; \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+} // namespace
+
} // namespace impl
} // namespace number
U_NAMESPACE_END
diff --git a/icu4c/source/i18n/number_symbolswrapper.cpp b/icu4c/source/i18n/number_symbolswrapper.cpp
new file mode 100644
index 00000000000..5f7648d7039
--- /dev/null
+++ b/icu4c/source/i18n/number_symbolswrapper.cpp
@@ -0,0 +1,125 @@
+// © 2020 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+
+#include "number_microprops.h"
+#include "unicode/numberformatter.h"
+
+using namespace icu;
+using namespace icu::number;
+using namespace icu::number::impl;
+
+SymbolsWrapper::SymbolsWrapper(const SymbolsWrapper &other) {
+ doCopyFrom(other);
+}
+
+SymbolsWrapper::SymbolsWrapper(SymbolsWrapper &&src) U_NOEXCEPT {
+ doMoveFrom(std::move(src));
+}
+
+SymbolsWrapper &SymbolsWrapper::operator=(const SymbolsWrapper &other) {
+ if (this == &other) {
+ return *this;
+ }
+ doCleanup();
+ doCopyFrom(other);
+ return *this;
+}
+
+SymbolsWrapper &SymbolsWrapper::operator=(SymbolsWrapper &&src) U_NOEXCEPT {
+ if (this == &src) {
+ return *this;
+ }
+ doCleanup();
+ doMoveFrom(std::move(src));
+ return *this;
+}
+
+SymbolsWrapper::~SymbolsWrapper() {
+ doCleanup();
+}
+
+void SymbolsWrapper::setTo(const DecimalFormatSymbols &dfs) {
+ doCleanup();
+ fType = SYMPTR_DFS;
+ fPtr.dfs = new DecimalFormatSymbols(dfs);
+}
+
+void SymbolsWrapper::setTo(const NumberingSystem *ns) {
+ doCleanup();
+ fType = SYMPTR_NS;
+ fPtr.ns = ns;
+}
+
+void SymbolsWrapper::doCopyFrom(const SymbolsWrapper &other) {
+ fType = other.fType;
+ switch (fType) {
+ case SYMPTR_NONE:
+ // No action necessary
+ break;
+ case SYMPTR_DFS:
+ // Memory allocation failures are exposed in copyErrorTo()
+ if (other.fPtr.dfs != nullptr) {
+ fPtr.dfs = new DecimalFormatSymbols(*other.fPtr.dfs);
+ } else {
+ fPtr.dfs = nullptr;
+ }
+ break;
+ case SYMPTR_NS:
+ // Memory allocation failures are exposed in copyErrorTo()
+ if (other.fPtr.ns != nullptr) {
+ fPtr.ns = new NumberingSystem(*other.fPtr.ns);
+ } else {
+ fPtr.ns = nullptr;
+ }
+ break;
+ }
+}
+
+void SymbolsWrapper::doMoveFrom(SymbolsWrapper &&src) {
+ fType = src.fType;
+ switch (fType) {
+ case SYMPTR_NONE:
+ // No action necessary
+ break;
+ case SYMPTR_DFS:
+ fPtr.dfs = src.fPtr.dfs;
+ src.fPtr.dfs = nullptr;
+ break;
+ case SYMPTR_NS:
+ fPtr.ns = src.fPtr.ns;
+ src.fPtr.ns = nullptr;
+ break;
+ }
+}
+
+void SymbolsWrapper::doCleanup() {
+ switch (fType) {
+ case SYMPTR_NONE:
+ // No action necessary
+ break;
+ case SYMPTR_DFS:
+ delete fPtr.dfs;
+ break;
+ case SYMPTR_NS:
+ delete fPtr.ns;
+ break;
+ }
+}
+
+bool SymbolsWrapper::isDecimalFormatSymbols() const {
+ return fType == SYMPTR_DFS;
+}
+
+bool SymbolsWrapper::isNumberingSystem() const {
+ return fType == SYMPTR_NS;
+}
+
+const DecimalFormatSymbols *SymbolsWrapper::getDecimalFormatSymbols() const {
+ U_ASSERT(fType == SYMPTR_DFS);
+ return fPtr.dfs;
+}
+
+const NumberingSystem *SymbolsWrapper::getNumberingSystem() const {
+ U_ASSERT(fType == SYMPTR_NS);
+ return fPtr.ns;
+}
diff --git a/icu4c/source/i18n/number_types.h b/icu4c/source/i18n/number_types.h
index 8180fe55317..8078851ba3f 100644
--- a/icu4c/source/i18n/number_types.h
+++ b/icu4c/source/i18n/number_types.h
@@ -262,7 +262,7 @@ class U_I18N_API ModifierStore {
*/
class U_I18N_API MicroPropsGenerator {
public:
- virtual ~MicroPropsGenerator();
+ virtual ~MicroPropsGenerator() = default;
/**
* Considers the given {@link DecimalQuantity}, optionally mutates it, and
diff --git a/icu4c/source/i18n/number_usageprefs.cpp b/icu4c/source/i18n/number_usageprefs.cpp
index 0c7988e32de..4738bfa5b0b 100644
--- a/icu4c/source/i18n/number_usageprefs.cpp
+++ b/icu4c/source/i18n/number_usageprefs.cpp
@@ -8,6 +8,7 @@
#include "number_decimalquantity.h"
#include "number_microprops.h"
#include "number_roundingutils.h"
+#include "number_skeletons.h"
#include "unicode/char16ptr.h"
#include "unicode/currunit.h"
#include "unicode/fmtable.h"
@@ -19,6 +20,7 @@
using namespace icu::number;
using namespace icu::number::impl;
+using icu::StringSegment;
// Copy constructor
Usage::Usage(const Usage &other) : fUsage(nullptr), fLength(other.fLength), fError(other.fError) {
@@ -100,25 +102,44 @@ void UsagePrefsHandler::processQuantity(DecimalQuantity &quantity, MicroProps &m
quantity.roundToInfinity(); // Enables toDouble
const auto routed = fUnitsRouter.route(quantity.toDouble(), status);
+ if (U_FAILURE(status)) {
+ return;
+ }
const auto& routedUnits = routed.measures;
micros.outputUnit = routedUnits[0]->getUnit();
quantity.setToDouble(routedUnits[0]->getNumber().getDouble());
- // TODO(units): here we are always overriding Precision. (1) get precision
- // from fUnitsRouter, (2) ensure we use the UnitPreference skeleton's
- // precision only when there isn't an explicit override we prefer to use.
- // This needs to be handled within
- // NumberFormatterImpl::macrosToMicroGenerator in number_formatimpl.cpp
- // TODO: Use precision from `routed` result.
- Precision precision = Precision::integer().withMinDigits(2);
- UNumberFormatRoundingMode roundingMode;
- // Temporary until ICU 64?
- roundingMode = precision.fRoundingMode;
- CurrencyUnit currency(u"", status);
- micros.rounder = {precision, roundingMode, currency, status};
- if (U_FAILURE(status)) {
- return;
+ UnicodeString precisionSkeleton = routed.precision;
+ if (micros.rounder.fPrecision.isBogus()) {
+ if (precisionSkeleton.length() > 0) {
+ micros.rounder.fPrecision = parseSkeletonToPrecision(precisionSkeleton, status);
+ } else {
+ // We use the same rounding mode as COMPACT notation: known to be a
+ // human-friendly rounding mode: integers, but add a decimal digit
+ // as needed to ensure we have at least 2 significant digits.
+ micros.rounder.fPrecision = Precision::integer().withMinDigits(2);
+ }
}
}
+Precision UsagePrefsHandler::parseSkeletonToPrecision(icu::UnicodeString precisionSkeleton,
+ UErrorCode status) {
+ if (U_FAILURE(status)) {
+ // As a member of UsagePrefsHandler, which is a friend of Precision, we
+ // get access to the default constructor.
+ return {};
+ }
+ constexpr int32_t kSkelPrefixLen = 20;
+ if (!precisionSkeleton.startsWith(UNICODE_STRING_SIMPLE("precision-increment/"))) {
+ status = U_INVALID_FORMAT_ERROR;
+ return {};
+ }
+ U_ASSERT(precisionSkeleton[kSkelPrefixLen - 1] == u'/');
+ StringSegment segment(precisionSkeleton, false);
+ segment.adjustOffset(kSkelPrefixLen);
+ MacroProps macros;
+ blueprint_helpers::parseIncrementOption(segment, macros, status);
+ return macros.precision;
+}
+
#endif /* #if !UCONFIG_NO_FORMATTING */
diff --git a/icu4c/source/i18n/number_usageprefs.h b/icu4c/source/i18n/number_usageprefs.h
index fa56c1ea5c7..bdb2a54f82c 100644
--- a/icu4c/source/i18n/number_usageprefs.h
+++ b/icu4c/source/i18n/number_usageprefs.h
@@ -52,6 +52,8 @@ class U_I18N_API UsagePrefsHandler : public MicroPropsGenerator, public UMemory
private:
UnitsRouter fUnitsRouter;
const MicroPropsGenerator *fParent;
+
+ static Precision parseSkeletonToPrecision(icu::UnicodeString precisionSkeleton, UErrorCode status);
};
} // namespace impl
diff --git a/icu4c/source/i18n/sources.txt b/icu4c/source/i18n/sources.txt
index 5e77d679aac..2d95da6cdf5 100644
--- a/icu4c/source/i18n/sources.txt
+++ b/icu4c/source/i18n/sources.txt
@@ -123,6 +123,7 @@ number_patternstring.cpp
number_rounding.cpp
number_scientific.cpp
number_skeletons.cpp
+number_symbolswrapper.cpp
number_usageprefs.cpp
number_utils.cpp
numfmt.cpp
diff --git a/icu4c/source/i18n/unicode/numberformatter.h b/icu4c/source/i18n/unicode/numberformatter.h
index b17e25373e6..b2c22bb135f 100644
--- a/icu4c/source/i18n/unicode/numberformatter.h
+++ b/icu4c/source/i18n/unicode/numberformatter.h
@@ -707,12 +707,8 @@ class U_I18N_API Precision : public UMemory {
typedef PrecisionUnion::FractionSignificantSettings FractionSignificantSettings;
typedef PrecisionUnion::IncrementSettings IncrementSettings;
- /** The Precision encapsulates the RoundingMode when used within the implementation. */
- UNumberFormatRoundingMode fRoundingMode;
-
- Precision(const PrecisionType& type, const PrecisionUnion& union_,
- UNumberFormatRoundingMode roundingMode)
- : fType(type), fUnion(union_), fRoundingMode(roundingMode) {}
+ Precision(const PrecisionType& type, const PrecisionUnion& union_)
+ : fType(type), fUnion(union_) {}
Precision(UErrorCode errorCode) : fType(RND_ERROR) {
fUnion.errorCode = errorCode;
@@ -746,8 +742,6 @@ class U_I18N_API Precision : public UMemory {
static CurrencyPrecision constructCurrency(UCurrencyUsage usage);
- static Precision constructPassThrough();
-
// To allow MacroProps/MicroProps to initialize bogus instances:
friend struct impl::MacroProps;
friend struct impl::MicroProps;
@@ -769,9 +763,7 @@ class U_I18N_API Precision : public UMemory {
// To allow access to the skeleton generation code:
friend class impl::GeneratorHelpers;
- // TODO(units): revisit when UnitsRouter is changed: do we still need this
- // once Precision is returned by UnitsRouter? For now, we allow access to
- // Precision constructor from UsagePrefsHandler:
+ // To allow access to isBogus and the default (bogus) constructor:
friend class impl::UsagePrefsHandler;
};
diff --git a/icu4c/source/i18n/unitsrouter.h b/icu4c/source/i18n/unitsrouter.h
index 68284663293..b98ee83a786 100644
--- a/icu4c/source/i18n/unitsrouter.h
+++ b/icu4c/source/i18n/unitsrouter.h
@@ -25,7 +25,16 @@ class Measure;
namespace units {
struct RouteResult : UMemory {
+ // A list of measures: a single measure for single units, multiple measures
+ // for mixed units.
+ //
+ // TODO(icu-units/icu#21): figure out the right mixed unit API.
MaybeStackVector measures;
+
+ // A skeleton string starting with a precision-increment.
+ //
+ // TODO(hugovdm): generalise? or narrow down to only a precision-increment?
+ // or document that other skeleton elements are ignored?
UnicodeString precision;
RouteResult(MaybeStackVector measures, UnicodeString precision)
diff --git a/icu4c/source/test/depstest/dependencies.txt b/icu4c/source/test/depstest/dependencies.txt
index 58d82cd8974..b43ceadd280 100644
--- a/icu4c/source/test/depstest/dependencies.txt
+++ b/icu4c/source/test/depstest/dependencies.txt
@@ -869,7 +869,8 @@ library: i18n
dayperiodrules
listformatter
formatting formattable_cnv regex regex_cnv translit
- double_conversion number_representation number_output numberformatter number_skeletons numberparser
+ double_conversion number_representation number_output numberformatter
+ number_skeletons number_usageprefs numberparser
units_extra unitsformatter
universal_time_scale
uclean_i18n
@@ -989,16 +990,41 @@ group: numberformatter
number_decimfmtprops.o
number_fluent.o number_formatimpl.o
number_grouping.o number_integerwidth.o number_longnames.o
- number_mapper.o number_modifiers.o number_multiplier.o
+ number_mapper.o number_modifiers.o
number_notation.o number_padding.o
- number_patternmodifier.o number_patternstring.o number_rounding.o
- number_scientific.o number_usageprefs.o
- currpinf.o dcfmtsym.o numsys.o
+ number_patternmodifier.o number_patternstring.o
+ number_scientific.o
+ currpinf.o
numrange_fluent.o numrange_impl.o
deps
decnumber double_conversion formattable units unitsformatter
number_representation number_output
+ numsys
+ number_usageprefs
uclean_i18n common
+ number_symbolswrapper
+
+group: numsys
+ dcfmtsym.o
+ numsys.o
+ deps
+ currency
+ resourcebundle
+ uclean_i18n
+
+group: number_usageprefs
+ number_multiplier.o
+ number_usageprefs.o
+ deps
+ number_rounding
+ number_symbolswrapper
+ unitsformatter
+
+group: number_rounding
+ number_rounding.o
+ deps
+ currency
+ number_representation
group: number_skeletons
# Number skeleton support; separated from numberformatter
@@ -1007,6 +1033,12 @@ group: number_skeletons
numberformatter
units_extra
+group: number_symbolswrapper
+ number_symbolswrapper.o
+ deps
+ platform
+ numsys
+
group: numberparser
numparse_affixes.o numparse_compositions.o numparse_currency.o
numparse_decimal.o numparse_impl.o numparse_parsednumber.o
diff --git a/icu4c/source/test/intltest/numbertest.h b/icu4c/source/test/intltest/numbertest.h
index b5f52ccc553..9ed6877b0a4 100644
--- a/icu4c/source/test/intltest/numbertest.h
+++ b/icu4c/source/test/intltest/numbertest.h
@@ -56,6 +56,7 @@ class NumberFormatterApiTest : public IntlTestWithFieldPosition {
void unitCompoundMeasure();
void unitUsage();
void unitUsageErrorCodes();
+ void unitUsageSkeletons();
void unitCurrency();
void unitPercent();
void percentParity();
diff --git a/icu4c/source/test/intltest/numbertest_api.cpp b/icu4c/source/test/intltest/numbertest_api.cpp
index 4c50e33add0..0e4c976d401 100644
--- a/icu4c/source/test/intltest/numbertest_api.cpp
+++ b/icu4c/source/test/intltest/numbertest_api.cpp
@@ -77,6 +77,7 @@ void NumberFormatterApiTest::runIndexedTest(int32_t index, UBool exec, const cha
TESTCASE_AUTO(unitCompoundMeasure);
TESTCASE_AUTO(unitUsage);
TESTCASE_AUTO(unitUsageErrorCodes);
+ TESTCASE_AUTO(unitUsageSkeletons);
TESTCASE_AUTO(unitCurrency);
TESTCASE_AUTO(unitPercent);
if (!quick) {
@@ -684,8 +685,12 @@ void NumberFormatterApiTest::unitUsage() {
IcuTestErrorCode status(*this, "unitUsage()");
- LocalizedNumberFormatter formatter = unloc_formatter.locale("en-ZA");
- FormattedNumber formattedNum = formatter.formatDouble(300, status);
+ LocalizedNumberFormatter formatter;
+ FormattedNumber formattedNum;
+
+ formatter = unloc_formatter.locale("en-ZA");
+ formattedNum = formatter.formatDouble(321, status);
+ status.errIfFailureAndReset("unitUsage() en-ZA road, formatDouble(...)");
assertTrue(UnicodeString("unitUsage() en-ZA road, got outputUnit: \"") +
formattedNum.getOutputUnit(status).getIdentifier() + "\"",
MeasureUnit::getMeter() == formattedNum.getOutputUnit(status));
@@ -701,17 +706,23 @@ void NumberFormatterApiTest::unitUsage() {
u"877 km",
u"88 km",
u"8,8 km",
- u"877 m",
- u"88 m",
- u"8,8 m",
+ u"900 m",
+ u"90 m",
+ u"10 m",
u"0 m");
formatter = unloc_formatter.locale("en-GB");
- formattedNum = formatter.formatDouble(300, status);
+ formattedNum = formatter.formatDouble(321, status);
+ status.errIfFailureAndReset("unitUsage() en-GB road, formatDouble(...)");
+ U_ASSERT(status == U_ZERO_ERROR);
assertTrue(UnicodeString("unitUsage() en-GB road, got outputUnit: \"") +
formattedNum.getOutputUnit(status).getIdentifier() + "\"",
MeasureUnit::getYard() == formattedNum.getOutputUnit(status));
- assertEquals("unitUsage() en-GB road", "328 yd", formattedNum.toString(status));
+ status.errIfFailureAndReset("unitUsage() en-GB road, getOutputUnit(...)");
+ U_ASSERT(status == U_ZERO_ERROR);
+ assertEquals("unitUsage() en-GB road", "350 yd", formattedNum.toString(status));
+ status.errIfFailureAndReset("unitUsage() en-GB road, toString(...)");
+ U_ASSERT(status == U_ZERO_ERROR);
assertFormatDescendingBig(
u"unitUsage() en-GB road",
u"measure-unit/length-meter usage/road",
@@ -729,11 +740,17 @@ void NumberFormatterApiTest::unitUsage() {
u"0 yd");
formatter = unloc_formatter.locale("en-US");
- formattedNum = formatter.formatDouble(300, status);
+ formattedNum = formatter.formatDouble(321, status);
+ status.errIfFailureAndReset("unitUsage() en-US road, formatDouble(...)");
+ U_ASSERT(status == U_ZERO_ERROR);
assertTrue(UnicodeString("unitUsage() en-US road, got outputUnit: \"") +
formattedNum.getOutputUnit(status).getIdentifier() + "\"",
MeasureUnit::getFoot() == formattedNum.getOutputUnit(status));
- assertEquals("unitUsage() en-US road", "984 ft", formattedNum.toString(status));
+ status.errIfFailureAndReset("unitUsage() en-US road, getOutputUnit(...)");
+ U_ASSERT(status == U_ZERO_ERROR);
+ assertEquals("unitUsage() en-US road", "1,050 ft", formattedNum.toString(status));
+ status.errIfFailureAndReset("unitUsage() en-US road, toString(...)");
+ U_ASSERT(status == U_ZERO_ERROR);
assertFormatDescendingBig(
u"unitUsage() en-US road",
u"measure-unit/length-meter usage/road",
@@ -746,9 +763,30 @@ void NumberFormatterApiTest::unitUsage() {
u"54 mi",
u"5.4 mi",
u"0.54 mi",
- u"288 ft",
- u"29 ft",
+ u"300 ft",
+ u"30 ft",
u"0 ft");
+
+ assertFormatDescendingBig(
+ u"Scientific notation with Usage: possible when using a reasonable Precision",
+ u"scientific @### usage/default measure-unit/area-square-meter unit-width-full-name",
+ u"scientific @### usage/default unit/square-meter unit-width-full-name",
+ NumberFormatter::with()
+ .unit(SQUARE_METER)
+ .usage("default")
+ .notation(Notation::scientific())
+ .precision(Precision::minMaxSignificantDigits(1, 4))
+ .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
+ Locale("en-ZA"),
+ u"8,765E1 square kilometres",
+ u"8,765E0 square kilometres",
+ u"8,765E1 hectares",
+ u"8,765E0 hectares",
+ u"8,765E3 square metres",
+ u"8,765E2 square metres",
+ u"8,765E1 square metres",
+ u"8,765E0 square metres",
+ u"0E0 square centimetres");
}
void NumberFormatterApiTest::unitUsageErrorCodes() {
@@ -772,6 +810,122 @@ void NumberFormatterApiTest::unitUsageErrorCodes() {
status.assertSuccess();
}
+// Tests for the "skeletons" field in unitPreferenceData, as well as precision
+// and notation overrides.
+void NumberFormatterApiTest::unitUsageSkeletons() {
+ IcuTestErrorCode status(*this, "unitUsageSkeletons()");
+
+ assertFormatSingle(
+ u"Default >300m road preference skeletons round to 50m",
+ u"usage/road measure-unit/length-meter",
+ u"usage/road unit/meter",
+ NumberFormatter::with().unit(METER).usage("road"),
+ Locale("en-ZA"),
+ 321,
+ u"300 m");
+
+ assertFormatSingle(
+ u"Precision can be overridden: override takes precedence",
+ u"usage/road measure-unit/length-meter @#",
+ u"usage/road unit/meter @#",
+ NumberFormatter::with()
+ .unit(METER)
+ .usage("road")
+ .precision(Precision::maxSignificantDigits(2)),
+ Locale("en-ZA"),
+ 321,
+ u"320 m");
+
+ assertFormatSingle(
+ u"Compact notation with Usage: bizarre, but possible (short)",
+ u"compact-short usage/road measure-unit/length-meter",
+ u"compact-short usage/road unit/meter",
+ NumberFormatter::with()
+ .unit(METER)
+ .usage("road")
+ .notation(Notation::compactShort()),
+ Locale("en-ZA"),
+ 987654321,
+ u"988K km");
+
+ assertFormatSingle(
+ u"Compact notation with Usage: bizarre, but possible (short, precision override)",
+ u"compact-short usage/road measure-unit/length-meter @#",
+ u"compact-short usage/road unit/meter @#",
+ NumberFormatter::with()
+ .unit(METER)
+ .usage("road")
+ .notation(Notation::compactShort())
+ .precision(Precision::maxSignificantDigits(2)),
+ Locale("en-ZA"),
+ 987654321,
+ u"990K km");
+
+ assertFormatSingle(
+ u"Compact notation with Usage: unusual but possible (long)",
+ u"compact-long usage/road measure-unit/length-meter @#",
+ u"compact-long usage/road unit/meter @#",
+ NumberFormatter::with()
+ .unit(METER)
+ .usage("road")
+ .notation(Notation::compactLong())
+ .precision(Precision::maxSignificantDigits(2)),
+ Locale("en-ZA"),
+ 987654321,
+ u"990 thousand km");
+
+ assertFormatSingle(
+ u"Compact notation with Usage: unusual but possible (long, precision override)",
+ u"compact-long usage/road measure-unit/length-meter @#",
+ u"compact-long usage/road unit/meter @#",
+ NumberFormatter::with()
+ .unit(METER)
+ .usage("road")
+ .notation(Notation::compactLong())
+ .precision(Precision::maxSignificantDigits(2)),
+ Locale("en-ZA"),
+ 987654321,
+ u"990 thousand km");
+
+ assertFormatSingle(
+ u"Scientific notation, not recommended, requires precision override for road",
+ u"scientific usage/road measure-unit/length-meter",
+ u"scientific usage/road unit/meter",
+ NumberFormatter::with().unit(METER).usage("road").notation(Notation::scientific()),
+ Locale("en-ZA"),
+ 321.45,
+ // Rounding to the nearest "50" is not exponent-adjusted in scientific notation:
+ u"0E2 m");
+
+ assertFormatSingle(
+ u"Scientific notation with Usage: possible when using a reasonable Precision",
+ u"scientific usage/road measure-unit/length-meter @###",
+ u"scientific usage/road unit/meter @###",
+ NumberFormatter::with()
+ .unit(METER)
+ .usage("road")
+ .notation(Notation::scientific())
+ .precision(Precision::maxSignificantDigits(4)),
+ Locale("en-ZA"),
+ 321.45,
+ u"3,215E2 m");
+
+ assertFormatSingle(
+ u"Scientific notation with Usage: possible when using a reasonable Precision",
+ u"scientific usage/default measure-unit/length-astronomical-unit unit-width-full-name",
+ u"scientific usage/default unit/astronomical-unit unit-width-full-name",
+ NumberFormatter::with()
+ .unit(MeasureUnit::forIdentifier("astronomical-unit", status))
+ .usage("default")
+ .notation(Notation::scientific())
+ .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
+ Locale("en-ZA"),
+ 1e20,
+ u"1,5E28 kilometres");
+
+ status.assertSuccess();
+}
+
void NumberFormatterApiTest::unitCompoundMeasure() {
assertFormatDescending(
u"Meters Per Second Short (unit that simplifies) and perUnit method",