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