Coverage Report

Created: 2025-06-24 06:43

/src/icu/source/i18n/units_converter.cpp
Line
Count
Source (jump to first uncovered line)
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
25
0
void U_I18N_API Factor::multiplyBy(const Factor &rhs) {
26
0
    factorNum *= rhs.factorNum;
27
0
    factorDen *= rhs.factorDen;
28
0
    for (int i = 0; i < CONSTANTS_COUNT; i++) {
29
0
        constantExponents[i] += rhs.constantExponents[i];
30
0
    }
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
0
    offset = std::max(rhs.offset, offset);
36
0
}
37
38
0
void U_I18N_API Factor::divideBy(const Factor &rhs) {
39
0
    factorNum *= rhs.factorDen;
40
0
    factorDen *= rhs.factorNum;
41
0
    for (int i = 0; i < CONSTANTS_COUNT; i++) {
42
0
        constantExponents[i] -= rhs.constantExponents[i];
43
0
    }
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
0
    offset = std::max(rhs.offset, offset);
49
0
}
50
51
0
void U_I18N_API Factor::power(int32_t power) {
52
    // multiply all the constant by the power.
53
0
    for (int i = 0; i < CONSTANTS_COUNT; i++) {
54
0
        constantExponents[i] *= power;
55
0
    }
56
57
0
    bool shouldFlip = power < 0; // This means that after applying the absolute power, we should flip
58
                                 // the Numerator and Denominator.
59
60
0
    factorNum = std::pow(factorNum, std::abs(power));
61
0
    factorDen = std::pow(factorDen, std::abs(power));
62
63
0
    if (shouldFlip) {
64
        // Flip Numerator and Denominator.
65
0
        std::swap(factorNum, factorDen);
66
0
    }
67
0
}
68
69
0
void U_I18N_API Factor::applyPrefix(UMeasurePrefix unitPrefix) {
70
0
    if (unitPrefix == UMeasurePrefix::UMEASURE_PREFIX_ONE) {
71
        // No need to do anything
72
0
        return;
73
0
    }
74
75
0
    int32_t prefixPower = umeas_getPrefixPower(unitPrefix);
76
0
    double prefixFactor = std::pow((double)umeas_getPrefixBase(unitPrefix), (double)std::abs(prefixPower));
77
0
    if (prefixPower >= 0) {
78
0
        factorNum *= prefixFactor;
79
0
    } else {
80
0
        factorDen *= prefixFactor;
81
0
    }
82
0
}
83
84
0
void U_I18N_API Factor::substituteConstants() {
85
0
    for (int i = 0; i < CONSTANTS_COUNT; i++) {
86
0
        if (this->constantExponents[i] == 0) {
87
0
            continue;
88
0
        }
89
90
0
        auto absPower = std::abs(this->constantExponents[i]);
91
0
        Signum powerSig = this->constantExponents[i] < 0 ? Signum::NEGATIVE : Signum::POSITIVE;
92
0
        double absConstantValue = std::pow(constantsValues[i], absPower);
93
94
0
        if (powerSig == Signum::NEGATIVE) {
95
0
            this->factorDen *= absConstantValue;
96
0
        } else {
97
0
            this->factorNum *= absConstantValue;
98
0
        }
99
100
0
        this->constantExponents[i] = 0;
101
0
    }
102
0
}
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")
112
0
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
0
    StringToDoubleConverter converter(0, 0, 0, "", "");
116
0
    int32_t count;
117
0
    double result = converter.StringToDouble(strNum.data(), strNum.length(), &count);
118
0
    if (count != strNum.length()) {
119
0
        status = U_INVALID_FORMAT_ERROR;
120
0
    }
121
122
0
    return result;
