mirror of
https://github.com/unicode-org/icu.git
synced 2025-04-06 14:05:32 +00:00
ICU-20568 Implement Usage "Glue Code" and skeleton support. Tests.
CLDR-13488: Samples of intermediate source/data/misc/units.txt PR: https://github.com/icu-units/icu/pull/5 Commit:08132e7a33
Unit test improvements (the rest of PR already upstreamed). PR: https://github.com/sffc/icu/pull/40 Commit:e92a2fcffb
Use namespace ::icu::units::impl for units-related code. PR: https://github.com/icu-units/icu/pull/16 Commit:57b35a8a93
Export addSingleFactorConstant in header file PR: https://github.com/icu-units/icu/pull/17 Commit:f357b5558e
Fix MeasureUnit identifiers to be spec-compliant. PR: https://github.com/icu-units/icu/pull/18 Commit:69c087154b
Cleanup: IWYU headers (with some exceptions), SigNum -> Signum PR: https://github.com/icu-units/icu/pull/19 Commit:1c29a6bcc7
Test that addSingleFactorConstant knowns all unitConstants. PR: https://github.com/icu-units/icu/pull/20 Commit:6c0c3bb640
Add Unit Usage support to Number Skeletons. PR: https://github.com/icu-units/icu/pull/33 Commit:3cf23222ca
Fix some compiler warnings PR: https://github.com/icu-units/icu/pull/43 Commit:be11edb0bd
This commit is contained in:
parent
72056d4df2
commit
7ed2a2d233
33 changed files with 1094 additions and 398 deletions
|
@ -6,15 +6,17 @@
|
|||
#if !UCONFIG_NO_FORMATTING
|
||||
|
||||
#include <math.h>
|
||||
#include <utility>
|
||||
|
||||
#include "cmemory.h"
|
||||
#include "complexunitsconverter.h"
|
||||
#include "uassert.h"
|
||||
#include "unicode/fmtable.h"
|
||||
#include "unicode/localpointer.h"
|
||||
#include "unicode/measure.h"
|
||||
#include "unitconverter.h"
|
||||
|
||||
U_NAMESPACE_BEGIN
|
||||
namespace units {
|
||||
|
||||
ComplexUnitsConverter::ComplexUnitsConverter(const MeasureUnit &inputUnit,
|
||||
const MeasureUnit &outputUnits,
|
||||
|
@ -123,6 +125,7 @@ MaybeStackVector<Measure> ComplexUnitsConverter::convert(double quantity, UError
|
|||
return result;
|
||||
}
|
||||
|
||||
} // namespace units
|
||||
U_NAMESPACE_END
|
||||
|
||||
#endif /* #if !UCONFIG_NO_FORMATTING */
|
||||
|
|
|
@ -8,14 +8,17 @@
|
|||
#define __COMPLEXUNITSCONVERTER_H__
|
||||
|
||||
#include "cmemory.h"
|
||||
#include "unicode/errorcode.h"
|
||||
#include "unicode/measunit.h"
|
||||
#include "unicode/measure.h"
|
||||
#include "unitconverter.h"
|
||||
#include "unitsdata.h"
|
||||
|
||||
U_NAMESPACE_BEGIN
|
||||
|
||||
// Forward declarations
|
||||
class Measure;
|
||||
|
||||
namespace units {
|
||||
|
||||
/**
|
||||
* Converts from single or compound unit to single, compound or mixed units.
|
||||
* For example, from `meter` to `foot+inch`.
|
||||
|
@ -60,6 +63,7 @@ class U_I18N_API ComplexUnitsConverter {
|
|||
MaybeStackVector<MeasureUnit> units_;
|
||||
};
|
||||
|
||||
} // namespace units
|
||||
U_NAMESPACE_END
|
||||
|
||||
#endif //__COMPLEXUNITSCONVERTER_H__
|
||||
|
|
|
@ -25,7 +25,7 @@ class FormattedValueStringBuilderImpl;
|
|||
*
|
||||
* <ol>
|
||||
* <li>Efficient prepend as well as append.
|
||||
* <li>Keeps tracks of Fields in an efficient manner.
|
||||
* <li>Keeps track of Fields in an efficient manner.
|
||||
* </ol>
|
||||
*
|
||||
* See also FormattedValueStringBuilderImpl.
|
||||
|
|
|
@ -196,6 +196,7 @@
|
|||
<ClCompile Include="number_rounding.cpp" />
|
||||
<ClCompile Include="number_scientific.cpp" />
|
||||
<ClCompile Include="formatted_string_builder.cpp" />
|
||||
<ClCompile Include="number_usageprefs.cpp" />
|
||||
<ClCompile Include="number_utils.cpp" />
|
||||
<ClCompile Include="number_mapper.cpp" />
|
||||
<ClCompile Include="number_multiplier.cpp" />
|
||||
|
@ -469,6 +470,7 @@
|
|||
<ClInclude Include="number_scientific.h" />
|
||||
<ClInclude Include="formatted_string_builder.h" />
|
||||
<ClInclude Include="number_types.h" />
|
||||
<ClCompile Include="number_usageprefs.h" />
|
||||
<ClInclude Include="number_utypes.h" />
|
||||
<ClInclude Include="number_utils.h" />
|
||||
<ClInclude Include="number_mapper.h" />
|
||||
|
|
|
@ -429,11 +429,13 @@
|
|||
<ClCompile Include="number_rounding.cpp" />
|
||||
<ClCompile Include="number_scientific.cpp" />
|
||||
<ClCompile Include="formatted_string_builder.cpp" />
|
||||
<ClCompile Include="number_usageprefs.cpp" />
|
||||
<ClCompile Include="number_utils.cpp" />
|
||||
<ClCompile Include="number_mapper.cpp" />
|
||||
<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" />
|
||||
|
@ -700,12 +702,14 @@
|
|||
<ClInclude Include="number_scientific.h" />
|
||||
<ClInclude Include="formatted_string_builder.h" />
|
||||
<ClInclude Include="number_types.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" />
|
||||
|
|
|
@ -20,7 +20,7 @@ namespace impl {
|
|||
class DecNum;
|
||||
|
||||
/**
|
||||
* An class for representing a number to be processed by the decimal formatting pipeline. Includes
|
||||
* A class for representing a number to be processed by the decimal formatting pipeline. Includes
|
||||
* methods for rounding, plural rules, and decimal digit extraction.
|
||||
*
|
||||
* <p>By design, this is NOT IMMUTABLE and NOT THREAD SAFE. It is intended to be an intermediate
|
||||
|
@ -217,7 +217,13 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory {
|
|||
|
||||
DecimalQuantity &setToDouble(double n);
|
||||
|
||||
/** decNumber is similar to BigDecimal in Java. */
|
||||
/**
|
||||
* Produces a DecimalQuantity that was parsed from a string by the decNumber
|
||||
* C Library.
|
||||
*
|
||||
* decNumber is similar to BigDecimal in Java, and supports parsing strings
|
||||
* such as "123.456621E+40".
|
||||
*/
|
||||
DecimalQuantity &setToDecNumber(StringPiece n, UErrorCode& status);
|
||||
|
||||
/** Internal method if the caller already has a DecNum. */
|
||||
|
|
|
@ -274,6 +274,20 @@ Derived NumberFormatterSettings<Derived>::scale(const Scale& scale)&& {
|
|||
return move;
|
||||
}
|
||||
|
||||
template<typename Derived>
|
||||
Derived NumberFormatterSettings<Derived>::usage(const StringPiece usage) const& {
|
||||
Derived copy(*this);
|
||||
copy.fMacros.usage.set(usage);
|
||||
return copy;
|
||||
}
|
||||
|
||||
template<typename Derived>
|
||||
Derived NumberFormatterSettings<Derived>::usage(const StringPiece usage)&& {
|
||||
Derived move(std::move(*this));
|
||||
move.fMacros.usage.set(usage);
|
||||
return move;
|
||||
}
|
||||
|
||||
template<typename Derived>
|
||||
Derived NumberFormatterSettings<Derived>::padding(const Padder& padder) const& {
|
||||
Derived copy(*this);
|
||||
|
@ -702,9 +716,9 @@ LocalizedNumberFormatter::formatDecimalQuantity(const DecimalQuantity& dq, UErro
|
|||
|
||||
void LocalizedNumberFormatter::formatImpl(impl::UFormattedNumberData* results, UErrorCode& status) const {
|
||||
if (computeCompiled(status)) {
|
||||
fCompiled->format(results->quantity, results->getStringRef(), status);
|
||||
fCompiled->format(results, status);
|
||||
} else {
|
||||
NumberFormatterImpl::formatStatic(fMacros, results->quantity, results->getStringRef(), status);
|
||||
NumberFormatterImpl::formatStatic(fMacros, results, status);
|
||||
}
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
|
|
|
@ -32,13 +32,16 @@ NumberFormatterImpl::NumberFormatterImpl(const MacroProps& macros, UErrorCode& s
|
|||
: NumberFormatterImpl(macros, true, status) {
|
||||
}
|
||||
|
||||
int32_t NumberFormatterImpl::formatStatic(const MacroProps& macros, DecimalQuantity& inValue,
|
||||
FormattedStringBuilder& outString, UErrorCode& status) {
|
||||
int32_t NumberFormatterImpl::formatStatic(const MacroProps ¯os, UFormattedNumberData *results,
|
||||
UErrorCode &status) {
|
||||
DecimalQuantity &inValue = results->quantity;
|
||||
FormattedStringBuilder &outString = results->getStringRef();
|
||||
NumberFormatterImpl impl(macros, false, status);
|
||||
MicroProps& micros = impl.preProcessUnsafe(inValue, status);
|
||||
if (U_FAILURE(status)) { return 0; }
|
||||
int32_t length = writeNumber(micros, inValue, outString, 0, status);
|
||||
length += writeAffixes(micros, outString, 0, length, status);
|
||||
results->outputUnit = std::move(micros.outputUnit);
|
||||
return length;
|
||||
}
|
||||
|
||||
|
@ -54,13 +57,15 @@ int32_t NumberFormatterImpl::getPrefixSuffixStatic(const MacroProps& macros, Sig
|
|||
// The "unsafe" method simply re-uses fMicros, eliminating the extra copy operation.
|
||||
// See MicroProps::processQuantity() for details.
|
||||
|
||||
int32_t NumberFormatterImpl::format(DecimalQuantity& inValue, FormattedStringBuilder& outString,
|
||||
UErrorCode& status) const {
|
||||
int32_t NumberFormatterImpl::format(UFormattedNumberData *results, UErrorCode &status) const {
|
||||
DecimalQuantity &inValue = results->quantity;
|
||||
FormattedStringBuilder &outString = results->getStringRef();
|
||||
MicroProps micros;
|
||||
preProcess(inValue, micros, status);
|
||||
if (U_FAILURE(status)) { return 0; }
|
||||
int32_t length = writeNumber(micros, inValue, outString, 0, status);
|
||||
length += writeAffixes(micros, outString, 0, length, status);
|
||||
results->outputUnit = std::move(micros.outputUnit);
|
||||
return length;
|
||||
}
|
||||
|
||||
|
@ -233,6 +238,19 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe,
|
|||
/// START POPULATING THE DEFAULT MICROPROPS AND BUILDING THE MICROPROPS GENERATOR ///
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Unit Preferences and Conversions as our first step
|
||||
if (macros.usage.isSet()) {
|
||||
if (!isCldrUnit) {
|
||||
// We only support "usage" when the input unit is a CLDR Unit.
|
||||
status = U_ILLEGAL_ARGUMENT_ERROR;
|
||||
return nullptr;
|
||||
}
|
||||
auto usagePrefsHandler =
|
||||
new UsagePrefsHandler(macros.locale, macros.unit, macros.usage.fUsage, chain, status);
|
||||
fUsagePrefsHandler.adoptInsteadAndCheckErrorCode(usagePrefsHandler, status);
|
||||
chain = fUsagePrefsHandler.getAlias();
|
||||
}
|
||||
|
||||
// Multiplier
|
||||
if (macros.scale.isValid()) {
|
||||
fMicros.helpers.multiplier.setAndChain(macros.scale, chain);
|
||||
|
@ -341,7 +359,8 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe,
|
|||
patternModifier->setSymbols(fMicros.symbols, currency, unitWidth, nullptr, status);
|
||||
}
|
||||
if (safe) {
|
||||
fImmutablePatternModifier.adoptInstead(patternModifier->createImmutable(status));
|
||||
fImmutablePatternModifier.adoptInsteadAndCheckErrorCode(patternModifier->createImmutable(status),
|
||||
status);
|
||||
}
|
||||
if (U_FAILURE(status)) {
|
||||
return nullptr;
|
||||
|
@ -349,24 +368,26 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe,
|
|||
|
||||
// Outer modifier (CLDR units and currency long names)
|
||||
if (isCldrUnit) {
|
||||
fLongNameHandler.adoptInstead(
|
||||
LongNameHandler::forMeasureUnit(
|
||||
macros.locale,
|
||||
macros.unit,
|
||||
macros.perUnit,
|
||||
unitWidth,
|
||||
resolvePluralRules(macros.rules, macros.locale, status),
|
||||
chain,
|
||||
status));
|
||||
chain = fLongNameHandler.getAlias();
|
||||
if (macros.usage.isSet()) {
|
||||
fLongNameMultiplexer.adoptInsteadAndCheckErrorCode(
|
||||
LongNameMultiplexer::forMeasureUnits(
|
||||
macros.locale, *fUsagePrefsHandler->getOutputUnits(), unitWidth,
|
||||
resolvePluralRules(macros.rules, macros.locale, status), chain, status),
|
||||
status);
|
||||
chain = fLongNameMultiplexer.getAlias();
|
||||
} else {
|
||||
fLongNameHandler.adoptInsteadAndCheckErrorCode(new LongNameHandler(), status);
|
||||
LongNameHandler::forMeasureUnit(macros.locale, macros.unit, macros.perUnit, unitWidth,
|
||||
resolvePluralRules(macros.rules, macros.locale, status),
|
||||
chain, fLongNameHandler.getAlias(), status);
|
||||
chain = fLongNameHandler.getAlias();
|
||||
}
|
||||
} else if (isCurrency && unitWidth == UNUM_UNIT_WIDTH_FULL_NAME) {
|
||||
fLongNameHandler.adoptInstead(
|
||||
LongNameHandler::forCurrencyLongNames(
|
||||
macros.locale,
|
||||
currency,
|
||||
resolvePluralRules(macros.rules, macros.locale, status),
|
||||
chain,
|
||||
status));
|
||||
fLongNameHandler.adoptInsteadAndCheckErrorCode(
|
||||
LongNameHandler::forCurrencyLongNames(
|
||||
macros.locale, currency, resolvePluralRules(macros.rules, macros.locale, status), chain,
|
||||
status),
|
||||
status);
|
||||
chain = fLongNameHandler.getAlias();
|
||||
} else {
|
||||
// No outer modifier required
|
||||
|
@ -390,6 +411,9 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe,
|
|||
safe,
|
||||
chain,
|
||||
status);
|
||||
if (U_FAILURE(status)) {
|
||||
return nullptr;
|
||||
}
|
||||
if (newCompactHandler == nullptr) {
|
||||
status = U_MEMORY_ALLOCATION_ERROR;
|
||||
return nullptr;
|
||||
|
|
|
@ -10,11 +10,13 @@
|
|||
#include "number_types.h"
|
||||
#include "formatted_string_builder.h"
|
||||
#include "number_patternstring.h"
|
||||
#include "number_usageprefs.h"
|
||||
#include "number_utils.h"
|
||||
#include "number_patternmodifier.h"
|
||||
#include "number_longnames.h"
|
||||
#include "number_compact.h"
|
||||
#include "number_microprops.h"
|
||||
#include "number_utypes.h"
|
||||
|
||||
U_NAMESPACE_BEGIN namespace number {
|
||||
namespace impl {
|
||||
|
@ -34,9 +36,8 @@ class NumberFormatterImpl : public UMemory {
|
|||
/**
|
||||
* Builds and evaluates an "unsafe" MicroPropsGenerator, which is cheaper but can be used only once.
|
||||
*/
|
||||
static int32_t
|
||||
formatStatic(const MacroProps ¯os, DecimalQuantity &inValue, FormattedStringBuilder &outString,
|
||||
UErrorCode &status);
|
||||
static int32_t formatStatic(const MacroProps ¯os, UFormattedNumberData *results,
|
||||
UErrorCode &status);
|
||||
|
||||
/**
|
||||
* Prints only the prefix and suffix; used for DecimalFormat getters.
|
||||
|
@ -51,7 +52,7 @@ class NumberFormatterImpl : public UMemory {
|
|||
/**
|
||||
* Evaluates the "safe" MicroPropsGenerator created by "fromMacros".
|
||||
*/
|
||||
int32_t format(DecimalQuantity& inValue, FormattedStringBuilder& outString, UErrorCode& status) const;
|
||||
int32_t format(UFormattedNumberData *results, UErrorCode &status) const;
|
||||
|
||||
/**
|
||||
* Like format(), but saves the result into an output MicroProps without additional processing.
|
||||
|
@ -82,7 +83,9 @@ class NumberFormatterImpl : public UMemory {
|
|||
int32_t end, UErrorCode& status);
|
||||
|
||||
private:
|
||||
// Head of the MicroPropsGenerator linked list:
|
||||
// Head of the MicroPropsGenerator linked list. Subclasses' processQuantity
|
||||
// methods process this list in a parent-first order, such that the last
|
||||
// item added, which this points to, typically has its logic executed last.
|
||||
const MicroPropsGenerator *fMicroPropsGenerator = nullptr;
|
||||
|
||||
// Tail of the list:
|
||||
|
@ -90,13 +93,15 @@ class NumberFormatterImpl : public UMemory {
|
|||
|
||||
// Other fields possibly used by the number formatting pipeline:
|
||||
// TODO: Convert more of these LocalPointers to value objects to reduce the number of news?
|
||||
LocalPointer<const UsagePrefsHandler> fUsagePrefsHandler;
|
||||
LocalPointer<const DecimalFormatSymbols> fSymbols;
|
||||
LocalPointer<const PluralRules> fRules;
|
||||
LocalPointer<const ParsedPatternInfo> fPatternInfo;
|
||||
LocalPointer<const ScientificHandler> fScientificHandler;
|
||||
LocalPointer<MutablePatternModifier> fPatternModifier;
|
||||
LocalPointer<ImmutablePatternModifier> fImmutablePatternModifier;
|
||||
LocalPointer<const LongNameHandler> fLongNameHandler;
|
||||
LocalPointer<LongNameHandler> fLongNameHandler;
|
||||
LocalPointer<const LongNameMultiplexer> fLongNameMultiplexer;
|
||||
LocalPointer<const CompactHandler> fCompactHandler;
|
||||
|
||||
// Value objects possibly used by the number formatting pipeline:
|
||||
|
|
|
@ -22,7 +22,21 @@ using namespace icu::number::impl;
|
|||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* Display Name (this format has no placeholder).
|
||||
*
|
||||
* Used as an index into the LongNameHandler::simpleFormats array. Units
|
||||
* resources cover the normal set of PluralRules keys, as well as `dnam` and
|
||||
* `per` forms.
|
||||
*/
|
||||
constexpr int32_t DNAM_INDEX = StandardPlural::Form::COUNT;
|
||||
/**
|
||||
* "per" form (e.g. "{0} per day" is day's "per" form).
|
||||
*
|
||||
* Used as an index into the LongNameHandler::simpleFormats array. Units
|
||||
* resources cover the normal set of PluralRules keys, as well as `dnam` and
|
||||
* `per` forms.
|
||||
*/
|
||||
constexpr int32_t PER_INDEX = StandardPlural::Form::COUNT + 1;
|
||||
constexpr int32_t ARRAY_LENGTH = StandardPlural::Form::COUNT + 2;
|
||||
|
||||
|
@ -87,6 +101,12 @@ class PluralTableSink : public ResourceSink {
|
|||
|
||||
// NOTE: outArray MUST have room for all StandardPlural values. No bounds checking is performed.
|
||||
|
||||
// Populates outArray with `locale`-specific values for `unit` through use of
|
||||
// PluralTableSink, reading from resources *unitsNarrow* and *unitsShort* (for
|
||||
// width UNUM_UNIT_WIDTH_NARROW), or just *unitsShort* (for width
|
||||
// UNUM_UNIT_WIDTH_SHORT). For other widths, it would read just "units".
|
||||
//
|
||||
// outArray must be of fixed length ARRAY_LENGTH.
|
||||
void getMeasureData(const Locale &locale, const MeasureUnit &unit, const UNumberUnitWidth &width,
|
||||
UnicodeString *outArray, UErrorCode &status) {
|
||||
PluralTableSink sink(outArray);
|
||||
|
@ -184,14 +204,19 @@ UnicodeString getPerUnitFormat(const Locale& locale, const UNumberUnitWidth &wid
|
|||
|
||||
} // namespace
|
||||
|
||||
LongNameHandler*
|
||||
LongNameHandler::forMeasureUnit(const Locale &loc, const MeasureUnit &unitRef, const MeasureUnit &perUnit,
|
||||
const UNumberUnitWidth &width, const PluralRules *rules,
|
||||
const MicroPropsGenerator *parent, UErrorCode &status) {
|
||||
// TODO(units,hugovdm): deal properly with "perUnit" parameter here:
|
||||
void LongNameHandler::forMeasureUnit(const Locale &loc, const MeasureUnit &unitRef,
|
||||
const MeasureUnit &perUnit, const UNumberUnitWidth &width,
|
||||
const PluralRules *rules, const MicroPropsGenerator *parent,
|
||||
LongNameHandler *fillIn, UErrorCode &status) {
|
||||
if (fillIn == nullptr) {
|
||||
status = U_INTERNAL_PROGRAM_ERROR;
|
||||
return;
|
||||
}
|
||||
if (uprv_strlen(unitRef.getType()) == 0 || uprv_strlen(perUnit.getType()) == 0) {
|
||||
// TODO(ICU-20941): Unsanctioned unit. Not yet fully supported. Set an error code.
|
||||
status = U_UNSUPPORTED_ERROR;
|
||||
return nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
MeasureUnit unit = unitRef;
|
||||
|
@ -203,59 +228,75 @@ LongNameHandler::forMeasureUnit(const Locale &loc, const MeasureUnit &unitRef, c
|
|||
unit = resolved;
|
||||
} else {
|
||||
// No simplified form is available.
|
||||
return forCompoundUnit(loc, unit, perUnit, width, rules, parent, status);
|
||||
forCompoundUnit(loc, unit, perUnit, width, rules, parent, fillIn, status);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto* result = new LongNameHandler(rules, parent);
|
||||
if (result == nullptr) {
|
||||
status = U_MEMORY_ALLOCATION_ERROR;
|
||||
return nullptr;
|
||||
}
|
||||
UnicodeString simpleFormats[ARRAY_LENGTH];
|
||||
getMeasureData(loc, unit, width, simpleFormats, status);
|
||||
if (U_FAILURE(status)) { return result; }
|
||||
result->simpleFormatsToModifiers(simpleFormats, {UFIELD_CATEGORY_NUMBER, UNUM_MEASURE_UNIT_FIELD}, status);
|
||||
return result;
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
fillIn->rules = rules;
|
||||
fillIn->parent = parent;
|
||||
fillIn->simpleFormatsToModifiers(simpleFormats, {UFIELD_CATEGORY_NUMBER, UNUM_MEASURE_UNIT_FIELD},
|
||||
status);
|
||||
}
|
||||
|
||||
LongNameHandler*
|
||||
LongNameHandler::forCompoundUnit(const Locale &loc, const MeasureUnit &unit, const MeasureUnit &perUnit,
|
||||
const UNumberUnitWidth &width, const PluralRules *rules,
|
||||
const MicroPropsGenerator *parent, UErrorCode &status) {
|
||||
auto* result = new LongNameHandler(rules, parent);
|
||||
if (result == nullptr) {
|
||||
status = U_MEMORY_ALLOCATION_ERROR;
|
||||
return nullptr;
|
||||
// TODO(units,hugovdm): deal properly with "perUnit" parameter here:
|
||||
void LongNameHandler::forCompoundUnit(const Locale &loc, const MeasureUnit &unit,
|
||||
const MeasureUnit &perUnit, const UNumberUnitWidth &width,
|
||||
const PluralRules *rules, const MicroPropsGenerator *parent,
|
||||
LongNameHandler *fillIn, UErrorCode &status) {
|
||||
if (fillIn == nullptr) {
|
||||
status = U_INTERNAL_PROGRAM_ERROR;
|
||||
return;
|
||||
}
|
||||
UnicodeString primaryData[ARRAY_LENGTH];
|
||||
getMeasureData(loc, unit, width, primaryData, status);
|
||||
if (U_FAILURE(status)) { return result; }
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
UnicodeString secondaryData[ARRAY_LENGTH];
|
||||
getMeasureData(loc, perUnit, width, secondaryData, status);
|
||||
if (U_FAILURE(status)) { return result; }
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
UnicodeString perUnitFormat;
|
||||
if (!secondaryData[PER_INDEX].isBogus()) {
|
||||
perUnitFormat = secondaryData[PER_INDEX];
|
||||
} else {
|
||||
UnicodeString rawPerUnitFormat = getPerUnitFormat(loc, width, status);
|
||||
if (U_FAILURE(status)) { return result; }
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
// rawPerUnitFormat is something like "{0}/{1}"; we need to substitute in the secondary unit.
|
||||
SimpleFormatter compiled(rawPerUnitFormat, 2, 2, status);
|
||||
if (U_FAILURE(status)) { return result; }
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
UnicodeString secondaryFormat = getWithPlural(secondaryData, StandardPlural::Form::ONE, status);
|
||||
if (U_FAILURE(status)) { return result; }
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
// Some "one" pattern may not contain "{0}". For example in "ar" or "ne" locale.
|
||||
SimpleFormatter secondaryCompiled(secondaryFormat, 0, 1, status);
|
||||
if (U_FAILURE(status)) { return result; }
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
UnicodeString secondaryString = secondaryCompiled.getTextWithNoArguments().trim();
|
||||
// TODO: Why does UnicodeString need to be explicit in the following line?
|
||||
compiled.format(UnicodeString(u"{0}"), secondaryString, perUnitFormat, status);
|
||||
if (U_FAILURE(status)) { return result; }
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
result->multiSimpleFormatsToModifiers(primaryData, perUnitFormat, {UFIELD_CATEGORY_NUMBER, UNUM_MEASURE_UNIT_FIELD}, status);
|
||||
return result;
|
||||
fillIn->rules = rules;
|
||||
fillIn->parent = parent;
|
||||
fillIn->multiSimpleFormatsToModifiers(primaryData, perUnitFormat,
|
||||
{UFIELD_CATEGORY_NUMBER, UNUM_MEASURE_UNIT_FIELD}, status);
|
||||
}
|
||||
|
||||
UnicodeString LongNameHandler::getUnitDisplayName(
|
||||
|
@ -338,7 +379,9 @@ void LongNameHandler::multiSimpleFormatsToModifiers(const UnicodeString *leadFor
|
|||
|
||||
void LongNameHandler::processQuantity(DecimalQuantity &quantity, MicroProps µs,
|
||||
UErrorCode &status) const {
|
||||
parent->processQuantity(quantity, micros, status);
|
||||
if (parent != NULL) {
|
||||
parent->processQuantity(quantity, micros, status);
|
||||
}
|
||||
StandardPlural::Form pluralForm = utils::getPluralSafe(micros.rounder, rules, quantity, status);
|
||||
micros.modOuter = &fModifiers[pluralForm];
|
||||
}
|
||||
|
@ -347,4 +390,49 @@ const Modifier* LongNameHandler::getModifier(Signum /*signum*/, StandardPlural::
|
|||
return &fModifiers[plural];
|
||||
}
|
||||
|
||||
LongNameMultiplexer *
|
||||
LongNameMultiplexer::forMeasureUnits(const Locale &loc, const MaybeStackVector<MeasureUnit> &units,
|
||||
const UNumberUnitWidth &width, const PluralRules *rules,
|
||||
const MicroPropsGenerator *parent, UErrorCode &status) {
|
||||
LocalPointer<LongNameMultiplexer> result(new LongNameMultiplexer(parent), status);
|
||||
if (U_FAILURE(status)) {
|
||||
return nullptr;
|
||||
}
|
||||
U_ASSERT(units.length() > 0);
|
||||
result->fMeasureUnits.adoptInstead(new MeasureUnit[units.length()]);
|
||||
for (int32_t i = 0, length = units.length(); i < length; i++) {
|
||||
// Create empty new LongNameHandler:
|
||||
LongNameHandler *lnh =
|
||||
result->fLongNameHandlers.emplaceBackAndCheckErrorCode(status);
|
||||
result->fMeasureUnits[i] = *units[i];
|
||||
// Fill in LongNameHandler:
|
||||
LongNameHandler::forMeasureUnit(loc, *units[i],
|
||||
MeasureUnit(), // TODO(units): deal with COMPOUND and MIXED units
|
||||
width, rules, NULL, lnh, status);
|
||||
if (U_FAILURE(status)) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return result.orphan();
|
||||
}
|
||||
|
||||
void LongNameMultiplexer::processQuantity(DecimalQuantity &quantity, MicroProps µs,
|
||||
UErrorCode &status) const {
|
||||
// We call parent->processQuantity() from the Multiplexer, instead of
|
||||
// letting LongNameHandler handle it: we don't know which LongNameHandler to
|
||||
// call until we've called the parent!
|
||||
fParent->processQuantity(quantity, micros, status);
|
||||
|
||||
// Call the correct LongNameHandler based on outputUnit
|
||||
for (int i = 0; i < fLongNameHandlers.length(); i++) {
|
||||
if (fMeasureUnits[i] == micros.outputUnit) {
|
||||
fLongNameHandlers[i]->processQuantity(quantity, micros, status);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// We shouldn't receive any outputUnit for which we haven't already got a
|
||||
// LongNameHandler:
|
||||
status = U_INTERNAL_PROGRAM_ERROR;
|
||||
}
|
||||
|
||||
#endif /* #if !UCONFIG_NO_FORMATTING */
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#ifndef __NUMBER_LONGNAMES_H__
|
||||
#define __NUMBER_LONGNAMES_H__
|
||||
|
||||
#include "cmemory.h"
|
||||
#include "unicode/uversion.h"
|
||||
#include "number_utils.h"
|
||||
#include "number_modifiers.h"
|
||||
|
@ -33,10 +34,10 @@ class LongNameHandler : public MicroPropsGenerator, public ModifierStore, public
|
|||
forCurrencyLongNames(const Locale &loc, const CurrencyUnit ¤cy, const PluralRules *rules,
|
||||
const MicroPropsGenerator *parent, UErrorCode &status);
|
||||
|
||||
static LongNameHandler*
|
||||
forMeasureUnit(const Locale &loc, const MeasureUnit &unit, const MeasureUnit &perUnit,
|
||||
const UNumberUnitWidth &width, const PluralRules *rules,
|
||||
const MicroPropsGenerator *parent, UErrorCode &status);
|
||||
static void forMeasureUnit(const Locale &loc, const MeasureUnit &unit, const MeasureUnit &perUnit,
|
||||
const UNumberUnitWidth &width, const PluralRules *rules,
|
||||
const MicroPropsGenerator *parent, LongNameHandler *fillIn,
|
||||
UErrorCode &status);
|
||||
|
||||
void
|
||||
processQuantity(DecimalQuantity &quantity, MicroProps µs, UErrorCode &status) const U_OVERRIDE;
|
||||
|
@ -45,22 +46,63 @@ class LongNameHandler : public MicroPropsGenerator, public ModifierStore, public
|
|||
|
||||
private:
|
||||
SimpleModifier fModifiers[StandardPlural::Form::COUNT];
|
||||
// Not owned
|
||||
const PluralRules *rules;
|
||||
// Not owned
|
||||
const MicroPropsGenerator *parent;
|
||||
|
||||
LongNameHandler(const PluralRules *rules, const MicroPropsGenerator *parent)
|
||||
: rules(rules), parent(parent) {}
|
||||
: rules(rules), parent(parent) {
|
||||
}
|
||||
|
||||
static LongNameHandler*
|
||||
forCompoundUnit(const Locale &loc, const MeasureUnit &unit, const MeasureUnit &perUnit,
|
||||
const UNumberUnitWidth &width, const PluralRules *rules,
|
||||
const MicroPropsGenerator *parent, UErrorCode &status);
|
||||
LongNameHandler() : rules(nullptr), parent(nullptr) {
|
||||
}
|
||||
|
||||
friend class MemoryPool<LongNameHandler>; // To enable emplaceBack();
|
||||
friend class NumberFormatterImpl;
|
||||
|
||||
static void forCompoundUnit(const Locale &loc, const MeasureUnit &unit, const MeasureUnit &perUnit,
|
||||
const UNumberUnitWidth &width, const PluralRules *rules,
|
||||
const MicroPropsGenerator *parent, LongNameHandler *fillIn,
|
||||
UErrorCode &status);
|
||||
|
||||
void simpleFormatsToModifiers(const UnicodeString *simpleFormats, Field field, UErrorCode &status);
|
||||
void multiSimpleFormatsToModifiers(const UnicodeString *leadFormats, UnicodeString trailFormat,
|
||||
Field field, UErrorCode &status);
|
||||
};
|
||||
|
||||
const int MAX_PREFS_COUNT = 10;
|
||||
|
||||
/**
|
||||
* A MicroPropsGenerator that multiplexes between different LongNameHandlers,
|
||||
* depending on the outputUnit (micros.helpers.outputUnit should be set earlier
|
||||
* in the chain).
|
||||
*/
|
||||
class LongNameMultiplexer : public MicroPropsGenerator, public UMemory {
|
||||
public:
|
||||
// FIXME: docstring?
|
||||
static LongNameMultiplexer *forMeasureUnits(const Locale &loc,
|
||||
const MaybeStackVector<MeasureUnit> &units,
|
||||
const UNumberUnitWidth &width, const PluralRules *rules,
|
||||
const MicroPropsGenerator *parent, UErrorCode &status);
|
||||
|
||||
void processQuantity(DecimalQuantity &quantity, MicroProps µs,
|
||||
UErrorCode &status) const U_OVERRIDE;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Because we only know which LongNameHandler we wish to call after calling
|
||||
* earlier MicroPropsGenerators in the chain, LongNameMultiplexer keeps the
|
||||
* parent link, while the LongNameHandlers are given no parents.
|
||||
*/
|
||||
MaybeStackVector<LongNameHandler> fLongNameHandlers;
|
||||
LocalArray<MeasureUnit> fMeasureUnits;
|
||||
const MicroPropsGenerator *fParent;
|
||||
|
||||
LongNameMultiplexer(const MicroPropsGenerator *parent) : fParent(parent) {
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace impl
|
||||
} // namespace number
|
||||
U_NAMESPACE_END
|
||||
|
|
|
@ -22,6 +22,11 @@
|
|||
U_NAMESPACE_BEGIN namespace number {
|
||||
namespace impl {
|
||||
|
||||
// TODO(units): generated by MicroPropsGenerator, but inherits from it too. Do
|
||||
// we want to better document why? There's an explanation for processQuantity:
|
||||
// * As MicroProps is the "base instance", this implementation of
|
||||
// * MicroPropsGenerator::processQuantity() just ensures that the output
|
||||
// * `micros` is correctly initialized.
|
||||
struct MicroProps : public MicroPropsGenerator {
|
||||
|
||||
// NOTE: All of these fields are properly initialized in NumberFormatterImpl.
|
||||
|
@ -49,6 +54,8 @@ struct MicroProps : public MicroPropsGenerator {
|
|||
MultiplierFormatHandler multiplier;
|
||||
} helpers;
|
||||
|
||||
// The MeasureUnit with which the output measurement is represented.
|
||||
MeasureUnit outputUnit;
|
||||
|
||||
MicroProps() = default;
|
||||
|
||||
|
@ -56,7 +63,23 @@ struct MicroProps : public MicroPropsGenerator {
|
|||
|
||||
MicroProps& operator=(const MicroProps& other) = default;
|
||||
|
||||
void processQuantity(DecimalQuantity&, MicroProps& micros, UErrorCode& status) const U_OVERRIDE {
|
||||
/**
|
||||
* As MicroProps is the "base instance", this implementation of
|
||||
* MicroPropsGenerator::processQuantity() just ensures that the output
|
||||
* `micros` is correctly initialized.
|
||||
*
|
||||
* For the "safe" invocation of this function, micros must not be *this,
|
||||
* such that a copy of the base instance is made. For the "unsafe" path,
|
||||
* this function can be used only once, because the base MicroProps instance
|
||||
* will be modified and thus not be available for re-use.
|
||||
*
|
||||
* @param quantity The quantity for consideration and optional mutation.
|
||||
* @param micros The MicroProps instance to populate. If this parameter is
|
||||
* not already `*this`, it will be overwritten with a copy of `*this`.
|
||||
*/
|
||||
void processQuantity(DecimalQuantity &quantity, MicroProps µs,
|
||||
UErrorCode &status) const U_OVERRIDE {
|
||||
(void) quantity;
|
||||
(void) status;
|
||||
if (this == µs) {
|
||||
// Unsafe path: no need to perform a copy.
|
||||
|
@ -65,6 +88,7 @@ struct MicroProps : public MicroPropsGenerator {
|
|||
U_ASSERT(exhausted);
|
||||
} else {
|
||||
// Safe path: copy self into the output micros.
|
||||
U_ASSERT(!exhausted);
|
||||
micros = *this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#if !UCONFIG_NO_FORMATTING
|
||||
|
||||
#include "unicode/measunit.h"
|
||||
#include "unicode/numberformatter.h"
|
||||
#include "number_utypes.h"
|
||||
#include "util.h"
|
||||
|
@ -32,6 +33,11 @@ void FormattedNumber::getAllFieldPositionsImpl(FieldPositionIteratorHandler& fpi
|
|||
fData->getAllFieldPositions(fpih, status);
|
||||
}
|
||||
|
||||
MeasureUnit FormattedNumber::getOutputUnit(UErrorCode& status) const {
|
||||
UPRV_FORMATTED_VALUE_METHOD_GUARD(MeasureUnit())
|
||||
return fData->outputUnit;
|
||||
}
|
||||
|
||||
void FormattedNumber::getDecimalQuantity(impl::DecimalQuantity& output, UErrorCode& status) const {
|
||||
UPRV_FORMATTED_VALUE_METHOD_GUARD(UPRV_NOARG)
|
||||
output = fData->quantity;
|
||||
|
|
|
@ -99,6 +99,7 @@ void U_CALLCONV initNumberSkeletons(UErrorCode& status) {
|
|||
b.add(u"measure-unit", STEM_MEASURE_UNIT, status);
|
||||
b.add(u"per-measure-unit", STEM_PER_MEASURE_UNIT, status);
|
||||
b.add(u"unit", STEM_UNIT, status);
|
||||
b.add(u"usage", STEM_UNIT_USAGE, status);
|
||||
b.add(u"currency", STEM_CURRENCY, status);
|
||||
b.add(u"integer-width", STEM_INTEGER_WIDTH, status);
|
||||
b.add(u"numbering-system", STEM_NUMBERING_SYSTEM, status);
|
||||
|
@ -559,6 +560,7 @@ MacroProps skeleton::parseSkeleton(
|
|||
case STATE_MEASURE_UNIT:
|
||||
case STATE_PER_MEASURE_UNIT:
|
||||
case STATE_IDENTIFIER_UNIT:
|
||||
case STATE_UNIT_USAGE:
|
||||
case STATE_CURRENCY_UNIT:
|
||||
case STATE_INTEGER_WIDTH:
|
||||
case STATE_NUMBERING_SYSTEM:
|
||||
|
@ -716,7 +718,7 @@ skeleton::parseStem(const StringSegment& segment, const UCharsTrie& stemTrie, Se
|
|||
macros.decimal = stem_to_object::decimalSeparatorDisplay(stem);
|
||||
return STATE_NULL;
|
||||
|
||||
// Stems requiring an option:
|
||||
// Stems requiring an option:
|
||||
|
||||
case STEM_PRECISION_INCREMENT:
|
||||
CHECK_NULL(seen, precision, status);
|
||||
|
@ -735,6 +737,10 @@ skeleton::parseStem(const StringSegment& segment, const UCharsTrie& stemTrie, Se
|
|||
CHECK_NULL(seen, perUnit, status);
|
||||
return STATE_IDENTIFIER_UNIT;
|
||||
|
||||
case STEM_UNIT_USAGE:
|
||||
CHECK_NULL(seen, usage, status);
|
||||
return STATE_UNIT_USAGE;
|
||||
|
||||
case STEM_CURRENCY:
|
||||
CHECK_NULL(seen, unit, status);
|
||||
return STATE_CURRENCY_UNIT;
|
||||
|
@ -774,6 +780,9 @@ ParseState skeleton::parseOption(ParseState stem, const StringSegment& segment,
|
|||
case STATE_IDENTIFIER_UNIT:
|
||||
blueprint_helpers::parseIdentifierUnitOption(segment, macros, status);
|
||||
return STATE_NULL;
|
||||
case STATE_UNIT_USAGE:
|
||||
blueprint_helpers::parseUnitUsageOption(segment, macros, status);
|
||||
return STATE_NULL;
|
||||
case STATE_INCREMENT_PRECISION:
|
||||
blueprint_helpers::parseIncrementOption(segment, macros, status);
|
||||
return STATE_NULL;
|
||||
|
@ -848,6 +857,10 @@ void GeneratorHelpers::generateSkeleton(const MacroProps& macros, UnicodeString&
|
|||
sb.append(u' ');
|
||||
}
|
||||
if (U_FAILURE(status)) { return; }
|
||||
if (GeneratorHelpers::usage(macros, sb, status)) {
|
||||
sb.append(u' ');
|
||||
}
|
||||
if (U_FAILURE(status)) { return; }
|
||||
if (GeneratorHelpers::precision(macros, sb, status)) {
|
||||
sb.append(u' ');
|
||||
}
|
||||
|
@ -1068,6 +1081,17 @@ void blueprint_helpers::parseIdentifierUnitOption(const StringSegment& segment,
|
|||
}
|
||||
}
|
||||
|
||||
void blueprint_helpers::parseUnitUsageOption(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);
|
||||
macros.usage.set(buffer.toStringPiece());
|
||||
// We do not do any validation of the usage string: it depends on the
|
||||
// unitPreferenceData in the units resources.
|
||||
}
|
||||
|
||||
void blueprint_helpers::parseFractionStem(const StringSegment& segment, MacroProps& macros,
|
||||
UErrorCode& status) {
|
||||
U_ASSERT(segment.charAt(0) == u'.');
|
||||
|
@ -1549,6 +1573,15 @@ bool GeneratorHelpers::perUnit(const MacroProps& macros, UnicodeString& sb, UErr
|
|||
}
|
||||
}
|
||||
|
||||
bool GeneratorHelpers::usage(const MacroProps& macros, UnicodeString& sb, UErrorCode&) {
|
||||
if (macros.usage.fLength > 0) {
|
||||
sb.append(u"usage/", -1);
|
||||
sb.append(UnicodeString(macros.usage.fUsage, -1, US_INV));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GeneratorHelpers::precision(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
|
||||
if (macros.precision.fType == Precision::RND_NONE) {
|
||||
sb.append(u"precision-unlimited", -1);
|
||||
|
|
|
@ -22,10 +22,12 @@ struct SeenMacroProps;
|
|||
// namespace for enums and entrypoint functions
|
||||
namespace skeleton {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// NOTE: For an example of how to add a new stem to the number skeleton parser, see: //
|
||||
// http://bugs.icu-project.org/trac/changeset/41193 //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
// NOTE: For examples of how to add a new stem to the number skeleton parser, see: //
|
||||
// https://github.com/unicode-org/icu/commit/a2a7982216b2348070dc71093775ac7195793d73 //
|
||||
// and //
|
||||
// https://github.com/unicode-org/icu/commit/6fe86f3934a8a5701034f648a8f7c5087e84aa28 //
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* While parsing a skeleton, this enum records what type of option we expect to find next.
|
||||
|
@ -47,6 +49,7 @@ enum ParseState {
|
|||
STATE_MEASURE_UNIT,
|
||||
STATE_PER_MEASURE_UNIT,
|
||||
STATE_IDENTIFIER_UNIT,
|
||||
STATE_UNIT_USAGE,
|
||||
STATE_CURRENCY_UNIT,
|
||||
STATE_INTEGER_WIDTH,
|
||||
STATE_NUMBERING_SYSTEM,
|
||||
|
@ -114,6 +117,7 @@ enum StemEnum {
|
|||
STEM_MEASURE_UNIT,
|
||||
STEM_PER_MEASURE_UNIT,
|
||||
STEM_UNIT,
|
||||
STEM_UNIT_USAGE,
|
||||
STEM_CURRENCY,
|
||||
STEM_INTEGER_WIDTH,
|
||||
STEM_NUMBERING_SYSTEM,
|
||||
|
@ -244,6 +248,8 @@ void parseMeasurePerUnitOption(const StringSegment& segment, MacroProps& macros,
|
|||
|
||||
void parseIdentifierUnitOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status);
|
||||
|
||||
void parseUnitUsageOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status);
|
||||
|
||||
void parseFractionStem(const StringSegment& segment, MacroProps& macros, UErrorCode& status);
|
||||
|
||||
void generateFractionStem(int32_t minFrac, int32_t maxFrac, UnicodeString& sb, UErrorCode& status);
|
||||
|
@ -306,6 +312,8 @@ class GeneratorHelpers {
|
|||
|
||||
static bool perUnit(const MacroProps& macros, UnicodeString& sb, UErrorCode& status);
|
||||
|
||||
static bool usage(const MacroProps& macros, UnicodeString& sb, UErrorCode& status);
|
||||
|
||||
static bool precision(const MacroProps& macros, UnicodeString& sb, UErrorCode& status);
|
||||
|
||||
static bool roundingMode(const MacroProps& macros, UnicodeString& sb, UErrorCode& status);
|
||||
|
@ -334,6 +342,7 @@ struct SeenMacroProps {
|
|||
bool notation = false;
|
||||
bool unit = false;
|
||||
bool perUnit = false;
|
||||
bool usage = false;
|
||||
bool precision = false;
|
||||
bool roundingMode = false;
|
||||
bool grouper = false;
|
||||
|
|
|
@ -246,16 +246,17 @@ class U_I18N_API ModifierStore {
|
|||
* itself. The {@link #processQuantity} method performs the final step in the number processing pipeline: it uses the
|
||||
* quantity to generate a finalized {@link MicroProps}, which can be used to render the number to output.
|
||||
*
|
||||
* <p>
|
||||
* In other words, this interface is used for the parts of number processing that are <em>quantity-dependent</em>.
|
||||
*
|
||||
* <p>
|
||||
* In order to allow for multiple different objects to all mutate the same MicroProps, a "chain" of MicroPropsGenerators
|
||||
* are linked together, and each one is responsible for manipulating a certain quantity-dependent part of the
|
||||
* MicroProps. At the tail of the linked list is a base instance of {@link MicroProps} with properties that are not
|
||||
* quantity-dependent. Each element in the linked list calls {@link #processQuantity} on its "parent", then does its
|
||||
* work, and then returns the result.
|
||||
*
|
||||
* This chain of MicroPropsGenerators is typically constructed by NumberFormatterImpl::macrosToMicroGenerator() when
|
||||
* constructing a NumberFormatter.
|
||||
*
|
||||
* Exported as U_I18N_API because it is a base class for other exported types
|
||||
*
|
||||
*/
|
||||
|
@ -264,13 +265,12 @@ class U_I18N_API MicroPropsGenerator {
|
|||
virtual ~MicroPropsGenerator();
|
||||
|
||||
/**
|
||||
* Considers the given {@link DecimalQuantity}, optionally mutates it, and returns a {@link MicroProps}.
|
||||
* Considers the given {@link DecimalQuantity}, optionally mutates it, and
|
||||
* populates a {@link MicroProps} instance.
|
||||
*
|
||||
* @param quantity
|
||||
* The quantity for consideration and optional mutation.
|
||||
* @param micros
|
||||
* The MicroProps instance to populate.
|
||||
* @return A MicroProps instance resolved for the quantity.
|
||||
* @param quantity The quantity for consideration and optional mutation.
|
||||
* @param micros The MicroProps instance to populate. It will be modified as
|
||||
* needed for the given quantity.
|
||||
*/
|
||||
virtual void processQuantity(DecimalQuantity& quantity, MicroProps& micros,
|
||||
UErrorCode& status) const = 0;
|
||||
|
|
124
icu4c/source/i18n/number_usageprefs.cpp
Normal file
124
icu4c/source/i18n/number_usageprefs.cpp
Normal file
|
@ -0,0 +1,124 @@
|
|||
// © 2020 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html
|
||||
|
||||
#include "unicode/utypes.h"
|
||||
|
||||
#if !UCONFIG_NO_FORMATTING
|
||||
|
||||
#include "number_usageprefs.h"
|
||||
#include "cstring.h"
|
||||
#include "number_decimalquantity.h"
|
||||
#include "number_microprops.h"
|
||||
#include "number_roundingutils.h"
|
||||
#include "unicode/char16ptr.h"
|
||||
#include "unicode/currunit.h"
|
||||
#include "unicode/fmtable.h"
|
||||
#include "unicode/measure.h"
|
||||
#include "unicode/numberformatter.h"
|
||||
#include "unicode/platform.h"
|
||||
#include "unicode/unum.h"
|
||||
#include "unicode/urename.h"
|
||||
|
||||
using namespace icu::number;
|
||||
using namespace icu::number::impl;
|
||||
|
||||
// Copy constructor
|
||||
Usage::Usage(const Usage &other) : fUsage(nullptr), fLength(other.fLength), fError(other.fError) {
|
||||
if (other.fUsage != nullptr) {
|
||||
fUsage = (char *)uprv_malloc(fLength + 1);
|
||||
uprv_strncpy(fUsage, other.fUsage, fLength + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy assignment operator
|
||||
Usage &Usage::operator=(const Usage &other) {
|
||||
fLength = other.fLength;
|
||||
if (other.fUsage != nullptr) {
|
||||
fUsage = (char *)uprv_malloc(fLength + 1);
|
||||
uprv_strncpy(fUsage, other.fUsage, fLength + 1);
|
||||
}
|
||||
fError = other.fError;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Move constructor - can it be improved by taking over src's "this" instead of
|
||||
// copying contents? Swapping pointers makes sense for heap objects but not for
|
||||
// stack objects.
|
||||
// *this = std::move(src);
|
||||
Usage::Usage(Usage &&src) U_NOEXCEPT : fUsage(src.fUsage), fLength(src.fLength), fError(src.fError) {
|
||||
// Take ownership away from src if necessary
|
||||
src.fUsage = nullptr;
|
||||
}
|
||||
|
||||
// Move assignment operator
|
||||
Usage &Usage::operator=(Usage &&src) U_NOEXCEPT {
|
||||
if (this == &src) {
|
||||
return *this;
|
||||
}
|
||||
if (fUsage != nullptr) {
|
||||
uprv_free(fUsage);
|
||||
}
|
||||
fUsage = src.fUsage;
|
||||
fLength = src.fLength;
|
||||
fError = src.fError;
|
||||
// Take ownership away from src if necessary
|
||||
src.fUsage = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Usage::~Usage() {
|
||||
if (fUsage != nullptr) {
|
||||
uprv_free(fUsage);
|
||||
fUsage = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Usage::set(StringPiece value) {
|
||||
if (fUsage != nullptr) {
|
||||
uprv_free(fUsage);
|
||||
fUsage = nullptr;
|
||||
}
|
||||
fLength = value.length();
|
||||
fUsage = (char *)uprv_malloc(fLength + 1);
|
||||
uprv_strncpy(fUsage, value.data(), fLength);
|
||||
fUsage[fLength] = 0;
|
||||
}
|
||||
|
||||
UsagePrefsHandler::UsagePrefsHandler(const Locale &locale,
|
||||
const MeasureUnit inputUnit,
|
||||
const StringPiece usage,
|
||||
const MicroPropsGenerator *parent,
|
||||
UErrorCode &status)
|
||||
: fUnitsRouter(inputUnit, StringPiece(locale.getCountry()), usage, status),
|
||||
fParent(parent) {
|
||||
}
|
||||
|
||||
void UsagePrefsHandler::processQuantity(DecimalQuantity &quantity, MicroProps µs,
|
||||
UErrorCode &status) const {
|
||||
fParent->processQuantity(quantity, micros, status);
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
quantity.roundToInfinity(); // Enables toDouble
|
||||
auto routed = fUnitsRouter.route(quantity.toDouble(), status);
|
||||
micros.outputUnit = routed[0]->getUnit();
|
||||
quantity.setToDouble(routed[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
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* #if !UCONFIG_NO_FORMATTING */
|
62
icu4c/source/i18n/number_usageprefs.h
Normal file
62
icu4c/source/i18n/number_usageprefs.h
Normal file
|
@ -0,0 +1,62 @@
|
|||
// © 2020 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html
|
||||
|
||||
#include "unicode/utypes.h"
|
||||
|
||||
#if !UCONFIG_NO_FORMATTING
|
||||
#ifndef __NUMBER_USAGEPREFS_H__
|
||||
#define __NUMBER_USAGEPREFS_H__
|
||||
|
||||
#include "cmemory.h"
|
||||
#include "number_types.h"
|
||||
#include "unicode/locid.h"
|
||||
#include "unicode/measunit.h"
|
||||
#include "unicode/stringpiece.h"
|
||||
#include "unicode/uobject.h"
|
||||
#include "unitsrouter.h"
|
||||
|
||||
U_NAMESPACE_BEGIN
|
||||
namespace number {
|
||||
namespace impl {
|
||||
|
||||
using ::icu::units::UnitsRouter;
|
||||
|
||||
/**
|
||||
* A MicroPropsGenerator which uses UnitsRouter to produce output converted to a
|
||||
* MeasureUnit appropriate for a particular localized usage: see
|
||||
* NumberFormatterSettings::usage().
|
||||
*/
|
||||
class U_I18N_API UsagePrefsHandler : public MicroPropsGenerator, public UMemory {
|
||||
public:
|
||||
UsagePrefsHandler(const Locale &locale, const MeasureUnit inputUnit, const StringPiece usage,
|
||||
const MicroPropsGenerator *parent, UErrorCode &status);
|
||||
|
||||
/**
|
||||
* Obtains the appropriate output value, MeasurementUnit and
|
||||
* rounding/precision behaviour from the UnitsRouter.
|
||||
*/
|
||||
void processQuantity(DecimalQuantity &quantity, MicroProps µs,
|
||||
UErrorCode &status) const U_OVERRIDE;
|
||||
|
||||
/**
|
||||
* Returns the list of possible output units, i.e. the full set of
|
||||
* preferences, for the localized, usage-specific unit preferences.
|
||||
*
|
||||
* The returned pointer should be valid for the lifetime of the
|
||||
* UsagePrefsHandler instance.
|
||||
*/
|
||||
const MaybeStackVector<MeasureUnit> *getOutputUnits() const {
|
||||
return fUnitsRouter.getOutputUnits();
|
||||
}
|
||||
|
||||
private:
|
||||
UnitsRouter fUnitsRouter;
|
||||
const MicroPropsGenerator *fParent;
|
||||
};
|
||||
|
||||
} // namespace impl
|
||||
} // namespace number
|
||||
U_NAMESPACE_END
|
||||
|
||||
#endif // __NUMBER_USAGEPREFS_H__
|
||||
#endif /* #if !UCONFIG_NO_FORMATTING */
|
|
@ -28,9 +28,6 @@ const DecimalQuantity* validateUFormattedNumberToDecimalQuantity(
|
|||
* This struct is held internally by the C++ version FormattedNumber since the member types are not
|
||||
* declared in the public header file.
|
||||
*
|
||||
* The DecimalQuantity is not currently being used by FormattedNumber, but at some point it could be used
|
||||
* to add a toDecNumber() or similar method.
|
||||
*
|
||||
* Exported as U_I18N_API for tests
|
||||
*/
|
||||
class U_I18N_API UFormattedNumberData : public FormattedValueStringBuilderImpl {
|
||||
|
@ -38,7 +35,13 @@ public:
|
|||
UFormattedNumberData() : FormattedValueStringBuilderImpl(kUndefinedField) {}
|
||||
virtual ~UFormattedNumberData();
|
||||
|
||||
// The formatted quantity.
|
||||
DecimalQuantity quantity;
|
||||
|
||||
// The output unit for the formatted quantity.
|
||||
// TODO(units,hugovdm): populate this correctly for the general case - it's
|
||||
// currently only implemented for the .usage() use case.
|
||||
MeasureUnit outputUnit;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -122,6 +122,7 @@ number_patternstring.cpp
|
|||
number_rounding.cpp
|
||||
number_scientific.cpp
|
||||
number_skeletons.cpp
|
||||
number_usageprefs.cpp
|
||||
number_utils.cpp
|
||||
numfmt.cpp
|
||||
numparse_affixes.cpp
|
||||
|
|
|
@ -158,6 +158,7 @@ struct UFormattedNumberImpl;
|
|||
class MutablePatternModifier;
|
||||
class ImmutablePatternModifier;
|
||||
struct DecimalFormatWarehouse;
|
||||
class UsagePrefsHandler;
|
||||
|
||||
/**
|
||||
* Used for NumberRangeFormatter and implemented in numrange_fluent.cpp.
|
||||
|
@ -767,6 +768,11 @@ 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:
|
||||
friend class impl::UsagePrefsHandler;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -1127,6 +1133,61 @@ class U_I18N_API Scale : public UMemory {
|
|||
|
||||
namespace impl {
|
||||
|
||||
// Do not enclose entire Usage with #ifndef U_HIDE_INTERNAL_API, needed for a protected field
|
||||
/**
|
||||
* Manages NumberFormatterSettings::usage()'s char* instance on the heap.
|
||||
* @internal
|
||||
*/
|
||||
class U_I18N_API Usage : public UMemory {
|
||||
|
||||
#ifndef U_HIDE_INTERNAL_API
|
||||
|
||||
public:
|
||||
/** @internal */
|
||||
Usage(const Usage& other);
|
||||
|
||||
/** @internal */
|
||||
Usage& operator=(const Usage& other);
|
||||
|
||||
/** @internal */
|
||||
Usage(Usage &&src) U_NOEXCEPT;
|
||||
|
||||
/** @internal */
|
||||
Usage& operator=(Usage&& src) U_NOEXCEPT;
|
||||
|
||||
/** @internal */
|
||||
~Usage();
|
||||
|
||||
/** @internal */
|
||||
int16_t length() const { return fLength; }
|
||||
|
||||
/** @internal
|
||||
* Makes a copy of value.
|
||||
*/
|
||||
void set(StringPiece value);
|
||||
|
||||
/** @internal */
|
||||
bool isSet() const { return fLength > 0; }
|
||||
|
||||
private:
|
||||
char *fUsage;
|
||||
int16_t fLength;
|
||||
UErrorCode fError;
|
||||
|
||||
Usage() : fUsage(nullptr), fLength(0), fError(U_ZERO_ERROR) {}
|
||||
|
||||
// Allow NumberFormatterImpl to access fUsage.
|
||||
friend class impl::NumberFormatterImpl;
|
||||
|
||||
// Allow skeleton generation code to access private members.
|
||||
friend class impl::GeneratorHelpers;
|
||||
|
||||
// Allow MacroProps/MicroProps to initialize empty instances.
|
||||
friend struct impl::MacroProps;
|
||||
|
||||
#endif // U_HIDE_INTERNAL_API
|
||||
};
|
||||
|
||||
// Do not enclose entire SymbolsWrapper with #ifndef U_HIDE_INTERNAL_API, needed for a protected field
|
||||
/** @internal */
|
||||
class U_I18N_API SymbolsWrapper : public UMemory {
|
||||
|
@ -1410,6 +1471,9 @@ struct U_I18N_API MacroProps : public UMemory {
|
|||
/** @internal */
|
||||
Scale scale; // = Scale(); (benign value)
|
||||
|
||||
/** @internal */
|
||||
Usage usage; // = Usage(); (no usage)
|
||||
|
||||
/** @internal */
|
||||
const AffixPatternProvider* affixProvider = nullptr; // no ownership
|
||||
|
||||
|
@ -2073,6 +2137,13 @@ class U_I18N_API NumberFormatterSettings {
|
|||
* Setting usage to an empty string clears the usage (disables usage-based
|
||||
* localized formatting).
|
||||
*
|
||||
* Setting a usage string but not a correct input unit will result in an
|
||||
* U_ILLEGAL_ARGUMENT_ERROR.
|
||||
*
|
||||
* When using usage, specifying rounding or precision is unnecessary.
|
||||
* Specifying a precision in some manner will override the default
|
||||
* formatting.
|
||||
*
|
||||
* @param usage A `usage` parameter from the units resource. See the
|
||||
* unitPreferenceData in *source/data/misc/units.txt*, generated from
|
||||
* `unitPreferenceData` in [CLDR's
|
||||
|
@ -2575,7 +2646,7 @@ class U_I18N_API FormattedNumber : public UMemory, public FormattedValue {
|
|||
* The output unit is dependent upon the localized preferences for the usage
|
||||
* specified via NumberFormatterSettings::usage(), and may be a unit with
|
||||
* UMEASURE_UNIT_MIXED unit complexity (MeasureUnit::getComplexity()), such
|
||||
* as "foot+inch" or "hour+minute+second".
|
||||
* as "foot-and-inch" or "hour-and-minute-and-second".
|
||||
*
|
||||
* @return `MeasureUnit`.
|
||||
* @draft ICU 68
|
||||
|
|
|
@ -5,146 +5,124 @@
|
|||
|
||||
#if !UCONFIG_NO_FORMATTING
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "charstr.h"
|
||||
#include "double-conversion.h"
|
||||
#include "cmemory.h"
|
||||
#include "double-conversion-string-to-double.h"
|
||||
#include "measunit_impl.h"
|
||||
#include "unicode/errorcode.h"
|
||||
#include "uassert.h"
|
||||
#include "unicode/localpointer.h"
|
||||
#include "unicode/measunit.h"
|
||||
#include "unicode/stringpiece.h"
|
||||
#include "unitconverter.h"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <stdlib.h>
|
||||
#include <utility>
|
||||
|
||||
U_NAMESPACE_BEGIN
|
||||
namespace units {
|
||||
|
||||
void U_I18N_API Factor::multiplyBy(const Factor &rhs) {
|
||||
factorNum *= rhs.factorNum;
|
||||
factorDen *= rhs.factorDen;
|
||||
for (int i = 0; i < CONSTANTS_COUNT; i++) {
|
||||
constants[i] += rhs.constants[i];
|
||||
}
|
||||
|
||||
// NOTE
|
||||
// We need the offset when the source and the target are simple units. e.g. the source is
|
||||
// celsius and the target is Fahrenheit. Therefore, we just keep the value using `std::max`.
|
||||
offset = std::max(rhs.offset, offset);
|
||||
}
|
||||
|
||||
void U_I18N_API Factor::divideBy(const Factor &rhs) {
|
||||
factorNum *= rhs.factorDen;
|
||||
factorDen *= rhs.factorNum;
|
||||
for (int i = 0; i < CONSTANTS_COUNT; i++) {
|
||||
constants[i] -= rhs.constants[i];
|
||||
}
|
||||
|
||||
// NOTE
|
||||
// We need the offset when the source and the target are simple units. e.g. the source is
|
||||
// celsius and the target is Fahrenheit. Therefore, we just keep the value using `std::max`.
|
||||
offset = std::max(rhs.offset, offset);
|
||||
}
|
||||
|
||||
void U_I18N_API Factor::power(int32_t power) {
|
||||
// multiply all the constant by the power.
|
||||
for (int i = 0; i < CONSTANTS_COUNT; i++) {
|
||||
constants[i] *= power;
|
||||
}
|
||||
|
||||
bool shouldFlip = power < 0; // This means that after applying the absolute power, we should flip
|
||||
// the Numerator and Denominator.
|
||||
|
||||
factorNum = std::pow(factorNum, std::abs(power));
|
||||
factorDen = std::pow(factorDen, std::abs(power));
|
||||
|
||||
if (shouldFlip) {
|
||||
// Flip Numerator and Denominator.
|
||||
std::swap(factorNum, factorDen);
|
||||
}
|
||||
}
|
||||
|
||||
void U_I18N_API Factor::flip() {
|
||||
std::swap(factorNum, factorDen);
|
||||
|
||||
for (int i = 0; i < CONSTANTS_COUNT; i++) {
|
||||
constants[i] *= -1;
|
||||
}
|
||||
}
|
||||
|
||||
void U_I18N_API Factor::applySiPrefix(UMeasureSIPrefix siPrefix) {
|
||||
if (siPrefix == UMeasureSIPrefix::UMEASURE_SI_PREFIX_ONE) return; // No need to do anything
|
||||
|
||||
double siApplied = std::pow(10.0, std::abs(siPrefix));
|
||||
|
||||
if (siPrefix < 0) {
|
||||
factorDen *= siApplied;
|
||||
return;
|
||||
}
|
||||
|
||||
factorNum *= siApplied;
|
||||
}
|
||||
|
||||
void U_I18N_API Factor::substituteConstants() {
|
||||
// These values are a hard-coded subset of unitConstants in the units
|
||||
// resources file. A unit test checks that all constants in the resource
|
||||
// file are at least recognised by the code. Derived constants' values or
|
||||
// hard-coded derivations are not checked.
|
||||
// double constantsValues[CONSTANTS_COUNT];
|
||||
static const double constantsValues[CONSTANTS_COUNT] = {
|
||||
[CONSTANT_FT2M] = 0.3048, //
|
||||
[CONSTANT_PI] = 411557987.0 / 131002976.0, //
|
||||
[CONSTANT_GRAVITY] = 9.80665, //
|
||||
[CONSTANT_G] = 6.67408E-11, //
|
||||
[CONSTANT_GAL_IMP2M3] = 0.00454609, //
|
||||
[CONSTANT_LB2KG] = 0.45359237, //
|
||||
};
|
||||
|
||||
for (int i = 0; i < CONSTANTS_COUNT; i++) {
|
||||
if (this->constants[i] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto absPower = std::abs(this->constants[i]);
|
||||
Signum powerSig = this->constants[i] < 0 ? Signum::NEGATIVE : Signum::POSITIVE;
|
||||
double absConstantValue = std::pow(constantsValues[i], absPower);
|
||||
|
||||
if (powerSig == Signum::NEGATIVE) {
|
||||
this->factorDen *= absConstantValue;
|
||||
} else {
|
||||
this->factorNum *= absConstantValue;
|
||||
}
|
||||
|
||||
this->constants[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
/* Internal Structure */
|
||||
|
||||
enum Constants {
|
||||
CONSTANT_FT2M, // ft2m stands for foot to meter.
|
||||
CONSTANT_PI, // PI
|
||||
CONSTANT_GRAVITY, // Gravity
|
||||
CONSTANT_G,
|
||||
CONSTANT_GAL_IMP2M3, // Gallon imp to m3
|
||||
CONSTANT_LB2KG, // Pound to Kilogram
|
||||
|
||||
// Must be the last element.
|
||||
CONSTANTS_COUNT
|
||||
};
|
||||
|
||||
typedef enum SigNum {
|
||||
NEGATIVE = -1,
|
||||
POSITIVE = 1,
|
||||
} SigNum;
|
||||
|
||||
/* Represents a conversion factor */
|
||||
struct Factor {
|
||||
double factorNum = 1;
|
||||
double factorDen = 1;
|
||||
double offset = 0;
|
||||
bool reciprocal = false;
|
||||
int32_t constants[CONSTANTS_COUNT] = {};
|
||||
|
||||
void multiplyBy(const Factor &rhs) {
|
||||
factorNum *= rhs.factorNum;
|
||||
factorDen *= rhs.factorDen;
|
||||
for (int i = 0; i < CONSTANTS_COUNT; i++) {
|
||||
constants[i] += rhs.constants[i];
|
||||
}
|
||||
|
||||
// NOTE
|
||||
// We need the offset when the source and the target are simple units. e.g. the source is
|
||||
// celsius and the target is Fahrenheit. Therefore, we just keep the value using `std::max`.
|
||||
offset = std::max(rhs.offset, offset);
|
||||
}
|
||||
|
||||
void divideBy(const Factor &rhs) {
|
||||
factorNum *= rhs.factorDen;
|
||||
factorDen *= rhs.factorNum;
|
||||
for (int i = 0; i < CONSTANTS_COUNT; i++) {
|
||||
constants[i] -= rhs.constants[i];
|
||||
}
|
||||
|
||||
// NOTE
|
||||
// We need the offset when the source and the target are simple units. e.g. the source is
|
||||
// celsius and the target is Fahrenheit. Therefore, we just keep the value using `std::max`.
|
||||
offset = std::max(rhs.offset, offset);
|
||||
}
|
||||
|
||||
// Apply the power to the factor.
|
||||
void power(int32_t power) {
|
||||
// multiply all the constant by the power.
|
||||
for (int i = 0; i < CONSTANTS_COUNT; i++) {
|
||||
constants[i] *= power;
|
||||
}
|
||||
|
||||
bool shouldFlip = power < 0; // This means that after applying the absolute power, we should flip
|
||||
// the Numerator and Denominator.
|
||||
|
||||
factorNum = std::pow(factorNum, std::abs(power));
|
||||
factorDen = std::pow(factorDen, std::abs(power));
|
||||
|
||||
if (shouldFlip) {
|
||||
// Flip Numerator and Denominator.
|
||||
std::swap(factorNum, factorDen);
|
||||
}
|
||||
}
|
||||
|
||||
// Flip the `Factor`, for example, factor= 2/3, flippedFactor = 3/2
|
||||
void flip() {
|
||||
std::swap(factorNum, factorDen);
|
||||
|
||||
for (int i = 0; i < CONSTANTS_COUNT; i++) {
|
||||
constants[i] *= -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply SI prefix to the `Factor`
|
||||
void applySiPrefix(UMeasureSIPrefix siPrefix) {
|
||||
if (siPrefix == UMeasureSIPrefix::UMEASURE_SI_PREFIX_ONE) return; // No need to do anything
|
||||
|
||||
double siApplied = std::pow(10.0, std::abs(siPrefix));
|
||||
|
||||
if (siPrefix < 0) {
|
||||
factorDen *= siApplied;
|
||||
return;
|
||||
}
|
||||
|
||||
factorNum *= siApplied;
|
||||
}
|
||||
|
||||
void substituteConstants() {
|
||||
double constantsValues[CONSTANTS_COUNT];
|
||||
|
||||
// TODO: Load those constant values from units data.
|
||||
constantsValues[CONSTANT_FT2M] = 0.3048;
|
||||
constantsValues[CONSTANT_PI] = 411557987.0 / 131002976.0;
|
||||
constantsValues[CONSTANT_GRAVITY] = 9.80665;
|
||||
constantsValues[CONSTANT_G] = 6.67408E-11;
|
||||
constantsValues[CONSTANT_LB2KG] = 0.45359237;
|
||||
constantsValues[CONSTANT_GAL_IMP2M3] = 0.00454609;
|
||||
|
||||
for (int i = 0; i < CONSTANTS_COUNT; i++) {
|
||||
if (this->constants[i] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto absPower = std::abs(this->constants[i]);
|
||||
SigNum powerSig = this->constants[i] < 0 ? SigNum::NEGATIVE : SigNum::POSITIVE;
|
||||
double absConstantValue = std::pow(constantsValues[i], absPower);
|
||||
|
||||
if (powerSig == SigNum::NEGATIVE) {
|
||||
this->factorDen *= absConstantValue;
|
||||
} else {
|
||||
this->factorNum *= absConstantValue;
|
||||
}
|
||||
|
||||
this->constants[i] = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* Helpers */
|
||||
|
||||
using icu::double_conversion::StringToDoubleConverter;
|
||||
|
@ -183,49 +161,11 @@ double strHasDivideSignToDouble(StringPiece strWithDivide, UErrorCode &status) {
|
|||
return strToDouble(strWithDivide, status);
|
||||
}
|
||||
|
||||
/*
|
||||
* Adds a single factor element to the `Factor`. e.g "ft3m", "2.333" or "cup2m3". But not "cup2m3^3".
|
||||
*/
|
||||
void addSingleFactorConstant(StringPiece baseStr, int32_t power, SigNum sigNum, Factor &factor,
|
||||
UErrorCode &status) {
|
||||
|
||||
if (baseStr == "ft_to_m") {
|
||||
factor.constants[CONSTANT_FT2M] += power * sigNum;
|
||||
} else if (baseStr == "ft2_to_m2") {
|
||||
factor.constants[CONSTANT_FT2M] += 2 * power * sigNum;
|
||||
} else if (baseStr == "ft3_to_m3") {
|
||||
factor.constants[CONSTANT_FT2M] += 3 * power * sigNum;
|
||||
} else if (baseStr == "in3_to_m3") {
|
||||
factor.constants[CONSTANT_FT2M] += 3 * power * sigNum;
|
||||
factor.factorDen *= 12 * 12 * 12;
|
||||
} else if (baseStr == "gal_to_m3") {
|
||||
factor.factorNum *= 231;
|
||||
factor.constants[CONSTANT_FT2M] += 3 * power * sigNum;
|
||||
factor.factorDen *= 12 * 12 * 12;
|
||||
} else if (baseStr == "gal_imp_to_m3") {
|
||||
factor.constants[CONSTANT_GAL_IMP2M3] += power * sigNum;
|
||||
} else if (baseStr == "G") {
|
||||
factor.constants[CONSTANT_G] += power * sigNum;
|
||||
} else if (baseStr == "gravity") {
|
||||
factor.constants[CONSTANT_GRAVITY] += power * sigNum;
|
||||
} else if (baseStr == "lb_to_kg") {
|
||||
factor.constants[CONSTANT_LB2KG] += power * sigNum;
|
||||
} else if (baseStr == "PI") {
|
||||
factor.constants[CONSTANT_PI] += power * sigNum;
|
||||
} else {
|
||||
if (sigNum == SigNum::NEGATIVE) {
|
||||
factor.factorDen *= std::pow(strToDouble(baseStr, status), power);
|
||||
} else {
|
||||
factor.factorNum *= std::pow(strToDouble(baseStr, status), power);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Adds single factor to a `Factor` object. Single factor means "23^2", "23.3333", "ft2m^3" ...etc.
|
||||
However, complex factor are not included, such as "ft2m^3*200/3"
|
||||
*/
|
||||
void addFactorElement(Factor &factor, StringPiece elementStr, SigNum sigNum, UErrorCode &status) {
|
||||
void addFactorElement(Factor &factor, StringPiece elementStr, Signum signum, UErrorCode &status) {
|
||||
StringPiece baseStr;
|
||||
StringPiece powerStr;
|
||||
int32_t power =
|
||||
|
@ -250,7 +190,7 @@ void addFactorElement(Factor &factor, StringPiece elementStr, SigNum sigNum, UEr
|
|||
baseStr = elementStr;
|
||||
}
|
||||
|
||||
addSingleFactorConstant(baseStr, power, sigNum, factor, status);
|
||||
addSingleFactorConstant(baseStr, power, signum, factor, status);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -258,21 +198,21 @@ void addFactorElement(Factor &factor, StringPiece elementStr, SigNum sigNum, UEr
|
|||
*/
|
||||
Factor extractFactorConversions(StringPiece stringFactor, UErrorCode &status) {
|
||||
Factor result;
|
||||
SigNum sigNum = SigNum::POSITIVE;
|
||||
Signum signum = Signum::POSITIVE;
|
||||
auto factorData = stringFactor.data();
|
||||
for (int32_t i = 0, start = 0, n = stringFactor.length(); i < n; i++) {
|
||||
if (factorData[i] == '*' || factorData[i] == '/') {
|
||||
StringPiece factorElement = stringFactor.substr(start, i - start);
|
||||
addFactorElement(result, factorElement, sigNum, status);
|
||||
addFactorElement(result, factorElement, signum, status);
|
||||
|
||||
start = i + 1; // Set `start` to point to the start of the new element.
|
||||
} else if (i == n - 1) {
|
||||
// Last element
|
||||
addFactorElement(result, stringFactor.substr(start, i + 1), sigNum, status);
|
||||
addFactorElement(result, stringFactor.substr(start, i + 1), signum, status);
|
||||
}
|
||||
|
||||
if (factorData[i] == '/') {
|
||||
sigNum = SigNum::NEGATIVE; // Change the sigNum because we reached the Denominator.
|
||||
signum = Signum::NEGATIVE; // Change the signum because we reached the Denominator.
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -386,6 +326,44 @@ void loadConversionRate(ConversionRate &conversionRate, const MeasureUnit &sourc
|
|||
|
||||
} // namespace
|
||||
|
||||
// Conceptually, this modifies factor: factor *= baseStr^(signum*power).
|
||||
//
|
||||
// baseStr must be a known constant or a value that strToDouble() is able to
|
||||
// parse.
|
||||
void U_I18N_API addSingleFactorConstant(StringPiece baseStr, int32_t power, Signum signum,
|
||||
Factor &factor, UErrorCode &status) {
|
||||
if (baseStr == "ft_to_m") {
|
||||
factor.constants[CONSTANT_FT2M] += power * signum;
|
||||
} else if (baseStr == "ft2_to_m2") {
|
||||
factor.constants[CONSTANT_FT2M] += 2 * power * signum;
|
||||
} else if (baseStr == "ft3_to_m3") {
|
||||
factor.constants[CONSTANT_FT2M] += 3 * power * signum;
|
||||
} else if (baseStr == "in3_to_m3") {
|
||||
factor.constants[CONSTANT_FT2M] += 3 * power * signum;
|
||||
factor.factorDen *= 12 * 12 * 12;
|
||||
} else if (baseStr == "gal_to_m3") {
|
||||
factor.factorNum *= 231;
|
||||
factor.constants[CONSTANT_FT2M] += 3 * power * signum;
|
||||
factor.factorDen *= 12 * 12 * 12;
|
||||
} else if (baseStr == "gal_imp_to_m3") {
|
||||
factor.constants[CONSTANT_GAL_IMP2M3] += power * signum;
|
||||
} else if (baseStr == "G") {
|
||||
factor.constants[CONSTANT_G] += power * signum;
|
||||
} else if (baseStr == "gravity") {
|
||||
factor.constants[CONSTANT_GRAVITY] += power * signum;
|
||||
} else if (baseStr == "lb_to_kg") {
|
||||
factor.constants[CONSTANT_LB2KG] += power * signum;
|
||||
} else if (baseStr == "PI") {
|
||||
factor.constants[CONSTANT_PI] += power * signum;
|
||||
} else {
|
||||
if (signum == Signum::NEGATIVE) {
|
||||
factor.factorDen *= std::pow(strToDouble(baseStr, status), power);
|
||||
} else {
|
||||
factor.factorNum *= std::pow(strToDouble(baseStr, status), power);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the compound base unit of a compound unit (`source`). For example, if the source unit is
|
||||
* `square-mile-per-hour`, the compound base unit will be `square-meter-per-second`
|
||||
|
@ -414,7 +392,7 @@ MeasureUnit U_I18N_API extractCompoundBaseUnit(const MeasureUnit &source,
|
|||
}
|
||||
|
||||
// Multiply the power of the singleUnit by the power of the baseUnit. For example, square-hectare
|
||||
// must be p4-meter. (NOTE: hectare --> square-meter)
|
||||
// must be pow4-meter. (NOTE: hectare --> square-meter)
|
||||
auto compoundBaseUnit = MeasureUnit::forIdentifier(rateInfo->baseUnit.toStringPiece(), status);
|
||||
|
||||
int32_t baseUnitsCount;
|
||||
|
@ -503,6 +481,7 @@ double UnitConverter::convert(double inputValue) const {
|
|||
return result * 1.000000000001;
|
||||
}
|
||||
|
||||
} // namespace units
|
||||
U_NAMESPACE_END
|
||||
|
||||
#endif /* #if !UCONFIG_NO_FORMATTING */
|
||||
|
|
|
@ -7,13 +7,60 @@
|
|||
#ifndef __UNITCONVERTER_H__
|
||||
#define __UNITCONVERTER_H__
|
||||
|
||||
#include "cmemory.h"
|
||||
#include "unicode/errorcode.h"
|
||||
#include "unicode/measunit.h"
|
||||
#include "unitconverter.h"
|
||||
#include "unicode/stringpiece.h"
|
||||
#include "unicode/uobject.h"
|
||||
#include "unitsdata.h"
|
||||
|
||||
U_NAMESPACE_BEGIN
|
||||
namespace units {
|
||||
|
||||
/* Internal Structure */
|
||||
|
||||
enum Constants {
|
||||
CONSTANT_FT2M, // ft2m stands for foot to meter.
|
||||
CONSTANT_PI, // PI
|
||||
CONSTANT_GRAVITY, // Gravity
|
||||
CONSTANT_G,
|
||||
CONSTANT_GAL_IMP2M3, // Gallon imp to m3
|
||||
CONSTANT_LB2KG, // Pound to Kilogram
|
||||
|
||||
// Must be the last element.
|
||||
CONSTANTS_COUNT
|
||||
};
|
||||
|
||||
typedef enum Signum {
|
||||
NEGATIVE = -1,
|
||||
POSITIVE = 1,
|
||||
} Signum;
|
||||
|
||||
/* Represents a conversion factor */
|
||||
struct U_I18N_API Factor {
|
||||
double factorNum = 1;
|
||||
double factorDen = 1;
|
||||
double offset = 0;
|
||||
bool reciprocal = false;
|
||||
int32_t constants[CONSTANTS_COUNT] = {};
|
||||
|
||||
void multiplyBy(const Factor &rhs);
|
||||
void divideBy(const Factor &rhs);
|
||||
|
||||
// Apply the power to the factor.
|
||||
void power(int32_t power);
|
||||
|
||||
// Flip the `Factor`, for example, factor= 2/3, flippedFactor = 3/2
|
||||
void flip();
|
||||
|
||||
// Apply SI prefix to the `Factor`
|
||||
void applySiPrefix(UMeasureSIPrefix siPrefix);
|
||||
void substituteConstants();
|
||||
};
|
||||
|
||||
/*
|
||||
* Adds a single factor element to the `Factor`. e.g "ft3m", "2.333" or "cup2m3". But not "cup2m3^3".
|
||||
*/
|
||||
void U_I18N_API addSingleFactorConstant(StringPiece baseStr, int32_t power, Signum sigNum,
|
||||
Factor &factor, UErrorCode &status);
|
||||
|
||||
/**
|
||||
* Represents the conversion rate between `source` and `target`.
|
||||
|
@ -90,6 +137,7 @@ class U_I18N_API UnitConverter : public UMemory {
|
|||
ConversionRate conversionRate_;
|
||||
};
|
||||
|
||||
} // namespace units
|
||||
U_NAMESPACE_END
|
||||
|
||||
#endif //__UNITCONVERTER_H__
|
||||
|
|
|
@ -5,15 +5,19 @@
|
|||
|
||||
#if !UCONFIG_NO_FORMATTING
|
||||
|
||||
#include "number_decimalquantity.h"
|
||||
#include "cstring.h"
|
||||
#include "number_decimalquantity.h"
|
||||
#include "resource.h"
|
||||
#include "uassert.h"
|
||||
#include "unicode/unistr.h"
|
||||
#include "unicode/ures.h"
|
||||
#include "unitsdata.h"
|
||||
#include "uresimp.h"
|
||||
#include "util.h"
|
||||
#include <utility>
|
||||
|
||||
U_NAMESPACE_BEGIN
|
||||
namespace units {
|
||||
|
||||
namespace {
|
||||
|
||||
|
@ -108,40 +112,6 @@ class ConversionRateDataSink : public ResourceSink {
|
|||
MaybeStackVector<ConversionRateInfo> *outVector;
|
||||
};
|
||||
|
||||
UnitPreferenceMetadata::UnitPreferenceMetadata(StringPiece category, StringPiece usage,
|
||||
StringPiece region, int32_t prefsOffset,
|
||||
int32_t prefsCount, UErrorCode &status) {
|
||||
this->category.append(category, status);
|
||||
this->usage.append(usage, status);
|
||||
this->region.append(region, status);
|
||||
this->prefsOffset = prefsOffset;
|
||||
this->prefsCount = prefsCount;
|
||||
}
|
||||
|
||||
int32_t UnitPreferenceMetadata::compareTo(const UnitPreferenceMetadata &other) const {
|
||||
int32_t cmp = uprv_strcmp(category.data(), other.category.data());
|
||||
if (cmp == 0) { cmp = uprv_strcmp(usage.data(), other.usage.data()); }
|
||||
if (cmp == 0) { cmp = uprv_strcmp(region.data(), other.region.data()); }
|
||||
return cmp;
|
||||
}
|
||||
|
||||
int32_t UnitPreferenceMetadata::compareTo(const UnitPreferenceMetadata &other, bool *foundCategory,
|
||||
bool *foundUsage, bool *foundRegion) const {
|
||||
int32_t cmp = uprv_strcmp(category.data(), other.category.data());
|
||||
if (cmp == 0) {
|
||||
*foundCategory = true;
|
||||
cmp = uprv_strcmp(usage.data(), other.usage.data());
|
||||
}
|
||||
if (cmp == 0) {
|
||||
*foundUsage = true;
|
||||
cmp = uprv_strcmp(region.data(), other.region.data());
|
||||
}
|
||||
if (cmp == 0) {
|
||||
*foundRegion = true;
|
||||
}
|
||||
return cmp;
|
||||
}
|
||||
|
||||
bool operator<(const UnitPreferenceMetadata &a, const UnitPreferenceMetadata &b) {
|
||||
return a.compareTo(b) < 0;
|
||||
}
|
||||
|
@ -325,6 +295,9 @@ int32_t getPreferenceMetadataIndex(const MaybeStackVector<UnitPreferenceMetadata
|
|||
} else if (uprv_strcmp(desired.usage.data(), "default") != 0) {
|
||||
desired.usage.truncate(0).append("default", status);
|
||||
} else {
|
||||
// TODO(icu-units/icu#36): reconsider consistency of errors.
|
||||
// Currently this U_MISSING_RESOURCE_ERROR propagates when a
|
||||
// U_NUMBER_SKELETON_SYNTAX_ERROR might be much more intuitive.
|
||||
status = U_MISSING_RESOURCE_ERROR;
|
||||
return -1;
|
||||
}
|
||||
|
@ -352,6 +325,44 @@ int32_t getPreferenceMetadataIndex(const MaybeStackVector<UnitPreferenceMetadata
|
|||
|
||||
} // namespace
|
||||
|
||||
UnitPreferenceMetadata::UnitPreferenceMetadata(StringPiece category, StringPiece usage,
|
||||
StringPiece region, int32_t prefsOffset,
|
||||
int32_t prefsCount, UErrorCode &status) {
|
||||
this->category.append(category, status);
|
||||
this->usage.append(usage, status);
|
||||
this->region.append(region, status);
|
||||
this->prefsOffset = prefsOffset;
|
||||
this->prefsCount = prefsCount;
|
||||
}
|
||||
|
||||
int32_t UnitPreferenceMetadata::compareTo(const UnitPreferenceMetadata &other) const {
|
||||
int32_t cmp = uprv_strcmp(category.data(), other.category.data());
|
||||
if (cmp == 0) {
|
||||
cmp = uprv_strcmp(usage.data(), other.usage.data());
|
||||
}
|
||||
if (cmp == 0) {
|
||||
cmp = uprv_strcmp(region.data(), other.region.data());
|
||||
}
|
||||
return cmp;
|
||||
}
|
||||
|
||||
int32_t UnitPreferenceMetadata::compareTo(const UnitPreferenceMetadata &other, bool *foundCategory,
|
||||
bool *foundUsage, bool *foundRegion) const {
|
||||
int32_t cmp = uprv_strcmp(category.data(), other.category.data());
|
||||
if (cmp == 0) {
|
||||
*foundCategory = true;
|
||||
cmp = uprv_strcmp(usage.data(), other.usage.data());
|
||||
}
|
||||
if (cmp == 0) {
|
||||
*foundUsage = true;
|
||||
cmp = uprv_strcmp(region.data(), other.region.data());
|
||||
}
|
||||
if (cmp == 0) {
|
||||
*foundRegion = true;
|
||||
}
|
||||
return cmp;
|
||||
}
|
||||
|
||||
CharString U_I18N_API getUnitCategory(const char *baseUnitIdentifier, UErrorCode &status) {
|
||||
CharString result;
|
||||
LocalUResourceBundlePointer unitsBundle(ures_openDirect(NULL, "units", &status));
|
||||
|
@ -414,6 +425,7 @@ void U_I18N_API UnitPreferences::getPreferencesFor(StringPiece category, StringP
|
|||
preferenceCount = m->prefsCount;
|
||||
}
|
||||
|
||||
} // namespace units
|
||||
U_NAMESPACE_END
|
||||
|
||||
#endif /* #if !UCONFIG_NO_FORMATTING */
|
||||
|
|
|
@ -9,10 +9,11 @@
|
|||
|
||||
#include "charstr.h"
|
||||
#include "cmemory.h"
|
||||
#include "unicode/measunit.h"
|
||||
#include "unicode/stringpiece.h"
|
||||
#include "unicode/uobject.h"
|
||||
|
||||
U_NAMESPACE_BEGIN
|
||||
namespace units {
|
||||
|
||||
/**
|
||||
* Looks up the unit category of a base unit identifier.
|
||||
|
@ -97,8 +98,6 @@ struct U_I18N_API UnitPreference : public UMemory {
|
|||
CharString skeleton;
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* Metadata about the preferences in UnitPreferences::unitPrefs_.
|
||||
*
|
||||
|
@ -134,8 +133,6 @@ class U_I18N_API UnitPreferenceMetadata : public UMemory {
|
|||
bool *foundRegion) const;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* Unit Preferences information for various locales and usages.
|
||||
*/
|
||||
|
@ -186,6 +183,7 @@ class U_I18N_API UnitPreferences {
|
|||
MaybeStackVector<UnitPreference> unitPrefs_;
|
||||
};
|
||||
|
||||
} // namespace units
|
||||
U_NAMESPACE_END
|
||||
|
||||
#endif //__GETUNITSDATA_H__
|
||||
|
|
|
@ -5,19 +5,15 @@
|
|||
|
||||
#if !UCONFIG_NO_FORMATTING
|
||||
|
||||
#include <stdio.h>
|
||||
#include <utility>
|
||||
|
||||
#include "charstr.h"
|
||||
#include "cmemory.h"
|
||||
#include "cstring.h"
|
||||
#include "number_decimalquantity.h"
|
||||
#include "resource.h"
|
||||
#include "unitconverter.h" // for extractCompoundBaseUnit
|
||||
#include "unitsdata.h" // for getUnitCategory
|
||||
#include "unicode/measure.h"
|
||||
#include "unitconverter.h"
|
||||
#include "unitsdata.h"
|
||||
#include "unitsrouter.h"
|
||||
#include "uresimp.h"
|
||||
|
||||
U_NAMESPACE_BEGIN
|
||||
namespace units {
|
||||
|
||||
UnitsRouter::UnitsRouter(MeasureUnit inputUnit, StringPiece region, StringPiece usage,
|
||||
UErrorCode &status) {
|
||||
|
@ -41,6 +37,7 @@ UnitsRouter::UnitsRouter(MeasureUnit inputUnit, StringPiece region, StringPiece
|
|||
return;
|
||||
}
|
||||
|
||||
outputUnits_.emplaceBackAndCheckErrorCode(status, complexTargetUnit);
|
||||
converterPreferences_.emplaceBackAndCheckErrorCode(status, inputUnit, complexTargetUnit,
|
||||
preference.geq, conversionRates, status);
|
||||
if (U_FAILURE(status)) {
|
||||
|
@ -49,7 +46,7 @@ UnitsRouter::UnitsRouter(MeasureUnit inputUnit, StringPiece region, StringPiece
|
|||
}
|
||||
}
|
||||
|
||||
MaybeStackVector<Measure> UnitsRouter::route(double quantity, UErrorCode &status) {
|
||||
MaybeStackVector<Measure> UnitsRouter::route(double quantity, UErrorCode &status) const {
|
||||
for (int i = 0, n = converterPreferences_.length(); i < n; i++) {
|
||||
const auto &converterPreference = *converterPreferences_[i];
|
||||
|
||||
|
@ -63,6 +60,11 @@ MaybeStackVector<Measure> UnitsRouter::route(double quantity, UErrorCode &status
|
|||
return lastConverter.convert(quantity, status);
|
||||
}
|
||||
|
||||
const MaybeStackVector<MeasureUnit> *UnitsRouter::getOutputUnits() const {
|
||||
return &outputUnits_;
|
||||
}
|
||||
|
||||
} // namespace units
|
||||
U_NAMESPACE_END
|
||||
|
||||
#endif /* #if !UCONFIG_NO_FORMATTING */
|
||||
|
|
|
@ -9,17 +9,20 @@
|
|||
|
||||
#include <limits>
|
||||
|
||||
#include "charstr.h" // CharString
|
||||
#include "cmemory.h"
|
||||
#include "complexunitsconverter.h"
|
||||
#include "unicode/errorcode.h"
|
||||
#include "unicode/measunit.h"
|
||||
#include "unicode/measure.h"
|
||||
#include "unicode/stringpiece.h"
|
||||
#include "unicode/uobject.h"
|
||||
#include "unitsdata.h"
|
||||
|
||||
U_NAMESPACE_BEGIN
|
||||
|
||||
// Forward declarations
|
||||
class Measure;
|
||||
|
||||
namespace units {
|
||||
|
||||
/**
|
||||
* Contains the complex unit converter and the limit which representing the smallest value that the
|
||||
* converter should accept. For example, if the converter is converting to `foot+inch` and the limit
|
||||
|
@ -75,12 +78,25 @@ class U_I18N_API UnitsRouter {
|
|||
public:
|
||||
UnitsRouter(MeasureUnit inputUnit, StringPiece locale, StringPiece usage, UErrorCode &status);
|
||||
|
||||
MaybeStackVector<Measure> route(double quantity, UErrorCode &status);
|
||||
MaybeStackVector<Measure> route(double quantity, UErrorCode &status) const;
|
||||
|
||||
/**
|
||||
* Returns the list of possible output units, i.e. the full set of
|
||||
* preferences, for the localized, usage-specific unit preferences.
|
||||
*
|
||||
* The returned pointer should be valid for the lifetime of the
|
||||
* UnitsRouter instance.
|
||||
*/
|
||||
const MaybeStackVector<MeasureUnit> *getOutputUnits() const;
|
||||
|
||||
private:
|
||||
// List of possible output units
|
||||
MaybeStackVector<MeasureUnit> outputUnits_;
|
||||
|
||||
MaybeStackVector<ConverterPreference> converterPreferences_;
|
||||
};
|
||||
|
||||
} // namespace units
|
||||
U_NAMESPACE_END
|
||||
|
||||
#endif //__UNITSROUTER_H__
|
||||
|
|
|
@ -979,6 +979,8 @@ group: number_output
|
|||
number_representation format formatted_value_sbimpl
|
||||
# PluralRules internals:
|
||||
unifiedcache
|
||||
# Unit Formatting
|
||||
units
|
||||
|
||||
group: numberformatter
|
||||
# ICU 60+ NumberFormatter API
|
||||
|
@ -990,11 +992,11 @@ group: numberformatter
|
|||
number_mapper.o number_modifiers.o number_multiplier.o
|
||||
number_notation.o number_padding.o
|
||||
number_patternmodifier.o number_patternstring.o number_rounding.o
|
||||
number_scientific.o
|
||||
number_scientific.o number_usageprefs.o
|
||||
currpinf.o dcfmtsym.o numsys.o
|
||||
numrange_fluent.o numrange_impl.o
|
||||
deps
|
||||
decnumber double_conversion formattable units
|
||||
decnumber double_conversion formattable units unitsformatter
|
||||
number_representation number_output
|
||||
uclean_i18n common
|
||||
|
||||
|
|
|
@ -35,6 +35,10 @@ typedef struct UFieldPositionWithCategory {
|
|||
|
||||
class IntlTestWithFieldPosition : public IntlTest {
|
||||
public:
|
||||
// Tests FormattedValue's toString, toTempString, and nextPosition methods.
|
||||
//
|
||||
// expectedCategory gets combined with expectedFieldPositions to call
|
||||
// checkMixedFormattedValue.
|
||||
void checkFormattedValue(
|
||||
const char16_t* message,
|
||||
const FormattedValue& fv,
|
||||
|
@ -43,6 +47,7 @@ public:
|
|||
const UFieldPosition* expectedFieldPositions,
|
||||
int32_t length);
|
||||
|
||||
// Tests FormattedValue's toString, toTempString, and nextPosition methods.
|
||||
void checkMixedFormattedValue(
|
||||
const char16_t* message,
|
||||
const FormattedValue& fv,
|
||||
|
|
|
@ -54,6 +54,7 @@ class NumberFormatterApiTest : public IntlTestWithFieldPosition {
|
|||
void notationCompact();
|
||||
void unitMeasure();
|
||||
void unitCompoundMeasure();
|
||||
void unitUsage();
|
||||
void unitCurrency();
|
||||
void unitPercent();
|
||||
void percentParity();
|
||||
|
|
|
@ -75,6 +75,7 @@ void NumberFormatterApiTest::runIndexedTest(int32_t index, UBool exec, const cha
|
|||
TESTCASE_AUTO(notationCompact);
|
||||
TESTCASE_AUTO(unitMeasure);
|
||||
TESTCASE_AUTO(unitCompoundMeasure);
|
||||
TESTCASE_AUTO(unitUsage);
|
||||
TESTCASE_AUTO(unitCurrency);
|
||||
TESTCASE_AUTO(unitPercent);
|
||||
if (!quick) {
|
||||
|
@ -687,6 +688,79 @@ void NumberFormatterApiTest::unitMeasure() {
|
|||
u"5 a\u00F1os");
|
||||
}
|
||||
|
||||
void NumberFormatterApiTest::unitUsage() {
|
||||
UnlocalizedNumberFormatter unloc_formatter =
|
||||
NumberFormatter::with().usage("road").unit(MeasureUnit::getMeter());
|
||||
|
||||
IcuTestErrorCode status(*this, "unitUsage()");
|
||||
|
||||
LocalizedNumberFormatter formatter = unloc_formatter.locale("en-ZA");
|
||||
FormattedNumber formattedNum = formatter.formatDouble(300, status);
|
||||
assertTrue(UnicodeString("unitUsage() en-ZA road, got outputUnit: \"") +
|
||||
formattedNum.getOutputUnit(status).getIdentifier() + "\"",
|
||||
MeasureUnit::getMeter() == formattedNum.getOutputUnit(status));
|
||||
assertEquals("unitUsage() en-ZA road", "300 m", formattedNum.toString(status));
|
||||
assertFormatDescendingBig(
|
||||
u"unitUsage() en-ZA road",
|
||||
u"measure-unit/length-meter usage/road",
|
||||
u"unit/meter usage/road",
|
||||
unloc_formatter,
|
||||
Locale("en-ZA"),
|
||||
u"87\u00A0650 km",
|
||||
u"8\u00A0765 km",
|
||||
u"877 km",
|
||||
u"88 km",
|
||||
u"8,8 km",
|
||||
u"877 m",
|
||||
u"88 m",
|
||||
u"8,8 m",
|
||||
u"0 m");
|
||||
|
||||
formatter = unloc_formatter.locale("en-GB");
|
||||
formattedNum = formatter.formatDouble(300, status);
|
||||
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));
|
||||
assertFormatDescendingBig(
|
||||
u"unitUsage() en-GB road",
|
||||
u"measure-unit/length-meter usage/road",
|
||||
u"unit/meter usage/road",
|
||||
unloc_formatter,
|
||||
Locale("en-GB"),
|
||||
u"54,463 mi",
|
||||
u"5,446 mi",
|
||||
u"545 mi",
|
||||
u"54 mi",
|
||||
u"5.4 mi",
|
||||
u"0.54 mi",
|
||||
u"96 yd",
|
||||
u"9.6 yd",
|
||||
u"0 yd");
|
||||
|
||||
formatter = unloc_formatter.locale("en-US");
|
||||
formattedNum = formatter.formatDouble(300, status);
|
||||
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));
|
||||
assertFormatDescendingBig(
|
||||
u"unitUsage() en-US road",
|
||||
u"measure-unit/length-meter usage/road",
|
||||
u"unit/meter usage/road",
|
||||
unloc_formatter,
|
||||
Locale("en-US"),
|
||||
u"54,463 mi",
|
||||
u"5,446 mi",
|
||||
u"545 mi",
|
||||
u"54 mi",
|
||||
u"5.4 mi",
|
||||
u"0.54 mi",
|
||||
u"288 ft",
|
||||
u"29 ft",
|
||||
u"0 ft");
|
||||
}
|
||||
|
||||
void NumberFormatterApiTest::unitCompoundMeasure() {
|
||||
assertFormatDescending(
|
||||
u"Meters Per Second Short (unit that simplifies) and perUnit method",
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
#include "unitsdata.h"
|
||||
#include "intltest.h"
|
||||
|
||||
using namespace ::icu::units;
|
||||
|
||||
class UnitsDataTest : public IntlTest {
|
||||
public:
|
||||
UnitsDataTest() {}
|
||||
|
|
|
@ -13,14 +13,17 @@
|
|||
#include "filestrm.h"
|
||||
#include "intltest.h"
|
||||
#include "number_decimalquantity.h"
|
||||
#include "putilimp.h"
|
||||
#include "unicode/ctest.h"
|
||||
#include "unicode/measunit.h"
|
||||
#include "unicode/unistr.h"
|
||||
#include "unicode/unum.h"
|
||||
#include "unicode/ures.h"
|
||||
#include "unitconverter.h"
|
||||
#include "unitsdata.h"
|
||||
#include "unitsrouter.h"
|
||||
#include "uparse.h"
|
||||
#include "uresimp.h"
|
||||
|
||||
struct UnitConversionTestCase {
|
||||
const StringPiece source;
|
||||
|
@ -29,7 +32,8 @@ struct UnitConversionTestCase {
|
|||
const double expectedValue;
|
||||
};
|
||||
|
||||
using icu::number::impl::DecimalQuantity;
|
||||
using ::icu::number::impl::DecimalQuantity;
|
||||
using namespace ::icu::units;
|
||||
|
||||
class UnitsTest : public IntlTest {
|
||||
public:
|
||||
|
@ -37,6 +41,7 @@ class UnitsTest : public IntlTest {
|
|||
|
||||
void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = NULL);
|
||||
|
||||
void testUnitConstantFreshness();
|
||||
void testConversionCapability();
|
||||
void testConversions();
|
||||
void testPreferences();
|
||||
|
@ -53,6 +58,7 @@ void UnitsTest::runIndexedTest(int32_t index, UBool exec, const char *&name, cha
|
|||
logln("TestSuite UnitsTest: ");
|
||||
}
|
||||
TESTCASE_AUTO_BEGIN;
|
||||
TESTCASE_AUTO(testUnitConstantFreshness);
|
||||
TESTCASE_AUTO(testConversionCapability);
|
||||
TESTCASE_AUTO(testConversions);
|
||||
TESTCASE_AUTO(testPreferences);
|
||||
|
@ -63,10 +69,58 @@ void UnitsTest::runIndexedTest(int32_t index, UBool exec, const char *&name, cha
|
|||
TESTCASE_AUTO_END;
|
||||
}
|
||||
|
||||
// Tests the hard-coded constants in the code against constants that appear in
|
||||
// units.txt.
|
||||
void UnitsTest::testUnitConstantFreshness() {
|
||||
IcuTestErrorCode status(*this, "testUnitConstantFreshness");
|
||||
LocalUResourceBundlePointer unitsBundle(ures_openDirect(NULL, "units", status));
|
||||
LocalUResourceBundlePointer unitConstants(
|
||||
ures_getByKey(unitsBundle.getAlias(), "unitConstants", NULL, status));
|
||||
|
||||
while (ures_hasNext(unitConstants.getAlias())) {
|
||||
int32_t len;
|
||||
const char *constant = NULL;
|
||||
ures_getNextString(unitConstants.getAlias(), &len, &constant, status);
|
||||
|
||||
Factor factor;
|
||||
addSingleFactorConstant(constant, 1, POSITIVE, factor, status);
|
||||
if (status.errDataIfFailureAndReset(
|
||||
"addSingleFactorConstant(<%s>, ...).\n\n"
|
||||
"If U_INVALID_FORMAT_ERROR, please check that \"icu4c/source/i18n/unitconverter.cpp\" "
|
||||
"has all constants? Is \"%s\" a new constant?\n",
|
||||
constant, constant)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check the values of constants that have a simple numeric value
|
||||
factor.substituteConstants();
|
||||
int32_t uLen;
|
||||
UnicodeString uVal = ures_getStringByKey(unitConstants.getAlias(), constant, &uLen, status);
|
||||
CharString val;
|
||||
val.appendInvariantChars(uVal, status);
|
||||
if (status.errDataIfFailureAndReset("Failed to get constant value for %s.", constant)) {
|
||||
continue;
|
||||
}
|
||||
DecimalQuantity dqVal;
|
||||
UErrorCode parseStatus = U_ZERO_ERROR;
|
||||
// TODO(units): unify with strToDouble() in unitconverter.cpp
|
||||
dqVal.setToDecNumber(val.toStringPiece(), parseStatus);
|
||||
if (!U_SUCCESS(parseStatus)) {
|
||||
// Not simple to parse, skip validating this constant's value. (We
|
||||
// leave catching mistakes to the data-driven integration tests.)
|
||||
continue;
|
||||
}
|
||||
double expectedNumerator = dqVal.toDouble();
|
||||
assertEquals(UnicodeString("Constant ") + constant + u" numerator", expectedNumerator,
|
||||
factor.factorNum);
|
||||
assertEquals(UnicodeString("Constant ") + constant + u" denominator", 1.0, factor.factorDen);
|
||||
}
|
||||
}
|
||||
|
||||
void UnitsTest::testConversionCapability() {
|
||||
struct TestCase {
|
||||
const StringPiece source;
|
||||
const StringPiece target;
|
||||
const char *const source;
|
||||
const char *const target;
|
||||
const UnitsConvertibilityState expectedState;
|
||||
} testCases[]{
|
||||
{"meter", "foot", CONVERTIBLE}, //
|
||||
|
@ -75,7 +129,7 @@ void UnitsTest::testConversionCapability() {
|
|||
{"kilometer-per-second", "second-per-meter", RECIPROCAL}, //
|
||||
{"square-meter", "square-foot", CONVERTIBLE}, //
|
||||
{"kilometer-per-second", "foot-per-second", CONVERTIBLE}, //
|
||||
{"square-hectare", "p4-foot", CONVERTIBLE}, //
|
||||
{"square-hectare", "pow4-foot", CONVERTIBLE}, //
|
||||
{"square-kilometer-per-second", "second-per-square-meter", RECIPROCAL}, //
|
||||
};
|
||||
|
||||
|
@ -86,9 +140,11 @@ void UnitsTest::testConversionCapability() {
|
|||
MeasureUnit target = MeasureUnit::forIdentifier(testCase.target, status);
|
||||
|
||||
ConversionRates conversionRates(status);
|
||||
auto convertibility = icu::checkConvertibility(source, target, conversionRates, status);
|
||||
auto convertibility = checkConvertibility(source, target, conversionRates, status);
|
||||
|
||||
assertEquals("Conversion Capability", testCase.expectedState, convertibility);
|
||||
assertEquals(UnicodeString("Conversion Capability: ") + testCase.source + " to " +
|
||||
testCase.target,
|
||||
testCase.expectedState, convertibility);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,8 +152,8 @@ void UnitsTest::testSiPrefixes() {
|
|||
IcuTestErrorCode status(*this, "Units testSiPrefixes");
|
||||
// Test Cases
|
||||
struct TestCase {
|
||||
StringPiece source;
|
||||
StringPiece target;
|
||||
const char *source;
|
||||
const char *target;
|
||||
const double inputValue;
|
||||
const double expectedValue;
|
||||
} testCases[]{
|
||||
|
@ -118,15 +174,12 @@ void UnitsTest::testSiPrefixes() {
|
|||
MeasureUnit source = MeasureUnit::forIdentifier(testCase.source, status);
|
||||
MeasureUnit target = MeasureUnit::forIdentifier(testCase.target, status);
|
||||
|
||||
MaybeStackVector<MeasureUnit> units;
|
||||
units.emplaceBack(source);
|
||||
units.emplaceBack(target);
|
||||
|
||||
ConversionRates conversionRates(status);
|
||||
UnitConverter converter(source, target, conversionRates, status);
|
||||
|
||||
assertEqualsNear("test conversion", testCase.expectedValue,
|
||||
converter.convert(testCase.inputValue), 0.001);
|
||||
assertEqualsNear(UnicodeString("testSiPrefixes: ") + testCase.source + " to " + testCase.target,
|
||||
testCase.expectedValue, converter.convert(testCase.inputValue),
|
||||
0.0001 * testCase.expectedValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -135,8 +188,8 @@ void UnitsTest::testMass() {
|
|||
|
||||
// Test Cases
|
||||
struct TestCase {
|
||||
StringPiece source;
|
||||
StringPiece target;
|
||||
const char *source;
|
||||
const char *target;
|
||||
const double inputValue;
|
||||
const double expectedValue;
|
||||
} testCases[]{
|
||||
|
@ -156,15 +209,12 @@ void UnitsTest::testMass() {
|
|||
MeasureUnit source = MeasureUnit::forIdentifier(testCase.source, status);
|
||||
MeasureUnit target = MeasureUnit::forIdentifier(testCase.target, status);
|
||||
|
||||
MaybeStackVector<MeasureUnit> units;
|
||||
units.emplaceBack(source);
|
||||
units.emplaceBack(target);
|
||||
|
||||
ConversionRates conversionRates(status);
|
||||
UnitConverter converter(source, target, conversionRates, status);
|
||||
|
||||
assertEqualsNear("test conversion", testCase.expectedValue,
|
||||
converter.convert(testCase.inputValue), 0.001);
|
||||
assertEqualsNear(UnicodeString("testMass: ") + testCase.source + " to " + testCase.target,
|
||||
testCase.expectedValue, converter.convert(testCase.inputValue),
|
||||
0.0001 * testCase.expectedValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -172,8 +222,8 @@ void UnitsTest::testTemperature() {
|
|||
IcuTestErrorCode status(*this, "Units testTemperature");
|
||||
// Test Cases
|
||||
struct TestCase {
|
||||
StringPiece source;
|
||||
StringPiece target;
|
||||
const char *source;
|
||||
const char *target;
|
||||
const double inputValue;
|
||||
const double expectedValue;
|
||||
} testCases[]{
|
||||
|
@ -193,15 +243,12 @@ void UnitsTest::testTemperature() {
|
|||
MeasureUnit source = MeasureUnit::forIdentifier(testCase.source, status);
|
||||
MeasureUnit target = MeasureUnit::forIdentifier(testCase.target, status);
|
||||
|
||||
MaybeStackVector<MeasureUnit> units;
|
||||
units.emplaceBack(source);
|
||||
units.emplaceBack(target);
|
||||
|
||||
ConversionRates conversionRates(status);
|
||||
UnitConverter converter(source, target, conversionRates, status);
|
||||
|
||||
assertEqualsNear("test conversion", testCase.expectedValue,
|
||||
converter.convert(testCase.inputValue), 0.001);
|
||||
assertEqualsNear(UnicodeString("testTemperature: ") + testCase.source + " to " + testCase.target,
|
||||
testCase.expectedValue, converter.convert(testCase.inputValue),
|
||||
0.0001 * uprv_fabs(testCase.expectedValue));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -210,8 +257,8 @@ void UnitsTest::testArea() {
|
|||
|
||||
// Test Cases
|
||||
struct TestCase {
|
||||
StringPiece source;
|
||||
StringPiece target;
|
||||
const char *source;
|
||||
const char *target;
|
||||
const double inputValue;
|
||||
const double expectedValue;
|
||||
} testCases[]{
|
||||
|
@ -234,15 +281,12 @@ void UnitsTest::testArea() {
|
|||
MeasureUnit source = MeasureUnit::forIdentifier(testCase.source, status);
|
||||
MeasureUnit target = MeasureUnit::forIdentifier(testCase.target, status);
|
||||
|
||||
MaybeStackVector<MeasureUnit> units;
|
||||
units.emplaceBack(source);
|
||||
units.emplaceBack(target);
|
||||
|
||||
ConversionRates conversionRates(status);
|
||||
UnitConverter converter(source, target, conversionRates, status);
|
||||
|
||||
assertEqualsNear("test conversion", testCase.expectedValue,
|
||||
converter.convert(testCase.inputValue), 0.001);
|
||||
assertEqualsNear(UnicodeString("testArea: ") + testCase.source + " to " + testCase.target,
|
||||
testCase.expectedValue, converter.convert(testCase.inputValue),
|
||||
0.0001 * testCase.expectedValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -353,7 +397,7 @@ void unitsTestDataLineFn(void *context, char *fields[][2], int32_t fieldCount, U
|
|||
double got = converter.convert(1000);
|
||||
msg.clear();
|
||||
msg.append("Converting 1000 ", status).append(x, status).append(" to ", status).append(y, status);
|
||||
unitsTest->assertEqualsNear(msg.data(), expected, got, 0.0001);
|
||||
unitsTest->assertEqualsNear(msg.data(), expected, got, 0.0001 * expected);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -469,38 +513,14 @@ class ExpectedOutput {
|
|||
}
|
||||
};
|
||||
|
||||
// TODO(Hugo): Add a comment and Use AssertEqualsNear.
|
||||
// Checks a vector of Measure instances against ExpectedOutput.
|
||||
void checkOutput(UnitsTest *unitsTest, const char *msg, ExpectedOutput expected,
|
||||
const MaybeStackVector<Measure> &actual, double precision) {
|
||||
IcuTestErrorCode status(*unitsTest, "checkOutput");
|
||||
bool success = true;
|
||||
if (expected._compoundCount != actual.length()) {
|
||||
success = false;
|
||||
}
|
||||
for (int i = 0; i < actual.length(); i++) {
|
||||
if (i >= expected._compoundCount) {
|
||||
break;
|
||||
}
|
||||
|
||||
// assertEqualsNear("test conversion", expected._amounts[i],
|
||||
// actual[i]->getNumber().getDouble(status), 0.0001);
|
||||
|
||||
double diff = std::abs(expected._amounts[i] - actual[i]->getNumber().getDouble(status));
|
||||
double diffPercent = expected._amounts[i] != 0 ? diff / expected._amounts[i] : diff;
|
||||
if (diffPercent > precision) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (expected._measureUnits[i] != actual[i]->getUnit()) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
CharString testMessage("test case: ", status);
|
||||
CharString testMessage("Test case \"", status);
|
||||
testMessage.append(msg, status);
|
||||
testMessage.append(", expected output: ", status);
|
||||
testMessage.append("\": expected output: ", status);
|
||||
testMessage.append(expected.toDebugString().c_str(), status);
|
||||
testMessage.append(", obtained output:", status);
|
||||
for (int i = 0; i < actual.length(); i++) {
|
||||
|
@ -509,8 +529,19 @@ void checkOutput(UnitsTest *unitsTest, const char *msg, ExpectedOutput expected,
|
|||
testMessage.append(" ", status);
|
||||
testMessage.appendInvariantChars(actual[i]->getUnit().getIdentifier(), status);
|
||||
}
|
||||
|
||||
unitsTest->assertTrue(testMessage.data(), success);
|
||||
if (!unitsTest->assertEquals(testMessage.data(), expected._compoundCount, actual.length())) {
|
||||
return;
|
||||
};
|
||||
for (int i = 0; i < actual.length(); i++) {
|
||||
double permittedDiff = precision * expected._amounts[i];
|
||||
if (permittedDiff == 0) {
|
||||
// If 0 is expected, still permit a small delta.
|
||||
// TODO: revisit this experimentally chosen value:
|
||||
permittedDiff = 0.00000001;
|
||||
}
|
||||
unitsTest->assertEqualsNear(testMessage.data(), expected._amounts[i],
|
||||
actual[i]->getNumber().getDouble(status), permittedDiff);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -590,7 +621,8 @@ void unitPreferencesTestDataLineFn(void *context, char *fields[][2], int32_t fie
|
|||
if (status.errIfFailureAndReset("router.route(inputAmount, ...)")) {
|
||||
return;
|
||||
}
|
||||
checkOutput(unitsTest, msg.data(), expected, result, 0.0001);
|
||||
// TODO: revisit this experimentally chosen precision:
|
||||
checkOutput(unitsTest, msg.data(), expected, result, 0.0000000001);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Add table
Reference in a new issue