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