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