mirror of
https://github.com/unicode-org/icu.git
synced 2025-04-08 06:53:45 +00:00
ICU-20568 Use Impl
libraries, add precision UnitsRouter#route output
Add precision to the output of UnitsRouter#route PR: https://github.com/icu-units/icu/pull/10 Commit:030bda3ec8
Use `Impl` libraries for all internal libraries PR: https://github.com/icu-units/icu/pull/15 Commit:cc786cfb3b
Sort the units in ComplexUnitConverter PR: https://github.com/icu-units/icu/pull/6 Commit: f65b181c4447bb4eb9eef5dc20ea1b296d053ffa
This commit is contained in:
parent
7ed2a2d233
commit
1b853904cd
13 changed files with 374 additions and 205 deletions
|
@ -5,33 +5,64 @@
|
|||
|
||||
#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) {
|
||||
|
||||
if (outputUnits.getComplexity(status) != UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) {
|
||||
unitConverters_.emplaceBackAndCheckErrorCode(status, inputUnit, outputUnits, ratesInfo, status);
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
units_.emplaceBackAndCheckErrorCode(status, outputUnits);
|
||||
ComplexUnitsConverter::ComplexUnitsConverter(const MeasureUnitImpl &inputUnit,
|
||||
const MeasureUnitImpl &outputUnits,
|
||||
const ConversionRates &ratesInfo, UErrorCode &status)
|
||||
: units_(outputUnits.extractIndividualUnits(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;
|
||||
}
|
||||
|
||||
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
|
||||
// converter from the first unit in the `outputUnits` to the second unit and so on.
|
||||
|
@ -46,47 +77,19 @@ ComplexUnitsConverter::ComplexUnitsConverter(const MeasureUnit &inputUnit,
|
|||
// 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 {
|
||||
|
@ -107,8 +110,8 @@ MaybeStackVector<Measure> ComplexUnitsConverter::convert(double quantity, UError
|
|||
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`
|
||||
|
@ -117,8 +120,8 @@ MaybeStackVector<Measure> ComplexUnitsConverter::convert(double quantity, UError
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,15 +8,14 @@
|
|||
#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 {
|
||||
|
||||
/**
|
||||
|
@ -28,7 +27,7 @@ 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`.
|
||||
|
@ -40,7 +39,7 @@ class U_I18N_API 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
|
||||
|
@ -60,7 +59,7 @@ class U_I18N_API ComplexUnitsConverter {
|
|||
|
||||
private:
|
||||
MaybeStackVector<UnitConverter> unitConverters_;
|
||||
MaybeStackVector<MeasureUnit> units_;
|
||||
MaybeStackVector<MeasureUnitImpl> units_;
|
||||
};
|
||||
|
||||
} // namespace units
|
||||
|
|
|
@ -788,6 +788,14 @@ const char *SingleUnitImpl::getSimpleUnitID() const {
|
|||
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);
|
||||
}
|
||||
|
@ -823,12 +831,26 @@ bool MeasureUnitImpl::append(const SingleUnitImpl& singleUnit, UErrorCode& statu
|
|||
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);
|
||||
}
|
||||
|
|
|
@ -134,6 +134,13 @@ template class U_I18N_API MaybeStackVector<SingleUnitImpl, 8>;
|
|||
* 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;
|
||||
|
@ -189,6 +196,16 @@ struct U_I18N_API MeasureUnitImpl : public UMemory {
|
|||
*/
|
||||
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);
|
||||
|
||||
|
|
|
@ -101,15 +101,17 @@ void UsagePrefsHandler::processQuantity(DecimalQuantity &quantity, MicroProps &m
|
|||
}
|
||||
|
||||
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?
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
#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>
|
||||
|
@ -88,20 +88,6 @@ void U_I18N_API Factor::applySiPrefix(UMeasureSIPrefix siPrefix) {
|
|||
}
|
||||
|
||||
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;
|
||||
|
@ -235,16 +221,12 @@ Factor loadSingleFactor(StringPiece source, const ConversionRates &ratesInfo, UE
|
|||
}
|
||||
|
||||
// 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;
|
||||
|
@ -264,30 +246,35 @@ Factor loadCompoundFactor(const MeasureUnit &source, const ConversionRates &rate
|
|||
/**
|
||||
* 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;
|
||||
|
@ -299,9 +286,9 @@ void loadConversionRate(ConversionRate &conversionRate, const MeasureUnit &sourc
|
|||
|
||||
// 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;
|
||||
|
@ -321,7 +308,48 @@ void loadConversionRate(ConversionRate &conversionRate, const MeasureUnit &sourc
|
|||
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
|
||||
|
@ -368,21 +396,20 @@ void U_I18N_API addSingleFactorConstant(StringPiece baseStr, int32_t power, Sign
|
|||
* 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;
|
||||
}
|
||||
|
@ -393,14 +420,12 @@ MeasureUnit U_I18N_API extractCompoundBaseUnit(const MeasureUnit &source,
|
|||
|
||||
// Multiply the power of the singleUnit by the power of the baseUnit. For example, square-hectare
|
||||
// must be 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;
|
||||
|
@ -411,53 +436,70 @@ MeasureUnit U_I18N_API extractCompoundBaseUnit(const MeasureUnit &source,
|
|||
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;
|
||||
|
||||
auto sourceSimplified = sourceBaseUnit.simplify(status);
|
||||
auto targetSimplified = targetBaseUnit.simplify(status);
|
||||
mergeUnitsAndDimensions(convertible, sourceBaseUnit, 1);
|
||||
mergeUnitsAndDimensions(reciprocal, sourceBaseUnit, 1);
|
||||
|
||||
if (sourceSimplified == targetSimplified) return CONVERTIBLE;
|
||||
if (sourceSimplified == targetSimplified.reciprocal(status)) return RECIPROCAL;
|
||||
mergeUnitsAndDimensions(convertible, targetBaseUnit, -1);
|
||||
mergeUnitsAndDimensions(reciprocal, targetBaseUnit, 1);
|
||||
|
||||
if (checkAllDimensionsAreZeros(convertible)) {
|
||||
return CONVERTIBLE;
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
|
@ -7,9 +7,12 @@
|
|||
#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
|
||||
|
@ -29,6 +32,19 @@ enum Constants {
|
|||
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,
|
||||
|
@ -65,25 +81,28 @@ void U_I18N_API addSingleFactorConstant(StringPiece baseStr, int32_t power, Sign
|
|||
/**
|
||||
* 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`.
|
||||
|
@ -96,10 +115,10 @@ MeasureUnit U_I18N_API extractCompoundBaseUnit(const MeasureUnit &source,
|
|||
* 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`.
|
||||
|
@ -121,8 +140,8 @@ class U_I18N_API UnitConverter : public UMemory {
|
|||
* @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.
|
||||
|
|
|
@ -214,9 +214,7 @@ class UnitPreferencesSink : public ResourceSink {
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
#ifndef __GETUNITSDATA_H__
|
||||
#define __GETUNITSDATA_H__
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "charstr.h"
|
||||
#include "cmemory.h"
|
||||
#include "unicode/stringpiece.h"
|
||||
|
@ -92,10 +94,11 @@ class U_I18N_API ConversionRates {
|
|||
// 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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,8 +7,11 @@
|
|||
|
||||
#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"
|
||||
|
||||
|
@ -22,7 +25,9 @@ UnitsRouter::UnitsRouter(MeasureUnit inputUnit, StringPiece region, StringPiece
|
|||
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;
|
||||
|
@ -32,35 +37,57 @@ UnitsRouter::UnitsRouter(MeasureUnit inputUnit, StringPiece region, StringPiece
|
|||
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_;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include "cmemory.h"
|
||||
#include "complexunitsconverter.h"
|
||||
#include "measunit_impl.h"
|
||||
#include "unicode/measunit.h"
|
||||
#include "unicode/stringpiece.h"
|
||||
#include "unicode/uobject.h"
|
||||
|
@ -23,6 +24,14 @@ class Measure;
|
|||
|
||||
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
|
||||
|
@ -35,15 +44,19 @@ namespace units {
|
|||
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)) {}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -78,7 +91,7 @@ class U_I18N_API UnitsRouter {
|
|||
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
|
||||
|
@ -90,7 +103,9 @@ class U_I18N_API UnitsRouter {
|
|||
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_;
|
||||
|
|
|
@ -1078,7 +1078,7 @@ group: units
|
|||
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
|
||||
|
|
|
@ -44,6 +44,7 @@ class UnitsTest : public IntlTest {
|
|||
void testUnitConstantFreshness();
|
||||
void testConversionCapability();
|
||||
void testConversions();
|
||||
void testComplexUnitConverterSorting();
|
||||
void testPreferences();
|
||||
void testSiPrefixes();
|
||||
void testMass();
|
||||
|
@ -61,6 +62,7 @@ void UnitsTest::runIndexedTest(int32_t index, UBool exec, const char *&name, cha
|
|||
TESTCASE_AUTO(testUnitConstantFreshness);
|
||||
TESTCASE_AUTO(testConversionCapability);
|
||||
TESTCASE_AUTO(testConversions);
|
||||
TESTCASE_AUTO(testComplexUnitConverterSorting);
|
||||
TESTCASE_AUTO(testPreferences);
|
||||
TESTCASE_AUTO(testSiPrefixes);
|
||||
TESTCASE_AUTO(testMass);
|
||||
|
@ -121,26 +123,27 @@ void UnitsTest::testConversionCapability() {
|
|||
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,
|
||||
|
@ -171,8 +174,8 @@ void UnitsTest::testSiPrefixes() {
|
|||
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);
|
||||
|
@ -206,8 +209,8 @@ void UnitsTest::testMass() {
|
|||
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);
|
||||
|
@ -240,8 +243,8 @@ void UnitsTest::testTemperature() {
|
|||
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);
|
||||
|
@ -278,8 +281,8 @@ void UnitsTest::testArea() {
|
|||
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);
|
||||
|
@ -356,12 +359,12 @@ void unitsTestDataLineFn(void *context, char *fields[][2], int32_t fieldCount, U
|
|||
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;
|
||||
}
|
||||
|
@ -373,16 +376,16 @@ void unitsTestDataLineFn(void *context, char *fields[][2], int32_t fieldCount, U
|
|||
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;
|
||||
}
|
||||
|
@ -391,7 +394,7 @@ void unitsTestDataLineFn(void *context, char *fields[][2], int32_t fieldCount, U
|
|||
// 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);
|
||||
|
@ -428,6 +431,24 @@ void UnitsTest::testConversions() {
|
|||
}
|
||||
}
|
||||
|
||||
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.
|
||||
|
@ -598,6 +619,7 @@ void unitPreferencesTestDataLineFn(void *context, char *fields[][2], int32_t fie
|
|||
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(),
|
||||
|
@ -617,12 +639,12 @@ void unitPreferencesTestDataLineFn(void *context, char *fields[][2], int32_t fie
|
|||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Add table
Reference in a new issue