mirror of
https://github.com/unicode-org/icu.git
synced 2025-04-08 06:53:45 +00:00
Merge pull request #32 from younies/check_convirtible_units
Check convertible units
This commit is contained in:
commit
3bf35258f4
6 changed files with 202 additions and 40 deletions
|
@ -5,14 +5,73 @@
|
|||
|
||||
#if !UCONFIG_NO_FORMATTING
|
||||
|
||||
#include "charstr.h"
|
||||
#include "measunit_impl.h"
|
||||
#include "unicode/errorcode.h"
|
||||
#include "unicode/measunit.h"
|
||||
#include "unicode/stringpiece.h"
|
||||
#include "unitconverter.h"
|
||||
|
||||
U_NAMESPACE_BEGIN
|
||||
|
||||
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 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;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
UnitsConvertibilityState U_I18N_API checkConvertibility(const MeasureUnit &source,
|
||||
const MeasureUnit &target,
|
||||
const ConversionRates &conversionRates,
|
||||
UErrorCode &status) {
|
||||
auto sourceBaseUnit = extractCompoundBaseUnit(source, conversionRates, status);
|
||||
auto targetBaseUnit = extractCompoundBaseUnit(target, conversionRates, status);
|
||||
|
||||
if (U_FAILURE(status)) return UNCONVERTIBLE;
|
||||
|
||||
if (sourceBaseUnit == targetBaseUnit) return CONVERTIBLE;
|
||||
if (sourceBaseUnit == targetBaseUnit.reciprocal(status)) return RECIPROCAL;
|
||||
|
||||
return UNCONVERTIBLE;
|
||||
}
|
||||
|
||||
U_NAMESPACE_END
|
||||
|
||||
|
|
|
@ -7,16 +7,27 @@
|
|||
#ifndef __UNITCONVERTER_H__
|
||||
#define __UNITCONVERTER_H__
|
||||
|
||||
|
||||
#include <vector>
|
||||
#include "cmemory.h"
|
||||
#include "unicode/errorcode.h"
|
||||
#include "unicode/measunit.h"
|
||||
#include "unitconverter.h"
|
||||
#include "unitsdata.h"
|
||||
|
||||
U_NAMESPACE_BEGIN
|
||||
|
||||
enum U_I18N_API UnitsConvertibilityState {
|
||||
RECIPROCAL,
|
||||
CONVERTIBLE,
|
||||
UNCONVERTIBLE,
|
||||
};
|
||||
|
||||
UnitsConvertibilityState U_I18N_API checkConvertibility(const MeasureUnit &source,
|
||||
const MeasureUnit &target,
|
||||
const ConversionRates &conversionRates,
|
||||
UErrorCode &status);
|
||||
|
||||
U_NAMESPACE_END
|
||||
|
||||
|
||||
#endif //__UNITCONVERTER_H__
|
||||
|
||||
#endif /* #if !UCONFIG_NO_FORMATTING */
|
||||
|
|
|
@ -102,13 +102,14 @@ void U_I18N_API getAllConversionRates(MaybeStackVector<ConversionRateInfo> &resu
|
|||
ures_getAllItemsWithFallback(unitsBundle.getAlias(), "convertUnits", sink, status);
|
||||
}
|
||||
|
||||
// TODO(hugovdm): ensure this gets removed. Currently
|
||||
// https://github.com/sffc/icu/pull/32 is making use of it.
|
||||
MaybeStackVector<ConversionRateInfo> U_I18N_API
|
||||
getConversionRatesInfo(const MaybeStackVector<MeasureUnit> &, UErrorCode &status) {
|
||||
MaybeStackVector<ConversionRateInfo> result;
|
||||
getAllConversionRates(result, status);
|
||||
return result;
|
||||
const ConversionRateInfo *ConversionRates::extractConversionInfo(StringPiece source,
|
||||
UErrorCode &status) const {
|
||||
for (size_t i = 0, n = conversionInfo_.length(); i < n; ++i) {
|
||||
if (conversionInfo_[i]->sourceUnit.toStringPiece() == source) return conversionInfo_[i];
|
||||
}
|
||||
|
||||
status = U_INTERNAL_PROGRAM_ERROR;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
U_NAMESPACE_END
|
||||
|
|
|
@ -49,16 +49,28 @@ class U_I18N_API ConversionRateInfo : public UMemory {
|
|||
void U_I18N_API getAllConversionRates(MaybeStackVector<ConversionRateInfo> &result, UErrorCode &status);
|
||||
|
||||
/**
|
||||
* Temporary backward-compatibility function.
|
||||
*
|
||||
* TODO(hugovdm): ensure this gets removed. Currently
|
||||
* https://github.com/sffc/icu/pull/32 is making use of it.
|
||||
*
|
||||
* @param units Ignored.
|
||||
* @return the result of getAllConversionRates.
|
||||
* Contains all the supported conversion rates.
|
||||
*/
|
||||
MaybeStackVector<ConversionRateInfo>
|
||||
U_I18N_API getConversionRatesInfo(const MaybeStackVector<MeasureUnit> &units, UErrorCode &status);
|
||||
class U_I18N_API ConversionRates {
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param status Receives status.
|
||||
*/
|
||||
ConversionRates(UErrorCode &status) { getAllConversionRates(conversionInfo_, status); }
|
||||
|
||||
/**
|
||||
* Returns a pointer to the conversion rate info that match the `source`.
|
||||
*
|
||||
* @param source Contains the source.
|
||||
* @param status Receives status.
|
||||
*/
|
||||
const ConversionRateInfo *extractConversionInfo(StringPiece source, UErrorCode &status) const;
|
||||
|
||||
private:
|
||||
MaybeStackVector<ConversionRateInfo> conversionInfo_;
|
||||
};
|
||||
|
||||
U_NAMESPACE_END
|
||||
|
||||
|
|
|
@ -1063,7 +1063,7 @@ group: sharedbreakiterator
|
|||
breakiterator
|
||||
|
||||
group: units_extra
|
||||
measunit_extra.o unitconverter.o
|
||||
measunit_extra.o
|
||||
deps
|
||||
units ucharstriebuilder ucharstrie uclean_i18n
|
||||
|
||||
|
@ -1073,9 +1073,9 @@ group: units
|
|||
stringenumeration errorcode
|
||||
|
||||
group: unitsformatter
|
||||
unitsdata.o
|
||||
unitsdata.o unitconverter.o
|
||||
deps
|
||||
resourcebundle
|
||||
resourcebundle units_extra
|
||||
|
||||
group: decnumber
|
||||
decContext.o decNumber.o
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#if !UCONFIG_NO_FORMATTING
|
||||
|
||||
#include "charstr.h"
|
||||
#include "cmemory.h"
|
||||
#include "filestrm.h"
|
||||
#include "intltest.h"
|
||||
#include "number_decimalquantity.h"
|
||||
|
@ -13,6 +14,8 @@
|
|||
#include "unicode/measunit.h"
|
||||
#include "unicode/unistr.h"
|
||||
#include "unicode/unum.h"
|
||||
#include "unitconverter.h"
|
||||
#include "unitsdata.h"
|
||||
#include "uparse.h"
|
||||
|
||||
using icu::number::impl::DecimalQuantity;
|
||||
|
@ -23,6 +26,7 @@ class UnitsTest : public IntlTest {
|
|||
|
||||
void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = NULL);
|
||||
|
||||
void testConversionCapability();
|
||||
void testConversions();
|
||||
void testPreferences();
|
||||
// void testBasic();
|
||||
|
@ -35,10 +39,9 @@ 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);
|
||||
|
@ -51,15 +54,83 @@ void UnitsTest::runIndexedTest(int32_t index, UBool exec, const char *&name, cha
|
|||
|
||||
// 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"meter" && target == u"foot" && input == 1.0) return 3.28084;
|
||||
|
||||
if ( source == u"kilometer" && target == u"foot" && input == 1.0)
|
||||
return 328.084;
|
||||
if (source == u"kilometer" && target == u"foot" && input == 1.0) return 328.084;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void UnitsTest::testConversionCapability() {
|
||||
struct TestCase {
|
||||
const StringPiece source;
|
||||
const StringPiece target;
|
||||
const UnitsConvertibilityState 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", "p4-foot", CONVERTIBLE}, //
|
||||
{"square-kilometer-per-second", "second-per-square-meter", RECIPROCAL}, //
|
||||
// TODO: Remove the following test cases after hocking up unitsTest.txt.
|
||||
{"g-force", "meter-per-square-second", CONVERTIBLE}, //
|
||||
{"ohm", "kilogram-square-meter-per-cubic-second-square-ampere", CONVERTIBLE}, //
|
||||
{"electronvolt", "kilogram-square-meter-per-square-second", CONVERTIBLE}, //
|
||||
{"dalton", "kilogram-square-meter-per-square-second", CONVERTIBLE}, //
|
||||
{"joule", "kilogram-square-meter-per-square-second", CONVERTIBLE}, //
|
||||
{"meter-newton", "kilogram-square-meter-per-square-second", CONVERTIBLE}, //
|
||||
{"foot-pound-force", "kilogram-square-meter-per-square-second", CONVERTIBLE}, //
|
||||
{"calorie", "kilogram-square-meter-per-square-second", CONVERTIBLE}, //
|
||||
{"kilojoule", "kilogram-square-meter-per-square-second", CONVERTIBLE}, //
|
||||
{"british-thermal-unit", "kilogram-square-meter-per-square-second", CONVERTIBLE}, //
|
||||
{"foodcalorie", "kilogram-square-meter-per-square-second", CONVERTIBLE}, //
|
||||
{"kilocalorie", "kilogram-square-meter-per-square-second", CONVERTIBLE}, //
|
||||
{"hour-kilowatt", "kilogram-square-meter-second-per-cubic-second", CONVERTIBLE}, //
|
||||
{"therm-us", "kilogram-square-meter-per-square-second", CONVERTIBLE}, //
|
||||
{"newton", "kilogram-meter-per-square-second", CONVERTIBLE}, //
|
||||
{"pound-force", "kilogram-meter-per-square-second", CONVERTIBLE}, //
|
||||
{"hertz", "revolution-per-second", CONVERTIBLE}, //
|
||||
{"kilohertz", "revolution-per-second", CONVERTIBLE}, //
|
||||
{"megahertz", "revolution-per-second", CONVERTIBLE}, //
|
||||
{"gigahertz", "revolution-per-second", CONVERTIBLE}, //
|
||||
{"lux", "candela-square-meter-per-square-meter", CONVERTIBLE}, //
|
||||
{"milliwatt", "kilogram-square-meter-per-cubic-second", CONVERTIBLE}, //
|
||||
{"watt", "kilogram-square-meter-per-cubic-second", CONVERTIBLE}, //
|
||||
{"horsepower", "kilogram-square-meter-per-cubic-second", CONVERTIBLE}, //
|
||||
{"kilowatt", "kilogram-square-meter-per-cubic-second", CONVERTIBLE}, //
|
||||
{"megawatt", "kilogram-square-meter-per-cubic-second", CONVERTIBLE}, //
|
||||
{"gigawatt", "kilogram-square-meter-per-cubic-second", CONVERTIBLE}, //
|
||||
{"solar-luminosity", "kilogram-square-meter-per-cubic-second", CONVERTIBLE}, //
|
||||
{"pascal", "kilogram-per-meter-square-second", CONVERTIBLE}, //
|
||||
{"hectopascal", "kilogram-per-meter-square-second", CONVERTIBLE}, //
|
||||
{"millibar", "kilogram-per-meter-square-second", CONVERTIBLE}, //
|
||||
{"millimeter-ofhg", "kilogram-meter-per-square-meter-square-second", CONVERTIBLE}, //
|
||||
{"kilopascal", "kilogram-per-meter-square-second", CONVERTIBLE}, //
|
||||
{"inch-ofhg", "kilogram-meter-per-square-meter-square-second", CONVERTIBLE}, //
|
||||
{"bar", "kilogram-per-meter-square-second", CONVERTIBLE}, //
|
||||
{"atmosphere", "kilogram-per-meter-square-second", CONVERTIBLE}, //
|
||||
{"megapascal", "kilogram-per-meter-square-second", CONVERTIBLE}, //
|
||||
{"ofhg", "kilogram-per-square-meter-square-second", CONVERTIBLE}, //
|
||||
{"knot", "meter-per-second", CONVERTIBLE}, //
|
||||
{"volt", "kilogram-square-meter-per-cubic-second-ampere", CONVERTIBLE}, //
|
||||
};
|
||||
|
||||
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);
|
||||
auto convertibility = checkConvertibility(source, target, conversionRates, status);
|
||||
|
||||
assertEquals("Conversion Capability", testCase.expectedState, convertibility);
|
||||
}
|
||||
}
|
||||
|
||||
// void UnitsTest::testBasic() {
|
||||
// IcuTestErrorCode status(*this, "Units testBasic");
|
||||
|
||||
|
@ -72,7 +143,8 @@ double testConvert(UnicodeString source, UnicodeString target, double input) {
|
|||
// } testCases[]{{u"meter", u"foot", 1.0, 3.28084}, {u"kilometer", u"foot", 1.0, 328.084}};
|
||||
|
||||
// for (const auto &testCase : testCases) {
|
||||
// assertEquals("test convert", testConvert(testCase.source, testCase.target, testCase.inputValue),
|
||||
// assertEquals("test convert", testConvert(testCase.source, testCase.target,
|
||||
// testCase.inputValue),
|
||||
// testCase.expectedValue);
|
||||
// }
|
||||
// }
|
||||
|
@ -95,7 +167,8 @@ double testConvert(UnicodeString source, UnicodeString target, double input) {
|
|||
// };
|
||||
|
||||
// for (const auto &testCase : testCases) {
|
||||
// assertEquals("test convert", testConvert(testCase.source, testCase.target, testCase.inputValue),
|
||||
// assertEquals("test convert", testConvert(testCase.source, testCase.target,
|
||||
// testCase.inputValue),
|
||||
// testCase.expectedValue);
|
||||
// }
|
||||
// }
|
||||
|
@ -121,7 +194,8 @@ double testConvert(UnicodeString source, UnicodeString target, double input) {
|
|||
// };
|
||||
|
||||
// for (const auto &testCase : testCases) {
|
||||
// assertEquals("test convert", testConvert(testCase.source, testCase.target, testCase.inputValue),
|
||||
// assertEquals("test convert", testConvert(testCase.source, testCase.target,
|
||||
// testCase.inputValue),
|
||||
// testCase.expectedValue);
|
||||
// }
|
||||
// }
|
||||
|
@ -146,7 +220,8 @@ double testConvert(UnicodeString source, UnicodeString target, double input) {
|
|||
// };
|
||||
|
||||
// for (const auto &testCase : testCases) {
|
||||
// assertEquals("test convert", testConvert(testCase.source, testCase.target, testCase.inputValue),
|
||||
// assertEquals("test convert", testConvert(testCase.source, testCase.target,
|
||||
// testCase.inputValue),
|
||||
// testCase.expectedValue);
|
||||
// }
|
||||
// }
|
||||
|
@ -167,7 +242,8 @@ double testConvert(UnicodeString source, UnicodeString target, double input) {
|
|||
// };
|
||||
|
||||
// for (const auto &testCase : testCases) {
|
||||
// assertEquals("test convert", testConvert(testCase.source, testCase.target, testCase.inputValue),
|
||||
// assertEquals("test convert", testConvert(testCase.source, testCase.target,
|
||||
// testCase.inputValue),
|
||||
// testCase.expectedValue);
|
||||
// }
|
||||
// }
|
||||
|
@ -190,8 +266,8 @@ StringPiece trimField(char *(&field)[2]) {
|
|||
}
|
||||
|
||||
/**
|
||||
* WIP(hugovdm): deals with a single data-driven unit test for unit conversions.
|
||||
* This is a UParseLineFn as required by u_parseDelimitedFile.
|
||||
* Deals with a single data-driven unit test for unit conversions. This
|
||||
* UParseLineFn for use by u_parseDelimitedFile is intended for "unitsTest.txt".
|
||||
*/
|
||||
void unitsTestDataLineFn(void *context, char *fields[][2], int32_t fieldCount, UErrorCode *pErrorCode) {
|
||||
if (U_FAILURE(*pErrorCode)) return;
|
||||
|
@ -246,7 +322,8 @@ void unitsTestDataLineFn(void *context, char *fields[][2], int32_t fieldCount, U
|
|||
|
||||
// unitsTest->assertTrue(msg.data(), actualState != UNCONVERTIBLE);
|
||||
|
||||
// Unit conversion... untested:
|
||||
// TODO(hugovdm,younies): add conversion testing in unitsTestDataLineFn:
|
||||
//
|
||||
// UnitConverter converter(sourceUnit, targetUnit, status);
|
||||
// double got = converter.convert(1000, status);
|
||||
// unitsTest->assertEqualsNear(quantity.data(), expected, got, 0.0001);
|
||||
|
@ -255,6 +332,9 @@ void unitsTestDataLineFn(void *context, char *fields[][2], int32_t fieldCount, U
|
|||
/**
|
||||
* Runs data-driven unit tests for unit conversion. It looks for the test cases
|
||||
* in source/test/testdata/units/unitsTest.txt, which originates in CLDR.
|
||||
*
|
||||
* TODO(hugovdm,younies): add conversion testing in unitsTestDataLineFn (it only
|
||||
* tests convertability at the moment).
|
||||
*/
|
||||
void UnitsTest::testConversions() {
|
||||
const char *filename = "unitsTest.txt";
|
||||
|
@ -273,9 +353,8 @@ void UnitsTest::testConversions() {
|
|||
path.appendPathPart(filename, errorCode);
|
||||
|
||||
u_parseDelimitedFile(path.data(), ';', fields, kNumFields, unitsTestDataLineFn, this, errorCode);
|
||||
if (errorCode.errIfFailureAndReset("error parsing %s: %s\n", path.data(), u_errorName(errorCode))) {
|
||||
if (errorCode.errIfFailureAndReset("error parsing %s: %s\n", path.data(), u_errorName(errorCode)))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -537,4 +616,4 @@ void UnitsTest::testPreferences() {
|
|||
}
|
||||
}
|
||||
|
||||
#endif /* #if !UCONFIG_NO_FORMATTING */
|
||||
#endif /* #if !UCONFIG_NO_FORMATTING */
|
Loading…
Add table
Reference in a new issue