/src/icu/source/i18n/units_complexconverter.cpp
Line | Count | Source (jump to first uncovered line) |
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 { |
25 | | ComplexUnitsConverter::ComplexUnitsConverter(const MeasureUnitImpl &targetUnit, |
26 | | const ConversionRates &ratesInfo, UErrorCode &status) |
27 | 0 | : units_(targetUnit.extractIndividualUnitsWithIndices(status)) { |
28 | 0 | if (U_FAILURE(status)) { |
29 | 0 | return; |
30 | 0 | } |
31 | 0 | U_ASSERT(units_.length() != 0); |
32 | | |
33 | | // Just borrowing a pointer to the instance |
34 | 0 | MeasureUnitImpl *biggestUnit = &units_[0]->unitImpl; |
35 | 0 | for (int32_t i = 1; i < units_.length(); i++) { |
36 | 0 | if (UnitsConverter::compareTwoUnits(units_[i]->unitImpl, *biggestUnit, ratesInfo, status) > 0 && |
37 | 0 | U_SUCCESS(status)) { |
38 | 0 | biggestUnit = &units_[i]->unitImpl; |
39 | 0 | } |
40 | |
|
41 | 0 | if (U_FAILURE(status)) { |
42 | 0 | return; |
43 | 0 | } |
44 | 0 | } |
45 | | |
46 | 0 | this->init(*biggestUnit, ratesInfo, status); |
47 | 0 | } |
48 | | |
49 | | ComplexUnitsConverter::ComplexUnitsConverter(StringPiece inputUnitIdentifier, |
50 | 0 | StringPiece outputUnitsIdentifier, UErrorCode &status) { |
51 | 0 | if (U_FAILURE(status)) { |
52 | 0 | return; |
53 | 0 | } |
54 | 0 | MeasureUnitImpl inputUnit = MeasureUnitImpl::forIdentifier(inputUnitIdentifier, status); |
55 | 0 | MeasureUnitImpl outputUnits = MeasureUnitImpl::forIdentifier(outputUnitsIdentifier, status); |
56 | |
|
57 | 0 | this->units_ = outputUnits.extractIndividualUnitsWithIndices(status); |
58 | 0 | U_ASSERT(units_.length() != 0); |
59 | |
|
60 | 0 | this->init(inputUnit, ConversionRates(status), status); |
61 | 0 | } |
62 | | |
63 | | ComplexUnitsConverter::ComplexUnitsConverter(const MeasureUnitImpl &inputUnit, |
64 | | const MeasureUnitImpl &outputUnits, |
65 | | const ConversionRates &ratesInfo, UErrorCode &status) |
66 | 0 | : units_(outputUnits.extractIndividualUnitsWithIndices(status)) { |
67 | 0 | if (U_FAILURE(status)) { |
68 | 0 | return; |
69 | 0 | } |
70 | | |
71 | 0 | U_ASSERT(units_.length() != 0); |
72 | |
|
73 | 0 | this->init(inputUnit, ratesInfo, status); |
74 | 0 | } |
75 | | |
76 | | void ComplexUnitsConverter::init(const MeasureUnitImpl &inputUnit, |
77 | | const ConversionRates &ratesInfo, |
78 | 0 | UErrorCode &status) { |
79 | | // Sorts units in descending order. Therefore, we return -1 if |
80 | | // the left is bigger than right and so on. |
81 | 0 | auto descendingCompareUnits = [](const void *context, const void *left, const void *right) { |
82 | 0 | UErrorCode status = U_ZERO_ERROR; |
83 | |
|
84 | 0 | const auto *leftPointer = static_cast<const MeasureUnitImplWithIndex *const *>(left); |
85 | 0 | const auto *rightPointer = static_cast<const MeasureUnitImplWithIndex *const *>(right); |
86 | | |
87 | | // Multiply by -1 to sort in descending order |
88 | 0 | return (-1) * UnitsConverter::compareTwoUnits((**leftPointer).unitImpl, // |
89 | 0 | (**rightPointer).unitImpl, // |
90 | 0 | *static_cast<const ConversionRates *>(context), // |
91 | 0 | status); |
92 | 0 | }; |
93 | |
|
94 | 0 | uprv_sortArray(units_.getAlias(), // |
95 | 0 | units_.length(), // |
96 | 0 | sizeof units_[0], /* NOTE: we have already asserted that the units_ is not empty.*/ // |
97 | 0 | descendingCompareUnits, // |
98 | 0 | &ratesInfo, // |
99 | 0 | false, // |
100 | 0 | &status // |
101 | 0 | ); |
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 | 0 | for (int i = 0, n = units_.length(); i < n; i++) { |
118 | 0 | if (i == 0) { // first element |
119 | 0 | unitsConverters_.emplaceBackAndCheckErrorCode(status, inputUnit, units_[i]->unitImpl, |
120 | 0 | ratesInfo, status); |
121 | 0 | } else { |
122 | 0 | unitsConverters_.emplaceBackAndCheckErrorCode(status, units_[i - 1]->unitImpl, |
123 | 0 | units_[i]->unitImpl, ratesInfo, status); |
124 | 0 | } |
125 | |
|
126 | 0 | if (U_FAILURE(status)) { |
127 | 0 | return; |
128 | 0 | } |
129 | 0 | } |
130 | 0 | } |
131 | | |
132 | 0 | UBool ComplexUnitsConverter::greaterThanOrEqual(double quantity, double limit) const { |
133 | 0 | U_ASSERT(unitsConverters_.length() > 0); |
134 | | |
135 | | // First converter converts to the biggest quantity. |
136 | 0 | double newQuantity = unitsConverters_[0]->convert(quantity); |
137 | 0 | return newQuantity >= limit; |
138 | 0 | } |
139 | | |
140 | | MaybeStackVector<Measure> ComplexUnitsConverter::convert(double quantity, |
141 | | icu::number::impl::RoundingImpl *rounder, |
142 | 0 | UErrorCode &status) const { |
143 | | // TODO: return an error for "foot-and-foot"? |
144 | 0 | MaybeStackVector<Measure> result; |
145 | 0 | int sign = 1; |
146 | 0 | if (quantity < 0) { |
147 | 0 | quantity *= -1; |
148 | 0 | sign = -1; |
149 | 0 | } |
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 | 0 | MaybeStackArray<int64_t, 5> intValues(unitsConverters_.length() - 1, status); |
157 | 0 | if (U_FAILURE(status)) { |
158 | 0 | return result; |
159 | 0 | } |
160 | 0 | uprv_memset(intValues.getAlias(), 0, (unitsConverters_.length() - 1) * sizeof(int64_t)); |
161 | |
|
162 | 0 | for (int i = 0, n = unitsConverters_.length(); i < n; ++i) { |
163 | 0 | quantity = (*unitsConverters_[i]).convert(quantity); |
164 | 0 | 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 | 0 | int64_t flooredQuantity = floor(quantity * (1 + DBL_EPSILON)); |
168 | 0 | 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 | 0 | flooredQuantity = 0; |
173 | 0 | } |
174 | 0 | intValues[i] = flooredQuantity; |
175 | | |
176 | | // Keep the residual of the quantity. |
177 | | // For example: `3.6 feet`, keep only `0.6 feet` |
178 | 0 | double remainder = quantity - flooredQuantity; |
179 | 0 | 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 | 0 | quantity = 0; |
183 | 0 | } else { |
184 | 0 | quantity = remainder; |
185 | 0 | } |
186 | 0 | } |
187 | 0 | } |
188 | |
|
189 | 0 | 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 | 0 | MaybeStackArray<Measure *, 4> tmpResult(unitsConverters_.length(), status); |
194 | 0 | if (U_FAILURE(status)) { |
195 | 0 | return result; |
196 | 0 | } |
197 | | |
198 | | // Package values into temporary Measure instances in tmpResult: |
199 | 0 | for (int i = 0, n = unitsConverters_.length(); i < n; ++i) { |
200 | 0 | if (i < n - 1) { |
201 | 0 | Formattable formattableQuantity(intValues[i] * sign); |
202 | | // Measure takes ownership of the MeasureUnit* |
203 | 0 | MeasureUnit *type = new MeasureUnit(units_[i]->unitImpl.copy(status).build(status)); |
204 | 0 | tmpResult[units_[i]->index] = new Measure(formattableQuantity, type, status); |
205 | 0 | } else { // LAST ELEMENT |
206 | 0 | Formattable formattableQuantity(quantity * sign); |
207 | | // Measure takes ownership of the MeasureUnit* |
208 | 0 | MeasureUnit *type = new MeasureUnit(units_[i]->unitImpl.copy(status).build(status)); |
209 | 0 | tmpResult[units_[i]->index] = new Measure(formattableQuantity, type, status); |
210 | 0 | } |
211 | 0 | } |
212 | | |
213 | | |
214 | | // Transfer values into result and return: |
215 | 0 | for(int32_t i = 0, n = unitsConverters_.length(); i < n; ++i) { |
216 | 0 | U_ASSERT(tmpResult[i] != nullptr); |
217 | 0 | result.emplaceBackAndCheckErrorCode(status, *tmpResult[i]); |
218 | 0 | delete tmpResult[i]; |
219 | 0 | } |
220 | |
|
221 | 0 | return result; |
222 | 0 | } |
223 | | |
224 | | void ComplexUnitsConverter::applyRounder(MaybeStackArray<int64_t, 5> &intValues, double &quantity, |
225 | | icu::number::impl::RoundingImpl *rounder, |
226 | 0 | UErrorCode &status) const { |
227 | 0 | if (rounder == nullptr) { |
228 | | // Nothing to do for the quantity. |
229 | 0 | return; |
230 | 0 | } |
231 | | |
232 | 0 | number::impl::DecimalQuantity decimalQuantity; |
233 | 0 | decimalQuantity.setToDouble(quantity); |
234 | 0 | rounder->apply(decimalQuantity, status); |
235 | 0 | if (U_FAILURE(status)) { |
236 | 0 | return; |
237 | 0 | } |
238 | 0 | quantity = decimalQuantity.toDouble(); |
239 | |
|
240 | 0 | int32_t lastIndex = unitsConverters_.length() - 1; |
241 | 0 | if (lastIndex == 0) { |
242 | | // Only one element, no need to bubble up the carry |
243 | 0 | return; |
244 | 0 | } |
245 | | |
246 | | // Check if there's a carry, and bubble it back up the resulting intValues. |
247 | 0 | int64_t carry = floor(unitsConverters_[lastIndex]->convertInverse(quantity) * (1 + DBL_EPSILON)); |
248 | 0 | if (carry <= 0) { |
249 | 0 | return; |
250 | 0 | } |
251 | 0 | quantity -= unitsConverters_[lastIndex]->convert(carry); |
252 | 0 | intValues[lastIndex - 1] += carry; |
253 | | |
254 | | // We don't use the first converter: that one is for the input unit |
255 | 0 | for (int32_t j = lastIndex - 1; j > 0; j--) { |
256 | 0 | carry = floor(unitsConverters_[j]->convertInverse(intValues[j]) * (1 + DBL_EPSILON)); |
257 | 0 | if (carry <= 0) { |
258 | 0 | return; |
259 | 0 | } |
260 | 0 | intValues[j] -= round(unitsConverters_[j]->convert(carry)); |
261 | 0 | intValues[j - 1] += carry; |
262 | 0 | } |
263 | 0 | } |
264 | | |
265 | | } // namespace units |
266 | | U_NAMESPACE_END |
267 | | |
268 | | #endif /* #if !UCONFIG_NO_FORMATTING */ |