/src/icu/source/i18n/units_converter.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 "charstr.h" |
9 | | #include "cmemory.h" |
10 | | #include "double-conversion-string-to-double.h" |
11 | | #include "measunit_impl.h" |
12 | | #include "uassert.h" |
13 | | #include "unicode/errorcode.h" |
14 | | #include "unicode/localpointer.h" |
15 | | #include "unicode/stringpiece.h" |
16 | | #include "units_converter.h" |
17 | | #include <algorithm> |
18 | | #include <cmath> |
19 | | #include <stdlib.h> |
20 | | #include <utility> |
21 | | |
22 | | U_NAMESPACE_BEGIN |
23 | | namespace units { |
24 | | |
25 | 0 | void U_I18N_API Factor::multiplyBy(const Factor &rhs) { |
26 | 0 | factorNum *= rhs.factorNum; |
27 | 0 | factorDen *= rhs.factorDen; |
28 | 0 | for (int i = 0; i < CONSTANTS_COUNT; i++) { |
29 | 0 | constantExponents[i] += rhs.constantExponents[i]; |
30 | 0 | } |
31 | | |
32 | | // NOTE |
33 | | // We need the offset when the source and the target are simple units. e.g. the source is |
34 | | // celsius and the target is Fahrenheit. Therefore, we just keep the value using `std::max`. |
35 | 0 | offset = std::max(rhs.offset, offset); |
36 | 0 | } |
37 | | |
38 | 0 | void U_I18N_API Factor::divideBy(const Factor &rhs) { |
39 | 0 | factorNum *= rhs.factorDen; |
40 | 0 | factorDen *= rhs.factorNum; |
41 | 0 | for (int i = 0; i < CONSTANTS_COUNT; i++) { |
42 | 0 | constantExponents[i] -= rhs.constantExponents[i]; |
43 | 0 | } |
44 | | |
45 | | // NOTE |
46 | | // We need the offset when the source and the target are simple units. e.g. the source is |
47 | | // celsius and the target is Fahrenheit. Therefore, we just keep the value using `std::max`. |
48 | 0 | offset = std::max(rhs.offset, offset); |
49 | 0 | } |
50 | | |
51 | 0 | void U_I18N_API Factor::power(int32_t power) { |
52 | | // multiply all the constant by the power. |
53 | 0 | for (int i = 0; i < CONSTANTS_COUNT; i++) { |
54 | 0 | constantExponents[i] *= power; |
55 | 0 | } |
56 | |
|
57 | 0 | bool shouldFlip = power < 0; // This means that after applying the absolute power, we should flip |
58 | | // the Numerator and Denominator. |
59 | |
|
60 | 0 | factorNum = std::pow(factorNum, std::abs(power)); |
61 | 0 | factorDen = std::pow(factorDen, std::abs(power)); |
62 | |
|
63 | 0 | if (shouldFlip) { |
64 | | // Flip Numerator and Denominator. |
65 | 0 | std::swap(factorNum, factorDen); |
66 | 0 | } |
67 | 0 | } |
68 | | |
69 | 0 | void U_I18N_API Factor::applyPrefix(UMeasurePrefix unitPrefix) { |
70 | 0 | if (unitPrefix == UMeasurePrefix::UMEASURE_PREFIX_ONE) { |
71 | | // No need to do anything |
72 | 0 | return; |
73 | 0 | } |
74 | | |
75 | 0 | int32_t prefixPower = umeas_getPrefixPower(unitPrefix); |
76 | 0 | double prefixFactor = std::pow((double)umeas_getPrefixBase(unitPrefix), (double)std::abs(prefixPower)); |
77 | 0 | if (prefixPower >= 0) { |
78 | 0 | factorNum *= prefixFactor; |
79 | 0 | } else { |
80 | 0 | factorDen *= prefixFactor; |
81 | 0 | } |
82 | 0 | } |
83 | | |
84 | 0 | void U_I18N_API Factor::substituteConstants() { |
85 | 0 | for (int i = 0; i < CONSTANTS_COUNT; i++) { |
86 | 0 | if (this->constantExponents[i] == 0) { |
87 | 0 | continue; |
88 | 0 | } |
89 | | |
90 | 0 | auto absPower = std::abs(this->constantExponents[i]); |
91 | 0 | Signum powerSig = this->constantExponents[i] < 0 ? Signum::NEGATIVE : Signum::POSITIVE; |
92 | 0 | double absConstantValue = std::pow(constantsValues[i], absPower); |
93 | |
|
94 | 0 | if (powerSig == Signum::NEGATIVE) { |
95 | 0 | this->factorDen *= absConstantValue; |
96 | 0 | } else { |
97 | 0 | this->factorNum *= absConstantValue; |
98 | 0 | } |
99 | |
|
100 | 0 | this->constantExponents[i] = 0; |
101 | 0 | } |
102 | 0 | } |
103 | | |
104 | | namespace { |
105 | | |
106 | | /* Helpers */ |
107 | | |
108 | | using icu::double_conversion::StringToDoubleConverter; |
109 | | |
110 | | // TODO: Make this a shared-utility function. |
111 | | // Returns `double` from a scientific number(i.e. "1", "2.01" or "3.09E+4") |
112 | 0 | double strToDouble(StringPiece strNum, UErrorCode &status) { |
113 | | // We are processing well-formed input, so we don't need any special options to |
114 | | // StringToDoubleConverter. |
115 | 0 | StringToDoubleConverter converter(0, 0, 0, "", ""); |
116 | 0 | int32_t count; |
117 | 0 | double result = converter.StringToDouble(strNum.data(), strNum.length(), &count); |
118 | 0 | if (count != strNum.length()) { |
119 | 0 | status = U_INVALID_FORMAT_ERROR; |
120 | 0 | } |
121 | |
|
122 | 0 | return result; |
123 | 0 | } |
124 | | |
125 | | // Returns `double` from a scientific number that could has a division sign (i.e. "1", "2.01", "3.09E+4" |
126 | | // or "2E+2/3") |
127 | 0 | double strHasDivideSignToDouble(StringPiece strWithDivide, UErrorCode &status) { |
128 | 0 | int divisionSignInd = -1; |
129 | 0 | for (int i = 0, n = strWithDivide.length(); i < n; ++i) { |
130 | 0 | if (strWithDivide.data()[i] == '/') { |
131 | 0 | divisionSignInd = i; |
132 | 0 | break; |
133 | 0 | } |
134 | 0 | } |
135 | |
|
136 | 0 | if (divisionSignInd >= 0) { |
137 | 0 | return strToDouble(strWithDivide.substr(0, divisionSignInd), status) / |
138 | 0 | strToDouble(strWithDivide.substr(divisionSignInd + 1), status); |
139 | 0 | } |
140 | | |
141 | 0 | return strToDouble(strWithDivide, status); |
142 | 0 | } |
143 | | |
144 | | /* |
145 | | Adds single factor to a `Factor` object. Single factor means "23^2", "23.3333", "ft2m^3" ...etc. |
146 | | However, complex factor are not included, such as "ft2m^3*200/3" |
147 | | */ |
148 | 0 | void addFactorElement(Factor &factor, StringPiece elementStr, Signum signum, UErrorCode &status) { |
149 | 0 | StringPiece baseStr; |
150 | 0 | StringPiece powerStr; |
151 | 0 | int32_t power = |
152 | 0 | 1; // In case the power is not written, then, the power is equal 1 ==> `ft2m^1` == `ft2m` |
153 | | |
154 | | // Search for the power part |
155 | 0 | int32_t powerInd = -1; |
156 | 0 | for (int32_t i = 0, n = elementStr.length(); i < n; ++i) { |
157 | 0 | if (elementStr.data()[i] == '^') { |
158 | 0 | powerInd = i; |
159 | 0 | break; |
160 | 0 | } |
161 | 0 | } |
162 | |
|
163 | 0 | if (powerInd > -1) { |
164 | | // There is power |
165 | 0 | baseStr = elementStr.substr(0, powerInd); |
166 | 0 | powerStr = elementStr.substr(powerInd + 1); |
167 | |
|
168 | 0 | power = static_cast<int32_t>(strToDouble(powerStr, status)); |
169 | 0 | } else { |
170 | 0 | baseStr = elementStr; |
171 | 0 | } |
172 | |
|
173 | 0 | addSingleFactorConstant(baseStr, power, signum, factor, status); |
174 | 0 | } |
175 | | |
176 | | /* |
177 | | * Extracts `Factor` from a complete string factor. e.g. "ft2m^3*1007/cup2m3*3" |
178 | | */ |
179 | 0 | Factor extractFactorConversions(StringPiece stringFactor, UErrorCode &status) { |
180 | 0 | Factor result; |
181 | 0 | Signum signum = Signum::POSITIVE; |
182 | 0 | auto factorData = stringFactor.data(); |
183 | 0 | for (int32_t i = 0, start = 0, n = stringFactor.length(); i < n; i++) { |
184 | 0 | if (factorData[i] == '*' || factorData[i] == '/') { |
185 | 0 | StringPiece factorElement = stringFactor.substr(start, i - start); |
186 | 0 | addFactorElement(result, factorElement, signum, status); |
187 | |
|
188 | 0 | start = i + 1; // Set `start` to point to the start of the new element. |
189 | 0 | } else if (i == n - 1) { |
190 | | // Last element |
191 | 0 | addFactorElement(result, stringFactor.substr(start, i + 1), signum, status); |
192 | 0 | } |
193 | |
|
194 | 0 | if (factorData[i] == '/') { |
195 | 0 | signum = Signum::NEGATIVE; // Change the signum because we reached the Denominator. |
196 | 0 | } |
197 | 0 | } |
198 | |
|
199 | 0 | return result; |
200 | 0 | } |
201 | | |
202 | | // Load factor for a single source |
203 | 0 | Factor loadSingleFactor(StringPiece source, const ConversionRates &ratesInfo, UErrorCode &status) { |
204 | 0 | const auto conversionUnit = ratesInfo.extractConversionInfo(source, status); |
205 | 0 | if (U_FAILURE(status)) return Factor(); |
206 | 0 | if (conversionUnit == nullptr) { |
207 | 0 | status = U_INTERNAL_PROGRAM_ERROR; |
208 | 0 | return Factor(); |
209 | 0 | } |
210 | | |
211 | 0 | Factor result = extractFactorConversions(conversionUnit->factor.toStringPiece(), status); |
212 | 0 | result.offset = strHasDivideSignToDouble(conversionUnit->offset.toStringPiece(), status); |
213 | |
|
214 | 0 | return result; |
215 | 0 | } |
216 | | |
217 | | // Load Factor of a compound source unit. |
218 | | // In ICU4J, this is a pair of ConversionRates.getFactorToBase() functions. |
219 | | Factor loadCompoundFactor(const MeasureUnitImpl &source, const ConversionRates &ratesInfo, |
220 | 0 | UErrorCode &status) { |
221 | |
|
222 | 0 | Factor result; |
223 | 0 | for (int32_t i = 0, n = source.singleUnits.length(); i < n; i++) { |
224 | 0 | SingleUnitImpl singleUnit = *source.singleUnits[i]; |
225 | |
|
226 | 0 | Factor singleFactor = loadSingleFactor(singleUnit.getSimpleUnitID(), ratesInfo, status); |
227 | 0 | if (U_FAILURE(status)) return result; |
228 | | |
229 | | // Prefix before power, because: |
230 | | // - square-kilometer to square-meter: (1000)^2 |
231 | | // - square-kilometer to square-foot (approximate): (3.28*1000)^2 |
232 | 0 | singleFactor.applyPrefix(singleUnit.unitPrefix); |
233 | | |
234 | | // Apply the power of the `dimensionality` |
235 | 0 | singleFactor.power(singleUnit.dimensionality); |
236 | |
|
237 | 0 | result.multiplyBy(singleFactor); |
238 | 0 | } |
239 | | |
240 | 0 | return result; |
241 | 0 | } |
242 | | |
243 | | /** |
244 | | * Checks if the source unit and the target unit are simple. For example celsius or fahrenheit. But not |
245 | | * square-celsius or square-fahrenheit. |
246 | | * |
247 | | * NOTE: |
248 | | * Empty unit means simple unit. |
249 | | * |
250 | | * In ICU4J, this is ConversionRates.checkSimpleUnit(). |
251 | | */ |
252 | 0 | UBool checkSimpleUnit(const MeasureUnitImpl &unit, UErrorCode &status) { |
253 | 0 | if (U_FAILURE(status)) return false; |
254 | | |
255 | 0 | if (unit.complexity != UMEASURE_UNIT_SINGLE) { |
256 | 0 | return false; |
257 | 0 | } |
258 | 0 | if (unit.singleUnits.length() == 0) { |
259 | | // Empty units means simple unit. |
260 | 0 | return true; |
261 | 0 | } |
262 | | |
263 | 0 | auto singleUnit = *(unit.singleUnits[0]); |
264 | |
|
265 | 0 | if (singleUnit.dimensionality != 1 || singleUnit.unitPrefix != UMEASURE_PREFIX_ONE) { |
266 | 0 | return false; |
267 | 0 | } |
268 | | |
269 | 0 | return true; |
270 | 0 | } |
271 | | |
272 | | /** |
273 | | * Extract conversion rate from `source` to `target` |
274 | | */ |
275 | | // In ICU4J, this function is partially inlined in the UnitsConverter constructor. |
276 | | void loadConversionRate(ConversionRate &conversionRate, const MeasureUnitImpl &source, |
277 | | const MeasureUnitImpl &target, Convertibility unitsState, |
278 | 0 | const ConversionRates &ratesInfo, UErrorCode &status) { |
279 | | // Represents the conversion factor from the source to the target. |
280 | 0 | Factor finalFactor; |
281 | | |
282 | | // Represents the conversion factor from the source to the base unit that specified in the conversion |
283 | | // data which is considered as the root of the source and the target. |
284 | 0 | Factor sourceToBase = loadCompoundFactor(source, ratesInfo, status); |
285 | 0 | Factor targetToBase = loadCompoundFactor(target, ratesInfo, status); |
286 | | |
287 | | // Merger Factors |
288 | 0 | finalFactor.multiplyBy(sourceToBase); |
289 | 0 | if (unitsState == Convertibility::CONVERTIBLE) { |
290 | 0 | finalFactor.divideBy(targetToBase); |
291 | 0 | } else if (unitsState == Convertibility::RECIPROCAL) { |
292 | 0 | finalFactor.multiplyBy(targetToBase); |
293 | 0 | } else { |
294 | 0 | status = UErrorCode::U_ARGUMENT_TYPE_MISMATCH; |
295 | 0 | return; |
296 | 0 | } |
297 | | |
298 | 0 | finalFactor.substituteConstants(); |
299 | |
|
300 | 0 | conversionRate.factorNum = finalFactor.factorNum; |
301 | 0 | conversionRate.factorDen = finalFactor.factorDen; |
302 | | |
303 | | // This code corresponds to ICU4J's ConversionRates.getOffset(). |
304 | | // In case of simple units (such as: celsius or fahrenheit), offsets are considered. |
305 | 0 | if (checkSimpleUnit(source, status) && checkSimpleUnit(target, status)) { |
306 | 0 | conversionRate.sourceOffset = |
307 | 0 | sourceToBase.offset * sourceToBase.factorDen / sourceToBase.factorNum; |
308 | 0 | conversionRate.targetOffset = |
309 | 0 | targetToBase.offset * targetToBase.factorDen / targetToBase.factorNum; |
310 | 0 | } |
311 | | // TODO(icu-units#127): should we consider failure if there's an offset for |
312 | | // a not-simple-unit? What about kilokelvin / kilocelsius? |
313 | |
|
314 | 0 | conversionRate.reciprocal = unitsState == Convertibility::RECIPROCAL; |
315 | 0 | } |
316 | | |
317 | | struct UnitIndexAndDimension : UMemory { |
318 | | int32_t index = 0; |
319 | | int32_t dimensionality = 0; |
320 | | |
321 | 0 | UnitIndexAndDimension(const SingleUnitImpl &singleUnit, int32_t multiplier) { |
322 | 0 | index = singleUnit.index; |
323 | 0 | dimensionality = singleUnit.dimensionality * multiplier; |
324 | 0 | } |
325 | | }; |
326 | | |
327 | | void mergeSingleUnitWithDimension(MaybeStackVector<UnitIndexAndDimension> &unitIndicesWithDimension, |
328 | 0 | const SingleUnitImpl &shouldBeMerged, int32_t multiplier) { |
329 | 0 | for (int32_t i = 0; i < unitIndicesWithDimension.length(); i++) { |
330 | 0 | auto &unitWithIndex = *unitIndicesWithDimension[i]; |
331 | 0 | if (unitWithIndex.index == shouldBeMerged.index) { |
332 | 0 | unitWithIndex.dimensionality += shouldBeMerged.dimensionality * multiplier; |
333 | 0 | return; |
334 | 0 | } |
335 | 0 | } |
336 | | |
337 | 0 | unitIndicesWithDimension.emplaceBack(shouldBeMerged, multiplier); |
338 | 0 | } |
339 | | |
340 | | void mergeUnitsAndDimensions(MaybeStackVector<UnitIndexAndDimension> &unitIndicesWithDimension, |
341 | 0 | const MeasureUnitImpl &shouldBeMerged, int32_t multiplier) { |
342 | 0 | for (int32_t unit_i = 0; unit_i < shouldBeMerged.singleUnits.length(); unit_i++) { |
343 | 0 | auto singleUnit = *shouldBeMerged.singleUnits[unit_i]; |
344 | 0 | mergeSingleUnitWithDimension(unitIndicesWithDimension, singleUnit, multiplier); |
345 | 0 | } |
346 | 0 | } |
347 | | |
348 | 0 | UBool checkAllDimensionsAreZeros(const MaybeStackVector<UnitIndexAndDimension> &dimensionVector) { |
349 | 0 | for (int32_t i = 0; i < dimensionVector.length(); i++) { |
350 | 0 | if (dimensionVector[i]->dimensionality != 0) { |
351 | 0 | return false; |
352 | 0 | } |
353 | 0 | } |
354 | | |
355 | 0 | return true; |
356 | 0 | } |
357 | | |
358 | | } // namespace |
359 | | |
360 | | // Conceptually, this modifies factor: factor *= baseStr^(signum*power). |
361 | | // |
362 | | // baseStr must be a known constant or a value that strToDouble() is able to |
363 | | // parse. |
364 | | void U_I18N_API addSingleFactorConstant(StringPiece baseStr, int32_t power, Signum signum, |
365 | 0 | Factor &factor, UErrorCode &status) { |
366 | 0 | if (baseStr == "ft_to_m") { |
367 | 0 | factor.constantExponents[CONSTANT_FT2M] += power * signum; |
368 | 0 | } else if (baseStr == "ft2_to_m2") { |
369 | 0 | factor.constantExponents[CONSTANT_FT2M] += 2 * power * signum; |
370 | 0 | } else if (baseStr == "ft3_to_m3") { |
371 | 0 | factor.constantExponents[CONSTANT_FT2M] += 3 * power * signum; |
372 | 0 | } else if (baseStr == "in3_to_m3") { |
373 | 0 | factor.constantExponents[CONSTANT_FT2M] += 3 * power * signum; |
374 | 0 | factor.factorDen *= 12 * 12 * 12; |
375 | 0 | } else if (baseStr == "gal_to_m3") { |
376 | 0 | factor.factorNum *= 231; |
377 | 0 | factor.constantExponents[CONSTANT_FT2M] += 3 * power * signum; |
378 | 0 | factor.factorDen *= 12 * 12 * 12; |
379 | 0 | } else if (baseStr == "gal_imp_to_m3") { |
380 | 0 | factor.constantExponents[CONSTANT_GAL_IMP2M3] += power * signum; |
381 | 0 | } else if (baseStr == "G") { |
382 | 0 | factor.constantExponents[CONSTANT_G] += power * signum; |
383 | 0 | } else if (baseStr == "gravity") { |
384 | 0 | factor.constantExponents[CONSTANT_GRAVITY] += power * signum; |
385 | 0 | } else if (baseStr == "lb_to_kg") { |
386 | 0 | factor.constantExponents[CONSTANT_LB2KG] += power * signum; |
387 | 0 | } else if (baseStr == "glucose_molar_mass") { |
388 | 0 | factor.constantExponents[CONSTANT_GLUCOSE_MOLAR_MASS] += power * signum; |
389 | 0 | } else if (baseStr == "item_per_mole") { |
390 | 0 | factor.constantExponents[CONSTANT_ITEM_PER_MOLE] += power * signum; |
391 | 0 | } else if (baseStr == "PI") { |
392 | 0 | factor.constantExponents[CONSTANT_PI] += power * signum; |
393 | 0 | } else { |
394 | 0 | if (signum == Signum::NEGATIVE) { |
395 | 0 | factor.factorDen *= std::pow(strToDouble(baseStr, status), power); |
396 | 0 | } else { |
397 | 0 | factor.factorNum *= std::pow(strToDouble(baseStr, status), power); |
398 | 0 | } |
399 | 0 | } |
400 | 0 | } |
401 | | |
402 | | /** |
403 | | * Extracts the compound base unit of a compound unit (`source`). For example, if the source unit is |
404 | | * `square-mile-per-hour`, the compound base unit will be `square-meter-per-second` |
405 | | */ |
406 | | MeasureUnitImpl U_I18N_API extractCompoundBaseUnit(const MeasureUnitImpl &source, |
407 | | const ConversionRates &conversionRates, |
408 | 0 | UErrorCode &status) { |
409 | |
|
410 | 0 | MeasureUnitImpl result; |
411 | 0 | if (U_FAILURE(status)) return result; |
412 | | |
413 | 0 | const auto &singleUnits = source.singleUnits; |
414 | 0 | for (int i = 0, count = singleUnits.length(); i < count; ++i) { |
415 | 0 | const auto &singleUnit = *singleUnits[i]; |
416 | | // Extract `ConversionRateInfo` using the absolute unit. For example: in case of `square-meter`, |
417 | | // we will use `meter` |
418 | 0 | const auto rateInfo = |
419 | 0 | conversionRates.extractConversionInfo(singleUnit.getSimpleUnitID(), status); |
420 | 0 | if (U_FAILURE(status)) { |
421 | 0 | return result; |
422 | 0 | } |
423 | 0 | if (rateInfo == nullptr) { |
424 | 0 | status = U_INTERNAL_PROGRAM_ERROR; |
425 | 0 | return result; |
426 | 0 | } |
427 | | |
428 | | // Multiply the power of the singleUnit by the power of the baseUnit. For example, square-hectare |
429 | | // must be pow4-meter. (NOTE: hectare --> square-meter) |
430 | 0 | auto baseUnits = |
431 | 0 | MeasureUnitImpl::forIdentifier(rateInfo->baseUnit.toStringPiece(), status).singleUnits; |
432 | 0 | for (int32_t i = 0, baseUnitsCount = baseUnits.length(); i < baseUnitsCount; i++) { |
433 | 0 | baseUnits[i]->dimensionality *= singleUnit.dimensionality; |
434 | | // TODO: Deal with SI-prefix |
435 | 0 | result.appendSingleUnit(*baseUnits[i], status); |
436 | |
|
437 | 0 | if (U_FAILURE(status)) { |
438 | 0 | return result; |
439 | 0 | } |
440 | 0 | } |
441 | 0 | } |
442 | | |
443 | 0 | return result; |
444 | 0 | } |
445 | | |
446 | | /** |
447 | | * Determine the convertibility between `source` and `target`. |
448 | | * For example: |
449 | | * `meter` and `foot` are `CONVERTIBLE`. |
450 | | * `meter-per-second` and `second-per-meter` are `RECIPROCAL`. |
451 | | * `meter` and `pound` are `UNCONVERTIBLE`. |
452 | | * |
453 | | * NOTE: |
454 | | * Only works with SINGLE and COMPOUND units. If one of the units is a |
455 | | * MIXED unit, an error will occur. For more information, see UMeasureUnitComplexity. |
456 | | */ |
457 | | Convertibility U_I18N_API extractConvertibility(const MeasureUnitImpl &source, |
458 | | const MeasureUnitImpl &target, |
459 | | const ConversionRates &conversionRates, |
460 | 0 | UErrorCode &status) { |
461 | |
|
462 | 0 | if (source.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED || |
463 | 0 | target.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) { |
464 | 0 | status = U_INTERNAL_PROGRAM_ERROR; |
465 | 0 | return UNCONVERTIBLE; |
466 | 0 | } |
467 | | |
468 | 0 | MeasureUnitImpl sourceBaseUnit = extractCompoundBaseUnit(source, conversionRates, status); |
469 | 0 | MeasureUnitImpl targetBaseUnit = extractCompoundBaseUnit(target, conversionRates, status); |
470 | 0 | if (U_FAILURE(status)) return UNCONVERTIBLE; |
471 | | |
472 | 0 | MaybeStackVector<UnitIndexAndDimension> convertible; |
473 | 0 | MaybeStackVector<UnitIndexAndDimension> reciprocal; |
474 | |
|
475 | 0 | mergeUnitsAndDimensions(convertible, sourceBaseUnit, 1); |
476 | 0 | mergeUnitsAndDimensions(reciprocal, sourceBaseUnit, 1); |
477 | |
|
478 | 0 | mergeUnitsAndDimensions(convertible, targetBaseUnit, -1); |
479 | 0 | mergeUnitsAndDimensions(reciprocal, targetBaseUnit, 1); |
480 | |
|
481 | 0 | if (checkAllDimensionsAreZeros(convertible)) { |
482 | 0 | return CONVERTIBLE; |
483 | 0 | } |
484 | | |
485 | 0 | if (checkAllDimensionsAreZeros(reciprocal)) { |
486 | 0 | return RECIPROCAL; |
487 | 0 | } |
488 | | |
489 | 0 | return UNCONVERTIBLE; |
490 | 0 | } |
491 | | |
492 | | UnitsConverter::UnitsConverter(const MeasureUnitImpl &source, const MeasureUnitImpl &target, |
493 | | const ConversionRates &ratesInfo, UErrorCode &status) |
494 | 0 | : conversionRate_(source.copy(status), target.copy(status)) { |
495 | 0 | this->init(ratesInfo, status); |
496 | 0 | } |
497 | | |
498 | | UnitsConverter::UnitsConverter(StringPiece sourceIdentifier, StringPiece targetIdentifier, |
499 | | UErrorCode &status) |
500 | 0 | : conversionRate_(MeasureUnitImpl::forIdentifier(sourceIdentifier, status), |
501 | 0 | MeasureUnitImpl::forIdentifier(targetIdentifier, status)) { |
502 | 0 | if (U_FAILURE(status)) { |
503 | 0 | return; |
504 | 0 | } |
505 | | |
506 | 0 | ConversionRates ratesInfo(status); |
507 | 0 | this->init(ratesInfo, status); |
508 | 0 | } |
509 | | |
510 | 0 | void UnitsConverter::init(const ConversionRates &ratesInfo, UErrorCode &status) { |
511 | 0 | if (U_FAILURE(status)) { |
512 | 0 | return; |
513 | 0 | } |
514 | | |
515 | 0 | if (this->conversionRate_.source.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED || |
516 | 0 | this->conversionRate_.target.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) { |
517 | 0 | status = U_INTERNAL_PROGRAM_ERROR; |
518 | 0 | return; |
519 | 0 | } |
520 | | |
521 | 0 | Convertibility unitsState = extractConvertibility(this->conversionRate_.source, |
522 | 0 | this->conversionRate_.target, ratesInfo, status); |
523 | 0 | if (U_FAILURE(status)) return; |
524 | 0 | if (unitsState == Convertibility::UNCONVERTIBLE) { |
525 | 0 | status = U_INTERNAL_PROGRAM_ERROR; |
526 | 0 | return; |
527 | 0 | } |
528 | | |
529 | 0 | loadConversionRate(conversionRate_, conversionRate_.source, conversionRate_.target, unitsState, |
530 | 0 | ratesInfo, status); |
531 | | |
532 | 0 | } |
533 | | |
534 | | int32_t UnitsConverter::compareTwoUnits(const MeasureUnitImpl &firstUnit, |
535 | | const MeasureUnitImpl &secondUnit, |
536 | 0 | const ConversionRates &ratesInfo, UErrorCode &status) { |
537 | 0 | if (U_FAILURE(status)) { |
538 | 0 | return 0; |
539 | 0 | } |
540 | | |
541 | 0 | if (firstUnit.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED || |
542 | 0 | secondUnit.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) { |
543 | 0 | status = U_INTERNAL_PROGRAM_ERROR; |
544 | 0 | return 0; |
545 | 0 | } |
546 | | |
547 | 0 | Convertibility unitsState = extractConvertibility(firstUnit, secondUnit, ratesInfo, status); |
548 | 0 | if (U_FAILURE(status)) { |
549 | 0 | return 0; |
550 | 0 | } |
551 | | |
552 | 0 | if (unitsState == Convertibility::UNCONVERTIBLE || unitsState == Convertibility::RECIPROCAL) { |
553 | 0 | status = U_INTERNAL_PROGRAM_ERROR; |
554 | 0 | return 0; |
555 | 0 | } |
556 | | |
557 | | // Represents the conversion factor from the firstUnit to the base |
558 | | // unit that specified in the conversion data which is considered as |
559 | | // the root of the firstUnit and the secondUnit. |
560 | 0 | Factor firstUnitToBase = loadCompoundFactor(firstUnit, ratesInfo, status); |
561 | 0 | Factor secondUnitToBase = loadCompoundFactor(secondUnit, ratesInfo, status); |
562 | |
|
563 | 0 | firstUnitToBase.substituteConstants(); |
564 | 0 | secondUnitToBase.substituteConstants(); |
565 | |
|
566 | 0 | double firstUnitToBaseConversionRate = firstUnitToBase.factorNum / firstUnitToBase.factorDen; |
567 | 0 | double secondUnitToBaseConversionRate = secondUnitToBase.factorNum / secondUnitToBase.factorDen; |
568 | |
|
569 | 0 | double diff = firstUnitToBaseConversionRate - secondUnitToBaseConversionRate; |
570 | 0 | if (diff > 0) { |
571 | 0 | return 1; |
572 | 0 | } |
573 | | |
574 | 0 | if (diff < 0) { |
575 | 0 | return -1; |
576 | 0 | } |
577 | | |
578 | 0 | return 0; |
579 | 0 | } |
580 | | |
581 | 0 | double UnitsConverter::convert(double inputValue) const { |
582 | 0 | double result = |
583 | 0 | inputValue + conversionRate_.sourceOffset; // Reset the input to the target zero index. |
584 | | // Convert the quantity to from the source scale to the target scale. |
585 | 0 | result *= conversionRate_.factorNum / conversionRate_.factorDen; |
586 | |
|
587 | 0 | result -= conversionRate_.targetOffset; // Set the result to its index. |
588 | |
|
589 | 0 | if (conversionRate_.reciprocal) { |
590 | 0 | if (result == 0) { |
591 | | // TODO: demonstrate the resulting behaviour in tests... and figure |
592 | | // out desired behaviour. (Theoretical result should be infinity, |
593 | | // not 0.) |
594 | 0 | return 0.0; |
595 | 0 | } |
596 | 0 | result = 1.0 / result; |
597 | 0 | } |
598 | | |
599 | 0 | return result; |
600 | 0 | } |
601 | | |
602 | 0 | double UnitsConverter::convertInverse(double inputValue) const { |
603 | 0 | double result = inputValue; |
604 | 0 | if (conversionRate_.reciprocal) { |
605 | 0 | if (result == 0) { |
606 | | // TODO: demonstrate the resulting behaviour in tests... and figure |
607 | | // out desired behaviour. (Theoretical result should be infinity, |
608 | | // not 0.) |
609 | 0 | return 0.0; |
610 | 0 | } |
611 | 0 | result = 1.0 / result; |
612 | 0 | } |
613 | 0 | result += conversionRate_.targetOffset; |
614 | 0 | result *= conversionRate_.factorDen / conversionRate_.factorNum; |
615 | 0 | result -= conversionRate_.sourceOffset; |
616 | 0 | return result; |
617 | 0 | } |
618 | | |
619 | 0 | ConversionInfo UnitsConverter::getConversionInfo() const { |
620 | 0 | ConversionInfo result; |
621 | 0 | result.conversionRate = conversionRate_.factorNum / conversionRate_.factorDen; |
622 | 0 | result.offset = |
623 | 0 | (conversionRate_.sourceOffset * (conversionRate_.factorNum / conversionRate_.factorDen)) - |
624 | 0 | conversionRate_.targetOffset; |
625 | 0 | result.reciprocal = conversionRate_.reciprocal; |
626 | |
|
627 | 0 | return result; |
628 | 0 | } |
629 | | |
630 | | } // namespace units |
631 | | U_NAMESPACE_END |
632 | | |
633 | | #endif /* #if !UCONFIG_NO_FORMATTING */ |