mirror of
https://github.com/unicode-org/icu.git
synced 2025-04-10 07:39:16 +00:00
ICU-22655 Implement "special" conversion for speed-beaufort, part 2 icu4c
This commit is contained in:
parent
4c664b2180
commit
ceee4f0b46
7 changed files with 274 additions and 42 deletions
icu4c/source
i18n
test/intltest
icu4j/main/core/src/main/java/com/ibm/icu/impl/units
|
@ -270,49 +270,96 @@ UBool checkSimpleUnit(const MeasureUnitImpl &unit, UErrorCode &status) {
|
|||
return true;
|
||||
}
|
||||
|
||||
// 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).
|
||||
CharString getSpecialMappingName(const MeasureUnitImpl &simpleUnit, const ConversionRates &ratesInfo,
|
||||
UErrorCode &status) {
|
||||
if (!checkSimpleUnit(simpleUnit, status)) {
|
||||
return CharString();
|
||||
}
|
||||
SingleUnitImpl singleUnit = *simpleUnit.singleUnits[0];
|
||||
const auto conversionUnit = ratesInfo.extractConversionInfo(singleUnit.getSimpleUnitID(), status);
|
||||
if (U_FAILURE(status)) {
|
||||
return CharString();
|
||||
}
|
||||
if (conversionUnit == nullptr) {
|
||||
status = U_INTERNAL_PROGRAM_ERROR;
|
||||
return CharString();
|
||||
}
|
||||
CharString result;
|
||||
result.copyFrom(conversionUnit->specialMappingName, status);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract conversion rate from `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) {
|
||||
// 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);
|
||||
conversionRate.specialSource = getSpecialMappingName(source, ratesInfo, status);
|
||||
conversionRate.specialTarget = getSpecialMappingName(target, ratesInfo, status);
|
||||
|
||||
if (conversionRate.specialSource.isEmpty() && conversionRate.specialTarget.isEmpty()) {
|
||||
// Represents the conversion factor from the source to the target.
|
||||
Factor finalFactor;
|
||||
|
||||
// Merger Factors
|
||||
finalFactor.multiplyBy(sourceToBase);
|
||||
if (unitsState == Convertibility::CONVERTIBLE) {
|
||||
finalFactor.divideBy(targetToBase);
|
||||
} else if (unitsState == Convertibility::RECIPROCAL) {
|
||||
finalFactor.multiplyBy(targetToBase);
|
||||
} else {
|
||||
status = UErrorCode::U_ARGUMENT_TYPE_MISMATCH;
|
||||
return;
|
||||
// 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);
|
||||
|
||||
// Merger Factors
|
||||
finalFactor.multiplyBy(sourceToBase);
|
||||
if (unitsState == Convertibility::CONVERTIBLE) {
|
||||
finalFactor.divideBy(targetToBase);
|
||||
} else if (unitsState == Convertibility::RECIPROCAL) {
|
||||
finalFactor.multiplyBy(targetToBase);
|
||||
} else {
|
||||
status = UErrorCode::U_ARGUMENT_TYPE_MISMATCH;
|
||||
return;
|
||||
}
|
||||
|
||||
finalFactor.substituteConstants();
|
||||
|
||||
conversionRate.factorNum = finalFactor.factorNum;
|
||||
conversionRate.factorDen = finalFactor.factorDen;
|
||||
|
||||
// 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;
|
||||
} else if (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;
|
||||
}
|
||||
|
||||
finalFactor.substituteConstants();
|
||||
|
||||
conversionRate.factorNum = finalFactor.factorNum;
|
||||
conversionRate.factorDen = finalFactor.factorDen;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
struct UnitIndexAndDimension : UMemory {
|
||||
|
@ -569,6 +616,23 @@ int32_t UnitsConverter::compareTwoUnits(const MeasureUnitImpl &firstUnit,
|
|||
return 0;
|
||||
}
|
||||
|
||||
CharString firstSpecial = getSpecialMappingName(firstUnit, ratesInfo, status);
|
||||
CharString secondSpecial = getSpecialMappingName(secondUnit, ratesInfo, status);
|
||||
if (!firstSpecial.isEmpty() || !secondSpecial.isEmpty()) {
|
||||
if (firstSpecial.isEmpty()) {
|
||||
// non-specials come first
|
||||
return -1;
|
||||
}
|
||||
if (secondSpecial.isEmpty()) {
|
||||
// non-specials come first
|
||||
return 1;
|
||||
}
|
||||
// both are specials, compare lexicographically
|
||||
StringPiece firstSpecialPiece = firstSpecial.toStringPiece();
|
||||
StringPiece secondSpecialPiece = secondSpecial.toStringPiece();
|
||||
return firstSpecialPiece.compare(secondSpecialPiece);
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
@ -593,8 +657,115 @@ int32_t UnitsConverter::compareTwoUnits(const MeasureUnitImpl &firstUnit,
|
|||
return 0;
|
||||
}
|
||||
|
||||
// TODO per CLDR-17421 and ICU-22683: consider getting the data below from CLDR
|
||||
static double 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
|
||||
};
|
||||
|
||||
static int maxBeaufort = UPRV_LENGTHOF(minMetersPerSecForBeaufort) - 2;
|
||||
|
||||
// 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 > (double)scaleMax) {
|
||||
scaleValue = (double)scaleMax;
|
||||
}
|
||||
int scaleInt = (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.
|
||||
static int 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;
|
||||
} else if (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 (double)scaleIndex;
|
||||
}
|
||||
|
||||
double UnitsConverter::convert(double inputValue) const {
|
||||
double result =
|
||||
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 = (conversionRate_.specialSource == StringPiece("beaufort"))?
|
||||
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 = (conversionRate_.specialTarget == StringPiece("beaufort"))?
|
||||
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;
|
||||
|
@ -613,6 +784,30 @@ double UnitsConverter::convert(double inputValue) const {
|
|||
|
||||
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 = (conversionRate_.specialTarget == StringPiece("beaufort"))?
|
||||
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 = (conversionRate_.specialSource == StringPiece("beaufort"))?
|
||||
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();
|
||||
|
|
|
@ -110,10 +110,14 @@ void U_I18N_API addSingleFactorConstant(StringPiece baseStr, int32_t power, Sign
|
|||
|
||||
/**
|
||||
* Represents the conversion rate between `source` and `target`.
|
||||
* TODO ICU-22683: COnsider moving the handling of special mappings (e.g. beaufort) to a separate
|
||||
* struct.
|
||||
*/
|
||||
struct U_I18N_API ConversionRate : public UMemory {
|
||||
const MeasureUnitImpl source;
|
||||
const MeasureUnitImpl target;
|
||||
CharString specialSource;
|
||||
CharString specialTarget;
|
||||
double factorNum = 1;
|
||||
double factorDen = 1;
|
||||
double sourceOffset = 0;
|
||||
|
@ -121,7 +125,7 @@ struct U_I18N_API ConversionRate : public UMemory {
|
|||
bool reciprocal = false;
|
||||
|
||||
ConversionRate(MeasureUnitImpl &&source, MeasureUnitImpl &&target)
|
||||
: source(std::move(source)), target(std::move(target)) {}
|
||||
: source(std::move(source)), target(std::move(target)), specialSource(), specialTarget() {}
|
||||
};
|
||||
|
||||
enum Convertibility {
|
||||
|
@ -224,6 +228,21 @@ class U_I18N_API UnitsConverter : public UMemory {
|
|||
* Initialises the object.
|
||||
*/
|
||||
void init(const ConversionRates &ratesInfo, UErrorCode &status);
|
||||
|
||||
/**
|
||||
* 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).
|
||||
* This can handle different scales, specified by minBaseForScaleValues[].
|
||||
*/
|
||||
double scaleToBase(double scaleValue, double minBaseForScaleValues[], int scaleMax) const;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* This can handle different scales, specified by minBaseForScaleValues[].
|
||||
*/
|
||||
double baseToScale(double baseValue, double minBaseForScaleValues[], int scaleMax) const;
|
||||
|
||||
};
|
||||
|
||||
} // namespace units
|
||||
|
|
|
@ -91,7 +91,7 @@ class ConversionRateDataSink : public ResourceSink {
|
|||
} else if (uprv_strcmp(key, "offset") == 0) {
|
||||
offset = value.getUnicodeString(status);
|
||||
} else if (uprv_strcmp(key, "special") == 0) {
|
||||
special = value.getUnicodeString(status);
|
||||
special = value.getUnicodeString(status); // the name of a special mapping used instead of factor + optional offset.
|
||||
} else if (uprv_strcmp(key, "systems") == 0) {
|
||||
systems = value.getUnicodeString(status);
|
||||
}
|
||||
|
@ -116,7 +116,7 @@ class ConversionRateDataSink : public ResourceSink {
|
|||
trimSpaces(cr->factor, status);
|
||||
}
|
||||
if (!offset.isBogus()) cr->offset.appendInvariantChars(offset, status);
|
||||
if (!special.isBogus()) cr->offset.appendInvariantChars(special, status);
|
||||
if (!special.isBogus()) cr->specialMappingName.appendInvariantChars(special, status);
|
||||
cr->systems.appendInvariantChars(systems, status);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ class U_I18N_API ConversionRateInfo : public UMemory {
|
|||
ConversionRateInfo() {}
|
||||
ConversionRateInfo(StringPiece sourceUnit, StringPiece baseUnit, StringPiece factor,
|
||||
StringPiece offset, UErrorCode &status)
|
||||
: sourceUnit(), baseUnit(), factor(), offset(), special() {
|
||||
: sourceUnit(), baseUnit(), factor(), offset(), specialMappingName() {
|
||||
this->sourceUnit.append(sourceUnit, status);
|
||||
this->baseUnit.append(baseUnit, status);
|
||||
this->factor.append(factor, status);
|
||||
|
@ -41,7 +41,7 @@ class U_I18N_API ConversionRateInfo : public UMemory {
|
|||
CharString baseUnit;
|
||||
CharString factor;
|
||||
CharString offset;
|
||||
CharString special;
|
||||
CharString specialMappingName; // the name of a special mapping used instead of factor + optional offset.
|
||||
CharString systems;
|
||||
};
|
||||
|
||||
|
|
|
@ -52,6 +52,8 @@ void UnitsDataTest::testGetUnitCategory() {
|
|||
// Tests are:
|
||||
// {"liter-per-100-kilometer", "consumption"},
|
||||
// {"mile-per-gallon", "consumption"},
|
||||
// {"knot", "speed"},
|
||||
// {"beaufort", "speed"},
|
||||
{"cubic-meter-per-meter", "consumption"},
|
||||
{"meter-per-cubic-meter", "consumption"},
|
||||
{"kilogram-meter-per-square-meter-square-second", "pressure"},
|
||||
|
@ -78,9 +80,7 @@ void UnitsDataTest::testGetAllConversionRates() {
|
|||
cri->sourceUnit.data(), cri->baseUnit.data(), cri->factor.data(), cri->offset.data());
|
||||
assertTrue("sourceUnit", cri->sourceUnit.length() > 0);
|
||||
assertTrue("baseUnit", cri->baseUnit.length() > 0);
|
||||
if (!logKnownIssue("ICU-22655", "Implement support for Beaufort conversion")) {
|
||||
assertTrue("factor || special", cri->factor.length() > 0 || cri->special.length() > 0);
|
||||
}
|
||||
assertTrue("factor || special", cri->factor.length() > 0 || cri->specialMappingName.length() > 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -145,6 +145,9 @@ void UnitsTest::testExtractConvertibility() {
|
|||
{"percent", "portion", CONVERTIBLE}, //
|
||||
{"ofhg", "kilogram-per-square-meter-square-second", CONVERTIBLE}, //
|
||||
{"second-per-meter", "meter-per-second", RECIPROCAL}, //
|
||||
{"mile-per-hour", "meter-per-second", CONVERTIBLE}, //
|
||||
{"knot", "meter-per-second", CONVERTIBLE}, //
|
||||
{"beaufort", "meter-per-second", CONVERTIBLE}, //
|
||||
};
|
||||
|
||||
for (const auto &testCase : testCases) {
|
||||
|
@ -299,6 +302,19 @@ void UnitsTest::testConverter() {
|
|||
{"ton", "pound", 1.0, 2000},
|
||||
{"stone", "pound", 1.0, 14},
|
||||
{"stone", "kilogram", 1.0, 6.35029},
|
||||
// Speed
|
||||
{"mile-per-hour", "meter-per-second", 1.0, 0.44704},
|
||||
{"knot", "meter-per-second", 1.0, 0.514444},
|
||||
{"beaufort", "meter-per-second", 1.0, 0.95},
|
||||
{"beaufort", "meter-per-second", 4.0, 6.75},
|
||||
{"beaufort", "meter-per-second", 7.0, 15.55},
|
||||
{"beaufort", "meter-per-second", 10.0, 26.5},
|
||||
{"beaufort", "meter-per-second", 13.0, 39.15},
|
||||
{"beaufort", "mile-per-hour", 1.0, 2.12509},
|
||||
{"beaufort", "mile-per-hour", 4.0, 15.099319971367215},
|
||||
{"beaufort", "mile-per-hour", 7.0, 34.784359341445956},
|
||||
{"beaufort", "mile-per-hour", 10.0, 59.2788},
|
||||
{"beaufort", "mile-per-hour", 13.0, 87.5761},
|
||||
// Temperature
|
||||
{"celsius", "fahrenheit", 0.0, 32.0},
|
||||
{"celsius", "fahrenheit", 10.0, 50.0},
|
||||
|
|
|
@ -13,6 +13,7 @@ import java.util.regex.Pattern;
|
|||
import com.ibm.icu.impl.IllegalIcuArgumentException;
|
||||
import com.ibm.icu.util.MeasureUnit;
|
||||
|
||||
// TODO ICU-22683: Consider splitting handling of special mappings into separate (possibly internal) class
|
||||
public class UnitsConverter {
|
||||
private BigDecimal conversionRate;
|
||||
private boolean reciprocal;
|
||||
|
@ -211,6 +212,7 @@ public class UnitsConverter {
|
|||
return result;
|
||||
}
|
||||
|
||||
// TODO per CLDR-17421 and ICU-22683: consider getting the data below from CLDR
|
||||
private static final BigDecimal[] 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
|
||||
|
|
Loading…
Add table
Reference in a new issue