tor-browser

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

units_complexconverter.cpp (11712B)


      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 <cmath>
      9 
     10 #include "cmemory.h"
     11 #include "number_decimalquantity.h"
     12 #include "number_roundingutils.h"
     13 #include "putilimp.h"
     14 #include "uarrsort.h"
     15 #include "uassert.h"
     16 #include "unicode/fmtable.h"
     17 #include "unicode/localpointer.h"
     18 #include "unicode/measunit.h"
     19 #include "unicode/measure.h"
     20 #include "units_complexconverter.h"
     21 #include "units_converter.h"
     22 
     23 U_NAMESPACE_BEGIN
     24 namespace units {
     25 ComplexUnitsConverter::ComplexUnitsConverter(const MeasureUnitImpl &targetUnit,
     26                                             const ConversionRates &ratesInfo, UErrorCode &status)
     27    : units_(targetUnit.extractIndividualUnitsWithIndices(status)) {
     28    if (U_FAILURE(status)) {
     29        return;
     30    }
     31    U_ASSERT(units_.length() != 0);
     32 
     33    // Just borrowing a pointer to the instance
     34    MeasureUnitImpl *biggestUnit = &units_[0]->unitImpl;
     35    for (int32_t i = 1; i < units_.length(); i++) {
     36        if (UnitsConverter::compareTwoUnits(units_[i]->unitImpl, *biggestUnit, ratesInfo, status) > 0 &&
     37            U_SUCCESS(status)) {
     38            biggestUnit = &units_[i]->unitImpl;
     39        }
     40 
     41        if (U_FAILURE(status)) {
     42            return;
     43        }
     44    }
     45 
     46    this->init(*biggestUnit, ratesInfo, status);
     47 }
     48 
     49 ComplexUnitsConverter::ComplexUnitsConverter(StringPiece inputUnitIdentifier,
     50                                             StringPiece outputUnitsIdentifier, UErrorCode &status) {
     51    if (U_FAILURE(status)) {
     52        return;
     53    }
     54    MeasureUnitImpl inputUnit = MeasureUnitImpl::forIdentifier(inputUnitIdentifier, status);
     55    MeasureUnitImpl outputUnits = MeasureUnitImpl::forIdentifier(outputUnitsIdentifier, status);
     56 
     57    this->units_ = outputUnits.extractIndividualUnitsWithIndices(status);
     58    U_ASSERT(units_.length() != 0);
     59 
     60    this->init(inputUnit, ConversionRates(status), status);
     61 }
     62 
     63 ComplexUnitsConverter::ComplexUnitsConverter(const MeasureUnitImpl &inputUnit,
     64                                             const MeasureUnitImpl &outputUnits,
     65                                             const ConversionRates &ratesInfo, UErrorCode &status)
     66    : units_(outputUnits.extractIndividualUnitsWithIndices(status)) {
     67    if (U_FAILURE(status)) {
     68        return;
     69    }
     70 
     71    U_ASSERT(units_.length() != 0);
     72 
     73    this->init(inputUnit, ratesInfo, status);
     74 }
     75 
     76 void ComplexUnitsConverter::init(const MeasureUnitImpl &inputUnit,
     77                                 const ConversionRates &ratesInfo,
     78                                 UErrorCode &status) {
     79    // Sorts units in descending order. Therefore, we return -1 if
     80    // the left is bigger than right and so on.
     81    auto descendingCompareUnits = [](const void *context, const void *left, const void *right) {
     82        UErrorCode status = U_ZERO_ERROR;
     83 
     84        const auto *leftPointer = static_cast<const MeasureUnitImplWithIndex *const *>(left);
     85        const auto *rightPointer = static_cast<const MeasureUnitImplWithIndex *const *>(right);
     86 
     87        // Multiply by -1 to sort in descending order
     88        return (-1) * UnitsConverter::compareTwoUnits((**leftPointer).unitImpl,                       //
     89                                                      (**rightPointer).unitImpl,                      //
     90                                                      *static_cast<const ConversionRates *>(context), //
     91                                                      status);
     92    };
     93 
     94    uprv_sortArray(units_.getAlias(),                                                                  //
     95                   units_.length(),                                                                    //
     96                   sizeof units_[0], /* NOTE: we have already asserted that the units_ is not empty.*/ //
     97                   descendingCompareUnits,                                                             //
     98                   &ratesInfo,                                                                         //
     99                   false,                                                                              //
    100                   &status                                                                             //
    101    );
    102 
    103    // In case the `outputUnits` are `UMEASURE_UNIT_MIXED` such as `foot+inch`. In this case we need more
    104    // converters to convert from the `inputUnit` to the first unit in the `outputUnits`. Then, a
    105    // converter from the first unit in the `outputUnits` to the second unit and so on.
    106    //      For Example:
    107    //          - inputUnit is `meter`
    108    //          - outputUnits is `foot+inch`
    109    //              - Therefore, we need to have two converters:
    110    //                      1. a converter from `meter` to `foot`
    111    //                      2. a converter from `foot` to `inch`
    112    //          - Therefore, if the input is `2 meter`:
    113    //              1. convert `meter` to `foot` --> 2 meter to 6.56168 feet
    114    //              2. convert the residual of 6.56168 feet (0.56168) to inches, which will be (6.74016
    115    //              inches)
    116    //              3. then, the final result will be (6 feet and 6.74016 inches)
    117    for (int i = 0, n = units_.length(); i < n; i++) {
    118        if (i == 0) { // first element
    119            unitsConverters_.emplaceBackAndCheckErrorCode(status, inputUnit, units_[i]->unitImpl,
    120                                                          ratesInfo, status);
    121        } else {
    122            unitsConverters_.emplaceBackAndCheckErrorCode(status, units_[i - 1]->unitImpl,
    123                                                          units_[i]->unitImpl, ratesInfo, status);
    124        }
    125 
    126        if (U_FAILURE(status)) {
    127            return;
    128        }
    129    }
    130 }
    131 
    132 UBool ComplexUnitsConverter::greaterThanOrEqual(double quantity, double limit) const {
    133    U_ASSERT(unitsConverters_.length() > 0);
    134 
    135    // First converter converts to the biggest quantity.
    136    double newQuantity = unitsConverters_[0]->convert(quantity);
    137    return newQuantity >= limit;
    138 }
    139 
    140 MaybeStackVector<Measure> ComplexUnitsConverter::convert(double quantity,
    141                                                         icu::number::impl::RoundingImpl *rounder,
    142                                                         UErrorCode &status) const {
    143    // TODO: return an error for "foot-and-foot"?
    144    MaybeStackVector<Measure> result;
    145    int sign = 1;
    146    if (quantity < 0 && unitsConverters_.length() > 1) {
    147        quantity *= -1;
    148        sign = -1;
    149    }
    150 
    151    // For N converters:
    152    // - the first converter converts from the input unit to the largest unit,
    153    // - the following N-2 converters convert to bigger units for which we want integers,
    154    // - the Nth converter (index N-1) converts to the smallest unit, for which
    155    //   we keep a double.
    156    MaybeStackArray<int64_t, 5> intValues(unitsConverters_.length() - 1, status);
    157    if (U_FAILURE(status)) {
    158        return result;
    159    }
    160    uprv_memset(intValues.getAlias(), 0, (unitsConverters_.length() - 1) * sizeof(int64_t));
    161 
    162    for (int i = 0, n = unitsConverters_.length(); i < n; ++i) {
    163        quantity = (*unitsConverters_[i]).convert(quantity);
    164        if (i < n - 1) {
    165            // If quantity is at the limits of double's precision from an
    166            // integer value, we take that integer value.
    167            int64_t flooredQuantity;
    168            if (uprv_isNaN(quantity)) {
    169                // With clang on Linux: floor does not support NaN, resulting in
    170                // a giant negative number. For now, we produce "0 feet, NaN
    171                // inches". TODO(icu-units#131): revisit desired output.
    172                flooredQuantity = 0;
    173            } else {
    174                flooredQuantity = static_cast<int64_t>(floor(quantity * (1 + DBL_EPSILON)));
    175            }
    176            intValues[i] = flooredQuantity;
    177 
    178            // Keep the residual of the quantity.
    179            //   For example: `3.6 feet`, keep only `0.6 feet`
    180            double remainder = quantity - flooredQuantity;
    181            if (remainder < 0) {
    182                // Because we nudged flooredQuantity up by eps, remainder may be
    183                // negative: we must treat such a remainder as zero.
    184                quantity = 0;
    185            } else {
    186                quantity = remainder;
    187            }
    188        }
    189    }
    190 
    191    applyRounder(intValues, quantity, rounder, status);
    192 
    193    // Initialize empty result. We use a MaybeStackArray directly so we can
    194    // assign pointers - for this privilege we have to take care of cleanup.
    195    MaybeStackArray<Measure *, 4> tmpResult(unitsConverters_.length(), status);
    196    if (U_FAILURE(status)) {
    197        return result;
    198    }
    199 
    200    // Package values into temporary Measure instances in tmpResult:
    201    for (int i = 0, n = unitsConverters_.length(); i < n; ++i) {
    202        if (i < n - 1) {
    203            Formattable formattableQuantity(intValues[i] * sign);
    204            // Measure takes ownership of the MeasureUnit*
    205            MeasureUnit *type = new MeasureUnit(units_[i]->unitImpl.copy(status).build(status));
    206            tmpResult[units_[i]->index] = new Measure(formattableQuantity, type, status);
    207        } else { // LAST ELEMENT
    208            Formattable formattableQuantity(quantity * sign);
    209            // Measure takes ownership of the MeasureUnit*
    210            MeasureUnit *type = new MeasureUnit(units_[i]->unitImpl.copy(status).build(status));
    211            tmpResult[units_[i]->index] = new Measure(formattableQuantity, type, status);
    212        }
    213    }
    214 
    215    // Transfer values into result and return:
    216    for(int32_t i = 0, n = unitsConverters_.length(); i < n; ++i) {
    217        U_ASSERT(tmpResult[i] != nullptr);
    218        result.emplaceBackAndCheckErrorCode(status, *tmpResult[i]);
    219        delete tmpResult[i];
    220    }
    221 
    222    return result;
    223 }
    224 
    225 void ComplexUnitsConverter::applyRounder(MaybeStackArray<int64_t, 5> &intValues, double &quantity,
    226                                         icu::number::impl::RoundingImpl *rounder,
    227                                         UErrorCode &status) const {
    228    if (uprv_isInfinite(quantity) || uprv_isNaN(quantity)) {
    229        // Inf and NaN can't be rounded, and calculating `carry` below is known
    230        // to fail on Gentoo on HPPA and OpenSUSE on riscv64. Nothing to do.
    231        return;
    232    }
    233 
    234    if (rounder == nullptr) {
    235        // Nothing to do for the quantity.
    236        return;
    237    }
    238 
    239    number::impl::DecimalQuantity decimalQuantity;
    240    decimalQuantity.setToDouble(quantity);
    241    rounder->apply(decimalQuantity, status);
    242    if (U_FAILURE(status)) {
    243        return;
    244    }
    245    quantity = decimalQuantity.toDouble();
    246 
    247    int32_t lastIndex = unitsConverters_.length() - 1;
    248    if (lastIndex == 0) {
    249        // Only one element, no need to bubble up the carry
    250        return;
    251    }
    252 
    253    // Check if there's a carry, and bubble it back up the resulting intValues.
    254    int64_t carry = static_cast<int64_t>(floor(unitsConverters_[lastIndex]->convertInverse(quantity) * (1 + DBL_EPSILON)));
    255    if (carry <= 0) {
    256        return;
    257    }
    258    quantity -= unitsConverters_[lastIndex]->convert(static_cast<double>(carry));
    259    intValues[lastIndex - 1] += carry;
    260 
    261    // We don't use the first converter: that one is for the input unit
    262    for (int32_t j = lastIndex - 1; j > 0; j--) {
    263        carry = static_cast<int64_t>(floor(unitsConverters_[j]->convertInverse(static_cast<double>(intValues[j])) * (1 + DBL_EPSILON)));
    264        if (carry <= 0) {
    265            return;
    266        }
    267        intValues[j] -= static_cast<int64_t>(round(unitsConverters_[j]->convert(static_cast<double>(carry))));
    268        intValues[j - 1] += carry;
    269    }
    270 }
    271 
    272 } // namespace units
    273 U_NAMESPACE_END
    274 
    275 #endif /* #if !UCONFIG_NO_FORMATTING */