ICU-21389 Merge maint/maint-68 to master
diff --git a/KEYS b/KEYS
index b732868..3e293a7 100644
--- a/KEYS
+++ b/KEYS
@@ -898,3 +898,65 @@
DH2mVgW7fxtB1JdUbJPU1i9vzAONu6ZKyrK4d6/MTGduyO+zZISqVKS7eg==
=DEHc
-----END PGP PUBLIC KEY BLOCK-----
+
+pub rsa3072 2020-10-14 [SC]
+ 0E51E7F06EF719FBD072782A5F56E5AFA63CCD33
+uid [ultimate] Craig Cornelius (For use with ICU releases) <ccornelius@google.com>
+sig 3 5F56E5AFA63CCD33 2020-10-14 Craig Cornelius (For use with ICU releases) <ccornelius@google.com>
+sig 3 9B432B27D1BA20D7 2020-10-14 Fredrik Roubert <fredrik@roubert.name>
+sub rsa3072 2020-10-14 [E]
+sig 5F56E5AFA63CCD33 2020-10-14 Craig Cornelius (For use with ICU releases) <ccornelius@google.com>
+
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQGNBF+HGz4BDAC00mXlPPgW5JI9ECxI9juSX/xpzAhM/2NvXzc4Smq7efJMzwvp
+PkfBwdfLavred6rxqjVfXJZuPL4rAY2fKBiMq2ANBRsx6t0Bl1jbZbI/MGb18Qcl
+5EQ/n1I0WslugsZpSX7crj2A+bO42BRWOgroQdpLTrdTjdeS774MaOvt7i3S2Cy/
+YT+U6ysrz0F7wWdiOMUftHEBOrXPJ0s7P3bJGDYsca0sV1o5EmefQWh7cymhroHQ
+UzUnsthmU0Qv8BQ7xc7zsCBrJR0V0ryMww8u/mLexz7i+W522olx78Vj887yC//l
+lAZYI+ORG9hTr3QvcVaF/FaZsbwrA3FPKB+LFWQr/CFa0eSuXWoY5u2g3aL5KoCy
+G/l7x7YlMx7Rw+65YIssZYv39AiZEU3nei+qsaneNc8v6HTX39mMXVdMc5/iWV6A
+pBo+1+wTgaOWMFf5zOg38DyzdQaDz6TNoAgwHbQhz7MDHqLZssy2U/ATnqAPi/Xf
+8oGmQtTh/8zupCsAEQEAAbRDQ3JhaWcgQ29ybmVsaXVzIChGb3IgdXNlIHdpdGgg
+SUNVIHJlbGVhc2VzKSA8Y2Nvcm5lbGl1c0Bnb29nbGUuY29tPokBzgQTAQoAOBYh
+BA5R5/Bu9xn70HJ4Kl9W5a+mPM0zBQJfhxs+AhsDBQsJCAcCBhUKCQgLAgQWAgMB
+Ah4BAheAAAoJEF9W5a+mPM0zJ5IL/2XJ6VHdSdMstzjpnCDjQTYuhFkwEfGSlMrh
+NTZftoQ1qYXuU8DKISgEo1OlguuK61xePn5wl8BfT6XfF2XCZpVUah2fom1m9ZAI
+2gkt/Bwn1ulBKQd3NQpAQKPSKzOVJ+0BBOoBCwoCy+hHr8oeRJGgxz+j8aux10IV
+OHviRkBizx0vnVcy3dN41QLNvtq2cZcKig68sAJPM3t4//co89pH3kjKzBFVCqxF
++INFyaGMyhT1zEhEYB+o6fvYGS4zX9qGRpLzx64beA84DZRmv6aLvjwzhSfA74Ey
+f+FLiAMYae1opWLS+to54WEB5hK2Ak251PqOfWCuR1KVGmQaa4sabT3uQQ4bRh6Y
+yGSXD9qSRJC/W2vNRwkEnKrjwFgH6w2XgHpVLlJiC6Ir7U57AkCfUDVdRCcWQoGN
+ZTuPIHfoj7IehJgR8NZvbHAWViC3FP9DZEm+1druAbc62PngbTUlF6zJrZ/zl90s
+kDg8cNNhQtCdgDKdL/cZwBm/G8RzUIkCMwQTAQgAHRYhBP+pEpoYDXZbelvqHJtD
+KyfRuiDXBQJfhzz/AAoJEJtDKyfRuiDXegYQALsvJzFyxsYUsgr7VYTlyEmjLx2k
+S87toZTgD4zDHprjGT/mRQvzKaMqgp6cwGtnGYLbJi3W17E6ykMk523CWfrqAbqV
+RpMH6nQvxNEbjHCAOAlXZc2zk78lqHkd+UePpDW0Lzwllyf5AK/hf9AvHGZlRSij
+OZEpvRIinwczPcKCyy1RdwO2aeKyjrk98e8SKjpjdl/OXFiuWpLWUXJE/QOkIwbk
+lwUUgHQuauuKZFDDu1T4AGBEAv5yUWU7m6PMa0riGQ7m1pSvTVX9wmV0KgkQ3/sW
+VDqYf3X1VL/NVt4GUBAFTCw04Xzqdb94ERlYde5yFn/wJm7Pnv5pDIlxvGvL+uA6
+e9oGc7msHA2PQipDaAPcvmDT6IQLLuhmO9PA85vZ8egGjKKBey5FE+zRrowWfsLm
+WRXeBPohHjHY2KRto3onFJM4JEydKLtpBAGQ04NAGAhth3opd3jpaPSu6u4rhx4i
+HLiBoJzsWtRTE0lTAeA6Wm0MoH9JQQEt4rgi5kd9BcYMsDfZiR9Fsq2FwEkPwFbi
+1sSNaxW5BWYFKSBhYHOkAFzsxRrD1OAj/anPO/QYfRUu08hUyYi1StTa/0zZ+Cf3
+NY/Q3YJLe/6PJrzxv4wnE7QZs5OSUDaTqL9kOB1mDz6Z3iLnPeOnkBuL6JFF5IUv
+lc9Y1w/UOFefZgGguQGNBF+HGz4BDADO2pKzANvXFSpT5IeMCcAvOIAf8vgJpO84
+BnjQ9/PgiOZMmQW03TIdrY+9wKATOxvUTpC7G+070M438UwlGJX86zjY7CxX+xdi
+yqL9ArK5QJlvaXSvhx1qcArgLIiW+X3qs/U/Y/qXOINS2gZUSYaMBnWHuXutIXSQ
+Liqi3l662nSPvKi7WjpHgi87TV2zG+B9xF23AxMZWth/0vtIevIheoB8vk0mJoos
+8Hmx1qknknDqICT/Flnxm9f1UyDOY+IwWMRyvZZUr+r08vg7qg0TIHkYeMwRMovn
+TZcAPoNvFT1yHsxTdf7mA1yTHCymBvP5aUuXh+FJ+tfPmKGvz65bs0VmlcU10Kl+
+EntF7TniVhCp/QB5M8MA6HKM0zxORFMGZ7xnV9gu6nRPcR+8PvEFxY2wgEcf7pfj
+6wGdS+Pri/dK3+53abE8RLi/aJW+wi2XNd/aRWjpSC2KBmHUk17Zgp3Jb6Iijb+e
+7bcTf+REXgi0+PskNaeCyU4LzStZCwUAEQEAAYkBtgQYAQoAIBYhBA5R5/Bu9xn7
+0HJ4Kl9W5a+mPM0zBQJfhxs+AhsMAAoJEF9W5a+mPM0zULYL/27+ED5a8s43q24Z
+COQQPZflXy+0MnzW4Fwj9f7WsxD/fkj651p/MoRVmugytRti3T8+A4ULUuKxatCZ
++Q+SIobXbWHD0H+hVosBohU6HmWX5Nbyi18wk5YDNWzN4n0duhv6lb4tn3M9utX9
+WdQWnudXNjKzddNiixalc5eU9vMEpEnwKLcmZ/efSimqfGXldMjTw+FIh6wHhBcn
+uVXQqj/+Ie20l4RXt6VxUltZwaQJnH6nf/4T1B4sllNo6Sz8RHjnrc+SjuIREaga
+nRNZ0BF6aAj6qk9RTlkmBGiAdmvtCuGRqqsG2i8OrTv0xArCGEs13Vv5BE6JfZI/
+YdzPuq2u0JM9dsEM6v3LLxhlfxxOmM9OZM4+GBP+JMB3TydK8t7vfLW0kDRvymVM
+uVrQ13BH7D1TWlzM7jN1dToIySXIjGRcMpqT/2LqYDwHfrJCdQZGKDBl5vNWXwNs
+OC7PFtJwtXNl8p7Jvk3lplU5rNSSkTF8hSFgtxu4cpJ5VAY8nQ==
+=FdGa
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/icu4c/source/common/cmemory.h b/icu4c/source/common/cmemory.h
index 210bc76..a9d9424 100644
--- a/icu4c/source/common/cmemory.h
+++ b/icu4c/source/common/cmemory.h
@@ -725,9 +725,14 @@
}
MemoryPool& operator=(MemoryPool&& other) U_NOEXCEPT {
- fCount = other.fCount;
- fPool = std::move(other.fPool);
- other.fCount = 0;
+ // Since `this` may contain instances that need to be deleted, we can't
+ // just throw them away and replace them with `other`. The normal way of
+ // dealing with this in C++ is to swap `this` and `other`, rather than
+ // simply overwrite: the destruction of `other` can then take care of
+ // running MemoryPool::~MemoryPool() over the still-to-be-deallocated
+ // instances.
+ std::swap(fCount, other.fCount);
+ std::swap(fPool, other.fPool);
return *this;
}
@@ -796,9 +801,6 @@
template<typename T, int32_t stackCapacity = 8>
class MaybeStackVector : protected MemoryPool<T, stackCapacity> {
public:
- using MemoryPool<T, stackCapacity>::MemoryPool;
- using MemoryPool<T, stackCapacity>::operator=;
-
template<typename... Args>
T* emplaceBack(Args&&... args) {
return this->create(args...);
diff --git a/icu4c/source/i18n/measunit.cpp b/icu4c/source/i18n/measunit.cpp
index 02624f8..dab3abb 100644
--- a/icu4c/source/i18n/measunit.cpp
+++ b/icu4c/source/i18n/measunit.cpp
@@ -33,8 +33,7 @@
// update this code, refer to:
// http://site.icu-project.org/design/formatting/measureformat/updating-measure-unit
//
-// Start generated code
-// TODO(ICU-21076): improve how this generated code is produced.
+// Start generated code for measunit.cpp
// Maps from Type ID to offset in gSubTypes.
static const int32_t gOffsets[] = {
@@ -55,44 +54,15 @@
404,
408,
423,
- 426,
- 432,
- 442,
- 446,
+ 424,
+ 430,
+ 440,
+ 444,
+ 448,
450,
- 452,
- 486
+ 484
};
-// TODO: FIX CODE GENERATION - leaving this here but commented-out to make it
-// clear that we no longer want this array. We needed it for only one thing: efficient checking of "currency".
-//
-// static const int32_t gIndexes[] = {
-// 0,
-// 2,
-// 7,
-// 17,
-// 25,
-// 29,
-// 29,
-// 40,
-// 56,
-// 60,
-// 69,
-// 71,
-// 75,
-// 83,
-// 105,
-// 109,
-// 124,
-// 127,
-// 133,
-// 143,
-// 147,
-// 151,
-// 153,
-// 187
-// };
static const int32_t kCurrencyOffset = 5;
// Must be sorted alphabetically.
@@ -547,9 +517,7 @@
"solar-mass",
"stone",
"ton",
- "", // TODO(ICU-21076): manual edit of what should have been generated by Java.
- "percent", // TODO(ICU-21076): regenerate, deal with duplication.
- "permille", // TODO(ICU-21076): regenerate, deal with duplication.
+ "",
"gigawatt",
"horsepower",
"kilowatt",
@@ -612,8 +580,6 @@
"teaspoon"
};
-// unitPerUnitToSingleUnit no longer in use! TODO: remove from code-generation code.
-
// Shortcuts to the base unit in order to make the default constructor fast
static const int32_t kBaseTypeIdx = 16;
static const int32_t kBaseSubTypeIdx = 0;
@@ -2090,7 +2056,7 @@
return MeasureUnit(22, 33);
}
-// End generated code
+// End generated code for measunit.cpp
static int32_t binarySearch(
const char * const * array, int32_t start, int32_t end, StringPiece key) {
@@ -2271,9 +2237,11 @@
}
bool MeasureUnit::findBySubType(StringPiece subType, MeasureUnit* output) {
+ // Sanity checking kCurrencyOffset and final entry in gOffsets
+ U_ASSERT(uprv_strcmp(gTypes[kCurrencyOffset], "currency") == 0);
+ U_ASSERT(gOffsets[UPRV_LENGTHOF(gOffsets) - 1] == UPRV_LENGTHOF(gSubTypes));
+
for (int32_t t = 0; t < UPRV_LENGTHOF(gOffsets) - 1; t++) {
- // Ensure kCurrencyOffset is set correctly
- U_ASSERT(uprv_strcmp(gTypes[kCurrencyOffset], "currency") == 0);
// Skip currency units
if (t == kCurrencyOffset) {
continue;
@@ -2304,7 +2272,7 @@
fTypeId = result;
result = binarySearch(gSubTypes, gOffsets[fTypeId], gOffsets[fTypeId + 1], timeId);
U_ASSERT(result != -1);
- fSubTypeId = result - gOffsets[fTypeId];
+ fSubTypeId = result - gOffsets[fTypeId];
}
void MeasureUnit::initCurrency(StringPiece isoCurrency) {
diff --git a/icu4c/source/i18n/number_formatimpl.cpp b/icu4c/source/i18n/number_formatimpl.cpp
index 9a9f3c8..f7e1fcb 100644
--- a/icu4c/source/i18n/number_formatimpl.cpp
+++ b/icu4c/source/i18n/number_formatimpl.cpp
@@ -389,8 +389,12 @@
fMixedUnitLongNameHandler.getAlias(), status);
chain = fMixedUnitLongNameHandler.getAlias();
} else {
+ MeasureUnit unit = macros.unit;
+ if (!utils::unitIsBaseUnit(macros.perUnit)) {
+ unit = unit.product(macros.perUnit.reciprocal(status), status);
+ }
fLongNameHandler.adoptInsteadAndCheckErrorCode(new LongNameHandler(), status);
- LongNameHandler::forMeasureUnit(macros.locale, macros.unit, macros.perUnit, unitWidth,
+ LongNameHandler::forMeasureUnit(macros.locale, unit, unitWidth,
resolvePluralRules(macros.rules, macros.locale, status),
chain, fLongNameHandler.getAlias(), status);
chain = fLongNameHandler.getAlias();
diff --git a/icu4c/source/i18n/number_longnames.cpp b/icu4c/source/i18n/number_longnames.cpp
index 3891d53..0fa56c7 100644
--- a/icu4c/source/i18n/number_longnames.cpp
+++ b/icu4c/source/i18n/number_longnames.cpp
@@ -218,38 +218,38 @@
} // namespace
void LongNameHandler::forMeasureUnit(const Locale &loc, const MeasureUnit &unitRef,
- const MeasureUnit &perUnit, const UNumberUnitWidth &width,
- const PluralRules *rules, const MicroPropsGenerator *parent,
- LongNameHandler *fillIn, UErrorCode &status) {
+ const UNumberUnitWidth &width, const PluralRules *rules,
+ const MicroPropsGenerator *parent, LongNameHandler *fillIn,
+ UErrorCode &status) {
// Not valid for mixed units that aren't built-in units, and there should
// not be any built-in mixed units!
U_ASSERT(uprv_strcmp(unitRef.getType(), "") != 0 ||
unitRef.getComplexity(status) != UMEASURE_UNIT_MIXED);
U_ASSERT(fillIn != nullptr);
- MeasureUnit unit = unitRef;
- if (uprv_strcmp(perUnit.getType(), "none") != 0) {
- // Compound unit: first try to simplify (e.g., meters per second is its own unit).
- MeasureUnit simplified = unit.product(perUnit.reciprocal(status), status);
- if (uprv_strcmp(simplified.getType(), "") != 0) {
- unit = simplified;
- } else {
- // No simplified form is available.
- forCompoundUnit(loc, unit, perUnit, width, rules, parent, fillIn, status);
- return;
+ if (uprv_strcmp(unitRef.getType(), "") == 0) {
+ // Not a built-in unit. Split it up, since we can already format
+ // "builtin-per-builtin".
+ // TODO(ICU-20941): support more generic case than builtin-per-builtin.
+ MeasureUnitImpl fullUnit = MeasureUnitImpl::forMeasureUnitMaybeCopy(unitRef, status);
+ MeasureUnitImpl unit;
+ MeasureUnitImpl perUnit;
+ for (int32_t i = 0; i < fullUnit.units.length(); i++) {
+ SingleUnitImpl *subUnit = fullUnit.units[i];
+ if (subUnit->dimensionality > 0) {
+ unit.append(*subUnit, status);
+ } else {
+ subUnit->dimensionality *= -1;
+ perUnit.append(*subUnit, status);
+ }
}
- }
-
- if (uprv_strcmp(unit.getType(), "") == 0) {
- // TODO(ICU-20941): Unsanctioned unit. Not yet fully supported. Set an
- // error code. Once we support not-built-in units here, unitRef may be
- // anything, but if not built-in, perUnit has to be "none".
- status = U_UNSUPPORTED_ERROR;
+ forCompoundUnit(loc, std::move(unit).build(status), std::move(perUnit).build(status), width,
+ rules, parent, fillIn, status);
return;
}
UnicodeString simpleFormats[ARRAY_LENGTH];
- getMeasureData(loc, unit, width, simpleFormats, status);
+ getMeasureData(loc, unitRef, width, simpleFormats, status);
if (U_FAILURE(status)) {
return;
}
@@ -574,7 +574,7 @@
result->fHandlers[i] = mlnh;
} else {
LongNameHandler *lnh = result->fLongNameHandlers.createAndCheckErrorCode(status);
- LongNameHandler::forMeasureUnit(loc, unit, MeasureUnit(), width, rules, NULL, lnh, status);
+ LongNameHandler::forMeasureUnit(loc, unit, width, rules, NULL, lnh, status);
result->fHandlers[i] = lnh;
}
if (U_FAILURE(status)) {
diff --git a/icu4c/source/i18n/number_longnames.h b/icu4c/source/i18n/number_longnames.h
index 67f2316..66eb9a9 100644
--- a/icu4c/source/i18n/number_longnames.h
+++ b/icu4c/source/i18n/number_longnames.h
@@ -38,9 +38,6 @@
/**
* Construct a localized LongNameHandler for the specified MeasureUnit.
*
- * Compound units can be constructed via `unit` and `perUnit`. Both of these
- * must then be built-in units.
- *
* Mixed units are not supported, use MixedUnitLongNameHandler::forMeasureUnit.
*
* This function uses a fillIn intead of returning a pointer, because we
@@ -48,15 +45,13 @@
* didn't create itself).
*
* @param loc The desired locale.
- * @param unit The measure unit to construct a LongNameHandler for. If
- * `perUnit` is also defined, `unit` must not be a mixed unit.
- * @param perUnit If `unit` is a mixed unit, `perUnit` must be "none".
+ * @param unitRef The measure unit to construct a LongNameHandler for.
* @param width Specifies the desired unit rendering.
* @param rules Does not take ownership.
* @param parent Does not take ownership.
* @param fillIn Required.
*/
- static void forMeasureUnit(const Locale &loc, const MeasureUnit &unit, const MeasureUnit &perUnit,
+ static void forMeasureUnit(const Locale &loc, const MeasureUnit &unitRef,
const UNumberUnitWidth &width, const PluralRules *rules,
const MicroPropsGenerator *parent, LongNameHandler *fillIn,
UErrorCode &status);
diff --git a/icu4c/source/i18n/number_skeletons.cpp b/icu4c/source/i18n/number_skeletons.cpp
index 028525a..958bf12 100644
--- a/icu4c/source/i18n/number_skeletons.cpp
+++ b/icu4c/source/i18n/number_skeletons.cpp
@@ -1041,37 +1041,12 @@
SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status);
ErrorCode internalStatus;
- auto fullUnit = MeasureUnitImpl::forIdentifier(buffer.toStringPiece(), internalStatus);
+ macros.unit = MeasureUnit::forIdentifier(buffer.toStringPiece(), internalStatus);
if (internalStatus.isFailure()) {
// throw new SkeletonSyntaxException("Invalid core unit identifier", segment, e);
status = U_NUMBER_SKELETON_SYNTAX_ERROR;
return;
}
-
- // Mixed units can only be represented by full MeasureUnit instances, so we
- // don't split the denominator into macros.perUnit.
- if (fullUnit.complexity == UMEASURE_UNIT_MIXED) {
- macros.unit = std::move(fullUnit).build(status);
- return;
- }
-
- // When we have a built-in unit (e.g. meter-per-second), we don't split it up
- MeasureUnit testBuiltin = fullUnit.copy(status).build(status);
- if (uprv_strcmp(testBuiltin.getType(), "") != 0) {
- macros.unit = std::move(testBuiltin);
- return;
- }
-
- // TODO(ICU-20941): Clean this up.
- for (int32_t i = 0; i < fullUnit.units.length(); i++) {
- SingleUnitImpl* subUnit = fullUnit.units[i];
- if (subUnit->dimensionality > 0) {
- macros.unit = macros.unit.product(subUnit->build(status), status);
- } else {
- subUnit->dimensionality *= -1;
- macros.perUnit = macros.perUnit.product(subUnit->build(status), status);
- }
- }
}
void blueprint_helpers::parseUnitUsageOption(const StringSegment &segment, MacroProps ¯os,
diff --git a/icu4c/source/i18n/unicode/measunit.h b/icu4c/source/i18n/unicode/measunit.h
index d86bab3..b9f732a 100644
--- a/icu4c/source/i18n/unicode/measunit.h
+++ b/icu4c/source/i18n/unicode/measunit.h
@@ -23,10 +23,10 @@
#include "unicode/localpointer.h"
/**
- * \file
+ * \file
* \brief C++ API: A unit for measuring a quantity.
*/
-
+
U_NAMESPACE_BEGIN
class StringEnumeration;
@@ -35,7 +35,7 @@
#ifndef U_HIDE_DRAFT_API
/**
* 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.
* - COMPOUND: A unit composed of the product of multiple single units. Examples:
@@ -58,7 +58,7 @@
/**
* A compound unit, like meter-per-second.
- *
+ *
* @draft ICU 67
*/
UMEASURE_UNIT_COMPOUND,
@@ -243,7 +243,7 @@
* @stable ICU 3.0
*/
MeasureUnit();
-
+
/**
* Copy constructor.
* @stable ICU 3.0
@@ -3519,7 +3519,6 @@
*/
static MeasureUnit getTeaspoon();
-
// End generated createXXX methods
protected:
diff --git a/icu4c/source/i18n/units_complexconverter.cpp b/icu4c/source/i18n/units_complexconverter.cpp
index 27f835e..645c9c0 100644
--- a/icu4c/source/i18n/units_complexconverter.cpp
+++ b/icu4c/source/i18n/units_complexconverter.cpp
@@ -37,28 +37,18 @@
outputUnits_.emplaceBackAndCheckErrorCode(status, units_[i]->copy(status).build(status));
}
- // NOTE:
- // This comparator is used to sort the units in a descending order. Therefore, we return -1 if
- // the left is bigger than right and so on.
+ // Sorts units in descending order. Therefore, we return -1 if
+ // the left is bigger than right and so on.
auto descendingCompareUnits = [](const void *context, const void *left, const void *right) {
UErrorCode status = U_ZERO_ERROR;
const auto *leftPointer = static_cast<const MeasureUnitImpl *const *>(left);
const auto *rightPointer = static_cast<const MeasureUnitImpl *const *>(right);
- UnitConverter fromLeftToRight(**leftPointer, //
- **rightPointer, //
- *static_cast<const ConversionRates *>(context), //
- status);
-
- double rightFromOneLeft = fromLeftToRight.convert(1.0);
- if (std::abs(rightFromOneLeft - 1.0) < 0.0000000001) { // Equals To
- return 0;
- } else if (rightFromOneLeft > 1.0) { // Greater Than
- return -1;
- }
-
- return 1; // Less Than
+ return -1 * UnitConverter::compareTwoUnits(**leftPointer, //
+ **rightPointer, //
+ *static_cast<const ConversionRates *>(context), //
+ status);
};
uprv_sortArray(units_.getAlias(), //
diff --git a/icu4c/source/i18n/units_converter.cpp b/icu4c/source/i18n/units_converter.cpp
index a777d02..9c16353 100644
--- a/icu4c/source/i18n/units_converter.cpp
+++ b/icu4c/source/i18n/units_converter.cpp
@@ -502,6 +502,52 @@
ratesInfo, status);
}
+int32_t UnitConverter::compareTwoUnits(const MeasureUnitImpl &firstUnit,
+ const MeasureUnitImpl &secondUnit,
+ const ConversionRates &ratesInfo, UErrorCode &status) {
+ if (U_FAILURE(status)) {
+ return 0;
+ }
+
+ if (firstUnit.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED ||
+ secondUnit.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) {
+ status = U_INTERNAL_PROGRAM_ERROR;
+ return 0;
+ }
+
+ Convertibility unitsState = extractConvertibility(firstUnit, secondUnit, ratesInfo, status);
+ if (U_FAILURE(status)) {
+ return 0;
+ }
+
+ if (unitsState == Convertibility::UNCONVERTIBLE || unitsState == Convertibility::RECIPROCAL) {
+ status = U_INTERNAL_PROGRAM_ERROR;
+ return 0;
+ }
+
+ // Represents the conversion factor from the firstUnit to the base unit that specified in the
+ // conversion data which is considered as the root of the firstUnit and the secondUnit.
+ Factor firstUnitToBase = loadCompoundFactor(firstUnit, ratesInfo, status);
+ Factor secondUnitToBase = loadCompoundFactor(secondUnit, ratesInfo, status);
+
+ firstUnitToBase.substituteConstants();
+ secondUnitToBase.substituteConstants();
+
+ double firstUnitToBaseConversionRate = firstUnitToBase.factorNum / firstUnitToBase.factorDen;
+ double secondUnitToBaseConversionRate = secondUnitToBase.factorNum / secondUnitToBase.factorDen;
+
+ double diff = firstUnitToBaseConversionRate - secondUnitToBaseConversionRate;
+ if (diff > 0) {
+ return 1;
+ }
+
+ if (diff < 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
double UnitConverter::convert(double inputValue) const {
double result =
inputValue + conversionRate_.sourceOffset; // Reset the input to the target zero index.
diff --git a/icu4c/source/i18n/units_converter.h b/icu4c/source/i18n/units_converter.h
index 7650131..b2c1ce8 100644
--- a/icu4c/source/i18n/units_converter.h
+++ b/icu4c/source/i18n/units_converter.h
@@ -144,6 +144,16 @@
const ConversionRates &ratesInfo, UErrorCode &status);
/**
+ * Compares two single units and returns 1 if the first one is greater, -1 if the second
+ * one is greater and 0 if they are equal.
+ *
+ * NOTE:
+ * Compares only single units that are convertible.
+ */
+ static int32_t compareTwoUnits(const MeasureUnitImpl &firstUnit, const MeasureUnitImpl &SecondUnit,
+ const ConversionRates &ratesInfo, UErrorCode &status);
+
+ /**
* Convert a measurement expressed in the source unit to a measurement
* expressed in the target unit.
*
diff --git a/icu4c/source/test/intltest/measfmttest.cpp b/icu4c/source/test/intltest/measfmttest.cpp
index deada9a..7d5330f 100644
--- a/icu4c/source/test/intltest/measfmttest.cpp
+++ b/icu4c/source/test/intltest/measfmttest.cpp
@@ -17,6 +17,9 @@
#if !UCONFIG_NO_FORMATTING
+#include "charstr.h"
+#include "cstr.h"
+#include "measunit_impl.h"
#include "unicode/decimfmt.h"
#include "unicode/measfmt.h"
#include "unicode/measure.h"
@@ -25,8 +28,6 @@
#include "unicode/tmunit.h"
#include "unicode/plurrule.h"
#include "unicode/ustring.h"
-#include "charstr.h"
-#include "cstr.h"
#include "unicode/reldatefmt.h"
#include "unicode/rbnf.h"
@@ -88,6 +89,7 @@
void TestDimensionlessBehaviour();
void Test21060_AddressSanitizerProblem();
void Test21223_FrenchDuration();
+ void TestInternalMeasureUnitImpl();
void verifyFormat(
const char *description,
@@ -216,6 +218,7 @@
TESTCASE_AUTO(TestDimensionlessBehaviour);
TESTCASE_AUTO(Test21060_AddressSanitizerProblem);
TESTCASE_AUTO(Test21223_FrenchDuration);
+ TESTCASE_AUTO(TestInternalMeasureUnitImpl);
TESTCASE_AUTO_END;
}
@@ -4037,6 +4040,56 @@
// }
}
+void MeasureFormatTest::TestInternalMeasureUnitImpl() {
+ IcuTestErrorCode status(*this, "TestInternalMeasureUnitImpl");
+ MeasureUnitImpl mu1 = MeasureUnitImpl::forIdentifier("meter", status);
+ status.assertSuccess();
+ assertEquals("mu1 initial identifier", "", mu1.identifier.data());
+ assertEquals("mu1 initial complexity", UMEASURE_UNIT_SINGLE, mu1.complexity);
+ assertEquals("mu1 initial units length", 1, mu1.units.length());
+ assertEquals("mu1 initial units[0]", "meter", mu1.units[0]->getSimpleUnitID());
+
+ // Producing identifier via build(): the std::move() means mu1 gets modified
+ // while it also gets assigned to tmp's internal fImpl.
+ MeasureUnit tmp = std::move(mu1).build(status);
+ status.assertSuccess();
+ assertEquals("mu1 post-move-build identifier", "meter", mu1.identifier.data());
+ assertEquals("mu1 post-move-build complexity", UMEASURE_UNIT_SINGLE, mu1.complexity);
+ assertEquals("mu1 post-move-build units length", 1, mu1.units.length());
+ assertEquals("mu1 post-move-build units[0]", "meter", mu1.units[0]->getSimpleUnitID());
+ assertEquals("MeasureUnit tmp identifier", "meter", tmp.getIdentifier());
+
+ // This temporary variable is used when forMeasureUnit's first parameter
+ // lacks an fImpl instance:
+ MeasureUnitImpl tmpMemory;
+ const MeasureUnitImpl &tmpImplRef = MeasureUnitImpl::forMeasureUnit(tmp, tmpMemory, status);
+ status.assertSuccess();
+ assertEquals("tmpMemory identifier", "", tmpMemory.identifier.data());
+ assertEquals("tmpMemory complexity", UMEASURE_UNIT_SINGLE, tmpMemory.complexity);
+ assertEquals("tmpMemory units length", 1, tmpMemory.units.length());
+ assertEquals("tmpMemory units[0]", "meter", tmpMemory.units[0]->getSimpleUnitID());
+ assertEquals("tmpImplRef identifier", "", tmpImplRef.identifier.data());
+ assertEquals("tmpImplRef complexity", UMEASURE_UNIT_SINGLE, tmpImplRef.complexity);
+
+ MeasureUnitImpl mu2 = MeasureUnitImpl::forIdentifier("newton-meter", status);
+ status.assertSuccess();
+ mu1 = std::move(mu2);
+ assertEquals("mu1 = move(mu2): identifier", "", mu1.identifier.data());
+ assertEquals("mu1 = move(mu2): complexity", UMEASURE_UNIT_COMPOUND, mu1.complexity);
+ assertEquals("mu1 = move(mu2): units length", 2, mu1.units.length());
+ assertEquals("mu1 = move(mu2): units[0]", "newton", mu1.units[0]->getSimpleUnitID());
+ assertEquals("mu1 = move(mu2): units[1]", "meter", mu1.units[1]->getSimpleUnitID());
+
+ mu1 = MeasureUnitImpl::forIdentifier("hour-and-minute-and-second", status);
+ status.assertSuccess();
+ assertEquals("mu1 = HMS: identifier", "", mu1.identifier.data());
+ assertEquals("mu1 = HMS: complexity", UMEASURE_UNIT_MIXED, mu1.complexity);
+ assertEquals("mu1 = HMS: units length", 3, mu1.units.length());
+ assertEquals("mu1 = HMS: units[0]", "hour", mu1.units[0]->getSimpleUnitID());
+ assertEquals("mu1 = HMS: units[1]", "minute", mu1.units[1]->getSimpleUnitID());
+ assertEquals("mu1 = HMS: units[2]", "second", mu1.units[2]->getSimpleUnitID());
+}
+
void MeasureFormatTest::verifyFieldPosition(
const char *description,
diff --git a/icu4c/source/test/intltest/numbertest_api.cpp b/icu4c/source/test/intltest/numbertest_api.cpp
index e653fbf..a57cf3d 100644
--- a/icu4c/source/test/intltest/numbertest_api.cpp
+++ b/icu4c/source/test/intltest/numbertest_api.cpp
@@ -876,22 +876,21 @@
u"0.008765 J/fur",
u"0 J/fur");
- // TODO(ICU-20941): Support constructions such as this one.
- // assertFormatDescending(
- // u"Joules Per Furlong Short with unit identifier via API",
- // u"measure-unit/energy-joule per-measure-unit/length-furlong",
- // u"unit/joule-per-furlong",
- // NumberFormatter::with().unit(MeasureUnit::forIdentifier("joule-per-furlong", status)),
- // Locale::getEnglish(),
- // u"87,650 J/fur",
- // u"8,765 J/fur",
- // u"876.5 J/fur",
- // u"87.65 J/fur",
- // u"8.765 J/fur",
- // u"0.8765 J/fur",
- // u"0.08765 J/fur",
- // u"0.008765 J/fur",
- // u"0 J/fur");
+ assertFormatDescending(
+ u"Joules Per Furlong Short with unit identifier via API",
+ u"measure-unit/energy-joule per-measure-unit/length-furlong",
+ u"unit/joule-per-furlong",
+ NumberFormatter::with().unit(MeasureUnit::forIdentifier("joule-per-furlong", status)),
+ Locale::getEnglish(),
+ u"87,650 J/fur",
+ u"8,765 J/fur",
+ u"876.5 J/fur",
+ u"87.65 J/fur",
+ u"8.765 J/fur",
+ u"0.8765 J/fur",
+ u"0.08765 J/fur",
+ u"0.008765 J/fur",
+ u"0 J/fur");
assertFormatDescending(
u"Pounds per Square Inch: composed",
diff --git a/icu4c/source/test/intltest/numfmtst.cpp b/icu4c/source/test/intltest/numfmtst.cpp
index dc193f2..add630a 100644
--- a/icu4c/source/test/intltest/numfmtst.cpp
+++ b/icu4c/source/test/intltest/numfmtst.cpp
@@ -248,6 +248,7 @@
TESTCASE_AUTO(Test21134_ToNumberFormatter);
TESTCASE_AUTO(Test13733_StrictAndLenient);
TESTCASE_AUTO(Test21232_ParseTimeout);
+ TESTCASE_AUTO(Test10997_FormatCurrency);
TESTCASE_AUTO_END;
}
@@ -9183,7 +9184,7 @@
pFormat->format(2.155, actual);
assertEquals("Should round percent toward even number", "216%", actual);
}
-
+
void NumberFormatTest::Test11839() {
IcuTestErrorCode errorCode(*this, "Test11839");
// Ticket #11839: DecimalFormat does not respect custom plus sign
@@ -10063,4 +10064,27 @@
// Should not hang
}
+void NumberFormatTest::Test10997_FormatCurrency() {
+ IcuTestErrorCode status(*this, "Test10997_FormatCurrency");
+
+ UErrorCode error = U_ZERO_ERROR;
+ NumberFormat* fmt = NumberFormat::createCurrencyInstance(Locale::getUS(), error);
+ fmt->setMinimumFractionDigits(4);
+ fmt->setMaximumFractionDigits(4);
+
+ FieldPosition fp;
+
+ UnicodeString str;
+ Formattable usdAmnt(new CurrencyAmount(123.45, u"USD", status));
+ fmt->format(usdAmnt, str, fp, status);
+ assertEquals("minFrac 4 should be respected in default currency", u"$123.4500", str);
+
+ UnicodeString str2;
+ Formattable eurAmnt(new CurrencyAmount(123.45, u"EUR", status));
+ fmt->format(eurAmnt, str2, fp, status);
+ assertEquals("minFrac 4 should be respected in different currency", u"€123.4500", str2);
+
+ delete fmt;
+}
+
#endif /* #if !UCONFIG_NO_FORMATTING */
diff --git a/icu4c/source/test/intltest/numfmtst.h b/icu4c/source/test/intltest/numfmtst.h
index 24ca15a..c92067a 100644
--- a/icu4c/source/test/intltest/numfmtst.h
+++ b/icu4c/source/test/intltest/numfmtst.h
@@ -304,6 +304,7 @@
void Test21134_ToNumberFormatter();
void Test13733_StrictAndLenient();
void Test21232_ParseTimeout();
+ void Test10997_FormatCurrency();
private:
UBool testFormattableAsUFormattable(const char *file, int line, Formattable &f);
diff --git a/icu4c/source/test/intltest/units_test.cpp b/icu4c/source/test/intltest/units_test.cpp
index 5e248d5..c88116a 100644
--- a/icu4c/source/test/intltest/units_test.cpp
+++ b/icu4c/source/test/intltest/units_test.cpp
@@ -16,6 +16,7 @@
#include "putilimp.h"
#include "unicode/ctest.h"
#include "unicode/measunit.h"
+#include "unicode/measure.h"
#include "unicode/unistr.h"
#include "unicode/unum.h"
#include "unicode/ures.h"
@@ -43,15 +44,12 @@
void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = NULL);
void testUnitConstantFreshness();
- void testConversionCapability();
- void testConversions();
+ void testExtractConvertibility();
+ void testConverterWithCLDRTests();
void testComplexUnitsConverter();
void testComplexUnitConverterSorting();
- void testPreferences();
- void testSiPrefixes();
- void testMass();
- void testTemperature();
- void testArea();
+ void testUnitPreferencesWithCLDRTests();
+ void testConverter();
};
extern IntlTest *createUnitsTest() { return new UnitsTest(); }
@@ -62,15 +60,12 @@
}
TESTCASE_AUTO_BEGIN;
TESTCASE_AUTO(testUnitConstantFreshness);
- TESTCASE_AUTO(testConversionCapability);
- TESTCASE_AUTO(testConversions);
+ TESTCASE_AUTO(testExtractConvertibility);
+ TESTCASE_AUTO(testConverterWithCLDRTests);
TESTCASE_AUTO(testComplexUnitsConverter);
TESTCASE_AUTO(testComplexUnitConverterSorting);
- TESTCASE_AUTO(testPreferences);
- TESTCASE_AUTO(testSiPrefixes);
- TESTCASE_AUTO(testMass);
- TESTCASE_AUTO(testTemperature);
- TESTCASE_AUTO(testArea);
+ TESTCASE_AUTO(testUnitPreferencesWithCLDRTests);
+ TESTCASE_AUTO(testConverter);
TESTCASE_AUTO_END;
}
@@ -122,7 +117,9 @@
}
}
-void UnitsTest::testConversionCapability() {
+void UnitsTest::testExtractConvertibility() {
+ IcuTestErrorCode status(*this, "UnitsTest::testExtractConvertibility");
+
struct TestCase {
const char *const source;
const char *const target;
@@ -137,16 +134,37 @@
{"square-hectare", "pow4-foot", CONVERTIBLE}, //
{"square-kilometer-per-second", "second-per-square-meter", RECIPROCAL}, //
{"cubic-kilometer-per-second-meter", "second-per-square-meter", RECIPROCAL}, //
+ {"square-meter-per-square-hour", "hectare-per-square-second", CONVERTIBLE}, //
+ {"hertz", "revolution-per-second", CONVERTIBLE}, //
+ {"millimeter", "meter", CONVERTIBLE}, //
+ {"yard", "meter", CONVERTIBLE}, //
+ {"ounce-troy", "kilogram", CONVERTIBLE}, //
+ {"percent", "portion", CONVERTIBLE}, //
+ {"ofhg", "kilogram-per-square-meter-square-second", CONVERTIBLE}, //
+ {"second-per-meter", "meter-per-second", RECIPROCAL}, //
};
for (const auto &testCase : testCases) {
- UErrorCode status = U_ZERO_ERROR;
-
MeasureUnitImpl source = MeasureUnitImpl::forIdentifier(testCase.source, status);
+ if (status.errIfFailureAndReset("source MeasureUnitImpl::forIdentifier(\"%s\", ...)",
+ testCase.source)) {
+ continue;
+ }
MeasureUnitImpl target = MeasureUnitImpl::forIdentifier(testCase.target, status);
+ if (status.errIfFailureAndReset("target MeasureUnitImpl::forIdentifier(\"%s\", ...)",
+ testCase.target)) {
+ continue;
+ }
ConversionRates conversionRates(status);
+ if (status.errIfFailureAndReset("conversionRates(status)")) {
+ continue;
+ }
auto convertibility = extractConvertibility(source, target, conversionRates, status);
+ if (status.errIfFailureAndReset("extractConvertibility(<%s>, <%s>, ...)", testCase.source,
+ testCase.target)) {
+ continue;
+ }
assertEquals(UnicodeString("Conversion Capability: ") + testCase.source + " to " +
testCase.target,
@@ -154,43 +172,8 @@
}
}
-void UnitsTest::testSiPrefixes() {
- IcuTestErrorCode status(*this, "Units testSiPrefixes");
- // Test Cases
- struct TestCase {
- const char *source;
- const char *target;
- const double inputValue;
- const double expectedValue;
- } testCases[]{
- {"gram", "kilogram", 1.0, 0.001}, //
- {"milligram", "kilogram", 1.0, 0.000001}, //
- {"microgram", "kilogram", 1.0, 0.000000001}, //
- {"megagram", "gram", 1.0, 1000000}, //
- {"megagram", "kilogram", 1.0, 1000}, //
- {"gigabyte", "byte", 1.0, 1000000000}, //
- // TODO: Fix `watt` probelms.
- // {"megawatt", "watt", 1.0, 1000000}, //
- // {"megawatt", "kilowatt", 1.0, 1000}, //
- };
-
- for (const auto &testCase : testCases) {
- UErrorCode status = U_ZERO_ERROR;
-
- MeasureUnitImpl source = MeasureUnitImpl::forIdentifier(testCase.source, status);
- MeasureUnitImpl target = MeasureUnitImpl::forIdentifier(testCase.target, status);
-
- ConversionRates conversionRates(status);
- UnitConverter converter(source, target, conversionRates, status);
-
- assertEqualsNear(UnicodeString("testSiPrefixes: ") + testCase.source + " to " + testCase.target,
- testCase.expectedValue, converter.convert(testCase.inputValue),
- 0.0001 * testCase.expectedValue);
- }
-}
-
-void UnitsTest::testMass() {
- IcuTestErrorCode status(*this, "Units testMass");
+void UnitsTest::testConverter() {
+ IcuTestErrorCode status(*this, "UnitsTest::testConverter");
// Test Cases
struct TestCase {
@@ -199,100 +182,77 @@
const double inputValue;
const double expectedValue;
} testCases[]{
- {"gram", "kilogram", 1.0, 0.001}, //
- {"pound", "kilogram", 1.0, 0.453592}, //
- {"pound", "kilogram", 2.0, 0.907185}, //
- {"ounce", "pound", 16.0, 1.0}, //
- {"ounce", "kilogram", 16.0, 0.453592}, //
- {"ton", "pound", 1.0, 2000}, //
- {"stone", "pound", 1.0, 14}, //
- {"stone", "kilogram", 1.0, 6.35029} //
+ // SI Prefixes
+ {"gram", "kilogram", 1.0, 0.001},
+ {"milligram", "kilogram", 1.0, 0.000001},
+ {"microgram", "kilogram", 1.0, 0.000000001},
+ {"megagram", "gram", 1.0, 1000000},
+ {"megagram", "kilogram", 1.0, 1000},
+ {"gigabyte", "byte", 1.0, 1000000000},
+ {"megawatt", "watt", 1.0, 1000000},
+ {"megawatt", "kilowatt", 1.0, 1000},
+ // Mass
+ {"gram", "kilogram", 1.0, 0.001},
+ {"pound", "kilogram", 1.0, 0.453592},
+ {"pound", "kilogram", 2.0, 0.907185},
+ {"ounce", "pound", 16.0, 1.0},
+ {"ounce", "kilogram", 16.0, 0.453592},
+ {"ton", "pound", 1.0, 2000},
+ {"stone", "pound", 1.0, 14},
+ {"stone", "kilogram", 1.0, 6.35029},
+ // Temperature
+ {"celsius", "fahrenheit", 0.0, 32.0},
+ {"celsius", "fahrenheit", 10.0, 50.0},
+ {"celsius", "fahrenheit", 1000, 1832},
+ {"fahrenheit", "celsius", 32.0, 0.0},
+ {"fahrenheit", "celsius", 89.6, 32},
+ {"fahrenheit", "fahrenheit", 1000, 1000},
+ {"kelvin", "fahrenheit", 0.0, -459.67},
+ {"kelvin", "fahrenheit", 300, 80.33},
+ {"kelvin", "celsius", 0.0, -273.15},
+ {"kelvin", "celsius", 300.0, 26.85},
+ // Area
+ {"square-meter", "square-yard", 10.0, 11.9599},
+ {"hectare", "square-yard", 1.0, 11959.9},
+ {"square-mile", "square-foot", 0.0001, 2787.84},
+ {"hectare", "square-yard", 1.0, 11959.9},
+ {"hectare", "square-meter", 1.0, 10000},
+ {"hectare", "square-meter", 0.0, 0.0},
+ {"square-mile", "square-foot", 0.0001, 2787.84},
+ {"square-yard", "square-foot", 10, 90},
+ {"square-yard", "square-foot", 0, 0},
+ {"square-yard", "square-foot", 0.000001, 0.000009},
+ {"square-mile", "square-foot", 0.0, 0.0},
};
for (const auto &testCase : testCases) {
- UErrorCode status = U_ZERO_ERROR;
-
MeasureUnitImpl source = MeasureUnitImpl::forIdentifier(testCase.source, status);
+ if (status.errIfFailureAndReset("source MeasureUnitImpl::forIdentifier(\"%s\", ...)",
+ testCase.source)) {
+ continue;
+ }
MeasureUnitImpl target = MeasureUnitImpl::forIdentifier(testCase.target, status);
+ if (status.errIfFailureAndReset("target MeasureUnitImpl::forIdentifier(\"%s\", ...)",
+ testCase.target)) {
+ continue;
+ }
ConversionRates conversionRates(status);
+ if (status.errIfFailureAndReset("conversionRates(status)")) {
+ continue;
+ }
UnitConverter converter(source, target, conversionRates, status);
+ if (status.errIfFailureAndReset("UnitConverter(<%s>, <%s>, ...)", testCase.source,
+ testCase.target)) {
+ continue;
+ }
- assertEqualsNear(UnicodeString("testMass: ") + testCase.source + " to " + testCase.target,
- testCase.expectedValue, converter.convert(testCase.inputValue),
- 0.0001 * testCase.expectedValue);
- }
-}
-
-void UnitsTest::testTemperature() {
- IcuTestErrorCode status(*this, "Units testTemperature");
- // Test Cases
- struct TestCase {
- const char *source;
- const char *target;
- const double inputValue;
- const double expectedValue;
- } testCases[]{
- {"celsius", "fahrenheit", 0.0, 32.0}, //
- {"celsius", "fahrenheit", 10.0, 50.0}, //
- {"fahrenheit", "celsius", 32.0, 0.0}, //
- {"fahrenheit", "celsius", 89.6, 32}, //
- {"kelvin", "fahrenheit", 0.0, -459.67}, //
- {"kelvin", "fahrenheit", 300, 80.33}, //
- {"kelvin", "celsius", 0.0, -273.15}, //
- {"kelvin", "celsius", 300.0, 26.85} //
- };
-
- for (const auto &testCase : testCases) {
- UErrorCode status = U_ZERO_ERROR;
-
- MeasureUnitImpl source = MeasureUnitImpl::forIdentifier(testCase.source, status);
- MeasureUnitImpl target = MeasureUnitImpl::forIdentifier(testCase.target, status);
-
- ConversionRates conversionRates(status);
- UnitConverter converter(source, target, conversionRates, status);
-
- assertEqualsNear(UnicodeString("testTemperature: ") + testCase.source + " to " + testCase.target,
- testCase.expectedValue, converter.convert(testCase.inputValue),
- 0.0001 * uprv_fabs(testCase.expectedValue));
- }
-}
-
-void UnitsTest::testArea() {
- IcuTestErrorCode status(*this, "Units Area");
-
- // Test Cases
- struct TestCase {
- const char *source;
- const char *target;
- const double inputValue;
- const double expectedValue;
- } testCases[]{
- {"square-meter", "square-yard", 10.0, 11.9599}, //
- {"hectare", "square-yard", 1.0, 11959.9}, //
- {"square-mile", "square-foot", 0.0001, 2787.84}, //
- {"hectare", "square-yard", 1.0, 11959.9}, //
- {"hectare", "square-meter", 1.0, 10000}, //
- {"hectare", "square-meter", 0.0, 0.0}, //
- {"square-mile", "square-foot", 0.0001, 2787.84}, //
- {"square-yard", "square-foot", 10, 90}, //
- {"square-yard", "square-foot", 0, 0}, //
- {"square-yard", "square-foot", 0.000001, 0.000009}, //
- {"square-mile", "square-foot", 0.0, 0.0}, //
- };
-
- for (const auto &testCase : testCases) {
- UErrorCode status = U_ZERO_ERROR;
-
- MeasureUnitImpl source = MeasureUnitImpl::forIdentifier(testCase.source, status);
- MeasureUnitImpl target = MeasureUnitImpl::forIdentifier(testCase.target, status);
-
- ConversionRates conversionRates(status);
- UnitConverter converter(source, target, conversionRates, status);
-
- assertEqualsNear(UnicodeString("testArea: ") + testCase.source + " to " + testCase.target,
- testCase.expectedValue, converter.convert(testCase.inputValue),
- 0.0001 * testCase.expectedValue);
+ double maxDelta = 1e-6 * uprv_fabs(testCase.expectedValue);
+ if (testCase.expectedValue == 0) {
+ maxDelta = 1e-12;
+ }
+ assertEqualsNear(UnicodeString("testConverter: ") + testCase.source + " to " + testCase.target,
+ testCase.expectedValue, converter.convert(testCase.inputValue), maxDelta);
}
}
@@ -400,8 +360,8 @@
// Conversion:
UnitConverter converter(sourceUnit, targetUnit, *ctx->conversionRates, status);
- if (status.errIfFailureAndReset("constructor: UnitConverter(<%s>, <%s>, status)",
- sourceIdent.data(), targetIdent.data())) {
+ if (status.errIfFailureAndReset("UnitConverter(<%s>, <%s>, ...)", sourceIdent.data(),
+ targetIdent.data())) {
return;
}
double got = converter.convert(1000);
@@ -418,12 +378,12 @@
* Runs data-driven unit tests for unit conversion. It looks for the test cases
* in source/test/testdata/cldr/units/unitsTest.txt, which originates in CLDR.
*/
-void UnitsTest::testConversions() {
+void UnitsTest::testConverterWithCLDRTests() {
const char *filename = "unitsTest.txt";
const int32_t kNumFields = 5;
char *fields[kNumFields][2];
- IcuTestErrorCode errorCode(*this, "UnitsTest::testConversions");
+ IcuTestErrorCode errorCode(*this, "UnitsTest::testConverterWithCLDRTests");
const char *sourceTestDataPath = getSourceTestData(errorCode);
if (errorCode.errIfFailureAndReset("unable to find the source/test/testdata "
"folder (getSourceTestData())")) {
@@ -444,96 +404,129 @@
void UnitsTest::testComplexUnitsConverter() {
IcuTestErrorCode status(*this, "UnitsTest::testComplexUnitsConverter");
+
+ struct TestCase {
+ const char* msg;
+ const char* input;
+ const char* output;
+ double value;
+ Measure expected[2];
+ int32_t expectedCount;
+ // For mixed units, accuracy of the smallest unit
+ double accuracy;
+ } testCases[]{
+ // Significantly less than 2.0.
+ {"1.9999",
+ "foot",
+ "foot-and-inch",
+ 1.9999,
+ {Measure(1, MeasureUnit::createFoot(status), status),
+ Measure(11.9988, MeasureUnit::createInch(status), status)},
+ 2,
+ 0},
+
+ // TODO(icu-units#108): reconsider whether desireable to round up:
+ // A minimal nudge under 2.0, rounding up to 2.0 ft, 0 in.
+ {"2-eps",
+ "foot",
+ "foot-and-inch",
+ 2.0 - DBL_EPSILON,
+ {Measure(2, MeasureUnit::createFoot(status), status),
+ Measure(0, MeasureUnit::createInch(status), status)},
+ 2,
+ 0},
+
+ // Testing precision with meter and light-year. 1e-16 light years is
+ // 0.946073 meters, and double precision can provide only ~15 decimal
+ // digits, so we don't expect to get anything less than 1 meter.
+
+ // TODO(icu-units#108): reconsider whether desireable to round up:
+ // A nudge under 2.0 light years, rounding up to 2.0 ly, 0 m.
+ {"2-eps",
+ "light-year",
+ "light-year-and-meter",
+ 2.0 - DBL_EPSILON,
+ {Measure(2, MeasureUnit::createLightYear(status), status),
+ Measure(0, MeasureUnit::createMeter(status), status)},
+ 2,
+ 0},
+
+ // TODO(icu-units#108): reconsider whether desireable to round up:
+ // A nudge under 1.0 light years, rounding up to 1.0 ly, 0 m.
+ {"1-eps",
+ "light-year",
+ "light-year-and-meter",
+ 1.0 - DBL_EPSILON,
+ {Measure(1, MeasureUnit::createLightYear(status), status),
+ Measure(0, MeasureUnit::createMeter(status), status)},
+ 2,
+ 0},
+
+ // 1e-15 light years is 9.46073 meters (calculated using "bc" and the
+ // CLDR conversion factor). With double-precision maths in C++, we get
+ // 10.5. In this case, we're off by a bit more than 1 meter. With Java
+ // BigDecimal, we get accurate results.
+ {"1 + 1e-15",
+ "light-year",
+ "light-year-and-meter",
+ 1.0 + 1e-15,
+ {Measure(1, MeasureUnit::createLightYear(status), status),
+ Measure(9.46073, MeasureUnit::createMeter(status), status)},
+ 2,
+ 1.5 /* meters, precision */},
+
+ // TODO(icu-units#108): reconsider whether epsilon rounding is desirable:
+ //
+ // 2e-16 light years is 1.892146 meters. For C++ double, we consider
+ // this in the noise, and thus expect a 0. (This test fails when
+ // 2e-16 is increased to 4e-16.) For Java, using BigDecimal, we
+ // actually get a good result.
+ {"1 + 2e-16",
+ "light-year",
+ "light-year-and-meter",
+ 1.0 + 2e-16,
+ {Measure(1, MeasureUnit::createLightYear(status), status),
+ Measure(0, MeasureUnit::createMeter(status), status)},
+ 2,
+ 0},
+ };
+ status.assertSuccess();
+
ConversionRates rates(status);
- MeasureUnit input = MeasureUnit::getFoot();
- MeasureUnit output = MeasureUnit::forIdentifier("foot-and-inch", status);
+ MeasureUnit input, output;
MeasureUnitImpl tempInput, tempOutput;
- const MeasureUnitImpl &inputImpl = MeasureUnitImpl::forMeasureUnit(input, tempInput, status);
- const MeasureUnitImpl &outputImpl = MeasureUnitImpl::forMeasureUnit(output, tempOutput, status);
- auto converter = ComplexUnitsConverter(inputImpl, outputImpl, rates, status);
+ MaybeStackVector<Measure> measures;
+ for (const TestCase &testCase : testCases) {
+ input = MeasureUnit::forIdentifier(testCase.input, status);
+ output = MeasureUnit::forIdentifier(testCase.output, status);
+ const MeasureUnitImpl& inputImpl = MeasureUnitImpl::forMeasureUnit(input, tempInput, status);
+ const MeasureUnitImpl& outputImpl = MeasureUnitImpl::forMeasureUnit(output, tempOutput, status);
+ auto converter = ComplexUnitsConverter(inputImpl, outputImpl, rates, status);
+ measures = converter.convert(testCase.value, nullptr, status);
- // Significantly less than 2.0.
- MaybeStackVector<Measure> measures = converter.convert(1.9999, nullptr, status);
- assertEquals("measures length", 2, measures.length());
- if (2 == measures.length()) {
- assertEquals("1.9999: measures[0] value", 1.0, measures[0]->getNumber().getDouble(status));
- assertEquals("1.9999: measures[0] unit", MeasureUnit::getFoot().getIdentifier(),
- measures[0]->getUnit().getIdentifier());
- assertEqualsNear("1.9999: measures[1] value", 11.9988, measures[1]->getNumber().getDouble(status), 0.0001);
- assertEquals("1.9999: measures[1] unit", MeasureUnit::getInch().getIdentifier(),
- measures[1]->getUnit().getIdentifier());
+ CharString msg;
+ msg.append(testCase.msg, status);
+ msg.append(" ", status);
+ msg.append(testCase.input, status);
+ msg.append(" -> ", status);
+ msg.append(testCase.output, status);
+
+ CharString msgCount(msg, status);
+ msgCount.append(", measures.length()", status);
+ assertEquals(msgCount.data(), testCase.expectedCount, measures.length());
+ for (int i = 0; i < measures.length() && i < testCase.expectedCount; i++) {
+ if (i == testCase.expectedCount-1) {
+ assertEqualsNear(msg.data(), testCase.expected[i].getNumber().getDouble(status),
+ measures[i]->getNumber().getDouble(status), testCase.accuracy);
+ } else {
+ assertEquals(msg.data(), testCase.expected[i].getNumber().getDouble(status),
+ measures[i]->getNumber().getDouble(status));
+ }
+ assertEquals(msg.data(), testCase.expected[i].getUnit().getIdentifier(),
+ measures[i]->getUnit().getIdentifier());
+ }
}
-
- // TODO(icu-units#100): consider factoring out the set of tests to make this function more
- // data-driven, *after* dealing appropriately with the memory leaks that can
- // be demonstrated by this code.
-
- // TODO(icu-units#100): reusing measures results in a leak.
- // A minimal nudge under 2.0.
- MaybeStackVector<Measure> measures2 = converter.convert((2.0 - DBL_EPSILON), nullptr, status);
- assertEquals("measures length", 2, measures2.length());
- if (2 == measures2.length()) {
- assertEquals("1 - eps: measures[0] value", 2.0, measures2[0]->getNumber().getDouble(status));
- assertEquals("1 - eps: measures[0] unit", MeasureUnit::getFoot().getIdentifier(),
- measures2[0]->getUnit().getIdentifier());
- assertEquals("1 - eps: measures[1] value", 0.0, measures2[1]->getNumber().getDouble(status));
- assertEquals("1 - eps: measures[1] unit", MeasureUnit::getInch().getIdentifier(),
- measures2[1]->getUnit().getIdentifier());
- }
-
- // Testing precision with meter and light-year. 1e-16 light years is
- // 0.946073 meters, and double precision can provide only ~15 decimal
- // digits, so we don't expect to get anything less than 1 meter.
-
- // An epsilon's nudge under one light-year: should give 1 ly, 0 m.
- input = MeasureUnit::getLightYear();
- output = MeasureUnit::forIdentifier("light-year-and-meter", status);
- // TODO(icu-units#100): reusing tempInput and tempOutput results in a leak.
- MeasureUnitImpl tempInput3, tempOutput3;
- const MeasureUnitImpl &inputImpl3 = MeasureUnitImpl::forMeasureUnit(input, tempInput3, status);
- const MeasureUnitImpl &outputImpl3 = MeasureUnitImpl::forMeasureUnit(output, tempOutput3, status);
- // TODO(icu-units#100): reusing converter results in a leak.
- ComplexUnitsConverter converter3 = ComplexUnitsConverter(inputImpl3, outputImpl3, rates, status);
- // TODO(icu-units#100): reusing measures results in a leak.
- MaybeStackVector<Measure> measures3 = converter3.convert((2.0 - DBL_EPSILON), nullptr, status);
- assertEquals("measures length", 2, measures3.length());
- if (2 == measures3.length()) {
- assertEquals("light-year test: measures[0] value", 2.0, measures3[0]->getNumber().getDouble(status));
- assertEquals("light-year test: measures[0] unit", MeasureUnit::getLightYear().getIdentifier(),
- measures3[0]->getUnit().getIdentifier());
- assertEquals("light-year test: measures[1] value", 0.0, measures3[1]->getNumber().getDouble(status));
- assertEquals("light-year test: measures[1] unit", MeasureUnit::getMeter().getIdentifier(),
- measures3[1]->getUnit().getIdentifier());
- }
-
- // 1e-15 light years is 9.46073 meters (calculated using "bc" and the CLDR
- // conversion factor). With double-precision maths, we get 10.5. In this
- // case, we're off by almost 1 meter.
- MaybeStackVector<Measure> measures4 = converter3.convert((1.0 + 1e-15), nullptr, status);
- assertEquals("measures length", 2, measures4.length());
- if (2 == measures4.length()) {
- assertEquals("light-year test: measures[0] value", 1.0, measures4[0]->getNumber().getDouble(status));
- assertEquals("light-year test: measures[0] unit", MeasureUnit::getLightYear().getIdentifier(),
- measures4[0]->getUnit().getIdentifier());
- assertEqualsNear("light-year test: measures[1] value", 10,
- measures4[1]->getNumber().getDouble(status), 1);
- assertEquals("light-year test: measures[1] unit", MeasureUnit::getMeter().getIdentifier(),
- measures4[1]->getUnit().getIdentifier());
- }
-
- // 2e-16 light years is 1.892146 meters. We consider this in the noise, and
- // thus expect a 0. (This test fails when 2e-16 is increased to 4e-16.)
- MaybeStackVector<Measure> measures5 = converter3.convert((1.0 + 2e-16), nullptr, status);
- assertEquals("measures length", 2, measures5.length());
- if (2 == measures5.length()) {
- assertEquals("light-year test: measures[0] value", 1.0, measures5[0]->getNumber().getDouble(status));
- assertEquals("light-year test: measures[0] unit", MeasureUnit::getLightYear().getIdentifier(),
- measures5[0]->getUnit().getIdentifier());
- assertEquals("light-year test: measures[1] value", 0.0,
- measures5[1]->getNumber().getDouble(status));
- assertEquals("light-year test: measures[1] unit", MeasureUnit::getMeter().getIdentifier(),
- measures5[1]->getUnit().getIdentifier());
- }
+ status.assertSuccess();
// TODO(icu-units#63): test negative numbers!
}
@@ -862,12 +855,12 @@
* in source/test/testdata/cldr/units/unitPreferencesTest.txt, which originates
* in CLDR.
*/
-void UnitsTest::testPreferences() {
+void UnitsTest::testUnitPreferencesWithCLDRTests() {
const char *filename = "unitPreferencesTest.txt";
const int32_t maxFields = 11;
char *fields[maxFields][2];
- IcuTestErrorCode errorCode(*this, "UnitsTest::testPreferences");
+ IcuTestErrorCode errorCode(*this, "UnitsTest::testUnitPreferencesWithCLDRTests");
const char *sourceTestDataPath = getSourceTestData(errorCode);
if (errorCode.errIfFailureAndReset("unable to find the source/test/testdata "
"folder (getSourceTestData())")) {
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java
index 9d972e4..13084a7 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java
@@ -13,6 +13,8 @@
import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.impl.UResource;
import com.ibm.icu.impl.number.Modifier.Signum;
+import com.ibm.icu.impl.units.MeasureUnitImpl;
+import com.ibm.icu.impl.units.SingleUnitImpl;
import com.ibm.icu.number.NumberFormatter.UnitWidth;
import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.text.PluralRules;
@@ -195,15 +197,10 @@
/**
* Construct a localized LongNameHandler for the specified MeasureUnit.
* <p>
- * Compound units can be constructed via `unit` and `perUnit`. Both of these
- * must then be built-in units.
- * <p>
* Mixed units are not supported, use MixedUnitLongNameHandler.forMeasureUnit.
*
* @param locale The desired locale.
- * @param unit The measure unit to construct a LongNameHandler for. If
- * `perUnit` is also defined, `unit` must not be a mixed unit.
- * @param perUnit If `unit` is a mixed unit, `perUnit` must be null.
+ * @param unit The measure unit to construct a LongNameHandler for.
* @param width Specifies the desired unit rendering.
* @param rules Plural rules.
* @param parent Plural rules.
@@ -211,25 +208,34 @@
public static LongNameHandler forMeasureUnit(
ULocale locale,
MeasureUnit unit,
- MeasureUnit perUnit,
UnitWidth width,
PluralRules rules,
MicroPropsGenerator parent) {
- if (perUnit != null) {
- // Compound unit: first try to simplify (e.g., meters per second is its own unit).
- MeasureUnit simplified = unit.product(perUnit.reciprocal());
- if (simplified.getType() != null) {
- unit = simplified;
- } else {
- // No simplified form is available.
- return forCompoundUnit(locale, unit, perUnit, width, rules, parent);
- }
- }
-
if (unit.getType() == null) {
- // TODO(ICU-20941): Unsanctioned unit. Not yet fully supported.
- throw new UnsupportedOperationException("Unsanctioned unit, not yet supported: " +
- unit.getIdentifier());
+ // Not a built-in unit. Split it up, since we can already format
+ // "builtin-per-builtin".
+ // TODO(ICU-20941): support more generic case than builtin-per-builtin.
+ MeasureUnitImpl fullUnit = unit.getCopyOfMeasureUnitImpl();
+ unit = null;
+ MeasureUnit perUnit = null;
+ for (SingleUnitImpl subUnit : fullUnit.getSingleUnits()) {
+ if (subUnit.getDimensionality() > 0) {
+ if (unit == null) {
+ unit = subUnit.build();
+ } else {
+ unit = unit.product(subUnit.build());
+ }
+ } else {
+ // It's okay to mutate fullUnit, we made a temporary copy:
+ subUnit.setDimensionality(subUnit.getDimensionality() * -1);
+ if (perUnit == null) {
+ perUnit = subUnit.build();
+ } else {
+ perUnit = perUnit.product(subUnit.build());
+ }
+ }
+ }
+ return forCompoundUnit(locale, unit, perUnit, width, rules, parent);
}
String[] simpleFormats = new String[ARRAY_LENGTH];
@@ -336,6 +342,7 @@
* Does not call parent.processQuantity, so cannot get a MicroProps instance
* that way. Instead, the instance is passed in as a parameter.
*/
+ @Override
public MicroProps processQuantityWithMicros(DecimalQuantity quantity, MicroProps micros) {
StandardPlural pluralForm = RoundingUtils.getPluralSafe(micros.rounder, rules, quantity);
micros.modOuter = modifiers.get(pluralForm);
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameMultiplexer.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameMultiplexer.java
index 963f637..044a48f 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameMultiplexer.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameMultiplexer.java
@@ -8,7 +8,6 @@
import com.ibm.icu.number.NumberFormatter;
import com.ibm.icu.text.PluralRules;
import com.ibm.icu.util.MeasureUnit;
-import com.ibm.icu.util.NoUnit;
import com.ibm.icu.util.ULocale;
/**
@@ -64,8 +63,7 @@
.forMeasureUnit(locale, unit, width, rules, null);
result.fHandlers.add(mlnh);
} else {
- LongNameHandler lnh = LongNameHandler
- .forMeasureUnit(locale, unit, NoUnit.BASE, width, rules, null);
+ LongNameHandler lnh = LongNameHandler.forMeasureUnit(locale, unit, width, rules, null);
result.fHandlers.add(lnh);
}
}
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/units/MeasureUnitImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/units/MeasureUnitImpl.java
index ba1dae1..cdaca18 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/units/MeasureUnitImpl.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/units/MeasureUnitImpl.java
@@ -745,8 +745,9 @@
@Override
public int compare(MeasureUnitImpl o1, MeasureUnitImpl o2) {
- UnitConverter fromO1toO2 = new UnitConverter(o1, o2, conversionRates);
- return fromO1toO2.convert(BigDecimal.valueOf(1)).compareTo(BigDecimal.valueOf(1));
+ BigDecimal factor1 = this.conversionRates.getFactorToBase(o1).getConversionRate();
+ BigDecimal factor2 = this.conversionRates.getFactorToBase(o2).getConversionRate();
+ return factor1.compareTo(factor2);
}
}
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java
index b639c8f..abcb141 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java
@@ -400,12 +400,11 @@
pluralRules,
chain);
} else {
- chain = LongNameHandler.forMeasureUnit(macros.loc,
- macros.unit,
- macros.perUnit,
- unitWidth,
- pluralRules,
- chain);
+ MeasureUnit unit = macros.unit;
+ if (macros.perUnit != null) {
+ unit = unit.product(macros.perUnit.reciprocal());
+ }
+ chain = LongNameHandler.forMeasureUnit(macros.loc, unit, unitWidth, pluralRules, chain);
}
} else if (isCurrency && unitWidth == UnitWidth.FULL_NAME) {
if (rules == null) {
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java
index 9b0f1ed..ab40477 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java
@@ -12,8 +12,6 @@
import com.ibm.icu.impl.StringSegment;
import com.ibm.icu.impl.number.MacroProps;
import com.ibm.icu.impl.number.RoundingUtils;
-import com.ibm.icu.impl.units.MeasureUnitImpl;
-import com.ibm.icu.impl.units.SingleUnitImpl;
import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay;
import com.ibm.icu.number.NumberFormatter.GroupingStrategy;
import com.ibm.icu.number.NumberFormatter.SignDisplay;
@@ -1070,45 +1068,11 @@
* specified via a "unit/" concise skeleton.
*/
private static void parseIdentifierUnitOption(StringSegment segment, MacroProps macros) {
- MeasureUnitImpl fullUnit;
try {
- fullUnit = MeasureUnitImpl.forIdentifier(segment.asString());
+ macros.unit = MeasureUnit.forIdentifier(segment.asString());
} catch (IllegalArgumentException e) {
throw new SkeletonSyntaxException("Invalid unit stem", segment);
}
-
- // Mixed units can only be represented by full MeasureUnit instances, so we
- // don't split the denominator into macros.perUnit.
- if (fullUnit.getComplexity() == MeasureUnit.Complexity.MIXED) {
- macros.unit = fullUnit.build();
- return;
- }
-
- // When we have a built-in unit (e.g. meter-per-second), we don't split it up
- MeasureUnit testBuiltin = fullUnit.build();
- if (testBuiltin.getType() != null) {
- macros.unit = testBuiltin;
- return;
- }
-
- // TODO(ICU-20941): Clean this up.
- for (SingleUnitImpl subUnit : fullUnit.getSingleUnits()) {
- if (subUnit.getDimensionality() > 0) {
- if (macros.unit == null) {
- macros.unit = subUnit.build();
- } else {
- macros.unit = macros.unit.product(subUnit.build());
- }
- } else {
- // It's okay to mutate fullUnit, we're throwing it away after this:
- subUnit.setDimensionality(subUnit.getDimensionality() * -1);
- if (macros.perUnit == null) {
- macros.perUnit = subUnit.build();
- } else {
- macros.perUnit = macros.perUnit.product(subUnit.build());
- }
- }
- }
}
private static void parseUnitUsageOption(StringSegment segment, MacroProps macros) {
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/util/MeasureUnit.java b/icu4j/main/classes/core/src/com/ibm/icu/util/MeasureUnit.java
index e3de81b..40c0770 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/util/MeasureUnit.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/util/MeasureUnit.java
@@ -542,7 +542,7 @@
return implCopy.build();
}
- final MeasureUnitImpl otherImplRef = other.getMayBeReferenceOfMeasureUnitImpl();
+ final MeasureUnitImpl otherImplRef = other.getMaybeReferenceOfMeasureUnitImpl();
if (implCopy.getComplexity() == Complexity.MIXED || otherImplRef.getComplexity() == Complexity.MIXED) {
throw new UnsupportedOperationException();
}
@@ -571,7 +571,8 @@
* @provisional This API might change or be removed in a future release.
*/
public List<MeasureUnit> splitToSingleUnits() {
- final ArrayList<SingleUnitImpl> singleUnits = getMayBeReferenceOfMeasureUnitImpl().getSingleUnits();
+ final ArrayList<SingleUnitImpl> singleUnits =
+ getMaybeReferenceOfMeasureUnitImpl().getSingleUnits();
List<MeasureUnit> result = new ArrayList<>(singleUnits.size());
for (SingleUnitImpl singleUnit : singleUnits) {
result.add(singleUnit.build());
@@ -1306,7 +1307,7 @@
* Constant for unit of graphics: dot
* @draft ICU 68
* @provisional This API might change or be removed in a future release.
- */
+ */
public static final MeasureUnit DOT = MeasureUnit.internalGetInstance("graphics", "dot");
/**
@@ -1373,7 +1374,7 @@
* Constant for unit of length: earth-radius
* @draft ICU 68
* @provisional This API might change or be removed in a future release.
- */
+ */
public static final MeasureUnit EARTH_RADIUS = MeasureUnit.internalGetInstance("length", "earth-radius");
/**
@@ -1488,14 +1489,14 @@
* Constant for unit of light: candela
* @draft ICU 68
* @provisional This API might change or be removed in a future release.
- */
+ */
public static final MeasureUnit CANDELA = MeasureUnit.internalGetInstance("light", "candela");
/**
* Constant for unit of light: lumen
* @draft ICU 68
* @provisional This API might change or be removed in a future release.
- */
+ */
public static final MeasureUnit LUMEN = MeasureUnit.internalGetInstance("light", "lumen");
/**
@@ -1532,7 +1533,7 @@
* Constant for unit of mass: grain
* @draft ICU 68
* @provisional This API might change or be removed in a future release.
- */
+ */
public static final MeasureUnit GRAIN = MeasureUnit.internalGetInstance("mass", "grain");
/**
@@ -1845,28 +1846,28 @@
* Constant for unit of volume: dessert-spoon
* @draft ICU 68
* @provisional This API might change or be removed in a future release.
- */
+ */
public static final MeasureUnit DESSERT_SPOON = MeasureUnit.internalGetInstance("volume", "dessert-spoon");
/**
* Constant for unit of volume: dessert-spoon-imperial
* @draft ICU 68
* @provisional This API might change or be removed in a future release.
- */
+ */
public static final MeasureUnit DESSERT_SPOON_IMPERIAL = MeasureUnit.internalGetInstance("volume", "dessert-spoon-imperial");
/**
* Constant for unit of volume: dram
* @draft ICU 68
* @provisional This API might change or be removed in a future release.
- */
+ */
public static final MeasureUnit DRAM = MeasureUnit.internalGetInstance("volume", "dram");
/**
* Constant for unit of volume: drop
* @draft ICU 68
* @provisional This API might change or be removed in a future release.
- */
+ */
public static final MeasureUnit DROP = MeasureUnit.internalGetInstance("volume", "drop");
/**
@@ -1903,7 +1904,7 @@
* Constant for unit of volume: jigger
* @draft ICU 68
* @provisional This API might change or be removed in a future release.
- */
+ */
public static final MeasureUnit JIGGER = MeasureUnit.internalGetInstance("volume", "jigger");
/**
@@ -1928,7 +1929,7 @@
* Constant for unit of volume: pinch
* @draft ICU 68
* @provisional This API might change or be removed in a future release.
- */
+ */
public static final MeasureUnit PINCH = MeasureUnit.internalGetInstance("volume", "pinch");
/**
@@ -1953,7 +1954,7 @@
* Constant for unit of volume: quart-imperial
* @draft ICU 68
* @provisional This API might change or be removed in a future release.
- */
+ */
public static final MeasureUnit QUART_IMPERIAL = MeasureUnit.internalGetInstance("volume", "quart-imperial");
/**
@@ -1968,9 +1969,8 @@
*/
public static final MeasureUnit TEASPOON = MeasureUnit.internalGetInstance("volume", "teaspoon");
- // unitPerUnitToSingleUnit no longer in use! TODO: remove from code-generation code.
-
// End generated MeasureUnit constants
+
/* Private */
private Object writeReplace() throws ObjectStreamException {
@@ -1993,8 +1993,11 @@
/**
*
* @return this object in a MeasureUnitImpl form.
+ * @internal
+ * @deprecated This API is ICU internal only.
*/
- private MeasureUnitImpl getCopyOfMeasureUnitImpl() {
+ @Deprecated
+ public MeasureUnitImpl getCopyOfMeasureUnitImpl() {
return this.measureUnitImpl == null ?
MeasureUnitImpl.forIdentifier(getIdentifier()) :
this.measureUnitImpl.copy();
@@ -2004,7 +2007,7 @@
*
* @return this object in a MeasureUnitImpl form.
*/
- private MeasureUnitImpl getMayBeReferenceOfMeasureUnitImpl(){
+ private MeasureUnitImpl getMaybeReferenceOfMeasureUnitImpl() {
return this.measureUnitImpl == null ?
MeasureUnitImpl.forIdentifier(getIdentifier()) :
this.measureUnitImpl;
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/MeasureUnitTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/MeasureUnitTest.java
index a7f706c..3033f85 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/MeasureUnitTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/MeasureUnitTest.java
@@ -2898,6 +2898,7 @@
// for MeasureFormat during the release process.
static void generateCXXHConstants(String thisVersion) {
Map<String, MeasureUnit> seen = new HashMap<>();
+ System.out.println("// Start generated createXXX methods");
System.out.println();
TreeMap<String, List<MeasureUnit>> allUnits = getAllUnits();
for (Map.Entry<String, List<MeasureUnit>> entry : allUnits.entrySet()) {
@@ -2929,13 +2930,15 @@
System.out.println(" /**");
System.out.println(" * Returns by value, unit of " + type + ": " + code + ".");
System.out.printf(" * Also see {@link #create%s()}.\n", name);
- // TODO: When the get* methods become stable in ICU 66, update their
- // @draft code to be more like that for the create* methods above.
String getterVersion = getVersion(javaName, thisVersion);
if (Integer.valueOf(getterVersion) < 64) {
getterVersion = "64";
}
- System.out.println(" * @draft ICU " + getterVersion);
+ if (isDraft(javaName)) {
+ System.out.println(" * @draft ICU " + getterVersion);
+ } else {
+ System.out.println(" * @stable ICU " + getterVersion);
+ }
System.out.println(" */");
System.out.printf(" static MeasureUnit get%s();\n", name);
if (isDraft(javaName)) {
@@ -2944,6 +2947,7 @@
System.out.println("");
}
}
+ System.out.println("// End generated createXXX methods");
}
private static void checkForDup(
@@ -2998,34 +3002,34 @@
// DO NOT DELETE THIS FUNCTION! It may appear as dead code, but we use this to generate code
// for MeasureFormat during the release process.
static void generateCXXConstants() {
+ System.out.println("// Start generated code for measunit.cpp");
System.out.println("");
TreeMap<String, List<MeasureUnit>> allUnits = getAllUnits();
- // Hack: for C++, add NoUnits here, but ignore them when printing the create methods.
- // ALso keep track of the base unit offset to make the C++ default constructor faster.
- allUnits.put("none", Arrays.asList(new MeasureUnit[]{NoUnit.BASE, NoUnit.PERCENT, NoUnit.PERMILLE}));
+ // Hack: for C++, add base unit here, but ignore them when printing the create methods.
+ // Also keep track of the base unit offset to make the C++ default constructor faster.
+ allUnits.put("none", Arrays.asList(new MeasureUnit[] {NoUnit.BASE}));
int baseTypeIdx = -1;
int baseSubTypeIdx = -1;
+ System.out.println("// Maps from Type ID to offset in gSubTypes.");
System.out.println("static const int32_t gOffsets[] = {");
int index = 0;
+ int typeCount = 0;
+ int currencyIndex = -1;
for (Map.Entry<String, List<MeasureUnit>> entry : allUnits.entrySet()) {
System.out.printf(" %d,\n", index);
+ if (entry.getKey() == "currency") {
+ currencyIndex = typeCount;
+ }
+ typeCount++;
index += entry.getValue().size();
}
+ assertTrue("currency present", currencyIndex >= 0);
System.out.printf(" %d\n", index);
System.out.println("};");
System.out.println();
- System.out.println("static const int32_t gIndexes[] = {");
- index = 0;
- for (Map.Entry<String, List<MeasureUnit>> entry : allUnits.entrySet()) {
- System.out.printf(" %d,\n", index);
- if (!entry.getKey().equals("currency")) {
- index += entry.getValue().size();
- }
- }
- System.out.printf(" %d\n", index);
- System.out.println("};");
+ System.out.println("static const int32_t kCurrencyOffset = " + currencyIndex + ";");
System.out.println();
System.out.println("// Must be sorted alphabetically.");
System.out.println("static const char * const gTypes[] = {");
@@ -3054,7 +3058,12 @@
if (!first) {
System.out.println(",");
}
- System.out.print(" \"" + unit.getSubtype() + "\"");
+ if (unit != null) {
+ System.out.print(" \"" + unit.getSubtype() + "\"");
+ } else {
+ assertEquals("unit only null for \"none\" type", "none", entry.getKey());
+ System.out.print(" \"\"");
+ }
first = false;
measureUnitToOffset.put(unit, offset);
measureUnitToTypeSubType.put(unit, Pair.of(typeIdx, subTypeIdx));
@@ -3085,27 +3094,6 @@
measureUnitToTypeSubType.get(entry.getKey()));
}
- System.out.println("// Must be sorted by first value and then second value.");
- System.out.println("static int32_t unitPerUnitToSingleUnit[][4] = {");
- first = true;
- for (Map.Entry<OrderedPair<Integer, Integer>, Pair<Integer, Integer>> entry
- : unitPerUnitOffsetsToTypeSubType.entrySet()) {
- if (!first) {
- System.out.println(",");
- }
- first = false;
- OrderedPair<Integer, Integer> unitPerUnitOffsets = entry.getKey();
- Pair<Integer, Integer> typeSubType = entry.getValue();
- System.out.printf(" {%d, %d, %d, %d}",
- unitPerUnitOffsets.first,
- unitPerUnitOffsets.second,
- typeSubType.first,
- typeSubType.second);
- }
- System.out.println();
- System.out.println("};");
- System.out.println();
-
// Print out the fast-path for the default constructor
System.out.println("// Shortcuts to the base unit in order to make the default constructor fast");
System.out.println("static const int32_t kBaseTypeIdx = " + baseTypeIdx + ";");
@@ -3138,6 +3126,7 @@
System.out.println();
}
}
+ System.out.println("// End generated code for measunit.cpp");
}
private static String toCamelCase(MeasureUnit unit) {
@@ -3243,6 +3232,7 @@
// DO NOT DELETE THIS FUNCTION! It may appear as dead code, but we use this to generate code
// for MeasureFormat during the release process.
static void generateConstants(String thisVersion) {
+ System.out.println(" // Start generated MeasureUnit constants");
System.out.println();
Map<String, MeasureUnit> seen = new HashMap<>();
TreeMap<String, List<MeasureUnit>> allUnits = getAllUnits();
@@ -3269,7 +3259,7 @@
} else {
System.out.println(" * @stable ICU " + getVersion(name, thisVersion));
}
- System.out.println(" */");
+ System.out.println(" */");
if ("duration".equals(type) && TIME_CODES.contains(code)) {
System.out.println(" public static final TimeUnit " + name + " = (TimeUnit) MeasureUnit.internalGetInstance(\"" +
type +
@@ -3286,16 +3276,7 @@
System.out.println();
}
}
- System.out.println(" private static HashMap<Pair<MeasureUnit, MeasureUnit>, MeasureUnit>unitPerUnitToSingleUnit =");
- System.out.println(" new HashMap<Pair<MeasureUnit, MeasureUnit>, MeasureUnit>();");
- System.out.println();
- System.out.println(" static {");
- for (Map.Entry<MeasureUnit, Pair<MeasureUnit, MeasureUnit>> unitPerUnitEntry
- : getUnitsToPerParts().entrySet()) {
- Pair<MeasureUnit, MeasureUnit> unitPerUnit = unitPerUnitEntry.getValue();
- System.out.println(" unitPerUnitToSingleUnit.put(Pair.<MeasureUnit, MeasureUnit>of(MeasureUnit." + toJAVAName(unitPerUnit.first) + ", MeasureUnit." + toJAVAName(unitPerUnit.second) + "), MeasureUnit." + toJAVAName(unitPerUnitEntry.getKey()) + ");");
- }
- System.out.println(" }");
+ System.out.println(" // End generated MeasureUnit constants");
}
private static String getVersion(String javaName, String thisVersion) {
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/impl/UnitsTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/impl/UnitsTest.java
index 564debe..554ed6b 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/impl/UnitsTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/impl/UnitsTest.java
@@ -40,88 +40,106 @@
@Test
public void testComplexUnitsConverter() {
+ class TestCase {
+ String input;
+ String output;
+ BigDecimal value;
+ Measure[] expected;
+ // For mixed units, accuracy of the smallest unit
+ double accuracy;
+
+ TestCase(String input, String output, BigDecimal value, Measure[] expected, double accuracy) {
+ this.input = input;
+ this.output = output;
+ this.value = value;
+ this.expected = expected;
+ this.accuracy = accuracy;
+ }
+ }
+ TestCase[] testCases = new TestCase[] {
+ // Significantly less than 2.0.
+ new TestCase(
+ "foot", "foot-and-inch", BigDecimal.valueOf(1.9999),
+ new Measure[] {new Measure(1, MeasureUnit.FOOT), new Measure(11.9988, MeasureUnit.INCH)},
+ 0),
+
+ // A minimal nudge under 2.0, rounding up to 2.0 ft, 0 in.
+ new TestCase(
+ "foot", "foot-and-inch", BigDecimal.valueOf(2.0).subtract(ComplexUnitsConverter.EPSILON),
+ new Measure[] {new Measure(2, MeasureUnit.FOOT), new Measure(0, MeasureUnit.INCH)}, 0),
+
+ // Testing precision with meter and light-year. 1e-16 light years is
+ // 0.946073 meters, and double precision can provide only ~15 decimal
+ // digits, so we don't expect to get anything less than 1 meter.
+
+ // TODO(icu-units#108): figure out precision thresholds for BigDecimal?
+ // A nudge under 2.0 light years, rounding up to 2.0 ly, 0 m.
+ new TestCase("light-year", "light-year-and-meter",
+ BigDecimal.valueOf(2.0).subtract(ComplexUnitsConverter.EPSILON),
+ new Measure[] {new Measure(2, MeasureUnit.LIGHT_YEAR),
+ new Measure(0, MeasureUnit.METER)},
+ 0),
+
+ // // TODO(icu-units#108): figure out precision thresholds for BigDecimal?
+ // // This test is passing in C++ but failing in Java:
+ // // A nudge under 1.0 light years, rounding up to 1.0 ly, 0 m.
+ // new TestCase("light-year", "light-year-and-meter",
+ // BigDecimal.valueOf(1.0).subtract(ComplexUnitsConverter.EPSILON),
+ // new Measure[] {new Measure(1, MeasureUnit.LIGHT_YEAR),
+ // new Measure(0, MeasureUnit.METER)},
+ // 0),
+
+ // 1e-15 light years is 9.46073 meters (calculated using "bc" and
+ // the CLDR conversion factor). With double-precision maths in C++,
+ // we get 10.5. In this case, we're off by a bit more than 1 meter.
+ // With Java BigDecimal, we get accurate results.
+ new TestCase("light-year", "light-year-and-meter", BigDecimal.valueOf(1.0 + 1e-15),
+ new Measure[] {new Measure(1, MeasureUnit.LIGHT_YEAR),
+ new Measure(9.46073, MeasureUnit.METER)},
+ 0 /* meters, precision */),
+
+ // TODO(icu-units#108): reconsider whether epsilon rounding is desirable:
+ //
+ // 2e-16 light years is 1.892146 meters. For C++ double, we consider
+ // this in the noise, and thus expect a 0. (This test fails when
+ // 2e-16 is increased to 4e-16.) For Java, using BigDecimal, we
+ // actually get a good result.
+ new TestCase("light-year", "light-year-and-meter", BigDecimal.valueOf(1.0 + 2e-16),
+ new Measure[] {new Measure(1, MeasureUnit.LIGHT_YEAR),
+ new Measure(1.892146, MeasureUnit.METER)},
+ 0),
+ };
+
ConversionRates rates = new ConversionRates();
- MeasureUnit input = MeasureUnit.FOOT;
- MeasureUnit output = MeasureUnit.forIdentifier("foot-and-inch");
- final MeasureUnitImpl inputImpl = MeasureUnitImpl.forIdentifier(input.getIdentifier());
- final MeasureUnitImpl outputImpl = MeasureUnitImpl.forIdentifier(output.getIdentifier());
- ComplexUnitsConverter converter = new ComplexUnitsConverter(inputImpl, outputImpl, rates);
+ MeasureUnit input, output;
+ List<Measure> measures;
+ for (TestCase testCase : testCases) {
+ input = MeasureUnit.forIdentifier(testCase.input);
+ output = MeasureUnit.forIdentifier(testCase.output);
+ final MeasureUnitImpl inputImpl = MeasureUnitImpl.forIdentifier(input.getIdentifier());
+ final MeasureUnitImpl outputImpl = MeasureUnitImpl.forIdentifier(output.getIdentifier());
+ ComplexUnitsConverter converter = new ComplexUnitsConverter(inputImpl, outputImpl, rates);
+ measures = converter.convert(testCase.value, null);
- // Significantly less than 2.0.
- List<Measure> measures = converter.convert(BigDecimal.valueOf(1.9999), null);
- assertEquals("measures length", 2, measures.size());
- assertEquals("1.9999: measures[0] value", BigDecimal.valueOf(1), measures.get(0).getNumber());
- assertEquals("1.9999: measures[0] unit", MeasureUnit.FOOT.getIdentifier(),
- measures.get(0).getUnit().getIdentifier());
-
- assertTrue("1.9999: measures[1] value", compareTwoBigDecimal(BigDecimal.valueOf(11.9988),
- BigDecimal.valueOf(measures.get(1).getNumber().doubleValue()), BigDecimal.valueOf(0.0001)));
- assertEquals("1.9999: measures[1] unit", MeasureUnit.INCH.getIdentifier(),
- measures.get(1).getUnit().getIdentifier());
-
- // TODO(icu-units#100): consider factoring out the set of tests to make
- // this function more data-driven, *after* dealing appropriately with
- // the C++ memory leaks that can be demonstrated by the C++ version of
- // this code.
-
- // A minimal nudge under 2.0.
- List<Measure> measures2 =
- converter.convert(BigDecimal.valueOf(2.0).subtract(ComplexUnitsConverter.EPSILON), null);
- assertEquals("measures length", 2, measures2.size());
- assertEquals("1 - eps: measures[0] value", BigDecimal.valueOf(2), measures2.get(0).getNumber());
- assertEquals("1 - eps: measures[0] unit", MeasureUnit.FOOT.getIdentifier(),
- measures2.get(0).getUnit().getIdentifier());
- assertEquals("1 - eps: measures[1] value", BigDecimal.ZERO, measures2.get(1).getNumber());
- assertEquals("1 - eps: measures[1] unit", MeasureUnit.INCH.getIdentifier(),
- measures2.get(1).getUnit().getIdentifier());
-
- // Testing precision with meter and light-year. 1e-16 light years is
- // 0.946073 meters, and double precision can provide only ~15 decimal
- // digits, so we don't expect to get anything less than 1 meter.
-
- // An epsilon's nudge under one light-year: should give 1 ly, 0 m.
- input = MeasureUnit.LIGHT_YEAR;
- output = MeasureUnit.forIdentifier("light-year-and-meter");
- final MeasureUnitImpl inputImpl3 = MeasureUnitImpl.forIdentifier(input.getIdentifier());
- final MeasureUnitImpl outputImpl3 = MeasureUnitImpl.forIdentifier(output.getIdentifier());
-
- ComplexUnitsConverter converter3 = new ComplexUnitsConverter(inputImpl3, outputImpl3, rates);
-
- List<Measure> measures3 =
- converter3.convert(BigDecimal.valueOf(2.0).subtract(ComplexUnitsConverter.EPSILON), null);
- assertEquals("measures length", 2, measures3.size());
- assertEquals("light-year test: measures[0] value", BigDecimal.valueOf(2), measures3.get(0).getNumber());
- assertEquals("light-year test: measures[0] unit", MeasureUnit.LIGHT_YEAR.getIdentifier(),
- measures3.get(0).getUnit().getIdentifier());
- assertEquals("light-year test: measures[1] value", BigDecimal.ZERO, measures3.get(1).getNumber());
- assertEquals("light-year test: measures[1] unit", MeasureUnit.METER.getIdentifier(),
- measures3.get(1).getUnit().getIdentifier());
-
- // 1e-15 light years is 9.46073 meters (calculated using "bc" and the CLDR
- // conversion factor). With double-precision maths, we get 10.5. In this
- // case, we're off by almost 1 meter.
- List<Measure> measures4 = converter3.convert(BigDecimal.valueOf(1.0 + 1e-15), null);
- assertEquals("measures length", 2, measures4.size());
- assertEquals("light-year test: measures[0] value", BigDecimal.ONE, measures4.get(0).getNumber());
- assertEquals("light-year test: measures[0] unit", MeasureUnit.LIGHT_YEAR.getIdentifier(),
- measures4.get(0).getUnit().getIdentifier());
- assertTrue("light-year test: measures[1] value", compareTwoBigDecimal(BigDecimal.valueOf(10),
- BigDecimal.valueOf(measures4.get(1).getNumber().doubleValue()),
- BigDecimal.valueOf(1)));
- assertEquals("light-year test: measures[1] unit", MeasureUnit.METER.getIdentifier(),
- measures4.get(1).getUnit().getIdentifier());
-
- // 2e-16 light years is 1.892146 meters. We consider this in the noise, and
- // thus expect a 0. (This test fails when 2e-16 is increased to 4e-16.)
- List<Measure> measures5 = converter3.convert(BigDecimal.valueOf(1.0 + 2e-17), null);
- assertEquals("measures length", 2, measures5.size());
- assertEquals("light-year test: measures[0] value", BigDecimal.ONE, measures5.get(0).getNumber());
- assertEquals("light-year test: measures[0] unit", MeasureUnit.LIGHT_YEAR.getIdentifier(),
- measures5.get(0).getUnit().getIdentifier());
- assertEquals("light-year test: measures[1] value", BigDecimal.valueOf(0.0),
- measures5.get(1).getNumber());
- assertEquals("light-year test: measures[1] unit", MeasureUnit.METER.getIdentifier(),
- measures5.get(1).getUnit().getIdentifier());
+ assertEquals("measures length", testCase.expected.length, measures.size());
+ int i = 0;
+ for (Measure measure : measures) {
+ double accuracy = 0.0;
+ if (i == testCase.expected.length - 1) {
+ accuracy = testCase.accuracy;
+ }
+ assertTrue("input " + testCase.value + ", output measure " + i + ": expected " +
+ testCase.expected[i] + ", expected unit " +
+ testCase.expected[i].getUnit() + " got unit " + measure.getUnit(),
+ testCase.expected[i].getUnit().equals(measure.getUnit()));
+ assertEquals("input " + testCase.value + ", output measure " + i + ": expected " +
+ testCase.expected[i] + ", expected number " +
+ testCase.expected[i].getNumber() + " got number " + measure.getNumber(),
+ testCase.expected[i].getNumber().doubleValue(),
+ measure.getNumber().doubleValue(), accuracy);
+ i++;
+ }
+ }
// TODO(icu-units#63): test negative numbers!
}
@@ -162,6 +180,14 @@
TestData[] tests = {
new TestData("meter", "foot", UnitConverter.Convertibility.CONVERTIBLE),
+ new TestData("kilometer", "foot", UnitConverter.Convertibility.CONVERTIBLE),
+ new TestData("hectare", "square-foot", UnitConverter.Convertibility.CONVERTIBLE),
+ new TestData("kilometer-per-second", "second-per-meter", UnitConverter.Convertibility.RECIPROCAL),
+ new TestData("square-meter", "square-foot", UnitConverter.Convertibility.CONVERTIBLE),
+ new TestData("kilometer-per-second", "foot-per-second", UnitConverter.Convertibility.CONVERTIBLE),
+ new TestData("square-hectare", "pow4-foot", UnitConverter.Convertibility.CONVERTIBLE),
+ new TestData("square-kilometer-per-second", "second-per-square-meter", UnitConverter.Convertibility.RECIPROCAL),
+ new TestData("cubic-kilometer-per-second-meter", "second-per-square-meter", UnitConverter.Convertibility.RECIPROCAL),
new TestData("square-meter-per-square-hour", "hectare-per-square-second", UnitConverter.Convertibility.CONVERTIBLE),
new TestData("hertz", "revolution-per-second", UnitConverter.Convertibility.CONVERTIBLE),
new TestData("millimeter", "meter", UnitConverter.Convertibility.CONVERTIBLE),
@@ -169,7 +195,6 @@
new TestData("ounce-troy", "kilogram", UnitConverter.Convertibility.CONVERTIBLE),
new TestData("percent", "portion", UnitConverter.Convertibility.CONVERTIBLE),
new TestData("ofhg", "kilogram-per-square-meter-square-second", UnitConverter.Convertibility.CONVERTIBLE),
-
new TestData("second-per-meter", "meter-per-second", UnitConverter.Convertibility.RECIPROCAL),
};
ConversionRates conversionRates = new ConversionRates();
@@ -180,6 +205,7 @@
}
}
+ // TODO(icu-units#92): add UnitsTest::testConverter(), to replace or extend this test.
@Test
public void testConverterForTemperature() {
class TestData {
@@ -213,7 +239,7 @@
}
@Test
- public void testConverterFromUnitTests() throws IOException {
+ public void testConverterWithCLDRTests() throws IOException {
class TestCase {
String category;
String sourceString;
@@ -290,7 +316,7 @@
}
@Test
- public void testUnitPreferencesFromUnitTests() throws IOException {
+ public void testUnitPreferencesWithCLDRTests() throws IOException {
class TestCase {
final ArrayList<Pair<String, MeasureUnitImpl>> outputUnitInOrder = new ArrayList<>();
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java
index 1b2c54d..3e14dc0 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java
@@ -833,22 +833,21 @@
"0.008765 J/fur",
"0 J/fur");
- // // TODO(ICU-20941): Support constructions such as this one.
- // assertFormatDescending(
- // "Joules Per Furlong Short with unit identifier via API",
- // "measure-unit/energy-joule per-measure-unit/length-furlong",
- // "unit/joule-per-furlong",
- // NumberFormatter.with().unit(MeasureUnit.forIdentifier("joule-per-furlong")),
- // ULocale.ENGLISH,
- // "87,650 J/fur",
- // "8,765 J/fur",
- // "876.5 J/fur",
- // "87.65 J/fur",
- // "8.765 J/fur",
- // "0.8765 J/fur",
- // "0.08765 J/fur",
- // "0.008765 J/fur",
- // "0 J/fur");
+ assertFormatDescending(
+ "Joules Per Furlong Short with unit identifier via API",
+ "measure-unit/energy-joule per-measure-unit/length-furlong",
+ "unit/joule-per-furlong",
+ NumberFormatter.with().unit(MeasureUnit.forIdentifier("joule-per-furlong")),
+ ULocale.ENGLISH,
+ "87,650 J/fur",
+ "8,765 J/fur",
+ "876.5 J/fur",
+ "87.65 J/fur",
+ "8.765 J/fur",
+ "0.8765 J/fur",
+ "0.08765 J/fur",
+ "0.008765 J/fur",
+ "0 J/fur");
assertFormatDescending(
"Pounds per Square Inch: composed",