summaryrefslogtreecommitdiffstats
path: root/intl/icu/source/i18n/units_complexconverter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'intl/icu/source/i18n/units_complexconverter.cpp')
-rw-r--r--intl/icu/source/i18n/units_complexconverter.cpp275
1 files changed, 275 insertions, 0 deletions
diff --git a/intl/icu/source/i18n/units_complexconverter.cpp b/intl/icu/source/i18n/units_complexconverter.cpp
new file mode 100644
index 0000000000..edbb6573ff
--- /dev/null
+++ b/intl/icu/source/i18n/units_complexconverter.cpp
@@ -0,0 +1,275 @@
+// © 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;
+ 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;
+ }
+
+ if (U_FAILURE(status)) {
+ return;
+ }
+ }
+
+ this->init(*biggestUnit, ratesInfo, status);
+}
+
+ComplexUnitsConverter::ComplexUnitsConverter(StringPiece inputUnitIdentifier,
+ StringPiece outputUnitsIdentifier, UErrorCode &status) {
+ if (U_FAILURE(status)) {
+ return;
+ }
+ MeasureUnitImpl inputUnit = MeasureUnitImpl::forIdentifier(inputUnitIdentifier, status);
+ MeasureUnitImpl outputUnits = MeasureUnitImpl::forIdentifier(outputUnitsIdentifier, status);
+
+ this->units_ = outputUnits.extractIndividualUnitsWithIndices(status);
+ U_ASSERT(units_.length() != 0);
+
+ this->init(inputUnit, ConversionRates(status), 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, //
+ (**rightPointer).unitImpl, //
+ *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 && unitsConverters_.length() > 1) {
+ 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;
+ 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;
+ } else {
+ flooredQuantity = static_cast<int64_t>(floor(quantity * (1 + DBL_EPSILON)));
+ }
+ 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 (uprv_isInfinite(quantity) || uprv_isNaN(quantity)) {
+ // Inf and NaN can't be rounded, and calculating `carry` below is known
+ // to fail on Gentoo on HPPA and OpenSUSE on riscv64. Nothing to do.
+ return;
+ }
+
+ 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 = static_cast<int64_t>(floor(unitsConverters_[lastIndex]->convertInverse(quantity) * (1 + DBL_EPSILON)));
+ if (carry <= 0) {
+ return;
+ }
+ quantity -= unitsConverters_[lastIndex]->convert(static_cast<double>(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 = static_cast<int64_t>(floor(unitsConverters_[j]->convertInverse(static_cast<double>(intValues[j])) * (1 + DBL_EPSILON)));
+ if (carry <= 0) {
+ return;
+ }
+ intValues[j] -= static_cast<int64_t>(round(unitsConverters_[j]->convert(static_cast<double>(carry))));
+ intValues[j - 1] += carry;
+ }
+}
+
+} // namespace units
+U_NAMESPACE_END
+
+#endif /* #if !UCONFIG_NO_FORMATTING */