• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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