// Extra functions for MeasureUnit not needed for all clients. // Separate .o file so that it can be removed for modularity.
#include"unicode/utypes.h"
#if !UCONFIG_NO_FORMATTING
// Allow implicit conversion from char16_t* to UnicodeString for this file: // Helpful in toString methods and elsewhere. #define UNISTR_FROM_STRING_EXPLICIT
// TODO: Propose a new error code for this?
constexpr UErrorCode kUnitIdentifierSyntaxError = U_ILLEGAL_ARGUMENT_ERROR;
// 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 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");
// Trie value offset for "per-".
constexpr int32_t kInitialCompoundPartOffset = 192;
enum InitialCompoundPart { // Represents "per-", the only compound part that can appear at the start of // an identifier.
INITIAL_COMPOUND_PART_PER = kInitialCompoundPartOffset,
};
// Trie value offset for powers like "square-", "cubic-", "pow2-" etc.
constexpr int32_t kPowerPartOffset = 256;
/** * A ResourceSink that collects simple unit identifiers from the keys of the * convertUnits table into an array, and adds these values to a TrieBuilder, * with associated values being their index into this array plus a specified * offset. * * Example code: * * UErrorCode status = U_ZERO_ERROR; * BytesTrieBuilder b(status); * int32_t ARR_SIZE = 200; * const char *unitIdentifiers[ARR_SIZE]; * int32_t *unitCategories[ARR_SIZE]; * SimpleUnitIdentifiersSink identifierSink(gSerializedUnitCategoriesTrie, unitIdentifiers, * unitCategories, ARR_SIZE, b, kTrieValueOffset); * LocalUResourceBundlePointer unitsBundle(ures_openDirect(nullptr, "units", &status)); * ures_getAllItemsWithFallback(unitsBundle.getAlias(), "convertUnits", identifierSink, status);
*/ class SimpleUnitIdentifiersSink : public icu::ResourceSink { public: /** * Constructor. * @param quantitiesTrieData The data for constructing a quantitiesTrie, * which maps from a simple unit identifier to an index into the * gCategories array. * @param out Array of char* to which pointers to the simple unit * identifiers will be saved. (Does not take ownership.) * @param outCategories Array of int32_t to which category indexes will be * saved: this corresponds to simple unit IDs saved to `out`, mapping * from the ID to the value produced by the quantitiesTrie (which is an * index into the gCategories array). * @param outSize The size of `out` and `outCategories`. * @param trieBuilder The trie builder to which the simple unit identifier * should be added. The trie builder must outlive this resource sink. * @param trieValueOffset This is added to the index of the identifier in * the `out` array, before adding to `trieBuilder` as the value * associated with the identifier.
*/ explicit SimpleUnitIdentifiersSink(StringPiece quantitiesTrieData, constchar **out,
int32_t *outCategories, int32_t outSize,
BytesTrieBuilder &trieBuilder, int32_t trieValueOffset)
: outArray(out), outCategories(outCategories), outSize(outSize), trieBuilder(trieBuilder),
trieValueOffset(trieValueOffset), quantitiesTrieData(quantitiesTrieData), outIndex(0) {}
/** * Adds the table keys found in value to the output vector. * @param key The key of the resource passed to `value`: the second * parameter of the ures_getAllItemsWithFallback() call. * @param value Should be a ResourceTable value, if * ures_getAllItemsWithFallback() was called correctly for this sink. * @param noFallback Ignored. * @param status The standard ICU error code output parameter.
*/ void put(constchar * /*key*/, ResourceValue &value, UBool /*noFallback*/, UErrorCode &status) override {
ResourceTable table = value.getTable(status); if (U_FAILURE(status)) return;
if (outIndex + table.getSize() > outSize) {
status = U_INDEX_OUTOFBOUNDS_ERROR; return;
}
// Collect keys from the table resource. constchar *simpleUnitID; for (int32_t i = 0; table.getKeyAndValue(i, simpleUnitID, value); ++i) {
U_ASSERT(i < table.getSize());
U_ASSERT(outIndex < outSize); if (uprv_strcmp(simpleUnitID, "kilogram") == 0) { // For parsing, we use "gram", the prefixless metric mass unit. We // thus ignore the SI Base Unit of Mass: it exists due to being the // mass conversion target unit, but not needed for MeasureUnit // parsing. continue;
}
outArray[outIndex] = simpleUnitID;
trieBuilder.add(simpleUnitID, trieValueOffset + outIndex, status);
// Find the base target unit for this simple unit
ResourceTable table = value.getTable(status); if (U_FAILURE(status)) { return; } if (!table.findValue("target", value)) {
status = U_INVALID_FORMAT_ERROR; break;
}
int32_t len; const char16_t* uTarget = value.getString(len, status);
CharString target;
target.appendInvariantChars(uTarget, len, status); if (U_FAILURE(status)) { return; }
quantitiesTrie.reset();
UStringTrieResult result = quantitiesTrie.next(target.data(), target.length()); if (!USTRINGTRIE_HAS_VALUE(result)) {
status = U_INVALID_FORMAT_ERROR; break;
}
outCategories[outIndex] = quantitiesTrie.getValue();
/** * A ResourceSink that collects information from `unitQuantities` in the `units` * resource to provide key->value lookups from base unit to category, as well as * preserving ordering information for these categories. See `units.txt`. * * For example: "kilogram" -> "mass", "meter-per-second" -> "speed". * * In C++ unitQuantity values are collected in order into a char16_t* array, while * unitQuantity keys are added added to a TrieBuilder, with associated values * being the index into the aforementioned char16_t* array.
*/ class CategoriesSink : public icu::ResourceSink { public: /** * Constructor. * @param out Array of char16_t* to which unitQuantity values will be saved. * The pointers returned not owned: they point directly at the resource * strings in static memory. * @param outSize The size of the `out` array. * @param trieBuilder The trie builder to which the keys (base units) of * each unitQuantity will be added, each with value being the offset * into `out`.
*/ explicit CategoriesSink(const char16_t **out, int32_t &outSize, BytesTrieBuilder &trieBuilder)
: outQuantitiesArray(out), outSize(outSize), trieBuilder(trieBuilder), outIndex(0) {}
// Array of simple unit IDs. // // The array memory itself is owned by this pointer, but the individual char* in // that array point at static memory. (Note that these char* are also returned // by SingleUnitImpl::getSimpleUnitID().) constchar **gSimpleUnits = nullptr;
// Maps from the value associated with each simple unit ID to an index into the // gCategories array.
int32_t *gSimpleUnitCategories = nullptr;
char *gSerializedUnitExtrasStemTrie = nullptr;
// Array of char16_t* pointing at the unit categories (aka "quantities", aka // "types"), as found in the `unitQuantities` resource. The array memory itself // is owned by this pointer, but the individual char16_t* in that array point at // static memory. const char16_t **gCategories = nullptr; // Number of items in `gCategories`.
int32_t gCategoriesCount = 0; // Serialized BytesTrie for mapping from base units to indices into gCategories. char *gSerializedUnitCategoriesTrie = nullptr;
// Add sanctioned simple units by offset: simple units all have entries in // units/convertUnits resources.
LocalUResourceBundlePointer convertUnits(
ures_getByKey(unitsBundle.getAlias(), "convertUnits", nullptr, &status)); if (U_FAILURE(status)) { return; }
// Allocate enough space: with identifierSink below skipping kilogram, we're // probably allocating one more than needed.
int32_t simpleUnitsCount = convertUnits.getAlias()->fSize;
int32_t arrayMallocSize = sizeof(char *) * simpleUnitsCount;
gSimpleUnits = static_cast<constchar **>(uprv_malloc(arrayMallocSize)); if (gSimpleUnits == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR; return;
}
uprv_memset(gSimpleUnits, 0, arrayMallocSize);
arrayMallocSize = sizeof(int32_t) * simpleUnitsCount;
gSimpleUnitCategories = static_cast<int32_t *>(uprv_malloc(arrayMallocSize)); if (gSimpleUnitCategories == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR; return;
}
uprv_memset(gSimpleUnitCategories, 0, arrayMallocSize);
// Populate gSimpleUnits and build the associated trie.
SimpleUnitIdentifiersSink identifierSink(resultQuantities, gSimpleUnits, gSimpleUnitCategories,
simpleUnitsCount, b, kSimpleUnitOffset);
ures_getAllItemsWithFallback(unitsBundle.getAlias(), "convertUnits", identifierSink, status);
// Build the CharsTrie // TODO: Use SLOW or FAST here?
StringPiece result = b.buildStringPiece(USTRINGTRIE_BUILD_FAST, status); if (U_FAILURE(status)) { return; }
// Copy the result into the global constant pointer
size_t numBytes = result.length();
gSerializedUnitExtrasStemTrie = static_cast<char *>(uprv_malloc(numBytes)); if (gSerializedUnitExtrasStemTrie == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR; return;
}
uprv_memcpy(gSerializedUnitExtrasStemTrie, result.data(), numBytes);
}
class Token { public:
Token(int32_t match) : fMatch(match) {}
enum Type {
TYPE_UNDEFINED,
TYPE_PREFIX, // Token type for "-per-", "-", and "-and-".
TYPE_COMPOUND_PART, // Token type for "per-".
TYPE_INITIAL_COMPOUND_PART,
TYPE_POWER_PART,
TYPE_SIMPLE_UNIT,
};
// Calling getType() is invalid, resulting in an assertion failure, if Token // value isn't positive.
Type getType() const {
U_ASSERT(fMatch > 0); if (fMatch < kCompoundPartOffset) { return TYPE_PREFIX;
} if (fMatch < kInitialCompoundPartOffset) { return TYPE_COMPOUND_PART;
} if (fMatch < kPowerPartOffset) { return TYPE_INITIAL_COMPOUND_PART;
} if (fMatch < kSimpleUnitOffset) { return TYPE_POWER_PART;
} return TYPE_SIMPLE_UNIT;
}
// Valid only for tokens with type TYPE_COMPOUND_PART.
int32_t getMatch() const {
U_ASSERT(getType() == TYPE_COMPOUND_PART); return fMatch;
}
int32_t getInitialCompoundPart() const { // Even if there is only one InitialCompoundPart value, we have this // function for the simplicity of code consistency.
U_ASSERT(getType() == TYPE_INITIAL_COMPOUND_PART); // Defensive: if this assert fails, code using this function also needs // to change.
U_ASSERT(fMatch == INITIAL_COMPOUND_PART_PER); return fMatch;
}
class Parser { public: /** * Factory function for parsing the given identifier. * * @param source The identifier to parse. This function does not make a copy * of source: the underlying string that source points at, must outlive the * parser. * @param status ICU error code.
*/ static Parser from(StringPiece source, UErrorCode& status) { if (U_FAILURE(status)) { return {};
}
umtx_initOnce(gUnitExtrasInitOnce, &initUnitExtras, status); if (U_FAILURE(status)) { return {};
} return {source};
}
if (sawAnd && !added) { // Two similar units are not allowed in a mixed unit.
status = kUnitIdentifierSyntaxError; return result;
}
if (result.singleUnits.length() >= 2) { // nextSingleUnit fails appropriately for "per" and "and" in the // same identifier. It doesn't fail for other compound units // (COMPOUND_PART_TIMES). Consequently we take care of that // here.
UMeasureUnitComplexity complexity =
sawAnd ? UMEASURE_UNIT_MIXED : UMEASURE_UNIT_COMPOUND; if (result.singleUnits.length() == 2) { // After appending two singleUnits, the complexity will be `UMEASURE_UNIT_COMPOUND`
U_ASSERT(result.complexity == UMEASURE_UNIT_COMPOUND);
result.complexity = complexity;
} elseif (result.complexity != complexity) { // Can't have mixed compound units
status = kUnitIdentifierSyntaxError; return result;
}
}
}
return result;
}
private: // Tracks parser progress: the offset into fSource.
int32_t fIndex = 0;
// Since we're not owning this memory, whatever is passed to the constructor // should live longer than this Parser - and the parser shouldn't return any // references to that string.
StringPiece fSource;
BytesTrie fTrie;
// Set to true when we've seen a "-per-" or a "per-", after which all units // are in the denominator. Until we find an "-and-", at which point the // identifier is invalid pending TODO(CLDR-13701). bool fAfterPer = false;
// Returns the next Token parsed from fSource, advancing fIndex to the end // of that token in fSource. In case of U_FAILURE(status), the token // returned will cause an abort if getType() is called on it.
Token nextToken(UErrorCode& status) {
fTrie.reset();
int32_t match = -1; // Saves the position in the fSource string for the end of the most // recent matching token.
int32_t previ = -1; // Find the longest token that matches a value in the trie: while (fIndex < fSource.length()) { auto result = fTrie.next(fSource.data()[fIndex++]); if (result == USTRINGTRIE_NO_MATCH) { break;
} elseif (result == USTRINGTRIE_NO_VALUE) { continue;
}
U_ASSERT(USTRINGTRIE_HAS_VALUE(result));
match = fTrie.getValue();
previ = fIndex; if (result == USTRINGTRIE_FINAL_VALUE) { break;
}
U_ASSERT(result == USTRINGTRIE_INTERMEDIATE_VALUE); // continue;
}
if (match < 0) {
status = kUnitIdentifierSyntaxError;
} else {
fIndex = previ;
} return {match};
}
/** * Returns the next "single unit" via result. * * If a "-per-" was parsed, the result will have appropriate negative * dimensionality. * * Returns an error if we parse both compound units and "-and-", since mixed * compound units are not yet supported - TODO(CLDR-13701). * * @param result Will be overwritten by the result, if status shows success. * @param sawAnd If an "-and-" was parsed prior to finding the "single * unit", sawAnd is set to true. If not, it is left as is. * @param status ICU error code.
*/
SingleUnitImpl nextSingleUnit(bool &sawAnd, UErrorCode &status) {
SingleUnitImpl result; if (U_FAILURE(status)) { return result;
}
// state: // 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 or binary prefix token seen (will not accept a power, or SI or binary prefix token)
int32_t state = 0;
if (atStart) { // Identifiers optionally start with "per-". if (token.getType() == Token::TYPE_INITIAL_COMPOUND_PART) {
U_ASSERT(token.getInitialCompoundPart() == INITIAL_COMPOUND_PART_PER);
fAfterPer = true;
result.dimensionality = -1;
token = nextToken(status); if (U_FAILURE(status)) { return result;
}
}
} else { // All other SingleUnit's are separated from previous SingleUnit's // via a compound part: if (token.getType() != Token::TYPE_COMPOUND_PART) {
status = kUnitIdentifierSyntaxError; return result;
}
switch (token.getMatch()) { case COMPOUND_PART_PER: if (sawAnd) { // Mixed compound units not yet supported, // TODO(CLDR-13701).
status = kUnitIdentifierSyntaxError; return result;
}
fAfterPer = true;
result.dimensionality = -1; break;
case COMPOUND_PART_TIMES: if (fAfterPer) {
result.dimensionality = -1;
} break;
case COMPOUND_PART_AND: if (fAfterPer) { // Can't start with "-and-", and mixed compound units // not yet supported, TODO(CLDR-13701).
status = kUnitIdentifierSyntaxError; return result;
}
sawAnd = true; break;
}
token = nextToken(status); if (U_FAILURE(status)) { return result;
}
}
// Read tokens until we have a complete SingleUnit or we reach the end. while (true) { switch (token.getType()) { case Token::TYPE_POWER_PART: if (state > 0) {
status = kUnitIdentifierSyntaxError; return result;
}
result.dimensionality *= token.getPower();
state = 1; break;
case Token::TYPE_PREFIX: if (state > 1) {
status = kUnitIdentifierSyntaxError; return result;
}
result.unitPrefix = token.getUnitPrefix();
state = 2; break;
case Token::TYPE_SIMPLE_UNIT:
result.index = token.getSimpleUnitIndex(); return result;
default:
status = kUnitIdentifierSyntaxError; return result;
}
if (!hasNext()) { // We ran out of tokens before finding a complete single unit.
status = kUnitIdentifierSyntaxError; return result;
}
token = nextToken(status); if (U_FAILURE(status)) { return result;
}
}
return result;
}
};
// Sorting function wrapping SingleUnitImpl::compareTo for use with uprv_sortArray.
int32_t U_CALLCONV
compareSingleUnits(constvoid* /*context*/, const void* left, const void* right) { constauto* realLeft = static_cast<const SingleUnitImpl* const*>(left); constauto* realRight = static_cast<const SingleUnitImpl* const*>(right); return (*realLeft)->compareTo(**realRight);
}
// Returns an index into the gCategories array, for the "unitQuantity" (aka // "type" or "category") associated with the given base unit identifier. Returns // -1 on failure, together with U_UNSUPPORTED_ERROR.
int32_t getUnitCategoryIndex(BytesTrie &trie, StringPiece baseUnitIdentifier, UErrorCode &status) {
UStringTrieResult result = trie.reset().next(baseUnitIdentifier.data(), baseUnitIdentifier.length()); if (!USTRINGTRIE_HAS_VALUE(result)) {
status = U_UNSUPPORTED_ERROR; return -1;
}
// In case the base unit identifier did not match any entry. if (U_FAILURE(localStatus)) {
localStatus = U_ZERO_ERROR;
baseUnitImpl.takeReciprocal(status);
baseUnitImpl.serialize(status);
identifier.set(baseUnitImpl.identifier.data());
idx = getUnitCategoryIndex(trie, identifier, localStatus);
if (U_FAILURE(status)) { return result;
}
}
// In case the reciprocal of the base unit identifier did not match any entry.
MeasureUnitImpl simplifiedUnit = baseMeasureUnitImpl.copyAndSimplify(status); if (U_FAILURE(status)) { return result;
} if (U_FAILURE(localStatus)) {
localStatus = U_ZERO_ERROR;
simplifiedUnit.serialize(status);
identifier.set(simplifiedUnit.identifier.data());
idx = getUnitCategoryIndex(trie, identifier, localStatus);
if (U_FAILURE(status)) { return result;
}
}
// In case the simplified base unit identifier did not match any entry. if (U_FAILURE(localStatus)) {
localStatus = U_ZERO_ERROR;
simplifiedUnit.takeReciprocal(status);
simplifiedUnit.serialize(status);
identifier.set(simplifiedUnit.identifier.data());
idx = getUnitCategoryIndex(trie, identifier, localStatus);
if (U_FAILURE(status)) { return result;
}
}
// If there is no match at all, throw an exception. if (U_FAILURE(localStatus)) {
status = U_INVALID_FORMAT_ERROR; return result;
}
if (idx < 0 || idx >= gCategoriesCount) {
status = U_INVALID_FORMAT_ERROR; return result;
}
// In ICU4J, this is MeasureUnit.getSingleUnitImpl().
SingleUnitImpl SingleUnitImpl::forMeasureUnit(const MeasureUnit& measureUnit, UErrorCode& status) {
MeasureUnitImpl temp; const MeasureUnitImpl& impl = MeasureUnitImpl::forMeasureUnit(measureUnit, temp, status); if (U_FAILURE(status)) { return {};
} if (impl.singleUnits.length() == 0) { return {};
} if (impl.singleUnits.length() == 1) { return *impl.singleUnits[0];
}
status = U_ILLEGAL_ARGUMENT_ERROR; return {};
}
MeasureUnit SingleUnitImpl::build(UErrorCode& status) const {
MeasureUnitImpl temp;
temp.appendSingleUnit(*this, status); // TODO(icu-units#28): the MeasureUnitImpl::build() method uses // findBySubtype, which is relatively slow. // - At the time of loading the simple unit IDs, we could also save a // mapping to the builtin MeasureUnit type and subtype they correspond to. // - This method could then check dimensionality and index, and if both are // 1, directly return MeasureUnit instances very quickly. return std::move(temp).build(status);
}
if (this->unitPrefix != UMEASURE_PREFIX_ONE) { bool found = false; for (constauto &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;
}
}
void MeasureUnitImpl::takeReciprocal(UErrorCode& /*status*/) {
identifier.clear(); for (int32_t i = 0; i < singleUnits.length(); i++) {
singleUnits[i]->dimensionality *= -1;
}
}
MeasureUnitImpl MeasureUnitImpl::copyAndSimplify(UErrorCode &status) const {
MeasureUnitImpl result; for (int32_t i = 0; i < singleUnits.length(); i++) { const SingleUnitImpl &singleUnit = *this->singleUnits[i];
// The following `for` loop will cause time complexity to be O(n^2). // However, n is very small (number of units, generally, at maximum equal to 10) bool unitExist = false; for (int32_t j = 0; j < result.singleUnits.length(); j++) { if (uprv_strcmp(result.singleUnits[j]->getSimpleUnitID(), singleUnit.getSimpleUnitID()) ==
0 &&
result.singleUnits[j]->unitPrefix == singleUnit.unitPrefix) {
unitExist = true;
result.singleUnits[j]->dimensionality =
result.singleUnits[j]->dimensionality + singleUnit.dimensionality; break;
}
}
if (!unitExist) {
result.appendSingleUnit(singleUnit, status);
}
}
if (singleUnit.isDimensionless()) { // Do not append dimensionless units. returnfalse;
}
// Find a similar unit that already exists, to attempt to coalesce
SingleUnitImpl *oldUnit = nullptr; for (int32_t i = 0; i < this->singleUnits.length(); i++) { auto *candidate = this->singleUnits[i]; if (candidate->isCompatibleWith(singleUnit)) {
oldUnit = candidate;
}
}
if (oldUnit) { // Both dimensionalities will be positive, or both will be negative, by // virtue of isCompatibleWith().
oldUnit->dimensionality += singleUnit.dimensionality;
returnfalse;
}
// Add a copy of singleUnit // NOTE: MaybeStackVector::emplaceBackAndCheckErrorCode creates new copy of singleUnit.
this->singleUnits.emplaceBackAndCheckErrorCode(status, singleUnit); if (U_FAILURE(status)) { returnfalse;
}
// If the MeasureUnitImpl is `UMEASURE_UNIT_SINGLE` and after the appending a unit, the `singleUnits` // contains more than one. thus means the complexity should be `UMEASURE_UNIT_COMPOUND` if (this->singleUnits.length() > 1 &&
this->complexity == UMeasureUnitComplexity::UMEASURE_UNIT_SINGLE) {
this->complexity = UMeasureUnitComplexity::UMEASURE_UNIT_COMPOUND;
}
for (int32_t i = 0; i < singleUnits.length(); ++i) {
result.emplaceBackAndCheckErrorCode(status, i, *singleUnits[i], status); if (U_FAILURE(status)) { return result;
}
}
return result;
}
/** * Normalize a MeasureUnitImpl and generate the identifier string in place.
*/ void MeasureUnitImpl::serialize(UErrorCode &status) { if (U_FAILURE(status)) { return;
}
if (this->singleUnits.length() == 0) { // Dimensionless, constructed by the default constructor. return;
}
if (this->complexity == UMEASURE_UNIT_COMPOUND) { // Note: don't sort a MIXED unit
uprv_sortArray(this->singleUnits.getAlias(), this->singleUnits.length(), sizeof(this->singleUnits[0]), compareSingleUnits, nullptr, false, &status); if (U_FAILURE(status)) { return;
}
}
LocalArray<MeasureUnit> MeasureUnit::splitToSingleUnitsImpl(int32_t& outCount, UErrorCode& status) const {
MeasureUnitImpl temp; const MeasureUnitImpl& impl = MeasureUnitImpl::forMeasureUnit(*this, temp, status);
outCount = impl.singleUnits.length();
MeasureUnit* arr = new MeasureUnit[outCount]; if (arr == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR; return LocalArray<MeasureUnit>();
} for (int32_t i = 0; i < outCount; i++) {
arr[i] = impl.singleUnits[i]->build(status);
} return LocalArray<MeasureUnit>(arr, status);
}
U_NAMESPACE_END
#endif/* !UNCONFIG_NO_FORMATTING */
Messung V0.5
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.20Bemerkung:
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.