ICU-21349 Add extra UnitsConverter constructor that takes only CLDR unit identifiers

See #1578
This commit is contained in:
Younies Mahmoud 2021-02-23 19:59:41 +00:00
parent 0a1352ee67
commit c825da1d29
5 changed files with 133 additions and 22 deletions

View file

@ -490,15 +490,36 @@ Convertibility U_I18N_API extractConvertibility(const MeasureUnitImpl &source,
}
UnitsConverter::UnitsConverter(const MeasureUnitImpl &source, const MeasureUnitImpl &target,
const ConversionRates &ratesInfo, UErrorCode &status)
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) {
this->init(ratesInfo, status);
}
UnitsConverter::UnitsConverter(StringPiece sourceIdentifier, StringPiece targetIdentifier,
UErrorCode &status)
: conversionRate_(MeasureUnitImpl::forIdentifier(sourceIdentifier, status),
MeasureUnitImpl::forIdentifier(targetIdentifier, status)) {
if (U_FAILURE(status)) {
return;
}
ConversionRates ratesInfo(status);
this->init(ratesInfo, status);
}
void UnitsConverter::init(const ConversionRates &ratesInfo, UErrorCode &status) {
if (U_FAILURE(status)) {
return;
}
if (this->conversionRate_.source.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED ||
this->conversionRate_.target.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) {
status = U_INTERNAL_PROGRAM_ERROR;
return;
}
Convertibility unitsState = extractConvertibility(source, target, ratesInfo, status);
Convertibility unitsState = extractConvertibility(this->conversionRate_.source,
this->conversionRate_.target, ratesInfo, status);
if (U_FAILURE(status)) return;
if (unitsState == Convertibility::UNCONVERTIBLE) {
status = U_INTERNAL_PROGRAM_ERROR;
@ -507,11 +528,12 @@ UnitsConverter::UnitsConverter(const MeasureUnitImpl &source, const MeasureUnitI
loadConversionRate(conversionRate_, conversionRate_.source, conversionRate_.target, unitsState,
ratesInfo, status);
}
int32_t UnitsConverter::compareTwoUnits(const MeasureUnitImpl &firstUnit,
const MeasureUnitImpl &secondUnit,
const ConversionRates &ratesInfo, UErrorCode &status) {
const MeasureUnitImpl &secondUnit,
const ConversionRates &ratesInfo, UErrorCode &status) {
if (U_FAILURE(status)) {
return 0;
}
@ -532,8 +554,9 @@ int32_t UnitsConverter::compareTwoUnits(const MeasureUnitImpl &firstUnit,
return 0;
}
// Represents the conversion factor from the firstUnit to the base unit that specified in the
// conversion data which is considered as the root of the firstUnit and the secondUnit.
// Represents the conversion factor from the firstUnit to the base
// unit that specified in the conversion data which is considered as
// the root of the firstUnit and the secondUnit.
Factor firstUnitToBase = loadCompoundFactor(firstUnit, ratesInfo, status);
Factor secondUnitToBase = loadCompoundFactor(secondUnit, ratesInfo, status);

View file

@ -145,6 +145,20 @@ Convertibility U_I18N_API extractConvertibility(const MeasureUnitImpl &source,
*/
class U_I18N_API UnitsConverter : public UMemory {
public:
/**
* Constructor of `UnitConverter`.
* NOTE:
* - source and target must be under the same category
* - e.g. meter to mile --> both of them are length units.
* NOTE:
* This constructor creates an instance of `ConversionRates` internally.
*
* @param sourceIdentifier represents the source unit identifier.
* @param targetIdentifier represents the target unit identifier.
* @param status
*/
UnitsConverter(StringPiece sourceIdentifier, StringPiece targetIdentifier, UErrorCode &status);
/**
* Constructor of `UnitConverter`.
* NOTE:
@ -191,6 +205,11 @@ class U_I18N_API UnitsConverter : public UMemory {
private:
ConversionRate conversionRate_;
/**
* Initialises the object.
*/
void init(const ConversionRates &ratesInfo, UErrorCode &status);
};
} // namespace units

View file

@ -363,6 +363,30 @@ void UnitsTest::testConverter() {
assertEqualsNear(
UnicodeString("testConverter inverse: ") + testCase.target + " back to " + testCase.source,
testCase.inputValue, converter.convertInverse(testCase.expectedValue), maxDelta);
// TODO: Test UnitsConverter created using CLDR separately.
// Test UnitsConverter created by CLDR unit identifiers
UnitsConverter converter2(testCase.source, testCase.target, status);
if (status.errIfFailureAndReset("UnitsConverter(<%s>, <%s>, ...)", testCase.source,
testCase.target)) {
continue;
}
maxDelta = 1e-6 * uprv_fabs(testCase.expectedValue);
if (testCase.expectedValue == 0) {
maxDelta = 1e-12;
}
assertEqualsNear(UnicodeString("testConverter2: ") + testCase.source + " to " + testCase.target,
testCase.expectedValue, converter2.convert(testCase.inputValue), maxDelta);
maxDelta = 1e-6 * uprv_fabs(testCase.inputValue);
if (testCase.inputValue == 0) {
maxDelta = 1e-12;
}
assertEqualsNear(
UnicodeString("testConverter2 inverse: ") + testCase.target + " back to " + testCase.source,
testCase.inputValue, converter2.convertInverse(testCase.expectedValue), maxDelta);
}
}

View file

@ -16,6 +16,26 @@ public class UnitsConverter {
private boolean reciprocal;
private BigDecimal offset;
/**
* Constructor of <code>UnitsConverter</code>.
* NOTE:
* - source and target must be under the same category
* - e.g. meter to mile --> both of them are length units.
* <p>
* NOTE:
* This constructor creates an instance of <code>UnitsConverter</code> internally.
*
* @param sourceIdentifier represents the source unit identifier.
* @param targetIdentifier represents the target unit identifier.
*/
public UnitsConverter(String sourceIdentifier, String targetIdentifier) {
this(
MeasureUnitImpl.forIdentifier(sourceIdentifier),
MeasureUnitImpl.forIdentifier(targetIdentifier),
new ConversionRates()
);
}
/**
* Constructor of <code>UnitsConverter</code>.
* NOTE:

View file

@ -371,14 +371,14 @@ public class UnitsTest {
@Test
public void testConverter() {
class TestData {
MeasureUnitImpl source;
MeasureUnitImpl target;
BigDecimal input;
BigDecimal expected;
final String sourceIdentifier;
final String targetIdentifier;
final BigDecimal input;
final BigDecimal expected;
TestData(String source, String target, double input, double expected) {
this.source = MeasureUnitImpl.UnitsParser.parseForIdentifier(source);
this.target = MeasureUnitImpl.UnitsParser.parseForIdentifier(target);
TestData(String sourceIdentifier, String targetIdentifier, double input, double expected) {
this.sourceIdentifier = sourceIdentifier;
this.targetIdentifier = targetIdentifier;
this.input = BigDecimal.valueOf(input);
this.expected = BigDecimal.valueOf(expected);
}
@ -440,23 +440,48 @@ public class UnitsTest {
ConversionRates conversionRates = new ConversionRates();
for (TestData test : tests) {
UnitsConverter converter = new UnitsConverter(test.source, test.target, conversionRates);
MeasureUnitImpl source = MeasureUnitImpl.forIdentifier(test.sourceIdentifier);
MeasureUnitImpl target = MeasureUnitImpl.forIdentifier(test.targetIdentifier);
UnitsConverter converter = new UnitsConverter(source, target, conversionRates);
double maxDelta = 1e-6 * Math.abs(test.expected.doubleValue());
if (test.expected.doubleValue() == 0) {
maxDelta = 1e-12;
}
assertEquals("testConverter: " + test.source + " to " + test.target,
test.expected.doubleValue(), converter.convert(test.input).doubleValue(),
maxDelta);
assertEquals("testConverter: " + test.sourceIdentifier + " to " + test.targetIdentifier,
test.expected.doubleValue(), converter.convert(test.input).doubleValue(),
maxDelta);
maxDelta = 1e-6 * Math.abs(test.input.doubleValue());
if (test.input.doubleValue() == 0) {
maxDelta = 1e-12;
}
assertEquals("testConverter inverse: " + test.target + " back to " + test.source,
test.input.doubleValue(), converter.convertInverse(test.expected).doubleValue(),
maxDelta);
assertEquals(
"testConverter inverse: " + test.targetIdentifier + " back to " + test.sourceIdentifier,
test.input.doubleValue(), converter.convertInverse(test.expected).doubleValue(),
maxDelta);
// TODO: Test UnitsConverter created using CLDR separately.
// Test UnitsConverter created by CLDR unit identifiers
UnitsConverter converter2 = new UnitsConverter(test.sourceIdentifier, test.targetIdentifier);
maxDelta = 1e-6 * Math.abs(test.expected.doubleValue());
if (test.expected.doubleValue() == 0) {
maxDelta = 1e-12;
}
assertEquals("testConverter2: " + test.sourceIdentifier + " to " + test.targetIdentifier,
test.expected.doubleValue(), converter2.convert(test.input).doubleValue(),
maxDelta);
maxDelta = 1e-6 * Math.abs(test.input.doubleValue());
if (test.input.doubleValue() == 0) {
maxDelta = 1e-12;
}
assertEquals("testConverter2 inverse: " + test.targetIdentifier + " back to " + test.sourceIdentifier,
test.input.doubleValue(), converter2.convertInverse(test.expected).doubleValue(),
maxDelta);
}
}