// 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-"
// "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 },
};
/**
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; }
enum Type {
TYPE_UNDEFINED,
- TYPE_SI_PREFIX,
+ TYPE_PREFIX,
// Token type for "-per-", "-", and "-and-".
TYPE_COMPOUND_PART,
// Token type for "per-".
Type getType() const {
U_ASSERT(fMatch > 0);
if (fMatch < kCompoundPartOffset) {
- return TYPE_SI_PREFIX;
+ return TYPE_PREFIX;
}
if (fMatch < kInitialCompoundPartOffset) {
return TYPE_COMPOUND_PART;
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.
}
// 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;
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;
}
};
+// 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);
} // 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);
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);
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);
}
};
/**
- * 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 {
/**
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;
/**
* 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);
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.
/**
* 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
*/
};
/**
- * 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
- */
- UMEASURE_SI_PREFIX_DEKA = 1,
-
- /**
- * The absence of an SI prefix.
- *
- * @draft ICU 67
+ * @draft ICU 69
*/
- 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_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_SI_PREFIX_YOCTO = -24
-} UMeasureSIPrefix;
+ 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
/**
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
}
}
-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() {
}
// Load Factor of a compound source unit.
+// In ICU4J, this is a pair of ConversionRates.getFactorToBase() functions.
Factor loadCompoundFactor(const MeasureUnitImpl &source, const ConversionRates &ratesInfo,
UErrorCode &status) {
Factor 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);
*
* 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;
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;
}
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 =
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;
}
// 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).
void TestNumericTimeSomeSpecialFormats();
void TestIdentifiers();
void TestInvalidIdentifiers();
+ void TestIdentifierDetails();
+ void TestPrefixes();
void TestParseToBuiltIn();
void TestKilogramIdentifier();
void TestCompoundUnitOperations();
int32_t end);
void verifySingleUnit(
const MeasureUnit& unit,
- UMeasureSIPrefix siPrefix,
+ UMeasurePrefix unitPrefix,
int8_t power,
const char* identifier);
void verifyCompoundUnit(
TESTCASE_AUTO(TestNumericTimeSomeSpecialFormats);
TESTCASE_AUTO(TestIdentifiers);
TESTCASE_AUTO(TestInvalidIdentifiers);
+ TESTCASE_AUTO(TestIdentifierDetails);
+ TESTCASE_AUTO(TestPrefixes);
TESTCASE_AUTO(TestParseToBuiltIn);
TESTCASE_AUTO(TestKilogramIdentifier);
TESTCASE_AUTO(TestCompoundUnitOperations);
{"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);
}
}
+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 {
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());
}
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);
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);
.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));
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);
// 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);
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);
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)));
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);
{"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},
* @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) {
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);
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;
// * 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;
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());
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++) {
}
- 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() {
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;
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;
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.
}
public int getSimpleUnitIndex() {
+ assert this.type == Type.TYPE_SIMPLE_UNIT;
return this.fMatch - UnitsData.Constants.kSimpleUnitOffset;
}
}
if (fMatch < UnitsData.Constants.kCompoundPartOffset) {
- return Type.TYPE_SI_PREFIX;
+ return Type.TYPE_PREFIX;
}
if (fMatch < UnitsData.Constants.kInitialCompoundPartOffset) {
return Type.TYPE_COMPOUND_PART;
enum Type {
TYPE_UNDEFINED,
- TYPE_SI_PREFIX,
+ TYPE_PREFIX,
// Token type for "-per-", "-", and "-and-".
TYPE_COMPOUND_PART,
// Token type for "per-".
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
*/
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;
}
throw new IllegalArgumentException("Unit Identifier Syntax Error");
}
- result.append(this.getSiPrefix().getIdentifier());
+ result.append(this.getPrefix().getIdentifier());
result.append(this.getSimpleUnitID());
return result.toString();
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.getPrefix().getPower() < other.getPrefix().getPower()) {
return -1;
}
- if (this.getSiPrefix().getPower() > other.getSiPrefix().getPower()) {
+ if (this.getPrefix().getPower() > other.getPrefix().getPower()) {
return 1;
}
return 0;
/**
* 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);
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() {
}
}
- 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;
}
* 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;
// 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*/
* 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
/**
* 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.
}
/**
- * 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.
* @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.
* @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.
* @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.
* @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.
* @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.
* @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.
* @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.
* @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.
* @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.
* @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.
* @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.
* @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.
* @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.
* @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.
* @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.
* @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.
* @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.
* @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.
* @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.
* @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.
* @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;
}
}
/**
- * 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() {
}
/**
- * 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();
}
/**
* <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>
* @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();
}
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"),
-
- // 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.
+ // 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"),
+
+ // 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);
}
}
+ @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));
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);
.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"
};
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");
}
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
// 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");
}
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");
// 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());
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());
}
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),
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);
}
}
"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];