mirror of
https://github.com/unicode-org/icu.git
synced 2025-04-10 07:39:16 +00:00
parent
242bf9655f
commit
ba4d4d3ac2
7 changed files with 534 additions and 85 deletions
|
@ -2400,6 +2400,7 @@ MeasureUnitImpl MeasureUnitImpl::copy(UErrorCode &status) const {
|
|||
MeasureUnitImpl result;
|
||||
result.complexity = complexity;
|
||||
result.identifier.append(identifier, status);
|
||||
result.constantDenominator = constantDenominator;
|
||||
for (int32_t i = 0; i < singleUnits.length(); i++) {
|
||||
SingleUnitImpl *item = result.singleUnits.emplaceBack(*singleUnits[i]);
|
||||
if (!item) {
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "charstr.h"
|
||||
#include "cmemory.h"
|
||||
#include "cstring.h"
|
||||
#include "double-conversion-string-to-double.h"
|
||||
#include "measunit_impl.h"
|
||||
#include "resource.h"
|
||||
#include "uarrsort.h"
|
||||
|
@ -30,13 +31,15 @@
|
|||
#include "unicode/ustringtrie.h"
|
||||
#include "uresimp.h"
|
||||
#include "util.h"
|
||||
#include <climits>
|
||||
#include <cstdlib>
|
||||
|
||||
U_NAMESPACE_BEGIN
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
using icu::double_conversion::StringToDoubleConverter;
|
||||
|
||||
// TODO: Propose a new error code for this?
|
||||
constexpr UErrorCode kUnitIdentifierSyntaxError = U_ILLEGAL_ARGUMENT_ERROR;
|
||||
|
||||
|
@ -467,37 +470,55 @@ void U_CALLCONV initUnitExtras(UErrorCode& status) {
|
|||
|
||||
class Token {
|
||||
public:
|
||||
Token(int32_t match) : fMatch(match) {}
|
||||
Token(int64_t match) : fMatch(match) {
|
||||
if (fMatch < kCompoundPartOffset) {
|
||||
this->fType = TYPE_PREFIX;
|
||||
} else if (fMatch < kInitialCompoundPartOffset) {
|
||||
this->fType = TYPE_COMPOUND_PART;
|
||||
} else if (fMatch < kPowerPartOffset) {
|
||||
this->fType = TYPE_INITIAL_COMPOUND_PART;
|
||||
} else if (fMatch < kSimpleUnitOffset) {
|
||||
this->fType = TYPE_POWER_PART;
|
||||
} else {
|
||||
this->fType = TYPE_SIMPLE_UNIT;
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
static Token constantToken(StringPiece str, UErrorCode &status) {
|
||||
Token result;
|
||||
auto value = Token::parseStringToLong(str, status);
|
||||
if (U_FAILURE(status)) {
|
||||
return result;
|
||||
}
|
||||
result.fMatch = value;
|
||||
result.fType = TYPE_CONSTANT_DENOMINATOR;
|
||||
return result;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
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,
|
||||
TYPE_CONSTANT_DENOMINATOR,
|
||||
};
|
||||
|
||||
// Calling getType() is invalid, resulting in an assertion failure, if Token
|
||||
// value isn't positive.
|
||||
Type getType() const {
|
||||
U_ASSERT(fMatch >= 0);
|
||||
return this->fType;
|
||||
}
|
||||
|
||||
// Retrieve the value of the constant denominator if the token is of type TYPE_CONSTANT_DENOMINATOR.
|
||||
uint64_t getConstantDenominator() const {
|
||||
U_ASSERT(getType() == TYPE_CONSTANT_DENOMINATOR);
|
||||
return static_cast<uint64_t>(fMatch);
|
||||
}
|
||||
|
||||
UMeasurePrefix getUnitPrefix() const {
|
||||
U_ASSERT(getType() == TYPE_PREFIX);
|
||||
|
@ -530,8 +551,41 @@ public:
|
|||
return fMatch - kSimpleUnitOffset;
|
||||
}
|
||||
|
||||
// TODO: Consider moving this to a separate utility class.
|
||||
// Utility function to parse a string into an unsigned long value.
|
||||
// The value must be a positive integer within the range [1, INT64_MAX].
|
||||
// The input can be in integer or scientific notation.
|
||||
static uint64_t parseStringToLong(const StringPiece strNum, UErrorCode &status) {
|
||||
// We are processing well-formed input, so we don't need any special options to
|
||||
// StringToDoubleConverter.
|
||||
StringToDoubleConverter converter(0, 0, 0, "", "");
|
||||
int32_t count;
|
||||
double double_result = converter.StringToDouble(strNum.data(), strNum.length(), &count);
|
||||
if (count != strNum.length()) {
|
||||
status = kUnitIdentifierSyntaxError;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (U_FAILURE(status) || double_result < 1.0 || double_result > static_cast<double>(INT64_MAX)) {
|
||||
status = kUnitIdentifierSyntaxError;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check if the value is integer.
|
||||
uint64_t int_result = static_cast<uint64_t>(double_result);
|
||||
const double kTolerance = 1e-9;
|
||||
if (abs(double_result - int_result) > kTolerance) {
|
||||
status = kUnitIdentifierSyntaxError;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return int_result;
|
||||
}
|
||||
|
||||
private:
|
||||
int32_t fMatch;
|
||||
Token() = default;
|
||||
int64_t fMatch;
|
||||
Type fType = TYPE_UNDEFINED;
|
||||
};
|
||||
|
||||
class Parser {
|
||||
|
@ -555,6 +609,50 @@ public:
|
|||
return {source};
|
||||
}
|
||||
|
||||
/**
|
||||
* A single unit or a constant denominator.
|
||||
*/
|
||||
struct SingleUnitOrConstant {
|
||||
enum ValueType {
|
||||
kSingleUnit,
|
||||
kConstantDenominator,
|
||||
};
|
||||
|
||||
ValueType type = kSingleUnit;
|
||||
SingleUnitImpl singleUnit;
|
||||
uint64_t constantDenominator;
|
||||
|
||||
static SingleUnitOrConstant singleUnitValue(SingleUnitImpl singleUnit) {
|
||||
SingleUnitOrConstant result;
|
||||
result.type = kSingleUnit;
|
||||
result.singleUnit = singleUnit;
|
||||
result.constantDenominator = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
static SingleUnitOrConstant constantDenominatorValue(uint64_t constant) {
|
||||
SingleUnitOrConstant result;
|
||||
result.type = kConstantDenominator;
|
||||
result.singleUnit = {};
|
||||
result.constantDenominator = constant;
|
||||
return result;
|
||||
}
|
||||
|
||||
uint64_t getConstantDenominator() const {
|
||||
U_ASSERT(type == kConstantDenominator);
|
||||
return constantDenominator;
|
||||
}
|
||||
|
||||
SingleUnitImpl getSingleUnit() const {
|
||||
U_ASSERT(type == kSingleUnit);
|
||||
return singleUnit;
|
||||
}
|
||||
|
||||
bool isSingleUnit() const { return type == kSingleUnit; }
|
||||
|
||||
bool isConstantDenominator() const { return type == kConstantDenominator; }
|
||||
};
|
||||
|
||||
MeasureUnitImpl parse(UErrorCode& status) {
|
||||
MeasureUnitImpl result;
|
||||
|
||||
|
@ -569,12 +667,19 @@ public:
|
|||
while (hasNext()) {
|
||||
bool sawAnd = false;
|
||||
|
||||
SingleUnitImpl singleUnit = nextSingleUnit(sawAnd, status);
|
||||
auto singleUnitOrConstant = nextSingleUnitOrConstant(sawAnd, status);
|
||||
if (U_FAILURE(status)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
bool added = result.appendSingleUnit(singleUnit, status);
|
||||
if (singleUnitOrConstant.isConstantDenominator()) {
|
||||
result.constantDenominator = singleUnitOrConstant.getConstantDenominator();
|
||||
result.complexity = UMEASURE_UNIT_COMPOUND;
|
||||
continue;
|
||||
}
|
||||
|
||||
U_ASSERT(singleUnitOrConstant.isSingleUnit());
|
||||
bool added = result.appendSingleUnit(singleUnitOrConstant.getSingleUnit(), status);
|
||||
if (U_FAILURE(status)) {
|
||||
return result;
|
||||
}
|
||||
|
@ -604,6 +709,12 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
if (result.singleUnits.length() == 0) {
|
||||
// The identifier was empty or only had a constant denominator.
|
||||
status = kUnitIdentifierSyntaxError;
|
||||
return result; // add it for code consistency.
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -622,6 +733,10 @@ private:
|
|||
// identifier is invalid pending TODO(CLDR-13701).
|
||||
bool fAfterPer = false;
|
||||
|
||||
// Set to true when we've just seen a "per-". This is used to determine if
|
||||
// the next token can be a constant denominator token.
|
||||
bool fJustSawPer = false;
|
||||
|
||||
Parser() : fSource(""), fTrie(u"") {}
|
||||
|
||||
Parser(StringPiece source)
|
||||
|
@ -640,6 +755,10 @@ private:
|
|||
// Saves the position in the fSource string for the end of the most
|
||||
// recent matching token.
|
||||
int32_t previ = -1;
|
||||
|
||||
// Saves the position in the fSource string for later use in case of unit constant found.
|
||||
int32_t currentFIndex = fIndex;
|
||||
|
||||
// Find the longest token that matches a value in the trie:
|
||||
while (fIndex < fSource.length()) {
|
||||
auto result = fTrie.next(fSource.data()[fIndex++]);
|
||||
|
@ -658,12 +777,25 @@ private:
|
|||
// continue;
|
||||
}
|
||||
|
||||
if (match < 0) {
|
||||
status = kUnitIdentifierSyntaxError;
|
||||
} else {
|
||||
if (match >= 0) {
|
||||
fIndex = previ;
|
||||
return {match};
|
||||
}
|
||||
return {match};
|
||||
|
||||
// If no match was found, we check if the token is a constant denominator.
|
||||
// 1. We find the index of the start of the next token or the end of the string.
|
||||
int32_t endOfConstantIndex = fSource.find("-", currentFIndex);
|
||||
endOfConstantIndex = (endOfConstantIndex == -1) ? fSource.length() : endOfConstantIndex;
|
||||
if (endOfConstantIndex <= currentFIndex) {
|
||||
status = kUnitIdentifierSyntaxError;
|
||||
return {match};
|
||||
}
|
||||
|
||||
// 2. We extract the substring from the start of the constant to the end of the constant.
|
||||
StringPiece constantDenominatorStr =
|
||||
fSource.substr(currentFIndex, endOfConstantIndex - currentFIndex);
|
||||
fIndex = endOfConstantIndex;
|
||||
return Token::constantToken(constantDenominatorStr, status);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -680,10 +812,10 @@ private:
|
|||
* 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;
|
||||
SingleUnitOrConstant nextSingleUnitOrConstant(bool &sawAnd, UErrorCode &status) {
|
||||
SingleUnitImpl singleUnitResult;
|
||||
if (U_FAILURE(status)) {
|
||||
return result;
|
||||
return {};
|
||||
}
|
||||
|
||||
// state:
|
||||
|
@ -695,19 +827,22 @@ private:
|
|||
bool atStart = fIndex == 0;
|
||||
Token token = nextToken(status);
|
||||
if (U_FAILURE(status)) {
|
||||
return result;
|
||||
return {};
|
||||
}
|
||||
|
||||
fJustSawPer = false;
|
||||
|
||||
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;
|
||||
fJustSawPer = true;
|
||||
singleUnitResult.dimensionality = -1;
|
||||
|
||||
token = nextToken(status);
|
||||
if (U_FAILURE(status)) {
|
||||
return result;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -715,7 +850,7 @@ private:
|
|||
// via a compound part:
|
||||
if (token.getType() != Token::TYPE_COMPOUND_PART) {
|
||||
status = kUnitIdentifierSyntaxError;
|
||||
return result;
|
||||
return {};
|
||||
}
|
||||
|
||||
switch (token.getMatch()) {
|
||||
|
@ -724,15 +859,16 @@ private:
|
|||
// Mixed compound units not yet supported,
|
||||
// TODO(CLDR-13701).
|
||||
status = kUnitIdentifierSyntaxError;
|
||||
return result;
|
||||
return {};
|
||||
}
|
||||
fAfterPer = true;
|
||||
result.dimensionality = -1;
|
||||
fJustSawPer = true;
|
||||
singleUnitResult.dimensionality = -1;
|
||||
break;
|
||||
|
||||
case COMPOUND_PART_TIMES:
|
||||
if (fAfterPer) {
|
||||
result.dimensionality = -1;
|
||||
singleUnitResult.dimensionality = -1;
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -741,7 +877,7 @@ private:
|
|||
// Can't start with "-and-", and mixed compound units
|
||||
// not yet supported, TODO(CLDR-13701).
|
||||
status = kUnitIdentifierSyntaxError;
|
||||
return result;
|
||||
return {};
|
||||
}
|
||||
sawAnd = true;
|
||||
break;
|
||||
|
@ -749,52 +885,65 @@ private:
|
|||
|
||||
token = nextToken(status);
|
||||
if (U_FAILURE(status)) {
|
||||
return result;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
if (token.getType() == Token::TYPE_CONSTANT_DENOMINATOR) {
|
||||
if (!fJustSawPer) {
|
||||
status = kUnitIdentifierSyntaxError;
|
||||
return {};
|
||||
}
|
||||
|
||||
return SingleUnitOrConstant::constantDenominatorValue(token.getConstantDenominator());
|
||||
}
|
||||
|
||||
// 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:
|
||||
case Token::TYPE_POWER_PART:
|
||||
if (state > 0) {
|
||||
status = kUnitIdentifierSyntaxError;
|
||||
return result;
|
||||
return {};
|
||||
}
|
||||
singleUnitResult.dimensionality *= token.getPower();
|
||||
state = 1;
|
||||
break;
|
||||
|
||||
case Token::TYPE_PREFIX:
|
||||
if (state > 1) {
|
||||
status = kUnitIdentifierSyntaxError;
|
||||
return {};
|
||||
}
|
||||
singleUnitResult.unitPrefix = token.getUnitPrefix();
|
||||
state = 2;
|
||||
break;
|
||||
|
||||
case Token::TYPE_SIMPLE_UNIT:
|
||||
singleUnitResult.index = token.getSimpleUnitIndex();
|
||||
break;
|
||||
|
||||
default:
|
||||
status = kUnitIdentifierSyntaxError;
|
||||
return {};
|
||||
}
|
||||
|
||||
if (token.getType() == Token::TYPE_SIMPLE_UNIT) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!hasNext()) {
|
||||
// We ran out of tokens before finding a complete single unit.
|
||||
status = kUnitIdentifierSyntaxError;
|
||||
return result;
|
||||
return {};
|
||||
}
|
||||
token = nextToken(status);
|
||||
if (U_FAILURE(status)) {
|
||||
return result;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return SingleUnitOrConstant::singleUnitValue(singleUnitResult);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1145,6 +1294,7 @@ void MeasureUnitImpl::serialize(UErrorCode &status) {
|
|||
CharString result;
|
||||
bool beforePer = true;
|
||||
bool firstTimeNegativeDimension = false;
|
||||
bool constantDenominatorAppended = false;
|
||||
for (int32_t i = 0; i < this->singleUnits.length(); i++) {
|
||||
if (beforePer && (*this->singleUnits[i]).dimensionality < 0) {
|
||||
beforePer = false;
|
||||
|
@ -1168,43 +1318,103 @@ void MeasureUnitImpl::serialize(UErrorCode &status) {
|
|||
} else {
|
||||
result.append(StringPiece("-per-"), status);
|
||||
}
|
||||
} else {
|
||||
if (result.length() != 0) {
|
||||
|
||||
if (this->constantDenominator != 0) {
|
||||
result.appendNumber(this->constantDenominator, status);
|
||||
result.append(StringPiece("-"), status);
|
||||
constantDenominatorAppended = true;
|
||||
}
|
||||
|
||||
} else if (result.length() != 0) {
|
||||
result.append(StringPiece("-"), status);
|
||||
}
|
||||
}
|
||||
|
||||
this->singleUnits[i]->appendNeutralIdentifier(result, status);
|
||||
}
|
||||
|
||||
if (!constantDenominatorAppended && this->constantDenominator != 0) {
|
||||
result.append(StringPiece("-per-"), status);
|
||||
result.appendNumber(this->constantDenominator, status);
|
||||
}
|
||||
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
this->identifier = CharString(result, status);
|
||||
}
|
||||
|
||||
MeasureUnit MeasureUnitImpl::build(UErrorCode& status) && {
|
||||
MeasureUnit MeasureUnitImpl::build(UErrorCode &status) && {
|
||||
this->serialize(status);
|
||||
return MeasureUnit(std::move(*this));
|
||||
}
|
||||
|
||||
MeasureUnit MeasureUnit::forIdentifier(StringPiece identifier, UErrorCode& status) {
|
||||
MeasureUnit MeasureUnit::forIdentifier(StringPiece identifier, UErrorCode &status) {
|
||||
return Parser::from(identifier, status).parse(status).build(status);
|
||||
}
|
||||
|
||||
UMeasureUnitComplexity MeasureUnit::getComplexity(UErrorCode& status) const {
|
||||
UMeasureUnitComplexity MeasureUnit::getComplexity(UErrorCode &status) const {
|
||||
MeasureUnitImpl temp;
|
||||
return MeasureUnitImpl::forMeasureUnit(*this, temp, status).complexity;
|
||||
}
|
||||
|
||||
UMeasurePrefix MeasureUnit::getPrefix(UErrorCode& status) const {
|
||||
UMeasurePrefix MeasureUnit::getPrefix(UErrorCode &status) const {
|
||||
return SingleUnitImpl::forMeasureUnit(*this, status).unitPrefix;
|
||||
}
|
||||
|
||||
MeasureUnit MeasureUnit::withPrefix(UMeasurePrefix prefix, UErrorCode& status) const UPRV_NO_SANITIZE_UNDEFINED {
|
||||
MeasureUnit MeasureUnit::withPrefix(UMeasurePrefix prefix,
|
||||
UErrorCode &status) const UPRV_NO_SANITIZE_UNDEFINED {
|
||||
SingleUnitImpl singleUnit = SingleUnitImpl::forMeasureUnit(*this, status);
|
||||
singleUnit.unitPrefix = prefix;
|
||||
return singleUnit.build(status);
|
||||
}
|
||||
|
||||
uint64_t MeasureUnit::getConstantDenominator(UErrorCode &status) const {
|
||||
auto complexity = this->getComplexity(status);
|
||||
if (U_FAILURE(status)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (complexity != UMEASURE_UNIT_SINGLE && complexity != UMEASURE_UNIT_COMPOUND) {
|
||||
status = U_ILLEGAL_ARGUMENT_ERROR;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (this->fImpl == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return this->fImpl->constantDenominator;
|
||||
}
|
||||
|
||||
MeasureUnit MeasureUnit::withConstantDenominator(uint64_t denominator, UErrorCode &status) const {
|
||||
// To match the behavior of the Java API, we do not allow a constant denominator
|
||||
// bigger than LONG_MAX.
|
||||
if (denominator > LONG_MAX) {
|
||||
status = U_ILLEGAL_ARGUMENT_ERROR;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto complexity = this->getComplexity(status);
|
||||
if (U_FAILURE(status)) {
|
||||
return {};
|
||||
}
|
||||
if (complexity != UMEASURE_UNIT_SINGLE && complexity != UMEASURE_UNIT_COMPOUND) {
|
||||
status = U_ILLEGAL_ARGUMENT_ERROR;
|
||||
return {};
|
||||
}
|
||||
|
||||
MeasureUnitImpl impl = MeasureUnitImpl::forMeasureUnitMaybeCopy(*this, status);
|
||||
if (U_FAILURE(status)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
impl.constantDenominator = denominator;
|
||||
impl.complexity = (impl.singleUnits.length() < 2 && denominator == 0) ? UMEASURE_UNIT_SINGLE
|
||||
: UMEASURE_UNIT_COMPOUND;
|
||||
return std::move(impl).build(status);
|
||||
}
|
||||
|
||||
int32_t MeasureUnit::getDimensionality(UErrorCode& status) const {
|
||||
SingleUnitImpl singleUnit = SingleUnitImpl::forMeasureUnit(*this, status);
|
||||
if (U_FAILURE(status)) { return 0; }
|
||||
|
@ -1222,6 +1432,11 @@ MeasureUnit MeasureUnit::withDimensionality(int32_t dimensionality, UErrorCode&
|
|||
|
||||
MeasureUnit MeasureUnit::reciprocal(UErrorCode& status) const {
|
||||
MeasureUnitImpl impl = MeasureUnitImpl::forMeasureUnitMaybeCopy(*this, status);
|
||||
// The reciprocal of a unit that has a constant denominator is not allowed.
|
||||
if (impl.constantDenominator != 0) {
|
||||
status = U_ILLEGAL_ARGUMENT_ERROR;
|
||||
return {};
|
||||
}
|
||||
impl.takeReciprocal(status);
|
||||
return std::move(impl).build(status);
|
||||
}
|
||||
|
|
|
@ -328,6 +328,14 @@ class U_I18N_API MeasureUnitImpl : public UMemory {
|
|||
*/
|
||||
CharString identifier;
|
||||
|
||||
/**
|
||||
* Represents the unit constant denominator.
|
||||
*
|
||||
* NOTE:
|
||||
* if set to 0, it means that the constant is not set.
|
||||
*/
|
||||
uint64_t constantDenominator = 0;
|
||||
|
||||
// For calling serialize
|
||||
// TODO(icu-units#147): revisit serialization
|
||||
friend class number::impl::LongNameHandler;
|
||||
|
|
|
@ -552,6 +552,44 @@ class U_I18N_API MeasureUnit: public UObject {
|
|||
*/
|
||||
UMeasurePrefix getPrefix(UErrorCode& status) const;
|
||||
|
||||
#ifndef U_HIDE_DRAFT_API
|
||||
|
||||
/**
|
||||
* Creates a new MeasureUnit with a specified constant denominator.
|
||||
*
|
||||
* This method is applicable only to COMPOUND and SINGLE units. If invoked on a
|
||||
* MIXED unit, an error will be set in the status.
|
||||
*
|
||||
* NOTE: If the constant denominator is set to 0, it means that you are removing
|
||||
* the constant denominator.
|
||||
*
|
||||
* @param denominator The constant denominator to set.
|
||||
* @param status Set if this is not a COMPOUND or SINGLE unit or if another error occurs.
|
||||
* @return A new MeasureUnit with the specified constant denominator.
|
||||
* @draft ICU 77
|
||||
*/
|
||||
MeasureUnit withConstantDenominator(uint64_t denominator, UErrorCode &status) const;
|
||||
|
||||
/**
|
||||
* Retrieves the constant denominator for this COMPOUND unit.
|
||||
*
|
||||
* Examples:
|
||||
* - For the unit "liter-per-1000-kiloliter", the constant denominator is 1000.
|
||||
* - For the unit "liter-per-kilometer", the constant denominator is zero.
|
||||
*
|
||||
* This method is applicable only to COMPOUND and SINGLE units. If invoked on
|
||||
* a MIXED unit, an error will be set in the status.
|
||||
*
|
||||
* NOTE: If no constant denominator exists, the method returns 0.
|
||||
*
|
||||
* @param status Set if this is not a COMPOUND or SINGLE unit or if another error occurs.
|
||||
* @return The value of the constant denominator.
|
||||
* @draft ICU 77
|
||||
*/
|
||||
uint64_t getConstantDenominator(UErrorCode &status) const;
|
||||
|
||||
#endif /* U_HIDE_DRAFT_API */
|
||||
|
||||
/**
|
||||
* Creates a MeasureUnit which is this SINGLE unit augmented with the specified dimensionality
|
||||
* (power). For example, if dimensionality is 2, the unit will be squared.
|
||||
|
@ -591,7 +629,9 @@ class U_I18N_API MeasureUnit: public UObject {
|
|||
* NOTE: Only works on SINGLE and COMPOUND units. If this is a MIXED unit, an error will
|
||||
* occur. For more information, see UMeasureUnitComplexity.
|
||||
*
|
||||
* @param status Set if this is a MIXED unit or if another error occurs.
|
||||
* NOTE: An Error will be returned for units that have a constant denominator.
|
||||
*
|
||||
* @param status Set if this is a MIXED unit, has a constant denominator or if another error occurs.
|
||||
* @return The reciprocal of the target unit.
|
||||
* @stable ICU 67
|
||||
*/
|
||||
|
@ -627,6 +667,10 @@ class U_I18N_API MeasureUnit: public UObject {
|
|||
*
|
||||
* If this is a SINGLE unit, an array of length 1 will be returned.
|
||||
*
|
||||
* NOTE: For units with a constant denominator, the returned single units will
|
||||
* not include the constant denominator. To obtain the constant denominator,
|
||||
* retrieve it from the original unit.
|
||||
*
|
||||
* @param status Set if an error occurs.
|
||||
* @return A pair with the list of units as a LocalArray and the number of units in the list.
|
||||
* @stable ICU 68
|
||||
|
|
|
@ -1123,6 +1123,7 @@ group: units_extra
|
|||
measunit_extra.o
|
||||
deps
|
||||
units bytestriebuilder bytestrie resourcebundle uclean_i18n
|
||||
double_conversion
|
||||
|
||||
group: units
|
||||
measunit.o currunit.o
|
||||
|
|
|
@ -5450,7 +5450,7 @@ void MeasureFormatTest::TestUnitPerUnitResolution() {
|
|||
actual,
|
||||
pos,
|
||||
status);
|
||||
assertEquals("", "50 psi", actual);
|
||||
assertEquals("TestUnitPerUnitResolution", "50 psi", actual);
|
||||
}
|
||||
|
||||
void MeasureFormatTest::TestIndividualPluralFallback() {
|
||||
|
@ -5708,6 +5708,19 @@ void MeasureFormatTest::TestInvalidIdentifiers() {
|
|||
|
||||
// Compound units not supported in mixed units yet. TODO(CLDR-13701).
|
||||
"kilonewton-meter-and-newton-meter",
|
||||
|
||||
// Invalid identifiers with constants.
|
||||
"meter-per--20--second",
|
||||
"meter-per-1000-1e9-second",
|
||||
"meter-per-1e20-second",
|
||||
"per-1000",
|
||||
"meter-per-1000-1000",
|
||||
"meter-per-1000-second-1000-kilometer",
|
||||
"1000-meter",
|
||||
"meter-1000",
|
||||
"meter-per-1000-1000",
|
||||
"meter-per-1000-second-1000-kilometer",
|
||||
"per-1000-and-per-1000",
|
||||
};
|
||||
|
||||
for (const auto& input : inputs) {
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include "units_router.h"
|
||||
#include "uparse.h"
|
||||
#include "uresimp.h"
|
||||
#include <climits>
|
||||
|
||||
struct UnitConversionTestCase {
|
||||
const StringPiece source;
|
||||
|
@ -50,6 +51,8 @@ class UnitsTest : public IntlTest {
|
|||
void testComplexUnitsConverter();
|
||||
void testComplexUnitsConverterSorting();
|
||||
void testUnitPreferencesWithCLDRTests();
|
||||
void testUnitsConstantsDenomenator();
|
||||
void testMeasureUnit_withConstantDenominator();
|
||||
void testConverter();
|
||||
};
|
||||
|
||||
|
@ -67,6 +70,8 @@ void UnitsTest::runIndexedTest(int32_t index, UBool exec, const char *&name, cha
|
|||
TESTCASE_AUTO(testComplexUnitsConverter);
|
||||
TESTCASE_AUTO(testComplexUnitsConverterSorting);
|
||||
TESTCASE_AUTO(testUnitPreferencesWithCLDRTests);
|
||||
TESTCASE_AUTO(testUnitsConstantsDenomenator);
|
||||
TESTCASE_AUTO(testMeasureUnit_withConstantDenominator);
|
||||
TESTCASE_AUTO(testConverter);
|
||||
TESTCASE_AUTO_END;
|
||||
}
|
||||
|
@ -1157,4 +1162,166 @@ void UnitsTest::testUnitPreferencesWithCLDRTests() {
|
|||
}
|
||||
}
|
||||
|
||||
void UnitsTest::testUnitsConstantsDenomenator() {
|
||||
IcuTestErrorCode status(*this, "UnitTests::testUnitsConstantsDenomenator");
|
||||
|
||||
// Test Cases
|
||||
struct TestCase {
|
||||
const char *source;
|
||||
const uint64_t expectedConstant;
|
||||
} testCases[]{
|
||||
{"meter-per-1000", 1000},
|
||||
{"liter-per-1000-kiloliter", 1000},
|
||||
{"liter-per-kilometer", 0},
|
||||
{"second-per-1000-minute", 1000},
|
||||
{"gram-per-1000-kilogram", 1000},
|
||||
{"meter-per-100", 100},
|
||||
{"portion-per-1", 1},
|
||||
{"portion-per-2", 2},
|
||||
{"portion-per-3", 3},
|
||||
{"portion-per-4", 4},
|
||||
{"portion-per-5", 5},
|
||||
{"portion-per-6", 6},
|
||||
{"portion-per-7", 7},
|
||||
{"portion-per-8", 8},
|
||||
{"portion-per-9", 9},
|
||||
// Test for constant denominators that are powers of 10
|
||||
{"portion-per-10", 10},
|
||||
{"portion-per-100", 100},
|
||||
{"portion-per-1000", 1000},
|
||||
{"portion-per-10000", 10000},
|
||||
{"portion-per-100000", 100000},
|
||||
{"portion-per-1000000", 1000000},
|
||||
{"portion-per-10000000", 10000000},
|
||||
{"portion-per-100000000", 100000000},
|
||||
{"portion-per-1000000000", 1000000000},
|
||||
{"portion-per-10000000000", 10000000000},
|
||||
{"portion-per-100000000000", 100000000000},
|
||||
{"portion-per-1000000000000", 1000000000000},
|
||||
{"portion-per-10000000000000", 10000000000000},
|
||||
{"portion-per-100000000000000", 100000000000000},
|
||||
{"portion-per-1000000000000000", 1000000000000000},
|
||||
{"portion-per-10000000000000000", 10000000000000000},
|
||||
{"portion-per-100000000000000000", 100000000000000000},
|
||||
{"portion-per-1000000000000000000", 1000000000000000000},
|
||||
// Test for constant denominators that are represented as scientific notation
|
||||
// numbers.
|
||||
{"portion-per-1e1", 10},
|
||||
{"portion-per-1E1", 10},
|
||||
{"portion-per-1e2", 100},
|
||||
{"portion-per-1E2", 100},
|
||||
{"portion-per-1e3", 1000},
|
||||
{"portion-per-1E3", 1000},
|
||||
{"portion-per-1e4", 10000},
|
||||
{"portion-per-1E4", 10000},
|
||||
{"portion-per-1e5", 100000},
|
||||
{"portion-per-1E5", 100000},
|
||||
{"portion-per-1e6", 1000000},
|
||||
{"portion-per-1E6", 1000000},
|
||||
{"portion-per-1e10", 10000000000},
|
||||
{"portion-per-1E10", 10000000000},
|
||||
{"portion-per-1e18", 1000000000000000000},
|
||||
{"portion-per-1E18", 1000000000000000000},
|
||||
// Test for constant denominators that are randomly selected.
|
||||
{"liter-per-12345-kilometer", 12345},
|
||||
{"per-1000-kilometer", 1000},
|
||||
{"liter-per-1000-kiloliter", 1000},
|
||||
// Test for constant denominators that give 0.
|
||||
{"meter", 0},
|
||||
{"meter-per-second", 0},
|
||||
{"meter-per-square-second", 0},
|
||||
// NOTE: The following constant denominator should be 0. However, since
|
||||
// `100-kilometer` is treated as a unit in CLDR,
|
||||
// the unit does not have a constant denominator.
|
||||
// This issue should be addressed in CLDR.
|
||||
{"meter-per-100-kilometer", 0},
|
||||
// NOTE: the following CLDR identifier should be invalid, but because
|
||||
// `100-kilometer` is considered a unit in CLDR,
|
||||
// one `100` will be considered as a unit constant denominator and the other
|
||||
// `100` will be considered part of the unit.
|
||||
// This issue should be addressed in CLDR.
|
||||
{"meter-per-100-100-kilometer", 100},
|
||||
};
|
||||
|
||||
for (const auto &testCase : testCases) {
|
||||
MeasureUnit unit = MeasureUnit::forIdentifier(testCase.source, status);
|
||||
if (status.errIfFailureAndReset("forIdentifier(\"%s\")", testCase.source)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint64_t constant = unit.getConstantDenominator(status);
|
||||
if (status.errIfFailureAndReset("getConstantDenominator(\"%s\")", testCase.source)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto complexity = unit.getComplexity(status);
|
||||
if (status.errIfFailureAndReset("getComplexity(\"%s\")", testCase.source)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (constant != testCase.expectedConstant) {
|
||||
assertTrue("getConstantDenominator(\"%s\")", false);
|
||||
}
|
||||
if (constant != 0) {
|
||||
assertEquals("getComplexity(\"%s\")", UMEASURE_UNIT_COMPOUND, complexity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UnitsTest::testMeasureUnit_withConstantDenominator() {
|
||||
IcuTestErrorCode status(*this, "UnitsTest::testMeasureUnit_withConstantDenominator");
|
||||
|
||||
// Test Cases
|
||||
struct TestCase {
|
||||
const char *source;
|
||||
const uint64_t constantDenominator;
|
||||
const UMeasureUnitComplexity expectedComplexity;
|
||||
} testCases[]{
|
||||
{"meter-per-second", 100, UMEASURE_UNIT_COMPOUND},
|
||||
{"meter-per-100-second", 0, UMEASURE_UNIT_COMPOUND},
|
||||
{"portion", 100, UMEASURE_UNIT_COMPOUND},
|
||||
{"portion-per-100", 0, UMEASURE_UNIT_SINGLE},
|
||||
|
||||
};
|
||||
|
||||
for (auto testCase : testCases) {
|
||||
auto unit = MeasureUnit::forIdentifier(testCase.source, status);
|
||||
if (status.errIfFailureAndReset("forIdentifier(\"%s\")", testCase.source)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
unit = unit.withConstantDenominator(testCase.constantDenominator, status);
|
||||
if (status.errIfFailureAndReset("withConstantDenominator(\"%s\")", testCase.source)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto actualConstantDenominator = unit.getConstantDenominator(status);
|
||||
if (status.errIfFailureAndReset("getConstantDenominator(\"%s\")", testCase.source)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto actualComplexity = unit.getComplexity(status);
|
||||
if (status.errIfFailureAndReset("getComplexity(\"%s\")", testCase.source)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (actualConstantDenominator != testCase.constantDenominator) {
|
||||
assertTrue("getConstantDenominator(\"%s\")", false);
|
||||
}
|
||||
assertEquals("getComplexity(\"%s\")", testCase.expectedComplexity, actualComplexity);
|
||||
}
|
||||
|
||||
// Test for invalid constant denominator
|
||||
auto unit = MeasureUnit::forIdentifier("portion", status);
|
||||
if (status.errIfFailureAndReset("forIdentifier(\"portion\")")) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t denominator = LONG_MAX;
|
||||
denominator++;
|
||||
unit = unit.withConstantDenominator(denominator, status);
|
||||
assertTrue("There is a failure caused by withConstantDenominator(\"portion\")", status.isFailure());
|
||||
status.reset();
|
||||
}
|
||||
|
||||
#endif /* #if !UCONFIG_NO_FORMATTING */
|
||||
|
|
Loading…
Add table
Reference in a new issue