123
0
}
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")
127
0
double strHasDivideSignToDouble(StringPiece strWithDivide, UErrorCode &status) {
128
0
    int divisionSignInd = -1;
129
0
    for (int i = 0, n = strWithDivide.length(); i < n; ++i) {
130
0
        if (strWithDivide.data()[i] == '/') {
131
0
            divisionSignInd = i;
132
0
            break;
133
0
        }
134
0
    }
135
136
0
    if (divisionSignInd >= 0) {
137
0
        return strToDouble(strWithDivide.substr(0, divisionSignInd), status) /
138
0
               strToDouble(strWithDivide.substr(divisionSignInd + 1), status);
139
0
    }
140
141
0
    return strToDouble(strWithDivide, status);
142
0
}
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
*/
148
0
void addFactorElement(Factor &factor, StringPiece elementStr, Signum signum, UErrorCode &status) {
149
0
    StringPiece baseStr;
150
0
    StringPiece powerStr;
151
0
    int32_t power =
152
0
        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
0
    int32_t powerInd = -1;
156
0
    for (int32_t i = 0, n = elementStr.length(); i < n; ++i) {
157
0
        if (elementStr.data()[i] == '^') {
158
0
            powerInd = i;
159
0
            break;
160
0
        }
161
0
    }
162
163
0
    if (powerInd > -1) {
164
        // There is power
165
0
        baseStr = elementStr.substr(0, powerInd);
166
0
        powerStr = elementStr.substr(powerInd + 1);
167
168
0
        power = static_cast<int32_t>(strToDouble(powerStr, status));
169
0
    } else {
170
0
        baseStr = elementStr;
171
0
    }
172
173
0
    addSingleFactorConstant(baseStr, power, signum, factor, status);
174
0
}
175
176
/*
177
 * Extracts `Factor` from a complete string factor. e.g. "ft2m^3*1007/cup2m3*3"
178
 */
179
0
Factor extractFactorConversions(StringPiece stringFactor, UErrorCode &status) {
180
0
    Factor result;
181
0
    Signum signum = Signum::POSITIVE;
182
0
    auto factorData = stringFactor.data();
183
0
    for (int32_t i = 0, start = 0, n = stringFactor.length(); i < n; i++) {
184
0
        if (factorData[i] == '*' || factorData[i] == '/') {
185
0
            StringPiece factorElement = stringFactor.substr(start, i - start);
186
0
            addFactorElement(result, factorElement, signum, status);
187
188
0
            start = i + 1; // Set `start` to point to the start of the new element.
189
0
        } else if (i == n - 1) {
190
            // Last element
191
0
            addFactorElement(result, stringFactor.substr(start, i + 1), signum, status);
192
0
        }
193
194
0
        if (factorData[i] == '/') {
195
0
            signum = Signum::NEGATIVE; // Change the signum because we reached the Denominator.
196
0
        }
197
0
    }
198
199
0
    return result;
200
0
}
201
202
// Load factor for a single source
203
0
Factor loadSingleFactor(StringPiece source, const ConversionRates &ratesInfo, UErrorCode &status) {
204
0
    const auto conversionUnit = ratesInfo.extractConversionInfo(source, status);
205
0
    if (U_FAILURE(status)) return Factor();
206
0
    if (conversionUnit == nullptr) {
207
0
        status = U_INTERNAL_PROGRAM_ERROR;
208
0
        return Factor();
209
0
    }
210
211
0
    Factor result = extractFactorConversions(conversionUnit->factor.toStringPiece(), status);
212
0
    result.offset = strHasDivideSignToDouble(conversionUnit->offset.toStringPiece(), status);
213
214
0
    return result;
215
0
}
216
217
// Load Factor of a compound source unit.
218
// In ICU4J, this is a pair of ConversionRates.getFactorToBase() functions.
219
Factor loadCompoundFactor(const MeasureUnitImpl &source, const ConversionRates &ratesInfo,
220
0
                          UErrorCode &status) {
221
222
0
    Factor result;
223
0
    for (int32_t i = 0, n = source.singleUnits.length(); i < n; i++) {
224
0
        SingleUnitImpl singleUnit = *source.singleUnits[i];
225
226
0
        Factor singleFactor = loadSingleFactor(singleUnit.getSimpleUnitID(), ratesInfo, status);
227
0
        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
0
        singleFactor.applyPrefix(singleUnit.unitPrefix);
233
234
        // Apply the power of the `dimensionality`
235
0
        singleFactor.power(singleUnit.dimensionality);
236
237
0
        result.multiplyBy(singleFactor);
238
0
    }
239
240
0
    return result;
241
0
}
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
 */
