blob: 2aa331ac3f0724537f39cf1751feb41ee005c514 [file] [log] [blame]
// © 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 */