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