252
0
UBool checkSimpleUnit(const MeasureUnitImpl &unit, UErrorCode &status) {
253
0
    if (U_FAILURE(status)) return false;
254
255
0
    if (unit.complexity != UMEASURE_UNIT_SINGLE) {
256
0
        return false;
257
0
    }
258
0
    if (unit.singleUnits.length() == 0) {
259
        // Empty units means simple unit.
260
0
        return true;
261
0
    }
262
263
0
    auto singleUnit = *(unit.singleUnits[0]);
264
265
0
    if (singleUnit.dimensionality != 1 || singleUnit.unitPrefix != UMEASURE_PREFIX_ONE) {
266
0
        return false;
267
0
    }
268
269
0
    return true;
270
0
}
271
272
/**
273
 *  Extract conversion rate from `source` to `target`
274
 */
275
// In ICU4J, this function is partially inlined in the UnitsConverter constructor.
276
void loadConversionRate(ConversionRate &conversionRate, const MeasureUnitImpl &source,
277
                        const MeasureUnitImpl &target, Convertibility unitsState,
278
0
                        const ConversionRates &ratesInfo, UErrorCode &status) {
279
    // Represents the conversion factor from the source to the target.
280
0
    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
0
    Factor sourceToBase = loadCompoundFactor(source, ratesInfo, status);
285
0
    Factor targetToBase = loadCompoundFactor(target, ratesInfo, status);
286
287
    // Merger Factors
288
0
    finalFactor.multiplyBy(sourceToBase);
289
0
    if (unitsState == Convertibility::CONVERTIBLE) {
290
0
        finalFactor.divideBy(targetToBase);
291
0
    } else if (unitsState == Convertibility::RECIPROCAL) {
292
0
        finalFactor.multiplyBy(targetToBase);
293
0
    } else {
294
0
        status = UErrorCode::U_ARGUMENT_TYPE_MISMATCH;
295
0
        return;
296
0
    }
297
298
0
    finalFactor.substituteConstants();
299
300
0
    conversionRate.factorNum = finalFactor.factorNum;
301
0
    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
0
    if (checkSimpleUnit(source, status) && checkSimpleUnit(target, status)) {
306
0
        conversionRate.sourceOffset =
307
0
            sourceToBase.offset * sourceToBase.factorDen / sourceToBase.factorNum;
308
0
        conversionRate.targetOffset =
309
0
            targetToBase.offset * targetToBase.factorDen / targetToBase.factorNum;
310
0
    }
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
0
    conversionRate.reciprocal = unitsState == Convertibility::RECIPROCAL;
315
0
}
316
317
struct UnitIndexAndDimension : UMemory {
318
    int32_t index = 0;
319
    int32_t dimensionality = 0;
320
321
0
    UnitIndexAndDimension(const SingleUnitImpl &singleUnit, int32_t multiplier) {
322
0
        index = singleUnit.index;
323
0
        dimensionality = singleUnit.dimensionality * multiplier;
324
0
    }
325
};
326
327
void mergeSingleUnitWithDimension(MaybeStackVector<UnitIndexAndDimension> &unitIndicesWithDimension,
328
0
                                  const SingleUnitImpl &shouldBeMerged, int32_t multiplier) {
329
0
    for (int32_t i = 0; i < unitIndicesWithDimension.length(); i++) {
330
0
        auto &unitWithIndex = *unitIndicesWithDimension[i];
331
0
        if (unitWithIndex.index == shouldBeMerged.index) {
332
0
            unitWithIndex.dimensionality += shouldBeMerged.dimensionality * multiplier;
333
0
            return;
334
0
        }
335
0
    }
336
337
0
    unitIndicesWithDimension.emplaceBack(shouldBeMerged, multiplier);
338
0
}
339
340
void mergeUnitsAndDimensions(MaybeStackVector<UnitIndexAndDimension> &unitIndicesWithDimension,
341
0
                             const MeasureUnitImpl &shouldBeMerged, int32_t multiplier) {
342
0
    for (int32_t unit_i = 0; unit_i < shouldBeMerged.singleUnits.length(); unit_i++) {
343
0
        auto singleUnit = *shouldBeMerged.singleUnits[unit_i];
344
0
        mergeSingleUnitWithDimension(unitIndicesWithDimension, singleUnit, multiplier);
345
0
    }
346
0
}
347
348
0
UBool checkAllDimensionsAreZeros(const MaybeStackVector<UnitIndexAndDimension> &dimensionVector) {
349
0
    for (int32_t i = 0; i < dimensionVector.length(); i++) {
350
0
        if (dimensionVector[i]->dimensionality != 0) {
351
0
            return false;
352
0
        }
353
0
    }
354
355
0
    return true;
356
0
}
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.
364
void U_I18N_API addSingleFactorConstant(StringPiece baseStr, int32_t power, Signum signum,
365
0
                                        Factor &factor, UErrorCode &status) {
366
0
    if (baseStr == "ft_to_m") {
367
0
        factor.constantExponents[CONSTANT_FT2M] += power * signum;
368
0
    } else if (baseStr == "ft2_to_m2") {
369
0
        factor.constantExponents[CONSTANT_FT2M] += 2 * power * signum;
370
0
    } else if (baseStr == "ft3_to_m3") {
371
0
        factor.constantExponents[CONSTANT_FT2M] += 3 * power * signum;
372
0
    } else if (baseStr == "in3_to_m3") {
373
0
        factor.constantExponents[CONSTANT_FT2M] += 3 * power * signum;
374
0
        factor.factorDen *= 12 * 12 * 12;
375
0
    } else if (baseStr == "gal_to_m3") {
376
0
        factor.factorNum *= 231;
377
0
        factor.constantExponents[CONSTANT_FT2M] += 3 * power * signum;
378
0
        factor.factorDen *= 12 * 12 * 12;
379
0
    } else if (baseStr == "gal_imp_to_m3") {
380
0
        factor.constantExponents[CONSTANT_GAL_IMP2M3] += power * signum;
381
0
    } else if (baseStr == "G") {
382
0
        factor.constantExponents[CONSTANT_G] += power * signum;
383
0
    } else if (baseStr == "gravity") {
384
0
        factor.constantExponents[CONSTANT_GRAVITY] += power * signum;
385
0
    } else if (baseStr == "lb_to_kg") {
386
0
        factor.constantExponents[CONSTANT_LB2KG] += power * signum;
387
0
    } else if (baseStr == "glucose_molar_mass") {
388
0
        factor.constantExponents[CONSTANT_GLUCOSE_MOLAR_MASS] += power * signum;
389
0
    } else if (baseStr == "item_per_mole") {
390
0
        factor.constantExponents[CONSTANT_ITEM_PER_MOLE] += power * signum;
391
0
    } else if (baseStr == "PI") {
392
0
        factor.constantExponents[CONSTANT_PI] += power * signum;
393
0
    } else {
394
0
        if (signum == Signum::NEGATIVE) {
395
0
            factor.factorDen *= std::pow(strToDouble(baseStr, status), power);
396
0
        } else {
397
0
            factor.factorNum *= std::pow(strToDouble(baseStr, status), power);
398
0
        }
399
0
    }
400
0
}
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
 */
