tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

units_converter.cpp (33501B)


      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 "cstring.h"
     11 #ifdef JS_HAS_INTL_API
     12 #include "double-conversion/string-to-double.h"
     13 #else
     14 #include "double-conversion-string-to-double.h"
     15 #endif
     16 #include "measunit_impl.h"
     17 #include "putilimp.h"
     18 #include "uassert.h"
     19 #include "unicode/errorcode.h"
     20 #include "unicode/localpointer.h"
     21 #include "unicode/stringpiece.h"
     22 #include "units_converter.h"
     23 #include <algorithm>
     24 #include <cmath>
     25 #include <stdlib.h>
     26 #include <utility>
     27 
     28 U_NAMESPACE_BEGIN
     29 namespace units {
     30 
     31 void Factor::multiplyBy(const Factor& rhs) {
     32    factorNum *= rhs.factorNum;
     33    factorDen *= rhs.factorDen;
     34    for (int i = 0; i < CONSTANTS_COUNT; i++) {
     35        constantExponents[i] += rhs.constantExponents[i];
     36    }
     37 
     38    // NOTE
     39    //  We need the offset when the source and the target are simple units. e.g. the source is
     40    //  celsius and the target is Fahrenheit. Therefore, we just keep the value using `std::max`.
     41    offset = std::max(rhs.offset, offset);
     42 }
     43 
     44 void Factor::divideBy(const Factor& rhs) {
     45    factorNum *= rhs.factorDen;
     46    factorDen *= rhs.factorNum;
     47    for (int i = 0; i < CONSTANTS_COUNT; i++) {
     48        constantExponents[i] -= rhs.constantExponents[i];
     49    }
     50 
     51    // NOTE
     52    //  We need the offset when the source and the target are simple units. e.g. the source is
     53    //  celsius and the target is Fahrenheit. Therefore, we just keep the value using `std::max`.
     54    offset = std::max(rhs.offset, offset);
     55 }
     56 
     57 void Factor::divideBy(const uint64_t constant) { factorDen *= constant; }
     58 
     59 void Factor::power(int32_t power) {
     60    // multiply all the constant by the power.
     61    for (int i = 0; i < CONSTANTS_COUNT; i++) {
     62        constantExponents[i] *= power;
     63    }
     64 
     65    bool shouldFlip = power < 0; // This means that after applying the absolute power, we should flip
     66                                 // the Numerator and Denominator.
     67 
     68    factorNum = std::pow(factorNum, std::abs(power));
     69    factorDen = std::pow(factorDen, std::abs(power));
     70 
     71    if (shouldFlip) {
     72        // Flip Numerator and Denominator.
     73        std::swap(factorNum, factorDen);
     74    }
     75 }
     76 
     77 void Factor::applyPrefix(UMeasurePrefix unitPrefix) {
     78    if (unitPrefix == UMeasurePrefix::UMEASURE_PREFIX_ONE) {
     79        // No need to do anything
     80        return;
     81    }
     82 
     83    int32_t prefixPower = umeas_getPrefixPower(unitPrefix);
     84    double prefixFactor = std::pow(static_cast<double>(umeas_getPrefixBase(unitPrefix)),
     85                                   static_cast<double>(std::abs(prefixPower)));
     86    if (prefixPower >= 0) {
     87        factorNum *= prefixFactor;
     88    } else {
     89        factorDen *= prefixFactor;
     90    }
     91 }
     92 
     93 void Factor::substituteConstants() {
     94    for (int i = 0; i < CONSTANTS_COUNT; i++) {
     95        if (this->constantExponents[i] == 0) {
     96            continue;
     97        }
     98 
     99        auto absPower = std::abs(this->constantExponents[i]);
    100        Signum powerSig = this->constantExponents[i] < 0 ? Signum::NEGATIVE : Signum::POSITIVE;
    101        double absConstantValue = std::pow(constantsValues[i], absPower);
    102 
    103        if (powerSig == Signum::NEGATIVE) {
    104            this->factorDen *= absConstantValue;
    105        } else {
    106            this->factorNum *= absConstantValue;
    107        }
    108 
    109        this->constantExponents[i] = 0;
    110    }
    111 }
    112 
    113 namespace {
    114 
    115 /* Helpers */
    116 
    117 #ifdef JS_HAS_INTL_API
    118 using double_conversion::StringToDoubleConverter;
    119 #else
    120 using icu::double_conversion::StringToDoubleConverter;
    121 #endif
    122 
    123 // TODO: Make this a shared-utility function.
    124 // Returns `double` from a scientific number(i.e. "1", "2.01" or "3.09E+4")
    125 double strToDouble(StringPiece strNum, UErrorCode &status) {
    126    // We are processing well-formed input, so we don't need any special options to
    127    // StringToDoubleConverter.
    128    StringToDoubleConverter converter(0, 0, 0, "", "");
    129    int32_t count;
    130    double result = converter.StringToDouble(strNum.data(), strNum.length(), &count);
    131    if (count != strNum.length()) {
    132        status = U_INVALID_FORMAT_ERROR;
    133    }
    134 
    135    return result;
    136 }
    137 
    138 // Returns `double` from a scientific number that could has a division sign (i.e. "1", "2.01", "3.09E+4"
    139 // or "2E+2/3")
    140 double strHasDivideSignToDouble(StringPiece strWithDivide, UErrorCode &status) {
    141    int divisionSignInd = -1;
    142    for (int i = 0, n = strWithDivide.length(); i < n; ++i) {
    143        if (strWithDivide.data()[i] == '/') {
    144            divisionSignInd = i;
    145            break;
    146        }
    147    }
    148 
    149    if (divisionSignInd >= 0) {
    150        return strToDouble(strWithDivide.substr(0, divisionSignInd), status) /
    151               strToDouble(strWithDivide.substr(divisionSignInd + 1), status);
    152    }
    153 
    154    return strToDouble(strWithDivide, status);
    155 }
    156 
    157 /*
    158  Adds single factor to a `Factor` object. Single factor means "23^2", "23.3333", "ft2m^3" ...etc.
    159  However, complex factor are not included, such as "ft2m^3*200/3"
    160 */
    161 void addFactorElement(Factor &factor, StringPiece elementStr, Signum signum, UErrorCode &status) {
    162    StringPiece baseStr;
    163    StringPiece powerStr;
    164    int32_t power =
    165        1; // In case the power is not written, then, the power is equal 1 ==> `ft2m^1` == `ft2m`
    166 
    167    // Search for the power part
    168    int32_t powerInd = -1;
    169    for (int32_t i = 0, n = elementStr.length(); i < n; ++i) {
    170        if (elementStr.data()[i] == '^') {
    171            powerInd = i;
    172            break;
    173        }
    174    }
    175 
    176    if (powerInd > -1) {
    177        // There is power
    178        baseStr = elementStr.substr(0, powerInd);
    179        powerStr = elementStr.substr(powerInd + 1);
    180 
    181        power = static_cast<int32_t>(strToDouble(powerStr, status));
    182    } else {
    183        baseStr = elementStr;
    184    }
    185 
    186    addSingleFactorConstant(baseStr, power, signum, factor, status);
    187 }
    188 
    189 /*
    190 * Extracts `Factor` from a complete string factor. e.g. "ft2m^3*1007/cup2m3*3"
    191 */
    192 Factor extractFactorConversions(StringPiece stringFactor, UErrorCode &status) {
    193    Factor result;
    194    Signum signum = Signum::POSITIVE;
    195    const auto* factorData = stringFactor.data();
    196    for (int32_t i = 0, start = 0, n = stringFactor.length(); i < n; i++) {
    197        if (factorData[i] == '*' || factorData[i] == '/') {
    198            StringPiece factorElement = stringFactor.substr(start, i - start);
    199            addFactorElement(result, factorElement, signum, status);
    200 
    201            start = i + 1; // Set `start` to point to the start of the new element.
    202        } else if (i == n - 1) {
    203            // Last element
    204            addFactorElement(result, stringFactor.substr(start, i + 1), signum, status);
    205        }
    206 
    207        if (factorData[i] == '/') {
    208            signum = Signum::NEGATIVE; // Change the signum because we reached the Denominator.
    209        }
    210    }
    211 
    212    return result;
    213 }
    214 
    215 // Load factor for a single source
    216 Factor loadSingleFactor(StringPiece source, const ConversionRates &ratesInfo, UErrorCode &status) {
    217    const auto* const conversionUnit = ratesInfo.extractConversionInfo(source, status);
    218    if (U_FAILURE(status)) return {};
    219    if (conversionUnit == nullptr) {
    220        status = U_INTERNAL_PROGRAM_ERROR;
    221        return {};
    222    }
    223 
    224    Factor result = extractFactorConversions(conversionUnit->factor.data(), status);
    225    result.offset = strHasDivideSignToDouble(conversionUnit->offset.data(), status);
    226 
    227    return result;
    228 }
    229 
    230 // Load Factor of a compound source unit.
    231 // In ICU4J, this is a pair of ConversionRates.getFactorToBase() functions.
    232 Factor loadCompoundFactor(const MeasureUnitImpl &source, const ConversionRates &ratesInfo,
    233                          UErrorCode &status) {
    234 
    235    Factor result;
    236    for (int32_t i = 0, n = source.singleUnits.length(); i < n; i++) {
    237        SingleUnitImpl singleUnit = *source.singleUnits[i];
    238 
    239        Factor singleFactor = loadSingleFactor(singleUnit.getSimpleUnitID(), ratesInfo, status);
    240        if (U_FAILURE(status)) return result;
    241 
    242        // Prefix before power, because:
    243        // - square-kilometer to square-meter: (1000)^2
    244        // - square-kilometer to square-foot (approximate): (3.28*1000)^2
    245        singleFactor.applyPrefix(singleUnit.unitPrefix);
    246 
    247        // Apply the power of the `dimensionality`
    248        singleFactor.power(singleUnit.dimensionality);
    249 
    250        result.multiplyBy(singleFactor);
    251    }
    252 
    253    // If the source has a constant denominator, then we need to divide the
    254    // factor by the constant denominator.
    255    if (source.constantDenominator != 0) {
    256        result.divideBy(source.constantDenominator);
    257    }
    258 
    259    return result;
    260 }
    261 
    262 /**
    263 * Checks if the source unit and the target unit are simple. For example celsius or fahrenheit. But not
    264 * square-celsius or square-fahrenheit.
    265 *
    266 * NOTE:
    267 *  Empty unit means simple unit.
    268 *
    269 * In ICU4J, this is ConversionRates.checkSimpleUnit().
    270 */
    271 UBool checkSimpleUnit(const MeasureUnitImpl &unit, UErrorCode &status) {
    272    if (U_FAILURE(status)) return false;
    273 
    274    if (unit.complexity != UMEASURE_UNIT_SINGLE) {
    275        return false;
    276    }
    277    if (unit.singleUnits.length() == 0) {
    278        // Empty units means simple unit.
    279        return true;
    280    }
    281 
    282    auto singleUnit = *(unit.singleUnits[0]);
    283 
    284    if (singleUnit.dimensionality != 1 || singleUnit.unitPrefix != UMEASURE_PREFIX_ONE) {
    285        return false;
    286    }
    287 
    288    return true;
    289 }
    290 
    291 // Map the MeasureUnitImpl for a simpleUnit to a SingleUnitImpl, then use that
    292 // SingleUnitImpl's simpleUnitID to get the corresponding ConversionRateInfo;
    293 // from that we get the specialMappingName (which may be empty if the simple unit
    294 // converts to base using factor + offset instelad of a special mapping).
    295 StringPiece getSpecialMappingName(const MeasureUnitImpl& simpleUnit, const ConversionRates& ratesInfo,
    296                                  UErrorCode& status) {
    297    if (!checkSimpleUnit(simpleUnit, status)) {
    298        return {};
    299    }
    300    SingleUnitImpl singleUnit = *simpleUnit.singleUnits[0];
    301    const auto* const conversionUnit =
    302        ratesInfo.extractConversionInfo(singleUnit.getSimpleUnitID(), status);
    303    if (U_FAILURE(status)) {
    304        return {};
    305    }
    306    if (conversionUnit == nullptr) {
    307        status = U_INTERNAL_PROGRAM_ERROR;
    308        return {};
    309    }
    310    return conversionUnit->specialMappingName.data();
    311 }
    312 
    313 /**
    314 *  Extract conversion rate from `source` to `target`
    315 */
    316 // In ICU4J, this function is partially inlined in the UnitsConverter constructor.
    317 // TODO ICU-22683: Consider splitting handling of special mappings into separate class
    318 void loadConversionRate(ConversionRate &conversionRate, const MeasureUnitImpl &source,
    319                        const MeasureUnitImpl &target, Convertibility unitsState,
    320                        const ConversionRates &ratesInfo, UErrorCode &status) {
    321    StringPiece specialSource = getSpecialMappingName(source, ratesInfo, status);
    322    StringPiece specialTarget = getSpecialMappingName(target, ratesInfo, status);
    323 
    324    conversionRate.specialSource = specialSource;
    325    conversionRate.specialTarget = specialTarget;
    326 
    327    if (conversionRate.specialSource.isEmpty() != specialSource.empty() ||
    328        conversionRate.specialTarget.isEmpty() != specialTarget.empty()) {
    329        status = U_MEMORY_ALLOCATION_ERROR;
    330        return;
    331    }
    332 
    333    if (conversionRate.specialSource.isEmpty() && conversionRate.specialTarget.isEmpty()) {
    334        // Represents the conversion factor from the source to the target.
    335        Factor finalFactor;
    336 
    337        // Represents the conversion factor from the source to the base unit that specified in the conversion
    338        // data which is considered as the root of the source and the target.
    339        Factor sourceToBase = loadCompoundFactor(source, ratesInfo, status);
    340        Factor targetToBase = loadCompoundFactor(target, ratesInfo, status);
    341 
    342        // Merger Factors
    343        finalFactor.multiplyBy(sourceToBase);
    344        if (unitsState == Convertibility::CONVERTIBLE) {
    345            finalFactor.divideBy(targetToBase);
    346        } else if (unitsState == Convertibility::RECIPROCAL) {
    347            finalFactor.multiplyBy(targetToBase);
    348        } else {
    349            status = UErrorCode::U_ARGUMENT_TYPE_MISMATCH;
    350            return;
    351        }
    352 
    353        finalFactor.substituteConstants();
    354 
    355        conversionRate.factorNum = finalFactor.factorNum;
    356        conversionRate.factorDen = finalFactor.factorDen;
    357 
    358        // This code corresponds to ICU4J's ConversionRates.getOffset().
    359        // In case of simple units (such as: celsius or fahrenheit), offsets are considered.
    360        if (checkSimpleUnit(source, status) && checkSimpleUnit(target, status)) {
    361            conversionRate.sourceOffset =
    362                sourceToBase.offset * sourceToBase.factorDen / sourceToBase.factorNum;
    363            conversionRate.targetOffset =
    364                targetToBase.offset * targetToBase.factorDen / targetToBase.factorNum;
    365        }
    366        // TODO(icu-units#127): should we consider failure if there's an offset for
    367        // a not-simple-unit? What about kilokelvin / kilocelsius?
    368 
    369        conversionRate.reciprocal = unitsState == Convertibility::RECIPROCAL;
    370    } else if (conversionRate.specialSource.isEmpty() || conversionRate.specialTarget.isEmpty()) {
    371        // Still need to set factorNum/factorDen for either source to base or base to target
    372        if (unitsState != Convertibility::CONVERTIBLE) {
    373            status = UErrorCode::U_ARGUMENT_TYPE_MISMATCH;
    374            return;
    375        }
    376        Factor finalFactor;
    377        if (conversionRate.specialSource.isEmpty()) {
    378            // factorNum/factorDen is for source to base only
    379            finalFactor = loadCompoundFactor(source, ratesInfo, status);
    380        } else {
    381            // factorNum/factorDen is for base to target only
    382            finalFactor = loadCompoundFactor(target, ratesInfo, status);
    383        }
    384        finalFactor.substituteConstants();
    385        conversionRate.factorNum = finalFactor.factorNum;
    386        conversionRate.factorDen = finalFactor.factorDen;
    387    }
    388 }
    389 
    390 struct UnitIndexAndDimension : UMemory {
    391    int32_t index = 0;
    392    int32_t dimensionality = 0;
    393 
    394    UnitIndexAndDimension(const SingleUnitImpl &singleUnit, int32_t multiplier) {
    395        index = singleUnit.index;
    396        dimensionality = singleUnit.dimensionality * multiplier;
    397    }
    398 };
    399 
    400 void mergeSingleUnitWithDimension(MaybeStackVector<UnitIndexAndDimension> &unitIndicesWithDimension,
    401                                  const SingleUnitImpl &shouldBeMerged, int32_t multiplier) {
    402    for (int32_t i = 0; i < unitIndicesWithDimension.length(); i++) {
    403        auto &unitWithIndex = *unitIndicesWithDimension[i];
    404        if (unitWithIndex.index == shouldBeMerged.index) {
    405            unitWithIndex.dimensionality += shouldBeMerged.dimensionality * multiplier;
    406            return;
    407        }
    408    }
    409 
    410    unitIndicesWithDimension.emplaceBack(shouldBeMerged, multiplier);
    411 }
    412 
    413 void mergeUnitsAndDimensions(MaybeStackVector<UnitIndexAndDimension> &unitIndicesWithDimension,
    414                             const MeasureUnitImpl &shouldBeMerged, int32_t multiplier) {
    415    for (int32_t unit_i = 0; unit_i < shouldBeMerged.singleUnits.length(); unit_i++) {
    416        auto singleUnit = *shouldBeMerged.singleUnits[unit_i];
    417        mergeSingleUnitWithDimension(unitIndicesWithDimension, singleUnit, multiplier);
    418    }
    419 }
    420 
    421 UBool checkAllDimensionsAreZeros(const MaybeStackVector<UnitIndexAndDimension> &dimensionVector) {
    422    for (int32_t i = 0; i < dimensionVector.length(); i++) {
    423        if (dimensionVector[i]->dimensionality != 0) {
    424            return false;
    425        }
    426    }
    427 
    428    return true;
    429 }
    430 
    431 } // namespace
    432 
    433 // Conceptually, this modifies factor: factor *= baseStr^(signum*power).
    434 //
    435 // baseStr must be a known constant or a value that strToDouble() is able to
    436 // parse.
    437 void U_I18N_API addSingleFactorConstant(StringPiece baseStr, int32_t power, Signum signum,
    438                                        Factor &factor, UErrorCode &status) {
    439    if (baseStr == "ft_to_m") {
    440        factor.constantExponents[CONSTANT_FT2M] += power * signum;
    441    } else if (baseStr == "ft2_to_m2") {
    442        factor.constantExponents[CONSTANT_FT2M] += 2 * power * signum;
    443    } else if (baseStr == "ft3_to_m3") {
    444        factor.constantExponents[CONSTANT_FT2M] += 3 * power * signum;
    445    } else if (baseStr == "in3_to_m3") {
    446        factor.constantExponents[CONSTANT_FT2M] += 3 * power * signum;
    447        factor.factorDen *= std::pow(12 * 12 * 12, power * signum);
    448    } else if (baseStr == "gal_to_m3") {
    449        factor.constantExponents[CONSTANT_FT2M] += 3 * power * signum;
    450        factor.factorNum *= std::pow(231, power * signum);
    451        factor.factorDen *= std::pow(12 * 12 * 12, power * signum);
    452    } else if (baseStr == "gal_imp_to_m3") {
    453        factor.constantExponents[CONSTANT_GAL_IMP2M3] += power * signum;
    454    } else if (baseStr == "G") {
    455        factor.constantExponents[CONSTANT_G] += power * signum;
    456    } else if (baseStr == "gravity") {
    457        factor.constantExponents[CONSTANT_GRAVITY] += power * signum;
    458    } else if (baseStr == "lb_to_kg") {
    459        factor.constantExponents[CONSTANT_LB2KG] += power * signum;
    460    } else if (baseStr == "glucose_molar_mass") {
    461        factor.constantExponents[CONSTANT_GLUCOSE_MOLAR_MASS] += power * signum;
    462    } else if (baseStr == "item_per_mole") {
    463        factor.constantExponents[CONSTANT_ITEM_PER_MOLE] += power * signum;
    464    } else if (baseStr == "meters_per_AU") {
    465        factor.constantExponents[CONSTANT_METERS_PER_AU] += power * signum;
    466    } else if (baseStr == "PI") {
    467        factor.constantExponents[CONSTANT_PI] += power * signum;
    468    } else if (baseStr == "sec_per_julian_year") {
    469        factor.constantExponents[CONSTANT_SEC_PER_JULIAN_YEAR] += power * signum;
    470    } else if (baseStr == "speed_of_light_meters_per_second") {
    471        factor.constantExponents[CONSTANT_SPEED_OF_LIGHT_METERS_PER_SECOND] += power * signum;
    472    } else if (baseStr == "sho_to_m3") {
    473        factor.constantExponents[CONSTANT_SHO_TO_M3] += power * signum;
    474    } else if (baseStr == "tsubo_to_m2") {
    475        factor.constantExponents[CONSTANT_TSUBO_TO_M2] += power * signum;
    476    } else if (baseStr == "shaku_to_m") {
    477        factor.constantExponents[CONSTANT_SHAKU_TO_M] += power * signum;
    478    } else if (baseStr == "AMU") {
    479        factor.constantExponents[CONSTANT_AMU] += power * signum;
    480    } else {
    481        if (signum == Signum::NEGATIVE) {
    482            factor.factorDen *= std::pow(strToDouble(baseStr, status), power);
    483        } else {
    484            factor.factorNum *= std::pow(strToDouble(baseStr, status), power);
    485        }
    486    }
    487 }
    488 
    489 /**
    490 * Extracts the compound base unit of a compound unit (`source`). For example, if the source unit is
    491 * `square-mile-per-hour`, the compound base unit will be `square-meter-per-second`
    492 */
    493 MeasureUnitImpl extractCompoundBaseUnit(const MeasureUnitImpl& source,
    494                                        const ConversionRates& conversionRates,
    495                                        UErrorCode& status) {
    496 
    497    MeasureUnitImpl result;
    498    if (U_FAILURE(status)) return result;
    499 
    500    const auto &singleUnits = source.singleUnits;
    501    for (int i = 0, count = singleUnits.length(); i < count; ++i) {
    502        const auto &singleUnit = *singleUnits[i];
    503        // Extract `ConversionRateInfo` using the absolute unit. For example: in case of `square-meter`,
    504        // we will use `meter`
    505        const auto* const rateInfo =
    506            conversionRates.extractConversionInfo(singleUnit.getSimpleUnitID(), status);
    507        if (U_FAILURE(status)) {
    508            return result;
    509        }
    510        if (rateInfo == nullptr) {
    511            status = U_INTERNAL_PROGRAM_ERROR;
    512            return result;
    513        }
    514 
    515        // Multiply the power of the singleUnit by the power of the baseUnit. For example, square-hectare
    516        // must be pow4-meter. (NOTE: hectare --> square-meter)
    517        auto baseUnits =
    518            MeasureUnitImpl::forIdentifier(rateInfo->baseUnit.data(), status).singleUnits;
    519        for (int32_t i = 0, baseUnitsCount = baseUnits.length(); i < baseUnitsCount; i++) {
    520            baseUnits[i]->dimensionality *= singleUnit.dimensionality;
    521            // TODO: Deal with SI-prefix
    522            result.appendSingleUnit(*baseUnits[i], status);
    523 
    524            if (U_FAILURE(status)) {
    525                return result;
    526            }
    527        }
    528    }
    529 
    530    return result;
    531 }
    532 
    533 /**
    534 * Determine the convertibility between `source` and `target`.
    535 * For example:
    536 *    `meter` and `foot` are `CONVERTIBLE`.
    537 *    `meter-per-second` and `second-per-meter` are `RECIPROCAL`.
    538 *    `meter` and `pound` are `UNCONVERTIBLE`.
    539 *
    540 * NOTE:
    541 *    Only works with SINGLE and COMPOUND units. If one of the units is a
    542 *    MIXED unit, an error will occur. For more information, see UMeasureUnitComplexity.
    543 */
    544 Convertibility U_I18N_API extractConvertibility(const MeasureUnitImpl &source,
    545                                                const MeasureUnitImpl &target,
    546                                                const ConversionRates &conversionRates,
    547                                                UErrorCode &status) {
    548 
    549    if (source.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED ||
    550        target.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) {
    551        status = U_ARGUMENT_TYPE_MISMATCH;
    552        return UNCONVERTIBLE;
    553    }
    554 
    555    MeasureUnitImpl sourceBaseUnit = extractCompoundBaseUnit(source, conversionRates, status);
    556    MeasureUnitImpl targetBaseUnit = extractCompoundBaseUnit(target, conversionRates, status);
    557    if (U_FAILURE(status)) return UNCONVERTIBLE;
    558 
    559    MaybeStackVector<UnitIndexAndDimension> convertible;
    560    MaybeStackVector<UnitIndexAndDimension> reciprocal;
    561 
    562    mergeUnitsAndDimensions(convertible, sourceBaseUnit, 1);
    563    mergeUnitsAndDimensions(reciprocal, sourceBaseUnit, 1);
    564 
    565    mergeUnitsAndDimensions(convertible, targetBaseUnit, -1);
    566    mergeUnitsAndDimensions(reciprocal, targetBaseUnit, 1);
    567 
    568    if (checkAllDimensionsAreZeros(convertible)) {
    569        return CONVERTIBLE;
    570    }
    571 
    572    if (checkAllDimensionsAreZeros(reciprocal)) {
    573        return RECIPROCAL;
    574    }
    575 
    576    return UNCONVERTIBLE;
    577 }
    578 
    579 UnitsConverter::UnitsConverter(const MeasureUnitImpl &source, const MeasureUnitImpl &target,
    580                               const ConversionRates &ratesInfo, UErrorCode &status)
    581    : conversionRate_(source.copy(status), target.copy(status)) {
    582    this->init(ratesInfo, status);
    583 }
    584 
    585 UnitsConverter::UnitsConverter(StringPiece sourceIdentifier, StringPiece targetIdentifier,
    586                               UErrorCode &status)
    587    : conversionRate_(MeasureUnitImpl::forIdentifier(sourceIdentifier, status),
    588                      MeasureUnitImpl::forIdentifier(targetIdentifier, status)) {
    589    if (U_FAILURE(status)) {
    590        return;
    591    }
    592 
    593    ConversionRates ratesInfo(status);
    594    this->init(ratesInfo, status);
    595 }
    596 
    597 void UnitsConverter::init(const ConversionRates &ratesInfo, UErrorCode &status) {
    598    if (U_FAILURE(status)) {
    599        return;
    600    }
    601 
    602    if (this->conversionRate_.source.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED ||
    603        this->conversionRate_.target.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) {
    604        status = U_ARGUMENT_TYPE_MISMATCH;
    605        return;
    606    }
    607 
    608    Convertibility unitsState = extractConvertibility(this->conversionRate_.source,
    609                                                      this->conversionRate_.target, ratesInfo, status);
    610    if (U_FAILURE(status)) return;
    611    if (unitsState == Convertibility::UNCONVERTIBLE) {
    612        status = U_ARGUMENT_TYPE_MISMATCH;
    613        return;
    614    }
    615 
    616    loadConversionRate(conversionRate_, conversionRate_.source, conversionRate_.target, unitsState,
    617                       ratesInfo, status);
    618 
    619 }
    620 
    621 int32_t UnitsConverter::compareTwoUnits(const MeasureUnitImpl &firstUnit,
    622                                        const MeasureUnitImpl &secondUnit,
    623                                        const ConversionRates &ratesInfo, UErrorCode &status) {
    624    if (U_FAILURE(status)) {
    625        return 0;
    626    }
    627 
    628    if (firstUnit.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED ||
    629        secondUnit.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) {
    630        status = U_ARGUMENT_TYPE_MISMATCH;
    631        return 0;
    632    }
    633 
    634    Convertibility unitsState = extractConvertibility(firstUnit, secondUnit, ratesInfo, status);
    635    if (U_FAILURE(status)) {
    636        return 0;
    637    }
    638 
    639    if (unitsState == Convertibility::UNCONVERTIBLE || unitsState == Convertibility::RECIPROCAL) {
    640        status = U_ARGUMENT_TYPE_MISMATCH;
    641        return 0;
    642    }
    643 
    644    StringPiece firstSpecial = getSpecialMappingName(firstUnit, ratesInfo, status);
    645    StringPiece secondSpecial = getSpecialMappingName(secondUnit, ratesInfo, status);
    646    if (!firstSpecial.empty() || !secondSpecial.empty()) {
    647        if (firstSpecial.empty()) {
    648            // non-specials come first
    649            return -1;
    650        }
    651        if (secondSpecial.empty()) {
    652            // non-specials come first
    653            return 1;
    654        }
    655        // both are specials, compare lexicographically
    656        return firstSpecial.compare(secondSpecial);
    657    }
    658 
    659    // Represents the conversion factor from the firstUnit to the base
    660    // unit that specified in the conversion data which is considered as
    661    // the root of the firstUnit and the secondUnit.
    662    Factor firstUnitToBase = loadCompoundFactor(firstUnit, ratesInfo, status);
    663    Factor secondUnitToBase = loadCompoundFactor(secondUnit, ratesInfo, status);
    664 
    665    firstUnitToBase.substituteConstants();
    666    secondUnitToBase.substituteConstants();
    667 
    668    double firstUnitToBaseConversionRate = firstUnitToBase.factorNum / firstUnitToBase.factorDen;
    669    double secondUnitToBaseConversionRate = secondUnitToBase.factorNum / secondUnitToBase.factorDen;
    670 
    671    double diff = firstUnitToBaseConversionRate - secondUnitToBaseConversionRate;
    672    if (diff > 0) {
    673        return 1;
    674    }
    675 
    676    if (diff < 0) {
    677        return -1;
    678    }
    679 
    680    return 0;
    681 }
    682 
    683 // TODO per CLDR-17421 and ICU-22683: consider getting the data below from CLDR
    684 static double minMetersPerSecForBeaufort[] = {
    685    // Minimum m/s (base) values for each Bft value, plus an extra artificial value;
    686    // when converting from Bft to m/s, the middle of the range will be used
    687    // (Values from table in Wikipedia, except for artificial value).
    688    // Since this is 0 based, max Beaufort value is thus array dimension minus 2.
    689    0.0, // 0 Bft
    690    0.3, // 1
    691    1.6, // 2
    692    3.4, // 3
    693    5.5, // 4
    694    8.0, // 5
    695    10.8, // 6
    696    13.9, // 7
    697    17.2, // 8
    698    20.8, // 9
    699    24.5, // 10
    700    28.5, // 11
    701    32.7, // 12
    702    36.9, // 13
    703    41.4, // 14
    704    46.1, // 15
    705    51.1, // 16
    706    55.8, // 17
    707    61.4, // artificial end of range 17 to give reasonable midpoint
    708 };
    709 
    710 static int maxBeaufort = UPRV_LENGTHOF(minMetersPerSecForBeaufort) - 2;
    711 
    712 // Convert from what should be discrete scale values for a particular unit like beaufort
    713 // to a corresponding value in the base unit (which can have any decimal value, like meters/sec).
    714 // First we round the scale value to the nearest integer (in case it is specified with a fractional value),
    715 // then we map that to a value in middle of the range of corresponding base values.
    716 // This can handle different scales, specified by minBaseForScaleValues[].
    717 double UnitsConverter::scaleToBase(double scaleValue, double minBaseForScaleValues[], int scaleMax) const {
    718    if (scaleValue < 0) {
    719        scaleValue = -scaleValue;
    720    }
    721    scaleValue += 0.5; // adjust up for later truncation
    722    if (scaleValue > static_cast<double>(scaleMax)) {
    723        scaleValue = static_cast<double>(scaleMax);
    724    }
    725    int scaleInt = static_cast<int>(scaleValue);
    726    return (minBaseForScaleValues[scaleInt] + minBaseForScaleValues[scaleInt+1])/2.0;
    727 }
    728 
    729 // Binary search to find the range that includes key;
    730 // if key (non-negative) is in the range rangeStarts[i] to just under rangeStarts[i+1],
    731 // then we return i; if key is >= rangeStarts[max] then we return max.
    732 // Note that max is the maximum scale value, not the number of elements in the array
    733 // (which should be larger than max).
    734 // The ranges for index 0 start at 0.0.
    735 static int bsearchRanges(double rangeStarts[], int max, double key) {
    736    if (key >= rangeStarts[max]) {
    737        return max;
    738    }
    739    int beg = 0, mid = 0, end = max + 1;
    740    while (beg < end) {
    741        mid = (beg + end) / 2;
    742        if (key < rangeStarts[mid]) {
    743            end = mid;
    744        } else if (key > rangeStarts[mid+1]) {
    745            beg = mid+1;
    746        } else {
    747            break;
    748        }
    749    }
    750    return mid;
    751 }
    752 
    753 // Convert from a value in the base unit (which can have any decimal value, like meters/sec) to a corresponding
    754 // discrete value in a scale (like beaufort), where each scale value represents a range of base values.
    755 // We binary-search the ranges to find the one that contains the specified base value, and return its index.
    756 // This can handle different scales, specified by minBaseForScaleValues[].
    757 double UnitsConverter::baseToScale(double baseValue, double minBaseForScaleValues[], int scaleMax) const {
    758    if (baseValue < 0) {
    759        baseValue = -baseValue;
    760    }
    761    int scaleIndex = bsearchRanges(minBaseForScaleValues, scaleMax, baseValue);
    762    return static_cast<double>(scaleIndex);
    763 }
    764 
    765 double UnitsConverter::convert(double inputValue) const {
    766    double result = inputValue;
    767    if (!conversionRate_.specialSource.isEmpty() || !conversionRate_.specialTarget.isEmpty()) {
    768        double base = inputValue;
    769        // convert input (=source) to base
    770        if (!conversionRate_.specialSource.isEmpty()) {
    771            // We  have a special mapping from source to base (not using factor, offset).
    772            // Currently the only supported mapping is a scale-based mapping for beaufort.
    773            base = uprv_strcmp(conversionRate_.specialSource.data(), "beaufort") == 0 ?
    774                scaleToBase(inputValue, minMetersPerSecForBeaufort, maxBeaufort): inputValue;
    775        } else {
    776            // Standard mapping (using factor) from source to base.
    777            base = inputValue * conversionRate_.factorNum / conversionRate_.factorDen;
    778        }
    779        // convert base to result (=target)
    780        if (!conversionRate_.specialTarget.isEmpty()) {
    781            // We  have a special mapping from base to target (not using factor, offset).
    782            // Currently the only supported mapping is a scale-based mapping for beaufort.
    783            result = uprv_strcmp(conversionRate_.specialTarget.data(), "beaufort") == 0 ?
    784                baseToScale(base, minMetersPerSecForBeaufort, maxBeaufort): base;
    785        } else {
    786            // Standard mapping (using factor) from base to target.
    787            result = base * conversionRate_.factorDen / conversionRate_.factorNum;
    788        }
    789        return result;
    790    }
    791    result =
    792        inputValue + conversionRate_.sourceOffset; // Reset the input to the target zero index.
    793    // Convert the quantity to from the source scale to the target scale.
    794    result *= conversionRate_.factorNum / conversionRate_.factorDen;
    795 
    796    result -= conversionRate_.targetOffset; // Set the result to its index.
    797 
    798    if (conversionRate_.reciprocal) {
    799        if (result == 0) {
    800            return uprv_getInfinity();
    801        }
    802        result = 1.0 / result;
    803    }
    804 
    805    return result;
    806 }
    807 
    808 double UnitsConverter::convertInverse(double inputValue) const {
    809    double result = inputValue;
    810    if (!conversionRate_.specialSource.isEmpty() || !conversionRate_.specialTarget.isEmpty()) {
    811        double base = inputValue;
    812        // convert input (=target) to base
    813        if (!conversionRate_.specialTarget.isEmpty()) {
    814            // We  have a special mapping from target to base (not using factor).
    815            // Currently the only supported mapping is a scale-based mapping for beaufort.
    816            base = uprv_strcmp(conversionRate_.specialTarget.data(), "beaufort") == 0 ?
    817                scaleToBase(inputValue, minMetersPerSecForBeaufort, maxBeaufort): inputValue;
    818        } else {
    819            // Standard mapping (using factor) from target to base.
    820            base = inputValue * conversionRate_.factorNum / conversionRate_.factorDen;
    821        }
    822        // convert base to result (=source)
    823        if (!conversionRate_.specialSource.isEmpty()) {
    824            // We  have a special mapping from base to source (not using factor).
    825            // Currently the only supported mapping is a scale-based mapping for beaufort.
    826            result = uprv_strcmp(conversionRate_.specialSource.data(), "beaufort") == 0 ?
    827                baseToScale(base, minMetersPerSecForBeaufort, maxBeaufort): base;
    828        } else {
    829            // Standard mapping (using factor) from base to source.
    830            result = base * conversionRate_.factorDen / conversionRate_.factorNum;
    831        }
    832        return result;
    833    }
    834    if (conversionRate_.reciprocal) {
    835        if (result == 0) {
    836            return uprv_getInfinity();
    837        }
    838        result = 1.0 / result;
    839    }
    840    result += conversionRate_.targetOffset;
    841    result *= conversionRate_.factorDen / conversionRate_.factorNum;
    842    result -= conversionRate_.sourceOffset;
    843    return result;
    844 }
    845 
    846 ConversionInfo UnitsConverter::getConversionInfo() const {
    847    ConversionInfo result;
    848    result.conversionRate = conversionRate_.factorNum / conversionRate_.factorDen;
    849    result.offset =
    850        (conversionRate_.sourceOffset * (conversionRate_.factorNum / conversionRate_.factorDen)) -
    851        conversionRate_.targetOffset;
    852    result.reciprocal = conversionRate_.reciprocal;
    853 
    854    return result;
    855 }
    856 
    857 } // namespace units
    858 U_NAMESPACE_END
    859 
    860 #endif /* #if !UCONFIG_NO_FORMATTING */