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 "uarrsort.h"
14 #include "uassert.h"
15 #include "unicode/fmtable.h"
16 #include "unicode/localpointer.h"
17 #include "unicode/measunit.h"
18 #include "unicode/measure.h"
19 #include "units_complexconverter.h"
20 #include "units_converter.h"
21 
22 U_NAMESPACE_BEGIN
23 namespace units {
24 
ComplexUnitsConverter(const MeasureUnitImpl & inputUnit,const MeasureUnitImpl & outputUnits,const ConversionRates & ratesInfo,UErrorCode & status)25 ComplexUnitsConverter::ComplexUnitsConverter(const MeasureUnitImpl &inputUnit,
26                                              const MeasureUnitImpl &outputUnits,
27                                              const ConversionRates &ratesInfo, UErrorCode &status)
28     : units_(outputUnits.extractIndividualUnits(status)) {
29     if (U_FAILURE(status)) {
30         return;
31     }
32 
33     U_ASSERT(units_.length() != 0);
34 
35     // Save the desired order of output units before we sort units_
36     for (int32_t i = 0; i < units_.length(); i++) {
37         outputUnits_.emplaceBackAndCheckErrorCode(status, units_[i]->copy(status).build(status));
38     }
39 
40     // NOTE:
41     //  This comparator is used to sort the units in a descending order. Therefore, we return -1 if
42     //  the left is bigger than right and so on.
43     auto descendingCompareUnits = [](const void *context, const void *left, const void *right) {
44         UErrorCode status = U_ZERO_ERROR;
45 
46         const auto *leftPointer = static_cast<const MeasureUnitImpl *const *>(left);
47         const auto *rightPointer = static_cast<const MeasureUnitImpl *const *>(right);
48 
49         UnitConverter fromLeftToRight(**leftPointer,                                  //
50                                       **rightPointer,                                 //
51                                       *static_cast<const ConversionRates *>(context), //
52                                       status);
53 
54         double rightFromOneLeft = fromLeftToRight.convert(1.0);
55         if (std::abs(rightFromOneLeft - 1.0) < 0.0000000001) { // Equals To
56             return 0;
57         } else if (rightFromOneLeft > 1.0) { // Greater Than
58             return -1;
59         }
60 
61         return 1; // Less Than
62     };
63 
64     uprv_sortArray(units_.getAlias(),                                                                  //
65                    units_.length(),                                                                    //
66                    sizeof units_[0], /* NOTE: we have already asserted that the units_ is not empty.*/ //
67                    descendingCompareUnits,                                                             //
68                    &ratesInfo,                                                                         //
69                    false,                                                                              //
70                    &status                                                                             //
71     );
72 
73     // In case the `outputUnits` are `UMEASURE_UNIT_MIXED` such as `foot+inch`. In this case we need more
74     // converters to convert from the `inputUnit` to the first unit in the `outputUnits`. Then, a
75     // converter from the first unit in the `outputUnits` to the second unit and so on.
76     //      For Example:
77     //          - inputUnit is `meter`
78     //          - outputUnits is `foot+inch`
79     //              - Therefore, we need to have two converters:
80     //                      1. a converter from `meter` to `foot`
81     //                      2. a converter from `foot` to `inch`
82     //          - Therefore, if the input is `2 meter`:
83     //              1. convert `meter` to `foot` --> 2 meter to 6.56168 feet
84     //              2. convert the residual of 6.56168 feet (0.56168) to inches, which will be (6.74016
85     //              inches)
86     //              3. then, the final result will be (6 feet and 6.74016 inches)
87     for (int i = 0, n = units_.length(); i < n; i++) {
88         if (i == 0) { // first element
89             unitConverters_.emplaceBackAndCheckErrorCode(status, inputUnit, *units_[i], ratesInfo,
90                                                          status);
91         } else {
92             unitConverters_.emplaceBackAndCheckErrorCode(status, *units_[i - 1], *units_[i], ratesInfo,
93                                                          status);
94         }
95 
96         if (U_FAILURE(status)) {
97             return;
98         }
99     }
100 }
101 
greaterThanOrEqual(double quantity,double limit) const102 UBool ComplexUnitsConverter::greaterThanOrEqual(double quantity, double limit) const {
103     U_ASSERT(unitConverters_.length() > 0);
104 
105     // First converter converts to the biggest quantity.
106     double newQuantity = unitConverters_[0]->convert(quantity);
107     return newQuantity >= limit;
108 }
109 
convert(double quantity,icu::number::impl::RoundingImpl * rounder,UErrorCode & status) const110 MaybeStackVector<Measure> ComplexUnitsConverter::convert(double quantity,
111                                                          icu::number::impl::RoundingImpl *rounder,
112                                                          UErrorCode &status) const {
113     // TODO(hugovdm): return an error for "foot-and-foot"?
114     MaybeStackVector<Measure> result;
115     int sign = 1;
116     if (quantity < 0) {
117         quantity *= -1;
118         sign = -1;
119     }
120 
121     // For N converters:
122     // - the first converter converts from the input unit to the largest unit,
123     // - N-1 converters convert to bigger units for which we want integers,
124     // - the Nth converter (index N-1) converts to the smallest unit, for which
125     //   we keep a double.
126     MaybeStackArray<int64_t, 5> intValues(unitConverters_.length() - 1, status);
127     if (U_FAILURE(status)) {
128         return result;
129     }
130     uprv_memset(intValues.getAlias(), 0, (unitConverters_.length() - 1) * sizeof(int64_t));
131 
132     for (int i = 0, n = unitConverters_.length(); i < n; ++i) {
133         quantity = (*unitConverters_[i]).convert(quantity);
134         if (i < n - 1) {
135             // The double type has 15 decimal digits of precision. For choosing
136             // whether to use the current unit or the next smaller unit, we
137             // therefore nudge up the number with which the thresholding
138             // decision is made. However after the thresholding, we use the
139             // original values to ensure unbiased accuracy (to the extent of
140             // double's capabilities).
141             int64_t roundedQuantity = floor(quantity * (1 + DBL_EPSILON));
142             intValues[i] = roundedQuantity;
143 
144             // Keep the residual of the quantity.
145             //   For example: `3.6 feet`, keep only `0.6 feet`
146             //
147             // When the calculation is near enough +/- DBL_EPSILON, we round to
148             // zero. (We also ensure no negative values here.)
149             if ((quantity - roundedQuantity) / quantity < DBL_EPSILON) {
150                 quantity = 0;
151             } else {
152                 quantity -= roundedQuantity;
153             }
154         } else { // LAST ELEMENT
155             if (rounder == nullptr) {
156                 // Nothing to do for the last element.
157                 break;
158             }
159 
160             // Round the last value
161             // TODO(ICU-21288): get smarter about precision for mixed units.
162             number::impl::DecimalQuantity quant;
163             quant.setToDouble(quantity);
164             rounder->apply(quant, status);
165             if (U_FAILURE(status)) {
166                 return result;
167             }
168             quantity = quant.toDouble();
169             if (i == 0) {
170                 // Last element is also the first element, so we're done
171                 break;
172             }
173 
174             // Check if there's a carry, and bubble it back up the resulting intValues.
175             int64_t carry = floor(unitConverters_[i]->convertInverse(quantity) * (1 + DBL_EPSILON));
176             if (carry <= 0) {
177                 break;
178             }
179             quantity -= unitConverters_[i]->convert(carry);
180             intValues[i - 1] += carry;
181 
182             // We don't use the first converter: that one is for the input unit
183             for (int32_t j = i - 1; j > 0; j--) {
184                 carry = floor(unitConverters_[j]->convertInverse(intValues[j]) * (1 + DBL_EPSILON));
185                 if (carry <= 0) {
186                     break;
187                 }
188                 intValues[j] -= round(unitConverters_[j]->convert(carry));
189                 intValues[j - 1] += carry;
190             }
191         }
192     }
193 
194     // Package values into Measure instances in result:
195     for (int i = 0, n = unitConverters_.length(); i < n; ++i) {
196         if (i < n - 1) {
197             Formattable formattableQuantity(intValues[i] * sign);
198             // Measure takes ownership of the MeasureUnit*
199             MeasureUnit *type = new MeasureUnit(units_[i]->copy(status).build(status));
200             if (result.emplaceBackAndCheckErrorCode(status, formattableQuantity, type, status) ==
201                 nullptr) {
202                 // Ownership wasn't taken
203                 U_ASSERT(U_FAILURE(status));
204                 delete type;
205             }
206             if (U_FAILURE(status)) {
207                 return result;
208             }
209         } else { // LAST ELEMENT
210             // Add the last element, not an integer:
211             Formattable formattableQuantity(quantity * sign);
212             // Measure takes ownership of the MeasureUnit*
213             MeasureUnit *type = new MeasureUnit(units_[i]->copy(status).build(status));
214             if (result.emplaceBackAndCheckErrorCode(status, formattableQuantity, type, status) ==
215                 nullptr) {
216                 // Ownership wasn't taken
217                 U_ASSERT(U_FAILURE(status));
218                 delete type;
219             }
220             if (U_FAILURE(status)) {
221                 return result;
222             }
223             U_ASSERT(result.length() == i + 1);
224             U_ASSERT(result[i] != nullptr);
225         }
226     }
227 
228     MaybeStackVector<Measure> orderedResult;
229     int32_t unitsCount = outputUnits_.length();
230     U_ASSERT(unitsCount == units_.length());
231     Measure **arr = result.getAlias();
232     // O(N^2) is fine: mixed units' unitsCount is usually 2 or 3.
233     for (int32_t i = 0; i < unitsCount; i++) {
234         for (int32_t j = i; j < unitsCount; j++) {
235             // Find the next expected unit, and swap it into place.
236             U_ASSERT(result[j] != nullptr);
237             if (result[j]->getUnit() == *outputUnits_[i]) {
238                 if (j != i) {
239                     Measure *tmp = arr[j];
240                     arr[j] = arr[i];
241                     arr[i] = tmp;
242                 }
243             }
244         }
245     }
246 
247     return result;
248 }
249 
250 } // namespace units
251 U_NAMESPACE_END
252 
253 #endif /* #if !UCONFIG_NO_FORMATTING */
254