406
MeasureUnitImpl U_I18N_API extractCompoundBaseUnit(const MeasureUnitImpl &source,
407
                                                   const ConversionRates &conversionRates,
408
0
                                                   UErrorCode &status) {
409
410
0
    MeasureUnitImpl result;
411
0
    if (U_FAILURE(status)) return result;
412
413
0
    const auto &singleUnits = source.singleUnits;
414
0
    for (int i = 0, count = singleUnits.length(); i < count; ++i) {
415
0
        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
0
        const auto rateInfo =
419
0
            conversionRates.extractConversionInfo(singleUnit.getSimpleUnitID(), status);
420
0
        if (U_FAILURE(status)) {
421
0
            return result;
422
0
        }
423
0
        if (rateInfo == nullptr) {
424
0
            status = U_INTERNAL_PROGRAM_ERROR;
425
0
            return result;
426
0
        }
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
0
        auto baseUnits =
431
0
            MeasureUnitImpl::forIdentifier(rateInfo->baseUnit.toStringPiece(), status).singleUnits;
432
0
        for (int32_t i = 0, baseUnitsCount = baseUnits.length(); i < baseUnitsCount; i++) {
433
0
            baseUnits[i]->dimensionality *= singleUnit.dimensionality;
434
            // TODO: Deal with SI-prefix
435
0
            result.appendSingleUnit(*baseUnits[i], status);
436
437
0
            if (U_FAILURE(status)) {
438
0
                return result;
439
0
            }
440
0
        }
441
0
    }
442
443
0
    return result;
444
0
}
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
 */
