Coverage Report

Created: 2025-06-24 06:43

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