1 // © 2020 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3 
4 #include "unicode/utypes.h"
5 
6 #if !UCONFIG_NO_FORMATTING
7 
8 #include "charstr.h"
9 #include "cmemory.h"
10 #include "double-conversion-string-to-double.h"
11 #include "measunit_impl.h"
12 #include "uassert.h"
13 #include "unicode/errorcode.h"
14 #include "unicode/localpointer.h"
15 #include "unicode/stringpiece.h"
16 #include "units_converter.h"
17 #include <algorithm>
18 #include <cmath>
19 #include <stdlib.h>
20 #include <utility>
21 
22 U_NAMESPACE_BEGIN
23 namespace units {
24 
multiplyBy(const Factor & rhs)25 void U_I18N_API Factor::multiplyBy(const Factor &rhs) {
26     factorNum *= rhs.factorNum;
27     factorDen *= rhs.factorDen;
28     for (int i = 0; i < CONSTANTS_COUNT; i++) {
29         constantExponents[i] += rhs.constantExponents[i];
30     }
31 
32     // NOTE
33     //  We need the offset when the source and the target are simple units. e.g. the source is
34     //  celsius and the target is Fahrenheit. Therefore, we just keep the value using `std::max`.
35     offset = std::max(rhs.offset, offset);
36 }
37 
divideBy(const Factor & rhs)38 void U_I18N_API Factor::divideBy(const Factor &rhs) {
39     factorNum *= rhs.factorDen;
40     factorDen *= rhs.factorNum;
41     for (int i = 0; i < CONSTANTS_COUNT; i++) {
42         constantExponents[i] -= rhs.constantExponents[i];
43     }
44 
45     // NOTE
46     //  We need the offset when the source and the target are simple units. e.g. the source is
47     //  celsius and the target is Fahrenheit. Therefore, we just keep the value using `std::max`.
48     offset = std::max(rhs.offset, offset);
49 }
50 
power(int32_t power)51 void U_I18N_API Factor::power(int32_t power) {
52     // multiply all the constant by the power.
53     for (int i = 0; i < CONSTANTS_COUNT; i++) {
54         constantExponents[i] *= power;
55     }
56 
57     bool shouldFlip = power < 0; // This means that after applying the absolute power, we should flip
58                                  // the Numerator and Denominator.
59 
60     factorNum = std::pow(factorNum, std::abs(power));
61     factorDen = std::pow(factorDen, std::abs(power));
62 
63     if (shouldFlip) {
64         // Flip Numerator and Denominator.
65         std::swap(factorNum, factorDen);
66     }
67 }
68 
applyPrefix(UMeasurePrefix unitPrefix)69 void U_I18N_API Factor::applyPrefix(UMeasurePrefix unitPrefix) {
70     if (unitPrefix == UMeasurePrefix::UMEASURE_PREFIX_ONE) {
71         // No need to do anything
72         return;
73     }
74 
75     int32_t prefixPower = umeas_getPrefixPower(unitPrefix);
76     double prefixFactor = std::pow((double)umeas_getPrefixBase(unitPrefix), (double)std::abs(prefixPower));
77     if (prefixPower >= 0) {
78         factorNum *= prefixFactor;
79     } else {
80         factorDen *= prefixFactor;
81     }
82 }
83 
substituteConstants()84 void U_I18N_API Factor::substituteConstants() {
85     for (int i = 0; i < CONSTANTS_COUNT; i++) {
86         if (this->constantExponents[i] == 0) {
87             continue;
88         }
89 
90         auto absPower = std::abs(this->constantExponents[i]);
91         Signum powerSig = this->constantExponents[i] < 0 ? Signum::NEGATIVE : Signum::POSITIVE;
92         double absConstantValue = std::pow(constantsValues[i], absPower);
93 
94         if (powerSig == Signum::NEGATIVE) {
95             this->factorDen *= absConstantValue;
96         } else {
97             this->factorNum *= absConstantValue;
98         }
99 
100         this->constantExponents[i] = 0;
101     }
102 }
103 
104 namespace {
105 
106 /* Helpers */
107 
108 using icu::double_conversion::StringToDoubleConverter;
109 
110 // TODO: Make this a shared-utility function.
111 // Returns `double` from a scientific number(i.e. "1", "2.01" or "3.09E+4")
strToDouble(StringPiece strNum,UErrorCode & status)112 double strToDouble(StringPiece strNum, UErrorCode &status) {
113     // We are processing well-formed input, so we don't need any special options to
114     // StringToDoubleConverter.
115     StringToDoubleConverter converter(0, 0, 0, "", "");
116     int32_t count;
117     double result = converter.StringToDouble(strNum.data(), strNum.length(), &count);
118     if (count != strNum.length()) {
119         status = U_INVALID_FORMAT_ERROR;
120     }
121 
122     return result;
123 }
124 
125 // Returns `double` from a scientific number that could has a division sign (i.e. "1", "2.01", "3.09E+4"
126 // or "2E+2/3")
strHasDivideSignToDouble(StringPiece strWithDivide,UErrorCode & status)127 double strHasDivideSignToDouble(StringPiece strWithDivide, UErrorCode &status) {
128     int divisionSignInd = -1;
129     for (int i = 0, n = strWithDivide.length(); i < n; ++i) {
130         if (strWithDivide.data()[i] == '/') {
131             divisionSignInd = i;
132             break;
133         }
134     }
135 
136     if (divisionSignInd >= 0) {
137         return strToDouble(strWithDivide.substr(0, divisionSignInd), status) /
138                strToDouble(strWithDivide.substr(divisionSignInd + 1), status);
139     }
140 
141     return strToDouble(strWithDivide, status);
142 }
143 
144 /*
145   Adds single factor to a `Factor` object. Single factor means "23^2", "23.3333", "ft2m^3" ...etc.
146   However, complex factor are not included, such as "ft2m^3*200/3"
147 */
addFactorElement(Factor & factor,StringPiece elementStr,Signum signum,UErrorCode & status)148 void addFactorElement(Factor &factor, StringPiece elementStr, Signum signum, UErrorCode &status) {
149     StringPiece baseStr;
150     StringPiece powerStr;
151     int32_t power =
152         1; // In case the power is not written, then, the power is equal 1 ==> `ft2m^1` == `ft2m`
153 
154     // Search for the power part
155     int32_t powerInd = -1;
156     for (int32_t i = 0, n = elementStr.length(); i < n; ++i) {
157         if (elementStr.data()[i] == '^') {
158             powerInd = i;
159             break;
160         }
161     }
162 
163     if (powerInd > -1) {
164         // There is power
165         baseStr = elementStr.substr(0, powerInd);
166         powerStr = elementStr.substr(powerInd + 1);
167 
168         power = static_cast<int32_t>(strToDouble(powerStr, status));
169     } else {
170         baseStr = elementStr;
171     }
172 
173     addSingleFactorConstant(baseStr, power, signum, factor, status);
174 }
175 
176 /*
177  * Extracts `Factor` from a complete string factor. e.g. "ft2m^3*1007/cup2m3*3"
178  */
extractFactorConversions(StringPiece stringFactor,UErrorCode & status)179 Factor extractFactorConversions(StringPiece stringFactor, UErrorCode &status) {
180     Factor result;
181     Signum signum = Signum::POSITIVE;
182     auto factorData = stringFactor.data();
183     for (int32_t i = 0, start = 0, n = stringFactor.length(); i < n; i++) {
184         if (factorData[i] == '*' || factorData[i] == '/') {
185             StringPiece factorElement = stringFactor.substr(start, i - start);
186             addFactorElement(result, factorElement, signum, status);
187 
188             start = i + 1; // Set `start` to point to the start of the new element.
189         } else if (i == n - 1) {
190             // Last element
191             addFactorElement(result, stringFactor.substr(start, i + 1), signum, status);
192         }
193 
194         if (factorData[i] == '/') {
195             signum = Signum::NEGATIVE; // Change the signum because we reached the Denominator.
196         }
197     }
198 
199     return result;
200 }
201 
202 // Load factor for a single source
loadSingleFactor(StringPiece source,const ConversionRates & ratesInfo,UErrorCode & status)203 Factor loadSingleFactor(StringPiece source, const ConversionRates &ratesInfo, UErrorCode &status) {
204     const auto conversionUnit = ratesInfo.extractConversionInfo(source, status);
205     if (U_FAILURE(status)) return Factor();
206     if (conversionUnit == nullptr) {
207         status = U_INTERNAL_PROGRAM_ERROR;
208         return Factor();
209     }
210 
211     Factor result = extractFactorConversions(conversionUnit->factor.toStringPiece(), status);
212     result.offset = strHasDivideSignToDouble(conversionUnit->offset.toStringPiece(), status);
213 
214     return result;
215 }
216 
217 // Load Factor of a compound source unit.
218 // In ICU4J, this is a pair of ConversionRates.getFactorToBase() functions.
loadCompoundFactor(const MeasureUnitImpl & source,const ConversionRates & ratesInfo,UErrorCode & status)219 Factor loadCompoundFactor(const MeasureUnitImpl &source, const ConversionRates &ratesInfo,
220                           UErrorCode &status) {
221 
222     Factor result;
223     for (int32_t i = 0, n = source.singleUnits.length(); i < n; i++) {
224         SingleUnitImpl singleUnit = *source.singleUnits[i];
225 
226         Factor singleFactor = loadSingleFactor(singleUnit.getSimpleUnitID(), ratesInfo, status);
227         if (U_FAILURE(status)) return result;
228 
229         // Prefix before power, because:
230         // - square-kilometer to square-meter: (1000)^2
231         // - square-kilometer to square-foot (approximate): (3.28*1000)^2
232         singleFactor.applyPrefix(singleUnit.unitPrefix);
233 
234         // Apply the power of the `dimensionality`
235         singleFactor.power(singleUnit.dimensionality);
236 
237         result.multiplyBy(singleFactor);
238     }
239 
240     return result;
241 }
242 
243 /**
244  * Checks if the source unit and the target unit are simple. For example celsius or fahrenheit. But not
245  * square-celsius or square-fahrenheit.
246  *
247  * NOTE:
248  *  Empty unit means simple unit.
249  *
250  * In ICU4J, this is ConversionRates.checkSimpleUnit().
251  */
checkSimpleUnit(const MeasureUnitImpl & unit,UErrorCode & status)252 UBool checkSimpleUnit(const MeasureUnitImpl &unit, UErrorCode &status) {
253     if (U_FAILURE(status)) return false;
254 
255     if (unit.complexity != UMEASURE_UNIT_SINGLE) {
256         return false;
257     }
258     if (unit.singleUnits.length() == 0) {
259         // Empty units means simple unit.
260         return true;
261     }
262 
263     auto singleUnit = *(unit.singleUnits[0]);
264 
265     if (singleUnit.dimensionality != 1 || singleUnit.unitPrefix != UMEASURE_PREFIX_ONE) {
266         return false;
267     }
268 
269     return true;
270 }
271 
272 /**
273  *  Extract conversion rate from `source` to `target`
274  */
275 // In ICU4J, this function is partially inlined in the UnitsConverter constructor.
loadConversionRate(ConversionRate & conversionRate,const MeasureUnitImpl & source,const MeasureUnitImpl & target,Convertibility unitsState,const ConversionRates & ratesInfo,UErrorCode & status)276 void loadConversionRate(ConversionRate &conversionRate, const MeasureUnitImpl &source,
277                         const MeasureUnitImpl &target, Convertibility unitsState,
278                         const ConversionRates &ratesInfo, UErrorCode &status) {
279     // Represents the conversion factor from the source to the target.
280     Factor finalFactor;
281 
282     // Represents the conversion factor from the source to the base unit that specified in the conversion
283     // data which is considered as the root of the source and the target.
284     Factor sourceToBase = loadCompoundFactor(source, ratesInfo, status);
285     Factor targetToBase = loadCompoundFactor(target, ratesInfo, status);
286 
287     // Merger Factors
288     finalFactor.multiplyBy(sourceToBase);
289     if (unitsState == Convertibility::CONVERTIBLE) {
290         finalFactor.divideBy(targetToBase);
291     } else if (unitsState == Convertibility::RECIPROCAL) {
292         finalFactor.multiplyBy(targetToBase);
293     } else {
294         status = UErrorCode::U_ARGUMENT_TYPE_MISMATCH;
295         return;
296     }
297 
298     finalFactor.substituteConstants();
299 
300     conversionRate.factorNum = finalFactor.factorNum;
301     conversionRate.factorDen = finalFactor.factorDen;
302 
303     // This code corresponds to ICU4J's ConversionRates.getOffset().
304     // In case of simple units (such as: celsius or fahrenheit), offsets are considered.
305     if (checkSimpleUnit(source, status) && checkSimpleUnit(target, status)) {
306         conversionRate.sourceOffset =
307             sourceToBase.offset * sourceToBase.factorDen / sourceToBase.factorNum;
308         conversionRate.targetOffset =
309             targetToBase.offset * targetToBase.factorDen / targetToBase.factorNum;
310     }
311     // TODO(icu-units#127): should we consider failure if there's an offset for
312     // a not-simple-unit? What about kilokelvin / kilocelsius?
313 
314     conversionRate.reciprocal = unitsState == Convertibility::RECIPROCAL;
315 }
316 
317 struct UnitIndexAndDimension : UMemory {
318     int32_t index = 0;
319     int32_t dimensionality = 0;
320 
UnitIndexAndDimensionunits::__anon818966e00111::UnitIndexAndDimension321     UnitIndexAndDimension(const SingleUnitImpl &singleUnit, int32_t multiplier) {
322         index = singleUnit.index;
323         dimensionality = singleUnit.dimensionality * multiplier;
324     }
325 };
326 
mergeSingleUnitWithDimension(MaybeStackVector<UnitIndexAndDimension> & unitIndicesWithDimension,const SingleUnitImpl & shouldBeMerged,int32_t multiplier)327 void mergeSingleUnitWithDimension(MaybeStackVector<UnitIndexAndDimension> &unitIndicesWithDimension,
328                                   const SingleUnitImpl &shouldBeMerged, int32_t multiplier) {
329     for (int32_t i = 0; i < unitIndicesWithDimension.length(); i++) {
330         auto &unitWithIndex = *unitIndicesWithDimension[i];
331         if (unitWithIndex.index == shouldBeMerged.index) {
332             unitWithIndex.dimensionality += shouldBeMerged.dimensionality * multiplier;
333             return;
334         }
335     }
336 
337     unitIndicesWithDimension.emplaceBack(shouldBeMerged, multiplier);
338 }
339 
mergeUnitsAndDimensions(MaybeStackVector<UnitIndexAndDimension> & unitIndicesWithDimension,const MeasureUnitImpl & shouldBeMerged,int32_t multiplier)340 void mergeUnitsAndDimensions(MaybeStackVector<UnitIndexAndDimension> &unitIndicesWithDimension,
341                              const MeasureUnitImpl &shouldBeMerged, int32_t multiplier) {
342     for (int32_t unit_i = 0; unit_i < shouldBeMerged.singleUnits.length(); unit_i++) {
343         auto singleUnit = *shouldBeMerged.singleUnits[unit_i];
344         mergeSingleUnitWithDimension(unitIndicesWithDimension, singleUnit, multiplier);
345     }
346 }
347 
checkAllDimensionsAreZeros(const MaybeStackVector<UnitIndexAndDimension> & dimensionVector)348 UBool checkAllDimensionsAreZeros(const MaybeStackVector<UnitIndexAndDimension> &dimensionVector) {
349     for (int32_t i = 0; i < dimensionVector.length(); i++) {
350         if (dimensionVector[i]->dimensionality != 0) {
351             return false;
352         }
353     }
354 
355     return true;
356 }
357 
358 } // namespace
359 
360 // Conceptually, this modifies factor: factor *= baseStr^(signum*power).
361 //
362 // baseStr must be a known constant or a value that strToDouble() is able to
363 // parse.
addSingleFactorConstant(StringPiece baseStr,int32_t power,Signum signum,Factor & factor,UErrorCode & status)364 void U_I18N_API addSingleFactorConstant(StringPiece baseStr, int32_t power, Signum signum,
365                                         Factor &factor, UErrorCode &status) {
366     if (baseStr == "ft_to_m") {
367         factor.constantExponents[CONSTANT_FT2M] += power * signum;
368     } else if (baseStr == "ft2_to_m2") {
369         factor.constantExponents[CONSTANT_FT2M] += 2 * power * signum;
370     } else if (baseStr == "ft3_to_m3") {
371         factor.constantExponents[CONSTANT_FT2M] += 3 * power * signum;
372     } else if (baseStr == "in3_to_m3") {
373         factor.constantExponents[CONSTANT_FT2M] += 3 * power * signum;
374         factor.factorDen *= 12 * 12 * 12;
375     } else if (baseStr == "gal_to_m3") {
376         factor.factorNum *= 231;
377         factor.constantExponents[CONSTANT_FT2M] += 3 * power * signum;
378         factor.factorDen *= 12 * 12 * 12;
379     } else if (baseStr == "gal_imp_to_m3") {
380         factor.constantExponents[CONSTANT_GAL_IMP2M3] += power * signum;
381     } else if (baseStr == "G") {
382         factor.constantExponents[CONSTANT_G] += power * signum;
383     } else if (baseStr == "gravity") {
384         factor.constantExponents[CONSTANT_GRAVITY] += power * signum;
385     } else if (baseStr == "lb_to_kg") {
386         factor.constantExponents[CONSTANT_LB2KG] += power * signum;
387     } else if (baseStr == "glucose_molar_mass") {
388         factor.constantExponents[CONSTANT_GLUCOSE_MOLAR_MASS] += power * signum;
389     } else if (baseStr == "item_per_mole") {
390         factor.constantExponents[CONSTANT_ITEM_PER_MOLE] += power * signum;
391     } else if (baseStr == "PI") {
392         factor.constantExponents[CONSTANT_PI] += power * signum;
393     } else {
394         if (signum == Signum::NEGATIVE) {
395             factor.factorDen *= std::pow(strToDouble(baseStr, status), power);
396         } else {
397             factor.factorNum *= std::pow(strToDouble(baseStr, status), power);
398         }
399     }
400 }
401 
402 /**
403  * Extracts the compound base unit of a compound unit (`source`). For example, if the source unit is
404  * `square-mile-per-hour`, the compound base unit will be `square-meter-per-second`
405  */
extractCompoundBaseUnit(const MeasureUnitImpl & source,const ConversionRates & conversionRates,UErrorCode & status)406 MeasureUnitImpl U_I18N_API extractCompoundBaseUnit(const MeasureUnitImpl &source,
407                                                    const ConversionRates &conversionRates,
408                                                    UErrorCode &status) {
409 
410     MeasureUnitImpl result;
411     if (U_FAILURE(status)) return result;
412 
413     const auto &singleUnits = source.singleUnits;
414     for (int i = 0, count = singleUnits.length(); i < count; ++i) {
415         const auto &singleUnit = *singleUnits[i];
416         // Extract `ConversionRateInfo` using the absolute unit. For example: in case of `square-meter`,
417         // we will use `meter`
418         const auto rateInfo =
419             conversionRates.extractConversionInfo(singleUnit.getSimpleUnitID(), status);
420         if (U_FAILURE(status)) {
421             return result;
422         }
423         if (rateInfo == nullptr) {
424             status = U_INTERNAL_PROGRAM_ERROR;
425             return result;
426         }
427 
428         // Multiply the power of the singleUnit by the power of the baseUnit. For example, square-hectare
429         // must be pow4-meter. (NOTE: hectare --> square-meter)
430         auto baseUnits =
431             MeasureUnitImpl::forIdentifier(rateInfo->baseUnit.toStringPiece(), status).singleUnits;
432         for (int32_t i = 0, baseUnitsCount = baseUnits.length(); i < baseUnitsCount; i++) {
433             baseUnits[i]->dimensionality *= singleUnit.dimensionality;
434             // TODO: Deal with SI-prefix
435             result.appendSingleUnit(*baseUnits[i], status);
436 
437             if (U_FAILURE(status)) {
438                 return result;
439             }
440         }
441     }
442 
443     return result;
444 }
445 
446 /**
447  * Determine the convertibility between `source` and `target`.
448  * For example:
449  *    `meter` and `foot` are `CONVERTIBLE`.
450  *    `meter-per-second` and `second-per-meter` are `RECIPROCAL`.
451  *    `meter` and `pound` are `UNCONVERTIBLE`.
452  *
453  * NOTE:
454  *    Only works with SINGLE and COMPOUND units. If one of the units is a
455  *    MIXED unit, an error will occur. For more information, see UMeasureUnitComplexity.
456  */
extractConvertibility(const MeasureUnitImpl & source,const MeasureUnitImpl & target,const ConversionRates & conversionRates,UErrorCode & status)457 Convertibility U_I18N_API extractConvertibility(const MeasureUnitImpl &source,
458                                                 const MeasureUnitImpl &target,
459                                                 const ConversionRates &conversionRates,
460                                                 UErrorCode &status) {
461 
462     if (source.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED ||
463         target.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) {
464         status = U_INTERNAL_PROGRAM_ERROR;
465         return UNCONVERTIBLE;
466     }
467 
468     MeasureUnitImpl sourceBaseUnit = extractCompoundBaseUnit(source, conversionRates, status);
469     MeasureUnitImpl targetBaseUnit = extractCompoundBaseUnit(target, conversionRates, status);
470     if (U_FAILURE(status)) return UNCONVERTIBLE;
471 
472     MaybeStackVector<UnitIndexAndDimension> convertible;
473     MaybeStackVector<UnitIndexAndDimension> reciprocal;
474 
475     mergeUnitsAndDimensions(convertible, sourceBaseUnit, 1);
476     mergeUnitsAndDimensions(reciprocal, sourceBaseUnit, 1);
477 
478     mergeUnitsAndDimensions(convertible, targetBaseUnit, -1);
479     mergeUnitsAndDimensions(reciprocal, targetBaseUnit, 1);
480 
481     if (checkAllDimensionsAreZeros(convertible)) {
482         return CONVERTIBLE;
483     }
484 
485     if (checkAllDimensionsAreZeros(reciprocal)) {
486         return RECIPROCAL;
487     }
488 
489     return UNCONVERTIBLE;
490 }
491 
UnitsConverter(const MeasureUnitImpl & source,const MeasureUnitImpl & target,const ConversionRates & ratesInfo,UErrorCode & status)492 UnitsConverter::UnitsConverter(const MeasureUnitImpl &source, const MeasureUnitImpl &target,
493                                const ConversionRates &ratesInfo, UErrorCode &status)
494     : conversionRate_(source.copy(status), target.copy(status)) {
495     this->init(ratesInfo, status);
496 }
497 
UnitsConverter(StringPiece sourceIdentifier,StringPiece targetIdentifier,UErrorCode & status)498 UnitsConverter::UnitsConverter(StringPiece sourceIdentifier, StringPiece targetIdentifier,
499                                UErrorCode &status)
500     : conversionRate_(MeasureUnitImpl::forIdentifier(sourceIdentifier, status),
501                       MeasureUnitImpl::forIdentifier(targetIdentifier, status)) {
502     if (U_FAILURE(status)) {
503         return;
504     }
505 
506     ConversionRates ratesInfo(status);
507     this->init(ratesInfo, status);
508 }
509 
init(const ConversionRates & ratesInfo,UErrorCode & status)510 void UnitsConverter::init(const ConversionRates &ratesInfo, UErrorCode &status) {
511     if (U_FAILURE(status)) {
512         return;
513     }
514 
515     if (this->conversionRate_.source.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED ||
516         this->conversionRate_.target.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) {
517         status = U_INTERNAL_PROGRAM_ERROR;
518         return;
519     }
520 
521     Convertibility unitsState = extractConvertibility(this->conversionRate_.source,
522                                                       this->conversionRate_.target, ratesInfo, status);
523     if (U_FAILURE(status)) return;
524     if (unitsState == Convertibility::UNCONVERTIBLE) {
525         status = U_INTERNAL_PROGRAM_ERROR;
526         return;
527     }
528 
529     loadConversionRate(conversionRate_, conversionRate_.source, conversionRate_.target, unitsState,
530                        ratesInfo, status);
531 
532 }
533 
compareTwoUnits(const MeasureUnitImpl & firstUnit,const MeasureUnitImpl & secondUnit,const ConversionRates & ratesInfo,UErrorCode & status)534 int32_t UnitsConverter::compareTwoUnits(const MeasureUnitImpl &firstUnit,
535                                         const MeasureUnitImpl &secondUnit,
536                                         const ConversionRates &ratesInfo, UErrorCode &status) {
537     if (U_FAILURE(status)) {
538         return 0;
539     }
540 
541     if (firstUnit.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED ||
542         secondUnit.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) {
543         status = U_INTERNAL_PROGRAM_ERROR;
544         return 0;
545     }
546 
547     Convertibility unitsState = extractConvertibility(firstUnit, secondUnit, ratesInfo, status);
548     if (U_FAILURE(status)) {
549         return 0;
550     }
551 
552     if (unitsState == Convertibility::UNCONVERTIBLE || unitsState == Convertibility::RECIPROCAL) {
553         status = U_INTERNAL_PROGRAM_ERROR;
554         return 0;
555     }
556 
557     // Represents the conversion factor from the firstUnit to the base
558     // unit that specified in the conversion data which is considered as
559     // the root of the firstUnit and the secondUnit.
560     Factor firstUnitToBase = loadCompoundFactor(firstUnit, ratesInfo, status);
561     Factor secondUnitToBase = loadCompoundFactor(secondUnit, ratesInfo, status);
562 
563     firstUnitToBase.substituteConstants();
564     secondUnitToBase.substituteConstants();
565 
566     double firstUnitToBaseConversionRate = firstUnitToBase.factorNum / firstUnitToBase.factorDen;
567     double secondUnitToBaseConversionRate = secondUnitToBase.factorNum / secondUnitToBase.factorDen;
568 
569     double diff = firstUnitToBaseConversionRate - secondUnitToBaseConversionRate;
570     if (diff > 0) {
571         return 1;
572     }
573 
574     if (diff < 0) {
575         return -1;
576     }
577 
578     return 0;
579 }
580 
convert(double inputValue) const581 double UnitsConverter::convert(double inputValue) const {
582     double result =
583         inputValue + conversionRate_.sourceOffset; // Reset the input to the target zero index.
584     // Convert the quantity to from the source scale to the target scale.
585     result *= conversionRate_.factorNum / conversionRate_.factorDen;
586 
587     result -= conversionRate_.targetOffset; // Set the result to its index.
588 
589     if (conversionRate_.reciprocal) {
590         if (result == 0) {
591             // TODO: demonstrate the resulting behaviour in tests... and figure
592             // out desired behaviour. (Theoretical result should be infinity,
593             // not 0.)
594             return 0.0;
595         }
596         result = 1.0 / result;
597     }
598 
599     return result;
600 }
601 
convertInverse(double inputValue) const602 double UnitsConverter::convertInverse(double inputValue) const {
603     double result = inputValue;
604     if (conversionRate_.reciprocal) {
605         if (result == 0) {
606             // TODO: demonstrate the resulting behaviour in tests... and figure
607             // out desired behaviour. (Theoretical result should be infinity,
608             // not 0.)
609             return 0.0;
610         }
611         result = 1.0 / result;
612     }
613     result += conversionRate_.targetOffset;
614     result *= conversionRate_.factorDen / conversionRate_.factorNum;
615     result -= conversionRate_.sourceOffset;
616     return result;
617 }
618 
getConversionInfo() const619 ConversionInfo UnitsConverter::getConversionInfo() const {
620     ConversionInfo result;
621     result.conversionRate = conversionRate_.factorNum / conversionRate_.factorDen;
622     result.offset =
623         (conversionRate_.sourceOffset * (conversionRate_.factorNum / conversionRate_.factorDen)) -
624         conversionRate_.targetOffset;
625     result.reciprocal = conversionRate_.reciprocal;
626 
627     return result;
628 }
629 
630 } // namespace units
631 U_NAMESPACE_END
632 
633 #endif /* #if !UCONFIG_NO_FORMATTING */
634