| // © 2020 and later: Unicode, Inc. and others. |
| // License & terms of use: http://www.unicode.org/copyright.html |
| |
| #include "unicode/utypes.h" |
| |
| #if !UCONFIG_NO_FORMATTING |
| |
| #include <cmath> |
| |
| #include "cmemory.h" |
| #include "number_decimalquantity.h" |
| #include "number_roundingutils.h" |
| #include "putilimp.h" |
| #include "uarrsort.h" |
| #include "uassert.h" |
| #include "unicode/fmtable.h" |
| #include "unicode/localpointer.h" |
| #include "unicode/measunit.h" |
| #include "unicode/measure.h" |
| #include "units_complexconverter.h" |
| #include "units_converter.h" |
| |
| U_NAMESPACE_BEGIN |
| namespace units { |
| ComplexUnitsConverter::ComplexUnitsConverter(const MeasureUnitImpl &targetUnit, |
| const ConversionRates &ratesInfo, UErrorCode &status) |
| : units_(targetUnit.extractIndividualUnitsWithIndices(status)) { |
| if (U_FAILURE(status)) { |
| return; |
| } |
| U_ASSERT(units_.length() != 0); |
| |
| // Just borrowing a pointer to the instance |
| MeasureUnitImpl *biggestUnit = units_[0]->unitImpl.getAlias(); |
| for (int32_t i = 1; i < units_.length(); i++) { |
| if (UnitsConverter::compareTwoUnits(*units_[i]->unitImpl, *biggestUnit, ratesInfo, status) > 0 && |
| U_SUCCESS(status)) { |
| biggestUnit = units_[i]->unitImpl.getAlias(); |
| } |
| |
| if (U_FAILURE(status)) { |
| return; |
| } |
| } |
| this->init(*biggestUnit, ratesInfo, status); |
| } |
| |
| ComplexUnitsConverter::ComplexUnitsConverter(const MeasureUnitImpl &inputUnit, |
| const MeasureUnitImpl &outputUnits, |
| const ConversionRates &ratesInfo, UErrorCode &status) |
| : units_(outputUnits.extractIndividualUnitsWithIndices(status)) { |
| if (U_FAILURE(status)) { |
| return; |
| } |
| |
| U_ASSERT(units_.length() != 0); |
| |
| this->init(inputUnit, ratesInfo, status); |
| } |
| |
| void ComplexUnitsConverter::init(const MeasureUnitImpl &inputUnit, |
| const ConversionRates &ratesInfo, |
| UErrorCode &status) { |
| // Sorts units in descending order. Therefore, we return -1 if |
| // the left is bigger than right and so on. |
| auto descendingCompareUnits = [](const void *context, const void *left, const void *right) { |
| UErrorCode status = U_ZERO_ERROR; |
| |
| const auto *leftPointer = static_cast<const MeasureUnitImplWithIndex *const *>(left); |
| const auto *rightPointer = static_cast<const MeasureUnitImplWithIndex *const *>(right); |
| |
| // Multiply by -1 to sort in descending order |
| return (-1) * UnitsConverter::compareTwoUnits(*((**leftPointer).unitImpl) /* left unit*/, // |
| *((**rightPointer).unitImpl) /* right unit */, // |
| *static_cast<const ConversionRates *>(context), // |
| status); |
| }; |
| |
| uprv_sortArray(units_.getAlias(), // |
| units_.length(), // |
| sizeof units_[0], /* NOTE: we have already asserted that the units_ is not empty.*/ // |
| descendingCompareUnits, // |
| &ratesInfo, // |
| false, // |
| &status // |
| ); |
| |
| // In case the `outputUnits` are `UMEASURE_UNIT_MIXED` such as `foot+inch`. In this case we need more |
| // converters to convert from the `inputUnit` to the first unit in the `outputUnits`. Then, a |
| // converter from the first unit in the `outputUnits` to the second unit and so on. |
| // For Example: |
| // - inputUnit is `meter` |
| // - outputUnits is `foot+inch` |
| // - Therefore, we need to have two converters: |
| // 1. a converter from `meter` to `foot` |
| // 2. a converter from `foot` to `inch` |
| // - Therefore, if the input is `2 meter`: |
| // 1. convert `meter` to `foot` --> 2 meter to 6.56168 feet |
| // 2. convert the residual of 6.56168 feet (0.56168) to inches, which will be (6.74016 |
| // inches) |
| // 3. then, the final result will be (6 feet and 6.74016 inches) |
| for (int i = 0, n = units_.length(); i < n; i++) { |
| if (i == 0) { // first element |
| unitsConverters_.emplaceBackAndCheckErrorCode(status, inputUnit, *(units_[i]->unitImpl), |
| ratesInfo, status); |
| } else { |
| unitsConverters_.emplaceBackAndCheckErrorCode(status, *(units_[i - 1]->unitImpl), |
| *(units_[i]->unitImpl), ratesInfo, status); |
| } |
| |
| if (U_FAILURE(status)) { |
| return; |
| } |
| } |
| } |
| |
| UBool ComplexUnitsConverter::greaterThanOrEqual(double quantity, double limit) const { |
| U_ASSERT(unitsConverters_.length() > 0); |
| |
| // First converter converts to the biggest quantity. |
| double newQuantity = unitsConverters_[0]->convert(quantity); |
| return newQuantity >= limit; |
| } |
| |
| MaybeStackVector<Measure> ComplexUnitsConverter::convert(double quantity, |
| icu::number::impl::RoundingImpl *rounder, |
| UErrorCode &status) const { |
| // TODO: return an error for "foot-and-foot"? |
| MaybeStackVector<Measure> result; |
| int sign = 1; |
| if (quantity < 0) { |
| quantity *= -1; |
| sign = -1; |
| } |
| |
| // For N converters: |
| // - the first converter converts from the input unit to the largest unit, |
| // - the following N-2 converters convert to bigger units for which we want integers, |
| // - the Nth converter (index N-1) converts to the smallest unit, for which |
| // we keep a double. |
| MaybeStackArray<int64_t, 5> intValues(unitsConverters_.length() - 1, status); |
| if (U_FAILURE(status)) { |
| return result; |
| } |
| uprv_memset(intValues.getAlias(), 0, (unitsConverters_.length() - 1) * sizeof(int64_t)); |
| |
| for (int i = 0, n = unitsConverters_.length(); i < n; ++i) { |
| quantity = (*unitsConverters_[i]).convert(quantity); |
| if (i < n - 1) { |
| // If quantity is at the limits of double's precision from an |
| // integer value, we take that integer value. |
| int64_t flooredQuantity = floor(quantity * (1 + DBL_EPSILON)); |
| if (uprv_isNaN(quantity)) { |
| // With clang on Linux: floor does not support NaN, resulting in |
| // a giant negative number. For now, we produce "0 feet, NaN |
| // inches". TODO(icu-units#131): revisit desired output. |
| flooredQuantity = 0; |
| } |
| intValues[i] = flooredQuantity; |
| |
| // Keep the residual of the quantity. |
| // For example: `3.6 feet`, keep only `0.6 feet` |
| double remainder = quantity - flooredQuantity; |
| if (remainder < 0) { |
| // Because we nudged flooredQuantity up by eps, remainder may be |
| // negative: we must treat such a remainder as zero. |
| quantity = 0; |
| } else { |
| quantity = remainder; |
| } |
| } |
| } |
| |
| applyRounder(intValues, quantity, rounder, status); |
| |
| // Initialize empty result. We use a MaybeStackArray directly so we can |
| // assign pointers - for this privilege we have to take care of cleanup. |
| MaybeStackArray<Measure *, 4> tmpResult(unitsConverters_.length(), status); |
| if (U_FAILURE(status)) { |
| return result; |
| } |
| |
| // Package values into temporary Measure instances in tmpResult: |
| for (int i = 0, n = unitsConverters_.length(); i < n; ++i) { |
| if (i < n - 1) { |
| Formattable formattableQuantity(intValues[i] * sign); |
| // Measure takes ownership of the MeasureUnit* |
| MeasureUnit *type = new MeasureUnit(units_[i]->unitImpl->copy(status).build(status)); |
| tmpResult[units_[i]->index] = new Measure(formattableQuantity, type, status); |
| } else { // LAST ELEMENT |
| Formattable formattableQuantity(quantity * sign); |
| // Measure takes ownership of the MeasureUnit* |
| MeasureUnit *type = new MeasureUnit(units_[i]->unitImpl->copy(status).build(status)); |
| tmpResult[units_[i]->index] = new Measure(formattableQuantity, type, status); |
| } |
| } |
| |
| |
| // Transfer values into result and return: |
| for(int32_t i = 0, n = unitsConverters_.length(); i < n; ++i) { |
| U_ASSERT(tmpResult[i] != nullptr); |
| result.emplaceBackAndCheckErrorCode(status, *tmpResult[i]); |
| delete tmpResult[i]; |
| } |
| |
| return result; |
| } |
| |
| void ComplexUnitsConverter::applyRounder(MaybeStackArray<int64_t, 5> &intValues, double &quantity, |
| icu::number::impl::RoundingImpl *rounder, |
| UErrorCode &status) const { |
| if (rounder == nullptr) { |
| // Nothing to do for the quantity. |
| return; |
| } |
| |
| number::impl::DecimalQuantity decimalQuantity; |
| decimalQuantity.setToDouble(quantity); |
| rounder->apply(decimalQuantity, status); |
| if (U_FAILURE(status)) { |
| return; |
| } |
| quantity = decimalQuantity.toDouble(); |
| |
| int32_t lastIndex = unitsConverters_.length() - 1; |
| if (lastIndex == 0) { |
| // Only one element, no need to bubble up the carry |
| return; |
| } |
| |
| // Check if there's a carry, and bubble it back up the resulting intValues. |
| int64_t carry = floor(unitsConverters_[lastIndex]->convertInverse(quantity) * (1 + DBL_EPSILON)); |
| if (carry <= 0) { |
| return; |
| } |
| quantity -= unitsConverters_[lastIndex]->convert(carry); |
| intValues[lastIndex - 1] += carry; |
| |
| // We don't use the first converter: that one is for the input unit |
| for (int32_t j = lastIndex - 1; j > 0; j--) { |
| carry = floor(unitsConverters_[j]->convertInverse(intValues[j]) * (1 + DBL_EPSILON)); |
| if (carry <= 0) { |
| return; |
| } |
| intValues[j] -= round(unitsConverters_[j]->convert(carry)); |
| intValues[j - 1] += carry; |
| } |
| } |
| |
| } // namespace units |
| U_NAMESPACE_END |
| |
| #endif /* #if !UCONFIG_NO_FORMATTING */ |