void Factor::multiplyBy(const Factor& rhs) {
factorNum *= rhs.factorNum;
factorDen *= rhs.factorDen; for (int i = 0; i < CONSTANTS_COUNT; i++) {
constantExponents[i] += rhs.constantExponents[i];
}
// NOTE // We need the offset when the source and the target are simple units. e.g. the source is // celsius and the target is Fahrenheit. Therefore, we just keep the value using `std::max`.
offset = std::max(rhs.offset, offset);
}
void Factor::divideBy(const Factor& rhs) {
factorNum *= rhs.factorDen;
factorDen *= rhs.factorNum; for (int i = 0; i < CONSTANTS_COUNT; i++) {
constantExponents[i] -= rhs.constantExponents[i];
}
// NOTE // We need the offset when the source and the target are simple units. e.g. the source is // celsius and the target is Fahrenheit. Therefore, we just keep the value using `std::max`.
offset = std::max(rhs.offset, offset);
}
void Factor::power(int32_t power) { // multiply all the constant by the power. for (int i = 0; i < CONSTANTS_COUNT; i++) {
constantExponents[i] *= power;
}
bool shouldFlip = power < 0; // This means that after applying the absolute power, we should flip // the Numerator and Denominator.
#ifdef JS_HAS_INTL_API
using double_conversion::StringToDoubleConverter; #else
using icu::double_conversion::StringToDoubleConverter; #endif
// TODO: Make this a shared-utility function. // Returns `double` from a scientific number(i.e. "1", "2.01" or "3.09E+4") double strToDouble(StringPiece strNum, UErrorCode &status) { // We are processing well-formed input, so we don't need any special options to // StringToDoubleConverter.
StringToDoubleConverter converter(0, 0, 0, "", "");
int32_t count; double result = converter.StringToDouble(strNum.data(), strNum.length(), &count); if (count != strNum.length()) {
status = U_INVALID_FORMAT_ERROR;
}
return result;
}
// Returns `double` from a scientific number that could has a division sign (i.e. "1", "2.01", "3.09E+4" // or "2E+2/3") double strHasDivideSignToDouble(StringPiece strWithDivide, UErrorCode &status) { int divisionSignInd = -1; for (int i = 0, n = strWithDivide.length(); i < n; ++i) { if (strWithDivide.data()[i] == '/') {
divisionSignInd = i; break;
}
}
/* Addssinglefactortoa`Factor`object.Singlefactormeans"23^2","23.3333","ft2m^3"...etc. However,complexfactorarenotincluded,suchas"ft2m^3*200/3"
*/ void addFactorElement(Factor &factor, StringPiece elementStr, Signum signum, UErrorCode &status) {
StringPiece baseStr;
StringPiece powerStr;
int32_t power = 1; // In case the power is not written, then, the power is equal 1 ==> `ft2m^1` == `ft2m`
// Search for the power part
int32_t powerInd = -1; for (int32_t i = 0, n = elementStr.length(); i < n; ++i) { if (elementStr.data()[i] == '^') {
powerInd = i; break;
}
}
if (powerInd > -1) { // There is power
baseStr = elementStr.substr(0, powerInd);
powerStr = elementStr.substr(powerInd + 1);
/* *Extracts`Factor`fromacompletestringfactor.e.g."ft2m^3*1007/cup2m3*3"
*/
Factor extractFactorConversions(StringPiece stringFactor, UErrorCode &status) {
Factor result;
Signum signum = Signum::POSITIVE; constauto* factorData = stringFactor.data(); for (int32_t i = 0, start = 0, n = stringFactor.length(); i < n; i++) { if (factorData[i] == '*' || factorData[i] == '/') {
StringPiece factorElement = stringFactor.substr(start, i - start);
addFactorElement(result, factorElement, signum, status);
start = i + 1; // Set `start` to point to the start of the new element.
} elseif (i == n - 1) { // Last element
addFactorElement(result, stringFactor.substr(start, i + 1), signum, status);
}
if (factorData[i] == '/') {
signum = Signum::NEGATIVE; // Change the signum because we reached the Denominator.
}
}
return result;
}
// Load factor for a single source
Factor loadSingleFactor(StringPiece source, const ConversionRates &ratesInfo, UErrorCode &status) { constauto* const conversionUnit = ratesInfo.extractConversionInfo(source, status); if (U_FAILURE(status)) return {}; if (conversionUnit == nullptr) {
status = U_INTERNAL_PROGRAM_ERROR; return {};
}
Factor result = extractFactorConversions(conversionUnit->factor.data(), status);
result.offset = strHasDivideSignToDouble(conversionUnit->offset.data(), status);
return result;
}
// Load Factor of a compound source unit. // In ICU4J, this is a pair of ConversionRates.getFactorToBase() functions.
Factor loadCompoundFactor(const MeasureUnitImpl &source, const ConversionRates &ratesInfo,
UErrorCode &status) {
Factor result; for (int32_t i = 0, n = source.singleUnits.length(); i < n; i++) {
SingleUnitImpl singleUnit = *source.singleUnits[i];
Factor singleFactor = loadSingleFactor(singleUnit.getSimpleUnitID(), ratesInfo, status); if (U_FAILURE(status)) return result;
// Prefix before power, because: // - square-kilometer to square-meter: (1000)^2 // - square-kilometer to square-foot (approximate): (3.28*1000)^2
singleFactor.applyPrefix(singleUnit.unitPrefix);
// Apply the power of the `dimensionality`
singleFactor.power(singleUnit.dimensionality);
result.multiplyBy(singleFactor);
}
// If the source has a constant denominator, then we need to divide the // factor by the constant denominator. if (source.constantDenominator != 0) {
result.divideBy(source.constantDenominator);
}
// Map the MeasureUnitImpl for a simpleUnit to a SingleUnitImpl, then use that // SingleUnitImpl's simpleUnitID to get the corresponding ConversionRateInfo; // from that we get the specialMappingName (which may be empty if the simple unit // converts to base using factor + offset instelad of a special mapping).
StringPiece getSpecialMappingName(const MeasureUnitImpl& simpleUnit, const ConversionRates& ratesInfo,
UErrorCode& status) { if (!checkSimpleUnit(simpleUnit, status)) { return {};
}
SingleUnitImpl singleUnit = *simpleUnit.singleUnits[0]; constauto* const conversionUnit =
ratesInfo.extractConversionInfo(singleUnit.getSimpleUnitID(), status); if (U_FAILURE(status)) { return {};
} if (conversionUnit == nullptr) {
status = U_INTERNAL_PROGRAM_ERROR; return {};
} return conversionUnit->specialMappingName.data();
}
/** *Extractconversionratefrom`source`to`target`
*/ // In ICU4J, this function is partially inlined in the UnitsConverter constructor. // TODO ICU-22683: Consider splitting handling of special mappings into separate class void loadConversionRate(ConversionRate &conversionRate, const MeasureUnitImpl &source, const MeasureUnitImpl &target, Convertibility unitsState, const ConversionRates &ratesInfo, UErrorCode &status) {
StringPiece specialSource = getSpecialMappingName(source, ratesInfo, status);
StringPiece specialTarget = getSpecialMappingName(target, ratesInfo, status);
if (conversionRate.specialSource.isEmpty() != specialSource.empty() ||
conversionRate.specialTarget.isEmpty() != specialTarget.empty()) {
status = U_MEMORY_ALLOCATION_ERROR; return;
}
if (conversionRate.specialSource.isEmpty() && conversionRate.specialTarget.isEmpty()) { // Represents the conversion factor from the source to the target.
Factor finalFactor;
// Represents the conversion factor from the source to the base unit that specified in the conversion // data which is considered as the root of the source and the target.
Factor sourceToBase = loadCompoundFactor(source, ratesInfo, status);
Factor targetToBase = loadCompoundFactor(target, ratesInfo, status);
// This code corresponds to ICU4J's ConversionRates.getOffset(). // In case of simple units (such as: celsius or fahrenheit), offsets are considered. if (checkSimpleUnit(source, status) && checkSimpleUnit(target, status)) {
conversionRate.sourceOffset =
sourceToBase.offset * sourceToBase.factorDen / sourceToBase.factorNum;
conversionRate.targetOffset =
targetToBase.offset * targetToBase.factorDen / targetToBase.factorNum;
} // TODO(icu-units#127): should we consider failure if there's an offset for // a not-simple-unit? What about kilokelvin / kilocelsius?
conversionRate.reciprocal = unitsState == Convertibility::RECIPROCAL;
} elseif (conversionRate.specialSource.isEmpty() || conversionRate.specialTarget.isEmpty()) { // Still need to set factorNum/factorDen for either source to base or base to target if (unitsState != Convertibility::CONVERTIBLE) {
status = UErrorCode::U_ARGUMENT_TYPE_MISMATCH; return;
}
Factor finalFactor; if (conversionRate.specialSource.isEmpty()) { // factorNum/factorDen is for source to base only
finalFactor = loadCompoundFactor(source, ratesInfo, status);
} else { // factorNum/factorDen is for base to target only
finalFactor = loadCompoundFactor(target, ratesInfo, status);
}
finalFactor.substituteConstants();
conversionRate.factorNum = finalFactor.factorNum;
conversionRate.factorDen = finalFactor.factorDen;
}
}
MeasureUnitImpl result; if (U_FAILURE(status)) return result;
constauto &singleUnits = source.singleUnits; for (int i = 0, count = singleUnits.length(); i < count; ++i) { constauto &singleUnit = *singleUnits[i]; // Extract `ConversionRateInfo` using the absolute unit. For example: in case of `square-meter`, // we will use `meter` constauto* const rateInfo =
conversionRates.extractConversionInfo(singleUnit.getSimpleUnitID(), status); if (U_FAILURE(status)) { return result;
} if (rateInfo == nullptr) {
status = U_INTERNAL_PROGRAM_ERROR; return result;
}
// Multiply the power of the singleUnit by the power of the baseUnit. For example, square-hectare // must be pow4-meter. (NOTE: hectare --> square-meter) auto baseUnits =
MeasureUnitImpl::forIdentifier(rateInfo->baseUnit.data(), status).singleUnits; for (int32_t i = 0, baseUnitsCount = baseUnits.length(); i < baseUnitsCount; i++) {
baseUnits[i]->dimensionality *= singleUnit.dimensionality; // TODO: Deal with SI-prefix
result.appendSingleUnit(*baseUnits[i], status);
if (unitsState == Convertibility::UNCONVERTIBLE || unitsState == Convertibility::RECIPROCAL) {
status = U_ARGUMENT_TYPE_MISMATCH; return0;
}
StringPiece firstSpecial = getSpecialMappingName(firstUnit, ratesInfo, status);
StringPiece secondSpecial = getSpecialMappingName(secondUnit, ratesInfo, status); if (!firstSpecial.empty() || !secondSpecial.empty()) { if (firstSpecial.empty()) { // non-specials come first return -1;
} if (secondSpecial.empty()) { // non-specials come first return1;
} // both are specials, compare lexicographically return firstSpecial.compare(secondSpecial);
}
// Represents the conversion factor from the firstUnit to the base // unit that specified in the conversion data which is considered as // the root of the firstUnit and the secondUnit.
Factor firstUnitToBase = loadCompoundFactor(firstUnit, ratesInfo, status);
Factor secondUnitToBase = loadCompoundFactor(secondUnit, ratesInfo, status);
// TODO per CLDR-17421 and ICU-22683: consider getting the data below from CLDR staticdouble minMetersPerSecForBeaufort[] = { // Minimum m/s (base) values for each Bft value, plus an extra artificial value; // when converting from Bft to m/s, the middle of the range will be used // (Values from table in Wikipedia, except for artificial value). // Since this is 0 based, max Beaufort value is thus array dimension minus 2. 0.0, // 0 Bft 0.3, // 1 1.6, // 2 3.4, // 3 5.5, // 4 8.0, // 5 10.8, // 6 13.9, // 7 17.2, // 8 20.8, // 9 24.5, // 10 28.5, // 11 32.7, // 12 36.9, // 13 41.4, // 14 46.1, // 15 51.1, // 16 55.8, // 17 61.4, // artificial end of range 17 to give reasonable midpoint
};
// Convert from what should be discrete scale values for a particular unit like beaufort // to a corresponding value in the base unit (which can have any decimal value, like meters/sec). // First we round the scale value to the nearest integer (in case it is specified with a fractional value), // then we map that to a value in middle of the range of corresponding base values. // This can handle different scales, specified by minBaseForScaleValues[]. double UnitsConverter::scaleToBase(double scaleValue, double minBaseForScaleValues[], int scaleMax) const { if (scaleValue < 0) {
scaleValue = -scaleValue;
}
scaleValue += 0.5; // adjust up for later truncation if (scaleValue > static_cast<double>(scaleMax)) {
scaleValue = static_cast<double>(scaleMax);
} int scaleInt = static_cast<int>(scaleValue); return (minBaseForScaleValues[scaleInt] + minBaseForScaleValues[scaleInt+1])/2.0;
}
// Binary search to find the range that includes key; // if key (non-negative) is in the range rangeStarts[i] to just under rangeStarts[i+1], // then we return i; if key is >= rangeStarts[max] then we return max. // Note that max is the maximum scale value, not the number of elements in the array // (which should be larger than max). // The ranges for index 0 start at 0.0. staticint bsearchRanges(double rangeStarts[], int max, double key) { if (key >= rangeStarts[max]) { return max;
} int beg = 0, mid = 0, end = max + 1; while (beg < end) {
mid = (beg + end) / 2; if (key < rangeStarts[mid]) {
end = mid;
} elseif (key > rangeStarts[mid+1]) {
beg = mid+1;
} else { break;
}
} return mid;
}
// Convert from a value in the base unit (which can have any decimal value, like meters/sec) to a corresponding // discrete value in a scale (like beaufort), where each scale value represents a range of base values. // We binary-search the ranges to find the one that contains the specified base value, and return its index. // This can handle different scales, specified by minBaseForScaleValues[]. double UnitsConverter::baseToScale(double baseValue, double minBaseForScaleValues[], int scaleMax) const { if (baseValue < 0) {
baseValue = -baseValue;
} int scaleIndex = bsearchRanges(minBaseForScaleValues, scaleMax, baseValue); return static_cast<double>(scaleIndex);
}
double UnitsConverter::convert(double inputValue) const { double result = inputValue; if (!conversionRate_.specialSource.isEmpty() || !conversionRate_.specialTarget.isEmpty()) { double base = inputValue; // convert input (=source) to base if (!conversionRate_.specialSource.isEmpty()) { // We have a special mapping from source to base (not using factor, offset). // Currently the only supported mapping is a scale-based mapping for beaufort.
base = uprv_strcmp(conversionRate_.specialSource.data(), "beaufort") == 0 ?
scaleToBase(inputValue, minMetersPerSecForBeaufort, maxBeaufort): inputValue;
} else { // Standard mapping (using factor) from source to base.
base = inputValue * conversionRate_.factorNum / conversionRate_.factorDen;
} // convert base to result (=target) if (!conversionRate_.specialTarget.isEmpty()) { // We have a special mapping from base to target (not using factor, offset). // Currently the only supported mapping is a scale-based mapping for beaufort.
result = uprv_strcmp(conversionRate_.specialTarget.data(), "beaufort") == 0 ?
baseToScale(base, minMetersPerSecForBeaufort, maxBeaufort): base;
} else { // Standard mapping (using factor) from base to target.
result = base * conversionRate_.factorDen / conversionRate_.factorNum;
} return result;
}
result =
inputValue + conversionRate_.sourceOffset; // Reset the input to the target zero index. // Convert the quantity to from the source scale to the target scale.
result *= conversionRate_.factorNum / conversionRate_.factorDen;
result -= conversionRate_.targetOffset; // Set the result to its index.
if (conversionRate_.reciprocal) { if (result == 0) { return uprv_getInfinity();
}
result = 1.0 / result;
}
return result;
}
double UnitsConverter::convertInverse(double inputValue) const { double result = inputValue; if (!conversionRate_.specialSource.isEmpty() || !conversionRate_.specialTarget.isEmpty()) { double base = inputValue; // convert input (=target) to base if (!conversionRate_.specialTarget.isEmpty()) { // We have a special mapping from target to base (not using factor). // Currently the only supported mapping is a scale-based mapping for beaufort.
base = uprv_strcmp(conversionRate_.specialTarget.data(), "beaufort") == 0 ?
scaleToBase(inputValue, minMetersPerSecForBeaufort, maxBeaufort): inputValue;
} else { // Standard mapping (using factor) from target to base.
base = inputValue * conversionRate_.factorNum / conversionRate_.factorDen;
} // convert base to result (=source) if (!conversionRate_.specialSource.isEmpty()) { // We have a special mapping from base to source (not using factor). // Currently the only supported mapping is a scale-based mapping for beaufort.
result = uprv_strcmp(conversionRate_.specialSource.data(), "beaufort") == 0 ?
baseToScale(base, minMetersPerSecForBeaufort, maxBeaufort): base;
} else { // Standard mapping (using factor) from base to source.
result = base * conversionRate_.factorDen / conversionRate_.factorNum;
} return result;
} if (conversionRate_.reciprocal) { if (result == 0) { return uprv_getInfinity();
}
result = 1.0 / result;
}
result += conversionRate_.targetOffset;
result *= conversionRate_.factorDen / conversionRate_.factorNum;
result -= conversionRate_.sourceOffset; return result;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.