Merge pull request #61 from icu-units/output_skeletons

Implement Precision handling in UsagePrefsHandler::processQuantity
This commit is contained in:
Hugo van der Merwe 2020-08-27 01:05:37 +02:00 committed by GitHub
commit 16547f3298
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 488 additions and 226 deletions

View file

@ -219,6 +219,7 @@
<ClCompile Include="number_multiplier.cpp" />
<ClCompile Include="number_currencysymbols.cpp" />
<ClCompile Include="number_skeletons.cpp" />
<ClCompile Include="number_symbolswrapper.cpp" />
<ClCompile Include="number_capi.cpp" />
<ClCompile Include="string_segment.cpp" />
<ClCompile Include="numparse_parsednumber.cpp" />
@ -487,13 +488,14 @@
<ClInclude Include="number_scientific.h" />
<ClInclude Include="formatted_string_builder.h" />
<ClInclude Include="number_types.h" />
<ClCompile Include="number_usageprefs.h" />
<ClInclude Include="number_usageprefs.h" />
<ClInclude Include="number_utypes.h" />
<ClInclude Include="number_utils.h" />
<ClInclude Include="number_mapper.h" />
<ClInclude Include="number_multiplier.h" />
<ClInclude Include="number_currencysymbols.h" />
<ClInclude Include="number_skeletons.h" />
<ClInclude Include="number_symbolswrapper.h" />
<ClInclude Include="string_segment.h" />
<ClInclude Include="numparse_impl.h" />
<ClInclude Include="numparse_symbols.h" />

View file

@ -591,6 +591,9 @@
<ClCompile Include="formatted_string_builder.cpp">
<Filter>formatting</Filter>
</ClCompile>
<ClCompile Include="number_usageprefs.cpp">
<Filter>formatting</Filter>
</ClCompile>
<ClCompile Include="number_utils.cpp">
<Filter>formatting</Filter>
</ClCompile>
@ -606,6 +609,9 @@
<ClCompile Include="number_skeletons.cpp">
<Filter>formatting</Filter>
</ClCompile>
<ClCompile Include="number_symbolswrapper.cpp">
<Filter>formatting</Filter>
</ClCompile>
<ClCompile Include="number_capi.cpp">
<Filter>formatting</Filter>
</ClCompile>
@ -917,6 +923,9 @@
<ClInclude Include="number_utypes.h">
<Filter>formatting</Filter>
</ClInclude>
<ClInclude Include="number_usageprefs.h">
<Filter>formatting</Filter>
</ClInclude>
<ClInclude Include="number_utils.h">
<Filter>formatting</Filter>
</ClInclude>
@ -932,6 +941,9 @@
<ClInclude Include="number_skeletons.h">
<Filter>formatting</Filter>
</ClInclude>
<ClInclude Include="number_symbolswrapper.h">
<Filter>formatting</Filter>
</ClInclude>
<ClInclude Include="string_segment.h">
<Filter>formatting</Filter>
</ClInclude>

View file

@ -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();

View file

@ -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;

View file

@ -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) {

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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 &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);
}
}
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<digits_t>(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<digits_t>(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;
}

View file

@ -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;
};

View file

@ -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));

View file

@ -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

View file

@ -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;
}

View file

@ -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

View file

@ -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 */

View file

@ -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

View file

@ -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

View file

@ -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;
};

View file

@ -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<Measure> 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<Measure> measures, UnicodeString precision)

View file

@ -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

View file

@ -56,6 +56,7 @@ class NumberFormatterApiTest : public IntlTestWithFieldPosition {
void unitCompoundMeasure();
void unitUsage();
void unitUsageErrorCodes();
void unitUsageSkeletons();
void unitCurrency();
void unitPercent();
void percentParity();

View file

@ -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",