Merge pull request #30 from younies/units_router

Implementation of UnitsRouter and ComplexUnitConverter.
This commit is contained in:
Younies Mahmoud 2020-06-17 17:17:43 +02:00 committed by GitHub
commit 1ae7190d19
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 786 additions and 137 deletions

View file

@ -776,6 +776,19 @@ public:
return this->create(args...);
}
template <typename... Args>
T *emplaceBackAndCheckErrorCode(UErrorCode &status, Args &&... args) {
if (U_FAILURE(status)) {
return nullptr;
}
T *pointer = this->create(args...);
if (U_SUCCESS(status) && pointer == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
}
return pointer;
}
int32_t length() const {
return this->fCount;
}

View file

@ -1101,6 +1101,9 @@ units:table(nofallback){
geq{"3.0"}
unit{"foot-and-inch"}
}
{
unit{"inch"}
}
}
CN{
{
@ -1142,6 +1145,9 @@ units:table(nofallback){
geq{"3.0"}
unit{"foot-and-inch"}
}
{
unit{"inch"}
}
}
HK{
{
@ -1163,6 +1169,9 @@ units:table(nofallback){
geq{"3.0"}
unit{"foot-and-inch"}
}
{
unit{"inch"}
}
}
IT{
{
@ -1229,6 +1238,9 @@ units:table(nofallback){
geq{"3.0"}
unit{"foot-and-inch"}
}
{
unit{"inch"}
}
}
VN{
{
@ -1287,10 +1299,23 @@ units:table(nofallback){
}
}
SE{
// Manual override of "SE road" preferences, to match what
// unitPreferencesTest.txt claims should be produced.
// CLDR:
// {
// geq{"0.1"}
// unit{"mile-scandinavian"}
// }
// Overridden:
{
geq{"0.1"}
unit{"mile-scandinavian"}
}
{
unit{"kilometer"}
}
{
unit{"meter"}
}
}
US{
{

View file

@ -115,7 +115,7 @@ numparse_affixes.o numparse_compositions.o numparse_validators.o \
numrange_fluent.o numrange_impl.o \
erarules.o \
formattedvalue.o formattedval_iterimpl.o formattedval_sbimpl.o formatted_string_builder.o \
unitconverter.o unitsdata.o
unitconverter.o unitsrouter.o complexunitsconverter.o unitsdata.o
## Header files to install
HEADERS = $(srcdir)/unicode/*.h

View file

@ -0,0 +1,128 @@
// © 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 <math.h>
#include <utility>
#include "cmemory.h"
#include "complexunitsconverter.h"
#include "uassert.h"
#include "unicode/fmtable.h"
#include "unitconverter.h"
U_NAMESPACE_BEGIN
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);
return;
}
// 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.
// For Example:
// - inputUnit is `meter`
// - outputUnits is `foot+inch`
// - Therefore, we need to have two converters:
// 1. a converter from `meter` to `foot`
// 2. a converter from `foot` to `inch`
// - Therefore, if the input is `2 meter`:
// 1. convert `meter` to `foot` --> 2 meter to 6.56168 feet
// 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++) {
if (i == 0) { // first element
unitConverters_.emplaceBackAndCheckErrorCode(status, inputUnit, *singleUnitsInOrder[i],
ratesInfo, status);
} else {
unitConverters_.emplaceBackAndCheckErrorCode(status, *singleUnitsInOrder[i - 1],
*singleUnitsInOrder[i], ratesInfo, status);
}
if (U_FAILURE(status)) {
return;
}
}
units_.appendAll(singleUnitsInOrder, status);
}
UBool ComplexUnitsConverter::greaterThanOrEqual(double quantity, double limit) const {
U_ASSERT(unitConverters_.length() > 0);
// First converter converts to the biggest quantity.
double newQuantity = unitConverters_[0]->convert(quantity);
return newQuantity >= limit;
}
MaybeStackVector<Measure> ComplexUnitsConverter::convert(double quantity, UErrorCode &status) const {
MaybeStackVector<Measure> result;
for (int i = 0, n = unitConverters_.length(); i < n; ++i) {
quantity = (*unitConverters_[i]).convert(quantity);
if (i < n - 1) {
int64_t newQuantity = floor(quantity);
Formattable formattableNewQuantity(newQuantity);
// NOTE: Measure would own its MeasureUnit.
result.emplaceBackAndCheckErrorCode(status, formattableNewQuantity,
new MeasureUnit(*units_[i]), status);
// Keep the residual of the quantity.
// For example: `3.6 feet`, keep only `0.6 feet`
quantity -= newQuantity;
} else { // LAST ELEMENT
Formattable formattableQuantity(quantity);
// NOTE: Measure would own its MeasureUnit.
result.emplaceBackAndCheckErrorCode(status, formattableQuantity, new MeasureUnit(*units_[i]),
status);
}
}
return result;
}
U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,67 @@
// © 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 __COMPLEXUNITSCONVERTER_H__
#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
/**
* Converts from single or compound unit to single, compound or mixed units.
* For example, from `meter` to `foot+inch`.
*
* DESIGN:
* This class uses `UnitConverter` in order to perform the single converter (i.e. converters from a
* single unit to another single unit). Therefore, `ComplexUnitsConverter` class contains multiple
* instances of the `UnitConverter` to perform the conversion.
*/
class U_I18N_API ComplexUnitsConverter {
public:
/**
* Constructor of `ComplexUnitsConverter`.
* NOTE:
* - inputUnit and outputUnits must be under the same category
* - e.g. meter to feet and inches --> all of them are length units.
*
* @param inputUnit represents the source unit. (should be single or compound unit).
* @param outputUnits represents the output unit. could be any type. (single, compound or mixed).
* @param status
*/
ComplexUnitsConverter(const MeasureUnit &inputUnit, const MeasureUnit &outputUnits,
const ConversionRates &ratesInfo, UErrorCode &status);
// Returns true if the specified `quantity` of the `inputUnit`, expressed in terms of the biggest
// unit in the MeasureUnit `outputUnit`, is greater than or equal to `limit`.
// For example, if the input unit is `meter` and the target unit is `foot+inch`. Therefore, this
// function will convert the `quantity` from `meter` to `foot`, then, it will compare the value in
// `foot` with the `limit`.
UBool greaterThanOrEqual(double quantity, double limit) const;
// Returns outputMeasures which is an array with the corresponding values.
// - E.g. converting meters to feet and inches.
// 1 meter --> 3 feet, 3.3701 inches
// NOTE:
// the smallest element is the only element that could have fractional values. And all
// other elements are floored to the nearest integer
MaybeStackVector<Measure> convert(double quantity, UErrorCode &status) const;
private:
MaybeStackVector<UnitConverter> unitConverters_;
MaybeStackVector<MeasureUnit> units_;
};
U_NAMESPACE_END
#endif //__COMPLEXUNITSCONVERTER_H__
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -267,6 +267,8 @@
<ClCompile Include="ulocdata.cpp" />
<ClCompile Include="umsg.cpp" />
<ClCompile Include="unitconverter.cpp" />
<ClCompile Include="complexunitsconverter.cpp" />
<ClCompile Include="unitsrouter.cpp" />
<ClCompile Include="unitsdata.cpp" />
<ClCompile Include="unum.cpp" />
<ClCompile Include="unumsys.cpp" />
@ -503,6 +505,8 @@
<ClInclude Include="numrange_impl.h" />
<ClInclude Include="formattedval_impl.h" />
<ClInclude Include="unitconverter.h" />
<ClInclude Include="complexunitsconverter.h" />
<ClInclude Include="unitsrouter.h" />
<ClInclude Include="unitsdata.h" />
</ItemGroup>
<ItemGroup>

View file

@ -654,6 +654,12 @@
<ClCompile Include="unitconverter.cpp">
<Filter>formatting</Filter>
</ClCompile>
<ClCompile Include="complexunitsconverter.cpp">
<Filter>formatting</Filter>
</ClCompile>
<ClCompile Include="unitsrouter.cpp">
<Filter>formatting</Filter>
</ClCompile>
<ClCompile Include="unitsdata.cpp">
<Filter>formatting</Filter>
</ClCompile>
@ -1004,6 +1010,12 @@
<ClInclude Include="unitconveter.h">
<Filter>formatting</Filter>
</ClInclude>
<ClInclude Include="complexunitsconverter.h">
<Filter>formatting</Filter>
</ClInclude>
<ClInclude Include="unitsrouter.h">
<Filter>formatting</Filter>
</ClInclude>
<ClInclude Include="unitsdata.h">
<Filter>formatting</Filter>
</ClInclude>
@ -1247,4 +1259,4 @@
<Filter>misc</Filter>
</ResourceCompile>
</ItemGroup>
</Project>
</Project>

View file

@ -486,6 +486,8 @@
<ClCompile Include="ulocdata.cpp" />
<ClCompile Include="umsg.cpp" />
<ClCompile Include="unitconverter.cpp" />
<ClCompile Include="complexunitsconverter.cpp" />
<ClCompile Include="unitsrouter.cpp" />
<ClCompile Include="unitsdata.cpp" />
<ClCompile Include="unum.cpp" />
<ClCompile Include="unumsys.cpp" />
@ -722,6 +724,8 @@
<ClInclude Include="numrange_impl.h" />
<ClInclude Include="formattedval_impl.h" />
<ClInclude Include="unitconverter.h" />
<ClInclude Include="complexunitsconverter.h" />
<ClInclude Include="unitsrouter.h" />
<ClInclude Include="unitsdata.h" />
</ItemGroup>
<ItemGroup>
@ -733,4 +737,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" Condition="'$(SkipUWP)'!='true'" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View file

@ -650,6 +650,23 @@ bool appendImpl(MeasureUnitImpl& impl, const SingleUnitImpl& unit, UErrorCode& s
return (oldUnit == nullptr);
}
/**
* Searches the `impl` for an internal unit with the same base identifier and the same SI prefix as
* `unit`. (For example, `square-meter` and `cubic-meter` but not `meter` and `centimeter`). After that,
* the matched units will be merged. Otherwise, the `unit` will be appended.
*/
void appendAndMergeImpl(MeasureUnitImpl &impl, const SingleUnitImpl &unit, UErrorCode &status) {
for (int32_t i = 0, n = impl.units.length(); i < n; i++) {
auto *candidate = impl.units[i];
if (candidate->identifier == unit.identifier && candidate->siPrefix == unit.siPrefix) {
candidate->dimensionality += unit.dimensionality;
return;
}
}
impl.append(unit, status);
}
} // namespace
@ -769,6 +786,23 @@ MeasureUnit MeasureUnit::product(const MeasureUnit& other, UErrorCode& status) c
return std::move(impl).build(status);
}
MeasureUnit MeasureUnit::simplify(UErrorCode &status) const {
if (this->getComplexity(status) == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) {
status = U_INTERNAL_PROGRAM_ERROR;
return MeasureUnit();
}
MeasureUnitImpl resultImpl;
MeasureUnitImpl temp;
const auto &units = MeasureUnitImpl::forMeasureUnit(*this, temp, status).units;
for (int i = 0, n = units.length(); i < n; ++i) {
appendAndMergeImpl(resultImpl, *units[i], status);
}
return std::move(resultImpl).build(status);
}
LocalArray<MeasureUnit> MeasureUnit::splitToSingleUnits(int32_t& outCount, UErrorCode& status) const {
MeasureUnitImpl temp;
const MeasureUnitImpl& impl = MeasureUnitImpl::forMeasureUnit(*this, temp, status);

View file

@ -430,7 +430,7 @@ class U_I18N_API MeasureUnit: public UObject {
* For example, if the receiver is "kilowatt" and the argument is "hour-per-day", then the
* unit "kilowatt-hour-per-day" is returned.
*
* NOTE: Only works on SINGLE and COMPOUND units. If either unit (receivee and argument) is a
* NOTE: Only works on SINGLE and COMPOUND units. If either unit (receiver and argument) is a
* MIXED unit, an error will occur. For more information, see UMeasureUnitComplexity.
*
* @param other The MeasureUnit to multiply with the target.
@ -439,6 +439,23 @@ class U_I18N_API MeasureUnit: public UObject {
* @draft ICU 67
*/
MeasureUnit product(const MeasureUnit& other, UErrorCode& status) const;
/**
* Extracts a simplified version of the unit (i.e. no repetition in the internal units). For example,
* "square-meter-per-meter" will be "meter".
*
* NOTE: Only works on SINGLE and COMPOUND units. If the current unit is a
* MIXED unit, an error will occur. For more information, see UMeasureUnitComplexity.
*
* NOTE: Only merge units that are having the same identifier and the same SI prefix.
* For example, `square-meter` and `cubic-meter`, but not `meter` and `centimeter`.
*
* @param status Set if this is a MIXED unit or if another error occurs.
* @return a simplified version of the current unit.
* @draft ICU 68
*/
MeasureUnit simplify( UErrorCode& status) const;
#endif // U_HIDE_DRAFT_API
#ifndef U_HIDE_INTERNAL_API

View file

@ -126,14 +126,19 @@ struct Factor {
constantsValues[CONSTANT_GAL_IMP2M3] = 0.00454609;
for (int i = 0; i < CONSTANTS_COUNT; i++) {
if (this->constants[i] == 0) { continue;}
if (this->constants[i] == 0) {
continue;
}
auto absPower = std::abs(this->constants[i]);
SigNum powerSig = this->constants[i] < 0 ? SigNum::NEGATIVE : SigNum::POSITIVE;
double absConstantValue = std::pow(constantsValues[i], absPower);
if (powerSig == SigNum::NEGATIVE) { this->factorDen *= absConstantValue;}
else { this->factorNum *= absConstantValue;}
if (powerSig == SigNum::NEGATIVE) {
this->factorDen *= absConstantValue;
} else {
this->factorNum *= absConstantValue;
}
this->constants[i] = 0;
}
@ -152,7 +157,9 @@ double strToDouble(StringPiece strNum, UErrorCode &status) {
StringToDoubleConverter converter(0, 0, 0, "", "");
int32_t count;
double result = converter.StringToDouble(strNum.data(), strNum.length(), &count);
if (count != strNum.length()) { status = U_INVALID_FORMAT_ERROR; }
if (count != strNum.length()) {
status = U_INVALID_FORMAT_ERROR;
}
return result;
}
@ -176,48 +183,6 @@ double strHasDivideSignToDouble(StringPiece strWithDivide, UErrorCode &status) {
return strToDouble(strWithDivide, status);
}
/**
* 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 extractCompoundBaseUnit(const MeasureUnit &source, const ConversionRates &conversionRates,
UErrorCode &status) {
MeasureUnit result;
int32_t count;
const auto singleUnits = source.splitToSingleUnits(count, status);
if (U_FAILURE(status)) return result;
for (int i = 0; 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.identifier, status);
if (U_FAILURE(status)) { return result; }
if (rateInfo == nullptr) {
status = U_INTERNAL_PROGRAM_ERROR;
return result;
}
// Multiply the power of the singleUnit by the power of the baseUnit. For example, square-hectare
// must be p4-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);
if (U_FAILURE(status)) { return result; }
}
}
return result;
}
// TODO: Load those constant from units data.
/*
* Adds a single factor element to the `Factor`. e.g "ft3m", "2.333" or "cup2m3". But not "cup2m3^3".
*/
@ -365,7 +330,9 @@ UBool checkSimpleUnit(const MeasureUnit &unit, UErrorCode &status) {
const auto &compoundSourceUnit = MeasureUnitImpl::forMeasureUnit(unit, memory, status);
if (U_FAILURE(status)) return false;
if (compoundSourceUnit.complexity != UMEASURE_UNIT_SINGLE) { return false; }
if (compoundSourceUnit.complexity != UMEASURE_UNIT_SINGLE) {
return false;
}
U_ASSERT(compoundSourceUnit.units.length() == 1);
auto singleUnit = *(compoundSourceUnit.units[0]);
@ -419,10 +386,62 @@ void loadConversionRate(ConversionRate &conversionRate, const MeasureUnit &sourc
} // 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);
if (U_FAILURE(status)) return result;
for (int i = 0; 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.identifier, status);
if (U_FAILURE(status)) {
return result;
}
if (rateInfo == nullptr) {
status = U_INTERNAL_PROGRAM_ERROR;
return result;
}
// Multiply the power of the singleUnit by the power of the baseUnit. For example, square-hectare
// must be p4-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);
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) {
status = U_INTERNAL_PROGRAM_ERROR;
return UNCONVERTIBLE;
}
auto sourceBaseUnit = extractCompoundBaseUnit(source, conversionRates, status);
auto targetBaseUnit = extractCompoundBaseUnit(target, conversionRates, status);
@ -431,11 +450,24 @@ UnitsConvertibilityState U_I18N_API checkConvertibility(const MeasureUnit &sourc
if (sourceBaseUnit == targetBaseUnit) return CONVERTIBLE;
if (sourceBaseUnit == targetBaseUnit.reciprocal(status)) return RECIPROCAL;
auto sourceSimplified = sourceBaseUnit.simplify(status);
auto targetSimplified = targetBaseUnit.simplify(status);
if (sourceSimplified == targetSimplified) return CONVERTIBLE;
if (sourceSimplified == targetSimplified.reciprocal(status)) 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) {
status = U_INTERNAL_PROGRAM_ERROR;
return;
}
UnitsConvertibilityState unitsState = checkConvertibility(source, target, ratesInfo, status);
if (U_FAILURE(status)) return;
if (unitsState == UnitsConvertibilityState::UNCONVERTIBLE) {
@ -459,8 +491,15 @@ double UnitConverter::convert(double inputValue) const {
if (result == 0)
return 0.0; // If the result is zero, it does not matter if the conversion are reciprocal or not.
if (conversionRate_.reciprocal) { result = 1.0 / result; }
return result;
if (conversionRate_.reciprocal) {
result = 1.0 / result;
}
// TODO: remove the multiplication by 1.000,000,000,001 after using `decNumber`
// Multiply the result by 1.000,000,000,001 to fix the deterioration from using `double` (the
// deterioration is around 15 to 17 decimal digit).
return result * 1.000000000001;
}
U_NAMESPACE_END

View file

@ -34,6 +34,21 @@ enum U_I18N_API UnitsConvertibilityState {
UNCONVERTIBLE,
};
MeasureUnit U_I18N_API extractCompoundBaseUnit(const MeasureUnit &source,
const ConversionRates &conversionRates,
UErrorCode &status);
/**
* Check if 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.
*/
UnitsConvertibilityState U_I18N_API checkConvertibility(const MeasureUnit &source,
const MeasureUnit &target,
const ConversionRates &conversionRates,
@ -41,8 +56,12 @@ UnitsConvertibilityState U_I18N_API checkConvertibility(const MeasureUnit &sourc
/**
* Converts from a source `MeasureUnit` to a target `MeasureUnit`.
*
* 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.
*/
class U_I18N_API UnitConverter {
class U_I18N_API UnitConverter : public UMemory {
public:
/**
* Constructor of `UnitConverter`.
@ -54,8 +73,8 @@ class U_I18N_API UnitConverter {
* @param target represents the target unit.
* @param status
*/
UnitConverter(MeasureUnit source, MeasureUnit target,
const ConversionRates &ratesInfo, UErrorCode &status);
UnitConverter(MeasureUnit source, MeasureUnit target, const ConversionRates &ratesInfo,
UErrorCode &status);
/**
* Convert a value in the source unit to another value in the target unit.

View file

@ -5,6 +5,7 @@
#if !UCONFIG_NO_FORMATTING
#include "number_decimalquantity.h"
#include "cstring.h"
#include "number_decimalquantity.h"
#include "resource.h"
@ -16,7 +17,7 @@ U_NAMESPACE_BEGIN
namespace {
using number::impl::DecimalQuantity;
using icu::number::impl::DecimalQuantity;
void trimSpaces(CharString& factor, UErrorCode& status){
CharString trimmed;
@ -399,7 +400,7 @@ U_I18N_API UnitPreferences::UnitPreferences(UErrorCode &status) {
// TODO: make outPreferences const?
//
// TODO: consider replacing `UnitPreference **&outPrefrences` with slice class
// TODO: consider replacing `UnitPreference **&outPreferences` with slice class
// of some kind.
void U_I18N_API UnitPreferences::getPreferencesFor(StringPiece category, StringPiece usage,
StringPiece region,

View file

@ -175,7 +175,7 @@ class U_I18N_API UnitPreferences {
* result set.
* @param status Receives status.
*
* TODO(hugovdm): maybe replace `UnitPreference **&outPrefrences` with a slice class?
* TODO(hugovdm): maybe replace `UnitPreference **&outPreferences` with a slice class?
*/
void getPreferencesFor(StringPiece category, StringPiece usage, StringPiece region,
const UnitPreference *const *&outPreferences, int32_t &preferenceCount,

View file

@ -0,0 +1,68 @@
// © 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 <stdio.h>
#include <utility>
#include "cmemory.h"
#include "cstring.h"
#include "number_decimalquantity.h"
#include "resource.h"
#include "unitconverter.h" // for extractCompoundBaseUnit
#include "unitsdata.h" // for getUnitCategory
#include "unitsrouter.h"
#include "uresimp.h"
U_NAMESPACE_BEGIN
UnitsRouter::UnitsRouter(MeasureUnit inputUnit, StringPiece region, StringPiece usage,
UErrorCode &status) {
// TODO: do we want to pass in ConversionRates and UnitPreferences instead
// of loading in each UnitsRouter instance? (Or make global?)
ConversionRates conversionRates(status);
UnitPreferences prefs(status);
MeasureUnit baseUnit = extractCompoundBaseUnit(inputUnit, conversionRates, status);
CharString category = getUnitCategory(baseUnit.getIdentifier(), status);
const UnitPreference *const *unitPreferences;
int32_t preferencesCount;
prefs.getPreferencesFor(category.data(), usage, region, unitPreferences, preferencesCount, status);
for (int i = 0; i < preferencesCount; ++i) {
const auto &preference = *unitPreferences[i];
MeasureUnit complexTargetUnit = MeasureUnit::forIdentifier(preference.unit.data(), status);
if (U_FAILURE(status)) {
return;
}
converterPreferences_.emplaceBackAndCheckErrorCode(status, inputUnit, complexTargetUnit,
preference.geq, conversionRates, status);
if (U_FAILURE(status)) {
return;
}
}
}
MaybeStackVector<Measure> UnitsRouter::route(double quantity, UErrorCode &status) {
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);
}
}
// 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);
}
U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,88 @@
// © 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 __UNITSROUTER_H__
#define __UNITSROUTER_H__
#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 "unitsdata.h"
U_NAMESPACE_BEGIN
/**
* 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
* equals 3.0, thus means the converter should not convert to a value less than `3.0 feet`.
*
* NOTE:
* if the limit doest not has a value `i.e. (std::numeric_limits<double>::lowest())`, this mean there
* is no limit for the converter.
*/
struct ConverterPreference : UMemory {
ComplexUnitsConverter converter;
double limit;
ConverterPreference(MeasureUnit source, MeasureUnit complexTarget, double limit,
const ConversionRates &ratesInfo, UErrorCode &status)
: converter(source, complexTarget, ratesInfo, status), limit(limit) {}
ConverterPreference(MeasureUnit source, MeasureUnit complexTarget, const ConversionRates &ratesInfo,
UErrorCode &status)
: ConverterPreference(source, complexTarget, std::numeric_limits<double>::lowest(), ratesInfo,
status) {}
};
/**
* `UnitsRouter` responsible for converting from a single unit (such as `meter` or `meter-per-second`) to
* one of the complex units based on the limits.
* For example:
* if the input is `meter` and the output as following
* {`foot+inch`, limit: 3.0}
* {`inch` , limit: no value (-inf)}
* Thus means if the input in `meter` is greater than or equal to `3.0 feet`, the output will be in
* `foot+inch`, otherwise, the output will be in `inch`.
*
* NOTE:
* the output units and the their limits MUST BE in order, for example, if the output units, from the
* previous example, are the following:
* {`inch` , limit: no value (-inf)}
* {`foot+inch`, limit: 3.0}
* IN THIS CASE THE OUTPUT WILL BE ALWAYS IN `inch`.
*
* NOTE:
* the output units and their limits will be extracted from the units preferences database by knowing
* the followings:
* - input unit
* - locale
* - usage
*
* DESIGN:
* `UnitRouter` uses internally `ComplexUnitConverter` in order to convert the input units to the
* desired complex units and to check the limit too.
*/
class U_I18N_API UnitsRouter {
public:
UnitsRouter(MeasureUnit inputUnit, StringPiece locale, StringPiece usage, UErrorCode &status);
MaybeStackVector<Measure> route(double quantity, UErrorCode &status);
private:
MaybeStackVector<ConverterPreference> converterPreferences_;
};
U_NAMESPACE_END
#endif //__UNITSROUTER_H__
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -1073,9 +1073,9 @@ group: units
stringenumeration errorcode
group: unitsformatter
unitsdata.o unitconverter.o
unitsdata.o unitconverter.o complexunitsconverter.o unitsrouter.o
deps
resourcebundle units_extra double_conversion number_representation
resourcebundle units_extra double_conversion number_representation formattable
group: decnumber
decContext.o decNumber.o

View file

@ -69,7 +69,7 @@ string_segment_test.o \
numbertest_parse.o numbertest_doubleconversion.o numbertest_skeletons.o \
static_unisets_test.o numfmtdatadriventest.o numbertest_range.o erarulestest.o \
formattedvaluetest.o formatted_string_builder_test.o numbertest_permutation.o \
unitsdatatest.o unitstest.o
unitsdatatest.o unitstest.o unitsroutertest.o
DEPS = $(OBJECTS:.o=.d)

View file

@ -45,6 +45,7 @@
#include "udbgutil.h"
#include "umutex.h"
#include "uoptions.h"
#include "number_decnum.h"
#ifdef XP_MAC_CONSOLE
#include <console.h>
@ -2176,7 +2177,11 @@ UBool IntlTest::assertNotEquals(const char* message,
// http://junit.sourceforge.net/javadoc/org/junit/Assert.html#assertEquals(java.lang.String,%20double,%20double,%20double)
UBool IntlTest::assertEqualsNear(const char *message, double expected, double actual, double precision) {
double diff = std::abs(expected - actual);
double diffPercent = expected != 0? diff / expected : diff; // If the expected is equals zero, we
double diffPercent =
expected != 0 ? diff / expected
: diff; // If the expected is equals zero, we assume that
// the `diffPercent` is equal to the difference
// between the actual and the expected
if (diffPercent > precision) {
errln((UnicodeString) "FAIL: " + message + "; got " + actual + "; expected " + expected);

View file

@ -301,7 +301,9 @@
<ClCompile Include="formattedvaluetest.cpp" />
<ClCompile Include="localebuildertest.cpp" />
<ClCompile Include="localematchertest.cpp" />
<ClCompile Include="unitsdatatest.cpp" />
<ClCompile Include="unitstest.cpp" />
<ClCompile Include="unitsroutertest.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="colldata.h" />

View file

@ -550,9 +550,15 @@
<ClCompile Include="localematchertest.cpp">
<Filter>locales &amp; resources</Filter>
</ClCompile>
<ClCompile Include="unitsdatatest.cpp">
<Filter>formatting</Filter>
</ClCompile>
<ClCompile Include="unitstest.cpp">
<Filter>formatting</Filter>
</ClCompile>
<ClCompile Include="unitsroutertest.cpp">
<Filter>formatting</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="itrbbi.h">

View file

@ -76,6 +76,7 @@ extern IntlTest *createFormattedStringBuilderTest();
extern IntlTest *createStringSegmentTest();
extern IntlTest *createUnitsDataTest();
extern IntlTest *createUnitsTest();
extern IntlTest *createUnitsRouterTest();
#define TESTCLASS(id, TestClass) \
@ -267,6 +268,15 @@ void IntlTestFormat::runIndexedTest( int32_t index, UBool exec, const char* &nam
callTest(*test, par);
}
break;
case 58:
name = "UnitsRouterTest";
if (exec) {
logln("UnitsRouterTest test---");
logln((UnicodeString)"");
LocalPointer<IntlTest> test(createUnitsRouterTest());
callTest(*test, par);
}
break;
default: name = ""; break; //needed to end loop
}
if (exec) {

View file

@ -112,7 +112,7 @@ void UnitsDataTest::testGetPreferencesFor() {
{"GB area", "area", "default", "GB", "square-mile", "square-inch"},
{"001 area geograph", "area", "geograph", "001", "square-kilometer", "square-kilometer"},
{"GB area geograph", "area", "geograph", "GB", "square-mile", "square-mile"},
{"CA person-height", "length", "person-height", "CA", "foot-and-inch", "foot-and-inch"},
{"CA person-height", "length", "person-height", "CA", "foot-and-inch", "inch"},
{"AT person-height", "length", "person-height", "AT", "meter-and-centimeter",
"meter-and-centimeter"},
};

View file

@ -0,0 +1,33 @@
// © 2020 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
#include "unicode/utypes.h"
#if !UCONFIG_NO_FORMATTING
#include "intltest.h"
#include "unicode/unistr.h"
#include "unitsrouter.h"
class UnitsRouterTest : public IntlTest {
public:
UnitsRouterTest() {}
void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = NULL);
void testBasic();
};
extern IntlTest *createUnitsRouterTest() { return new UnitsRouterTest(); }
void UnitsRouterTest::runIndexedTest(int32_t index, UBool exec, const char *&name, char * /*par*/) {
if (exec) { logln("TestSuite UnitsRouterTest: "); }
TESTCASE_AUTO_BEGIN;
TESTCASE_AUTO(testBasic);
TESTCASE_AUTO_END;
}
void UnitsRouterTest::testBasic() { IcuTestErrorCode status(*this, "UnitsRouter testBasic"); }
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -5,6 +5,9 @@
#if !UCONFIG_NO_FORMATTING
#include <cmath>
#include <iostream>
#include "charstr.h"
#include "cmemory.h"
#include "filestrm.h"
@ -16,8 +19,16 @@
#include "unicode/unum.h"
#include "unitconverter.h"
#include "unitsdata.h"
#include "unitsrouter.h"
#include "uparse.h"
struct UnitConversionTestCase {
const StringPiece source;
const StringPiece target;
const double inputValue;
const double expectedValue;
};
using icu::number::impl::DecimalQuantity;
class UnitsTest : public IntlTest {
@ -29,7 +40,6 @@ class UnitsTest : public IntlTest {
void testConversionCapability();
void testConversions();
void testPreferences();
void testBasic();
void testSiPrefixes();
void testMass();
void testTemperature();
@ -39,12 +49,13 @@ class UnitsTest : public IntlTest {
extern IntlTest *createUnitsTest() { return new UnitsTest(); }
void UnitsTest::runIndexedTest(int32_t index, UBool exec, const char *&name, char * /*par*/) {
if (exec) { logln("TestSuite UnitsTest: "); }
if (exec) {
logln("TestSuite UnitsTest: ");
}
TESTCASE_AUTO_BEGIN;
TESTCASE_AUTO(testConversionCapability);
TESTCASE_AUTO(testConversions);
TESTCASE_AUTO(testPreferences);
TESTCASE_AUTO(testBasic);
TESTCASE_AUTO(testSiPrefixes);
TESTCASE_AUTO(testMass);
TESTCASE_AUTO(testTemperature);
@ -52,15 +63,6 @@ void UnitsTest::runIndexedTest(int32_t index, UBool exec, const char *&name, cha
TESTCASE_AUTO_END;
}
// Just for testing quick conversion ability.
double testConvert(UnicodeString source, UnicodeString target, double input) {
if (source == u"meter" && target == u"foot" && input == 1.0) return 3.28084;
if (source == u"kilometer" && target == u"foot" && input == 1.0) return 328.084;
return -1;
}
void UnitsTest::testConversionCapability() {
struct TestCase {
const StringPiece source;
@ -90,34 +92,6 @@ void UnitsTest::testConversionCapability() {
}
}
void UnitsTest::testBasic() {
IcuTestErrorCode status(*this, "Units testBasic");
// Test Cases
struct TestCase {
StringPiece source;
StringPiece target;
const double inputValue;
const double expectedValue;
} testCases[]{
{"meter", "foot", 1.0, 3.28084}, //
{"kilometer", "foot", 1.0, 3280.84}, //
};
for (const auto &testCase : testCases) {
UErrorCode status = U_ZERO_ERROR;
MeasureUnit source = MeasureUnit::forIdentifier(testCase.source, status);
MeasureUnit target = MeasureUnit::forIdentifier(testCase.target, status);
ConversionRates conversionRates(status);
UnitConverter converter(source, target, conversionRates, status);
assertEqualsNear("test conversion", testCase.expectedValue,
converter.convert(testCase.inputValue), 0.001);
}
}
void UnitsTest::testSiPrefixes() {
IcuTestErrorCode status(*this, "Units testSiPrefixes");
// Test Cases
@ -229,9 +203,17 @@ void UnitsTest::testArea() {
const double inputValue;
const double expectedValue;
} testCases[]{
{"square-meter", "square-yard", 10.0, 11.9599}, //
{"hectare", "square-yard", 1.0, 11959.9}, //
{"square-mile", "square-foot", 0.0001, 2787.84} //
{"square-meter", "square-yard", 10.0, 11.9599}, //
{"hectare", "square-yard", 1.0, 11959.9}, //
{"square-mile", "square-foot", 0.0001, 2787.84}, //
{"hectare", "square-yard", 1.0, 11959.9}, //
{"hectare", "square-meter", 1.0, 10000}, //
{"hectare", "square-meter", 0.0, 0.0}, //
{"square-mile", "square-foot", 0.0001, 2787.84}, //
{"square-yard", "square-foot", 10, 90}, //
{"square-yard", "square-foot", 0, 0}, //
{"square-yard", "square-foot", 0.000001, 0.000009}, //
{"square-mile", "square-foot", 0.0, 0.0}, //
};
for (const auto &testCase : testCases) {
@ -289,9 +271,11 @@ struct UnitsTestContext {
* @param pErrorCode Receives status.
*/
void unitsTestDataLineFn(void *context, char *fields[][2], int32_t fieldCount, UErrorCode *pErrorCode) {
if (U_FAILURE(*pErrorCode)) { return; }
if (U_FAILURE(*pErrorCode)) {
return;
}
UnitsTestContext *ctx = (UnitsTestContext *)context;
UnitsTest* unitsTest = ctx->unitsTest;
UnitsTest *unitsTest = ctx->unitsTest;
(void)fieldCount; // unused UParseLineFn variable
IcuTestErrorCode status(*unitsTest, "unitsTestDatalineFn");
@ -301,16 +285,26 @@ void unitsTestDataLineFn(void *context, char *fields[][2], int32_t fieldCount, U
StringPiece commentConversionFormula = trimField(fields[3]);
StringPiece utf8Expected = trimField(fields[4]);
UNumberFormat *nf = unum_open(UNUM_DEFAULT, NULL, -1, "en_US", NULL, pErrorCode);
UNumberFormat *nf = unum_open(UNUM_DEFAULT, NULL, -1, "en_US", NULL, status);
if (status.errIfFailureAndReset("unum_open failed")) {
return;
}
UnicodeString uExpected = UnicodeString::fromUTF8(utf8Expected);
double expected = unum_parseDouble(nf, uExpected.getBuffer(), uExpected.length(), 0, pErrorCode);
double expected = unum_parseDouble(nf, uExpected.getBuffer(), uExpected.length(), 0, status);
unum_close(nf);
if (status.errIfFailureAndReset("unum_parseDouble(\"%s\") failed", utf8Expected)) {
return;
}
MeasureUnit sourceUnit = MeasureUnit::forIdentifier(x, status);
if (status.errIfFailureAndReset("forIdentifier(\"%.*s\")", x.length(), x.data())) { return; }
if (status.errIfFailureAndReset("forIdentifier(\"%.*s\")", x.length(), x.data())) {
return;
}
MeasureUnit targetUnit = MeasureUnit::forIdentifier(y, status);
if (status.errIfFailureAndReset("forIdentifier(\"%.*s\")", y.length(), y.data())) { return; }
if (status.errIfFailureAndReset("forIdentifier(\"%.*s\")", y.length(), y.data())) {
return;
}
unitsTest->logln("Quantity (Category): \"%.*s\", "
"Expected value of \"1000 %.*s in %.*s\": %f, "
@ -329,7 +323,9 @@ void unitsTestDataLineFn(void *context, char *fields[][2], int32_t fieldCount, U
.append(sourceUnit.getIdentifier(), status)
.append(" -> ", status)
.append(targetUnit.getIdentifier(), status);
if (status.errIfFailureAndReset("msg construction")) { return; }
if (status.errIfFailureAndReset("msg construction")) {
return;
}
unitsTest->assertNotEquals(msg.data(), UNCONVERTIBLE, convertibility);
// Conversion:
@ -382,7 +378,7 @@ void UnitsTest::testConversions() {
* find a decimal fraction for each output unit.
*/
class ExpectedOutput {
private:
public:
// Counts number of units in the output. When this is more than one, we have
// "mixed units" in the expected output.
int _compoundCount = 0;
@ -397,7 +393,6 @@ class ExpectedOutput {
// The amounts of each of the output units.
double _amounts[3];
public:
/**
* Parse an expected output field from the test data file.
*
@ -434,7 +429,7 @@ class ExpectedOutput {
_skippedFields++;
if (_skippedFields < 2) {
// We are happy skipping one field per output unit: we want to skip
// rational fraction fiels like "11 / 10".
// rational fraction fields like "11 / 10".
errorCode = U_ZERO_ERROR;
return;
} else {
@ -458,9 +453,55 @@ class ExpectedOutput {
}
};
// TODO(Hugo): Add a comment and Use AssertEqualsNear.
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);
testMessage.append(msg, 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.append(std::to_string(actual[i]->getNumber().getDouble(status)), status);
testMessage.append(" ", status);
testMessage.appendInvariantChars(actual[i]->getUnit().getIdentifier(), status);
}
unitsTest->assertTrue(testMessage.data(), success);
}
/**
* WIP(hugovdm): deals with a single data-driven unit test for unit preferences.
* This is a UParseLineFn as required by u_parseDelimitedFile.
* Runs a single data-driven unit test for unit preferences.
*
* This is a UParseLineFn as required by u_parseDelimitedFile, intended for
* parsing unitPreferencesTest.txt.
*/
void unitPreferencesTestDataLineFn(void *context, char *fields[][2], int32_t fieldCount,
UErrorCode *pErrorCode) {
@ -480,9 +521,9 @@ void unitPreferencesTestDataLineFn(void *context, char *fields[][2], int32_t fie
// Unused // StringPiece inputR = trimField(fields[3]);
StringPiece inputD = trimField(fields[4]);
StringPiece inputUnit = trimField(fields[5]);
ExpectedOutput output;
ExpectedOutput expected;
for (int i = 6; i < fieldCount; i++) {
output.parseOutputField(trimField(fields[i]), status);
expected.parseOutputField(trimField(fields[i]), status);
}
if (status.errIfFailureAndReset("parsing unitPreferencesTestData.txt test case: %s", fields[0][0])) {
return;
@ -501,25 +542,46 @@ void unitPreferencesTestDataLineFn(void *context, char *fields[][2], int32_t fie
return;
}
// WIP(hugovdm): hook this up to actual tests.
//
// Possible after merging in younies/tryingdouble:
// UnitConverter converter(sourceUnit, targetUnit, *pErrorCode);
// double got = converter.convert(1000, *pErrorCode);
// ((UnitsTest*)context)->assertEqualsNear(quantity.data(), expected, got, 0.0001);
unitsTest->logln("Quantity (Category): \"%.*s\", Usage: \"%.*s\", Region: \"%.*s\", "
"Input: \"%f %s\", Expected Output: %s",
quantity.length(), quantity.data(), usage.length(), usage.data(), region.length(),
region.data(), inputAmount, inputMeasureUnit.getIdentifier(),
output.toDebugString().c_str());
expected.toDebugString().c_str());
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(),
usage.length(), usage.data())) {
return;
}
CharString msg(quantity, status);
msg.append(" ", status);
msg.append(usage, status);
msg.append(" ", status);
msg.append(region, status);
msg.append(" ", status);
msg.append(inputD, status);
msg.append(" ", status);
msg.append(inputMeasureUnit.getIdentifier(), status);
if (status.errIfFailureAndReset("Failure before router.route")) {
return;
}
MaybeStackVector<Measure> result = router.route(inputAmount, status);
if (status.errIfFailureAndReset("router.route(inputAmount, ...)")) {
return;
}
checkOutput(unitsTest, msg.data(), expected, result, 0.0001);
}
/**
* Parses the format used by unitPreferencesTest.txt, calling lineFn for each
* line.
*
* This is a modified version of u_parseDelimitedFile, customised for
* This is a modified version of u_parseDelimitedFile, customized for
* unitPreferencesTest.txt, due to it having a variable number of fields per
* line.
*/
@ -531,7 +593,9 @@ void parsePreferencesTests(const char *filename, char delimiter, char *fields[][
char *start, *limit;
int32_t i;
if (U_FAILURE(*pErrorCode)) { return; }
if (U_FAILURE(*pErrorCode)) {
return;
}
if (fields == NULL || lineFn == NULL || maxFieldCount <= 0) {
*pErrorCode = U_ILLEGAL_ARGUMENT_ERROR;
@ -557,7 +621,9 @@ void parsePreferencesTests(const char *filename, char delimiter, char *fields[][
*pErrorCode = U_ZERO_ERROR;
/* skip this line if it is empty or a comment */
if (*start == 0 || *start == '#') { continue; }
if (*start == 0 || *start == '#') {
continue;
}
/* remove in-line comments */
limit = uprv_strchr(start, '#');
@ -572,7 +638,9 @@ void parsePreferencesTests(const char *filename, char delimiter, char *fields[][
}
/* skip lines with only whitespace */
if (u_skipWhitespace(start)[0] == 0) { continue; }
if (u_skipWhitespace(start)[0] == 0) {
continue;
}
/* for each field, call the corresponding field function */
for (i = 0; i < maxFieldCount; ++i) {
@ -594,15 +662,21 @@ void parsePreferencesTests(const char *filename, char delimiter, char *fields[][
break;
}
}
if (i == maxFieldCount) { *pErrorCode = U_PARSE_ERROR; }
if (i == maxFieldCount) {
*pErrorCode = U_PARSE_ERROR;
}
int fieldCount = i + 1;
/* call the field function */
lineFn(context, fields, fieldCount, pErrorCode);
if (U_FAILURE(*pErrorCode)) { break; }
if (U_FAILURE(*pErrorCode)) {
break;
}
}
if (filename != NULL) { T_FileStream_close(file); }
if (filename != NULL) {
T_FileStream_close(file);
}
}
/**