457
Convertibility U_I18N_API extractConvertibility(const MeasureUnitImpl &source,
458
                                                const MeasureUnitImpl &target,
459
                                                const ConversionRates &conversionRates,
460
0
                                                UErrorCode &status) {
461
462
0
    if (source.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED ||
463
0
        target.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) {
464
0
        status = U_INTERNAL_PROGRAM_ERROR;
465
0
        return UNCONVERTIBLE;
466
0
    }
467
468
0
    MeasureUnitImpl sourceBaseUnit = extractCompoundBaseUnit(source, conversionRates, status);
469
0
    MeasureUnitImpl targetBaseUnit = extractCompoundBaseUnit(target, conversionRates, status);
470
0
    if (U_FAILURE(status)) return UNCONVERTIBLE;
471
472
0
    MaybeStackVector<UnitIndexAndDimension> convertible;
473
0
    MaybeStackVector<UnitIndexAndDimension> reciprocal;
474
475
0
    mergeUnitsAndDimensions(convertible, sourceBaseUnit, 1);
476
0
    mergeUnitsAndDimensions(reciprocal, sourceBaseUnit, 1);
477
478
0
    mergeUnitsAndDimensions(convertible, targetBaseUnit, -1);
479
0
    mergeUnitsAndDimensions(reciprocal, targetBaseUnit, 1);
480
481
0
    if (checkAllDimensionsAreZeros(convertible)) {
482
0
        return CONVERTIBLE;
483
0
    }
484
485
0
    if (checkAllDimensionsAreZeros(reciprocal)) {
486
0
        return RECIPROCAL;
487
0
    }
488
489
0
    return UNCONVERTIBLE;
490
0
}
491
492
UnitsConverter::UnitsConverter(const MeasureUnitImpl &source, const MeasureUnitImpl &target,
493
                               const ConversionRates &ratesInfo, UErrorCode &status)
494
0
    : conversionRate_(source.copy(status), target.copy(status)) {
495
0
    this->init(ratesInfo, status);
496
0
}
497
498
UnitsConverter::UnitsConverter(StringPiece sourceIdentifier, StringPiece targetIdentifier,
499
                               UErrorCode &status)
