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 {
ComplexUnitsConverter(const MeasureUnitImpl & targetUnit,const ConversionRates & ratesInfo,UErrorCode & status)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
ComplexUnitsConverter(StringPiece inputUnitIdentifier,StringPiece outputUnitsIdentifier,UErrorCode & status)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
ComplexUnitsConverter(const MeasureUnitImpl & inputUnit,const MeasureUnitImpl & outputUnits,const ConversionRates & ratesInfo,UErrorCode & status)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
init(const MeasureUnitImpl & inputUnit,const ConversionRates & ratesInfo,UErrorCode & status)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
greaterThanOrEqual(double quantity,double limit) const132 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
convert(double quantity,icu::number::impl::RoundingImpl * rounder,UErrorCode & status) const140 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) {
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 = floor(quantity * (1 + DBL_EPSILON));
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 }
174 intValues[i] = flooredQuantity;
175
176 // Keep the residual of the quantity.
177 // For example: `3.6 feet`, keep only `0.6 feet`
178 double remainder = quantity - flooredQuantity;
179 if (remainder < 0) {
180 // Because we nudged flooredQuantity up by eps, remainder may be
181 // negative: we must treat such a remainder as zero.
182 quantity = 0;
183 } else {
184 quantity = remainder;
185 }
186 }
187 }
188
189 applyRounder(intValues, quantity, rounder, status);
190
191 // Initialize empty result. We use a MaybeStackArray directly so we can
192 // assign pointers - for this privilege we have to take care of cleanup.
193 MaybeStackArray<Measure *, 4> tmpResult(unitsConverters_.length(), status);
194 if (U_FAILURE(status)) {
195 return result;
196 }
197
198 // Package values into temporary Measure instances in tmpResult:
199 for (int i = 0, n = unitsConverters_.length(); i < n; ++i) {
200 if (i < n - 1) {
201 Formattable formattableQuantity(intValues[i] * sign);
202 // Measure takes ownership of the MeasureUnit*
203 MeasureUnit *type = new MeasureUnit(units_[i]->unitImpl.copy(status).build(status));
204 tmpResult[units_[i]->index] = new Measure(formattableQuantity, type, status);
205 } else { // LAST ELEMENT
206 Formattable formattableQuantity(quantity * sign);
207 // Measure takes ownership of the MeasureUnit*
208 MeasureUnit *type = new MeasureUnit(units_[i]->unitImpl.copy(status).build(status));
209 tmpResult[units_[i]->index] = new Measure(formattableQuantity, type, status);
210 }
211 }
212
213
214 // Transfer values into result and return:
215 for(int32_t i = 0, n = unitsConverters_.length(); i < n; ++i) {
216 U_ASSERT(tmpResult[i] != nullptr);
217 result.emplaceBackAndCheckErrorCode(status, *tmpResult[i]);
218 delete tmpResult[i];
219 }
220
221 return result;
222 }
223
applyRounder(MaybeStackArray<int64_t,5> & intValues,double & quantity,icu::number::impl::RoundingImpl * rounder,UErrorCode & status) const224 void ComplexUnitsConverter::applyRounder(MaybeStackArray<int64_t, 5> &intValues, double &quantity,
225 icu::number::impl::RoundingImpl *rounder,
226 UErrorCode &status) const {
227 if (rounder == nullptr) {
228 // Nothing to do for the quantity.
229 return;
230 }
231
232 number::impl::DecimalQuantity decimalQuantity;
233 decimalQuantity.setToDouble(quantity);
234 rounder->apply(decimalQuantity, status);
235 if (U_FAILURE(status)) {
236 return;
237 }
238 quantity = decimalQuantity.toDouble();
239
240 int32_t lastIndex = unitsConverters_.length() - 1;
241 if (lastIndex == 0) {
242 // Only one element, no need to bubble up the carry
243 return;
244 }
245
246 // Check if there's a carry, and bubble it back up the resulting intValues.
247 int64_t carry = floor(unitsConverters_[lastIndex]->convertInverse(quantity) * (1 + DBL_EPSILON));
248 if (carry <= 0) {
249 return;
250 }
251 quantity -= unitsConverters_[lastIndex]->convert(carry);
252 intValues[lastIndex - 1] += carry;
253
254 // We don't use the first converter: that one is for the input unit
255 for (int32_t j = lastIndex - 1; j > 0; j--) {
256 carry = floor(unitsConverters_[j]->convertInverse(intValues[j]) * (1 + DBL_EPSILON));
257 if (carry <= 0) {
258 return;
259 }
260 intValues[j] -= round(unitsConverters_[j]->convert(carry));
261 intValues[j - 1] += carry;
262 }
263 }
264
265 } // namespace units
266 U_NAMESPACE_END
267
268 #endif /* #if !UCONFIG_NO_FORMATTING */
269