ICU-21357 Support IEC Binary Prefixes in MeasureUnit

See #1474
This commit is contained in:
Hugo van der Merwe 2021-01-26 15:22:42 +00:00
parent 7c412f9c8f
commit 1c61c57ece
17 changed files with 893 additions and 344 deletions

View file

@ -41,12 +41,20 @@ namespace {
// TODO: Propose a new error code for this?
constexpr UErrorCode kUnitIdentifierSyntaxError = U_ILLEGAL_ARGUMENT_ERROR;
// Trie value offset for SI Prefixes. This is big enough to ensure we only
// Trie value offset for SI or binary prefixes. This is big enough to ensure we only
// insert positive integers into the trie.
constexpr int32_t kSIPrefixOffset = 64;
constexpr int32_t kPrefixOffset = 64;
static_assert(kPrefixOffset + UMEASURE_PREFIX_INTERNAL_MIN_BIN > 0,
"kPrefixOffset is too small for minimum UMeasurePrefix value");
static_assert(kPrefixOffset + UMEASURE_PREFIX_INTERNAL_MIN_SI > 0,
"kPrefixOffset is too small for minimum UMeasurePrefix value");
// Trie value offset for compound parts, e.g. "-per-", "-", "-and-".
constexpr int32_t kCompoundPartOffset = 128;
static_assert(kCompoundPartOffset > kPrefixOffset + UMEASURE_PREFIX_INTERNAL_MAX_BIN,
"Ambiguous token values: prefix tokens are overlapping with CompoundPart tokens");
static_assert(kCompoundPartOffset > kPrefixOffset + UMEASURE_PREFIX_INTERNAL_MAX_SI,
"Ambiguous token values: prefix tokens are overlapping with CompoundPart tokens");
enum CompoundPart {
// Represents "-per-"
@ -90,30 +98,40 @@ enum PowerPart {
// "fluid-ounce-imperial".
constexpr int32_t kSimpleUnitOffset = 512;
const struct SIPrefixStrings {
const struct UnitPrefixStrings {
const char* const string;
UMeasureSIPrefix value;
} gSIPrefixStrings[] = {
{ "yotta", UMEASURE_SI_PREFIX_YOTTA },
{ "zetta", UMEASURE_SI_PREFIX_ZETTA },
{ "exa", UMEASURE_SI_PREFIX_EXA },
{ "peta", UMEASURE_SI_PREFIX_PETA },
{ "tera", UMEASURE_SI_PREFIX_TERA },
{ "giga", UMEASURE_SI_PREFIX_GIGA },
{ "mega", UMEASURE_SI_PREFIX_MEGA },
{ "kilo", UMEASURE_SI_PREFIX_KILO },
{ "hecto", UMEASURE_SI_PREFIX_HECTO },
{ "deka", UMEASURE_SI_PREFIX_DEKA },
{ "deci", UMEASURE_SI_PREFIX_DECI },
{ "centi", UMEASURE_SI_PREFIX_CENTI },
{ "milli", UMEASURE_SI_PREFIX_MILLI },
{ "micro", UMEASURE_SI_PREFIX_MICRO },
{ "nano", UMEASURE_SI_PREFIX_NANO },
{ "pico", UMEASURE_SI_PREFIX_PICO },
{ "femto", UMEASURE_SI_PREFIX_FEMTO },
{ "atto", UMEASURE_SI_PREFIX_ATTO },
{ "zepto", UMEASURE_SI_PREFIX_ZEPTO },
{ "yocto", UMEASURE_SI_PREFIX_YOCTO },
UMeasurePrefix value;
} gUnitPrefixStrings[] = {
// SI prefixes
{ "yotta", UMEASURE_PREFIX_YOTTA },
{ "zetta", UMEASURE_PREFIX_ZETTA },
{ "exa", UMEASURE_PREFIX_EXA },
{ "peta", UMEASURE_PREFIX_PETA },
{ "tera", UMEASURE_PREFIX_TERA },
{ "giga", UMEASURE_PREFIX_GIGA },
{ "mega", UMEASURE_PREFIX_MEGA },
{ "kilo", UMEASURE_PREFIX_KILO },
{ "hecto", UMEASURE_PREFIX_HECTO },
{ "deka", UMEASURE_PREFIX_DEKA },
{ "deci", UMEASURE_PREFIX_DECI },
{ "centi", UMEASURE_PREFIX_CENTI },
{ "milli", UMEASURE_PREFIX_MILLI },
{ "micro", UMEASURE_PREFIX_MICRO },
{ "nano", UMEASURE_PREFIX_NANO },
{ "pico", UMEASURE_PREFIX_PICO },
{ "femto", UMEASURE_PREFIX_FEMTO },
{ "atto", UMEASURE_PREFIX_ATTO },
{ "zepto", UMEASURE_PREFIX_ZEPTO },
{ "yocto", UMEASURE_PREFIX_YOCTO },
// Binary prefixes
{ "yobi", UMEASURE_PREFIX_YOBI },
{ "zebi", UMEASURE_PREFIX_ZEBI },
{ "exbi", UMEASURE_PREFIX_EXBI },
{ "pebi", UMEASURE_PREFIX_PEBI },
{ "tebi", UMEASURE_PREFIX_TEBI },
{ "gibi", UMEASURE_PREFIX_GIBI },
{ "mebi", UMEASURE_PREFIX_MEBI },
{ "kibi", UMEASURE_PREFIX_KIBI },
};
/**
@ -221,9 +239,9 @@ void U_CALLCONV initUnitExtras(UErrorCode& status) {
BytesTrieBuilder b(status);
if (U_FAILURE(status)) { return; }
// Add SI prefixes
for (const auto& siPrefixInfo : gSIPrefixStrings) {
b.add(siPrefixInfo.string, siPrefixInfo.value + kSIPrefixOffset, status);
// Add SI and binary prefixes
for (const auto& unitPrefixInfo : gUnitPrefixStrings) {
b.add(unitPrefixInfo.string, unitPrefixInfo.value + kPrefixOffset, status);
}
if (U_FAILURE(status)) { return; }
@ -295,7 +313,7 @@ public:
enum Type {
TYPE_UNDEFINED,
TYPE_SI_PREFIX,
TYPE_PREFIX,
// Token type for "-per-", "-", and "-and-".
TYPE_COMPOUND_PART,
// Token type for "per-".
@ -309,7 +327,7 @@ public:
Type getType() const {
U_ASSERT(fMatch > 0);
if (fMatch < kCompoundPartOffset) {
return TYPE_SI_PREFIX;
return TYPE_PREFIX;
}
if (fMatch < kInitialCompoundPartOffset) {
return TYPE_COMPOUND_PART;
@ -323,9 +341,9 @@ public:
return TYPE_SIMPLE_UNIT;
}
UMeasureSIPrefix getSIPrefix() const {
U_ASSERT(getType() == TYPE_SI_PREFIX);
return static_cast<UMeasureSIPrefix>(fMatch - kSIPrefixOffset);
UMeasurePrefix getUnitPrefix() const {
U_ASSERT(getType() == TYPE_PREFIX);
return static_cast<UMeasurePrefix>(fMatch - kPrefixOffset);
}
// Valid only for tokens with type TYPE_COMPOUND_PART.
@ -511,9 +529,9 @@ private:
}
// state:
// 0 = no tokens seen yet (will accept power, SI prefix, or simple unit)
// 0 = no tokens seen yet (will accept power, SI or binary prefix, or simple unit)
// 1 = power token seen (will not accept another power token)
// 2 = SI prefix token seen (will not accept a power or SI prefix token)
// 2 = SI or binary prefix token seen (will not accept a power, or SI or binary prefix token)
int32_t state = 0;
bool atStart = fIndex == 0;
@ -589,12 +607,12 @@ private:
state = 1;
break;
case Token::TYPE_SI_PREFIX:
case Token::TYPE_PREFIX:
if (state > 1) {
status = kUnitIdentifierSyntaxError;
return result;
}
result.siPrefix = token.getSIPrefix();
result.unitPrefix = token.getUnitPrefix();
state = 2;
break;
@ -622,6 +640,7 @@ private:
}
};
// Sorting function wrapping SingleUnitImpl::compareTo for use with uprv_sortArray.
int32_t U_CALLCONV
compareSingleUnits(const void* /*context*/, const void* left, const void* right) {
auto realLeft = static_cast<const SingleUnitImpl* const*>(left);
@ -631,7 +650,29 @@ compareSingleUnits(const void* /*context*/, const void* left, const void* right)
} // namespace
U_CAPI int32_t U_EXPORT2
umeas_getPrefixPower(UMeasurePrefix unitPrefix) {
if (unitPrefix >= UMEASURE_PREFIX_INTERNAL_MIN_BIN &&
unitPrefix <= UMEASURE_PREFIX_INTERNAL_MAX_BIN) {
return unitPrefix - UMEASURE_PREFIX_INTERNAL_ONE_BIN;
}
U_ASSERT(unitPrefix >= UMEASURE_PREFIX_INTERNAL_MIN_SI &&
unitPrefix <= UMEASURE_PREFIX_INTERNAL_MAX_SI);
return unitPrefix - UMEASURE_PREFIX_ONE;
}
U_CAPI int32_t U_EXPORT2
umeas_getPrefixBase(UMeasurePrefix unitPrefix) {
if (unitPrefix >= UMEASURE_PREFIX_INTERNAL_MIN_BIN &&
unitPrefix <= UMEASURE_PREFIX_INTERNAL_MAX_BIN) {
return 1024;
}
U_ASSERT(unitPrefix >= UMEASURE_PREFIX_INTERNAL_MIN_SI &&
unitPrefix <= UMEASURE_PREFIX_INTERNAL_MAX_SI);
return 10;
}
// In ICU4J, this is MeasureUnit.getSingleUnitImpl().
SingleUnitImpl SingleUnitImpl::forMeasureUnit(const MeasureUnit& measureUnit, UErrorCode& status) {
MeasureUnitImpl temp;
const MeasureUnitImpl& impl = MeasureUnitImpl::forMeasureUnit(measureUnit, temp, status);
@ -682,13 +723,21 @@ void SingleUnitImpl::appendNeutralIdentifier(CharString &result, UErrorCode &sta
return;
}
if (this->siPrefix != UMEASURE_SI_PREFIX_ONE) {
for (const auto &siPrefixInfo : gSIPrefixStrings) {
if (siPrefixInfo.value == this->siPrefix) {
result.append(siPrefixInfo.string, status);
if (this->unitPrefix != UMEASURE_PREFIX_ONE) {
bool found = false;
for (const auto &unitPrefixInfo : gUnitPrefixStrings) {
// TODO: consider using binary search? If we do this, add a unit
// test to ensure gUnitPrefixStrings is sorted?
if (unitPrefixInfo.value == this->unitPrefix) {
result.append(unitPrefixInfo.string, status);
found = true;
break;
}
}
if (!found) {
status = U_UNSUPPORTED_ERROR;
return;
}
}
result.append(StringPiece(this->getSimpleUnitID()), status);
@ -868,13 +917,13 @@ UMeasureUnitComplexity MeasureUnit::getComplexity(UErrorCode& status) const {
return MeasureUnitImpl::forMeasureUnit(*this, temp, status).complexity;
}
UMeasureSIPrefix MeasureUnit::getSIPrefix(UErrorCode& status) const {
return SingleUnitImpl::forMeasureUnit(*this, status).siPrefix;
UMeasurePrefix MeasureUnit::getPrefix(UErrorCode& status) const {
return SingleUnitImpl::forMeasureUnit(*this, status).unitPrefix;
}
MeasureUnit MeasureUnit::withSIPrefix(UMeasureSIPrefix prefix, UErrorCode& status) const {
MeasureUnit MeasureUnit::withPrefix(UMeasurePrefix prefix, UErrorCode& status) const {
SingleUnitImpl singleUnit = SingleUnitImpl::forMeasureUnit(*this, status);
singleUnit.siPrefix = prefix;
singleUnit.unitPrefix = prefix;
return singleUnit.build(status);
}

View file

@ -42,7 +42,7 @@ struct U_I18N_API MeasureUnitImplWithIndex : public UMemory {
};
/**
* A struct representing a single unit (optional SI prefix and dimensionality).
* A struct representing a single unit (optional SI or binary prefix, and dimensionality).
*/
struct U_I18N_API SingleUnitImpl : public UMemory {
/**
@ -96,10 +96,12 @@ struct U_I18N_API SingleUnitImpl : public UMemory {
if (index > other.index) {
return 1;
}
if (siPrefix < other.siPrefix) {
// TODO(icu-units#70): revisit when fixing normalization. For now we're
// sorting binary prefixes before SI prefixes, as per enum values order.
if (unitPrefix < other.unitPrefix) {
return -1;
}
if (siPrefix > other.siPrefix) {
if (unitPrefix > other.unitPrefix) {
return 1;
}
return 0;
@ -108,8 +110,8 @@ struct U_I18N_API SingleUnitImpl : public UMemory {
/**
* Return whether this SingleUnitImpl is compatible with another for the purpose of coalescing.
*
* Units with the same base unit and SI prefix should match, except that they must also have
* the same dimensionality sign, such that we don't merge numerator and denominator.
* Units with the same base unit and SI or binary prefix should match, except that they must also
* have the same dimensionality sign, such that we don't merge numerator and denominator.
*/
bool isCompatibleWith(const SingleUnitImpl& other) const {
return (compareTo(other) == 0);
@ -134,11 +136,11 @@ struct U_I18N_API SingleUnitImpl : public UMemory {
int32_t index = -1;
/**
* SI prefix.
* SI or binary prefix.
*
* This is ignored for the dimensionless unit.
*/
UMeasureSIPrefix siPrefix = UMEASURE_SI_PREFIX_ONE;
UMeasurePrefix unitPrefix = UMEASURE_PREFIX_ONE;
/**
* Dimensionality.

View file

@ -36,15 +36,15 @@ class MeasureUnitImpl;
/**
* Enumeration for unit complexity. There are three levels:
*
* - SINGLE: A single unit, optionally with a power and/or SI prefix. Examples: hectare,
* square-kilometer, kilojoule, per-second.
* - SINGLE: A single unit, optionally with a power and/or SI or binary prefix.
* Examples: hectare, square-kilometer, kilojoule, per-second, mebibyte.
* - COMPOUND: A unit composed of the product of multiple single units. Examples:
* meter-per-second, kilowatt-hour, kilogram-meter-per-square-second.
* - MIXED: A unit composed of the sum of multiple single units. Examples: foot+inch,
* hour+minute+second, degree+arcminute+arcsecond.
*
* The complexity determines which operations are available. For example, you cannot set the power
* or SI prefix of a compound unit.
* or prefix of a compound unit.
*
* @draft ICU 67
*/
@ -72,159 +72,276 @@ enum UMeasureUnitComplexity {
};
/**
* Enumeration for SI prefixes, such as "kilo".
* Enumeration for SI and binary prefixes, e.g. "kilo-", "nano-", "mebi-".
*
* @draft ICU 67
* Enum values should be treated as opaque: use umeas_getPrefixPower() and
* umeas_getPrefixBase() to find their corresponding values.
*
* @draft ICU 69
* @see umeas_getPrefixBase
* @see umeas_getPrefixPower
*/
typedef enum UMeasureSIPrefix {
typedef enum UMeasurePrefix {
/**
* The absence of an SI or binary prefix.
*
* The integer representation of this enum value is an arbitrary
* implementation detail and should not be relied upon: use
* umeas_getPrefixPower() to obtain meaningful values.
*
* @draft ICU 69
*/
UMEASURE_PREFIX_ONE = 30 + 0,
/**
* SI prefix: yotta, 10^24.
*
* @draft ICU 67
* @draft ICU 69
*/
UMEASURE_SI_PREFIX_YOTTA = 24,
UMEASURE_PREFIX_YOTTA = UMEASURE_PREFIX_ONE + 24,
/**
* ICU use only.
* Used to determine the set of base-10 SI prefixes.
* @internal
*/
UMEASURE_PREFIX_INTERNAL_MAX_SI = UMEASURE_PREFIX_YOTTA,
/**
* SI prefix: zetta, 10^21.
*
* @draft ICU 67
* @draft ICU 69
*/
UMEASURE_SI_PREFIX_ZETTA = 21,
UMEASURE_PREFIX_ZETTA = UMEASURE_PREFIX_ONE + 21,
/**
* SI prefix: exa, 10^18.
*
* @draft ICU 67
* @draft ICU 69
*/
UMEASURE_SI_PREFIX_EXA = 18,
UMEASURE_PREFIX_EXA = UMEASURE_PREFIX_ONE + 18,
/**
* SI prefix: peta, 10^15.
*
* @draft ICU 67
* @draft ICU 69
*/
UMEASURE_SI_PREFIX_PETA = 15,
UMEASURE_PREFIX_PETA = UMEASURE_PREFIX_ONE + 15,
/**
* SI prefix: tera, 10^12.
*
* @draft ICU 67
* @draft ICU 69
*/
UMEASURE_SI_PREFIX_TERA = 12,
UMEASURE_PREFIX_TERA = UMEASURE_PREFIX_ONE + 12,
/**
* SI prefix: giga, 10^9.
*
* @draft ICU 67
* @draft ICU 69
*/
UMEASURE_SI_PREFIX_GIGA = 9,
UMEASURE_PREFIX_GIGA = UMEASURE_PREFIX_ONE + 9,
/**
* SI prefix: mega, 10^6.
*
* @draft ICU 67
* @draft ICU 69
*/
UMEASURE_SI_PREFIX_MEGA = 6,
UMEASURE_PREFIX_MEGA = UMEASURE_PREFIX_ONE + 6,
/**
* SI prefix: kilo, 10^3.
*
* @draft ICU 67
* @draft ICU 69
*/
UMEASURE_SI_PREFIX_KILO = 3,
UMEASURE_PREFIX_KILO = UMEASURE_PREFIX_ONE + 3,
/**
* SI prefix: hecto, 10^2.
*
* @draft ICU 67
* @draft ICU 69
*/
UMEASURE_SI_PREFIX_HECTO = 2,
UMEASURE_PREFIX_HECTO = UMEASURE_PREFIX_ONE + 2,
/**
* SI prefix: deka, 10^1.
*
* @draft ICU 67
* @draft ICU 69
*/
UMEASURE_SI_PREFIX_DEKA = 1,
/**
* The absence of an SI prefix.
*
* @draft ICU 67
*/
UMEASURE_SI_PREFIX_ONE = 0,
UMEASURE_PREFIX_DEKA = UMEASURE_PREFIX_ONE + 1,
/**
* SI prefix: deci, 10^-1.
*
* @draft ICU 67
* @draft ICU 69
*/
UMEASURE_SI_PREFIX_DECI = -1,
UMEASURE_PREFIX_DECI = UMEASURE_PREFIX_ONE + -1,
/**
* SI prefix: centi, 10^-2.
*
* @draft ICU 67
* @draft ICU 69
*/
UMEASURE_SI_PREFIX_CENTI = -2,
UMEASURE_PREFIX_CENTI = UMEASURE_PREFIX_ONE + -2,
/**
* SI prefix: milli, 10^-3.
*
* @draft ICU 67
* @draft ICU 69
*/
UMEASURE_SI_PREFIX_MILLI = -3,
UMEASURE_PREFIX_MILLI = UMEASURE_PREFIX_ONE + -3,
/**
* SI prefix: micro, 10^-6.
*
* @draft ICU 67
* @draft ICU 69
*/
UMEASURE_SI_PREFIX_MICRO = -6,
UMEASURE_PREFIX_MICRO = UMEASURE_PREFIX_ONE + -6,
/**
* SI prefix: nano, 10^-9.
*
* @draft ICU 67
* @draft ICU 69
*/
UMEASURE_SI_PREFIX_NANO = -9,
UMEASURE_PREFIX_NANO = UMEASURE_PREFIX_ONE + -9,
/**
* SI prefix: pico, 10^-12.
*
* @draft ICU 67
* @draft ICU 69
*/
UMEASURE_SI_PREFIX_PICO = -12,
UMEASURE_PREFIX_PICO = UMEASURE_PREFIX_ONE + -12,
/**
* SI prefix: femto, 10^-15.
*
* @draft ICU 67
* @draft ICU 69
*/
UMEASURE_SI_PREFIX_FEMTO = -15,
UMEASURE_PREFIX_FEMTO = UMEASURE_PREFIX_ONE + -15,
/**
* SI prefix: atto, 10^-18.
*
* @draft ICU 67
* @draft ICU 69
*/
UMEASURE_SI_PREFIX_ATTO = -18,
UMEASURE_PREFIX_ATTO = UMEASURE_PREFIX_ONE + -18,
/**
* SI prefix: zepto, 10^-21.
*
* @draft ICU 67
* @draft ICU 69
*/
UMEASURE_SI_PREFIX_ZEPTO = -21,
UMEASURE_PREFIX_ZEPTO = UMEASURE_PREFIX_ONE + -21,
/**
* SI prefix: yocto, 10^-24.
*
* @draft ICU 67
* @draft ICU 69
*/
UMEASURE_SI_PREFIX_YOCTO = -24
} UMeasureSIPrefix;
UMEASURE_PREFIX_YOCTO = UMEASURE_PREFIX_ONE + -24,
/**
* ICU use only.
* Used to determine the set of base-10 SI prefixes.
* @internal
*/
UMEASURE_PREFIX_INTERNAL_MIN_SI = UMEASURE_PREFIX_YOCTO,
/**
* ICU use only.
* Sets the arbitrary offset of the base-1024 binary prefixes' enum values.
* @internal
*/
UMEASURE_PREFIX_INTERNAL_ONE_BIN = -60,
/**
* Binary prefix: kibi, 1024^1.
*
* @draft ICU 69
*/
UMEASURE_PREFIX_KIBI = UMEASURE_PREFIX_INTERNAL_ONE_BIN + 1,
/**
* ICU use only.
* Used to determine the set of base-1024 binary prefixes.
* @internal
*/
UMEASURE_PREFIX_INTERNAL_MIN_BIN = UMEASURE_PREFIX_KIBI,
/**
* Binary prefix: mebi, 1024^2.
*
* @draft ICU 69
*/
UMEASURE_PREFIX_MEBI = UMEASURE_PREFIX_INTERNAL_ONE_BIN + 2,
/**
* Binary prefix: gibi, 1024^3.
*
* @draft ICU 69
*/
UMEASURE_PREFIX_GIBI = UMEASURE_PREFIX_INTERNAL_ONE_BIN + 3,
/**
* Binary prefix: tebi, 1024^4.
*
* @draft ICU 69
*/
UMEASURE_PREFIX_TEBI = UMEASURE_PREFIX_INTERNAL_ONE_BIN + 4,
/**
* Binary prefix: pebi, 1024^5.
*
* @draft ICU 69
*/
UMEASURE_PREFIX_PEBI = UMEASURE_PREFIX_INTERNAL_ONE_BIN + 5,
/**
* Binary prefix: exbi, 1024^6.
*
* @draft ICU 69
*/
UMEASURE_PREFIX_EXBI = UMEASURE_PREFIX_INTERNAL_ONE_BIN + 6,
/**
* Binary prefix: zebi, 1024^7.
*
* @draft ICU 69
*/
UMEASURE_PREFIX_ZEBI = UMEASURE_PREFIX_INTERNAL_ONE_BIN + 7,
/**
* Binary prefix: yobi, 1024^8.
*
* @draft ICU 69
*/
UMEASURE_PREFIX_YOBI = UMEASURE_PREFIX_INTERNAL_ONE_BIN + 8,
/**
* ICU use only.
* Used to determine the set of base-1024 binary prefixes.
* @internal
*/
UMEASURE_PREFIX_INTERNAL_MAX_BIN = UMEASURE_PREFIX_YOBI,
} UMeasurePrefix;
/**
* Returns the base of the factor associated with the given unit prefix: the
* base is 10 for SI prefixes (kilo, micro) and 1024 for binary prefixes (kibi,
* mebi).
*
* @draft ICU 69
*/
U_CAPI int32_t U_EXPORT2 umeas_getPrefixBase(UMeasurePrefix unitPrefix);
/**
* Returns the exponent of the factor associated with the given unit prefix, for
* example 3 for kilo, -6 for micro, 1 for kibi, 2 for mebi, 3 for gibi.
*
* @draft ICU 69
*/
U_CAPI int32_t U_EXPORT2 umeas_getPrefixPower(UMeasurePrefix unitPrefix);
#endif // U_HIDE_DRAFT_API
/**
@ -352,33 +469,36 @@ class U_I18N_API MeasureUnit: public UObject {
UMeasureUnitComplexity getComplexity(UErrorCode& status) const;
/**
* Creates a MeasureUnit which is this SINGLE unit augmented with the specified SI prefix.
* For example, UMEASURE_SI_PREFIX_KILO for "kilo".
* Creates a MeasureUnit which is this SINGLE unit augmented with the specified prefix.
* For example, UMEASURE_PREFIX_KILO for "kilo", or UMEASURE_PREFIX_KIBI for "kibi".
*
* There is sufficient locale data to format all standard SI prefixes.
* There is sufficient locale data to format all standard prefixes.
*
* NOTE: Only works on SINGLE units. If this is a COMPOUND or MIXED unit, an error will
* occur. For more information, see UMeasureUnitComplexity.
*
* @param prefix The SI prefix, from UMeasureSIPrefix.
* @param prefix The prefix, from UMeasurePrefix.
* @param status Set if this is not a SINGLE unit or if another error occurs.
* @return A new SINGLE unit.
* @draft ICU 67
* @draft ICU 69
*/
MeasureUnit withSIPrefix(UMeasureSIPrefix prefix, UErrorCode& status) const;
MeasureUnit withPrefix(UMeasurePrefix prefix, UErrorCode& status) const;
/**
* Gets the current SI prefix of this SINGLE unit. For example, if the unit has the SI prefix
* "kilo", then UMEASURE_SI_PREFIX_KILO is returned.
* Returns the current SI or binary prefix of this SINGLE unit. For example,
* if the unit has the prefix "kilo", then UMEASURE_PREFIX_KILO is
* returned.
*
* NOTE: Only works on SINGLE units. If this is a COMPOUND or MIXED unit, an error will
* occur. For more information, see UMeasureUnitComplexity.
*
* @param status Set if this is not a SINGLE unit or if another error occurs.
* @return The SI prefix of this SINGLE unit, from UMeasureSIPrefix.
* @draft ICU 67
* @return The prefix of this SINGLE unit, from UMeasurePrefix.
* @see umeas_getPrefixBase
* @see umeas_getPrefixPower
* @draft ICU 69
*/
UMeasureSIPrefix getSIPrefix(UErrorCode& status) const;
UMeasurePrefix getPrefix(UErrorCode& status) const;
/**
* Creates a MeasureUnit which is this SINGLE unit augmented with the specified dimensionality

View file

@ -66,17 +66,19 @@ void U_I18N_API Factor::power(int32_t power) {
}
}
void U_I18N_API Factor::applySiPrefix(UMeasureSIPrefix siPrefix) {
if (siPrefix == UMeasureSIPrefix::UMEASURE_SI_PREFIX_ONE) return; // No need to do anything
double siApplied = std::pow(10.0, std::abs(siPrefix));
if (siPrefix < 0) {
factorDen *= siApplied;
void U_I18N_API Factor::applyPrefix(UMeasurePrefix unitPrefix) {
if (unitPrefix == UMeasurePrefix::UMEASURE_PREFIX_ONE) {
// No need to do anything
return;
}
factorNum *= siApplied;
int32_t prefixPower = umeas_getPrefixPower(unitPrefix);
double prefixFactor = std::pow((double)umeas_getPrefixBase(unitPrefix), (double)std::abs(prefixPower));
if (prefixPower >= 0) {
factorNum *= prefixFactor;
} else {
factorDen *= prefixFactor;
}
}
void U_I18N_API Factor::substituteConstants() {
@ -213,6 +215,7 @@ Factor loadSingleFactor(StringPiece source, const ConversionRates &ratesInfo, UE
}
// 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) {
@ -223,8 +226,10 @@ Factor loadCompoundFactor(const MeasureUnitImpl &source, const ConversionRates &
Factor singleFactor = loadSingleFactor(singleUnit.getSimpleUnitID(), ratesInfo, status);
if (U_FAILURE(status)) return result;
// Apply SiPrefix before the power, because the power may be will flip the factor.
singleFactor.applySiPrefix(singleUnit.siPrefix);
// 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);
@ -241,6 +246,8 @@ Factor loadCompoundFactor(const MeasureUnitImpl &source, const ConversionRates &
*
* NOTE:
* Empty unit means simple unit.
*
* In ICU4J, this is ConversionRates.checkSimpleUnit().
*/
UBool checkSimpleUnit(const MeasureUnitImpl &unit, UErrorCode &status) {
if (U_FAILURE(status)) return false;
@ -255,7 +262,7 @@ UBool checkSimpleUnit(const MeasureUnitImpl &unit, UErrorCode &status) {
auto singleUnit = *(unit.singleUnits[0]);
if (singleUnit.dimensionality != 1 || singleUnit.siPrefix != UMEASURE_SI_PREFIX_ONE) {
if (singleUnit.dimensionality != 1 || singleUnit.unitPrefix != UMEASURE_PREFIX_ONE) {
return false;
}
@ -293,6 +300,7 @@ void loadConversionRate(ConversionRate &conversionRate, const MeasureUnitImpl &s
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 =
@ -300,6 +308,8 @@ void loadConversionRate(ConversionRate &conversionRate, const MeasureUnitImpl &s
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;
}

View file

@ -68,8 +68,8 @@ struct U_I18N_API Factor {
// Apply the power to the factor.
void power(int32_t power);
// Apply SI prefix to the `Factor`
void applySiPrefix(UMeasureSIPrefix siPrefix);
// Apply SI or binary prefix to the Factor.
void applyPrefix(UMeasurePrefix unitPrefix);
// Does an in-place substition of the "symbolic constants" based on
// constantExponents (resetting the exponents).

View file

@ -83,6 +83,8 @@ private:
void TestNumericTimeSomeSpecialFormats();
void TestIdentifiers();
void TestInvalidIdentifiers();
void TestIdentifierDetails();
void TestPrefixes();
void TestParseToBuiltIn();
void TestKilogramIdentifier();
void TestCompoundUnitOperations();
@ -152,7 +154,7 @@ private:
int32_t end);
void verifySingleUnit(
const MeasureUnit& unit,
UMeasureSIPrefix siPrefix,
UMeasurePrefix unitPrefix,
int8_t power,
const char* identifier);
void verifyCompoundUnit(
@ -212,6 +214,8 @@ void MeasureFormatTest::runIndexedTest(
TESTCASE_AUTO(TestNumericTimeSomeSpecialFormats);
TESTCASE_AUTO(TestIdentifiers);
TESTCASE_AUTO(TestInvalidIdentifiers);
TESTCASE_AUTO(TestIdentifierDetails);
TESTCASE_AUTO(TestPrefixes);
TESTCASE_AUTO(TestParseToBuiltIn);
TESTCASE_AUTO(TestKilogramIdentifier);
TESTCASE_AUTO(TestCompoundUnitOperations);
@ -3664,6 +3668,45 @@ void MeasureFormatTest::TestIdentifiers() {
{"kilogram-per-meter-per-second", "kilogram-per-meter-second"},
// TODO(ICU-21284): Add more test cases once the proper ranking is available.
// Testing prefixes are parsed and produced correctly (ensures no
// collisions in the enum values)
{"yoctofoot", "yoctofoot"},
{"zeptofoot", "zeptofoot"},
{"attofoot", "attofoot"},
{"femtofoot", "femtofoot"},
{"picofoot", "picofoot"},
{"nanofoot", "nanofoot"},
{"microfoot", "microfoot"},
{"millifoot", "millifoot"},
{"centifoot", "centifoot"},
{"decifoot", "decifoot"},
{"foot", "foot"},
{"dekafoot", "dekafoot"},
{"hectofoot", "hectofoot"},
{"kilofoot", "kilofoot"},
{"megafoot", "megafoot"},
{"gigafoot", "gigafoot"},
{"terafoot", "terafoot"},
{"petafoot", "petafoot"},
{"exafoot", "exafoot"},
{"zettafoot", "zettafoot"},
{"yottafoot", "yottafoot"},
{"kibibyte", "kibibyte"},
{"mebibyte", "mebibyte"},
{"gibibyte", "gibibyte"},
{"tebibyte", "tebibyte"},
{"pebibyte", "pebibyte"},
{"exbibyte", "exbibyte"},
{"zebibyte", "zebibyte"},
{"yobibyte", "yobibyte"},
// Testing sort order of prefixes.
//
// TODO(icu-units#70): revisit when fixing normalization. For now we're
// just checking some consistency between C&J.
{"megafoot-mebifoot-kibifoot-kilofoot", "kibifoot-mebifoot-kilofoot-megafoot"},
};
for (const auto &cas : cases) {
status.setScope(cas.id);
@ -3722,6 +3765,77 @@ void MeasureFormatTest::TestInvalidIdentifiers() {
}
}
void MeasureFormatTest::TestIdentifierDetails() {
IcuTestErrorCode status(*this, "TestIdentifierDetails()");
MeasureUnit joule = MeasureUnit::forIdentifier("joule", status);
status.assertSuccess();
assertEquals("Initial joule", "joule", joule.getIdentifier());
static_assert(UMEASURE_PREFIX_INTERNAL_MAX_SI < 99, "Tests assume there is no prefix 99.");
static_assert(UMEASURE_PREFIX_INTERNAL_MAX_BIN < 99, "Tests assume there is no prefix 99.");
MeasureUnit unit = joule.withPrefix((UMeasurePrefix)99, status);
if (!status.expectErrorAndReset(U_UNSUPPORTED_ERROR)) {
errln("Invalid prefix should result in an error.");
}
assertEquals("Invalid prefix results in no identifier", "", unit.getIdentifier());
unit = joule.withPrefix(UMEASURE_PREFIX_HECTO, status);
status.assertSuccess();
assertEquals("foo identifier", "hectojoule", unit.getIdentifier());
unit = unit.withPrefix(UMEASURE_PREFIX_EXBI, status);
status.assertSuccess();
assertEquals("foo identifier", "exbijoule", unit.getIdentifier());
}
void MeasureFormatTest::TestPrefixes() {
IcuTestErrorCode status(*this, "TestPrefixes()");
const struct TestCase {
UMeasurePrefix prefix;
int32_t expectedBase;
int32_t expectedPower;
} cases[] = {
{UMEASURE_PREFIX_YOCTO, 10, -24},
{UMEASURE_PREFIX_ZEPTO, 10, -21},
{UMEASURE_PREFIX_ATTO, 10, -18},
{UMEASURE_PREFIX_FEMTO, 10, -15},
{UMEASURE_PREFIX_PICO, 10, -12},
{UMEASURE_PREFIX_NANO, 10, -9},
{UMEASURE_PREFIX_MICRO, 10, -6},
{UMEASURE_PREFIX_MILLI, 10, -3},
{UMEASURE_PREFIX_CENTI, 10, -2},
{UMEASURE_PREFIX_DECI, 10, -1},
{UMEASURE_PREFIX_ONE, 10, 0},
{UMEASURE_PREFIX_DEKA, 10, 1},
{UMEASURE_PREFIX_HECTO, 10, 2},
{UMEASURE_PREFIX_KILO, 10, 3},
{UMEASURE_PREFIX_MEGA, 10, 6},
{UMEASURE_PREFIX_GIGA, 10, 9},
{UMEASURE_PREFIX_TERA, 10, 12},
{UMEASURE_PREFIX_PETA, 10, 15},
{UMEASURE_PREFIX_EXA, 10, 18},
{UMEASURE_PREFIX_ZETTA, 10, 21},
{UMEASURE_PREFIX_YOTTA, 10, 24},
{UMEASURE_PREFIX_KIBI, 1024, 1},
{UMEASURE_PREFIX_MEBI, 1024, 2},
{UMEASURE_PREFIX_GIBI, 1024, 3},
{UMEASURE_PREFIX_TEBI, 1024, 4},
{UMEASURE_PREFIX_PEBI, 1024, 5},
{UMEASURE_PREFIX_EXBI, 1024, 6},
{UMEASURE_PREFIX_ZEBI, 1024, 7},
{UMEASURE_PREFIX_YOBI, 1024, 8},
};
for (auto cas : cases) {
MeasureUnit m = MeasureUnit::getAmpere().withPrefix(cas.prefix, status);
assertEquals("umeas_getPrefixPower()", cas.expectedPower,
umeas_getPrefixPower(m.getPrefix(status)));
assertEquals("umeas_getPrefixBase()", cas.expectedBase,
umeas_getPrefixBase(m.getPrefix(status)));
}
}
void MeasureFormatTest::TestParseToBuiltIn() {
IcuTestErrorCode status(*this, "TestParseToBuiltIn()");
const struct TestCase {
@ -3778,12 +3892,12 @@ void MeasureFormatTest::TestKilogramIdentifier() {
assertEquals("nanogram", "", nanogram.getType());
assertEquals("nanogram", "nanogram", nanogram.getIdentifier());
assertEquals("prefix of kilogram", UMEASURE_SI_PREFIX_KILO, kilogram.getSIPrefix(status));
assertEquals("prefix of gram", UMEASURE_SI_PREFIX_ONE, gram.getSIPrefix(status));
assertEquals("prefix of microgram", UMEASURE_SI_PREFIX_MICRO, microgram.getSIPrefix(status));
assertEquals("prefix of nanogram", UMEASURE_SI_PREFIX_NANO, nanogram.getSIPrefix(status));
assertEquals("prefix of kilogram", UMEASURE_PREFIX_KILO, kilogram.getPrefix(status));
assertEquals("prefix of gram", UMEASURE_PREFIX_ONE, gram.getPrefix(status));
assertEquals("prefix of microgram", UMEASURE_PREFIX_MICRO, microgram.getPrefix(status));
assertEquals("prefix of nanogram", UMEASURE_PREFIX_NANO, nanogram.getPrefix(status));
MeasureUnit tmp = kilogram.withSIPrefix(UMEASURE_SI_PREFIX_MILLI, status);
MeasureUnit tmp = kilogram.withPrefix(UMEASURE_PREFIX_MILLI, status);
assertEquals(UnicodeString("Kilogram + milli should be milligram, got: ") + tmp.getIdentifier(),
MeasureUnit::getMilligram().getIdentifier(), tmp.getIdentifier());
}
@ -3795,16 +3909,16 @@ void MeasureFormatTest::TestCompoundUnitOperations() {
MeasureUnit kilometer = MeasureUnit::getKilometer();
MeasureUnit cubicMeter = MeasureUnit::getCubicMeter();
MeasureUnit meter = kilometer.withSIPrefix(UMEASURE_SI_PREFIX_ONE, status);
MeasureUnit centimeter1 = kilometer.withSIPrefix(UMEASURE_SI_PREFIX_CENTI, status);
MeasureUnit centimeter2 = meter.withSIPrefix(UMEASURE_SI_PREFIX_CENTI, status);
MeasureUnit cubicDecimeter = cubicMeter.withSIPrefix(UMEASURE_SI_PREFIX_DECI, status);
MeasureUnit meter = kilometer.withPrefix(UMEASURE_PREFIX_ONE, status);
MeasureUnit centimeter1 = kilometer.withPrefix(UMEASURE_PREFIX_CENTI, status);
MeasureUnit centimeter2 = meter.withPrefix(UMEASURE_PREFIX_CENTI, status);
MeasureUnit cubicDecimeter = cubicMeter.withPrefix(UMEASURE_PREFIX_DECI, status);
verifySingleUnit(kilometer, UMEASURE_SI_PREFIX_KILO, 1, "kilometer");
verifySingleUnit(meter, UMEASURE_SI_PREFIX_ONE, 1, "meter");
verifySingleUnit(centimeter1, UMEASURE_SI_PREFIX_CENTI, 1, "centimeter");
verifySingleUnit(centimeter2, UMEASURE_SI_PREFIX_CENTI, 1, "centimeter");
verifySingleUnit(cubicDecimeter, UMEASURE_SI_PREFIX_DECI, 3, "cubic-decimeter");
verifySingleUnit(kilometer, UMEASURE_PREFIX_KILO, 1, "kilometer");
verifySingleUnit(meter, UMEASURE_PREFIX_ONE, 1, "meter");
verifySingleUnit(centimeter1, UMEASURE_PREFIX_CENTI, 1, "centimeter");
verifySingleUnit(centimeter2, UMEASURE_PREFIX_CENTI, 1, "centimeter");
verifySingleUnit(cubicDecimeter, UMEASURE_PREFIX_DECI, 3, "cubic-decimeter");
assertTrue("centimeter equality", centimeter1 == centimeter2);
assertTrue("kilometer inequality", centimeter1 != kilometer);
@ -3814,10 +3928,10 @@ void MeasureFormatTest::TestCompoundUnitOperations() {
MeasureUnit quarticKilometer = kilometer.withDimensionality(4, status);
MeasureUnit overQuarticKilometer1 = kilometer.withDimensionality(-4, status);
verifySingleUnit(squareMeter, UMEASURE_SI_PREFIX_ONE, 2, "square-meter");
verifySingleUnit(overCubicCentimeter, UMEASURE_SI_PREFIX_CENTI, -3, "per-cubic-centimeter");
verifySingleUnit(quarticKilometer, UMEASURE_SI_PREFIX_KILO, 4, "pow4-kilometer");
verifySingleUnit(overQuarticKilometer1, UMEASURE_SI_PREFIX_KILO, -4, "per-pow4-kilometer");
verifySingleUnit(squareMeter, UMEASURE_PREFIX_ONE, 2, "square-meter");
verifySingleUnit(overCubicCentimeter, UMEASURE_PREFIX_CENTI, -3, "per-cubic-centimeter");
verifySingleUnit(quarticKilometer, UMEASURE_PREFIX_KILO, 4, "pow4-kilometer");
verifySingleUnit(overQuarticKilometer1, UMEASURE_PREFIX_KILO, -4, "per-pow4-kilometer");
assertTrue("power inequality", quarticKilometer != overQuarticKilometer1);
@ -3828,26 +3942,26 @@ void MeasureFormatTest::TestCompoundUnitOperations() {
.reciprocal(status);
MeasureUnit overQuarticKilometer4 = meter.withDimensionality(4, status)
.reciprocal(status)
.withSIPrefix(UMEASURE_SI_PREFIX_KILO, status);
.withPrefix(UMEASURE_PREFIX_KILO, status);
verifySingleUnit(overQuarticKilometer2, UMEASURE_SI_PREFIX_KILO, -4, "per-pow4-kilometer");
verifySingleUnit(overQuarticKilometer3, UMEASURE_SI_PREFIX_KILO, -4, "per-pow4-kilometer");
verifySingleUnit(overQuarticKilometer4, UMEASURE_SI_PREFIX_KILO, -4, "per-pow4-kilometer");
verifySingleUnit(overQuarticKilometer2, UMEASURE_PREFIX_KILO, -4, "per-pow4-kilometer");
verifySingleUnit(overQuarticKilometer3, UMEASURE_PREFIX_KILO, -4, "per-pow4-kilometer");
verifySingleUnit(overQuarticKilometer4, UMEASURE_PREFIX_KILO, -4, "per-pow4-kilometer");
assertTrue("reciprocal equality", overQuarticKilometer1 == overQuarticKilometer2);
assertTrue("reciprocal equality", overQuarticKilometer1 == overQuarticKilometer3);
assertTrue("reciprocal equality", overQuarticKilometer1 == overQuarticKilometer4);
MeasureUnit kiloSquareSecond = MeasureUnit::getSecond()
.withDimensionality(2, status).withSIPrefix(UMEASURE_SI_PREFIX_KILO, status);
.withDimensionality(2, status).withPrefix(UMEASURE_PREFIX_KILO, status);
MeasureUnit meterSecond = meter.product(kiloSquareSecond, status);
MeasureUnit cubicMeterSecond1 = meter.withDimensionality(3, status).product(kiloSquareSecond, status);
MeasureUnit centimeterSecond1 = meter.withSIPrefix(UMEASURE_SI_PREFIX_CENTI, status).product(kiloSquareSecond, status);
MeasureUnit centimeterSecond1 = meter.withPrefix(UMEASURE_PREFIX_CENTI, status).product(kiloSquareSecond, status);
MeasureUnit secondCubicMeter = kiloSquareSecond.product(meter.withDimensionality(3, status), status);
MeasureUnit secondCentimeter = kiloSquareSecond.product(meter.withSIPrefix(UMEASURE_SI_PREFIX_CENTI, status), status);
MeasureUnit secondCentimeter = kiloSquareSecond.product(meter.withPrefix(UMEASURE_PREFIX_CENTI, status), status);
MeasureUnit secondCentimeterPerKilometer = secondCentimeter.product(kilometer.reciprocal(status), status);
verifySingleUnit(kiloSquareSecond, UMEASURE_SI_PREFIX_KILO, 2, "square-kilosecond");
verifySingleUnit(kiloSquareSecond, UMEASURE_PREFIX_KILO, 2, "square-kilosecond");
const char* meterSecondSub[] = {"meter", "square-kilosecond"};
verifyCompoundUnit(meterSecond, "meter-square-kilosecond",
meterSecondSub, UPRV_LENGTHOF(meterSecondSub));
@ -3870,20 +3984,20 @@ void MeasureFormatTest::TestCompoundUnitOperations() {
assertTrue("reordering equality", cubicMeterSecond1 == secondCubicMeter);
assertTrue("additional simple units inequality", secondCubicMeter != secondCentimeter);
// Don't allow get/set power or SI prefix on compound units
// Don't allow get/set power or SI or binary prefix on compound units
status.errIfFailureAndReset();
meterSecond.getDimensionality(status);
status.expectErrorAndReset(U_ILLEGAL_ARGUMENT_ERROR);
meterSecond.withDimensionality(3, status);
status.expectErrorAndReset(U_ILLEGAL_ARGUMENT_ERROR);
meterSecond.getSIPrefix(status);
meterSecond.getPrefix(status);
status.expectErrorAndReset(U_ILLEGAL_ARGUMENT_ERROR);
meterSecond.withSIPrefix(UMEASURE_SI_PREFIX_CENTI, status);
meterSecond.withPrefix(UMEASURE_PREFIX_CENTI, status);
status.expectErrorAndReset(U_ILLEGAL_ARGUMENT_ERROR);
// Test that StringPiece does not overflow
MeasureUnit centimeter3 = MeasureUnit::forIdentifier({secondCentimeter.getIdentifier(), 10}, status);
verifySingleUnit(centimeter3, UMEASURE_SI_PREFIX_CENTI, 1, "centimeter");
verifySingleUnit(centimeter3, UMEASURE_PREFIX_CENTI, 1, "centimeter");
assertTrue("string piece equality", centimeter1 == centimeter3);
MeasureUnit footInch = MeasureUnit::forIdentifier("foot-and-inch", status);
@ -3907,19 +4021,19 @@ void MeasureFormatTest::TestCompoundUnitOperations() {
// with others via product:
MeasureUnit kilometer2 = dimensionless.product(kilometer, status);
status.errIfFailureAndReset("dimensionless.product(kilometer, status)");
verifySingleUnit(kilometer2, UMEASURE_SI_PREFIX_KILO, 1, "kilometer");
verifySingleUnit(kilometer2, UMEASURE_PREFIX_KILO, 1, "kilometer");
assertTrue("kilometer equality", kilometer == kilometer2);
// Test out-of-range powers
MeasureUnit power15 = MeasureUnit::forIdentifier("pow15-kilometer", status);
verifySingleUnit(power15, UMEASURE_SI_PREFIX_KILO, 15, "pow15-kilometer");
verifySingleUnit(power15, UMEASURE_PREFIX_KILO, 15, "pow15-kilometer");
status.errIfFailureAndReset();
MeasureUnit power16a = MeasureUnit::forIdentifier("pow16-kilometer", status);
status.expectErrorAndReset(U_ILLEGAL_ARGUMENT_ERROR);
MeasureUnit power16b = power15.product(kilometer, status);
status.expectErrorAndReset(U_ILLEGAL_ARGUMENT_ERROR);
MeasureUnit powerN15 = MeasureUnit::forIdentifier("per-pow15-kilometer", status);
verifySingleUnit(powerN15, UMEASURE_SI_PREFIX_KILO, -15, "per-pow15-kilometer");
verifySingleUnit(powerN15, UMEASURE_PREFIX_KILO, -15, "per-pow15-kilometer");
status.errIfFailureAndReset();
MeasureUnit powerN16a = MeasureUnit::forIdentifier("per-pow16-kilometer", status);
status.expectErrorAndReset(U_ILLEGAL_ARGUMENT_ERROR);
@ -3945,22 +4059,22 @@ void MeasureFormatTest::TestDimensionlessBehaviour() {
MeasureUnit mile = MeasureUnit::getMile();
mile = mile.product(dimensionless, status);
status.errIfFailureAndReset("mile.product(dimensionless, ...)");
verifySingleUnit(mile, UMEASURE_SI_PREFIX_ONE, 1, "mile");
verifySingleUnit(mile, UMEASURE_PREFIX_ONE, 1, "mile");
// dimensionless.getSIPrefix()
UMeasureSIPrefix siPrefix = dimensionless.getSIPrefix(status);
status.errIfFailureAndReset("dimensionless.getSIPrefix(...)");
assertEquals("dimensionless SIPrefix", UMEASURE_SI_PREFIX_ONE, siPrefix);
// dimensionless.getPrefix()
UMeasurePrefix unitPrefix = dimensionless.getPrefix(status);
status.errIfFailureAndReset("dimensionless.getPrefix(...)");
assertEquals("dimensionless SIPrefix", UMEASURE_PREFIX_ONE, unitPrefix);
// dimensionless.withSIPrefix()
modified = dimensionless.withSIPrefix(UMEASURE_SI_PREFIX_KILO, status);
status.errIfFailureAndReset("dimensionless.withSIPrefix(...)");
// dimensionless.withPrefix()
modified = dimensionless.withPrefix(UMEASURE_PREFIX_KILO, status);
status.errIfFailureAndReset("dimensionless.withPrefix(...)");
pair = dimensionless.splitToSingleUnits(status);
count = pair.second;
assertEquals("no singles in modified", 0, count);
siPrefix = modified.getSIPrefix(status);
status.errIfFailureAndReset("modified.getSIPrefix(...)");
assertEquals("modified SIPrefix", UMEASURE_SI_PREFIX_ONE, siPrefix);
unitPrefix = modified.getPrefix(status);
status.errIfFailureAndReset("modified.getPrefix(...)");
assertEquals("modified SIPrefix", UMEASURE_PREFIX_ONE, unitPrefix);
// dimensionless.getComplexity()
UMeasureUnitComplexity complexity = dimensionless.getComplexity(status);
@ -4192,15 +4306,15 @@ void MeasureFormatTest::verifyFormat(
void MeasureFormatTest::verifySingleUnit(
const MeasureUnit& unit,
UMeasureSIPrefix siPrefix,
UMeasurePrefix unitPrefix,
int8_t power,
const char* identifier) {
IcuTestErrorCode status(*this, "verifySingleUnit");
UnicodeString uid(identifier, -1, US_INV);
assertEquals(uid + ": SI prefix",
siPrefix,
unit.getSIPrefix(status));
status.errIfFailureAndReset("%s: SI prefix", identifier);
assertEquals(uid + ": SI or binary prefix",
unitPrefix,
unit.getPrefix(status));
status.errIfFailureAndReset("%s: SI or binary prefix", identifier);
assertEquals(uid + ": Power",
static_cast<int32_t>(power),
static_cast<int32_t>(unit.getDimensionality(status)));

View file

@ -1102,10 +1102,9 @@ void NumberFormatterApiTest::unitSkeletons() {
u"measure-unit/concentr-permille", //
u"permille"},
// // TODO: binary prefixes not supported yet!
// {"Round-trip example from icu-units#35", //
// u"unit/kibijoule-per-furlong", //
// u"unit/kibijoule-per-furlong"},
{"Round-trip example from icu-units#35", //
u"unit/kibijoule-per-furlong", //
u"unit/kibijoule-per-furlong"},
};
for (auto &cas : cases) {
IcuTestErrorCode status(*this, cas.msg);

View file

@ -192,6 +192,14 @@ void UnitsTest::testConverter() {
{"gigabyte", "byte", 1.0, 1000000000},
{"megawatt", "watt", 1.0, 1000000},
{"megawatt", "kilowatt", 1.0, 1000},
// Binary Prefixes
{"kilobyte", "byte", 1, 1000},
{"kibibyte", "byte", 1, 1024},
{"mebibyte", "byte", 1, 1048576},
{"gibibyte", "kibibyte", 1, 1048576},
{"pebibyte", "tebibyte", 4, 4096},
{"zebibyte", "pebibyte", 1.0/16, 65536.0},
{"yobibyte", "exbibyte", 1, 1048576},
// Mass
{"gram", "kilogram", 1.0, 0.001},
{"pound", "kilogram", 1.0, 0.453592},

View file

@ -37,12 +37,16 @@ public class ConversionRates {
* @param singleUnit
* @return
*/
// In ICU4C, this is called loadCompoundFactor().
private UnitConverter.Factor getFactorToBase(SingleUnitImpl singleUnit) {
int power = singleUnit.getDimensionality();
MeasureUnit.SIPrefix siPrefix = singleUnit.getSiPrefix();
MeasureUnit.MeasurePrefix unitPrefix = singleUnit.getPrefix();
UnitConverter.Factor result = UnitConverter.Factor.processFactor(mapToConversionRate.get(singleUnit.getSimpleUnitID()).getConversionRate());
return result.applySiPrefix(siPrefix).power(power); // NOTE: you must apply the SI prefixes before the power.
// Prefix before power, because:
// - square-kilometer to square-meter: (1000)^2
// - square-kilometer to square-foot (approximate): (3.28*1000)^2
return result.applyPrefix(unitPrefix).power(power);
}
public UnitConverter.Factor getFactorToBase(MeasureUnitImpl measureUnit) {
@ -55,6 +59,7 @@ public class ConversionRates {
return result;
}
// In ICU4C, this functionality is found in loadConversionRate().
protected BigDecimal getOffset(MeasureUnitImpl source, MeasureUnitImpl target, UnitConverter.Factor
sourceToBase, UnitConverter.Factor targetToBase, UnitConverter.Convertibility convertibility) {
if (convertibility != UnitConverter.Convertibility.CONVERTIBLE) return BigDecimal.valueOf(0);
@ -124,7 +129,7 @@ public class ConversionRates {
if (measureUnitImpl.getComplexity() != MeasureUnit.Complexity.SINGLE) return false;
SingleUnitImpl singleUnit = measureUnitImpl.getSingleUnits().get(0);
if (singleUnit.getSiPrefix() != MeasureUnit.SIPrefix.ONE) return false;
if (singleUnit.getPrefix() != MeasureUnit.MeasurePrefix.ONE) return false;
if (singleUnit.getDimensionality() != 1) return false;
return true;

View file

@ -388,6 +388,11 @@ public class MeasureUnitImpl {
// * unit", sawAnd is set to true. If not, it is left as is.
private boolean fSawAnd = false;
// Cache the MeasurePrefix values array to make getPrefixFromTrieIndex()
// more efficient
private static MeasureUnit.MeasurePrefix[] measurePrefixValues =
MeasureUnit.MeasurePrefix.values();
private UnitsParser(String identifier) {
this.fSource = identifier;
@ -403,6 +408,11 @@ public class MeasureUnitImpl {
CharsTrieBuilder trieBuilder;
trieBuilder = new CharsTrieBuilder();
// Add SI and binary prefixes
for (MeasureUnit.MeasurePrefix unitPrefix : measurePrefixValues) {
trieBuilder.add(unitPrefix.getIdentifier(), getTrieIndexForPrefix(unitPrefix));
}
// Add syntax parts (compound, power prefixes)
trieBuilder.add("-per-", CompoundPart.PER.getTrieIndex());
trieBuilder.add("-", CompoundPart.TIMES.getTrieIndex());
@ -425,12 +435,6 @@ public class MeasureUnitImpl {
trieBuilder.add("pow14-", PowerPart.P14.getTrieIndex());
trieBuilder.add("pow15-", PowerPart.P15.getTrieIndex());
// Add SI prefixes
for (MeasureUnit.SIPrefix siPrefix :
MeasureUnit.SIPrefix.values()) {
trieBuilder.add(siPrefix.getIdentifier(), getTrieIndex(siPrefix));
}
// Add simple units
String[] simpleUnits = UnitsData.getSimpleUnits();
for (int i = 0; i < simpleUnits.length; i++) {
@ -459,18 +463,12 @@ public class MeasureUnitImpl {
}
private static MeasureUnit.SIPrefix getSiPrefixFromTrieIndex(int trieIndex) {
for (MeasureUnit.SIPrefix element :
MeasureUnit.SIPrefix.values()) {
if (getTrieIndex(element) == trieIndex)
return element;
}
throw new IllegalArgumentException("Incorrect trieIndex");
private static MeasureUnit.MeasurePrefix getPrefixFromTrieIndex(int trieIndex) {
return measurePrefixValues[trieIndex - UnitsData.Constants.kPrefixOffset];
}
private static int getTrieIndex(MeasureUnit.SIPrefix prefix) {
return prefix.getPower() + UnitsData.Constants.kSIPrefixOffset;
private static int getTrieIndexForPrefix(MeasureUnit.MeasurePrefix prefix) {
return prefix.ordinal() + UnitsData.Constants.kPrefixOffset;
}
private MeasureUnitImpl parse() {
@ -524,9 +522,9 @@ public class MeasureUnitImpl {
SingleUnitImpl result = new SingleUnitImpl();
// state:
// 0 = no tokens seen yet (will accept power, SI prefix, or simple unit)
// 0 = no tokens seen yet (will accept power, SI or binary prefix, or simple unit)
// 1 = power token seen (will not accept another power token)
// 2 = SI prefix token seen (will not accept a power or SI prefix token)
// 2 = SI or binary prefix token seen (will not accept a power, or SI or binary prefix token)
int state = 0;
boolean atStart = fIndex == 0;
@ -591,12 +589,12 @@ public class MeasureUnitImpl {
state = 1;
break;
case TYPE_SI_PREFIX:
case TYPE_PREFIX:
if (state > 1) {
throw new IllegalArgumentException();
}
result.setSiPrefix(token.getSIPrefix());
result.setPrefix(token.getPrefix());
state = 2;
break;
@ -674,9 +672,9 @@ public class MeasureUnitImpl {
return this.type;
}
public MeasureUnit.SIPrefix getSIPrefix() {
assert this.type == Type.TYPE_SI_PREFIX;
return getSiPrefixFromTrieIndex(this.fMatch);
public MeasureUnit.MeasurePrefix getPrefix() {
assert this.type == Type.TYPE_PREFIX;
return getPrefixFromTrieIndex(this.fMatch);
}
// Valid only for tokens with type TYPE_COMPOUND_PART.
@ -700,6 +698,7 @@ public class MeasureUnitImpl {
}
public int getSimpleUnitIndex() {
assert this.type == Type.TYPE_SIMPLE_UNIT;
return this.fMatch - UnitsData.Constants.kSimpleUnitOffset;
}
@ -711,7 +710,7 @@ public class MeasureUnitImpl {
}
if (fMatch < UnitsData.Constants.kCompoundPartOffset) {
return Type.TYPE_SI_PREFIX;
return Type.TYPE_PREFIX;
}
if (fMatch < UnitsData.Constants.kInitialCompoundPartOffset) {
return Type.TYPE_COMPOUND_PART;
@ -728,7 +727,7 @@ public class MeasureUnitImpl {
enum Type {
TYPE_UNDEFINED,
TYPE_SI_PREFIX,
TYPE_PREFIX,
// Token type for "-per-", "-", and "-and-".
TYPE_COMPOUND_PART,
// Token type for "per-".

View file

@ -5,6 +5,9 @@ package com.ibm.icu.impl.units;
import com.ibm.icu.util.MeasureUnit;
/**
* A class representing a single unit (optional SI or binary prefix, and dimensionality).
*/
public class SingleUnitImpl {
/**
* Simple unit index, unique for every simple unit, -1 for the dimensionless
@ -29,16 +32,16 @@ public class SingleUnitImpl {
*/
private int dimensionality = 1;
/**
* SI Prefix
* SI or binary prefix.
*/
private MeasureUnit.SIPrefix siPrefix = MeasureUnit.SIPrefix.ONE;
private MeasureUnit.MeasurePrefix unitPrefix = MeasureUnit.MeasurePrefix.ONE;
public SingleUnitImpl copy() {
SingleUnitImpl result = new SingleUnitImpl();
result.index = this.index;
result.dimensionality = this.dimensionality;
result.simpleUnitID = this.simpleUnitID;
result.siPrefix = this.siPrefix;
result.unitPrefix = this.unitPrefix;
return result;
}
@ -71,7 +74,7 @@ public class SingleUnitImpl {
throw new IllegalArgumentException("Unit Identifier Syntax Error");
}
result.append(this.getSiPrefix().getIdentifier());
result.append(this.getPrefix().getIdentifier());
result.append(this.getSimpleUnitID());
return result.toString();
@ -103,10 +106,18 @@ public class SingleUnitImpl {
if (index > other.index) {
return 1;
}
if (this.getSiPrefix().getPower() < other.getSiPrefix().getPower()) {
// TODO(icu-units#70): revisit when fixing normalization. For now we're
// sorting binary prefixes before SI prefixes, for consistency with ICU4C.
if (this.getPrefix().getBase() < other.getPrefix().getBase()) {
return 1;
}
if (this.getPrefix().getBase() > other.getPrefix().getBase()) {
return -1;
}
if (this.getSiPrefix().getPower() > other.getSiPrefix().getPower()) {
if (this.getPrefix().getPower() < other.getPrefix().getPower()) {
return -1;
}
if (this.getPrefix().getPower() > other.getPrefix().getPower()) {
return 1;
}
return 0;
@ -115,8 +126,8 @@ public class SingleUnitImpl {
/**
* Checks whether this SingleUnitImpl is compatible with another for the purpose of coalescing.
* <p>
* Units with the same base unit and SI prefix should match, except that they must also have
* the same dimensionality sign, such that we don't merge numerator and denominator.
* Units with the same base unit and SI or binary prefix should match, except that they must also
* have the same dimensionality sign, such that we don't merge numerator and denominator.
*/
boolean isCompatibleWith(SingleUnitImpl other) {
return (compareTo(other) == 0);
@ -139,12 +150,12 @@ public class SingleUnitImpl {
this.dimensionality = dimensionality;
}
public MeasureUnit.SIPrefix getSiPrefix() {
return siPrefix;
public MeasureUnit.MeasurePrefix getPrefix() {
return unitPrefix;
}
public void setSiPrefix(MeasureUnit.SIPrefix siPrefix) {
this.siPrefix = siPrefix;
public void setPrefix(MeasureUnit.MeasurePrefix unitPrefix) {
this.unitPrefix = unitPrefix;
}
public int getIndex() {

View file

@ -211,20 +211,24 @@ public class UnitConverter {
}
}
public Factor applySiPrefix(MeasureUnit.SIPrefix siPrefix) {
/** Apply SI or binary prefix to the Factor. */
public Factor applyPrefix(MeasureUnit.MeasurePrefix unitPrefix) {
Factor result = this.copy();
if (siPrefix == MeasureUnit.SIPrefix.ONE) {
if (unitPrefix == MeasureUnit.MeasurePrefix.ONE) {
return result;
}
BigDecimal siApplied = BigDecimal.valueOf(Math.pow(10.0, Math.abs(siPrefix.getPower())));
int base = unitPrefix.getBase();
int power = unitPrefix.getPower();
BigDecimal absFactor =
BigDecimal.valueOf(base).pow(Math.abs(power), DECIMAL128);
if (siPrefix.getPower() < 0) {
result.factorDen = this.factorDen.multiply(siApplied);
if (power < 0) {
result.factorDen = this.factorDen.multiply(absFactor);
return result;
}
result.factorNum = this.factorNum.multiply(siApplied);
result.factorNum = this.factorNum.multiply(absFactor);
return result;
}

View file

@ -109,6 +109,9 @@ public class UnitsData {
* Contains all the needed constants.
*/
public static class Constants {
// TODO: consider moving the Trie-offset-related constants into
// MeasureUnitImpl.java, the only place they're being used?
// Trie value offset for simple units, e.g. "gram", "nautical-mile",
// "fluid-ounce-imperial".
public static final int kSimpleUnitOffset = 512;
@ -123,9 +126,9 @@ public class UnitsData {
// Trie value offset for compound parts, e.g. "-per-", "-", "-and-".
public final static int kCompoundPartOffset = 128;
// Trie value offset for SI Prefixes. This is big enough to ensure we only
// insert positive integers into the trie.
public static final int kSIPrefixOffset = 64;
// Trie value offset for SI or binary prefixes. This is big enough to
// ensure we only insert positive integers into the trie.
public static final int kPrefixOffset = 64;
/* Tables Names*/

View file

@ -35,7 +35,6 @@ import com.ibm.icu.text.UnicodeSet;
* A unit such as length, mass, volume, currency, etc. A unit is
* coupled with a numeric amount to produce a Measure. MeasureUnit objects are immutable.
* All subclasses must guarantee that. (However, subclassing is discouraged.)
*
* @see com.ibm.icu.util.Measure
* @author Alan Liu
@ -76,16 +75,16 @@ public class MeasureUnit implements Serializable {
/**
* Enumeration for unit complexity. There are three levels:
* <p>
* - SINGLE: A single unit, optionally with a power and/or SI prefix. Examples: hectare,
* square-kilometer, kilojoule, one-per-second.
* - COMPOUND: A unit composed of the product of multiple single units. Examples:
* meter-per-second, kilowatt-hour, kilogram-meter-per-square-second.
* - MIXED: A unit composed of the sum of multiple single units. Examples: foot-and-inch,
* hour-and-minute-and-second, degree-and-arcminute-and-arcsecond.
* <p>
* <ul>
* <li>SINGLE: A single unit, optionally with a power and/or SI or binary prefix.
* Examples: hectare, square-kilometer, kilojoule, per-second, mebibyte.</li>
* <li>COMPOUND: A unit composed of the product of multiple single units. Examples:
* meter-per-second, kilowatt-hour, kilogram-meter-per-square-second.</li>
* <li>MIXED: A unit composed of the sum of multiple single units. Examples: foot-and-inch,
* hour-and-minute-and-second, degree-and-arcminute-and-arcsecond.</li>
* </ul>
* The complexity determines which operations are available. For example, you cannot set the power
* or SI prefix of a compound unit.
* or prefix of a compound unit.
*
* @draft ICU 68
* @provisional This API might change or be removed in a future release.
@ -117,12 +116,12 @@ public class MeasureUnit implements Serializable {
}
/**
* Enumeration for SI prefixes, such as "kilo".
* Enumeration for SI and binary prefixes, e.g. "kilo-", "nano-", "mebi-".
*
* @draft ICU 68
* @draft ICU 69
* @provisional This API might change or be removed in a future release.
*/
public enum SIPrefix {
public enum MeasurePrefix {
/**
* SI prefix: yotta, 10^24.
@ -130,7 +129,7 @@ public class MeasureUnit implements Serializable {
* @draft ICU 68
* @provisional This API might change or be removed in a future release.
*/
YOTTA(24, "yotta"),
YOTTA(24, "yotta", 10),
/**
* SI prefix: zetta, 10^21.
@ -138,7 +137,7 @@ public class MeasureUnit implements Serializable {
* @draft ICU 68
* @provisional This API might change or be removed in a future release.
*/
ZETTA(21, "zetta"),
ZETTA(21, "zetta", 10),
/**
* SI prefix: exa, 10^18.
@ -146,7 +145,7 @@ public class MeasureUnit implements Serializable {
* @draft ICU 68
* @provisional This API might change or be removed in a future release.
*/
EXA(18, "exa"),
EXA(18, "exa", 10),
/**
* SI prefix: peta, 10^15.
@ -154,7 +153,7 @@ public class MeasureUnit implements Serializable {
* @draft ICU 68
* @provisional This API might change or be removed in a future release.
*/
PETA(15, "peta"),
PETA(15, "peta", 10),
/**
* SI prefix: tera, 10^12.
@ -162,7 +161,7 @@ public class MeasureUnit implements Serializable {
* @draft ICU 68
* @provisional This API might change or be removed in a future release.
*/
TERA(12, "tera"),
TERA(12, "tera", 10),
/**
* SI prefix: giga, 10^9.
@ -170,7 +169,7 @@ public class MeasureUnit implements Serializable {
* @draft ICU 68
* @provisional This API might change or be removed in a future release.
*/
GIGA(9, "giga"),
GIGA(9, "giga", 10),
/**
* SI prefix: mega, 10^6.
@ -178,7 +177,7 @@ public class MeasureUnit implements Serializable {
* @draft ICU 68
* @provisional This API might change or be removed in a future release.
*/
MEGA(6, "mega"),
MEGA(6, "mega", 10),
/**
* SI prefix: kilo, 10^3.
@ -186,7 +185,7 @@ public class MeasureUnit implements Serializable {
* @draft ICU 68
* @provisional This API might change or be removed in a future release.
*/
KILO(3, "kilo"),
KILO(3, "kilo", 10),
/**
* SI prefix: hecto, 10^2.
@ -194,7 +193,7 @@ public class MeasureUnit implements Serializable {
* @draft ICU 68
* @provisional This API might change or be removed in a future release.
*/
HECTO(2, "hecto"),
HECTO(2, "hecto", 10),
/**
* SI prefix: deka, 10^1.
@ -202,7 +201,7 @@ public class MeasureUnit implements Serializable {
* @draft ICU 68
* @provisional This API might change or be removed in a future release.
*/
DEKA(1, "deka"),
DEKA(1, "deka", 10),
/**
* The absence of an SI prefix.
@ -210,7 +209,7 @@ public class MeasureUnit implements Serializable {
* @draft ICU 68
* @provisional This API might change or be removed in a future release.
*/
ONE(0, ""),
ONE(0, "", 10),
/**
* SI prefix: deci, 10^-1.
@ -218,7 +217,7 @@ public class MeasureUnit implements Serializable {
* @draft ICU 68
* @provisional This API might change or be removed in a future release.
*/
DECI(-1, "deci"),
DECI(-1, "deci", 10),
/**
* SI prefix: centi, 10^-2.
@ -226,7 +225,7 @@ public class MeasureUnit implements Serializable {
* @draft ICU 68
* @provisional This API might change or be removed in a future release.
*/
CENTI(-2, "centi"),
CENTI(-2, "centi", 10),
/**
* SI prefix: milli, 10^-3.
@ -234,7 +233,7 @@ public class MeasureUnit implements Serializable {
* @draft ICU 68
* @provisional This API might change or be removed in a future release.
*/
MILLI(-3, "milli"),
MILLI(-3, "milli", 10),
/**
* SI prefix: micro, 10^-6.
@ -242,7 +241,7 @@ public class MeasureUnit implements Serializable {
* @draft ICU 68
* @provisional This API might change or be removed in a future release.
*/
MICRO(-6, "micro"),
MICRO(-6, "micro", 10),
/**
* SI prefix: nano, 10^-9.
@ -250,7 +249,7 @@ public class MeasureUnit implements Serializable {
* @draft ICU 68
* @provisional This API might change or be removed in a future release.
*/
NANO(-9, "nano"),
NANO(-9, "nano", 10),
/**
* SI prefix: pico, 10^-12.
@ -258,7 +257,7 @@ public class MeasureUnit implements Serializable {
* @draft ICU 68
* @provisional This API might change or be removed in a future release.
*/
PICO(-12, "pico"),
PICO(-12, "pico", 10),
/**
* SI prefix: femto, 10^-15.
@ -266,7 +265,7 @@ public class MeasureUnit implements Serializable {
* @draft ICU 68
* @provisional This API might change or be removed in a future release.
*/
FEMTO(-15, "femto"),
FEMTO(-15, "femto", 10),
/**
* SI prefix: atto, 10^-18.
@ -274,7 +273,7 @@ public class MeasureUnit implements Serializable {
* @draft ICU 68
* @provisional This API might change or be removed in a future release.
*/
ATTO(-18, "atto"),
ATTO(-18, "atto", 10),
/**
* SI prefix: zepto, 10^-21.
@ -282,7 +281,7 @@ public class MeasureUnit implements Serializable {
* @draft ICU 68
* @provisional This API might change or be removed in a future release.
*/
ZEPTO(-21, "zepto"),
ZEPTO(-21, "zepto", 10),
/**
* SI prefix: yocto, 10^-24.
@ -290,12 +289,78 @@ public class MeasureUnit implements Serializable {
* @draft ICU 68
* @provisional This API might change or be removed in a future release.
*/
YOCTO(-24, "yocto");
YOCTO(-24, "yocto", 10),
/**
* IEC binary prefix: kibi, 1024^1.
*
* @draft ICU 69
* @provisional This API might change or be removed in a future release.
*/
KIBI(1, "kibi", 1024),
/**
* IEC binary prefix: mebi, 1024^2.
*
* @draft ICU 69
* @provisional This API might change or be removed in a future release.
*/
MEBI(2, "mebi", 1024),
/**
* IEC binary prefix: gibi, 1024^3.
*
* @draft ICU 69
* @provisional This API might change or be removed in a future release.
*/
GIBI(3, "gibi", 1024),
/**
* IEC binary prefix: tebi, 1024^4.
*
* @draft ICU 69
* @provisional This API might change or be removed in a future release.
*/
TEBI(4, "tebi", 1024),
/**
* IEC binary prefix: pebi, 1024^5.
*
* @draft ICU 69
* @provisional This API might change or be removed in a future release.
*/
PEBI(5, "pebi", 1024),
/**
* IEC binary prefix: exbi, 1024^6.
*
* @draft ICU 69
* @provisional This API might change or be removed in a future release.
*/
EXBI(6, "exbi", 1024),
/**
* IEC binary prefix: zebi, 1024^7.
*
* @draft ICU 69
* @provisional This API might change or be removed in a future release.
*/
ZEBI(7, "zebi", 1024),
/**
* IEC binary prefix: yobi, 1024^8.
*
* @draft ICU 69
* @provisional This API might change or be removed in a future release.
*/
YOBI(8, "yobi", 1024);
private final int base;
private final int power;
private final String identifier;
SIPrefix(int power, String identifier) {
MeasurePrefix(int power, String identifier, int base) {
this.base = base;
this.power = power;
this.identifier = identifier;
}
@ -312,9 +377,23 @@ public class MeasureUnit implements Serializable {
}
/**
* Returns the power of 10 of the prefix. For example, if the prefix is "centi", the power will be -2.
* Returns the base of the prefix. For example:
* - if the prefix is "centi", the base will be 10.
* - if the prefix is "gibi", the base will be 1024.
*
* @draft ICU 68
* @draft ICU 69
* @provisional This API might change or be removed in a future release.
*/
public int getBase() {
return base;
}
/**
* Returns the power of the prefix. For example:
* - if the prefix is "centi", the power will be -2.
* - if the prefix is "gibi", the power will be 3 (for base 1024).
*
* @draft ICU 69
* @provisional This API might change or be removed in a future release.
*/
public int getPower() {
@ -426,41 +505,41 @@ public class MeasureUnit implements Serializable {
}
/**
* Creates a MeasureUnit which is this SINGLE unit augmented with the specified SI prefix.
* For example, SI_PREFIX_KILO for "kilo".
* May return this if this unit already has that prefix.
* Creates a MeasureUnit which is this SINGLE unit augmented with the specified prefix.
* For example, MeasurePrefix.KILO for "kilo", or MeasurePrefix.KIBI for "kibi".
* May return `this` if this unit already has that prefix.
* <p>
* There is sufficient locale data to format all standard SI prefixes.
* There is sufficient locale data to format all standard prefixes.
* <p>
* NOTE: Only works on SINGLE units. If this is a COMPOUND or MIXED unit, an error will
* occur. For more information, see `Complexity`.
*
* @param prefix The SI prefix, from SIPrefix.
* @param prefix The prefix, from MeasurePrefix.
* @return A new SINGLE unit.
* @throws UnsupportedOperationException if this unit is a COMPOUND or MIXED unit.
* @draft ICU 68
* @draft ICU 69
* @provisional This API might change or be removed in a future release.
*/
public MeasureUnit withSIPrefix(SIPrefix prefix) {
public MeasureUnit withPrefix(MeasurePrefix prefix) {
SingleUnitImpl singleUnit = getSingleUnitImpl();
singleUnit.setSiPrefix(prefix);
singleUnit.setPrefix(prefix);
return singleUnit.build();
}
/**
* Returns the current SI prefix of this SINGLE unit. For example, if the unit has the SI prefix
* "kilo", then SI_PREFIX_KILO is returned.
* Returns the current SI or binary prefix of this SINGLE unit. For example,
* if the unit has the prefix "kilo", then MeasurePrefix.KILO is returned.
* <p>
* NOTE: Only works on SINGLE units. If this is a COMPOUND or MIXED unit, an error will
* occur. For more information, see `Complexity`.
* NOTE: Only works on SINGLE units. If this is a COMPOUND or MIXED unit, an
* error will occur. For more information, see `Complexity`.
*
* @return The SI prefix of this SINGLE unit, from SIPrefix.
* @return The prefix of this SINGLE unit, from MeasurePrefix.
* @throws UnsupportedOperationException if the unit is COMPOUND or MIXED.
* @draft ICU 68
* @draft ICU 69
* @provisional This API might change or be removed in a future release.
*/
public SIPrefix getSIPrefix() {
return getSingleUnitImpl().getSiPrefix();
public MeasurePrefix getPrefix() {
return getSingleUnitImpl().getPrefix();
}
/**
@ -560,7 +639,7 @@ public class MeasureUnit implements Serializable {
* <p>
* Examples:
* - Given "meter-kilogram-per-second", three units will be returned: "meter",
* "kilogram", and "one-per-second".
* "kilogram", and "per-second".
* - Given "hour+minute+second", three units will be returned: "hour", "minute",
* and "second".
* <p>
@ -1982,6 +2061,7 @@ public class MeasureUnit implements Serializable {
* @return this object as a SingleUnitImpl.
* @throws UnsupportedOperationException if this object could not be converted to a single unit.
*/
// In ICU4C, this is SingleUnitImpl::forMeasureUnit().
private SingleUnitImpl getSingleUnitImpl() {
if (measureUnitImpl == null) {
return MeasureUnitImpl.forIdentifier(getIdentifier()).getSingleUnitImpl();

View file

@ -3481,27 +3481,61 @@ public class MeasureUnitTest extends TestFmwk {
}
TestCase cases[] = {
// Correctly normalized identifiers should not change
new TestCase("square-meter-per-square-meter", "square-meter-per-square-meter"),
new TestCase("kilogram-meter-per-square-meter-square-second",
"kilogram-meter-per-square-meter-square-second"),
new TestCase("square-mile-and-square-foot", "square-mile-and-square-foot"),
new TestCase("square-foot-and-square-mile", "square-foot-and-square-mile"),
new TestCase("per-cubic-centimeter", "per-cubic-centimeter"),
new TestCase("per-kilometer", "per-kilometer"),
// Correctly normalized identifiers should not change
new TestCase("square-meter-per-square-meter", "square-meter-per-square-meter"),
new TestCase("kilogram-meter-per-square-meter-square-second",
"kilogram-meter-per-square-meter-square-second"),
new TestCase("square-mile-and-square-foot", "square-mile-and-square-foot"),
new TestCase("square-foot-and-square-mile", "square-foot-and-square-mile"),
new TestCase("per-cubic-centimeter", "per-cubic-centimeter"),
new TestCase("per-kilometer", "per-kilometer"),
// Normalization of power and per
new TestCase(
"pow2-foot-and-pow2-mile", "square-foot-and-square-mile"),
new TestCase(
"gram-square-gram-per-dekagram", "cubic-gram-per-dekagram"),
new TestCase(
"kilogram-per-meter-per-second", "kilogram-per-meter-second"),
// Normalization of power and per
new TestCase("pow2-foot-and-pow2-mile", "square-foot-and-square-mile"),
new TestCase("gram-square-gram-per-dekagram", "cubic-gram-per-dekagram"),
new TestCase("kilogram-per-meter-per-second", "kilogram-per-meter-second"),
// TODO(ICU-21284): Add more test cases once the proper ranking is available.
// TODO(ICU-21284): Add more test cases once the proper ranking is available.
// Testing prefixes are parsed and produced correctly (ensures no
// collisions in the enum values)
new TestCase("yoctofoot", "yoctofoot"),
new TestCase("zeptofoot", "zeptofoot"),
new TestCase("attofoot", "attofoot"),
new TestCase("femtofoot", "femtofoot"),
new TestCase("picofoot", "picofoot"),
new TestCase("nanofoot", "nanofoot"),
new TestCase("microfoot", "microfoot"),
new TestCase("millifoot", "millifoot"),
new TestCase("centifoot", "centifoot"),
new TestCase("decifoot", "decifoot"),
new TestCase("foot", "foot"),
new TestCase("dekafoot", "dekafoot"),
new TestCase("hectofoot", "hectofoot"),
new TestCase("kilofoot", "kilofoot"),
new TestCase("megafoot", "megafoot"),
new TestCase("gigafoot", "gigafoot"),
new TestCase("terafoot", "terafoot"),
new TestCase("petafoot", "petafoot"),
new TestCase("exafoot", "exafoot"),
new TestCase("zettafoot", "zettafoot"),
new TestCase("yottafoot", "yottafoot"),
new TestCase("kibibyte", "kibibyte"),
new TestCase("mebibyte", "mebibyte"),
new TestCase("gibibyte", "gibibyte"),
new TestCase("tebibyte", "tebibyte"),
new TestCase("pebibyte", "pebibyte"),
new TestCase("exbibyte", "exbibyte"),
new TestCase("zebibyte", "zebibyte"),
new TestCase("yobibyte", "yobibyte"),
// Testing sort order of prefixes.
//
// TODO(icu-units#70): revisit when fixing normalization. For now we're
// just checking some consistency between C&J.
new TestCase("megafoot-mebifoot-kibifoot-kilofoot", "kibifoot-mebifoot-kilofoot-megafoot"),
};
for (TestCase testCase : cases) {
MeasureUnit unit = MeasureUnit.forIdentifier(testCase.id);
@ -3562,22 +3596,120 @@ public class MeasureUnitTest extends TestFmwk {
}
}
@Test
public void TestIdentifierDetails() {
MeasureUnit joule = MeasureUnit.forIdentifier("joule");
assertEquals("Initial joule", "joule", joule.getIdentifier());
// "Invalid prefix" test not needed: in Java we cannot pass a
// non-existant enum instance. (In C++ an int can be typecast.)
MeasureUnit unit = joule.withPrefix(MeasureUnit.MeasurePrefix.HECTO);
assertEquals("Joule with hecto prefix", "hectojoule", unit.getIdentifier());
unit = unit.withPrefix(MeasureUnit.MeasurePrefix.EXBI);
assertEquals("Joule with exbi prefix", "exbijoule", unit.getIdentifier());
}
@Test
public void TestPrefixes() {
class TestCase {
final MeasureUnit.MeasurePrefix prefix;
final int expectedBase;
final int expectedPower;
TestCase(MeasureUnit.MeasurePrefix prefix, int expectedBase, int expectedPower) {
this.prefix = prefix;
this.expectedBase = expectedBase;
this.expectedPower = expectedPower;
}
}
TestCase cases[] = {
new TestCase(MeasureUnit.MeasurePrefix.YOCTO, 10, -24),
new TestCase(MeasureUnit.MeasurePrefix.ZEPTO, 10, -21),
new TestCase(MeasureUnit.MeasurePrefix.ATTO, 10, -18),
new TestCase(MeasureUnit.MeasurePrefix.FEMTO, 10, -15),
new TestCase(MeasureUnit.MeasurePrefix.PICO, 10, -12),
new TestCase(MeasureUnit.MeasurePrefix.NANO, 10, -9),
new TestCase(MeasureUnit.MeasurePrefix.MICRO, 10, -6),
new TestCase(MeasureUnit.MeasurePrefix.MILLI, 10, -3),
new TestCase(MeasureUnit.MeasurePrefix.CENTI, 10, -2),
new TestCase(MeasureUnit.MeasurePrefix.DECI, 10, -1),
new TestCase(MeasureUnit.MeasurePrefix.ONE, 10, 0),
new TestCase(MeasureUnit.MeasurePrefix.DEKA, 10, 1),
new TestCase(MeasureUnit.MeasurePrefix.HECTO, 10, 2),
new TestCase(MeasureUnit.MeasurePrefix.KILO, 10, 3),
new TestCase(MeasureUnit.MeasurePrefix.MEGA, 10, 6),
new TestCase(MeasureUnit.MeasurePrefix.GIGA, 10, 9),
new TestCase(MeasureUnit.MeasurePrefix.TERA, 10, 12),
new TestCase(MeasureUnit.MeasurePrefix.PETA, 10, 15),
new TestCase(MeasureUnit.MeasurePrefix.EXA, 10, 18),
new TestCase(MeasureUnit.MeasurePrefix.ZETTA, 10, 21),
new TestCase(MeasureUnit.MeasurePrefix.YOTTA, 10, 24),
new TestCase(MeasureUnit.MeasurePrefix.KIBI, 1024, 1),
new TestCase(MeasureUnit.MeasurePrefix.MEBI, 1024, 2),
new TestCase(MeasureUnit.MeasurePrefix.GIBI, 1024, 3),
new TestCase(MeasureUnit.MeasurePrefix.TEBI, 1024, 4),
new TestCase(MeasureUnit.MeasurePrefix.PEBI, 1024, 5),
new TestCase(MeasureUnit.MeasurePrefix.EXBI, 1024, 6),
new TestCase(MeasureUnit.MeasurePrefix.ZEBI, 1024, 7),
new TestCase(MeasureUnit.MeasurePrefix.YOBI, 1024, 8),
};
for (TestCase testCase : cases) {
MeasureUnit m = MeasureUnit.AMPERE.withPrefix(testCase.prefix);
assertEquals("getPrefixPower()", testCase.expectedPower, m.getPrefix().getPower());
assertEquals("getPrefixBase()", testCase.expectedBase, m.getPrefix().getBase());
}
}
@Test
public void TestParseToBuiltIn() {
class TestCase {
final String identifier;
MeasureUnit expectedBuiltin;
TestCase(String identifier, MeasureUnit expectedBuiltin) {
this.identifier = identifier;
this.expectedBuiltin = expectedBuiltin;
}
}
TestCase cases[] = {
new TestCase("meter-per-second-per-second", MeasureUnit.METER_PER_SECOND_SQUARED),
new TestCase("meter-per-second-second", MeasureUnit.METER_PER_SECOND_SQUARED),
new TestCase("centimeter-centimeter", MeasureUnit.SQUARE_CENTIMETER),
new TestCase("square-foot", MeasureUnit.SQUARE_FOOT),
new TestCase("pow2-inch", MeasureUnit.SQUARE_INCH),
new TestCase("milligram-per-deciliter", MeasureUnit.MILLIGRAM_PER_DECILITER),
new TestCase("pound-force-per-pow2-inch", MeasureUnit.POUND_PER_SQUARE_INCH),
new TestCase("yard-pow2-yard", MeasureUnit.CUBIC_YARD),
new TestCase("square-yard-yard", MeasureUnit.CUBIC_YARD),
};
for (TestCase testCase : cases) {
MeasureUnit m = MeasureUnit.forIdentifier(testCase.identifier);
assertTrue(testCase.identifier + " parsed to builtin", m.equals(testCase.expectedBuiltin));
}
}
@Test
public void TestCompoundUnitOperations() {
MeasureUnit.forIdentifier("kilometer-per-second-joule");
MeasureUnit kilometer = MeasureUnit.KILOMETER;
MeasureUnit cubicMeter = MeasureUnit.CUBIC_METER;
MeasureUnit meter = kilometer.withSIPrefix(MeasureUnit.SIPrefix.ONE);
MeasureUnit centimeter1 = kilometer.withSIPrefix(MeasureUnit.SIPrefix.CENTI);
MeasureUnit centimeter2 = meter.withSIPrefix(MeasureUnit.SIPrefix.CENTI);
MeasureUnit cubicDecimeter = cubicMeter.withSIPrefix(MeasureUnit.SIPrefix.DECI);
MeasureUnit meter = kilometer.withPrefix(MeasureUnit.MeasurePrefix.ONE);
MeasureUnit centimeter1 = kilometer.withPrefix(MeasureUnit.MeasurePrefix.CENTI);
MeasureUnit centimeter2 = meter.withPrefix(MeasureUnit.MeasurePrefix.CENTI);
MeasureUnit cubicDecimeter = cubicMeter.withPrefix(MeasureUnit.MeasurePrefix.DECI);
verifySingleUnit(kilometer, MeasureUnit.SIPrefix.KILO, 1, "kilometer");
verifySingleUnit(meter, MeasureUnit.SIPrefix.ONE, 1, "meter");
verifySingleUnit(centimeter1, MeasureUnit.SIPrefix.CENTI, 1, "centimeter");
verifySingleUnit(centimeter2, MeasureUnit.SIPrefix.CENTI, 1, "centimeter");
verifySingleUnit(cubicDecimeter, MeasureUnit.SIPrefix.DECI, 3, "cubic-decimeter");
verifySingleUnit(kilometer, MeasureUnit.MeasurePrefix.KILO, 1, "kilometer");
verifySingleUnit(meter, MeasureUnit.MeasurePrefix.ONE, 1, "meter");
verifySingleUnit(centimeter1, MeasureUnit.MeasurePrefix.CENTI, 1, "centimeter");
verifySingleUnit(centimeter2, MeasureUnit.MeasurePrefix.CENTI, 1, "centimeter");
verifySingleUnit(cubicDecimeter, MeasureUnit.MeasurePrefix.DECI, 3, "cubic-decimeter");
assertTrue("centimeter equality", centimeter1.equals( centimeter2));
assertTrue("kilometer inequality", !centimeter1.equals( kilometer));
@ -3587,10 +3719,10 @@ public class MeasureUnitTest extends TestFmwk {
MeasureUnit quarticKilometer = kilometer.withDimensionality(4);
MeasureUnit overQuarticKilometer1 = kilometer.withDimensionality(-4);
verifySingleUnit(squareMeter, MeasureUnit.SIPrefix.ONE, 2, "square-meter");
verifySingleUnit(overCubicCentimeter, MeasureUnit.SIPrefix.CENTI, -3, "per-cubic-centimeter");
verifySingleUnit(quarticKilometer, MeasureUnit.SIPrefix.KILO, 4, "pow4-kilometer");
verifySingleUnit(overQuarticKilometer1, MeasureUnit.SIPrefix.KILO, -4, "per-pow4-kilometer");
verifySingleUnit(squareMeter, MeasureUnit.MeasurePrefix.ONE, 2, "square-meter");
verifySingleUnit(overCubicCentimeter, MeasureUnit.MeasurePrefix.CENTI, -3, "per-cubic-centimeter");
verifySingleUnit(quarticKilometer, MeasureUnit.MeasurePrefix.KILO, 4, "pow4-kilometer");
verifySingleUnit(overQuarticKilometer1, MeasureUnit.MeasurePrefix.KILO, -4, "per-pow4-kilometer");
assertTrue("power inequality", quarticKilometer != overQuarticKilometer1);
@ -3601,26 +3733,26 @@ public class MeasureUnitTest extends TestFmwk {
.reciprocal();
MeasureUnit overQuarticKilometer4 = meter.withDimensionality(4)
.reciprocal()
.withSIPrefix(MeasureUnit.SIPrefix.KILO);
.withPrefix(MeasureUnit.MeasurePrefix.KILO);
verifySingleUnit(overQuarticKilometer2, MeasureUnit.SIPrefix.KILO, -4, "per-pow4-kilometer");
verifySingleUnit(overQuarticKilometer3, MeasureUnit.SIPrefix.KILO, -4, "per-pow4-kilometer");
verifySingleUnit(overQuarticKilometer4, MeasureUnit.SIPrefix.KILO, -4, "per-pow4-kilometer");
verifySingleUnit(overQuarticKilometer2, MeasureUnit.MeasurePrefix.KILO, -4, "per-pow4-kilometer");
verifySingleUnit(overQuarticKilometer3, MeasureUnit.MeasurePrefix.KILO, -4, "per-pow4-kilometer");
verifySingleUnit(overQuarticKilometer4, MeasureUnit.MeasurePrefix.KILO, -4, "per-pow4-kilometer");
assertTrue("reciprocal equality", overQuarticKilometer1.equals(overQuarticKilometer2));
assertTrue("reciprocal equality", overQuarticKilometer1.equals(overQuarticKilometer3));
assertTrue("reciprocal equality", overQuarticKilometer1.equals(overQuarticKilometer4));
MeasureUnit kiloSquareSecond = MeasureUnit.SECOND
.withDimensionality(2).withSIPrefix(MeasureUnit.SIPrefix.KILO);
.withDimensionality(2).withPrefix(MeasureUnit.MeasurePrefix.KILO);
MeasureUnit meterSecond = meter.product(kiloSquareSecond);
MeasureUnit cubicMeterSecond1 = meter.withDimensionality(3).product(kiloSquareSecond);
MeasureUnit centimeterSecond1 = meter.withSIPrefix(MeasureUnit.SIPrefix.CENTI).product(kiloSquareSecond);
MeasureUnit centimeterSecond1 = meter.withPrefix(MeasureUnit.MeasurePrefix.CENTI).product(kiloSquareSecond);
MeasureUnit secondCubicMeter = kiloSquareSecond.product(meter.withDimensionality(3));
MeasureUnit secondCentimeter = kiloSquareSecond.product(meter.withSIPrefix(MeasureUnit.SIPrefix.CENTI));
MeasureUnit secondCentimeter = kiloSquareSecond.product(meter.withPrefix(MeasureUnit.MeasurePrefix.CENTI));
MeasureUnit secondCentimeterPerKilometer = secondCentimeter.product(kilometer.reciprocal());
verifySingleUnit(kiloSquareSecond, MeasureUnit.SIPrefix.KILO, 2, "square-kilosecond");
verifySingleUnit(kiloSquareSecond, MeasureUnit.MeasurePrefix.KILO, 2, "square-kilosecond");
String meterSecondSub[] = {
"meter", "square-kilosecond"
};
@ -3655,7 +3787,7 @@ public class MeasureUnitTest extends TestFmwk {
assertTrue("reordering equality", cubicMeterSecond1.equals(secondCubicMeter));
assertTrue("additional simple units inequality", !secondCubicMeter.equals(secondCentimeter));
// Don't allow get/set power or SI prefix on compound units
// Don't allow get/set power or SI or binary prefix on compound units
try {
meterSecond.getDimensionality();
fail("UnsupportedOperationException must be thrown");
@ -3671,14 +3803,14 @@ public class MeasureUnitTest extends TestFmwk {
}
try {
meterSecond.getSIPrefix();
meterSecond.getPrefix();
fail("UnsupportedOperationException must be thrown");
} catch (UnsupportedOperationException e) {
// Expecting an exception to be thrown
}
try {
meterSecond.withSIPrefix(MeasureUnit.SIPrefix.CENTI);
meterSecond.withPrefix(MeasureUnit.MeasurePrefix.CENTI);
fail("UnsupportedOperationException must be thrown");
} catch (UnsupportedOperationException e) {
// Expecting an exception to be thrown
@ -3709,12 +3841,12 @@ public class MeasureUnitTest extends TestFmwk {
// with others via product:
MeasureUnit kilometer2 = kilometer.product(dimensionless);
verifySingleUnit(kilometer2, MeasureUnit.SIPrefix.KILO, 1, "kilometer");
verifySingleUnit(kilometer2, MeasureUnit.MeasurePrefix.KILO, 1, "kilometer");
assertTrue("kilometer equality", kilometer.equals(kilometer2));
// Test out-of-range powers
MeasureUnit power15 = MeasureUnit.forIdentifier("pow15-kilometer");
verifySingleUnit(power15, MeasureUnit.SIPrefix.KILO, 15, "pow15-kilometer");
verifySingleUnit(power15, MeasureUnit.MeasurePrefix.KILO, 15, "pow15-kilometer");
try {
MeasureUnit.forIdentifier("pow16-kilometer");
@ -3731,7 +3863,7 @@ public class MeasureUnitTest extends TestFmwk {
}
MeasureUnit powerN15 = MeasureUnit.forIdentifier("per-pow15-kilometer");
verifySingleUnit(powerN15, MeasureUnit.SIPrefix.KILO, -15, "per-pow15-kilometer");
verifySingleUnit(powerN15, MeasureUnit.MeasurePrefix.KILO, -15, "per-pow15-kilometer");
try {
MeasureUnit.forIdentifier("per-pow16-kilometer");
@ -3762,11 +3894,11 @@ public class MeasureUnitTest extends TestFmwk {
// product(dimensionless)
MeasureUnit mile = MeasureUnit.MILE;
mile = mile.product(dimensionless);
verifySingleUnit(mile, MeasureUnit.SIPrefix.ONE, 1, "mile");
verifySingleUnit(mile, MeasureUnit.MeasurePrefix.ONE, 1, "mile");
}
private void verifySingleUnit(MeasureUnit singleMeasureUnit, MeasureUnit.SIPrefix prefix, int power, String identifier) {
assertEquals(identifier + ": SI prefix", prefix, singleMeasureUnit.getSIPrefix());
private void verifySingleUnit(MeasureUnit singleMeasureUnit, MeasureUnit.MeasurePrefix prefix, int power, String identifier) {
assertEquals(identifier + ": SI or binary prefix", prefix, singleMeasureUnit.getPrefix());
assertEquals(identifier + ": Power", power, singleMeasureUnit.getDimensionality());
@ -3805,12 +3937,12 @@ public class MeasureUnitTest extends TestFmwk {
assertEquals("nanogram", null, nanogram.getType());
assertEquals("nanogram", "nanogram", nanogram.getIdentifier());
assertEquals("prefix of kilogram", MeasureUnit.SIPrefix.KILO, kilogram.getSIPrefix());
assertEquals("prefix of gram", MeasureUnit.SIPrefix.ONE, gram.getSIPrefix());
assertEquals("prefix of microgram", MeasureUnit.SIPrefix.MICRO, microgram.getSIPrefix());
assertEquals("prefix of nanogram", MeasureUnit.SIPrefix.NANO, nanogram.getSIPrefix());
assertEquals("prefix of kilogram", MeasureUnit.MeasurePrefix.KILO, kilogram.getPrefix());
assertEquals("prefix of gram", MeasureUnit.MeasurePrefix.ONE, gram.getPrefix());
assertEquals("prefix of microgram", MeasureUnit.MeasurePrefix.MICRO, microgram.getPrefix());
assertEquals("prefix of nanogram", MeasureUnit.MeasurePrefix.NANO, nanogram.getPrefix());
MeasureUnit tmp = kilogram.withSIPrefix(MeasureUnit.SIPrefix.MILLI);
MeasureUnit tmp = kilogram.withPrefix(MeasureUnit.MeasurePrefix.MILLI);
assertEquals("Kilogram + milli should be milligram, got: " + tmp.getIdentifier(),
MeasureUnit.MILLIGRAM.getIdentifier(), tmp.getIdentifier());
}

View file

@ -296,6 +296,14 @@ public class UnitsTest {
new TestData("gigabyte", "byte", 1.0, 1000000000),
new TestData("megawatt", "watt", 1.0, 1000000),
new TestData("megawatt", "kilowatt", 1.0, 1000),
// Binary Prefixes
new TestData("kilobyte", "byte", 1, 1000),
new TestData("kibibyte", "byte", 1, 1024),
new TestData("mebibyte", "byte", 1, 1048576),
new TestData("gibibyte", "kibibyte", 1, 1048576),
new TestData("pebibyte", "tebibyte", 4, 4096),
new TestData("zebibyte", "pebibyte", 1.0/16, 65536.0),
new TestData("yobibyte", "exbibyte", 1, 1048576),
// Mass
new TestData("gram", "kilogram", 1.0, 0.001),
new TestData("pound", "kilogram", 1.0, 0.453592),
@ -333,7 +341,13 @@ public class UnitsTest {
ConversionRates conversionRates = new ConversionRates();
for (TestData test : tests) {
UnitConverter converter = new UnitConverter(test.source, test.target, conversionRates);
assertEquals(test.expected.doubleValue(), converter.convert(test.input).doubleValue(), (0.001));
double maxDelta = 1e-6 * Math.abs(test.expected.doubleValue());
if (test.expected.doubleValue() == 0) {
maxDelta = 1e-12;
}
assertEquals("testConverter: " + test.source + " to " + test.target,
test.expected.doubleValue(), converter.convert(test.input).doubleValue(),
maxDelta);
}
}

View file

@ -1067,10 +1067,9 @@ public class NumberFormatterApiTest extends TestFmwk {
"measure-unit/concentr-permille", //
"permille"},
// // TODO: binary prefixes not supported yet!
// {"Round-trip example from icu-units#35", //
// "unit/kibijoule-per-furlong", //
// "unit/kibijoule-per-furlong"},
{"Round-trip example from icu-units#35", //
"unit/kibijoule-per-furlong", //
"unit/kibijoule-per-furlong"},
};
for (Object[] cas : cases) {
String msg = (String)cas[0];