/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 */  |