500
0
    : conversionRate_(MeasureUnitImpl::forIdentifier(sourceIdentifier, status),
501
0
                      MeasureUnitImpl::forIdentifier(targetIdentifier, status)) {
502
0
    if (U_FAILURE(status)) {
503
0
        return;
504
0
    }
505
506
0
    ConversionRates ratesInfo(status);
507
0
    this->init(ratesInfo, status);
508
0
}
509
510
0
void UnitsConverter::init(const ConversionRates &ratesInfo, UErrorCode &status) {
511
0
    if (U_FAILURE(status)) {
512
0
        return;
513
0
    }
514
515
0
    if (this->conversionRate_.source.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED ||
516
0
        this->conversionRate_.target.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) {
517
0
        status = U_INTERNAL_PROGRAM_ERROR;
518
0
        return;
519
0
    }
520
521
0
    Convertibility unitsState = extractConvertibility(this->conversionRate_.source,
522
0
                                                      this->conversionRate_.target, ratesInfo, status);
523
0
    if (U_FAILURE(status)) return;
524
0
    if (unitsState == Convertibility::UNCONVERTIBLE) {
525
0
        status = U_INTERNAL_PROGRAM_ERROR;
526
0
        return;
527
0
    }
528
529
0
    loadConversionRate(conversionRate_, conversionRate_.source, conversionRate_.target, unitsState,
530
0
                       ratesInfo, status);
531
                          
532
0
}
533
534
int32_t UnitsConverter::compareTwoUnits(const MeasureUnitImpl &firstUnit,
535
                                        const MeasureUnitImpl &secondUnit,
536
0
                                        const ConversionRates &ratesInfo, UErrorCode &status) {
537
0
    if (U_FAILURE(status)) {
538
0
        return 0;
539
0
    }
540
541
0
    if (firstUnit.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED ||
542
0
        secondUnit.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) {
543
0
        status = U_INTERNAL_PROGRAM_ERROR;
544
0
        return 0;
545
0
    }
546
547
0
    Convertibility unitsState = extractConvertibility(firstUnit, secondUnit, ratesInfo, status);
548
0
    if (U_FAILURE(status)) {
549
0
        return 0;
550
0
    }
551
552
0
    if (unitsState == Convertibility::UNCONVERTIBLE || unitsState == Convertibility::RECIPROCAL) {
553
0
        status = U_INTERNAL_PROGRAM_ERROR;
554
0
        return 0;
555
0
    }
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
0
    Factor firstUnitToBase = loadCompoundFactor(firstUnit, ratesInfo, status);
561
0
    Factor secondUnitToBase = loadCompoundFactor(secondUnit, ratesInfo, status);
562
563
0
    firstUnitToBase.substituteConstants();
564
0
    secondUnitToBase.substituteConstants();
565
566
0
    double firstUnitToBaseConversionRate = firstUnitToBase.factorNum / firstUnitToBase.factorDen;
567
0
    double secondUnitToBaseConversionRate = secondUnitToBase.factorNum / secondUnitToBase.factorDen;
568
569
0
    double diff = firstUnitToBaseConversionRate - secondUnitToBaseConversionRate;
570
0
    if (diff > 0) {
571
0
        return 1;
572
0
    }
573
574
0
    if (diff < 0) {
575
0
        return -1;
576
0
    }
577
578
0
    return 0;
579
0
}
580
581
0
double UnitsConverter::convert(double inputValue) const {
582
0
    double result =
583
0
        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
0
    result *= conversionRate_.factorNum / conversionRate_.factorDen;
586
587
0
    result -= conversionRate_.targetOffset; // Set the result to its index.
588
589
0
    if (conversionRate_.reciprocal) {
590
0
        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
0
            return 0.0;
595
0
        }
596
0
        result = 1.0 / result;
597
0
    }
598
599
0
    return result;
600
0
}
601
602
0
double UnitsConverter::convertInverse(double inputValue) const {
603
0
    double result = inputValue;
604
0
    if (conversionRate_.reciprocal) {
605
0
        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
0
            return 0.0;
610
0
        }
611
0
        result = 1.0 / result;
612
0
    }
613
0
    result += conversionRate_.targetOffset;
614
0
    result *= conversionRate_.factorDen / conversionRate_.factorNum;
615
0
    result -= conversionRate_.sourceOffset;
616
0
    return result;
617
0
}
618
619
0
ConversionInfo UnitsConverter::getConversionInfo() const {
620
0
    ConversionInfo result;
621
0
    result.conversionRate = conversionRate_.factorNum / conversionRate_.factorDen;
622
0
    result.offset =
623
0
        (conversionRate_.sourceOffset * (conversionRate_.factorNum / conversionRate_.factorDen)) -
624
0
        conversionRate_.targetOffset;
625
0
    result.reciprocal = conversionRate_.reciprocal;
626
627
0
    return result;
628
0
}
629
630
} // namespace units
631
U_NAMESPACE_END
632
633
#endif /* #if !UCONFIG_NO_FORMATTING */