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 "putilimp.h"
13 #include "uassert.h"
14 #include "unicode/errorcode.h"
15 #include "unicode/localpointer.h"
16 #include "unicode/stringpiece.h"
17 #include "units_converter.h"
18 #include <algorithm>
19 #include <cmath>
20 #include <stdlib.h>
21 #include <utility>
22
23 U_NAMESPACE_BEGIN
24 namespace units {
25
multiplyBy(const Factor & rhs)26 void U_I18N_API Factor::multiplyBy(const Factor &rhs) {
27 factorNum *= rhs.factorNum;
28 factorDen *= rhs.factorDen;
29 for (int i = 0; i < CONSTANTS_COUNT; i++) {
30 constantExponents[i] += rhs.constantExponents[i];
31 }
32
33 // NOTE
34 // We need the offset when the source and the target are simple units. e.g. the source is
35 // celsius and the target is Fahrenheit. Therefore, we just keep the value using `std::max`.
36 offset = std::max(rhs.offset, offset);
37 }
38
divideBy(const Factor & rhs)39 void U_I18N_API Factor::divideBy(const Factor &rhs) {
40 factorNum *= rhs.factorDen;
41 factorDen *= rhs.factorNum;
42 for (int i = 0; i < CONSTANTS_COUNT; i++) {
43 constantExponents[i] -= rhs.constantExponents[i];
44 }
45
46 // NOTE
47 // We need the offset when the source and the target are simple units. e.g. the source is
48 // celsius and the target is Fahrenheit. Therefore, we just keep the value using `std::max`.
49 offset = std::max(rhs.offset, offset);
50 }
51
power(int32_t power)52 void U_I18N_API Factor::power(int32_t power) {
53 // multiply all the constant by the power.
54 for (int i = 0; i < CONSTANTS_COUNT; i++) {
55 constantExponents[i] *= power;
56 }
57
58 bool shouldFlip = power < 0; // This means that after applying the absolute power, we should flip
59 // the Numerator and Denominator.
60
61 factorNum = std::pow(factorNum, std::abs(power));
62 factorDen = std::pow(factorDen, std::abs(power));
63
64 if (shouldFlip) {
65 // Flip Numerator and Denominator.
66 std::swap(factorNum, factorDen);
67 }
68 }
69
applyPrefix(UMeasurePrefix unitPrefix)70 void U_I18N_API Factor::applyPrefix(UMeasurePrefix unitPrefix) {
71 if (unitPrefix == UMeasurePrefix::UMEASURE_PREFIX_ONE) {
72 // No need to do anything
73 return;
74 }
75
76 int32_t prefixPower = umeas_getPrefixPower(unitPrefix);
77 double prefixFactor = std::pow((double)umeas_getPrefixBase(unitPrefix), (double)std::abs(prefixPower));
78 if (prefixPower >= 0) {
79 factorNum *= prefixFactor;
80 } else {
81 factorDen *= prefixFactor;
82 }
83 }
84
substituteConstants()85 void U_I18N_API Factor::substituteConstants() {
86 for (int i = 0; i < CONSTANTS_COUNT; i++) {
87 if (this->constantExponents[i] == 0) {
88 continue;
89 }
90
91 auto absPower = std::abs(this->constantExponents[i]);
92 Signum powerSig = this->constantExponents[i] < 0 ? Signum::NEGATIVE : Signum::POSITIVE;
93 double absConstantValue = std::pow(constantsValues[i], absPower);
94
95 if (powerSig == Signum::NEGATIVE) {
96 this->factorDen *= absConstantValue;
97 } else {
98 this->factorNum *= absConstantValue;
99 }
100
101 this->constantExponents[i] = 0;
102 }
103 }
104
105 namespace {
106
107 /* Helpers */
108
109 using icu::double_conversion::StringToDoubleConverter;
110
111 // TODO: Make this a shared-utility function.
112 // Returns `double` from a scientific number(i.e. "1", "2.01" or "3.09E+4")
strToDouble(StringPiece strNum,UErrorCode & status)113 double strToDouble(StringPiece strNum, UErrorCode &status) {
114 // We are processing well-formed input, so we don't need any special options to
115 // StringToDoubleConverter.
116 StringToDoubleConverter converter(0, 0, 0, "", "");
117 int32_t count;
118 double result = converter.StringToDouble(strNum.data(), strNum.length(), &count);
119 if (count != strNum.length()) {
120 status = U_INVALID_FORMAT_ERROR;
121 }
122
123 return result;
124 }
125
126 // Returns `double` from a scientific number that could has a division sign (i.e. "1", "2.01", "3.09E+4"
127 // or "2E+2/3")
strHasDivideSignToDouble(StringPiece strWithDivide,UErrorCode & status)128 double strHasDivideSignToDouble(StringPiece strWithDivide, UErrorCode &status) {
129 int divisionSignInd = -1;
130 for (int i = 0, n = strWithDivide.length(); i < n; ++i) {
131 if (strWithDivide.data()[i] == '/') {
132 divisionSignInd = i;
133 break;
134 }
135 }
136
137 if (divisionSignInd >= 0) {
138 return strToDouble(strWithDivide.substr(0, divisionSignInd), status) /
139 strToDouble(strWithDivide.substr(divisionSignInd + 1), status);
140 }
141
142 return strToDouble(strWithDivide, status);
143 }
144
145 /*
146 Adds single factor to a `Factor` object. Single factor means "23^2", "23.3333", "ft2m^3" ...etc.
147 However, complex factor are not included, such as "ft2m^3*200/3"
148 */
addFactorElement(Factor & factor,StringPiece elementStr,Signum signum,UErrorCode & status)149 void addFactorElement(Factor &factor, StringPiece elementStr, Signum signum, UErrorCode &status) {
150 StringPiece baseStr;
151 StringPiece powerStr;
152 int32_t power =
153 1; // In case the power is not written, then, the power is equal 1 ==> `ft2m^1` == `ft2m`
154
155 // Search for the power part
156 int32_t powerInd = -1;
157 for (int32_t i = 0, n = elementStr.length(); i < n; ++i) {
158 if (elementStr.data()[i] == '^') {
159 powerInd = i;
160 break;
161 }
162 }
163
164 if (powerInd > -1) {
165 // There is power
166 baseStr = elementStr.substr(0, powerInd);
167 powerStr = elementStr.substr(powerInd + 1);
168
169 power = static_cast<int32_t>(strToDouble(powerStr, status));
170 } else {
171 baseStr = elementStr;
172 }
173
174 addSingleFactorConstant(baseStr, power, signum, factor, status);
175 }
176
177 /*
178 * Extracts `Factor` from a complete string factor. e.g. "ft2m^3*1007/cup2m3*3"
179 */
extractFactorConversions(StringPiece stringFactor,UErrorCode & status)180 Factor extractFactorConversions(StringPiece stringFactor, UErrorCode &status) {
181 Factor result;
182 Signum signum = Signum::POSITIVE;
183 const auto* factorData = stringFactor.data();
184 for (int32_t i = 0, start = 0, n = stringFactor.length(); i < n; i++) {
185 if (factorData[i] == '*' || factorData[i] == '/') {
186 StringPiece factorElement = stringFactor.substr(start, i - start);
187 addFactorElement(result, factorElement, signum, status);
188
189 start = i + 1; // Set `start` to point to the start of the new element.
190 } else if (i == n - 1) {
191 // Last element
192 addFactorElement(result, stringFactor.substr(start, i + 1), signum, status);
193 }
194
195 if (factorData[i] == '/') {
196 signum = Signum::NEGATIVE; // Change the signum because we reached the Denominator.
197 }
198 }
199
200 return result;
201 }
202
203 // Load factor for a single source
loadSingleFactor(StringPiece source,const ConversionRates & ratesInfo,UErrorCode & status)204 Factor loadSingleFactor(StringPiece source, const ConversionRates &ratesInfo, UErrorCode &status) {
205 const auto* const conversionUnit = ratesInfo.extractConversionInfo(source, status);
206 if (U_FAILURE(status)) return {};
207 if (conversionUnit == nullptr) {
208 status = U_INTERNAL_PROGRAM_ERROR;
209 return {};
210 }
211
212 Factor result = extractFactorConversions(conversionUnit->factor.toStringPiece(), status);
213 result.offset = strHasDivideSignToDouble(conversionUnit->offset.toStringPiece(), status);
214
215 return result;
216 }
217
218 // Load Factor of a compound source unit.
219 // In ICU4J, this is a pair of ConversionRates.getFactorToBase() functions.
loadCompoundFactor(const MeasureUnitImpl & source,const ConversionRates & ratesInfo,UErrorCode & status)220 Factor loadCompoundFactor(const MeasureUnitImpl &source, const ConversionRates &ratesInfo,
221 UErrorCode &status) {
222
223 Factor result;
224 for (int32_t i = 0, n = source.singleUnits.length(); i < n; i++) {
225 SingleUnitImpl singleUnit = *source.singleUnits[i];
226
227 Factor singleFactor = loadSingleFactor(singleUnit.getSimpleUnitID(), ratesInfo, status);
228 if (U_FAILURE(status)) return result;
229
230 // Prefix before power, because:
231 // - square-kilometer to square-meter: (1000)^2
232 // - square-kilometer to square-foot (approximate): (3.28*1000)^2
233 singleFactor.applyPrefix(singleUnit.unitPrefix);
234
235 // Apply the power of the `dimensionality`
236 singleFactor.power(singleUnit.dimensionality);
237
238 result.multiplyBy(singleFactor);
239 }
240
241 return result;
242 }
243
244 /**
245 * Checks if the source unit and the target unit are simple. For example celsius or fahrenheit. But not
246 * square-celsius or square-fahrenheit.
247 *
248 * NOTE:
249 * Empty unit means simple unit.
250 *
251 * In ICU4J, this is ConversionRates.checkSimpleUnit().
252 */
checkSimpleUnit(const MeasureUnitImpl & unit,UErrorCode & status)253 UBool checkSimpleUnit(const MeasureUnitImpl &unit, UErrorCode &status) {
254 if (U_FAILURE(status)) return false;
255
256 if (unit.complexity != UMEASURE_UNIT_SINGLE) {
257 return false;
258 }
259 if (unit.singleUnits.length() == 0) {
260 // Empty units means simple unit.
261 return true;
262 }
263
264 auto singleUnit = *(unit.singleUnits[0]);
265
266 if (singleUnit.dimensionality != 1 || singleUnit.unitPrefix != UMEASURE_PREFIX_ONE) {
267 return false;
268 }
269
270 return true;
271 }
272
273 // Map the MeasureUnitImpl for a simpleUnit to a SingleUnitImpl, then use that
274 // SingleUnitImpl's simpleUnitID to get the corresponding ConversionRateInfo;
275 // from that we get the specialMappingName (which may be empty if the simple unit
276 // converts to base using factor + offset instelad of a special mapping).
getSpecialMappingName(const MeasureUnitImpl & simpleUnit,const ConversionRates & ratesInfo,UErrorCode & status)277 CharString getSpecialMappingName(const MeasureUnitImpl &simpleUnit, const ConversionRates &ratesInfo,
278 UErrorCode &status) {
279 if (!checkSimpleUnit(simpleUnit, status)) {
280 return {};
281 }
282 SingleUnitImpl singleUnit = *simpleUnit.singleUnits[0];
283 const auto* const conversionUnit =
284 ratesInfo.extractConversionInfo(singleUnit.getSimpleUnitID(), status);
285 if (U_FAILURE(status)) {
286 return {};
287 }
288 if (conversionUnit == nullptr) {
289 status = U_INTERNAL_PROGRAM_ERROR;
290 return {};
291 }
292 CharString result;
293 result.copyFrom(conversionUnit->specialMappingName, status);
294 return result;
295 }
296
297 /**
298 * Extract conversion rate from `source` to `target`
299 */
300 // In ICU4J, this function is partially inlined in the UnitsConverter constructor.
301 // TODO ICU-22683: Consider splitting handling of special mappings into separate class
loadConversionRate(ConversionRate & conversionRate,const MeasureUnitImpl & source,const MeasureUnitImpl & target,Convertibility unitsState,const ConversionRates & ratesInfo,UErrorCode & status)302 void loadConversionRate(ConversionRate &conversionRate, const MeasureUnitImpl &source,
303 const MeasureUnitImpl &target, Convertibility unitsState,
304 const ConversionRates &ratesInfo, UErrorCode &status) {
305
306 conversionRate.specialSource = getSpecialMappingName(source, ratesInfo, status);
307 conversionRate.specialTarget = getSpecialMappingName(target, ratesInfo, status);
308
309 if (conversionRate.specialSource.isEmpty() && conversionRate.specialTarget.isEmpty()) {
310 // Represents the conversion factor from the source to the target.
311 Factor finalFactor;
312
313 // Represents the conversion factor from the source to the base unit that specified in the conversion
314 // data which is considered as the root of the source and the target.
315 Factor sourceToBase = loadCompoundFactor(source, ratesInfo, status);
316 Factor targetToBase = loadCompoundFactor(target, ratesInfo, status);
317
318 // Merger Factors
319 finalFactor.multiplyBy(sourceToBase);
320 if (unitsState == Convertibility::CONVERTIBLE) {
321 finalFactor.divideBy(targetToBase);
322 } else if (unitsState == Convertibility::RECIPROCAL) {
323 finalFactor.multiplyBy(targetToBase);
324 } else {
325 status = UErrorCode::U_ARGUMENT_TYPE_MISMATCH;
326 return;
327 }
328
329 finalFactor.substituteConstants();
330
331 conversionRate.factorNum = finalFactor.factorNum;
332 conversionRate.factorDen = finalFactor.factorDen;
333
334 // This code corresponds to ICU4J's ConversionRates.getOffset().
335 // In case of simple units (such as: celsius or fahrenheit), offsets are considered.
336 if (checkSimpleUnit(source, status) && checkSimpleUnit(target, status)) {
337 conversionRate.sourceOffset =
338 sourceToBase.offset * sourceToBase.factorDen / sourceToBase.factorNum;
339 conversionRate.targetOffset =
340 targetToBase.offset * targetToBase.factorDen / targetToBase.factorNum;
341 }
342 // TODO(icu-units#127): should we consider failure if there's an offset for
343 // a not-simple-unit? What about kilokelvin / kilocelsius?
344
345 conversionRate.reciprocal = unitsState == Convertibility::RECIPROCAL;
346 } else if (conversionRate.specialSource.isEmpty() || conversionRate.specialTarget.isEmpty()) {
347 // Still need to set factorNum/factorDen for either source to base or base to target
348 if (unitsState != Convertibility::CONVERTIBLE) {
349 status = UErrorCode::U_ARGUMENT_TYPE_MISMATCH;
350 return;
351 }
352 Factor finalFactor;
353 if (conversionRate.specialSource.isEmpty()) {
354 // factorNum/factorDen is for source to base only
355 finalFactor = loadCompoundFactor(source, ratesInfo, status);
356 } else {
357 // factorNum/factorDen is for base to target only
358 finalFactor = loadCompoundFactor(target, ratesInfo, status);
359 }
360 finalFactor.substituteConstants();
361 conversionRate.factorNum = finalFactor.factorNum;
362 conversionRate.factorDen = finalFactor.factorDen;
363 }
364 }
365
366 struct UnitIndexAndDimension : UMemory {
367 int32_t index = 0;
368 int32_t dimensionality = 0;
369
UnitIndexAndDimensionunits::__anon7239dd260111::UnitIndexAndDimension370 UnitIndexAndDimension(const SingleUnitImpl &singleUnit, int32_t multiplier) {
371 index = singleUnit.index;
372 dimensionality = singleUnit.dimensionality * multiplier;
373 }
374 };
375
mergeSingleUnitWithDimension(MaybeStackVector<UnitIndexAndDimension> & unitIndicesWithDimension,const SingleUnitImpl & shouldBeMerged,int32_t multiplier)376 void mergeSingleUnitWithDimension(MaybeStackVector<UnitIndexAndDimension> &unitIndicesWithDimension,
377 const SingleUnitImpl &shouldBeMerged, int32_t multiplier) {
378 for (int32_t i = 0; i < unitIndicesWithDimension.length(); i++) {
379 auto &unitWithIndex = *unitIndicesWithDimension[i];
380 if (unitWithIndex.index == shouldBeMerged.index) {
381 unitWithIndex.dimensionality += shouldBeMerged.dimensionality * multiplier;
382 return;
383 }
384 }
385
386 unitIndicesWithDimension.emplaceBack(shouldBeMerged, multiplier);
387 }
388
mergeUnitsAndDimensions(MaybeStackVector<UnitIndexAndDimension> & unitIndicesWithDimension,const MeasureUnitImpl & shouldBeMerged,int32_t multiplier)389 void mergeUnitsAndDimensions(MaybeStackVector<UnitIndexAndDimension> &unitIndicesWithDimension,
390 const MeasureUnitImpl &shouldBeMerged, int32_t multiplier) {
391 for (int32_t unit_i = 0; unit_i < shouldBeMerged.singleUnits.length(); unit_i++) {
392 auto singleUnit = *shouldBeMerged.singleUnits[unit_i];
393 mergeSingleUnitWithDimension(unitIndicesWithDimension, singleUnit, multiplier);
394 }
395 }
396
checkAllDimensionsAreZeros(const MaybeStackVector<UnitIndexAndDimension> & dimensionVector)397 UBool checkAllDimensionsAreZeros(const MaybeStackVector<UnitIndexAndDimension> &dimensionVector) {
398 for (int32_t i = 0; i < dimensionVector.length(); i++) {
399 if (dimensionVector[i]->dimensionality != 0) {
400 return false;
401 }
402 }
403
404 return true;
405 }
406
407 } // namespace
408
409 // Conceptually, this modifies factor: factor *= baseStr^(signum*power).
410 //
411 // baseStr must be a known constant or a value that strToDouble() is able to
412 // parse.
addSingleFactorConstant(StringPiece baseStr,int32_t power,Signum signum,Factor & factor,UErrorCode & status)413 void U_I18N_API addSingleFactorConstant(StringPiece baseStr, int32_t power, Signum signum,
414 Factor &factor, UErrorCode &status) {
415 if (baseStr == "ft_to_m") {
416 factor.constantExponents[CONSTANT_FT2M] += power * signum;
417 } else if (baseStr == "ft2_to_m2") {
418 factor.constantExponents[CONSTANT_FT2M] += 2 * power * signum;
419 } else if (baseStr == "ft3_to_m3") {
420 factor.constantExponents[CONSTANT_FT2M] += 3 * power * signum;
421 } else if (baseStr == "in3_to_m3") {
422 factor.constantExponents[CONSTANT_FT2M] += 3 * power * signum;
423 factor.factorDen *= std::pow(12 * 12 * 12, power * signum);
424 } else if (baseStr == "gal_to_m3") {
425 factor.constantExponents[CONSTANT_FT2M] += 3 * power * signum;
426 factor.factorNum *= std::pow(231, power * signum);
427 factor.factorDen *= std::pow(12 * 12 * 12, power * signum);
428 } else if (baseStr == "gal_imp_to_m3") {
429 factor.constantExponents[CONSTANT_GAL_IMP2M3] += power * signum;
430 } else if (baseStr == "G") {
431 factor.constantExponents[CONSTANT_G] += power * signum;
432 } else if (baseStr == "gravity") {
433 factor.constantExponents[CONSTANT_GRAVITY] += power * signum;
434 } else if (baseStr == "lb_to_kg") {
435 factor.constantExponents[CONSTANT_LB2KG] += power * signum;
436 } else if (baseStr == "glucose_molar_mass") {
437 factor.constantExponents[CONSTANT_GLUCOSE_MOLAR_MASS] += power * signum;
438 } else if (baseStr == "item_per_mole") {
439 factor.constantExponents[CONSTANT_ITEM_PER_MOLE] += power * signum;
440 } else if (baseStr == "meters_per_AU") {
441 factor.constantExponents[CONSTANT_METERS_PER_AU] += power * signum;
442 } else if (baseStr == "PI") {
443 factor.constantExponents[CONSTANT_PI] += power * signum;
444 } else if (baseStr == "sec_per_julian_year") {
445 factor.constantExponents[CONSTANT_SEC_PER_JULIAN_YEAR] += power * signum;
446 } else if (baseStr == "speed_of_light_meters_per_second") {
447 factor.constantExponents[CONSTANT_SPEED_OF_LIGHT_METERS_PER_SECOND] += power * signum;
448 } else if (baseStr == "sho_to_m3") {
449 factor.constantExponents[CONSTANT_SHO_TO_M3] += power * signum;
450 } else if (baseStr == "tsubo_to_m2") {
451 factor.constantExponents[CONSTANT_TSUBO_TO_M2] += power * signum;
452 } else if (baseStr == "shaku_to_m") {
453 factor.constantExponents[CONSTANT_SHAKU_TO_M] += power * signum;
454 } else if (baseStr == "AMU") {
455 factor.constantExponents[CONSTANT_AMU] += power * signum;
456 } else {
457 if (signum == Signum::NEGATIVE) {
458 factor.factorDen *= std::pow(strToDouble(baseStr, status), power);
459 } else {
460 factor.factorNum *= std::pow(strToDouble(baseStr, status), power);
461 }
462 }
463 }
464
465 /**
466 * Extracts the compound base unit of a compound unit (`source`). For example, if the source unit is
467 * `square-mile-per-hour`, the compound base unit will be `square-meter-per-second`
468 */
extractCompoundBaseUnit(const MeasureUnitImpl & source,const ConversionRates & conversionRates,UErrorCode & status)469 MeasureUnitImpl U_I18N_API extractCompoundBaseUnit(const MeasureUnitImpl &source,
470 const ConversionRates &conversionRates,
471 UErrorCode &status) {
472
473 MeasureUnitImpl result;
474 if (U_FAILURE(status)) return result;
475
476 const auto &singleUnits = source.singleUnits;
477 for (int i = 0, count = singleUnits.length(); i < count; ++i) {
478 const auto &singleUnit = *singleUnits[i];
479 // Extract `ConversionRateInfo` using the absolute unit. For example: in case of `square-meter`,
480 // we will use `meter`
481 const auto* const rateInfo =
482 conversionRates.extractConversionInfo(singleUnit.getSimpleUnitID(), status);
483 if (U_FAILURE(status)) {
484 return result;
485 }
486 if (rateInfo == nullptr) {
487 status = U_INTERNAL_PROGRAM_ERROR;
488 return result;
489 }
490
491 // Multiply the power of the singleUnit by the power of the baseUnit. For example, square-hectare
492 // must be pow4-meter. (NOTE: hectare --> square-meter)
493 auto baseUnits =
494 MeasureUnitImpl::forIdentifier(rateInfo->baseUnit.toStringPiece(), status).singleUnits;
495 for (int32_t i = 0, baseUnitsCount = baseUnits.length(); i < baseUnitsCount; i++) {
496 baseUnits[i]->dimensionality *= singleUnit.dimensionality;
497 // TODO: Deal with SI-prefix
498 result.appendSingleUnit(*baseUnits[i], status);
499
500 if (U_FAILURE(status)) {
501 return result;
502 }
503 }
504 }
505
506 return result;
507 }
508
509 /**
510 * Determine the convertibility between `source` and `target`.
511 * For example:
512 * `meter` and `foot` are `CONVERTIBLE`.
513 * `meter-per-second` and `second-per-meter` are `RECIPROCAL`.
514 * `meter` and `pound` are `UNCONVERTIBLE`.
515 *
516 * NOTE:
517 * Only works with SINGLE and COMPOUND units. If one of the units is a
518 * MIXED unit, an error will occur. For more information, see UMeasureUnitComplexity.
519 */
extractConvertibility(const MeasureUnitImpl & source,const MeasureUnitImpl & target,const ConversionRates & conversionRates,UErrorCode & status)520 Convertibility U_I18N_API extractConvertibility(const MeasureUnitImpl &source,
521 const MeasureUnitImpl &target,
522 const ConversionRates &conversionRates,
523 UErrorCode &status) {
524
525 if (source.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED ||
526 target.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) {
527 status = U_ARGUMENT_TYPE_MISMATCH;
528 return UNCONVERTIBLE;
529 }
530
531 MeasureUnitImpl sourceBaseUnit = extractCompoundBaseUnit(source, conversionRates, status);
532 MeasureUnitImpl targetBaseUnit = extractCompoundBaseUnit(target, conversionRates, status);
533 if (U_FAILURE(status)) return UNCONVERTIBLE;
534
535 MaybeStackVector<UnitIndexAndDimension> convertible;
536 MaybeStackVector<UnitIndexAndDimension> reciprocal;
537
538 mergeUnitsAndDimensions(convertible, sourceBaseUnit, 1);
539 mergeUnitsAndDimensions(reciprocal, sourceBaseUnit, 1);
540
541 mergeUnitsAndDimensions(convertible, targetBaseUnit, -1);
542 mergeUnitsAndDimensions(reciprocal, targetBaseUnit, 1);
543
544 if (checkAllDimensionsAreZeros(convertible)) {
545 return CONVERTIBLE;
546 }
547
548 if (checkAllDimensionsAreZeros(reciprocal)) {
549 return RECIPROCAL;
550 }
551
552 return UNCONVERTIBLE;
553 }
554
UnitsConverter(const MeasureUnitImpl & source,const MeasureUnitImpl & target,const ConversionRates & ratesInfo,UErrorCode & status)555 UnitsConverter::UnitsConverter(const MeasureUnitImpl &source, const MeasureUnitImpl &target,
556 const ConversionRates &ratesInfo, UErrorCode &status)
557 : conversionRate_(source.copy(status), target.copy(status)) {
558 this->init(ratesInfo, status);
559 }
560
UnitsConverter(StringPiece sourceIdentifier,StringPiece targetIdentifier,UErrorCode & status)561 UnitsConverter::UnitsConverter(StringPiece sourceIdentifier, StringPiece targetIdentifier,
562 UErrorCode &status)
563 : conversionRate_(MeasureUnitImpl::forIdentifier(sourceIdentifier, status),
564 MeasureUnitImpl::forIdentifier(targetIdentifier, status)) {
565 if (U_FAILURE(status)) {
566 return;
567 }
568
569 ConversionRates ratesInfo(status);
570 this->init(ratesInfo, status);
571 }
572
init(const ConversionRates & ratesInfo,UErrorCode & status)573 void UnitsConverter::init(const ConversionRates &ratesInfo, UErrorCode &status) {
574 if (U_FAILURE(status)) {
575 return;
576 }
577
578 if (this->conversionRate_.source.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED ||
579 this->conversionRate_.target.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) {
580 status = U_ARGUMENT_TYPE_MISMATCH;
581 return;
582 }
583
584 Convertibility unitsState = extractConvertibility(this->conversionRate_.source,
585 this->conversionRate_.target, ratesInfo, status);
586 if (U_FAILURE(status)) return;
587 if (unitsState == Convertibility::UNCONVERTIBLE) {
588 status = U_ARGUMENT_TYPE_MISMATCH;
589 return;
590 }
591
592 loadConversionRate(conversionRate_, conversionRate_.source, conversionRate_.target, unitsState,
593 ratesInfo, status);
594
595 }
596
compareTwoUnits(const MeasureUnitImpl & firstUnit,const MeasureUnitImpl & secondUnit,const ConversionRates & ratesInfo,UErrorCode & status)597 int32_t UnitsConverter::compareTwoUnits(const MeasureUnitImpl &firstUnit,
598 const MeasureUnitImpl &secondUnit,
599 const ConversionRates &ratesInfo, UErrorCode &status) {
600 if (U_FAILURE(status)) {
601 return 0;
602 }
603
604 if (firstUnit.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED ||
605 secondUnit.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) {
606 status = U_ARGUMENT_TYPE_MISMATCH;
607 return 0;
608 }
609
610 Convertibility unitsState = extractConvertibility(firstUnit, secondUnit, ratesInfo, status);
611 if (U_FAILURE(status)) {
612 return 0;
613 }
614
615 if (unitsState == Convertibility::UNCONVERTIBLE || unitsState == Convertibility::RECIPROCAL) {
616 status = U_ARGUMENT_TYPE_MISMATCH;
617 return 0;
618 }
619
620 CharString firstSpecial = getSpecialMappingName(firstUnit, ratesInfo, status);
621 CharString secondSpecial = getSpecialMappingName(secondUnit, ratesInfo, status);
622 if (!firstSpecial.isEmpty() || !secondSpecial.isEmpty()) {
623 if (firstSpecial.isEmpty()) {
624 // non-specials come first
625 return -1;
626 }
627 if (secondSpecial.isEmpty()) {
628 // non-specials come first
629 return 1;
630 }
631 // both are specials, compare lexicographically
632 StringPiece firstSpecialPiece = firstSpecial.toStringPiece();
633 StringPiece secondSpecialPiece = secondSpecial.toStringPiece();
634 return firstSpecialPiece.compare(secondSpecialPiece);
635 }
636
637 // Represents the conversion factor from the firstUnit to the base
638 // unit that specified in the conversion data which is considered as
639 // the root of the firstUnit and the secondUnit.
640 Factor firstUnitToBase = loadCompoundFactor(firstUnit, ratesInfo, status);
641 Factor secondUnitToBase = loadCompoundFactor(secondUnit, ratesInfo, status);
642
643 firstUnitToBase.substituteConstants();
644 secondUnitToBase.substituteConstants();
645
646 double firstUnitToBaseConversionRate = firstUnitToBase.factorNum / firstUnitToBase.factorDen;
647 double secondUnitToBaseConversionRate = secondUnitToBase.factorNum / secondUnitToBase.factorDen;
648
649 double diff = firstUnitToBaseConversionRate - secondUnitToBaseConversionRate;
650 if (diff > 0) {
651 return 1;
652 }
653
654 if (diff < 0) {
655 return -1;
656 }
657
658 return 0;
659 }
660
661 // TODO per CLDR-17421 and ICU-22683: consider getting the data below from CLDR
662 static double minMetersPerSecForBeaufort[] = {
663 // Minimum m/s (base) values for each Bft value, plus an extra artificial value;
664 // when converting from Bft to m/s, the middle of the range will be used
665 // (Values from table in Wikipedia, except for artificial value).
666 // Since this is 0 based, max Beaufort value is thus array dimension minus 2.
667 0.0, // 0 Bft
668 0.3, // 1
669 1.6, // 2
670 3.4, // 3
671 5.5, // 4
672 8.0, // 5
673 10.8, // 6
674 13.9, // 7
675 17.2, // 8
676 20.8, // 9
677 24.5, // 10
678 28.5, // 11
679 32.7, // 12
680 36.9, // 13
681 41.4, // 14
682 46.1, // 15
683 51.1, // 16
684 55.8, // 17
685 61.4, // artificial end of range 17 to give reasonable midpoint
686 };
687
688 static int maxBeaufort = UPRV_LENGTHOF(minMetersPerSecForBeaufort) - 2;
689
690 // Convert from what should be discrete scale values for a particular unit like beaufort
691 // to a corresponding value in the base unit (which can have any decimal value, like meters/sec).
692 // First we round the scale value to the nearest integer (in case it is specified with a fractional value),
693 // then we map that to a value in middle of the range of corresponding base values.
694 // This can handle different scales, specified by minBaseForScaleValues[].
scaleToBase(double scaleValue,double minBaseForScaleValues[],int scaleMax) const695 double UnitsConverter::scaleToBase(double scaleValue, double minBaseForScaleValues[], int scaleMax) const {
696 if (scaleValue < 0) {
697 scaleValue = -scaleValue;
698 }
699 scaleValue += 0.5; // adjust up for later truncation
700 if (scaleValue > (double)scaleMax) {
701 scaleValue = (double)scaleMax;
702 }
703 int scaleInt = (int)scaleValue;
704 return (minBaseForScaleValues[scaleInt] + minBaseForScaleValues[scaleInt+1])/2.0;
705 }
706
707 // Binary search to find the range that includes key;
708 // if key (non-negative) is in the range rangeStarts[i] to just under rangeStarts[i+1],
709 // then we return i; if key is >= rangeStarts[max] then we return max.
710 // Note that max is the maximum scale value, not the number of elements in the array
711 // (which should be larger than max).
712 // The ranges for index 0 start at 0.0.
bsearchRanges(double rangeStarts[],int max,double key)713 static int bsearchRanges(double rangeStarts[], int max, double key) {
714 if (key >= rangeStarts[max]) {
715 return max;
716 }
717 int beg = 0, mid = 0, end = max + 1;
718 while (beg < end) {
719 mid = (beg + end) / 2;
720 if (key < rangeStarts[mid]) {
721 end = mid;
722 } else if (key > rangeStarts[mid+1]) {
723 beg = mid+1;
724 } else {
725 break;
726 }
727 }
728 return mid;
729 }
730
731 // Convert from a value in the base unit (which can have any decimal value, like meters/sec) to a corresponding
732 // discrete value in a scale (like beaufort), where each scale value represents a range of base values.
733 // We binary-search the ranges to find the one that contains the specified base value, and return its index.
734 // This can handle different scales, specified by minBaseForScaleValues[].
baseToScale(double baseValue,double minBaseForScaleValues[],int scaleMax) const735 double UnitsConverter::baseToScale(double baseValue, double minBaseForScaleValues[], int scaleMax) const {
736 if (baseValue < 0) {
737 baseValue = -baseValue;
738 }
739 int scaleIndex = bsearchRanges(minBaseForScaleValues, scaleMax, baseValue);
740 return (double)scaleIndex;
741 }
742
convert(double inputValue) const743 double UnitsConverter::convert(double inputValue) const {
744 double result = inputValue;
745 if (!conversionRate_.specialSource.isEmpty() || !conversionRate_.specialTarget.isEmpty()) {
746 double base = inputValue;
747 // convert input (=source) to base
748 if (!conversionRate_.specialSource.isEmpty()) {
749 // We have a special mapping from source to base (not using factor, offset).
750 // Currently the only supported mapping is a scale-based mapping for beaufort.
751 base = (conversionRate_.specialSource == StringPiece("beaufort"))?
752 scaleToBase(inputValue, minMetersPerSecForBeaufort, maxBeaufort): inputValue;
753 } else {
754 // Standard mapping (using factor) from source to base.
755 base = inputValue * conversionRate_.factorNum / conversionRate_.factorDen;
756 }
757 // convert base to result (=target)
758 if (!conversionRate_.specialTarget.isEmpty()) {
759 // We have a special mapping from base to target (not using factor, offset).
760 // Currently the only supported mapping is a scale-based mapping for beaufort.
761 result = (conversionRate_.specialTarget == StringPiece("beaufort"))?
762 baseToScale(base, minMetersPerSecForBeaufort, maxBeaufort): base;
763 } else {
764 // Standard mapping (using factor) from base to target.
765 result = base * conversionRate_.factorDen / conversionRate_.factorNum;
766 }
767 return result;
768 }
769 result =
770 inputValue + conversionRate_.sourceOffset; // Reset the input to the target zero index.
771 // Convert the quantity to from the source scale to the target scale.
772 result *= conversionRate_.factorNum / conversionRate_.factorDen;
773
774 result -= conversionRate_.targetOffset; // Set the result to its index.
775
776 if (conversionRate_.reciprocal) {
777 if (result == 0) {
778 return uprv_getInfinity();
779 }
780 result = 1.0 / result;
781 }
782
783 return result;
784 }
785
convertInverse(double inputValue) const786 double UnitsConverter::convertInverse(double inputValue) const {
787 double result = inputValue;
788 if (!conversionRate_.specialSource.isEmpty() || !conversionRate_.specialTarget.isEmpty()) {
789 double base = inputValue;
790 // convert input (=target) to base
791 if (!conversionRate_.specialTarget.isEmpty()) {
792 // We have a special mapping from target to base (not using factor).
793 // Currently the only supported mapping is a scale-based mapping for beaufort.
794 base = (conversionRate_.specialTarget == StringPiece("beaufort"))?
795 scaleToBase(inputValue, minMetersPerSecForBeaufort, maxBeaufort): inputValue;
796 } else {
797 // Standard mapping (using factor) from target to base.
798 base = inputValue * conversionRate_.factorNum / conversionRate_.factorDen;
799 }
800 // convert base to result (=source)
801 if (!conversionRate_.specialSource.isEmpty()) {
802 // We have a special mapping from base to source (not using factor).
803 // Currently the only supported mapping is a scale-based mapping for beaufort.
804 result = (conversionRate_.specialSource == StringPiece("beaufort"))?
805 baseToScale(base, minMetersPerSecForBeaufort, maxBeaufort): base;
806 } else {
807 // Standard mapping (using factor) from base to source.
808 result = base * conversionRate_.factorDen / conversionRate_.factorNum;
809 }
810 return result;
811 }
812 if (conversionRate_.reciprocal) {
813 if (result == 0) {
814 return uprv_getInfinity();
815 }
816 result = 1.0 / result;
817 }
818 result += conversionRate_.targetOffset;
819 result *= conversionRate_.factorDen / conversionRate_.factorNum;
820 result -= conversionRate_.sourceOffset;
821 return result;
822 }
823
getConversionInfo() const824 ConversionInfo UnitsConverter::getConversionInfo() const {
825 ConversionInfo result;
826 result.conversionRate = conversionRate_.factorNum / conversionRate_.factorDen;
827 result.offset =
828 (conversionRate_.sourceOffset * (conversionRate_.factorNum / conversionRate_.factorDen)) -
829 conversionRate_.targetOffset;
830 result.reciprocal = conversionRate_.reciprocal;
831
832 return result;
833 }
834
835 } // namespace units
836 U_NAMESPACE_END
837
838 #endif /* #if !UCONFIG_NO_FORMATTING */
839