#if !UCONFIG_NO_FORMATTING
-#include <math.h>
+#include <cmath>
#include "cmemory.h"
#include "complexunitsconverter.h"
+#include "uarrsort.h"
#include "uassert.h"
#include "unicode/fmtable.h"
#include "unicode/localpointer.h"
+#include "unicode/measunit.h"
#include "unicode/measure.h"
#include "unitconverter.h"
U_NAMESPACE_BEGIN
namespace units {
-ComplexUnitsConverter::ComplexUnitsConverter(const MeasureUnit &inputUnit,
- const MeasureUnit &outputUnits,
- const ConversionRates &ratesInfo, UErrorCode &status) {
+ComplexUnitsConverter::ComplexUnitsConverter(const MeasureUnitImpl &inputUnit,
+ const MeasureUnitImpl &outputUnits,
+ const ConversionRates &ratesInfo, UErrorCode &status)
+ : units_(outputUnits.extractIndividualUnits(status)) {
+ if (U_FAILURE(status)) {
+ return;
+ }
- if (outputUnits.getComplexity(status) != UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) {
- unitConverters_.emplaceBackAndCheckErrorCode(status, inputUnit, outputUnits, ratesInfo, status);
- if (U_FAILURE(status)) {
- return;
+ U_ASSERT(units_.length() != 0);
+
+ // NOTE:
+ // This comparator is used to sort the units in a descending order. Therefore, we return -1 if
+ // the left is bigger than right and so on.
+ auto descendingCompareUnits = [](const void *context, const void *left, const void *right) {
+ UErrorCode status = U_ZERO_ERROR;
+
+ const auto *leftPointer = static_cast<const MeasureUnitImpl *const *>(left);
+ const auto *rightPointer = static_cast<const MeasureUnitImpl *const *>(right);
+
+ UnitConverter fromLeftToRight(**leftPointer, //
+ **rightPointer, //
+ *static_cast<const ConversionRates *>(context), //
+ status);
+
+ double rightFromOneLeft = fromLeftToRight.convert(1.0);
+ if (std::abs(rightFromOneLeft - 1.0) < 0.0000000001) { // Equals To
+ return 0;
+ } else if (rightFromOneLeft > 1.0) { // Greater Than
+ return -1;
}
- units_.emplaceBackAndCheckErrorCode(status, outputUnits);
- return;
- }
+ return 1; // Less Than
+ };
+
+ uprv_sortArray(units_.getAlias(), //
+ units_.length(), //
+ sizeof units_[0], /* NOTE: we have already asserted that the units_ is not empty.*/ //
+ descendingCompareUnits, //
+ &ratesInfo, //
+ false, //
+ &status //
+ );
// In case the `outputUnits` are `UMEASURE_UNIT_MIXED` such as `foot+inch`. In this case we need more
// converters to convert from the `inputUnit` to the first unit in the `outputUnits`. Then, a
// 2. convert the residual of 6.56168 feet (0.56168) to inches, which will be (6.74016
// inches)
// 3. then, the final result will be (6 feet and 6.74016 inches)
- int32_t length;
- auto singleUnits = outputUnits.splitToSingleUnits(length, status);
- MaybeStackVector<MeasureUnit> singleUnitsInOrder;
- for (int i = 0; i < length; ++i) {
- /**
- * TODO(younies): ensure units being in order by the biggest unit at first.
- *
- * HINT:
- * MaybeStackVector<SingleUnitImpl> singleUnitsInOrder = MeasureUnitImpl::forMeasureUnitMaybeCopy(outputUnits, status).units;
- * uprv_sortArray(
- * singleUnitsInOrder.getAlias(),
- * singleUnitsInOrder.length(),
- * sizeof(singleUnitsInOrder[0]),
- * compareSingleUnits,
- * nullptr,
- * false,
- * &status);
- */
- singleUnitsInOrder.emplaceBackAndCheckErrorCode(status, singleUnits[i]);
- }
-
- if (singleUnitsInOrder.length() == 0) {
- status = U_ILLEGAL_ARGUMENT_ERROR;
- return;
- }
-
- for (int i = 0, n = singleUnitsInOrder.length(); i < n; i++) {
+ for (int i = 0, n = units_.length(); i < n; i++) {
if (i == 0) { // first element
- unitConverters_.emplaceBackAndCheckErrorCode(status, inputUnit, *singleUnitsInOrder[i],
- ratesInfo, status);
+ unitConverters_.emplaceBackAndCheckErrorCode(status, inputUnit, *units_[i], ratesInfo,
+ status);
} else {
- unitConverters_.emplaceBackAndCheckErrorCode(status, *singleUnitsInOrder[i - 1],
- *singleUnitsInOrder[i], ratesInfo, status);
+ unitConverters_.emplaceBackAndCheckErrorCode(status, *units_[i - 1], *units_[i], ratesInfo,
+ status);
}
if (U_FAILURE(status)) {
return;
}
}
-
- units_.appendAll(singleUnitsInOrder, status);
}
UBool ComplexUnitsConverter::greaterThanOrEqual(double quantity, double limit) const {
Formattable formattableNewQuantity(newQuantity);
// NOTE: Measure would own its MeasureUnit.
- result.emplaceBackAndCheckErrorCode(status, formattableNewQuantity,
- new MeasureUnit(*units_[i]), status);
+ MeasureUnit *type = new MeasureUnit(units_[i]->copy(status).build(status));
+ result.emplaceBackAndCheckErrorCode(status, formattableNewQuantity, type, status);
// Keep the residual of the quantity.
// For example: `3.6 feet`, keep only `0.6 feet`
Formattable formattableQuantity(quantity);
// NOTE: Measure would own its MeasureUnit.
- result.emplaceBackAndCheckErrorCode(status, formattableQuantity, new MeasureUnit(*units_[i]),
- status);
+ MeasureUnit *type = new MeasureUnit(units_[i]->copy(status).build(status));
+ result.emplaceBackAndCheckErrorCode(status, formattableQuantity, type, status);
}
}
#define __COMPLEXUNITSCONVERTER_H__
#include "cmemory.h"
-#include "unicode/measunit.h"
+#include "measunit_impl.h"
+#include "unicode/errorcode.h"
+#include "unicode/measure.h"
#include "unitconverter.h"
#include "unitsdata.h"
U_NAMESPACE_BEGIN
-// Forward declarations
-class Measure;
-
namespace units {
/**
* single unit to another single unit). Therefore, `ComplexUnitsConverter` class contains multiple
* instances of the `UnitConverter` to perform the conversion.
*/
-class U_I18N_API ComplexUnitsConverter {
+class U_I18N_API ComplexUnitsConverter : UMemory {
public:
/**
* Constructor of `ComplexUnitsConverter`.
* @param outputUnits represents the output unit. could be any type. (single, compound or mixed).
* @param status
*/
- ComplexUnitsConverter(const MeasureUnit &inputUnit, const MeasureUnit &outputUnits,
+ ComplexUnitsConverter(const MeasureUnitImpl &inputUnit, const MeasureUnitImpl &outputUnits,
const ConversionRates &ratesInfo, UErrorCode &status);
// Returns true if the specified `quantity` of the `inputUnit`, expressed in terms of the biggest
private:
MaybeStackVector<UnitConverter> unitConverters_;
- MaybeStackVector<MeasureUnit> units_;
+ MaybeStackVector<MeasureUnitImpl> units_;
};
} // namespace units
return gSimpleUnits[index];
}
+MeasureUnitImpl::MeasureUnitImpl(const MeasureUnitImpl &other, UErrorCode &status) {
+ *this = other.copy(status);
+}
+
+MeasureUnitImpl::MeasureUnitImpl(const SingleUnitImpl &singleUnit, UErrorCode &status) {
+ this->append(singleUnit, status);
+}
+
MeasureUnitImpl MeasureUnitImpl::forIdentifier(StringPiece identifier, UErrorCode& status) {
return Parser::from(identifier, status).parse(status);
}
return appendImpl(*this, singleUnit, status);
}
+MaybeStackVector<MeasureUnitImpl> MeasureUnitImpl::extractIndividualUnits(UErrorCode &status) const {
+ MaybeStackVector<MeasureUnitImpl> result;
+
+ if (this->complexity != UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) {
+ result.emplaceBackAndCheckErrorCode(status, *this, status);
+ return result;
+ }
+
+ for (int32_t i = 0; i < units.length(); i++) {
+ result.emplaceBackAndCheckErrorCode(status, *units[i], status);
+ }
+
+ return result;
+}
+
MeasureUnit MeasureUnitImpl::build(UErrorCode& status) && {
serialize(*this, status);
return MeasureUnit(std::move(*this));
}
-
MeasureUnit MeasureUnit::forIdentifier(StringPiece identifier, UErrorCode& status) {
return Parser::from(identifier, status).parse(status).build(status);
}
* including mixed and compound units.
*/
struct U_I18N_API MeasureUnitImpl : public UMemory {
+ MeasureUnitImpl() = default;
+ MeasureUnitImpl(MeasureUnitImpl &&other) = default;
+ MeasureUnitImpl(const MeasureUnitImpl &other, UErrorCode &status);
+ MeasureUnitImpl(const SingleUnitImpl &singleUnit, UErrorCode &status);
+
+ MeasureUnitImpl &operator=(MeasureUnitImpl &&other) noexcept = default;
+
/** Extract the MeasureUnitImpl from a MeasureUnit. */
static inline const MeasureUnitImpl* get(const MeasureUnit& measureUnit) {
return measureUnit.fImpl;
*/
MeasureUnitImpl copy(UErrorCode& status) const;
+ /**
+ * Extracts the list of all the individual units inside the `MeasureUnitImpl`.
+ * For example:
+ * - if the `MeasureUnitImpl` is `foot-per-hour`
+ * it will return a list of 1 {`foot-per-hour`}
+ * - if the `MeasureUnitImpl` is `foot-and-inch`
+ * it will return a list of 2 { `foot`, `inch`}
+ */
+ MaybeStackVector<MeasureUnitImpl> extractIndividualUnits(UErrorCode &status) const;
+
/** Mutates this MeasureUnitImpl to take the reciprocal. */
void takeReciprocal(UErrorCode& status);
}
quantity.roundToInfinity(); // Enables toDouble
- auto routed = fUnitsRouter.route(quantity.toDouble(), status);
- micros.outputUnit = routed[0]->getUnit();
- quantity.setToDouble(routed[0]->getNumber().getDouble());
+ const auto routed = fUnitsRouter.route(quantity.toDouble(), status);
+ const auto& routedUnits = routed.measures;
+ micros.outputUnit = routedUnits[0]->getUnit();
+ quantity.setToDouble(routedUnits[0]->getNumber().getDouble());
// TODO(units): here we are always overriding Precision. (1) get precision
// from fUnitsRouter, (2) ensure we use the UnitPreference skeleton's
// precision only when there isn't an explicit override we prefer to use.
// This needs to be handled within
// NumberFormatterImpl::macrosToMicroGenerator in number_formatimpl.cpp
+ // TODO: Use precision from `routed` result.
Precision precision = Precision::integer().withMinDigits(2);
UNumberFormatRoundingMode roundingMode;
// Temporary until ICU 64?
#include "double-conversion-string-to-double.h"
#include "measunit_impl.h"
#include "uassert.h"
+#include "unicode/errorcode.h"
#include "unicode/localpointer.h"
-#include "unicode/measunit.h"
#include "unicode/stringpiece.h"
#include "unitconverter.h"
#include <algorithm>
}
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;
}
// Load Factor of a compound source unit.
-Factor loadCompoundFactor(const MeasureUnit &source, const ConversionRates &ratesInfo,
+Factor loadCompoundFactor(const MeasureUnitImpl &source, const ConversionRates &ratesInfo,
UErrorCode &status) {
Factor result;
- MeasureUnitImpl memory;
- const auto &compoundSourceUnit = MeasureUnitImpl::forMeasureUnit(source, memory, status);
- if (U_FAILURE(status)) return result;
-
- for (int32_t i = 0, n = compoundSourceUnit.units.length(); i < n; i++) {
- auto singleUnit = *compoundSourceUnit.units[i]; // a SingleUnitImpl
+ for (int32_t i = 0, n = source.units.length(); i < n; i++) {
+ SingleUnitImpl singleUnit = *source.units[i];
Factor singleFactor = loadSingleFactor(singleUnit.getSimpleUnitID(), ratesInfo, status);
if (U_FAILURE(status)) return result;
/**
* Checks if the source unit and the target unit are simple. For example celsius or fahrenheit. But not
* square-celsius or square-fahrenheit.
+ *
+ * NOTE:
+ * Empty unit means simple unit.
*/
-UBool checkSimpleUnit(const MeasureUnit &unit, UErrorCode &status) {
- MeasureUnitImpl memory;
- const auto &compoundSourceUnit = MeasureUnitImpl::forMeasureUnit(unit, memory, status);
+UBool checkSimpleUnit(const MeasureUnitImpl &unit, UErrorCode &status) {
if (U_FAILURE(status)) return false;
- if (compoundSourceUnit.complexity != UMEASURE_UNIT_SINGLE) {
+ if (unit.complexity != UMEASURE_UNIT_SINGLE) {
return false;
}
+ if (unit.units.length() == 0) {
+ // Empty units means simple unit.
+ return true;
+ }
- U_ASSERT(compoundSourceUnit.units.length() == 1);
- auto singleUnit = *(compoundSourceUnit.units[0]);
+ auto singleUnit = *(unit.units[0]);
if (singleUnit.dimensionality != 1 || singleUnit.siPrefix != UMEASURE_SI_PREFIX_ONE) {
return false;
}
+
return true;
}
/**
* Extract conversion rate from `source` to `target`
*/
-void loadConversionRate(ConversionRate &conversionRate, const MeasureUnit &source,
- const MeasureUnit &target, UnitsConvertibilityState unitsState,
+void loadConversionRate(ConversionRate &conversionRate, const MeasureUnitImpl &source,
+ const MeasureUnitImpl &target, Convertibility unitsState,
const ConversionRates &ratesInfo, UErrorCode &status) {
// Represents the conversion factor from the source to the target.
Factor finalFactor;
// Merger Factors
finalFactor.multiplyBy(sourceToBase);
- if (unitsState == UnitsConvertibilityState::CONVERTIBLE) {
+ if (unitsState == Convertibility::CONVERTIBLE) {
finalFactor.divideBy(targetToBase);
- } else if (unitsState == UnitsConvertibilityState::RECIPROCAL) {
+ } else if (unitsState == Convertibility::RECIPROCAL) {
finalFactor.multiplyBy(targetToBase);
} else {
status = UErrorCode::U_ARGUMENT_TYPE_MISMATCH;
targetToBase.offset * targetToBase.factorDen / targetToBase.factorNum;
}
- conversionRate.reciprocal = unitsState == UnitsConvertibilityState::RECIPROCAL;
+ conversionRate.reciprocal = unitsState == Convertibility::RECIPROCAL;
+}
+
+struct UnitIndexAndDimension : UMemory {
+ int32_t index = 0;
+ int32_t dimensionality = 0;
+
+ UnitIndexAndDimension(const SingleUnitImpl &singleUnit, int32_t multiplier) {
+ index = singleUnit.index;
+ dimensionality = singleUnit.dimensionality * multiplier;
+ }
+};
+
+void mergeSingleUnitWithDimension(MaybeStackVector<UnitIndexAndDimension> &unitIndicesWithDimension,
+ const SingleUnitImpl &shouldBeMerged, int32_t multiplier) {
+ for (int32_t i = 0; i < unitIndicesWithDimension.length(); i++) {
+ auto &unitWithIndex = *unitIndicesWithDimension[i];
+ if (unitWithIndex.index == shouldBeMerged.index) {
+ unitWithIndex.dimensionality += shouldBeMerged.dimensionality * multiplier;
+ return;
+ }
+ }
+
+ unitIndicesWithDimension.emplaceBack(shouldBeMerged, multiplier);
+}
+
+void mergeUnitsAndDimensions(MaybeStackVector<UnitIndexAndDimension> &unitIndicesWithDimension,
+ const MeasureUnitImpl &shouldBeMerged, int32_t multiplier) {
+ for (int32_t unit_i = 0; unit_i < shouldBeMerged.units.length(); unit_i++) {
+ auto singleUnit = *shouldBeMerged.units[unit_i];
+ mergeSingleUnitWithDimension(unitIndicesWithDimension, singleUnit, multiplier);
+ }
+}
+
+UBool checkAllDimensionsAreZeros(const MaybeStackVector<UnitIndexAndDimension> &dimensionVector) {
+ for (int32_t i = 0; i < dimensionVector.length(); i++) {
+ if (dimensionVector[i]->dimensionality != 0) {
+ return false;
+ }
+ }
+
+ return true;
}
} // namespace
* 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`
*/
-MeasureUnit U_I18N_API extractCompoundBaseUnit(const MeasureUnit &source,
- const ConversionRates &conversionRates,
- UErrorCode &status) {
- MeasureUnit result;
- int32_t count;
- const auto singleUnits = source.splitToSingleUnits(count, status);
+MeasureUnitImpl U_I18N_API extractCompoundBaseUnit(const MeasureUnitImpl &source,
+ const ConversionRates &conversionRates,
+ UErrorCode &status) {
+
+ MeasureUnitImpl result;
if (U_FAILURE(status)) return result;
- for (int i = 0; i < count; ++i) {
- const auto &singleUnit = singleUnits[i];
+ const auto &singleUnits = source.units;
+ for (int i = 0, count = singleUnits.length(); i < count; ++i) {
+ const auto &singleUnit = *singleUnits[i];
// Extract `ConversionRateInfo` using the absolute unit. For example: in case of `square-meter`,
// we will use `meter`
- const auto singleUnitImpl = SingleUnitImpl::forMeasureUnit(singleUnit, status);
const auto rateInfo =
- conversionRates.extractConversionInfo(singleUnitImpl.getSimpleUnitID(), status);
+ conversionRates.extractConversionInfo(singleUnit.getSimpleUnitID(), status);
if (U_FAILURE(status)) {
return result;
}
// Multiply the power of the singleUnit by the power of the baseUnit. For example, square-hectare
// must be pow4-meter. (NOTE: hectare --> square-meter)
- auto compoundBaseUnit = MeasureUnit::forIdentifier(rateInfo->baseUnit.toStringPiece(), status);
-
- int32_t baseUnitsCount;
- auto baseUnits = compoundBaseUnit.splitToSingleUnits(baseUnitsCount, status);
- for (int j = 0; j < baseUnitsCount; j++) {
- int8_t newDimensionality =
- baseUnits[j].getDimensionality(status) * singleUnit.getDimensionality(status);
- result = result.product(baseUnits[j].withDimensionality(newDimensionality, status), status);
+ auto baseUnits =
+ MeasureUnitImpl::forIdentifier(rateInfo->baseUnit.toStringPiece(), status).units;
+ for (int32_t i = 0, baseUnitsCount = baseUnits.length(); i < baseUnitsCount; i++) {
+ baseUnits[i]->dimensionality *= singleUnit.dimensionality;
+ // TODO: Deal with SI-prefix
+ result.append(*baseUnits[i], status);
if (U_FAILURE(status)) {
return result;
return result;
}
-UnitsConvertibilityState U_I18N_API checkConvertibility(const MeasureUnit &source,
- const MeasureUnit &target,
- const ConversionRates &conversionRates,
- UErrorCode &status) {
- if (source.getComplexity(status) == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED ||
- target.getComplexity(status) == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) {
+/**
+ * Determine the convertibility between `source` and `target`.
+ * For example:
+ * `meter` and `foot` are `CONVERTIBLE`.
+ * `meter-per-second` and `second-per-meter` are `RECIPROCAL`.
+ * `meter` and `pound` are `UNCONVERTIBLE`.
+ *
+ * NOTE:
+ * Only works with SINGLE and COMPOUND units. If one of the units is a
+ * MIXED unit, an error will occur. For more information, see UMeasureUnitComplexity.
+ */
+Convertibility U_I18N_API extractConvertibility(const MeasureUnitImpl &source,
+ const MeasureUnitImpl &target,
+ const ConversionRates &conversionRates,
+ UErrorCode &status) {
+
+ if (source.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED ||
+ target.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) {
status = U_INTERNAL_PROGRAM_ERROR;
return UNCONVERTIBLE;
}
- auto sourceBaseUnit = extractCompoundBaseUnit(source, conversionRates, status);
- auto targetBaseUnit = extractCompoundBaseUnit(target, conversionRates, status);
-
+ MeasureUnitImpl sourceBaseUnit = extractCompoundBaseUnit(source, conversionRates, status);
+ MeasureUnitImpl targetBaseUnit = extractCompoundBaseUnit(target, conversionRates, status);
if (U_FAILURE(status)) return UNCONVERTIBLE;
- if (sourceBaseUnit == targetBaseUnit) return CONVERTIBLE;
- if (sourceBaseUnit == targetBaseUnit.reciprocal(status)) return RECIPROCAL;
+ MaybeStackVector<UnitIndexAndDimension> convertible;
+ MaybeStackVector<UnitIndexAndDimension> reciprocal;
+
+ mergeUnitsAndDimensions(convertible, sourceBaseUnit, 1);
+ mergeUnitsAndDimensions(reciprocal, sourceBaseUnit, 1);
+
+ mergeUnitsAndDimensions(convertible, targetBaseUnit, -1);
+ mergeUnitsAndDimensions(reciprocal, targetBaseUnit, 1);
- auto sourceSimplified = sourceBaseUnit.simplify(status);
- auto targetSimplified = targetBaseUnit.simplify(status);
+ if (checkAllDimensionsAreZeros(convertible)) {
+ return CONVERTIBLE;
+ }
- if (sourceSimplified == targetSimplified) return CONVERTIBLE;
- if (sourceSimplified == targetSimplified.reciprocal(status)) return RECIPROCAL;
+ if (checkAllDimensionsAreZeros(reciprocal)) {
+ return RECIPROCAL;
+ }
return UNCONVERTIBLE;
}
-UnitConverter::UnitConverter(MeasureUnit source, MeasureUnit target, const ConversionRates &ratesInfo,
- UErrorCode &status) {
-
- if (source.getComplexity(status) == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED ||
- target.getComplexity(status) == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) {
+UnitConverter::UnitConverter(const MeasureUnitImpl &source, const MeasureUnitImpl &target,
+ const ConversionRates &ratesInfo, UErrorCode &status)
+ : conversionRate_(source.copy(status), target.copy(status)) {
+ if (source.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED ||
+ target.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) {
status = U_INTERNAL_PROGRAM_ERROR;
return;
}
- UnitsConvertibilityState unitsState = checkConvertibility(source, target, ratesInfo, status);
+ Convertibility unitsState = extractConvertibility(source, target, ratesInfo, status);
if (U_FAILURE(status)) return;
- if (unitsState == UnitsConvertibilityState::UNCONVERTIBLE) {
+ if (unitsState == Convertibility::UNCONVERTIBLE) {
status = U_INTERNAL_PROGRAM_ERROR;
return;
}
- conversionRate_.source = source;
- conversionRate_.target = target;
-
- loadConversionRate(conversionRate_, source, target, unitsState, ratesInfo, status);
+ loadConversionRate(conversionRate_, conversionRate_.source, conversionRate_.target, unitsState,
+ ratesInfo, status);
}
double UnitConverter::convert(double inputValue) const {
#ifndef __UNITCONVERTER_H__
#define __UNITCONVERTER_H__
-#include "unicode/measunit.h"
+#include "cmemory.h"
+#include "measunit_impl.h"
+#include "unicode/errorcode.h"
#include "unicode/stringpiece.h"
#include "unicode/uobject.h"
+#include "unitconverter.h"
#include "unitsdata.h"
U_NAMESPACE_BEGIN
CONSTANTS_COUNT
};
+// 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.
+static const double constantsValues[CONSTANTS_COUNT] = {
+ 0.3048, // CONSTANT_FT2M
+ 411557987.0 / 131002976.0, // CONSTANT_PI
+ 9.80665, // CONSTANT_GRAVITY
+ 6.67408E-11, // CONSTANT_G
+ 0.00454609, // CONSTANT_GAL_IMP2M3
+ 0.45359237, // CONSTANT_LB2KG
+};
+
typedef enum Signum {
NEGATIVE = -1,
POSITIVE = 1,
/**
* Represents the conversion rate between `source` and `target`.
*/
-struct ConversionRate {
- MeasureUnit source;
- MeasureUnit target;
+struct ConversionRate : public UMemory {
+ const MeasureUnitImpl source;
+ const MeasureUnitImpl target;
double factorNum = 1;
double factorDen = 1;
double sourceOffset = 0;
double targetOffset = 0;
bool reciprocal = false;
+
+ ConversionRate(MeasureUnitImpl &&source, MeasureUnitImpl &&target)
+ : source(std::move(source)), target(std::move(target)) {}
};
-enum U_I18N_API UnitsConvertibilityState {
+enum U_I18N_API Convertibility {
RECIPROCAL,
CONVERTIBLE,
UNCONVERTIBLE,
};
-MeasureUnit U_I18N_API extractCompoundBaseUnit(const MeasureUnit &source,
- const ConversionRates &conversionRates,
- UErrorCode &status);
+MeasureUnitImpl U_I18N_API extractCompoundBaseUnit(const MeasureUnitImpl &source,
+ const ConversionRates &conversionRates,
+ UErrorCode &status);
/**
* Check if the convertibility between `source` and `target`.
* Only works with SINGLE and COMPOUND units. If one of the units is a
* MIXED unit, an error will occur. For more information, see UMeasureUnitComplexity.
*/
-UnitsConvertibilityState U_I18N_API checkConvertibility(const MeasureUnit &source,
- const MeasureUnit &target,
- const ConversionRates &conversionRates,
- UErrorCode &status);
+Convertibility U_I18N_API extractConvertibility(const MeasureUnitImpl &source,
+ const MeasureUnitImpl &target,
+ const ConversionRates &conversionRates,
+ UErrorCode &status);
/**
* Converts from a source `MeasureUnit` to a target `MeasureUnit`.
* @param ratesInfo Contains all the needed conversion rates.
* @param status
*/
- UnitConverter(MeasureUnit source, MeasureUnit target, const ConversionRates &ratesInfo,
- UErrorCode &status);
+ UnitConverter(const MeasureUnitImpl &source, const MeasureUnitImpl &target,
+ const ConversionRates &ratesInfo, UErrorCode &status);
/**
* Convert a value in the source unit to another value in the target unit.
dq.setToDecNumber(geq.data(), status);
up->geq = dq.toDouble();
} else if (uprv_strcmp(key, "skeleton") == 0) {
- int32_t length;
- const UChar *s = value.getString(length, status);
- up->skeleton.appendInvariantChars(s, length, status);
+ up->skeleton = value.getUnicodeString(status);
}
}
}
#ifndef __GETUNITSDATA_H__
#define __GETUNITSDATA_H__
+#include <limits>
+
#include "charstr.h"
#include "cmemory.h"
#include "unicode/stringpiece.h"
// Encapsulates unitPreferenceData information from units resources, specifying
// a sequence of output unit preferences.
struct U_I18N_API UnitPreference : public UMemory {
- UnitPreference() : geq(1) {}
+ // Set geq to 1.0 by default
+ UnitPreference() : geq(1.0) {}
CharString unit;
double geq;
- CharString skeleton;
+ UnicodeString skeleton;
};
/**
#include "charstr.h"
#include "cmemory.h"
+#include "cstring.h"
+#include "measunit_impl.h"
+#include "number_decimalquantity.h"
+#include "resource.h"
#include "unicode/measure.h"
-#include "unitconverter.h"
#include "unitsdata.h"
#include "unitsrouter.h"
ConversionRates conversionRates(status);
UnitPreferences prefs(status);
- MeasureUnit baseUnit = extractCompoundBaseUnit(inputUnit, conversionRates, status);
+ MeasureUnitImpl inputUnitImpl = MeasureUnitImpl::forMeasureUnitMaybeCopy(inputUnit, status);
+ MeasureUnit baseUnit =
+ (extractCompoundBaseUnit(inputUnitImpl, conversionRates, status)).build(status);
CharString category = getUnitCategory(baseUnit.getIdentifier(), status);
const UnitPreference *const *unitPreferences;
for (int i = 0; i < preferencesCount; ++i) {
const auto &preference = *unitPreferences[i];
- MeasureUnit complexTargetUnit = MeasureUnit::forIdentifier(preference.unit.data(), status);
+ MeasureUnitImpl complexTargetUnitImpl =
+ MeasureUnitImpl::forIdentifier(preference.unit.data(), status);
if (U_FAILURE(status)) {
return;
}
- outputUnits_.emplaceBackAndCheckErrorCode(status, complexTargetUnit);
- converterPreferences_.emplaceBackAndCheckErrorCode(status, inputUnit, complexTargetUnit,
- preference.geq, conversionRates, status);
+ UnicodeString precision = preference.skeleton;
+
+ // For now, we only have "precision-increment" in Units Preferences skeleton.
+ // Therefore, we check if the skeleton starts with "precision-increment" and force the program to
+ // fail otherwise.
+ // NOTE:
+ // It is allowed to have an empty precision.
+ if (!precision.isEmpty() && !precision.startsWith(u"precision-increment", 19)) {
+ status = U_INTERNAL_PROGRAM_ERROR;
+ return;
+ }
+
+ outputUnits_.emplaceBackAndCheckErrorCode(status,
+ complexTargetUnitImpl.copy(status).build(status));
+ converterPreferences_.emplaceBackAndCheckErrorCode(status, inputUnitImpl, complexTargetUnitImpl,
+ preference.geq, std::move(precision),
+ conversionRates, status);
+
if (U_FAILURE(status)) {
return;
}
}
}
-MaybeStackVector<Measure> UnitsRouter::route(double quantity, UErrorCode &status) const {
+RouteResult UnitsRouter::route(double quantity, UErrorCode &status) const {
for (int i = 0, n = converterPreferences_.length(); i < n; i++) {
const auto &converterPreference = *converterPreferences_[i];
if (converterPreference.converter.greaterThanOrEqual(quantity, converterPreference.limit)) {
- return converterPreference.converter.convert(quantity, status);
+ return RouteResult(converterPreference.converter.convert(quantity, status), //
+ converterPreference.precision //
+ );
}
}
// In case of the `quantity` does not fit in any converter limit, use the last converter.
- const auto &lastConverter = (*converterPreferences_[converterPreferences_.length() - 1]).converter;
- return lastConverter.convert(quantity, status);
+ const auto &lastConverterPreference = (*converterPreferences_[converterPreferences_.length() - 1]);
+ return RouteResult(lastConverterPreference.converter.convert(quantity, status), //
+ lastConverterPreference.precision //
+ );
}
const MaybeStackVector<MeasureUnit> *UnitsRouter::getOutputUnits() const {
+ // TODO: consider pulling this from converterPreferences_ and dropping
+ // outputUnits_?
return &outputUnits_;
}
#include "cmemory.h"
#include "complexunitsconverter.h"
+#include "measunit_impl.h"
#include "unicode/measunit.h"
#include "unicode/stringpiece.h"
#include "unicode/uobject.h"
namespace units {
+struct RouteResult : UMemory {
+ MaybeStackVector<Measure> measures;
+ UnicodeString precision;
+
+ RouteResult(MaybeStackVector<Measure> measures, UnicodeString precision)
+ : measures(std::move(measures)), precision(std::move(precision)) {}
+};
+
/**
* 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
struct ConverterPreference : UMemory {
ComplexUnitsConverter converter;
double limit;
+ UnicodeString precision;
- ConverterPreference(MeasureUnit source, MeasureUnit complexTarget, double limit,
- const ConversionRates &ratesInfo, UErrorCode &status)
- : converter(source, complexTarget, ratesInfo, status), limit(limit) {}
+ // In case there is no limit, the limit will be -inf.
+ ConverterPreference(const MeasureUnitImpl &source, const MeasureUnitImpl &complexTarget,
+ UnicodeString precision, const ConversionRates &ratesInfo, UErrorCode &status)
+ : ConverterPreference(source, complexTarget, std::numeric_limits<double>::lowest(), precision,
+ ratesInfo, status) {}
- ConverterPreference(MeasureUnit source, MeasureUnit complexTarget, const ConversionRates &ratesInfo,
+ ConverterPreference(const MeasureUnitImpl &source, const MeasureUnitImpl &complexTarget,
+ double limit, UnicodeString precision, const ConversionRates &ratesInfo,
UErrorCode &status)
- : ConverterPreference(source, complexTarget, std::numeric_limits<double>::lowest(), ratesInfo,
- status) {}
+ : converter(source, complexTarget, ratesInfo, status), limit(limit),
+ precision(std::move(precision)) {}
};
/**
public:
UnitsRouter(MeasureUnit inputUnit, StringPiece locale, StringPiece usage, UErrorCode &status);
- MaybeStackVector<Measure> route(double quantity, UErrorCode &status) const;
+ RouteResult route(double quantity, UErrorCode &status) const;
/**
* Returns the list of possible output units, i.e. the full set of
const MaybeStackVector<MeasureUnit> *getOutputUnits() const;
private:
- // List of possible output units
+ // List of possible output units. TODO: converterPreferences_ now also has
+ // this data available. Maybe drop outputUnits_ and have getOutputUnits
+ // construct a the list from data in converterPreferences_ instead?
MaybeStackVector<MeasureUnit> outputUnits_;
MaybeStackVector<ConverterPreference> converterPreferences_;
group: unitsformatter
unitsdata.o unitconverter.o complexunitsconverter.o unitsrouter.o
deps
- resourcebundle units_extra double_conversion number_representation formattable
+ resourcebundle units_extra double_conversion number_representation formattable sort
group: decnumber
decContext.o decNumber.o
void testUnitConstantFreshness();
void testConversionCapability();
void testConversions();
+ void testComplexUnitConverterSorting();
void testPreferences();
void testSiPrefixes();
void testMass();
TESTCASE_AUTO(testUnitConstantFreshness);
TESTCASE_AUTO(testConversionCapability);
TESTCASE_AUTO(testConversions);
+ TESTCASE_AUTO(testComplexUnitConverterSorting);
TESTCASE_AUTO(testPreferences);
TESTCASE_AUTO(testSiPrefixes);
TESTCASE_AUTO(testMass);
struct TestCase {
const char *const source;
const char *const target;
- const UnitsConvertibilityState expectedState;
+ const Convertibility expectedState;
} testCases[]{
- {"meter", "foot", CONVERTIBLE}, //
- {"kilometer", "foot", CONVERTIBLE}, //
- {"hectare", "square-foot", CONVERTIBLE}, //
- {"kilometer-per-second", "second-per-meter", RECIPROCAL}, //
- {"square-meter", "square-foot", CONVERTIBLE}, //
- {"kilometer-per-second", "foot-per-second", CONVERTIBLE}, //
- {"square-hectare", "pow4-foot", CONVERTIBLE}, //
- {"square-kilometer-per-second", "second-per-square-meter", RECIPROCAL}, //
+ {"meter", "foot", CONVERTIBLE}, //
+ {"kilometer", "foot", CONVERTIBLE}, //
+ {"hectare", "square-foot", CONVERTIBLE}, //
+ {"kilometer-per-second", "second-per-meter", RECIPROCAL}, //
+ {"square-meter", "square-foot", CONVERTIBLE}, //
+ {"kilometer-per-second", "foot-per-second", CONVERTIBLE}, //
+ {"square-hectare", "pow4-foot", CONVERTIBLE}, //
+ {"square-kilometer-per-second", "second-per-square-meter", RECIPROCAL}, //
+ {"cubic-kilometer-per-second-meter", "second-per-square-meter", RECIPROCAL}, //
};
for (const auto &testCase : testCases) {
UErrorCode status = U_ZERO_ERROR;
- MeasureUnit source = MeasureUnit::forIdentifier(testCase.source, status);
- MeasureUnit target = MeasureUnit::forIdentifier(testCase.target, status);
+ MeasureUnitImpl source = MeasureUnitImpl::forIdentifier(testCase.source, status);
+ MeasureUnitImpl target = MeasureUnitImpl::forIdentifier(testCase.target, status);
ConversionRates conversionRates(status);
- auto convertibility = checkConvertibility(source, target, conversionRates, status);
+ auto convertibility = extractConvertibility(source, target, conversionRates, status);
assertEquals(UnicodeString("Conversion Capability: ") + testCase.source + " to " +
testCase.target,
for (const auto &testCase : testCases) {
UErrorCode status = U_ZERO_ERROR;
- MeasureUnit source = MeasureUnit::forIdentifier(testCase.source, status);
- MeasureUnit target = MeasureUnit::forIdentifier(testCase.target, status);
+ MeasureUnitImpl source = MeasureUnitImpl::forIdentifier(testCase.source, status);
+ MeasureUnitImpl target = MeasureUnitImpl::forIdentifier(testCase.target, status);
ConversionRates conversionRates(status);
UnitConverter converter(source, target, conversionRates, status);
for (const auto &testCase : testCases) {
UErrorCode status = U_ZERO_ERROR;
- MeasureUnit source = MeasureUnit::forIdentifier(testCase.source, status);
- MeasureUnit target = MeasureUnit::forIdentifier(testCase.target, status);
+ MeasureUnitImpl source = MeasureUnitImpl::forIdentifier(testCase.source, status);
+ MeasureUnitImpl target = MeasureUnitImpl::forIdentifier(testCase.target, status);
ConversionRates conversionRates(status);
UnitConverter converter(source, target, conversionRates, status);
for (const auto &testCase : testCases) {
UErrorCode status = U_ZERO_ERROR;
- MeasureUnit source = MeasureUnit::forIdentifier(testCase.source, status);
- MeasureUnit target = MeasureUnit::forIdentifier(testCase.target, status);
+ MeasureUnitImpl source = MeasureUnitImpl::forIdentifier(testCase.source, status);
+ MeasureUnitImpl target = MeasureUnitImpl::forIdentifier(testCase.target, status);
ConversionRates conversionRates(status);
UnitConverter converter(source, target, conversionRates, status);
for (const auto &testCase : testCases) {
UErrorCode status = U_ZERO_ERROR;
- MeasureUnit source = MeasureUnit::forIdentifier(testCase.source, status);
- MeasureUnit target = MeasureUnit::forIdentifier(testCase.target, status);
+ MeasureUnitImpl source = MeasureUnitImpl::forIdentifier(testCase.source, status);
+ MeasureUnitImpl target = MeasureUnitImpl::forIdentifier(testCase.target, status);
ConversionRates conversionRates(status);
UnitConverter converter(source, target, conversionRates, status);
return;
}
- MeasureUnit sourceUnit = MeasureUnit::forIdentifier(x, status);
+ MeasureUnitImpl sourceUnit = MeasureUnitImpl::forIdentifier(x, status);
if (status.errIfFailureAndReset("forIdentifier(\"%.*s\")", x.length(), x.data())) {
return;
}
- MeasureUnit targetUnit = MeasureUnit::forIdentifier(y, status);
+ MeasureUnitImpl targetUnit = MeasureUnitImpl::forIdentifier(y, status);
if (status.errIfFailureAndReset("forIdentifier(\"%.*s\")", y.length(), y.data())) {
return;
}
expected, commentConversionFormula.length(), commentConversionFormula.data());
// Convertibility:
- auto convertibility = checkConvertibility(sourceUnit, targetUnit, *ctx->conversionRates, status);
- if (status.errIfFailureAndReset("checkConvertibility(<%s>, <%s>, ...)", sourceUnit.getIdentifier(),
- targetUnit.getIdentifier())) {
+ auto convertibility = extractConvertibility(sourceUnit, targetUnit, *ctx->conversionRates, status);
+ if (status.errIfFailureAndReset("extractConvertibility(<%s>, <%s>, ...)",
+ sourceUnit.identifier.data(), targetUnit.identifier.data())) {
return;
}
CharString msg;
msg.append("convertible: ", status)
- .append(sourceUnit.getIdentifier(), status)
+ .append(sourceUnit.identifier.data(), status)
.append(" -> ", status)
- .append(targetUnit.getIdentifier(), status);
+ .append(targetUnit.identifier.data(), status);
if (status.errIfFailureAndReset("msg construction")) {
return;
}
// Conversion:
UnitConverter converter(sourceUnit, targetUnit, *ctx->conversionRates, status);
if (status.errIfFailureAndReset("constructor: UnitConverter(<%s>, <%s>, status)",
- sourceUnit.getIdentifier(), targetUnit.getIdentifier())) {
+ sourceUnit.identifier.data(), targetUnit.identifier.data())) {
return;
}
double got = converter.convert(1000);
}
}
+void UnitsTest::testComplexUnitConverterSorting() {
+ IcuTestErrorCode status(*this, "UnitsTest::testComplexUnitConverterSorting");
+
+ MeasureUnitImpl source = MeasureUnitImpl::forIdentifier("meter", status);
+ MeasureUnitImpl target = MeasureUnitImpl::forIdentifier("inch-and-foot", status);
+ ConversionRates conversionRates(status);
+
+ ComplexUnitsConverter complexConverter(source, target, conversionRates, status);
+ auto measures = complexConverter.convert(10.0, status);
+
+ U_ASSERT(measures.length() == 2);
+ assertEquals("Sorted Data", "foot", measures[0]->getUnit().getIdentifier());
+ assertEquals("Sorted Data", "inch", measures[1]->getUnit().getIdentifier());
+
+ assertEqualsNear("Sorted Data", 32, measures[0]->getNumber().getInt64(), 0.00001);
+ assertEqualsNear("Sorted Data", 9.7008, measures[1]->getNumber().getDouble(), 0.0001);
+}
+
/**
* This class represents the output fields from unitPreferencesTest.txt. Please
* see the documentation at the top of that file for details.
if (U_FAILURE(status)) {
return;
}
+
UnitsRouter router(inputMeasureUnit, region, usage, status);
if (status.errIfFailureAndReset("UnitsRouter(<%s>, \"%.*s\", \"%.*s\", status)",
inputMeasureUnit.getIdentifier(), region.length(), region.data(),
if (status.errIfFailureAndReset("Failure before router.route")) {
return;
}
- MaybeStackVector<Measure> result = router.route(inputAmount, status);
+ auto routeResult = router.route(inputAmount, status);
if (status.errIfFailureAndReset("router.route(inputAmount, ...)")) {
return;
}
// TODO: revisit this experimentally chosen precision:
- checkOutput(unitsTest, msg.data(), expected, result, 0.0000000001);
+ checkOutput(unitsTest, msg.data(), expected, routeResult.measures, 0.0000000001);
}
/**