You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
254 lines
10 KiB
254 lines
10 KiB
// © 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 "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 &inputUnit,
|
|
const MeasureUnitImpl &outputUnits,
|
|
const ConversionRates &ratesInfo, UErrorCode &status)
|
|
: units_(outputUnits.extractIndividualUnits(status)) {
|
|
if (U_FAILURE(status)) {
|
|
return;
|
|
}
|
|
|
|
U_ASSERT(units_.length() != 0);
|
|
|
|
// Save the desired order of output units before we sort units_
|
|
for (int32_t i = 0; i < units_.length(); i++) {
|
|
outputUnits_.emplaceBackAndCheckErrorCode(status, units_[i]->copy(status).build(status));
|
|
}
|
|
|
|
// NOTE:
|
|
// This comparator is used to sort the units in a 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 MeasureUnitImpl *const *>(left);
|
|
const auto *rightPointer = static_cast<const MeasureUnitImpl *const *>(right);
|
|
|
|
UnitConverter fromLeftToRight(**leftPointer, //
|
|
**rightPointer, //
|
|
*static_cast<const ConversionRates *>(context), //
|
|
status);
|
|
|
|
double rightFromOneLeft = fromLeftToRight.convert(1.0);
|
|
if (std::abs(rightFromOneLeft - 1.0) < 0.0000000001) { // Equals To
|
|
return 0;
|
|
} else if (rightFromOneLeft > 1.0) { // Greater Than
|
|
return -1;
|
|
}
|
|
|
|
return 1; // Less Than
|
|
};
|
|
|
|
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
|
|
unitConverters_.emplaceBackAndCheckErrorCode(status, inputUnit, *units_[i], ratesInfo,
|
|
status);
|
|
} else {
|
|
unitConverters_.emplaceBackAndCheckErrorCode(status, *units_[i - 1], *units_[i], ratesInfo,
|
|
status);
|
|
}
|
|
|
|
if (U_FAILURE(status)) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
UBool ComplexUnitsConverter::greaterThanOrEqual(double quantity, double limit) const {
|
|
U_ASSERT(unitConverters_.length() > 0);
|
|
|
|
// First converter converts to the biggest quantity.
|
|
double newQuantity = unitConverters_[0]->convert(quantity);
|
|
return newQuantity >= limit;
|
|
}
|
|
|
|
MaybeStackVector<Measure> ComplexUnitsConverter::convert(double quantity,
|
|
icu::number::impl::RoundingImpl *rounder,
|
|
UErrorCode &status) const {
|
|
// TODO(hugovdm): 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,
|
|
// - N-1 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(unitConverters_.length() - 1, status);
|
|
if (U_FAILURE(status)) {
|
|
return result;
|
|
}
|
|
uprv_memset(intValues.getAlias(), 0, (unitConverters_.length() - 1) * sizeof(int64_t));
|
|
|
|
for (int i = 0, n = unitConverters_.length(); i < n; ++i) {
|
|
quantity = (*unitConverters_[i]).convert(quantity);
|
|
if (i < n - 1) {
|
|
// The double type has 15 decimal digits of precision. For choosing
|
|
// whether to use the current unit or the next smaller unit, we
|
|
// therefore nudge up the number with which the thresholding
|
|
// decision is made. However after the thresholding, we use the
|
|
// original values to ensure unbiased accuracy (to the extent of
|
|
// double's capabilities).
|
|
int64_t roundedQuantity = floor(quantity * (1 + DBL_EPSILON));
|
|
intValues[i] = roundedQuantity;
|
|
|
|
// Keep the residual of the quantity.
|
|
// For example: `3.6 feet`, keep only `0.6 feet`
|
|
//
|
|
// When the calculation is near enough +/- DBL_EPSILON, we round to
|
|
// zero. (We also ensure no negative values here.)
|
|
if ((quantity - roundedQuantity) / quantity < DBL_EPSILON) {
|
|
quantity = 0;
|
|
} else {
|
|
quantity -= roundedQuantity;
|
|
}
|
|
} else { // LAST ELEMENT
|
|
if (rounder == nullptr) {
|
|
// Nothing to do for the last element.
|
|
break;
|
|
}
|
|
|
|
// Round the last value
|
|
// TODO(ICU-21288): get smarter about precision for mixed units.
|
|
number::impl::DecimalQuantity quant;
|
|
quant.setToDouble(quantity);
|
|
rounder->apply(quant, status);
|
|
if (U_FAILURE(status)) {
|
|
return result;
|
|
}
|
|
quantity = quant.toDouble();
|
|
if (i == 0) {
|
|
// Last element is also the first element, so we're done
|
|
break;
|
|
}
|
|
|
|
// Check if there's a carry, and bubble it back up the resulting intValues.
|
|
int64_t carry = floor(unitConverters_[i]->convertInverse(quantity) * (1 + DBL_EPSILON));
|
|
if (carry <= 0) {
|
|
break;
|
|
}
|
|
quantity -= unitConverters_[i]->convert(carry);
|
|
intValues[i - 1] += carry;
|
|
|
|
// We don't use the first converter: that one is for the input unit
|
|
for (int32_t j = i - 1; j > 0; j--) {
|
|
carry = floor(unitConverters_[j]->convertInverse(intValues[j]) * (1 + DBL_EPSILON));
|
|
if (carry <= 0) {
|
|
break;
|
|
}
|
|
intValues[j] -= round(unitConverters_[j]->convert(carry));
|
|
intValues[j - 1] += carry;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Package values into Measure instances in result:
|
|
for (int i = 0, n = unitConverters_.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]->copy(status).build(status));
|
|
if (result.emplaceBackAndCheckErrorCode(status, formattableQuantity, type, status) ==
|
|
nullptr) {
|
|
// Ownership wasn't taken
|
|
U_ASSERT(U_FAILURE(status));
|
|
delete type;
|
|
}
|
|
if (U_FAILURE(status)) {
|
|
return result;
|
|
}
|
|
} else { // LAST ELEMENT
|
|
// Add the last element, not an integer:
|
|
Formattable formattableQuantity(quantity * sign);
|
|
// Measure takes ownership of the MeasureUnit*
|
|
MeasureUnit *type = new MeasureUnit(units_[i]->copy(status).build(status));
|
|
if (result.emplaceBackAndCheckErrorCode(status, formattableQuantity, type, status) ==
|
|
nullptr) {
|
|
// Ownership wasn't taken
|
|
U_ASSERT(U_FAILURE(status));
|
|
delete type;
|
|
}
|
|
if (U_FAILURE(status)) {
|
|
return result;
|
|
}
|
|
U_ASSERT(result.length() == i + 1);
|
|
U_ASSERT(result[i] != nullptr);
|
|
}
|
|
}
|
|
|
|
MaybeStackVector<Measure> orderedResult;
|
|
int32_t unitsCount = outputUnits_.length();
|
|
U_ASSERT(unitsCount == units_.length());
|
|
Measure **arr = result.getAlias();
|
|
// O(N^2) is fine: mixed units' unitsCount is usually 2 or 3.
|
|
for (int32_t i = 0; i < unitsCount; i++) {
|
|
for (int32_t j = i; j < unitsCount; j++) {
|
|
// Find the next expected unit, and swap it into place.
|
|
U_ASSERT(result[j] != nullptr);
|
|
if (result[j]->getUnit() == *outputUnits_[i]) {
|
|
if (j != i) {
|
|
Measure *tmp = arr[j];
|
|
arr[j] = arr[i];
|
|
arr[i] = tmp;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
} // namespace units
|
|
U_NAMESPACE_END
|
|
|
|
#endif /* #if !UCONFIG_NO_FORMATTING */
|