#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,
return result;
}
+} // namespace units
U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */
#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`.
MaybeStackVector<MeasureUnit> units_;
};
+} // namespace units
U_NAMESPACE_END
#endif //__COMPLEXUNITSCONVERTER_H__
*
* <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.
<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" />
<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" />
<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" />
<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" />
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
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. */
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);
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;
: 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;
}
// 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;
}
/// 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);
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;
// 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
safe,
chain,
status);
+ if (U_FAILURE(status)) {
+ return nullptr;
+ }
if (newCompactHandler == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return nullptr;
#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 {
/**
* 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.
/**
* 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.
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:
// 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:
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;
// 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);
} // 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;
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(
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];
}
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 */
#ifndef __NUMBER_LONGNAMES_H__
#define __NUMBER_LONGNAMES_H__
+#include "cmemory.h"
#include "unicode/uversion.h"
#include "number_utils.h"
#include "number_modifiers.h"
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;
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
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.
MultiplierFormatHandler multiplier;
} helpers;
+ // The MeasureUnit with which the output measurement is represented.
+ MeasureUnit outputUnit;
MicroProps() = default;
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.
U_ASSERT(exhausted);
} else {
// Safe path: copy self into the output micros.
+ U_ASSERT(!exhausted);
micros = *this;
}
}
#if !UCONFIG_NO_FORMATTING
+#include "unicode/measunit.h"
#include "unicode/numberformatter.h"
#include "number_utypes.h"
#include "util.h"
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;
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);
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:
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);
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;
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;
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' ');
}
}
}
+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'.');
}
}
+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);
// 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.
STATE_MEASURE_UNIT,
STATE_PER_MEASURE_UNIT,
STATE_IDENTIFIER_UNIT,
+ STATE_UNIT_USAGE,
STATE_CURRENCY_UNIT,
STATE_INTEGER_WIDTH,
STATE_NUMBERING_SYSTEM,
STEM_MEASURE_UNIT,
STEM_PER_MEASURE_UNIT,
STEM_UNIT,
+ STEM_UNIT_USAGE,
STEM_CURRENCY,
STEM_INTEGER_WIDTH,
STEM_NUMBERING_SYSTEM,
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);
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);
bool notation = false;
bool unit = false;
bool perUnit = false;
+ bool usage = false;
bool precision = false;
bool roundingMode = false;
bool grouper = false;
* 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
*
*/
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;
--- /dev/null
+// © 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 */
--- /dev/null
+// © 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 */
* 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 {
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;
};
number_rounding.cpp
number_scientific.cpp
number_skeletons.cpp
+number_usageprefs.cpp
number_utils.cpp
numfmt.cpp
numparse_affixes.cpp
class MutablePatternModifier;
class ImmutablePatternModifier;
struct DecimalFormatWarehouse;
+class UsagePrefsHandler;
/**
* Used for NumberRangeFormatter and implemented in numrange_fluent.cpp.
// 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;
};
/**
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 {
/** @internal */
Scale scale; // = Scale(); (benign value)
+ /** @internal */
+ Usage usage; // = Usage(); (no usage)
+
/** @internal */
const AffixPatternProvider* affixProvider = nullptr; // no ownership
* 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
* 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
#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 {
-namespace {
+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];
+ }
-/* 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);
+}
- // 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];
}
- 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);
+}
- // 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;
}
- // 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.
+ 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));
+ factorNum = std::pow(factorNum, std::abs(power));
+ factorDen = std::pow(factorDen, std::abs(power));
- if (shouldFlip) {
- // Flip Numerator and Denominator.
- std::swap(factorNum, factorDen);
- }
+ 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);
+void U_I18N_API Factor::flip() {
+ std::swap(factorNum, factorDen);
- for (int i = 0; i < CONSTANTS_COUNT; i++) {
- constants[i] *= -1;
- }
+ 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
+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));
+ double siApplied = std::pow(10.0, std::abs(siPrefix));
- if (siPrefix < 0) {
- factorDen *= siApplied;
- return;
- }
-
- factorNum *= siApplied;
+ if (siPrefix < 0) {
+ factorDen *= siApplied;
+ return;
}
- 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;
- }
+ factorNum *= siApplied;
+}
- auto absPower = std::abs(this->constants[i]);
- SigNum powerSig = this->constants[i] < 0 ? SigNum::NEGATIVE : SigNum::POSITIVE;
- double absConstantValue = std::pow(constantsValues[i], absPower);
+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;
+ }
- if (powerSig == SigNum::NEGATIVE) {
- this->factorDen *= absConstantValue;
- } else {
- this->factorNum *= absConstantValue;
- }
+ auto absPower = std::abs(this->constants[i]);
+ Signum powerSig = this->constants[i] < 0 ? Signum::NEGATIVE : Signum::POSITIVE;
+ double absConstantValue = std::pow(constantsValues[i], absPower);
- this->constants[i] = 0;
+ if (powerSig == Signum::NEGATIVE) {
+ this->factorDen *= absConstantValue;
+ } else {
+ this->factorNum *= absConstantValue;
}
+
+ this->constants[i] = 0;
}
-};
+}
+
+namespace {
/* Helpers */
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 =
baseStr = elementStr;
}
- addSingleFactorConstant(baseStr, power, sigNum, factor, status);
+ addSingleFactorConstant(baseStr, power, signum, factor, status);
}
/*
*/
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.
}
}
} // 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`
}
// 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;
return result * 1.000000000001;
}
+} // namespace units
U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */
#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`.
ConversionRate conversionRate_;
};
+} // namespace units
U_NAMESPACE_END
#endif //__UNITCONVERTER_H__
#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 {
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;
}
} 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;
}
} // 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));
preferenceCount = m->prefsCount;
}
+} // namespace units
U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */
#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.
CharString skeleton;
};
-namespace {
-
/**
* Metadata about the preferences in UnitPreferences::unitPrefs_.
*
bool *foundRegion) const;
};
-} // namespace
-
/**
* Unit Preferences information for various locales and usages.
*/
MaybeStackVector<UnitPreference> unitPrefs_;
};
+} // namespace units
U_NAMESPACE_END
#endif //__GETUNITSDATA_H__
#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) {
return;
}
+ outputUnits_.emplaceBackAndCheckErrorCode(status, complexTargetUnit);
converterPreferences_.emplaceBackAndCheckErrorCode(status, inputUnit, complexTargetUnit,
preference.geq, conversionRates, status);
if (U_FAILURE(status)) {
}
}
-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];
return lastConverter.convert(quantity, status);
}
+const MaybeStackVector<MeasureUnit> *UnitsRouter::getOutputUnits() const {
+ return &outputUnits_;
+}
+
+} // namespace units
U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */
#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
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__
number_representation format formatted_value_sbimpl
# PluralRules internals:
unifiedcache
+ # Unit Formatting
+ units
group: numberformatter
# ICU 60+ NumberFormatter API
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
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,
const UFieldPosition* expectedFieldPositions,
int32_t length);
+ // Tests FormattedValue's toString, toTempString, and nextPosition methods.
void checkMixedFormattedValue(
const char16_t* message,
const FormattedValue& fv,
void notationCompact();
void unitMeasure();
void unitCompoundMeasure();
+ void unitUsage();
void unitCurrency();
void unitPercent();
void percentParity();
TESTCASE_AUTO(notationCompact);
TESTCASE_AUTO(unitMeasure);
TESTCASE_AUTO(unitCompoundMeasure);
+ TESTCASE_AUTO(unitUsage);
TESTCASE_AUTO(unitCurrency);
TESTCASE_AUTO(unitPercent);
if (!quick) {
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",
#include "unitsdata.h"
#include "intltest.h"
+using namespace ::icu::units;
+
class UnitsDataTest : public IntlTest {
public:
UnitsDataTest() {}
#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;
const double expectedValue;
};
-using icu::number::impl::DecimalQuantity;
+using ::icu::number::impl::DecimalQuantity;
+using namespace ::icu::units;
class UnitsTest : public IntlTest {
public:
void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = NULL);
+ void testUnitConstantFreshness();
void testConversionCapability();
void testConversions();
void testPreferences();
logln("TestSuite UnitsTest: ");
}
TESTCASE_AUTO_BEGIN;
+ TESTCASE_AUTO(testUnitConstantFreshness);
TESTCASE_AUTO(testConversionCapability);
TESTCASE_AUTO(testConversions);
TESTCASE_AUTO(testPreferences);
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}, //
{"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}, //
};
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);
}
}
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[]{
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);
}
}
// Test Cases
struct TestCase {
- StringPiece source;
- StringPiece target;
+ const char *source;
+ const char *target;
const double inputValue;
const double expectedValue;
} testCases[]{
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);
}
}
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[]{
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));
}
}
// Test Cases
struct TestCase {
- StringPiece source;
- StringPiece target;
+ const char *source;
+ const char *target;
const double inputValue;
const double expectedValue;
} testCases[]{
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);
}
}
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);
}
/**
}
};
-// 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++) {
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);
+ }
}
/**
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);
}
/**