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