Coverage Report

Created: 2025-06-24 06:54

/src/icu/icu4c/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 && unitsConverters_.length() > 1) {
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;
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
            } else {
174
0
                flooredQuantity = static_cast<int64_t>(floor(quantity * (1 + DBL_EPSILON)));
175
0
            }
176
0
            intValues[i] = flooredQuantity;
177
178
            // Keep the residual of the quantity.
179
            //   For example: `3.6 feet`, keep only `0.6 feet`
180
0
            double remainder = quantity - flooredQuantity;
181
0
            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
0
                quantity = 0;
185
0
            } else {
186
0
                quantity = remainder;
187
0
            }
188
0
        }
189
0
    }
190
191
0
    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
0
    MaybeStackArray<Measure *, 4> tmpResult(unitsConverters_.length(), status);
196
0
    if (U_FAILURE(status)) {
197
0
        return result;
198
0
    }
199
200
    // Package values into temporary Measure instances in tmpResult:
201
0
    for (int i = 0, n = unitsConverters_.length(); i < n; ++i) {
202
0
        if (i < n - 1) {
203
0
            Formattable formattableQuantity(intValues[i] * sign);
204
            // Measure takes ownership of the MeasureUnit*
205
0
            MeasureUnit *type = new MeasureUnit(units_[i]->unitImpl.copy(status).build(status));
206
0
            tmpResult[units_[i]->index] = new Measure(formattableQuantity, type, status);
207
0
        } else { // LAST ELEMENT
208
0
            Formattable formattableQuantity(quantity * sign);
209
            // Measure takes ownership of the MeasureUnit*
210
0
            MeasureUnit *type = new MeasureUnit(units_[i]->unitImpl.copy(status).build(status));
211
0
            tmpResult[units_[i]->index] = new Measure(formattableQuantity, type, status);
212
0
        }
213
0
    }
214
215
    // Transfer values into result and return:
216
0
    for(int32_t i = 0, n = unitsConverters_.length(); i < n; ++i) {
217
0
        U_ASSERT(tmpResult[i] != nullptr);
218
0
        result.emplaceBackAndCheckErrorCode(status, *tmpResult[i]);
219
0
        delete tmpResult[i];
220
0
    }
221
222
0
    return result;
223
0
}
224
225
void ComplexUnitsConverter::applyRounder(MaybeStackArray<int64_t, 5> &intValues, double &quantity,
226
                                         icu::number::impl::RoundingImpl *rounder,
227
0
                                         UErrorCode &status) const {
228
0
    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
0
        return;
232
0
    }
233
234
0
    if (rounder == nullptr) {
235
        // Nothing to do for the quantity.
236
0
        return;
237
0
    }
238
239
0
    number::impl::DecimalQuantity decimalQuantity;
240
0
    decimalQuantity.setToDouble(quantity);
241
0
    rounder->apply(decimalQuantity, status);
242
0
    if (U_FAILURE(status)) {
243
0
        return;
244
0
    }
245
0
    quantity = decimalQuantity.toDouble();
246
247
0
    int32_t lastIndex = unitsConverters_.length() - 1;
248
0
    if (lastIndex == 0) {
249
        // Only one element, no need to bubble up the carry
250
0
        return;
251
0
    }
252
253
    // Check if there's a carry, and bubble it back up the resulting intValues.
254
0
    int64_t carry = static_cast<int64_t>(floor(unitsConverters_[lastIndex]->convertInverse(quantity) * (1 + DBL_EPSILON)));
255
0
    if (carry <= 0) {
256
0
        return;
257
0
    }
258
0
    quantity -= unitsConverters_[lastIndex]->convert(static_cast<double>(carry));
259
0
    intValues[lastIndex - 1] += carry;
260
261
    // We don't use the first converter: that one is for the input unit
262
0
    for (int32_t j = lastIndex - 1; j > 0; j--) {
263
0
        carry = static_cast<int64_t>(floor(unitsConverters_[j]->convertInverse(static_cast<double>(intValues[j])) * (1 + DBL_EPSILON)));
264
0
        if (carry <= 0) {
265
0
            return;
266
0
        }
267
0
        intValues[j] -= static_cast<int64_t>(round(unitsConverters_[j]->convert(static_cast<double>(carry))));
268
0
        intValues[j - 1] += carry;
269
0
    }
270
0
}
271
272
} // namespace units
273
U_NAMESPACE_END
274
275
#endif /* #if !UCONFIG_NO_FORMATTING */