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