ICU-13513 Merging trunk to branch (includes the big reformatting commit).
X-SVN-Rev: 40751
This commit is contained in:
commit
bdb19075f8
@ -867,9 +867,7 @@ int32_t RuleBasedBreakIterator::handleNext() {
|
||||
// State Transition - move machine to its next state
|
||||
//
|
||||
|
||||
// Note: fNextState is defined as uint16_t[2], but we are casting
|
||||
// a generated RBBI table to RBBIStateTableRow and some tables
|
||||
// actually have more than 2 categories.
|
||||
// fNextState is a variable-length array.
|
||||
U_ASSERT(category<fData->fHeader->fCatCount);
|
||||
state = row->fNextState[category]; /*Not accessing beyond memory*/
|
||||
row = (RBBIStateTableRow *)
|
||||
@ -1041,9 +1039,7 @@ int32_t RuleBasedBreakIterator::handlePrevious(int32_t fromPosition) {
|
||||
// State Transition - move machine to its next state
|
||||
//
|
||||
|
||||
// Note: fNextState is defined as uint16_t[2], but we are casting
|
||||
// a generated RBBI table to RBBIStateTableRow and some tables
|
||||
// actually have more than 2 categories.
|
||||
// fNextState is a variable-length array.
|
||||
U_ASSERT(category<fData->fHeader->fCatCount);
|
||||
state = row->fNextState[category]; /*Not accessing beyond memory*/
|
||||
row = (RBBIStateTableRow *)
|
||||
|
@ -116,9 +116,10 @@ struct RBBIStateTableRow {
|
||||
/* StatusTable of the set of matching */
|
||||
/* tags (rule status values) */
|
||||
int16_t fReserved;
|
||||
uint16_t fNextState[2]; /* Next State, indexed by char category. */
|
||||
/* This array does not have two elements */
|
||||
/* Array Size is actually fData->fHeader->fCatCount */
|
||||
uint16_t fNextState[1]; /* Next State, indexed by char category. */
|
||||
/* Variable-length array declared with length 1 */
|
||||
/* to disable bounds checkers. */
|
||||
/* Array Size is actually fData->fHeader->fCatCount*/
|
||||
/* CAUTION: see RBBITableBuilder::getTableSize() */
|
||||
/* before changing anything here. */
|
||||
};
|
||||
@ -129,7 +130,9 @@ struct RBBIStateTable {
|
||||
uint32_t fRowLen; /* Length of a state table row, in bytes. */
|
||||
uint32_t fFlags; /* Option Flags for this state table */
|
||||
uint32_t fReserved; /* reserved */
|
||||
char fTableData[4]; /* First RBBIStateTableRow begins here. */
|
||||
char fTableData[1]; /* First RBBIStateTableRow begins here. */
|
||||
/* Variable-length array declared with length 1 */
|
||||
/* to disable bounds checkers. */
|
||||
/* (making it char[] simplifies ugly address */
|
||||
/* arithmetic for indexing variable length rows.) */
|
||||
};
|
||||
|
@ -1095,15 +1095,12 @@ int32_t RBBITableBuilder::getTableSize() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size = sizeof(RBBIStateTable) - 4; // The header, with no rows to the table.
|
||||
size = offsetof(RBBIStateTable, fTableData); // The header, with no rows to the table.
|
||||
|
||||
numRows = fDStates->size();
|
||||
numCols = fRB->fSetBuilder->getNumCharCategories();
|
||||
|
||||
// Note The declaration of RBBIStateTableRow is for a table of two columns.
|
||||
// Therefore we subtract two from numCols when determining
|
||||
// how much storage to add to a row for the total columns.
|
||||
rowSize = sizeof(RBBIStateTableRow) + sizeof(uint16_t)*(numCols-2);
|
||||
rowSize = offsetof(RBBIStateTableRow, fNextState) + sizeof(uint16_t)*numCols;
|
||||
size += numRows * rowSize;
|
||||
return size;
|
||||
}
|
||||
@ -1126,14 +1123,14 @@ void RBBITableBuilder::exportTable(void *where) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (fRB->fSetBuilder->getNumCharCategories() > 0x7fff ||
|
||||
int32_t catCount = fRB->fSetBuilder->getNumCharCategories();
|
||||
if (catCount > 0x7fff ||
|
||||
fDStates->size() > 0x7fff) {
|
||||
*fStatus = U_BRK_INTERNAL_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
table->fRowLen = sizeof(RBBIStateTableRow) +
|
||||
sizeof(uint16_t) * (fRB->fSetBuilder->getNumCharCategories() - 2);
|
||||
table->fRowLen = offsetof(RBBIStateTableRow, fNextState) + sizeof(uint16_t) * catCount;
|
||||
table->fNumStates = fDStates->size();
|
||||
table->fFlags = 0;
|
||||
if (fRB->fLookAheadHardBreak) {
|
||||
@ -1152,7 +1149,7 @@ void RBBITableBuilder::exportTable(void *where) {
|
||||
row->fAccepting = (int16_t)sd->fAccepting;
|
||||
row->fLookAhead = (int16_t)sd->fLookAhead;
|
||||
row->fTagIdx = (int16_t)sd->fTagsIdx;
|
||||
for (col=0; col<fRB->fSetBuilder->getNumCharCategories(); col++) {
|
||||
for (col=0; col<catCount; col++) {
|
||||
row->fNextState[col] = (uint16_t)sd->fDtran->elementAti(col);
|
||||
}
|
||||
}
|
||||
|
@ -77,7 +77,11 @@ typedef struct {
|
||||
typedef struct {
|
||||
uint32_t count;
|
||||
uint32_t reserved;
|
||||
PointerTOCEntry entry[2]; /* Actual size is from count. */
|
||||
/**
|
||||
* Variable-length array declared with length 1 to disable bounds checkers.
|
||||
* The actual array length is in the count field.
|
||||
*/
|
||||
PointerTOCEntry entry[1];
|
||||
} PointerTOC;
|
||||
|
||||
|
||||
|
@ -52,7 +52,11 @@ typedef struct {
|
||||
|
||||
typedef struct {
|
||||
uint32_t count;
|
||||
UDataOffsetTOCEntry entry[2]; /* Actual size of array is from count. */
|
||||
/**
|
||||
* Variable-length array declared with length 1 to disable bounds checkers.
|
||||
* The actual array length is in the count field.
|
||||
*/
|
||||
UDataOffsetTOCEntry entry[1];
|
||||
} UDataOffsetTOC;
|
||||
|
||||
/**
|
||||
|
@ -342,7 +342,7 @@ _strFromWCS( UChar *dest,
|
||||
pSrcLimit = src + srcLength;
|
||||
|
||||
for(;;){
|
||||
register int32_t nulLen = 0;
|
||||
int32_t nulLen = 0;
|
||||
|
||||
/* find nulls in the string */
|
||||
while(nulLen<srcLength && pSrc[nulLen++]!=0){
|
||||
|
@ -50,10 +50,12 @@ static void syntaxError(const UnicodeString& pattern,
|
||||
parseError.preContext[stop-start] = 0;
|
||||
|
||||
//for post-context
|
||||
start = pos+1;
|
||||
stop = ((pos+U_PARSE_CONTEXT_LEN)<=pattern.length()) ? (pos+(U_PARSE_CONTEXT_LEN-1)) :
|
||||
pattern.length();
|
||||
pattern.extract(start,stop-start,parseError.postContext,0);
|
||||
start = pattern.moveIndex32(pos, 1);
|
||||
stop = pos + U_PARSE_CONTEXT_LEN - 1;
|
||||
if (stop > pattern.length()) {
|
||||
stop = pattern.length();
|
||||
}
|
||||
pattern.extract(start, stop - start, parseError.postContext, 0);
|
||||
//null terminate the buffer
|
||||
parseError.postContext[stop-start]= 0;
|
||||
}
|
||||
|
@ -764,10 +764,11 @@ UnicodeString &MeasureFormat::formatMeasurePerUnit(
|
||||
if (U_FAILURE(status)) {
|
||||
return appendTo;
|
||||
}
|
||||
MeasureUnit *resolvedUnit =
|
||||
MeasureUnit::resolveUnitPerUnit(measure.getUnit(), perUnit);
|
||||
if (resolvedUnit != NULL) {
|
||||
Measure newMeasure(measure.getNumber(), resolvedUnit, status);
|
||||
bool isResolved = false;
|
||||
MeasureUnit resolvedUnit =
|
||||
MeasureUnit::resolveUnitPerUnit(measure.getUnit(), perUnit, &isResolved);
|
||||
if (isResolved) {
|
||||
Measure newMeasure(measure.getNumber(), new MeasureUnit(resolvedUnit), status);
|
||||
return formatMeasure(
|
||||
newMeasure, **numberFormat, appendTo, pos, status);
|
||||
}
|
||||
|
@ -1211,8 +1211,8 @@ int32_t MeasureUnit::internalGetIndexForTypeAndSubtype(const char *type, const c
|
||||
return gIndexes[t] + st - gOffsets[t];
|
||||
}
|
||||
|
||||
MeasureUnit *MeasureUnit::resolveUnitPerUnit(
|
||||
const MeasureUnit &unit, const MeasureUnit &perUnit) {
|
||||
MeasureUnit MeasureUnit::resolveUnitPerUnit(
|
||||
const MeasureUnit &unit, const MeasureUnit &perUnit, bool* isResolved) {
|
||||
int32_t unitOffset = unit.getOffset();
|
||||
int32_t perUnitOffset = perUnit.getOffset();
|
||||
|
||||
@ -1233,10 +1233,13 @@ MeasureUnit *MeasureUnit::resolveUnitPerUnit(
|
||||
} else {
|
||||
// We found a resolution for our unit / per-unit combo
|
||||
// return it.
|
||||
return new MeasureUnit(midRow[2], midRow[3]);
|
||||
*isResolved = true;
|
||||
return MeasureUnit(midRow[2], midRow[3]);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
|
||||
*isResolved = false;
|
||||
return MeasureUnit();
|
||||
}
|
||||
|
||||
MeasureUnit *MeasureUnit::create(int typeId, int subTypeId, UErrorCode &status) {
|
||||
|
@ -70,6 +70,7 @@ int32_t AffixUtils::estimateLength(const CharSequence &patternString, UErrorCode
|
||||
case STATE_FIRST_QUOTE:
|
||||
case STATE_INSIDE_QUOTE:
|
||||
status = U_ILLEGAL_ARGUMENT_ERROR;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -45,6 +45,25 @@ Derived NumberFormatterSettings<Derived>::adoptUnit(const icu::MeasureUnit *unit
|
||||
return copy;
|
||||
}
|
||||
|
||||
template<typename Derived>
|
||||
Derived NumberFormatterSettings<Derived>::perUnit(const icu::MeasureUnit &perUnit) const {
|
||||
Derived copy(*this);
|
||||
// See comments above about slicing.
|
||||
copy.fMacros.perUnit = perUnit;
|
||||
return copy;
|
||||
}
|
||||
|
||||
template<typename Derived>
|
||||
Derived NumberFormatterSettings<Derived>::adoptPerUnit(const icu::MeasureUnit *perUnit) const {
|
||||
Derived copy(*this);
|
||||
// See comments above about slicing and ownership.
|
||||
if (perUnit != nullptr) {
|
||||
copy.fMacros.perUnit = *perUnit;
|
||||
delete perUnit;
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
template<typename Derived>
|
||||
Derived NumberFormatterSettings<Derived>::rounding(const Rounder &rounder) const {
|
||||
Derived copy(*this);
|
||||
|
@ -308,6 +308,7 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps ¯os, bool safe,
|
||||
LongNameHandler::forMeasureUnit(
|
||||
macros.locale,
|
||||
macros.unit,
|
||||
macros.perUnit,
|
||||
unitWidth,
|
||||
resolvePluralRules(macros.rules, macros.locale, status),
|
||||
chain,
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
|
||||
|
||||
#include "unicode/simpleformatter.h"
|
||||
#include "unicode/ures.h"
|
||||
#include "ureslocs.h"
|
||||
#include "charstr.h"
|
||||
@ -19,6 +20,37 @@ using namespace icu::number::impl;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int32_t DNAM_INDEX = StandardPlural::Form::COUNT;
|
||||
constexpr int32_t PER_INDEX = StandardPlural::Form::COUNT + 1;
|
||||
constexpr int32_t ARRAY_LENGTH = StandardPlural::Form::COUNT + 2;
|
||||
|
||||
static int32_t getIndex(const char* pluralKeyword, UErrorCode& status) {
|
||||
// pluralKeyword can also be "dnam" or "per"
|
||||
if (uprv_strcmp(pluralKeyword, "dnam") == 0) {
|
||||
return DNAM_INDEX;
|
||||
} else if (uprv_strcmp(pluralKeyword, "per") == 0) {
|
||||
return PER_INDEX;
|
||||
} else {
|
||||
StandardPlural::Form plural = StandardPlural::fromString(pluralKeyword, status);
|
||||
return plural;
|
||||
}
|
||||
}
|
||||
|
||||
static UnicodeString getWithPlural(
|
||||
const UnicodeString* strings,
|
||||
int32_t plural,
|
||||
UErrorCode& status) {
|
||||
UnicodeString result = strings[plural];
|
||||
if (result.isBogus()) {
|
||||
result = strings[StandardPlural::Form::OTHER];
|
||||
}
|
||||
if (result.isBogus()) {
|
||||
// There should always be data in the "other" plural variant.
|
||||
status = U_INTERNAL_PROGRAM_ERROR;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////
|
||||
/// BEGIN DATA LOADING ///
|
||||
@ -28,7 +60,7 @@ class PluralTableSink : public ResourceSink {
|
||||
public:
|
||||
explicit PluralTableSink(UnicodeString *outArray) : outArray(outArray) {
|
||||
// Initialize the array to bogus strings.
|
||||
for (int32_t i = 0; i < StandardPlural::Form::COUNT; i++) {
|
||||
for (int32_t i = 0; i < ARRAY_LENGTH; i++) {
|
||||
outArray[i].setToBogus();
|
||||
}
|
||||
}
|
||||
@ -36,17 +68,13 @@ class PluralTableSink : public ResourceSink {
|
||||
void put(const char *key, ResourceValue &value, UBool /*noFallback*/, UErrorCode &status) U_OVERRIDE {
|
||||
ResourceTable pluralsTable = value.getTable(status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
for (int i = 0; pluralsTable.getKeyAndValue(i, key, value); ++i) {
|
||||
// In MeasureUnit data, ignore dnam and per units for now.
|
||||
if (uprv_strcmp(key, "dnam") == 0 || uprv_strcmp(key, "per") == 0) {
|
||||
continue;
|
||||
}
|
||||
StandardPlural::Form plural = StandardPlural::fromString(key, status);
|
||||
for (int32_t i = 0; pluralsTable.getKeyAndValue(i, key, value); ++i) {
|
||||
int32_t index = getIndex(key, status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
if (!outArray[plural].isBogus()) {
|
||||
if (!outArray[index].isBogus()) {
|
||||
continue;
|
||||
}
|
||||
outArray[plural] = value.getUnicodeString(status);
|
||||
outArray[index] = value.getUnicodeString(status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
}
|
||||
}
|
||||
@ -105,6 +133,22 @@ void getCurrencyLongNameData(const Locale &locale, const CurrencyUnit ¤cy,
|
||||
}
|
||||
}
|
||||
|
||||
UnicodeString getPerUnitFormat(const Locale& locale, const UNumberUnitWidth &width, UErrorCode& status) {
|
||||
LocalUResourceBundlePointer unitsBundle(ures_open(U_ICUDATA_UNIT, locale.getName(), &status));
|
||||
if (U_FAILURE(status)) { return {}; }
|
||||
CharString key;
|
||||
key.append("units", status);
|
||||
if (width == UNUM_UNIT_WIDTH_NARROW) {
|
||||
key.append("Narrow", status);
|
||||
} else if (width == UNUM_UNIT_WIDTH_SHORT) {
|
||||
key.append("Short", status);
|
||||
}
|
||||
key.append("/compound/per", status);
|
||||
int32_t len = 0;
|
||||
const UChar* ptr = ures_getStringByKeyWithFallback(unitsBundle.getAlias(), key.data(), &len, &status);
|
||||
return UnicodeString(ptr, len);
|
||||
}
|
||||
|
||||
////////////////////////
|
||||
/// END DATA LOADING ///
|
||||
////////////////////////
|
||||
@ -112,11 +156,24 @@ void getCurrencyLongNameData(const Locale &locale, const CurrencyUnit ¤cy,
|
||||
} // namespace
|
||||
|
||||
LongNameHandler
|
||||
LongNameHandler::forMeasureUnit(const Locale &loc, const MeasureUnit &unit, const UNumberUnitWidth &width,
|
||||
const PluralRules *rules, const MicroPropsGenerator *parent,
|
||||
UErrorCode &status) {
|
||||
LongNameHandler::forMeasureUnit(const Locale &loc, const MeasureUnit &unitRef, const MeasureUnit &perUnit,
|
||||
const UNumberUnitWidth &width, const PluralRules *rules,
|
||||
const MicroPropsGenerator *parent, UErrorCode &status) {
|
||||
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).
|
||||
bool isResolved = false;
|
||||
MeasureUnit resolved = MeasureUnit::resolveUnitPerUnit(unit, perUnit, &isResolved);
|
||||
if (isResolved) {
|
||||
unit = resolved;
|
||||
} else {
|
||||
// No simplified form is available.
|
||||
return forCompoundUnit(loc, unit, perUnit, width, rules, parent, status);
|
||||
}
|
||||
}
|
||||
|
||||
LongNameHandler result(rules, parent);
|
||||
UnicodeString simpleFormats[StandardPlural::Form::COUNT];
|
||||
UnicodeString simpleFormats[ARRAY_LENGTH];
|
||||
getMeasureData(loc, unit, width, simpleFormats, status);
|
||||
if (U_FAILURE(status)) { return result; }
|
||||
// TODO: What field to use for units?
|
||||
@ -124,12 +181,47 @@ LongNameHandler::forMeasureUnit(const Locale &loc, const MeasureUnit &unit, cons
|
||||
return result;
|
||||
}
|
||||
|
||||
LongNameHandler
|
||||
LongNameHandler::forCompoundUnit(const Locale &loc, const MeasureUnit &unit, const MeasureUnit &perUnit,
|
||||
const UNumberUnitWidth &width, const PluralRules *rules,
|
||||
const MicroPropsGenerator *parent, UErrorCode &status) {
|
||||
LongNameHandler result(rules, parent);
|
||||
UnicodeString primaryData[ARRAY_LENGTH];
|
||||
getMeasureData(loc, unit, width, primaryData, status);
|
||||
if (U_FAILURE(status)) { return result; }
|
||||
UnicodeString secondaryData[ARRAY_LENGTH];
|
||||
getMeasureData(loc, perUnit, width, secondaryData, status);
|
||||
if (U_FAILURE(status)) { return result; }
|
||||
|
||||
UnicodeString perUnitFormat;
|
||||
if (!secondaryData[PER_INDEX].isBogus()) {
|
||||
perUnitFormat = secondaryData[PER_INDEX];
|
||||
} else {
|
||||
UnicodeString rawPerUnitFormat = getPerUnitFormat(loc, width, status);
|
||||
if (U_FAILURE(status)) { return result; }
|
||||
// rawPerUnitFormat is something like "{0}/{1}"; we need to substitute in the secondary unit.
|
||||
SimpleFormatter compiled(rawPerUnitFormat, 2, 2, status);
|
||||
if (U_FAILURE(status)) { return result; }
|
||||
UnicodeString secondaryFormat = getWithPlural(secondaryData, StandardPlural::Form::ONE, status);
|
||||
if (U_FAILURE(status)) { return result; }
|
||||
SimpleFormatter secondaryCompiled(secondaryFormat, 1, 1, status);
|
||||
if (U_FAILURE(status)) { return result; }
|
||||
UnicodeString secondaryString = secondaryCompiled.getTextWithNoArguments().trim();
|
||||
// TODO: Why does UnicodeString need to be explicit in the following line?
|
||||
compiled.format(UnicodeString(u"{0}"), secondaryString, perUnitFormat, status);
|
||||
if (U_FAILURE(status)) { return result; }
|
||||
}
|
||||
// TODO: What field to use for units?
|
||||
multiSimpleFormatsToModifiers(primaryData, perUnitFormat, UNUM_FIELD_COUNT, result.fModifiers, status);
|
||||
return result;
|
||||
}
|
||||
|
||||
LongNameHandler LongNameHandler::forCurrencyLongNames(const Locale &loc, const CurrencyUnit ¤cy,
|
||||
const PluralRules *rules,
|
||||
const MicroPropsGenerator *parent,
|
||||
UErrorCode &status) {
|
||||
LongNameHandler result(rules, parent);
|
||||
UnicodeString simpleFormats[StandardPlural::Form::COUNT];
|
||||
UnicodeString simpleFormats[ARRAY_LENGTH];
|
||||
getCurrencyLongNameData(loc, currency, simpleFormats, status);
|
||||
if (U_FAILURE(status)) { return result; }
|
||||
simpleFormatsToModifiers(simpleFormats, UNUM_CURRENCY_FIELD, result.fModifiers, status);
|
||||
@ -139,20 +231,30 @@ LongNameHandler LongNameHandler::forCurrencyLongNames(const Locale &loc, const C
|
||||
void LongNameHandler::simpleFormatsToModifiers(const UnicodeString *simpleFormats, Field field,
|
||||
SimpleModifier *output, UErrorCode &status) {
|
||||
for (int32_t i = 0; i < StandardPlural::Form::COUNT; i++) {
|
||||
UnicodeString simpleFormat = simpleFormats[i];
|
||||
if (simpleFormat.isBogus()) {
|
||||
simpleFormat = simpleFormats[StandardPlural::Form::OTHER];
|
||||
}
|
||||
if (simpleFormat.isBogus()) {
|
||||
// There should always be data in the "other" plural variant.
|
||||
status = U_INTERNAL_PROGRAM_ERROR;
|
||||
return;
|
||||
}
|
||||
UnicodeString simpleFormat = getWithPlural(simpleFormats, i, status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
SimpleFormatter compiledFormatter(simpleFormat, 1, 1, status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
output[i] = SimpleModifier(compiledFormatter, field, false);
|
||||
}
|
||||
}
|
||||
|
||||
void LongNameHandler::multiSimpleFormatsToModifiers(const UnicodeString *leadFormats, UnicodeString trailFormat,
|
||||
Field field, SimpleModifier *output, UErrorCode &status) {
|
||||
SimpleFormatter trailCompiled(trailFormat, 1, 1, status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
for (int32_t i = 0; i < StandardPlural::Form::COUNT; i++) {
|
||||
UnicodeString leadFormat = getWithPlural(leadFormats, i, status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
UnicodeString compoundFormat;
|
||||
trailCompiled.format(leadFormat, compoundFormat, status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
SimpleFormatter compoundCompiled(compoundFormat, 1, 1, status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
output[i] = SimpleModifier(compoundCompiled, field, false);
|
||||
}
|
||||
}
|
||||
|
||||
void LongNameHandler::processQuantity(DecimalQuantity &quantity, MicroProps µs,
|
||||
UErrorCode &status) const {
|
||||
parent->processQuantity(quantity, micros, status);
|
||||
|
@ -21,8 +21,9 @@ class LongNameHandler : public MicroPropsGenerator, public UMemory {
|
||||
const MicroPropsGenerator *parent, UErrorCode &status);
|
||||
|
||||
static LongNameHandler
|
||||
forMeasureUnit(const Locale &loc, const MeasureUnit &unit, const UNumberUnitWidth &width,
|
||||
const PluralRules *rules, const MicroPropsGenerator *parent, UErrorCode &status);
|
||||
forMeasureUnit(const Locale &loc, const MeasureUnit &unit, const MeasureUnit &perUnit,
|
||||
const UNumberUnitWidth &width, const PluralRules *rules,
|
||||
const MicroPropsGenerator *parent, UErrorCode &status);
|
||||
|
||||
void
|
||||
processQuantity(DecimalQuantity &quantity, MicroProps µs, UErrorCode &status) const U_OVERRIDE;
|
||||
@ -35,8 +36,15 @@ class LongNameHandler : public MicroPropsGenerator, public UMemory {
|
||||
LongNameHandler(const PluralRules *rules, const MicroPropsGenerator *parent)
|
||||
: rules(rules), parent(parent) {}
|
||||
|
||||
static LongNameHandler
|
||||
forCompoundUnit(const Locale &loc, const MeasureUnit &unit, const MeasureUnit &perUnit,
|
||||
const UNumberUnitWidth &width, const PluralRules *rules,
|
||||
const MicroPropsGenerator *parent, UErrorCode &status);
|
||||
|
||||
static void simpleFormatsToModifiers(const UnicodeString *simpleFormats, Field field,
|
||||
SimpleModifier *output, UErrorCode &status);
|
||||
static void multiSimpleFormatsToModifiers(const UnicodeString *leadFormats, UnicodeString trailFormat,
|
||||
Field field, SimpleModifier *output, UErrorCode &status);
|
||||
};
|
||||
|
||||
} // namespace impl
|
||||
|
@ -251,28 +251,39 @@ void Rounder::setLocaleData(const CurrencyUnit ¤cy, UErrorCode &status) {
|
||||
int32_t
|
||||
Rounder::chooseMultiplierAndApply(impl::DecimalQuantity &input, const impl::MultiplierProducer &producer,
|
||||
UErrorCode &status) {
|
||||
// TODO: Make a better and more efficient implementation.
|
||||
// TODO: Avoid the object creation here.
|
||||
DecimalQuantity copy(input);
|
||||
|
||||
// Do not call this method with zero.
|
||||
U_ASSERT(!input.isZero());
|
||||
int32_t magnitude = input.getMagnitude();
|
||||
int32_t multiplier = producer.getMultiplier(magnitude);
|
||||
|
||||
// Perform the first attempt at rounding.
|
||||
int magnitude = input.getMagnitude();
|
||||
int multiplier = producer.getMultiplier(magnitude);
|
||||
input.adjustMagnitude(multiplier);
|
||||
apply(input, status);
|
||||
|
||||
// If the number turned to zero when rounding, do not re-attempt the rounding.
|
||||
if (!input.isZero() && input.getMagnitude() == magnitude + multiplier + 1) {
|
||||
magnitude += 1;
|
||||
input = copy;
|
||||
multiplier = producer.getMultiplier(magnitude);
|
||||
input.adjustMagnitude(multiplier);
|
||||
U_ASSERT(input.getMagnitude() == magnitude + multiplier - 1);
|
||||
apply(input, status);
|
||||
U_ASSERT(input.getMagnitude() == magnitude + multiplier);
|
||||
// If the number rounded to zero, exit.
|
||||
if (input.isZero() || U_FAILURE(status)) {
|
||||
return multiplier;
|
||||
}
|
||||
|
||||
return multiplier;
|
||||
// If the new magnitude after rounding is the same as it was before rounding, then we are done.
|
||||
// This case applies to most numbers.
|
||||
if (input.getMagnitude() == magnitude + multiplier) {
|
||||
return multiplier;
|
||||
}
|
||||
|
||||
// If the above case DIDN'T apply, then we have a case like 99.9 -> 100 or 999.9 -> 1000:
|
||||
// The number rounded up to the next magnitude. Check if the multiplier changes; if it doesn't,
|
||||
// we do not need to make any more adjustments.
|
||||
int _multiplier = producer.getMultiplier(magnitude + 1);
|
||||
if (multiplier == _multiplier) {
|
||||
return multiplier;
|
||||
}
|
||||
|
||||
// We have a case like 999.9 -> 1000, where the correct output is "1K", not "1000".
|
||||
// Fix the magnitude and re-apply the rounding strategy.
|
||||
input.adjustMagnitude(_multiplier - multiplier);
|
||||
apply(input, status);
|
||||
return _multiplier;
|
||||
}
|
||||
|
||||
/** This is the method that contains the actual rounding logic. */
|
||||
@ -331,6 +342,7 @@ void Rounder::apply(impl::DecimalQuantity &value, UErrorCode& status) const {
|
||||
case RND_CURRENCY:
|
||||
// Call .withCurrency() before .apply()!
|
||||
U_ASSERT(false);
|
||||
break;
|
||||
|
||||
case RND_PASS_THROUGH:
|
||||
break;
|
||||
|
@ -230,10 +230,21 @@ class U_I18N_API MicroPropsGenerator {
|
||||
virtual void processQuantity(DecimalQuantity& quantity, MicroProps& micros, UErrorCode& status) const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* An interface used by compact notation and scientific notation to choose a multiplier while rounding.
|
||||
*/
|
||||
class MultiplierProducer {
|
||||
public:
|
||||
virtual ~MultiplierProducer() = default;
|
||||
|
||||
/**
|
||||
* Maps a magnitude to a multiplier in powers of ten. For example, in compact notation in English, a magnitude of 5
|
||||
* (e.g., 100,000) should return a multiplier of -3, since the number is displayed in thousands.
|
||||
*
|
||||
* @param magnitude
|
||||
* The power of ten of the input number.
|
||||
* @return The shift in powers of ten.
|
||||
*/
|
||||
virtual int32_t getMultiplier(int32_t magnitude) const = 0;
|
||||
};
|
||||
|
||||
|
@ -196,8 +196,8 @@ class U_I18N_API MeasureUnit: public UObject {
|
||||
* ICU use only.
|
||||
* @internal
|
||||
*/
|
||||
static MeasureUnit *resolveUnitPerUnit(
|
||||
const MeasureUnit &unit, const MeasureUnit &perUnit);
|
||||
static MeasureUnit resolveUnitPerUnit(
|
||||
const MeasureUnit &unit, const MeasureUnit &perUnit, bool* isResolved);
|
||||
#endif /* U_HIDE_INTERNAL_API */
|
||||
|
||||
// All code between the "Start generated createXXX methods" comment and
|
||||
|
@ -10,17 +10,17 @@
|
||||
#ifndef __NOUNIT_H__
|
||||
#define __NOUNIT_H__
|
||||
|
||||
#include "unicode/utypes.h"
|
||||
|
||||
#if !UCONFIG_NO_FORMATTING
|
||||
|
||||
#include "unicode/measunit.h"
|
||||
|
||||
/**
|
||||
* \file
|
||||
* \brief C++ API: units for percent and permille
|
||||
*/
|
||||
|
||||
|
||||
#include "unicode/measunit.h"
|
||||
|
||||
#if !UCONFIG_NO_FORMATTING
|
||||
|
||||
U_NAMESPACE_BEGIN
|
||||
|
||||
#ifndef U_HIDE_DRAFT_API
|
||||
|
@ -836,6 +836,20 @@ class U_I18N_API Rounder : public UMemory {
|
||||
/** Version of {@link #apply} that obeys minInt constraints. Used for scientific notation compatibility mode. */
|
||||
void apply(impl::DecimalQuantity &value, int32_t minInt, UErrorCode status);
|
||||
|
||||
/**
|
||||
* Rounding endpoint used by Engineering and Compact notation. Chooses the most appropriate multiplier (magnitude
|
||||
* adjustment), applies the adjustment, rounds, and returns the chosen multiplier.
|
||||
*
|
||||
* <p>
|
||||
* In most cases, this is simple. However, when rounding the number causes it to cross a multiplier boundary, we
|
||||
* need to re-do the rounding. For example, to display 999,999 in Engineering notation with 2 sigfigs, first you
|
||||
* guess the multiplier to be -3. However, then you end up getting 1000E3, which is not the correct output. You then
|
||||
* change your multiplier to be -6, and you get 1.0E6, which is correct.
|
||||
*
|
||||
* @param input The quantity to process.
|
||||
* @param producer Function to call to return a multiplier based on a magnitude.
|
||||
* @return The number of orders of magnitude the input was adjusted by this method.
|
||||
*/
|
||||
int32_t
|
||||
chooseMultiplierAndApply(impl::DecimalQuantity &input, const impl::MultiplierProducer &producer,
|
||||
UErrorCode &status);
|
||||
@ -1283,6 +1297,9 @@ struct U_I18N_API MacroProps : public UMemory {
|
||||
/** @internal */
|
||||
MeasureUnit unit; // = NoUnit::base();
|
||||
|
||||
/** @internal */
|
||||
MeasureUnit perUnit; // = NoUnit::base();
|
||||
|
||||
/** @internal */
|
||||
Rounder rounder; // = Rounder(); (bogus)
|
||||
|
||||
@ -1375,29 +1392,29 @@ class U_I18N_API NumberFormatterSettings {
|
||||
* <li>Percent: "12.3%"
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* All units will be properly localized with locale data, and all units are compatible with notation styles,
|
||||
* rounding strategies, and other number formatter settings.
|
||||
*
|
||||
* <p>
|
||||
* Pass this method any instance of {@link MeasureUnit}. For units of measure:
|
||||
*
|
||||
* <pre>
|
||||
* NumberFormatter.with().adoptUnit(MeasureUnit::createMeter(status))
|
||||
* NumberFormatter::with().adoptUnit(MeasureUnit::createMeter(status))
|
||||
* </pre>
|
||||
*
|
||||
* Currency:
|
||||
*
|
||||
* <pre>
|
||||
* NumberFormatter.with()::unit(CurrencyUnit(u"USD", status))
|
||||
* NumberFormatter::with().unit(CurrencyUnit(u"USD", status))
|
||||
* </pre>
|
||||
*
|
||||
* Percent:
|
||||
*
|
||||
* <pre>
|
||||
* NumberFormatter.with()::unit(NoUnit.percent())
|
||||
* NumberFormatter::with().unit(NoUnit.percent())
|
||||
* </pre>
|
||||
*
|
||||
* See {@link #perUnit} for information on how to format strings like "5 meters per second".
|
||||
*
|
||||
* The default is to render without units (equivalent to NoUnit.base()).
|
||||
*
|
||||
* @param unit
|
||||
@ -1406,6 +1423,7 @@ class U_I18N_API NumberFormatterSettings {
|
||||
* @see MeasureUnit
|
||||
* @see Currency
|
||||
* @see NoUnit
|
||||
* @see #perUnit
|
||||
* @draft ICU 60
|
||||
*/
|
||||
Derived unit(const icu::MeasureUnit &unit) const;
|
||||
@ -1415,7 +1433,7 @@ class U_I18N_API NumberFormatterSettings {
|
||||
* methods, which return pointers that need ownership.
|
||||
*
|
||||
* @param unit
|
||||
* The unit to render.
|
||||
* The unit to render.
|
||||
* @return The fluent chain.
|
||||
* @see #unit
|
||||
* @see MeasureUnit
|
||||
@ -1423,6 +1441,43 @@ class U_I18N_API NumberFormatterSettings {
|
||||
*/
|
||||
Derived adoptUnit(const icu::MeasureUnit *unit) const;
|
||||
|
||||
/**
|
||||
* Sets a unit to be used in the denominator. For example, to format "3 m/s", pass METER to the unit and SECOND to
|
||||
* the perUnit.
|
||||
*
|
||||
* Pass this method any instance of {@link MeasureUnit}. For example:
|
||||
*
|
||||
* <pre>
|
||||
* NumberFormatter::with()
|
||||
* .adoptUnit(MeasureUnit::createMeter(status))
|
||||
* .adoptPerUnit(MeasureUnit::createSecond(status))
|
||||
* </pre>
|
||||
*
|
||||
* The default is not to display any unit in the denominator.
|
||||
*
|
||||
* If a per-unit is specified without a primary unit via {@link #unit}, the behavior is undefined.
|
||||
*
|
||||
* @param perUnit
|
||||
* The unit to render in the denominator.
|
||||
* @return The fluent chain
|
||||
* @see #unit
|
||||
* @draft ICU 61
|
||||
*/
|
||||
Derived perUnit(const icu::MeasureUnit &perUnit) const;
|
||||
|
||||
/**
|
||||
* Like perUnit(), but takes ownership of a pointer. Convenient for use with the MeasureFormat factory
|
||||
* methods, which return pointers that need ownership.
|
||||
*
|
||||
* @param perUnit
|
||||
* The unit to render in the denominator.
|
||||
* @return The fluent chain.
|
||||
* @see #perUnit
|
||||
* @see MeasureUnit
|
||||
* @draft ICU 61
|
||||
*/
|
||||
Derived adoptPerUnit(const icu::MeasureUnit *perUnit) const;
|
||||
|
||||
/**
|
||||
* Specifies the rounding strategy to use when formatting numbers.
|
||||
*
|
||||
|
@ -45,6 +45,7 @@ class NumberFormatterApiTest : public IntlTest {
|
||||
void notationScientific();
|
||||
void notationCompact();
|
||||
void unitMeasure();
|
||||
void unitCompoundMeasure();
|
||||
void unitCurrency();
|
||||
void unitPercent();
|
||||
void roundingFraction();
|
||||
@ -75,6 +76,11 @@ class NumberFormatterApiTest : public IntlTest {
|
||||
MeasureUnit DAY;
|
||||
MeasureUnit SQUARE_METER;
|
||||
MeasureUnit FAHRENHEIT;
|
||||
MeasureUnit SECOND;
|
||||
MeasureUnit POUND;
|
||||
MeasureUnit SQUARE_MILE;
|
||||
MeasureUnit JOULE;
|
||||
MeasureUnit FURLONG;
|
||||
|
||||
NumberingSystem MATHSANB;
|
||||
NumberingSystem LATN;
|
||||
|
@ -26,29 +26,25 @@ NumberFormatterApiTest::NumberFormatterApiTest(UErrorCode &status)
|
||||
SWISS_SYMBOLS(Locale("de-CH"), status),
|
||||
MYANMAR_SYMBOLS(Locale("my"), status) {
|
||||
|
||||
MeasureUnit *unit = MeasureUnit::createMeter(status);
|
||||
// Check for error on the first MeasureUnit in case there is no data
|
||||
LocalPointer<MeasureUnit> unit(MeasureUnit::createMeter(status));
|
||||
if (U_FAILURE(status)) {
|
||||
dataerrln("%s %d status = %s", __FILE__, __LINE__, u_errorName(status));
|
||||
return;
|
||||
}
|
||||
METER = *unit;
|
||||
delete unit;
|
||||
unit = MeasureUnit::createDay(status);
|
||||
DAY = *unit;
|
||||
delete unit;
|
||||
unit = MeasureUnit::createSquareMeter(status);
|
||||
SQUARE_METER = *unit;
|
||||
delete unit;
|
||||
unit = MeasureUnit::createFahrenheit(status);
|
||||
FAHRENHEIT = *unit;
|
||||
delete unit;
|
||||
|
||||
NumberingSystem *ns = NumberingSystem::createInstanceByName("mathsanb", status);
|
||||
MATHSANB = *ns;
|
||||
delete ns;
|
||||
ns = NumberingSystem::createInstanceByName("latn", status);
|
||||
LATN = *ns;
|
||||
delete ns;
|
||||
DAY = *LocalPointer<MeasureUnit>(MeasureUnit::createDay(status));
|
||||
SQUARE_METER = *LocalPointer<MeasureUnit>(MeasureUnit::createSquareMeter(status));
|
||||
FAHRENHEIT = *LocalPointer<MeasureUnit>(MeasureUnit::createFahrenheit(status));
|
||||
SECOND = *LocalPointer<MeasureUnit>(MeasureUnit::createSecond(status));
|
||||
POUND = *LocalPointer<MeasureUnit>(MeasureUnit::createPound(status));
|
||||
SQUARE_MILE = *LocalPointer<MeasureUnit>(MeasureUnit::createSquareMile(status));
|
||||
JOULE = *LocalPointer<MeasureUnit>(MeasureUnit::createJoule(status));
|
||||
FURLONG = *LocalPointer<MeasureUnit>(MeasureUnit::createFurlong(status));
|
||||
|
||||
MATHSANB = *LocalPointer<NumberingSystem>(NumberingSystem::createInstanceByName("mathsanb", status));
|
||||
LATN = *LocalPointer<NumberingSystem>(NumberingSystem::createInstanceByName("latn", status));
|
||||
}
|
||||
|
||||
void NumberFormatterApiTest::runIndexedTest(int32_t index, UBool exec, const char *&name, char *) {
|
||||
@ -60,6 +56,7 @@ void NumberFormatterApiTest::runIndexedTest(int32_t index, UBool exec, const cha
|
||||
TESTCASE_AUTO(notationScientific);
|
||||
TESTCASE_AUTO(notationCompact);
|
||||
TESTCASE_AUTO(unitMeasure);
|
||||
TESTCASE_AUTO(unitCompoundMeasure);
|
||||
TESTCASE_AUTO(unitCurrency);
|
||||
TESTCASE_AUTO(unitPercent);
|
||||
TESTCASE_AUTO(roundingFraction);
|
||||
@ -355,8 +352,8 @@ void NumberFormatterApiTest::notationCompact() {
|
||||
|
||||
void NumberFormatterApiTest::unitMeasure() {
|
||||
assertFormatDescending(
|
||||
u"Meters Short",
|
||||
NumberFormatter::with().adoptUnit(new MeasureUnit(METER)),
|
||||
u"Meters Short and unit() method",
|
||||
NumberFormatter::with().unit(METER),
|
||||
Locale::getEnglish(),
|
||||
u"87,650 m",
|
||||
u"8,765 m",
|
||||
@ -369,7 +366,7 @@ void NumberFormatterApiTest::unitMeasure() {
|
||||
u"0 m");
|
||||
|
||||
assertFormatDescending(
|
||||
u"Meters Long",
|
||||
u"Meters Long and adoptUnit() method",
|
||||
NumberFormatter::with().adoptUnit(new MeasureUnit(METER))
|
||||
.unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
|
||||
Locale::getEnglish(),
|
||||
@ -386,7 +383,7 @@ void NumberFormatterApiTest::unitMeasure() {
|
||||
assertFormatDescending(
|
||||
u"Compact Meters Long",
|
||||
NumberFormatter::with().notation(Notation::compactLong())
|
||||
.adoptUnit(new MeasureUnit(METER))
|
||||
.unit(METER)
|
||||
.unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
|
||||
Locale::getEnglish(),
|
||||
u"88 thousand meters",
|
||||
@ -410,14 +407,14 @@ void NumberFormatterApiTest::unitMeasure() {
|
||||
// TODO: Implement Measure in C++
|
||||
// assertFormatSingleMeasure(
|
||||
// u"Measure format method takes precedence over fluent chain",
|
||||
// NumberFormatter::with().adoptUnit(new MeasureUnit(METER)),
|
||||
// NumberFormatter::with().unit(METER),
|
||||
// Locale::getEnglish(),
|
||||
// new Measure(5.43, USD),
|
||||
// u"$5.43");
|
||||
|
||||
assertFormatSingle(
|
||||
u"Meters with Negative Sign",
|
||||
NumberFormatter::with().adoptUnit(new MeasureUnit(METER)),
|
||||
NumberFormatter::with().unit(METER),
|
||||
Locale::getEnglish(),
|
||||
-9876543.21,
|
||||
u"-9,876,543.21 m");
|
||||
@ -425,7 +422,7 @@ void NumberFormatterApiTest::unitMeasure() {
|
||||
// The locale string "सान" appears only in brx.txt:
|
||||
assertFormatSingle(
|
||||
u"Interesting Data Fallback 1",
|
||||
NumberFormatter::with().adoptUnit(new MeasureUnit(DAY))
|
||||
NumberFormatter::with().unit(DAY)
|
||||
.unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
|
||||
Locale::createFromName("brx"),
|
||||
5.43,
|
||||
@ -434,7 +431,7 @@ void NumberFormatterApiTest::unitMeasure() {
|
||||
// Requires following the alias from unitsNarrow to unitsShort:
|
||||
assertFormatSingle(
|
||||
u"Interesting Data Fallback 2",
|
||||
NumberFormatter::with().adoptUnit(new MeasureUnit(DAY))
|
||||
NumberFormatter::with().unit(DAY)
|
||||
.unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW),
|
||||
Locale::createFromName("brx"),
|
||||
5.43,
|
||||
@ -444,7 +441,7 @@ void NumberFormatterApiTest::unitMeasure() {
|
||||
// requiring fallback to the root.
|
||||
assertFormatSingle(
|
||||
u"Interesting Data Fallback 3",
|
||||
NumberFormatter::with().adoptUnit(new MeasureUnit(SQUARE_METER))
|
||||
NumberFormatter::with().unit(SQUARE_METER)
|
||||
.unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW),
|
||||
Locale::createFromName("en-GB"),
|
||||
5.43,
|
||||
@ -454,7 +451,7 @@ void NumberFormatterApiTest::unitMeasure() {
|
||||
// NOTE: This example is in the documentation.
|
||||
assertFormatSingle(
|
||||
u"Difference between Narrow and Short (Narrow Version)",
|
||||
NumberFormatter::with().adoptUnit(new MeasureUnit(FAHRENHEIT))
|
||||
NumberFormatter::with().unit(FAHRENHEIT)
|
||||
.unitWidth(UNUM_UNIT_WIDTH_NARROW),
|
||||
Locale("es-US"),
|
||||
5.43,
|
||||
@ -462,13 +459,57 @@ void NumberFormatterApiTest::unitMeasure() {
|
||||
|
||||
assertFormatSingle(
|
||||
u"Difference between Narrow and Short (Short Version)",
|
||||
NumberFormatter::with().adoptUnit(new MeasureUnit(FAHRENHEIT))
|
||||
NumberFormatter::with().unit(FAHRENHEIT)
|
||||
.unitWidth(UNUM_UNIT_WIDTH_SHORT),
|
||||
Locale("es-US"),
|
||||
5.43,
|
||||
u"5.43 °F");
|
||||
}
|
||||
|
||||
void NumberFormatterApiTest::unitCompoundMeasure() {
|
||||
assertFormatDescending(
|
||||
u"Meters Per Second Short (unit that simplifies) and perUnit method",
|
||||
NumberFormatter::with().unit(METER).perUnit(SECOND),
|
||||
Locale::getEnglish(),
|
||||
u"87,650 m/s",
|
||||
u"8,765 m/s",
|
||||
u"876.5 m/s",
|
||||
u"87.65 m/s",
|
||||
u"8.765 m/s",
|
||||
u"0.8765 m/s",
|
||||
u"0.08765 m/s",
|
||||
u"0.008765 m/s",
|
||||
u"0 m/s");
|
||||
|
||||
assertFormatDescending(
|
||||
u"Pounds Per Square Mile Short (secondary unit has per-format) and adoptPerUnit method",
|
||||
NumberFormatter::with().unit(POUND).adoptPerUnit(new MeasureUnit(SQUARE_MILE)),
|
||||
Locale::getEnglish(),
|
||||
u"87,650 lb/mi²",
|
||||
u"8,765 lb/mi²",
|
||||
u"876.5 lb/mi²",
|
||||
u"87.65 lb/mi²",
|
||||
u"8.765 lb/mi²",
|
||||
u"0.8765 lb/mi²",
|
||||
u"0.08765 lb/mi²",
|
||||
u"0.008765 lb/mi²",
|
||||
u"0 lb/mi²");
|
||||
|
||||
assertFormatDescending(
|
||||
u"Joules Per Furlong Short (unit with no simplifications or special patterns)",
|
||||
NumberFormatter::with().unit(JOULE).perUnit(FURLONG),
|
||||
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");
|
||||
}
|
||||
|
||||
void NumberFormatterApiTest::unitCurrency() {
|
||||
assertFormatDescending(
|
||||
u"Currency",
|
||||
|
@ -63,6 +63,7 @@ void UnicodeTest::runIndexedTest( int32_t index, UBool exec, const char* &name,
|
||||
TESTCASE_AUTO(TestBidiPairedBracketType);
|
||||
TESTCASE_AUTO(TestEmojiProperties);
|
||||
TESTCASE_AUTO(TestDefaultScriptExtensions);
|
||||
TESTCASE_AUTO(TestInvalidCodePointFolding);
|
||||
TESTCASE_AUTO_END;
|
||||
}
|
||||
|
||||
@ -546,3 +547,21 @@ void UnicodeTest::TestDefaultScriptExtensions() {
|
||||
uscript_getScriptExtensions(0x3012, scx, UPRV_LENGTHOF(scx), errorCode));
|
||||
assertEquals("U+3012 num scx[0]", USCRIPT_COMMON, scx[0]);
|
||||
}
|
||||
|
||||
void UnicodeTest::TestInvalidCodePointFolding(void) {
|
||||
// Test behavior when an invalid code point is passed to u_foldCase
|
||||
static const UChar32 invalidCodePoints[] = {
|
||||
0xD800, // lead surrogate
|
||||
0xDFFF, // trail surrogate
|
||||
0xFDD0, // noncharacter
|
||||
0xFFFF, // noncharacter
|
||||
0x110000, // out of range
|
||||
-1 // negative
|
||||
};
|
||||
for (auto cp : invalidCodePoints) {
|
||||
assertEquals("Invalid code points should be echoed back",
|
||||
cp, u_foldCase(cp, U_FOLD_CASE_DEFAULT));
|
||||
assertEquals("Invalid code points should be echoed back",
|
||||
cp, u_foldCase(cp, U_FOLD_CASE_EXCLUDE_SPECIAL_I));
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ public:
|
||||
void TestBidiPairedBracketType();
|
||||
void TestEmojiProperties();
|
||||
void TestDefaultScriptExtensions();
|
||||
void TestInvalidCodePointFolding();
|
||||
|
||||
private:
|
||||
|
||||
|
@ -3,32 +3,32 @@
|
||||
package com.ibm.icu.impl.number;
|
||||
|
||||
public interface AffixPatternProvider {
|
||||
public static final class Flags {
|
||||
public static final int PLURAL_MASK = 0xff;
|
||||
public static final int PREFIX = 0x100;
|
||||
public static final int NEGATIVE_SUBPATTERN = 0x200;
|
||||
public static final int PADDING = 0x400;
|
||||
}
|
||||
public static final class Flags {
|
||||
public static final int PLURAL_MASK = 0xff;
|
||||
public static final int PREFIX = 0x100;
|
||||
public static final int NEGATIVE_SUBPATTERN = 0x200;
|
||||
public static final int PADDING = 0x400;
|
||||
}
|
||||
|
||||
// Convenience compound flags
|
||||
public static final int FLAG_POS_PREFIX = Flags.PREFIX;
|
||||
public static final int FLAG_POS_SUFFIX = 0;
|
||||
public static final int FLAG_NEG_PREFIX = Flags.PREFIX | Flags.NEGATIVE_SUBPATTERN;
|
||||
public static final int FLAG_NEG_SUFFIX = Flags.NEGATIVE_SUBPATTERN;
|
||||
// Convenience compound flags
|
||||
public static final int FLAG_POS_PREFIX = Flags.PREFIX;
|
||||
public static final int FLAG_POS_SUFFIX = 0;
|
||||
public static final int FLAG_NEG_PREFIX = Flags.PREFIX | Flags.NEGATIVE_SUBPATTERN;
|
||||
public static final int FLAG_NEG_SUFFIX = Flags.NEGATIVE_SUBPATTERN;
|
||||
|
||||
public char charAt(int flags, int i);
|
||||
public char charAt(int flags, int i);
|
||||
|
||||
public int length(int flags);
|
||||
public int length(int flags);
|
||||
|
||||
public String getString(int flags);
|
||||
public String getString(int flags);
|
||||
|
||||
public boolean hasCurrencySign();
|
||||
public boolean hasCurrencySign();
|
||||
|
||||
public boolean positiveHasPlusSign();
|
||||
public boolean positiveHasPlusSign();
|
||||
|
||||
public boolean hasNegativeSubpattern();
|
||||
public boolean hasNegativeSubpattern();
|
||||
|
||||
public boolean negativeHasMinusSign();
|
||||
public boolean negativeHasMinusSign();
|
||||
|
||||
public boolean containsSymbolType(int type);
|
||||
public boolean containsSymbolType(int type);
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -39,10 +39,15 @@ public class CompactData implements MultiplierProducer {
|
||||
isEmpty = true;
|
||||
}
|
||||
|
||||
public void populate(ULocale locale, String nsName, CompactStyle compactStyle, CompactType compactType) {
|
||||
public void populate(
|
||||
ULocale locale,
|
||||
String nsName,
|
||||
CompactStyle compactStyle,
|
||||
CompactType compactType) {
|
||||
assert isEmpty;
|
||||
CompactDataSink sink = new CompactDataSink(this);
|
||||
ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, locale);
|
||||
ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle
|
||||
.getBundleInstance(ICUData.ICU_BASE_NAME, locale);
|
||||
|
||||
boolean nsIsLatn = nsName.equals("latn");
|
||||
boolean compactIsShort = compactStyle == CompactStyle.SHORT;
|
||||
@ -71,7 +76,11 @@ public class CompactData implements MultiplierProducer {
|
||||
}
|
||||
|
||||
/** Produces a string like "NumberElements/latn/patternsShort/decimalFormat". */
|
||||
private static void getResourceBundleKey(String nsName, CompactStyle compactStyle, CompactType compactType, StringBuilder sb) {
|
||||
private static void getResourceBundleKey(
|
||||
String nsName,
|
||||
CompactStyle compactStyle,
|
||||
CompactType compactType,
|
||||
StringBuilder sb) {
|
||||
sb.setLength(0);
|
||||
sb.append("NumberElements/");
|
||||
sb.append(nsName);
|
||||
@ -82,7 +91,8 @@ public class CompactData implements MultiplierProducer {
|
||||
/** Java-only method used by CLDR tooling. */
|
||||
public void populate(Map<String, Map<String, String>> powersToPluralsToPatterns) {
|
||||
assert isEmpty;
|
||||
for (Map.Entry<String, Map<String, String>> magnitudeEntry : powersToPluralsToPatterns.entrySet()) {
|
||||
for (Map.Entry<String, Map<String, String>> magnitudeEntry : powersToPluralsToPatterns
|
||||
.entrySet()) {
|
||||
byte magnitude = (byte) (magnitudeEntry.getKey().length() - 1);
|
||||
for (Map.Entry<String, String> pluralEntry : magnitudeEntry.getValue().entrySet()) {
|
||||
StandardPlural plural = StandardPlural.fromString(pluralEntry.getKey().toString());
|
||||
@ -155,7 +165,7 @@ public class CompactData implements MultiplierProducer {
|
||||
for (int i3 = 0; powersOfTenTable.getKeyAndValue(i3, key, value); ++i3) {
|
||||
|
||||
// Assumes that the keys are always of the form "10000" where the magnitude is the
|
||||
// length of the key minus one. We expect magnitudes to be less than MAX_DIGITS.
|
||||
// length of the key minus one. We expect magnitudes to be less than MAX_DIGITS.
|
||||
byte magnitude = (byte) (key.length() - 1);
|
||||
byte multiplier = data.multipliers[magnitude];
|
||||
assert magnitude < COMPACT_MAX_DIGITS;
|
||||
|
@ -21,7 +21,8 @@ public class ConstantAffixModifier implements Modifier {
|
||||
* Constructs an instance with the given strings.
|
||||
*
|
||||
* <p>
|
||||
* The arguments need to be Strings, not CharSequences, because Strings are immutable but CharSequences are not.
|
||||
* The arguments need to be Strings, not CharSequences, because Strings are immutable but
|
||||
* CharSequences are not.
|
||||
*
|
||||
* @param prefix
|
||||
* The prefix string.
|
||||
|
@ -5,8 +5,9 @@ package com.ibm.icu.impl.number;
|
||||
import com.ibm.icu.text.NumberFormat.Field;
|
||||
|
||||
/**
|
||||
* An implementation of {@link Modifier} that allows for multiple types of fields in the same modifier. Constructed
|
||||
* based on the contents of two {@link NumberStringBuilder} instances (one for the prefix, one for the suffix).
|
||||
* An implementation of {@link Modifier} that allows for multiple types of fields in the same modifier.
|
||||
* Constructed based on the contents of two {@link NumberStringBuilder} instances (one for the prefix,
|
||||
* one for the suffix).
|
||||
*/
|
||||
public class ConstantMultiFieldModifier implements Modifier {
|
||||
|
||||
@ -18,7 +19,10 @@ public class ConstantMultiFieldModifier implements Modifier {
|
||||
protected final Field[] suffixFields;
|
||||
private final boolean strong;
|
||||
|
||||
public ConstantMultiFieldModifier(NumberStringBuilder prefix, NumberStringBuilder suffix, boolean strong) {
|
||||
public ConstantMultiFieldModifier(
|
||||
NumberStringBuilder prefix,
|
||||
NumberStringBuilder suffix,
|
||||
boolean strong) {
|
||||
prefixChars = prefix.toCharArray();
|
||||
suffixChars = suffix.toCharArray();
|
||||
prefixFields = prefix.toFieldArray();
|
||||
@ -55,7 +59,8 @@ public class ConstantMultiFieldModifier implements Modifier {
|
||||
NumberStringBuilder temp = new NumberStringBuilder();
|
||||
apply(temp, 0, 0);
|
||||
int prefixLength = getPrefixLength();
|
||||
return String.format("<ConstantMultiFieldModifier prefix:'%s' suffix:'%s'>", temp.subSequence(0, prefixLength),
|
||||
return String.format("<ConstantMultiFieldModifier prefix:'%s' suffix:'%s'>",
|
||||
temp.subSequence(0, prefixLength),
|
||||
temp.subSequence(prefixLength, temp.length()));
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,10 @@ public class CurrencySpacingEnabledModifier extends ConstantMultiFieldModifier {
|
||||
private final String beforeSuffixInsert;
|
||||
|
||||
/** Safe code path */
|
||||
public CurrencySpacingEnabledModifier(NumberStringBuilder prefix, NumberStringBuilder suffix, boolean strong,
|
||||
public CurrencySpacingEnabledModifier(
|
||||
NumberStringBuilder prefix,
|
||||
NumberStringBuilder suffix,
|
||||
boolean strong,
|
||||
DecimalFormatSymbols symbols) {
|
||||
super(prefix, suffix, strong);
|
||||
|
||||
@ -70,12 +73,14 @@ public class CurrencySpacingEnabledModifier extends ConstantMultiFieldModifier {
|
||||
public int apply(NumberStringBuilder output, int leftIndex, int rightIndex) {
|
||||
// Currency spacing logic
|
||||
int length = 0;
|
||||
if (rightIndex - leftIndex > 0 && afterPrefixUnicodeSet != null
|
||||
if (rightIndex - leftIndex > 0
|
||||
&& afterPrefixUnicodeSet != null
|
||||
&& afterPrefixUnicodeSet.contains(output.codePointAt(leftIndex))) {
|
||||
// TODO: Should we use the CURRENCY field here?
|
||||
length += output.insert(leftIndex, afterPrefixInsert, null);
|
||||
}
|
||||
if (rightIndex - leftIndex > 0 && beforeSuffixUnicodeSet != null
|
||||
if (rightIndex - leftIndex > 0
|
||||
&& beforeSuffixUnicodeSet != null
|
||||
&& beforeSuffixUnicodeSet.contains(output.codePointBefore(rightIndex))) {
|
||||
// TODO: Should we use the CURRENCY field here?
|
||||
length += output.insert(rightIndex + length, beforeSuffixInsert, null);
|
||||
@ -87,8 +92,13 @@ public class CurrencySpacingEnabledModifier extends ConstantMultiFieldModifier {
|
||||
}
|
||||
|
||||
/** Unsafe code path */
|
||||
public static int applyCurrencySpacing(NumberStringBuilder output, int prefixStart, int prefixLen, int suffixStart,
|
||||
int suffixLen, DecimalFormatSymbols symbols) {
|
||||
public static int applyCurrencySpacing(
|
||||
NumberStringBuilder output,
|
||||
int prefixStart,
|
||||
int prefixLen,
|
||||
int suffixStart,
|
||||
int suffixLen,
|
||||
DecimalFormatSymbols symbols) {
|
||||
int length = 0;
|
||||
boolean hasPrefix = (prefixLen > 0);
|
||||
boolean hasSuffix = (suffixLen > 0);
|
||||
@ -103,12 +113,16 @@ public class CurrencySpacingEnabledModifier extends ConstantMultiFieldModifier {
|
||||
}
|
||||
|
||||
/** Unsafe code path */
|
||||
private static int applyCurrencySpacingAffix(NumberStringBuilder output, int index, byte affix,
|
||||
private static int applyCurrencySpacingAffix(
|
||||
NumberStringBuilder output,
|
||||
int index,
|
||||
byte affix,
|
||||
DecimalFormatSymbols symbols) {
|
||||
// NOTE: For prefix, output.fieldAt(index-1) gets the last field type in the prefix.
|
||||
// This works even if the last code point in the prefix is 2 code units because the
|
||||
// field value gets populated to both indices in the field array.
|
||||
NumberFormat.Field affixField = (affix == PREFIX) ? output.fieldAt(index - 1) : output.fieldAt(index);
|
||||
NumberFormat.Field affixField = (affix == PREFIX) ? output.fieldAt(index - 1)
|
||||
: output.fieldAt(index);
|
||||
if (affixField != NumberFormat.Field.CURRENCY) {
|
||||
return 0;
|
||||
}
|
||||
@ -135,8 +149,10 @@ public class CurrencySpacingEnabledModifier extends ConstantMultiFieldModifier {
|
||||
|
||||
private static UnicodeSet getUnicodeSet(DecimalFormatSymbols symbols, short position, byte affix) {
|
||||
String pattern = symbols
|
||||
.getPatternForCurrencySpacing(position == IN_CURRENCY ? DecimalFormatSymbols.CURRENCY_SPC_CURRENCY_MATCH
|
||||
: DecimalFormatSymbols.CURRENCY_SPC_SURROUNDING_MATCH, affix == SUFFIX);
|
||||
.getPatternForCurrencySpacing(
|
||||
position == IN_CURRENCY ? DecimalFormatSymbols.CURRENCY_SPC_CURRENCY_MATCH
|
||||
: DecimalFormatSymbols.CURRENCY_SPC_SURROUNDING_MATCH,
|
||||
affix == SUFFIX);
|
||||
if (pattern.equals("[:digit:]")) {
|
||||
return UNISET_DIGIT;
|
||||
} else if (pattern.equals("[:^S:]")) {
|
||||
@ -147,6 +163,7 @@ public class CurrencySpacingEnabledModifier extends ConstantMultiFieldModifier {
|
||||
}
|
||||
|
||||
private static String getInsertString(DecimalFormatSymbols symbols, byte affix) {
|
||||
return symbols.getPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_INSERT, affix == SUFFIX);
|
||||
return symbols.getPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_INSERT,
|
||||
affix == SUFFIX);
|
||||
}
|
||||
}
|
||||
|
@ -7,70 +7,69 @@ import com.ibm.icu.util.Currency;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
|
||||
public class CustomSymbolCurrency extends Currency {
|
||||
private static final long serialVersionUID = 2497493016770137670L;
|
||||
// TODO: Serialization methods?
|
||||
private static final long serialVersionUID = 2497493016770137670L;
|
||||
// TODO: Serialization methods?
|
||||
|
||||
private String symbol1;
|
||||
private String symbol2;
|
||||
private String symbol1;
|
||||
private String symbol2;
|
||||
|
||||
public static Currency resolve(Currency currency, ULocale locale, DecimalFormatSymbols symbols) {
|
||||
if (currency == null) {
|
||||
currency = symbols.getCurrency();
|
||||
public static Currency resolve(Currency currency, ULocale locale, DecimalFormatSymbols symbols) {
|
||||
if (currency == null) {
|
||||
currency = symbols.getCurrency();
|
||||
}
|
||||
String currency1Sym = symbols.getCurrencySymbol();
|
||||
String currency2Sym = symbols.getInternationalCurrencySymbol();
|
||||
if (currency == null) {
|
||||
return new CustomSymbolCurrency("XXX", currency1Sym, currency2Sym);
|
||||
}
|
||||
if (!currency.equals(symbols.getCurrency())) {
|
||||
return currency;
|
||||
}
|
||||
String currency1 = currency.getName(symbols.getULocale(), Currency.SYMBOL_NAME, null);
|
||||
String currency2 = currency.getCurrencyCode();
|
||||
if (!currency1.equals(currency1Sym) || !currency2.equals(currency2Sym)) {
|
||||
return new CustomSymbolCurrency(currency2, currency1Sym, currency2Sym);
|
||||
}
|
||||
return currency;
|
||||
}
|
||||
String currency1Sym = symbols.getCurrencySymbol();
|
||||
String currency2Sym = symbols.getInternationalCurrencySymbol();
|
||||
if (currency == null) {
|
||||
return new CustomSymbolCurrency("XXX", currency1Sym, currency2Sym);
|
||||
|
||||
public CustomSymbolCurrency(String isoCode, String currency1Sym, String currency2Sym) {
|
||||
super(isoCode);
|
||||
this.symbol1 = currency1Sym;
|
||||
this.symbol2 = currency2Sym;
|
||||
}
|
||||
if (!currency.equals(symbols.getCurrency())) {
|
||||
return currency;
|
||||
|
||||
@Override
|
||||
public String getName(ULocale locale, int nameStyle, boolean[] isChoiceFormat) {
|
||||
if (nameStyle == SYMBOL_NAME) {
|
||||
return symbol1;
|
||||
}
|
||||
return super.getName(locale, nameStyle, isChoiceFormat);
|
||||
}
|
||||
String currency1 = currency.getName(symbols.getULocale(), Currency.SYMBOL_NAME, null);
|
||||
String currency2 = currency.getCurrencyCode();
|
||||
if (!currency1.equals(currency1Sym) || !currency2.equals(currency2Sym)) {
|
||||
return new CustomSymbolCurrency(currency2, currency1Sym, currency2Sym);
|
||||
|
||||
@Override
|
||||
public String getName(ULocale locale, int nameStyle, String pluralCount, boolean[] isChoiceFormat) {
|
||||
if (nameStyle == PLURAL_LONG_NAME && subType.equals("XXX")) {
|
||||
// Plural in absence of a currency should return the symbol
|
||||
return symbol1;
|
||||
}
|
||||
return super.getName(locale, nameStyle, pluralCount, isChoiceFormat);
|
||||
}
|
||||
return currency;
|
||||
}
|
||||
|
||||
public CustomSymbolCurrency(String isoCode, String currency1Sym, String currency2Sym) {
|
||||
super(isoCode);
|
||||
this.symbol1 = currency1Sym;
|
||||
this.symbol2 = currency2Sym;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(ULocale locale, int nameStyle, boolean[] isChoiceFormat) {
|
||||
if (nameStyle == SYMBOL_NAME) {
|
||||
return symbol1;
|
||||
@Override
|
||||
public String getCurrencyCode() {
|
||||
return symbol2;
|
||||
}
|
||||
return super.getName(locale, nameStyle, isChoiceFormat);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(
|
||||
ULocale locale, int nameStyle, String pluralCount, boolean[] isChoiceFormat) {
|
||||
if (nameStyle == PLURAL_LONG_NAME && subType.equals("XXX")) {
|
||||
// Plural in absence of a currency should return the symbol
|
||||
return symbol1;
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return super.hashCode() ^ symbol1.hashCode() ^ symbol2.hashCode();
|
||||
}
|
||||
return super.getName(locale, nameStyle, pluralCount, isChoiceFormat);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCurrencyCode() {
|
||||
return symbol2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return super.hashCode() ^ symbol1.hashCode() ^ symbol2.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
return super.equals(other)
|
||||
&& ((CustomSymbolCurrency)other).symbol1.equals(symbol1)
|
||||
&& ((CustomSymbolCurrency)other).symbol2.equals(symbol2);
|
||||
}
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
return super.equals(other)
|
||||
&& ((CustomSymbolCurrency) other).symbol1.equals(symbol1)
|
||||
&& ((CustomSymbolCurrency) other).symbol2.equals(symbol2);
|
||||
}
|
||||
}
|
||||
|
@ -105,8 +105,8 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
* Sets all properties to their defaults (unset).
|
||||
*
|
||||
* <p>
|
||||
* All integers default to -1 EXCEPT FOR MAGNITUDE MULTIPLIER which has a default of 0 (since negative numbers are
|
||||
* important).
|
||||
* All integers default to -1 EXCEPT FOR MAGNITUDE MULTIPLIER which has a default of 0 (since
|
||||
* negative numbers are important).
|
||||
*
|
||||
* <p>
|
||||
* All booleans default to false.
|
||||
@ -550,7 +550,8 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
readObjectImpl(ois);
|
||||
}
|
||||
|
||||
/* package-private */ void readObjectImpl(ObjectInputStream ois) throws IOException, ClassNotFoundException {
|
||||
/* package-private */ void readObjectImpl(ObjectInputStream ois)
|
||||
throws IOException, ClassNotFoundException {
|
||||
ois.defaultReadObject();
|
||||
|
||||
// Initialize to empty
|
||||
@ -596,8 +597,8 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies custom data to be used instead of CLDR data when constructing a CompactDecimalFormat. The argument
|
||||
* should be a map with the following structure:
|
||||
* Specifies custom data to be used instead of CLDR data when constructing a CompactDecimalFormat.
|
||||
* The argument should be a map with the following structure:
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
@ -619,15 +620,17 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
* A map with the above structure.
|
||||
* @return The property bag, for chaining.
|
||||
*/
|
||||
public DecimalFormatProperties setCompactCustomData(Map<String, Map<String, String>> compactCustomData) {
|
||||
public DecimalFormatProperties setCompactCustomData(
|
||||
Map<String, Map<String, String>> compactCustomData) {
|
||||
// TODO: compactCustomData is not immutable.
|
||||
this.compactCustomData = compactCustomData;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use compact decimal formatting with the specified {@link CompactStyle}. CompactStyle.SHORT produces output like
|
||||
* "10K" in locale <em>en-US</em>, whereas CompactStyle.LONG produces output like "10 thousand" in that locale.
|
||||
* Use compact decimal formatting with the specified {@link CompactStyle}. CompactStyle.SHORT
|
||||
* produces output like "10K" in locale <em>en-US</em>, whereas CompactStyle.LONG produces output
|
||||
* like "10 thousand" in that locale.
|
||||
*
|
||||
* @param compactStyle
|
||||
* The style of prefixes/suffixes to append.
|
||||
@ -668,11 +671,12 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the specified {@link CurrencyUsage} instance, which provides default rounding rules for the currency in two
|
||||
* styles, CurrencyUsage.CASH and CurrencyUsage.STANDARD.
|
||||
* Use the specified {@link CurrencyUsage} instance, which provides default rounding rules for the
|
||||
* currency in two styles, CurrencyUsage.CASH and CurrencyUsage.STANDARD.
|
||||
*
|
||||
* <p>
|
||||
* The CurrencyUsage specified here will not be used unless there is a currency placeholder in the pattern.
|
||||
* The CurrencyUsage specified here will not be used unless there is a currency placeholder in the
|
||||
* pattern.
|
||||
*
|
||||
* @param currencyUsage
|
||||
* The currency usage. Defaults to CurrencyUsage.STANDARD.
|
||||
@ -684,9 +688,10 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* PARSING: Whether to require that the presence of decimal point matches the pattern. If a decimal point is not
|
||||
* present, but the pattern contained a decimal point, parse will not succeed: null will be returned from
|
||||
* <code>parse()</code>, and an error index will be set in the {@link ParsePosition}.
|
||||
* PARSING: Whether to require that the presence of decimal point matches the pattern. If a decimal
|
||||
* point is not present, but the pattern contained a decimal point, parse will not succeed: null will
|
||||
* be returned from <code>parse()</code>, and an error index will be set in the
|
||||
* {@link ParsePosition}.
|
||||
*
|
||||
* @param decimalPatternMatchRequired
|
||||
* true to set an error if decimal is not present
|
||||
@ -698,8 +703,9 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to always show the decimal point, even if the number doesn't require one. For example, if always
|
||||
* show decimal is true, the number 123 would be formatted as "123." in locale <em>en-US</em>.
|
||||
* Sets whether to always show the decimal point, even if the number doesn't require one. For
|
||||
* example, if always show decimal is true, the number 123 would be formatted as "123." in locale
|
||||
* <em>en-US</em>.
|
||||
*
|
||||
* @param alwaysShowDecimal
|
||||
* Whether to show the decimal point when it is optional.
|
||||
@ -711,8 +717,9 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to show the plus sign in the exponent part of numbers with a zero or positive exponent. For example,
|
||||
* the number "1200" with the pattern "0.0E0" would be formatted as "1.2E+3" instead of "1.2E3" in <em>en-US</em>.
|
||||
* Sets whether to show the plus sign in the exponent part of numbers with a zero or positive
|
||||
* exponent. For example, the number "1200" with the pattern "0.0E0" would be formatted as "1.2E+3"
|
||||
* instead of "1.2E3" in <em>en-US</em>.
|
||||
*
|
||||
* @param exponentSignAlwaysShown
|
||||
* Whether to show the plus sign in positive exponents.
|
||||
@ -724,13 +731,13 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum width of the string output by the formatting pipeline. For example, if padding is enabled and
|
||||
* paddingWidth is set to 6, formatting the number "3.14159" with the pattern "0.00" will result in "··3.14" if '·'
|
||||
* is your padding string.
|
||||
* Sets the minimum width of the string output by the formatting pipeline. For example, if padding is
|
||||
* enabled and paddingWidth is set to 6, formatting the number "3.14159" with the pattern "0.00" will
|
||||
* result in "··3.14" if '·' is your padding string.
|
||||
*
|
||||
* <p>
|
||||
* If the number is longer than your padding width, the number will display as if no padding width had been
|
||||
* specified, which may result in strings longer than the padding width.
|
||||
* If the number is longer than your padding width, the number will display as if no padding width
|
||||
* had been specified, which may result in strings longer than the padding width.
|
||||
*
|
||||
* <p>
|
||||
* Width is counted in UTF-16 code units.
|
||||
@ -747,9 +754,9 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of digits between grouping separators. For example, the <em>en-US</em> locale uses a grouping
|
||||
* size of 3, so the number 1234567 would be formatted as "1,234,567". For locales whose grouping sizes vary with
|
||||
* magnitude, see {@link #setSecondaryGroupingSize(int)}.
|
||||
* Sets the number of digits between grouping separators. For example, the <em>en-US</em> locale uses
|
||||
* a grouping size of 3, so the number 1234567 would be formatted as "1,234,567". For locales whose
|
||||
* grouping sizes vary with magnitude, see {@link #setSecondaryGroupingSize(int)}.
|
||||
*
|
||||
* @param groupingSize
|
||||
* The primary grouping size.
|
||||
@ -761,8 +768,8 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiply all numbers by this power of ten before formatting. Negative multipliers reduce the magnitude and make
|
||||
* numbers smaller (closer to zero).
|
||||
* Multiply all numbers by this power of ten before formatting. Negative multipliers reduce the
|
||||
* magnitude and make numbers smaller (closer to zero).
|
||||
*
|
||||
* @param magnitudeMultiplier
|
||||
* The number of powers of ten to scale.
|
||||
@ -775,8 +782,8 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link MathContext} to be used during math and rounding operations. A MathContext encapsulates a
|
||||
* RoundingMode and the number of significant digits in the output.
|
||||
* Sets the {@link MathContext} to be used during math and rounding operations. A MathContext
|
||||
* encapsulates a RoundingMode and the number of significant digits in the output.
|
||||
*
|
||||
* @param mathContext
|
||||
* The math context to use when rounding is required.
|
||||
@ -790,11 +797,12 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum number of digits to display after the decimal point. If the number has fewer than this number of
|
||||
* digits, the number will be rounded off using the rounding mode specified by
|
||||
* {@link #setRoundingMode(RoundingMode)}. The pattern "#00.0#", for example, corresponds to 2 maximum fraction
|
||||
* digits, and the number 456.789 would be formatted as "456.79" in locale <em>en-US</em> with the default rounding
|
||||
* mode. Note that the number 456.999 would be formatted as "457.0" given the same configurations.
|
||||
* Sets the maximum number of digits to display after the decimal point. If the number has fewer than
|
||||
* this number of digits, the number will be rounded off using the rounding mode specified by
|
||||
* {@link #setRoundingMode(RoundingMode)}. The pattern "#00.0#", for example, corresponds to 2
|
||||
* maximum fraction digits, and the number 456.789 would be formatted as "456.79" in locale
|
||||
* <em>en-US</em> with the default rounding mode. Note that the number 456.999 would be formatted as
|
||||
* "457.0" given the same configurations.
|
||||
*
|
||||
* @param maximumFractionDigits
|
||||
* The maximum number of fraction digits to output.
|
||||
@ -806,10 +814,11 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum number of digits to display before the decimal point. If the number has more than this number of
|
||||
* digits, the extra digits will be truncated. For example, if maximum integer digits is 2, and you attempt to
|
||||
* format the number 1970, you will get "70" in locale <em>en-US</em>. It is not possible to specify the maximum
|
||||
* integer digits using a pattern string, except in the special case of a scientific format pattern.
|
||||
* Sets the maximum number of digits to display before the decimal point. If the number has more than
|
||||
* this number of digits, the extra digits will be truncated. For example, if maximum integer digits
|
||||
* is 2, and you attempt to format the number 1970, you will get "70" in locale <em>en-US</em>. It is
|
||||
* not possible to specify the maximum integer digits using a pattern string, except in the special
|
||||
* case of a scientific format pattern.
|
||||
*
|
||||
* @param maximumIntegerDigits
|
||||
* The maximum number of integer digits to output.
|
||||
@ -821,20 +830,21 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum number of significant digits to display. The number of significant digits is equal to the number
|
||||
* of digits counted from the leftmost nonzero digit through the rightmost nonzero digit; for example, the number
|
||||
* "2010" has 3 significant digits. If the number has more significant digits than specified here, the extra
|
||||
* significant digits will be rounded off using the rounding mode specified by
|
||||
* {@link #setRoundingMode(RoundingMode)}. For example, if maximum significant digits is 3, the number 1234.56 will
|
||||
* be formatted as "1230" in locale <em>en-US</em> with the default rounding mode.
|
||||
* Sets the maximum number of significant digits to display. The number of significant digits is
|
||||
* equal to the number of digits counted from the leftmost nonzero digit through the rightmost
|
||||
* nonzero digit; for example, the number "2010" has 3 significant digits. If the number has more
|
||||
* significant digits than specified here, the extra significant digits will be rounded off using the
|
||||
* rounding mode specified by {@link #setRoundingMode(RoundingMode)}. For example, if maximum
|
||||
* significant digits is 3, the number 1234.56 will be formatted as "1230" in locale <em>en-US</em>
|
||||
* with the default rounding mode.
|
||||
*
|
||||
* <p>
|
||||
* If both maximum significant digits and maximum integer/fraction digits are set at the same time, the behavior is
|
||||
* undefined.
|
||||
* If both maximum significant digits and maximum integer/fraction digits are set at the same time,
|
||||
* the behavior is undefined.
|
||||
*
|
||||
* <p>
|
||||
* The number of significant digits can be specified in a pattern string using the '@' character. For example, the
|
||||
* pattern "@@#" corresponds to a minimum of 2 and a maximum of 3 significant digits.
|
||||
* The number of significant digits can be specified in a pattern string using the '@' character. For
|
||||
* example, the pattern "@@#" corresponds to a minimum of 2 and a maximum of 3 significant digits.
|
||||
*
|
||||
* @param maximumSignificantDigits
|
||||
* The maximum number of significant digits to display.
|
||||
@ -846,8 +856,9 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum number of digits to display in the exponent. For example, the number "1200" with the pattern
|
||||
* "0.0E00", which has 2 exponent digits, would be formatted as "1.2E03" in <em>en-US</em>.
|
||||
* Sets the minimum number of digits to display in the exponent. For example, the number "1200" with
|
||||
* the pattern "0.0E00", which has 2 exponent digits, would be formatted as "1.2E03" in
|
||||
* <em>en-US</em>.
|
||||
*
|
||||
* @param minimumExponentDigits
|
||||
* The minimum number of digits to display in the exponent field.
|
||||
@ -859,9 +870,10 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum number of digits to display after the decimal point. If the number has fewer than this number of
|
||||
* digits, the number will be padded with zeros. The pattern "#00.0#", for example, corresponds to 1 minimum
|
||||
* fraction digit, and the number 456 would be formatted as "456.0" in locale <em>en-US</em>.
|
||||
* Sets the minimum number of digits to display after the decimal point. If the number has fewer than
|
||||
* this number of digits, the number will be padded with zeros. The pattern "#00.0#", for example,
|
||||
* corresponds to 1 minimum fraction digit, and the number 456 would be formatted as "456.0" in
|
||||
* locale <em>en-US</em>.
|
||||
*
|
||||
* @param minimumFractionDigits
|
||||
* The minimum number of fraction digits to output.
|
||||
@ -873,10 +885,10 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum number of digits required to be beyond the first grouping separator in order to enable grouping.
|
||||
* For example, if the minimum grouping digits is 2, then 1234 would be formatted as "1234" but 12345 would be
|
||||
* formatted as "12,345" in <em>en-US</em>. Note that 1234567 would still be formatted as "1,234,567", not
|
||||
* "1234,567".
|
||||
* Sets the minimum number of digits required to be beyond the first grouping separator in order to
|
||||
* enable grouping. For example, if the minimum grouping digits is 2, then 1234 would be formatted as
|
||||
* "1234" but 12345 would be formatted as "12,345" in <em>en-US</em>. Note that 1234567 would still
|
||||
* be formatted as "1,234,567", not "1234,567".
|
||||
*
|
||||
* @param minimumGroupingDigits
|
||||
* How many digits must appear before a grouping separator before enabling grouping.
|
||||
@ -888,9 +900,10 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum number of digits to display before the decimal point. If the number has fewer than this number
|
||||
* of digits, the number will be padded with zeros. The pattern "#00.0#", for example, corresponds to 2 minimum
|
||||
* integer digits, and the number 5.3 would be formatted as "05.3" in locale <em>en-US</em>.
|
||||
* Sets the minimum number of digits to display before the decimal point. If the number has fewer
|
||||
* than this number of digits, the number will be padded with zeros. The pattern "#00.0#", for
|
||||
* example, corresponds to 2 minimum integer digits, and the number 5.3 would be formatted as "05.3"
|
||||
* in locale <em>en-US</em>.
|
||||
*
|
||||
* @param minimumIntegerDigits
|
||||
* The minimum number of integer digits to output.
|
||||
@ -902,20 +915,22 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum number of significant digits to display. If, after rounding to the number of significant digits
|
||||
* specified by {@link #setMaximumSignificantDigits}, the number of remaining significant digits is less than the
|
||||
* minimum, the number will be padded with zeros. For example, if minimum significant digits is 3, the number 5.8
|
||||
* will be formatted as "5.80" in locale <em>en-US</em>. Note that minimum significant digits is relevant only when
|
||||
* numbers have digits after the decimal point.
|
||||
* Sets the minimum number of significant digits to display. If, after rounding to the number of
|
||||
* significant digits specified by {@link #setMaximumSignificantDigits}, the number of remaining
|
||||
* significant digits is less than the minimum, the number will be padded with zeros. For example, if
|
||||
* minimum significant digits is 3, the number 5.8 will be formatted as "5.80" in locale
|
||||
* <em>en-US</em>. Note that minimum significant digits is relevant only when numbers have digits
|
||||
* after the decimal point.
|
||||
*
|
||||
* <p>
|
||||
* If both minimum significant digits and minimum integer/fraction digits are set at the same time, both values will
|
||||
* be respected, and the one that results in the greater number of padding zeros will be used. For example,
|
||||
* formatting the number 73 with 3 minimum significant digits and 2 minimum fraction digits will produce "73.00".
|
||||
* If both minimum significant digits and minimum integer/fraction digits are set at the same time,
|
||||
* both values will be respected, and the one that results in the greater number of padding zeros
|
||||
* will be used. For example, formatting the number 73 with 3 minimum significant digits and 2
|
||||
* minimum fraction digits will produce "73.00".
|
||||
*
|
||||
* <p>
|
||||
* The number of significant digits can be specified in a pattern string using the '@' character. For example, the
|
||||
* pattern "@@#" corresponds to a minimum of 2 and a maximum of 3 significant digits.
|
||||
* The number of significant digits can be specified in a pattern string using the '@' character. For
|
||||
* example, the pattern "@@#" corresponds to a minimum of 2 and a maximum of 3 significant digits.
|
||||
*
|
||||
* @param minimumSignificantDigits
|
||||
* The minimum number of significant digits to display.
|
||||
@ -940,9 +955,10 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the prefix to prepend to negative numbers. The prefix will be interpreted literally. For example, if you set
|
||||
* a negative prefix of <code>n</code>, then the number -123 will be formatted as "n123" in the locale
|
||||
* <em>en-US</em>. Note that if the negative prefix is left unset, the locale's minus sign is used.
|
||||
* Sets the prefix to prepend to negative numbers. The prefix will be interpreted literally. For
|
||||
* example, if you set a negative prefix of <code>n</code>, then the number -123 will be formatted as
|
||||
* "n123" in the locale <em>en-US</em>. Note that if the negative prefix is left unset, the locale's
|
||||
* minus sign is used.
|
||||
*
|
||||
* <p>
|
||||
* For more information on prefixes and suffixes, see {@link MutablePatternModifier}.
|
||||
@ -958,14 +974,15 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the prefix to prepend to negative numbers. Locale-specific symbols will be substituted into the string
|
||||
* according to Unicode Technical Standard #35 (LDML).
|
||||
* Sets the prefix to prepend to negative numbers. Locale-specific symbols will be substituted into
|
||||
* the string according to Unicode Technical Standard #35 (LDML).
|
||||
*
|
||||
* <p>
|
||||
* For more information on prefixes and suffixes, see {@link MutablePatternModifier}.
|
||||
*
|
||||
* @param negativePrefixPattern
|
||||
* The CharSequence to prepend to negative numbers after locale symbol substitutions take place.
|
||||
* The CharSequence to prepend to negative numbers after locale symbol substitutions take
|
||||
* place.
|
||||
* @return The property bag, for chaining.
|
||||
* @see #setNegativePrefix
|
||||
*/
|
||||
@ -975,10 +992,11 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the suffix to append to negative numbers. The suffix will be interpreted literally. For example, if you set
|
||||
* a suffix prefix of <code>n</code>, then the number -123 will be formatted as "-123n" in the locale
|
||||
* <em>en-US</em>. Note that the minus sign is prepended by default unless otherwise specified in either the pattern
|
||||
* string or in one of the {@link #setNegativePrefix} methods.
|
||||
* Sets the suffix to append to negative numbers. The suffix will be interpreted literally. For
|
||||
* example, if you set a suffix prefix of <code>n</code>, then the number -123 will be formatted as
|
||||
* "-123n" in the locale <em>en-US</em>. Note that the minus sign is prepended by default unless
|
||||
* otherwise specified in either the pattern string or in one of the {@link #setNegativePrefix}
|
||||
* methods.
|
||||
*
|
||||
* <p>
|
||||
* For more information on prefixes and suffixes, see {@link MutablePatternModifier}.
|
||||
@ -994,14 +1012,15 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the suffix to append to negative numbers. Locale-specific symbols will be substituted into the string
|
||||
* according to Unicode Technical Standard #35 (LDML).
|
||||
* Sets the suffix to append to negative numbers. Locale-specific symbols will be substituted into
|
||||
* the string according to Unicode Technical Standard #35 (LDML).
|
||||
*
|
||||
* <p>
|
||||
* For more information on prefixes and suffixes, see {@link MutablePatternModifier}.
|
||||
*
|
||||
* @param negativeSuffixPattern
|
||||
* The CharSequence to append to negative numbers after locale symbol substitutions take place.
|
||||
* The CharSequence to append to negative numbers after locale symbol substitutions take
|
||||
* place.
|
||||
* @return The property bag, for chaining.
|
||||
* @see #setNegativeSuffix
|
||||
*/
|
||||
@ -1011,8 +1030,8 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the location where the padding string is to be inserted to maintain the padding width: one of BEFORE_PREFIX,
|
||||
* AFTER_PREFIX, BEFORE_SUFFIX, or AFTER_SUFFIX.
|
||||
* Sets the location where the padding string is to be inserted to maintain the padding width: one of
|
||||
* BEFORE_PREFIX, AFTER_PREFIX, BEFORE_SUFFIX, or AFTER_SUFFIX.
|
||||
*
|
||||
* <p>
|
||||
* Must be used in conjunction with {@link #setFormatWidth}.
|
||||
@ -1028,7 +1047,8 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the string used for padding. The string should contain a single character or grapheme cluster.
|
||||
* Sets the string used for padding. The string should contain a single character or grapheme
|
||||
* cluster.
|
||||
*
|
||||
* <p>
|
||||
* Must be used in conjunction with {@link #setFormatWidth}.
|
||||
@ -1044,12 +1064,14 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to require cases to match when parsing strings; default is true. Case sensitivity applies to prefixes,
|
||||
* suffixes, the exponent separator, the symbol "NaN", and the infinity symbol. Grouping separators, decimal
|
||||
* separators, and padding are always case-sensitive. Currencies are always case-insensitive.
|
||||
* Whether to require cases to match when parsing strings; default is true. Case sensitivity applies
|
||||
* to prefixes, suffixes, the exponent separator, the symbol "NaN", and the infinity symbol. Grouping
|
||||
* separators, decimal separators, and padding are always case-sensitive. Currencies are always
|
||||
* case-insensitive.
|
||||
*
|
||||
* <p>
|
||||
* This setting is ignored in fast mode. In fast mode, strings are always compared in a case-sensitive way.
|
||||
* This setting is ignored in fast mode. In fast mode, strings are always compared in a
|
||||
* case-sensitive way.
|
||||
*
|
||||
* @param parseCaseSensitive
|
||||
* true to be case-sensitive when parsing; false to allow any case.
|
||||
@ -1061,23 +1083,23 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the strategy used during parsing when a code point needs to be interpreted as either a decimal separator or
|
||||
* a grouping separator.
|
||||
* Sets the strategy used during parsing when a code point needs to be interpreted as either a
|
||||
* decimal separator or a grouping separator.
|
||||
*
|
||||
* <p>
|
||||
* The comma, period, space, and apostrophe have different meanings in different locales. For example, in
|
||||
* <em>en-US</em> and most American locales, the period is used as a decimal separator, but in <em>es-PY</em> and
|
||||
* most European locales, it is used as a grouping separator.
|
||||
* The comma, period, space, and apostrophe have different meanings in different locales. For
|
||||
* example, in <em>en-US</em> and most American locales, the period is used as a decimal separator,
|
||||
* but in <em>es-PY</em> and most European locales, it is used as a grouping separator.
|
||||
*
|
||||
* <p>
|
||||
* Suppose you are in <em>fr-FR</em> the parser encounters the string "1.234". In <em>fr-FR</em>, the grouping is a
|
||||
* space and the decimal is a comma. The <em>grouping mode</em> is a mechanism to let you specify whether to accept
|
||||
* the string as 1234 (GroupingMode.DEFAULT) or whether to reject it since the separators don't match
|
||||
* (GroupingMode.RESTRICTED).
|
||||
* Suppose you are in <em>fr-FR</em> the parser encounters the string "1.234". In <em>fr-FR</em>, the
|
||||
* grouping is a space and the decimal is a comma. The <em>grouping mode</em> is a mechanism to let
|
||||
* you specify whether to accept the string as 1234 (GroupingMode.DEFAULT) or whether to reject it
|
||||
* since the separators don't match (GroupingMode.RESTRICTED).
|
||||
*
|
||||
* <p>
|
||||
* When resolving grouping separators, it is the <em>equivalence class</em> of separators that is considered. For
|
||||
* example, a period is seen as equal to a fixed set of other period-like characters.
|
||||
* When resolving grouping separators, it is the <em>equivalence class</em> of separators that is
|
||||
* considered. For example, a period is seen as equal to a fixed set of other period-like characters.
|
||||
*
|
||||
* @param parseGroupingMode
|
||||
* The {@link GroupingMode} to use; either DEFAULT or RESTRICTED.
|
||||
@ -1089,7 +1111,8 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to ignore the fractional part of numbers. For example, parses "123.4" to "123" instead of "123.4".
|
||||
* Whether to ignore the fractional part of numbers. For example, parses "123.4" to "123" instead of
|
||||
* "123.4".
|
||||
*
|
||||
* @param parseIntegerOnly
|
||||
* true to parse integers only; false to parse integers with their fraction parts
|
||||
@ -1101,8 +1124,8 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Controls certain rules for how strict this parser is when reading strings. See {@link ParseMode#LENIENT} and
|
||||
* {@link ParseMode#STRICT}.
|
||||
* Controls certain rules for how strict this parser is when reading strings. See
|
||||
* {@link ParseMode#LENIENT} and {@link ParseMode#STRICT}.
|
||||
*
|
||||
* @param parseMode
|
||||
* Either {@link ParseMode#LENIENT} or {@link ParseMode#STRICT}.
|
||||
@ -1114,7 +1137,8 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to ignore the exponential part of numbers. For example, parses "123E4" to "123" instead of "1230000".
|
||||
* Whether to ignore the exponential part of numbers. For example, parses "123E4" to "123" instead of
|
||||
* "1230000".
|
||||
*
|
||||
* @param parseNoExponent
|
||||
* true to ignore exponents; false to parse them.
|
||||
@ -1126,11 +1150,12 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to always return a BigDecimal from {@link Parse#parse} and all other parse methods. By default, a Long or
|
||||
* a BigInteger are returned when possible.
|
||||
* Whether to always return a BigDecimal from {@link Parse#parse} and all other parse methods. By
|
||||
* default, a Long or a BigInteger are returned when possible.
|
||||
*
|
||||
* @param parseToBigDecimal
|
||||
* true to always return a BigDecimal; false to return a Long or a BigInteger when possible.
|
||||
* true to always return a BigDecimal; false to return a Long or a BigInteger when
|
||||
* possible.
|
||||
* @return The property bag, for chaining.
|
||||
*/
|
||||
public DecimalFormatProperties setParseToBigDecimal(boolean parseToBigDecimal) {
|
||||
@ -1151,9 +1176,9 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the prefix to prepend to positive numbers. The prefix will be interpreted literally. For example, if you set
|
||||
* a positive prefix of <code>p</code>, then the number 123 will be formatted as "p123" in the locale
|
||||
* <em>en-US</em>.
|
||||
* Sets the prefix to prepend to positive numbers. The prefix will be interpreted literally. For
|
||||
* example, if you set a positive prefix of <code>p</code>, then the number 123 will be formatted as
|
||||
* "p123" in the locale <em>en-US</em>.
|
||||
*
|
||||
* <p>
|
||||
* For more information on prefixes and suffixes, see {@link MutablePatternModifier}.
|
||||
@ -1169,14 +1194,15 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the prefix to prepend to positive numbers. Locale-specific symbols will be substituted into the string
|
||||
* according to Unicode Technical Standard #35 (LDML).
|
||||
* Sets the prefix to prepend to positive numbers. Locale-specific symbols will be substituted into
|
||||
* the string according to Unicode Technical Standard #35 (LDML).
|
||||
*
|
||||
* <p>
|
||||
* For more information on prefixes and suffixes, see {@link MutablePatternModifier}.
|
||||
*
|
||||
* @param positivePrefixPattern
|
||||
* The CharSequence to prepend to positive numbers after locale symbol substitutions take place.
|
||||
* The CharSequence to prepend to positive numbers after locale symbol substitutions take
|
||||
* place.
|
||||
* @return The property bag, for chaining.
|
||||
* @see #setPositivePrefix
|
||||
*/
|
||||
@ -1186,9 +1212,9 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the suffix to append to positive numbers. The suffix will be interpreted literally. For example, if you set
|
||||
* a positive suffix of <code>p</code>, then the number 123 will be formatted as "123p" in the locale
|
||||
* <em>en-US</em>.
|
||||
* Sets the suffix to append to positive numbers. The suffix will be interpreted literally. For
|
||||
* example, if you set a positive suffix of <code>p</code>, then the number 123 will be formatted as
|
||||
* "123p" in the locale <em>en-US</em>.
|
||||
*
|
||||
* <p>
|
||||
* For more information on prefixes and suffixes, see {@link MutablePatternModifier}.
|
||||
@ -1204,14 +1230,15 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the suffix to append to positive numbers. Locale-specific symbols will be substituted into the string
|
||||
* according to Unicode Technical Standard #35 (LDML).
|
||||
* Sets the suffix to append to positive numbers. Locale-specific symbols will be substituted into
|
||||
* the string according to Unicode Technical Standard #35 (LDML).
|
||||
*
|
||||
* <p>
|
||||
* For more information on prefixes and suffixes, see {@link MutablePatternModifier}.
|
||||
*
|
||||
* @param positiveSuffixPattern
|
||||
* The CharSequence to append to positive numbers after locale symbol substitutions take place.
|
||||
* The CharSequence to append to positive numbers after locale symbol substitutions take
|
||||
* place.
|
||||
* @return The property bag, for chaining.
|
||||
* @see #setPositiveSuffix
|
||||
*/
|
||||
@ -1221,15 +1248,16 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the increment to which to round numbers. For example, with a rounding interval of 0.05, the number 11.17
|
||||
* would be formatted as "11.15" in locale <em>en-US</em> with the default rounding mode.
|
||||
* Sets the increment to which to round numbers. For example, with a rounding interval of 0.05, the
|
||||
* number 11.17 would be formatted as "11.15" in locale <em>en-US</em> with the default rounding
|
||||
* mode.
|
||||
*
|
||||
* <p>
|
||||
* You can use either a rounding increment or significant digits, but not both at the same time.
|
||||
*
|
||||
* <p>
|
||||
* The rounding increment can be specified in a pattern string. For example, the pattern "#,##0.05" corresponds to a
|
||||
* rounding interval of 0.05 with 1 minimum integer digit and a grouping size of 3.
|
||||
* The rounding increment can be specified in a pattern string. For example, the pattern "#,##0.05"
|
||||
* corresponds to a rounding interval of 0.05 with 1 minimum integer digit and a grouping size of 3.
|
||||
*
|
||||
* @param roundingIncrement
|
||||
* The interval to which to round.
|
||||
@ -1241,9 +1269,9 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the rounding mode, which determines under which conditions extra decimal places are rounded either up or
|
||||
* down. See {@link RoundingMode} for details on the choices of rounding mode. The default if not set explicitly is
|
||||
* {@link RoundingMode#HALF_EVEN}.
|
||||
* Sets the rounding mode, which determines under which conditions extra decimal places are rounded
|
||||
* either up or down. See {@link RoundingMode} for details on the choices of rounding mode. The
|
||||
* default if not set explicitly is {@link RoundingMode#HALF_EVEN}.
|
||||
*
|
||||
* <p>
|
||||
* This setting is ignored if {@link #setMathContext} is used.
|
||||
@ -1260,13 +1288,13 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of digits between grouping separators higher than the least-significant grouping separator. For
|
||||
* example, the locale <em>hi</em> uses a primary grouping size of 3 and a secondary grouping size of 2, so the
|
||||
* number 1234567 would be formatted as "12,34,567".
|
||||
* Sets the number of digits between grouping separators higher than the least-significant grouping
|
||||
* separator. For example, the locale <em>hi</em> uses a primary grouping size of 3 and a secondary
|
||||
* grouping size of 2, so the number 1234567 would be formatted as "12,34,567".
|
||||
*
|
||||
* <p>
|
||||
* The two levels of grouping separators can be specified in the pattern string. For example, the <em>hi</em>
|
||||
* locale's default decimal format pattern is "#,##,##0.###".
|
||||
* The two levels of grouping separators can be specified in the pattern string. For example, the
|
||||
* <em>hi</em> locale's default decimal format pattern is "#,##,##0.###".
|
||||
*
|
||||
* @param secondaryGroupingSize
|
||||
* The secondary grouping size.
|
||||
@ -1281,14 +1309,16 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
* Sets whether to always display of a plus sign on positive numbers.
|
||||
*
|
||||
* <p>
|
||||
* If the location of the negative sign is specified by the decimal format pattern (or by the negative prefix/suffix
|
||||
* pattern methods), a plus sign is substituted into that location, in accordance with Unicode Technical Standard
|
||||
* #35 (LDML) section 3.2.1. Otherwise, the plus sign is prepended to the number. For example, if the decimal format
|
||||
* pattern <code>#;#-</code> is used, then formatting 123 would result in "123+" in the locale <em>en-US</em>.
|
||||
* If the location of the negative sign is specified by the decimal format pattern (or by the
|
||||
* negative prefix/suffix pattern methods), a plus sign is substituted into that location, in
|
||||
* accordance with Unicode Technical Standard #35 (LDML) section 3.2.1. Otherwise, the plus sign is
|
||||
* prepended to the number. For example, if the decimal format pattern <code>#;#-</code> is used,
|
||||
* then formatting 123 would result in "123+" in the locale <em>en-US</em>.
|
||||
*
|
||||
* <p>
|
||||
* This method should be used <em>instead of</em> setting the positive prefix/suffix. The behavior is undefined if
|
||||
* alwaysShowPlusSign is set but the positive prefix/suffix already contains a plus sign.
|
||||
* This method should be used <em>instead of</em> setting the positive prefix/suffix. The behavior is
|
||||
* undefined if alwaysShowPlusSign is set but the positive prefix/suffix already contains a plus
|
||||
* sign.
|
||||
*
|
||||
* @param signAlwaysShown
|
||||
* Whether positive numbers should display a plus sign.
|
||||
@ -1309,8 +1339,8 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a string containing properties that differ from the default, but without being surrounded by
|
||||
* <Properties>.
|
||||
* Appends a string containing properties that differ from the default, but without being surrounded
|
||||
* by <Properties>.
|
||||
*/
|
||||
public void toStringBare(StringBuilder result) {
|
||||
Field[] fields = DecimalFormatProperties.class.getDeclaredFields();
|
||||
@ -1337,8 +1367,8 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom serialization: save fields along with their name, so that fields can be easily added in the future in any
|
||||
* order. Only save fields that differ from their default value.
|
||||
* Custom serialization: save fields along with their name, so that fields can be easily added in the
|
||||
* future in any order. Only save fields that differ from their default value.
|
||||
*/
|
||||
private void writeObject(ObjectOutputStream oos) throws IOException {
|
||||
writeObjectImpl(oos);
|
||||
|
@ -14,172 +14,188 @@ import com.ibm.icu.text.UFieldPosition;
|
||||
* An interface representing a number to be processed by the decimal formatting pipeline. Includes
|
||||
* methods for rounding, plural rules, and decimal digit extraction.
|
||||
*
|
||||
* <p>By design, this is NOT IMMUTABLE and NOT THREAD SAFE. It is intended to be an intermediate
|
||||
* object holding state during a pass through the decimal formatting pipeline.
|
||||
* <p>
|
||||
* By design, this is NOT IMMUTABLE and NOT THREAD SAFE. It is intended to be an intermediate object
|
||||
* holding state during a pass through the decimal formatting pipeline.
|
||||
*
|
||||
* <p>Implementations of this interface are free to use any internal storage mechanism.
|
||||
* <p>
|
||||
* Implementations of this interface are free to use any internal storage mechanism.
|
||||
*
|
||||
* <p>TODO: Should I change this to an abstract class so that logic for min/max digits doesn't need
|
||||
* to be copied to every implementation?
|
||||
* <p>
|
||||
* TODO: Should I change this to an abstract class so that logic for min/max digits doesn't need to be
|
||||
* copied to every implementation?
|
||||
*/
|
||||
public interface DecimalQuantity extends PluralRules.IFixedDecimal {
|
||||
/**
|
||||
* Sets the minimum and maximum integer digits that this {@link DecimalQuantity} should generate.
|
||||
* This method does not perform rounding.
|
||||
*
|
||||
* @param minInt The minimum number of integer digits.
|
||||
* @param maxInt The maximum number of integer digits.
|
||||
*/
|
||||
public void setIntegerLength(int minInt, int maxInt);
|
||||
/**
|
||||
* Sets the minimum and maximum integer digits that this {@link DecimalQuantity} should generate.
|
||||
* This method does not perform rounding.
|
||||
*
|
||||
* @param minInt
|
||||
* The minimum number of integer digits.
|
||||
* @param maxInt
|
||||
* The maximum number of integer digits.
|
||||
*/
|
||||
public void setIntegerLength(int minInt, int maxInt);
|
||||
|
||||
/**
|
||||
* Sets the minimum and maximum fraction digits that this {@link DecimalQuantity} should generate.
|
||||
* This method does not perform rounding.
|
||||
*
|
||||
* @param minFrac The minimum number of fraction digits.
|
||||
* @param maxFrac The maximum number of fraction digits.
|
||||
*/
|
||||
public void setFractionLength(int minFrac, int maxFrac);
|
||||
/**
|
||||
* Sets the minimum and maximum fraction digits that this {@link DecimalQuantity} should generate.
|
||||
* This method does not perform rounding.
|
||||
*
|
||||
* @param minFrac
|
||||
* The minimum number of fraction digits.
|
||||
* @param maxFrac
|
||||
* The maximum number of fraction digits.
|
||||
*/
|
||||
public void setFractionLength(int minFrac, int maxFrac);
|
||||
|
||||
/**
|
||||
* Rounds the number to a specified interval, such as 0.05.
|
||||
*
|
||||
* <p>If rounding to a power of ten, use the more efficient {@link #roundToMagnitude} instead.
|
||||
*
|
||||
* @param roundingInterval The increment to which to round.
|
||||
* @param mathContext The {@link MathContext} to use if rounding is necessary. Undefined behavior
|
||||
* if null.
|
||||
*/
|
||||
public void roundToIncrement(BigDecimal roundingInterval, MathContext mathContext);
|
||||
/**
|
||||
* Rounds the number to a specified interval, such as 0.05.
|
||||
*
|
||||
* <p>
|
||||
* If rounding to a power of ten, use the more efficient {@link #roundToMagnitude} instead.
|
||||
*
|
||||
* @param roundingInterval
|
||||
* The increment to which to round.
|
||||
* @param mathContext
|
||||
* The {@link MathContext} to use if rounding is necessary. Undefined behavior if null.
|
||||
*/
|
||||
public void roundToIncrement(BigDecimal roundingInterval, MathContext mathContext);
|
||||
|
||||
/**
|
||||
* Rounds the number to a specified magnitude (power of ten).
|
||||
*
|
||||
* @param roundingMagnitude The power of ten to which to round. For example, a value of -2 will
|
||||
* round to 2 decimal places.
|
||||
* @param mathContext The {@link MathContext} to use if rounding is necessary. Undefined behavior
|
||||
* if null.
|
||||
*/
|
||||
public void roundToMagnitude(int roundingMagnitude, MathContext mathContext);
|
||||
/**
|
||||
* Rounds the number to a specified magnitude (power of ten).
|
||||
*
|
||||
* @param roundingMagnitude
|
||||
* The power of ten to which to round. For example, a value of -2 will round to 2 decimal
|
||||
* places.
|
||||
* @param mathContext
|
||||
* The {@link MathContext} to use if rounding is necessary. Undefined behavior if null.
|
||||
*/
|
||||
public void roundToMagnitude(int roundingMagnitude, MathContext mathContext);
|
||||
|
||||
/**
|
||||
* Rounds the number to an infinite number of decimal points. This has no effect except for
|
||||
* forcing the double in {@link DecimalQuantity_AbstractBCD} to adopt its exact representation.
|
||||
*/
|
||||
public void roundToInfinity();
|
||||
/**
|
||||
* Rounds the number to an infinite number of decimal points. This has no effect except for forcing
|
||||
* the double in {@link DecimalQuantity_AbstractBCD} to adopt its exact representation.
|
||||
*/
|
||||
public void roundToInfinity();
|
||||
|
||||
/**
|
||||
* Truncates the decimals from this DecimalQuantity. Equivalent to calling roundToMagnitude(0, FLOOR)
|
||||
*/
|
||||
void truncate();
|
||||
/**
|
||||
* Truncates the decimals from this DecimalQuantity. Equivalent to calling roundToMagnitude(0, FLOOR)
|
||||
*/
|
||||
void truncate();
|
||||
|
||||
/**
|
||||
* Multiply the internal value.
|
||||
*
|
||||
* @param multiplicand The value by which to multiply.
|
||||
*/
|
||||
public void multiplyBy(BigDecimal multiplicand);
|
||||
/**
|
||||
* Multiply the internal value.
|
||||
*
|
||||
* @param multiplicand
|
||||
* The value by which to multiply.
|
||||
*/
|
||||
public void multiplyBy(BigDecimal multiplicand);
|
||||
|
||||
/**
|
||||
* Scales the number by a power of ten. For example, if the value is currently "1234.56", calling
|
||||
* this method with delta=-3 will change the value to "1.23456".
|
||||
*
|
||||
* @param delta The number of magnitudes of ten to change by.
|
||||
*/
|
||||
public void adjustMagnitude(int delta);
|
||||
/**
|
||||
* Scales the number by a power of ten. For example, if the value is currently "1234.56", calling
|
||||
* this method with delta=-3 will change the value to "1.23456".
|
||||
*
|
||||
* @param delta
|
||||
* The number of magnitudes of ten to change by.
|
||||
*/
|
||||
public void adjustMagnitude(int delta);
|
||||
|
||||
/**
|
||||
* @return The power of ten corresponding to the most significant nonzero digit.
|
||||
* @throws ArithmeticException If the value represented is zero.
|
||||
*/
|
||||
public int getMagnitude() throws ArithmeticException;
|
||||
/**
|
||||
* @return The power of ten corresponding to the most significant nonzero digit.
|
||||
* @throws ArithmeticException
|
||||
* If the value represented is zero.
|
||||
*/
|
||||
public int getMagnitude() throws ArithmeticException;
|
||||
|
||||
/** @return Whether the value represented by this {@link DecimalQuantity} is zero. */
|
||||
public boolean isZero();
|
||||
/** @return Whether the value represented by this {@link DecimalQuantity} is zero. */
|
||||
public boolean isZero();
|
||||
|
||||
/** @return Whether the value represented by this {@link DecimalQuantity} is less than zero. */
|
||||
public boolean isNegative();
|
||||
/** @return Whether the value represented by this {@link DecimalQuantity} is less than zero. */
|
||||
public boolean isNegative();
|
||||
|
||||
/** @return Whether the value represented by this {@link DecimalQuantity} is infinite. */
|
||||
@Override
|
||||
public boolean isInfinite();
|
||||
/** @return Whether the value represented by this {@link DecimalQuantity} is infinite. */
|
||||
@Override
|
||||
public boolean isInfinite();
|
||||
|
||||
/** @return Whether the value represented by this {@link DecimalQuantity} is not a number. */
|
||||
@Override
|
||||
public boolean isNaN();
|
||||
/** @return Whether the value represented by this {@link DecimalQuantity} is not a number. */
|
||||
@Override
|
||||
public boolean isNaN();
|
||||
|
||||
/** @return The value contained in this {@link DecimalQuantity} approximated as a double. */
|
||||
public double toDouble();
|
||||
/** @return The value contained in this {@link DecimalQuantity} approximated as a double. */
|
||||
public double toDouble();
|
||||
|
||||
public BigDecimal toBigDecimal();
|
||||
public BigDecimal toBigDecimal();
|
||||
|
||||
public void setToBigDecimal(BigDecimal input);
|
||||
public void setToBigDecimal(BigDecimal input);
|
||||
|
||||
public int maxRepresentableDigits();
|
||||
public int maxRepresentableDigits();
|
||||
|
||||
// TODO: Should this method be removed, since DecimalQuantity implements IFixedDecimal now?
|
||||
/**
|
||||
* Computes the plural form for this number based on the specified set of rules.
|
||||
*
|
||||
* @param rules A {@link PluralRules} object representing the set of rules.
|
||||
* @return The {@link StandardPlural} according to the PluralRules. If the plural form is not in
|
||||
* the set of standard plurals, {@link StandardPlural#OTHER} is returned instead.
|
||||
*/
|
||||
public StandardPlural getStandardPlural(PluralRules rules);
|
||||
// TODO: Should this method be removed, since DecimalQuantity implements IFixedDecimal now?
|
||||
/**
|
||||
* Computes the plural form for this number based on the specified set of rules.
|
||||
*
|
||||
* @param rules
|
||||
* A {@link PluralRules} object representing the set of rules.
|
||||
* @return The {@link StandardPlural} according to the PluralRules. If the plural form is not in the
|
||||
* set of standard plurals, {@link StandardPlural#OTHER} is returned instead.
|
||||
*/
|
||||
public StandardPlural getStandardPlural(PluralRules rules);
|
||||
|
||||
/**
|
||||
* Gets the digit at the specified magnitude. For example, if the represented number is 12.3,
|
||||
* getDigit(-1) returns 3, since 3 is the digit corresponding to 10^-1.
|
||||
*
|
||||
* @param magnitude The magnitude of the digit.
|
||||
* @return The digit at the specified magnitude.
|
||||
*/
|
||||
public byte getDigit(int magnitude);
|
||||
/**
|
||||
* Gets the digit at the specified magnitude. For example, if the represented number is 12.3,
|
||||
* getDigit(-1) returns 3, since 3 is the digit corresponding to 10^-1.
|
||||
*
|
||||
* @param magnitude
|
||||
* The magnitude of the digit.
|
||||
* @return The digit at the specified magnitude.
|
||||
*/
|
||||
public byte getDigit(int magnitude);
|
||||
|
||||
/**
|
||||
* Gets the largest power of ten that needs to be displayed. The value returned by this function
|
||||
* will be bounded between minInt and maxInt.
|
||||
*
|
||||
* @return The highest-magnitude digit to be displayed.
|
||||
*/
|
||||
public int getUpperDisplayMagnitude();
|
||||
/**
|
||||
* Gets the largest power of ten that needs to be displayed. The value returned by this function will
|
||||
* be bounded between minInt and maxInt.
|
||||
*
|
||||
* @return The highest-magnitude digit to be displayed.
|
||||
*/
|
||||
public int getUpperDisplayMagnitude();
|
||||
|
||||
/**
|
||||
* Gets the smallest power of ten that needs to be displayed. The value returned by this function
|
||||
* will be bounded between -minFrac and -maxFrac.
|
||||
*
|
||||
* @return The lowest-magnitude digit to be displayed.
|
||||
*/
|
||||
public int getLowerDisplayMagnitude();
|
||||
/**
|
||||
* Gets the smallest power of ten that needs to be displayed. The value returned by this function
|
||||
* will be bounded between -minFrac and -maxFrac.
|
||||
*
|
||||
* @return The lowest-magnitude digit to be displayed.
|
||||
*/
|
||||
public int getLowerDisplayMagnitude();
|
||||
|
||||
/**
|
||||
* Returns the string in "plain" format (no exponential notation) using ASCII digits.
|
||||
*/
|
||||
public String toPlainString();
|
||||
/**
|
||||
* Returns the string in "plain" format (no exponential notation) using ASCII digits.
|
||||
*/
|
||||
public String toPlainString();
|
||||
|
||||
/**
|
||||
* Like clone, but without the restrictions of the Cloneable interface clone.
|
||||
*
|
||||
* @return A copy of this instance which can be mutated without affecting this instance.
|
||||
*/
|
||||
public DecimalQuantity createCopy();
|
||||
/**
|
||||
* Like clone, but without the restrictions of the Cloneable interface clone.
|
||||
*
|
||||
* @return A copy of this instance which can be mutated without affecting this instance.
|
||||
*/
|
||||
public DecimalQuantity createCopy();
|
||||
|
||||
/**
|
||||
* Sets this instance to be equal to another instance.
|
||||
*
|
||||
* @param other The instance to copy from.
|
||||
*/
|
||||
public void copyFrom(DecimalQuantity other);
|
||||
/**
|
||||
* Sets this instance to be equal to another instance.
|
||||
*
|
||||
* @param other
|
||||
* The instance to copy from.
|
||||
*/
|
||||
public void copyFrom(DecimalQuantity other);
|
||||
|
||||
/** This method is for internal testing only. */
|
||||
public long getPositionFingerprint();
|
||||
/** This method is for internal testing only. */
|
||||
public long getPositionFingerprint();
|
||||
|
||||
/**
|
||||
* If the given {@link FieldPosition} is a {@link UFieldPosition}, populates it with the fraction
|
||||
* length and fraction long value. If the argument is not a {@link UFieldPosition}, nothing
|
||||
* happens.
|
||||
*
|
||||
* @param fp The {@link UFieldPosition} to populate.
|
||||
*/
|
||||
public void populateUFieldPosition(FieldPosition fp);
|
||||
/**
|
||||
* If the given {@link FieldPosition} is a {@link UFieldPosition}, populates it with the fraction
|
||||
* length and fraction long value. If the argument is not a {@link UFieldPosition}, nothing happens.
|
||||
*
|
||||
* @param fp
|
||||
* The {@link UFieldPosition} to populate.
|
||||
*/
|
||||
public void populateUFieldPosition(FieldPosition fp);
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -6,412 +6,433 @@ import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* A DecimalQuantity with internal storage as a 64-bit BCD, with fallback to a byte array
|
||||
* for numbers that don't fit into the standard BCD.
|
||||
* A DecimalQuantity with internal storage as a 64-bit BCD, with fallback to a byte array for numbers
|
||||
* that don't fit into the standard BCD.
|
||||
*/
|
||||
public final class DecimalQuantity_DualStorageBCD extends DecimalQuantity_AbstractBCD {
|
||||
|
||||
/**
|
||||
* The BCD of the 16 digits of the number represented by this object. Every 4 bits of the long map
|
||||
* to one digit. For example, the number "12345" in BCD is "0x12345".
|
||||
*
|
||||
* <p>Whenever bcd changes internally, {@link #compact()} must be called, except in special cases
|
||||
* like setting the digit to zero.
|
||||
*/
|
||||
private byte[] bcdBytes;
|
||||
/**
|
||||
* The BCD of the 16 digits of the number represented by this object. Every 4 bits of the long map to
|
||||
* one digit. For example, the number "12345" in BCD is "0x12345".
|
||||
*
|
||||
* <p>
|
||||
* Whenever bcd changes internally, {@link #compact()} must be called, except in special cases like
|
||||
* setting the digit to zero.
|
||||
*/
|
||||
private byte[] bcdBytes;
|
||||
|
||||
private long bcdLong = 0L;
|
||||
private long bcdLong = 0L;
|
||||
|
||||
private boolean usingBytes = false;
|
||||
private boolean usingBytes = false;
|
||||
|
||||
@Override
|
||||
public int maxRepresentableDigits() {
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
public DecimalQuantity_DualStorageBCD() {
|
||||
setBcdToZero();
|
||||
flags = 0;
|
||||
}
|
||||
|
||||
public DecimalQuantity_DualStorageBCD(long input) {
|
||||
setToLong(input);
|
||||
}
|
||||
|
||||
public DecimalQuantity_DualStorageBCD(int input) {
|
||||
setToInt(input);
|
||||
}
|
||||
|
||||
public DecimalQuantity_DualStorageBCD(double input) {
|
||||
setToDouble(input);
|
||||
}
|
||||
|
||||
public DecimalQuantity_DualStorageBCD(BigInteger input) {
|
||||
setToBigInteger(input);
|
||||
}
|
||||
|
||||
public DecimalQuantity_DualStorageBCD(BigDecimal input) {
|
||||
setToBigDecimal(input);
|
||||
}
|
||||
|
||||
public DecimalQuantity_DualStorageBCD(DecimalQuantity_DualStorageBCD other) {
|
||||
copyFrom(other);
|
||||
}
|
||||
|
||||
public DecimalQuantity_DualStorageBCD(Number number) {
|
||||
if (number instanceof Long) {
|
||||
setToLong(number.longValue());
|
||||
} else if (number instanceof Integer) {
|
||||
setToInt(number.intValue());
|
||||
} else if (number instanceof Double) {
|
||||
setToDouble(number.doubleValue());
|
||||
} else if (number instanceof BigInteger) {
|
||||
setToBigInteger((BigInteger) number);
|
||||
} else if (number instanceof BigDecimal) {
|
||||
setToBigDecimal((BigDecimal) number);
|
||||
} else if (number instanceof com.ibm.icu.math.BigDecimal) {
|
||||
setToBigDecimal(((com.ibm.icu.math.BigDecimal) number).toBigDecimal());
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Number is of an unsupported type: " + number.getClass().getName());
|
||||
@Override
|
||||
public int maxRepresentableDigits() {
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecimalQuantity createCopy() {
|
||||
return new DecimalQuantity_DualStorageBCD(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte getDigitPos(int position) {
|
||||
if (usingBytes) {
|
||||
if (position < 0 || position > precision) return 0;
|
||||
return bcdBytes[position];
|
||||
} else {
|
||||
if (position < 0 || position >= 16) return 0;
|
||||
return (byte) ((bcdLong >>> (position * 4)) & 0xf);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setDigitPos(int position, byte value) {
|
||||
assert position >= 0;
|
||||
if (usingBytes) {
|
||||
ensureCapacity(position + 1);
|
||||
bcdBytes[position] = value;
|
||||
} else if (position >= 16) {
|
||||
switchStorage();
|
||||
ensureCapacity(position + 1);
|
||||
bcdBytes[position] = value;
|
||||
} else {
|
||||
int shift = position * 4;
|
||||
bcdLong = bcdLong & ~(0xfL << shift) | ((long) value << shift);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void shiftLeft(int numDigits) {
|
||||
if (!usingBytes && precision + numDigits > 16) {
|
||||
switchStorage();
|
||||
}
|
||||
if (usingBytes) {
|
||||
ensureCapacity(precision + numDigits);
|
||||
int i = precision + numDigits - 1;
|
||||
for (; i >= numDigits; i--) {
|
||||
bcdBytes[i] = bcdBytes[i - numDigits];
|
||||
}
|
||||
for (; i >= 0; i--) {
|
||||
bcdBytes[i] = 0;
|
||||
}
|
||||
} else {
|
||||
bcdLong <<= (numDigits * 4);
|
||||
}
|
||||
scale -= numDigits;
|
||||
precision += numDigits;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void shiftRight(int numDigits) {
|
||||
if (usingBytes) {
|
||||
int i = 0;
|
||||
for (; i < precision - numDigits; i++) {
|
||||
bcdBytes[i] = bcdBytes[i + numDigits];
|
||||
}
|
||||
for (; i < precision; i++) {
|
||||
bcdBytes[i] = 0;
|
||||
}
|
||||
} else {
|
||||
bcdLong >>>= (numDigits * 4);
|
||||
}
|
||||
scale += numDigits;
|
||||
precision -= numDigits;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setBcdToZero() {
|
||||
if (usingBytes) {
|
||||
bcdBytes = null;
|
||||
usingBytes = false;
|
||||
}
|
||||
bcdLong = 0L;
|
||||
scale = 0;
|
||||
precision = 0;
|
||||
isApproximate = false;
|
||||
origDouble = 0;
|
||||
origDelta = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readIntToBcd(int n) {
|
||||
assert n != 0;
|
||||
// ints always fit inside the long implementation.
|
||||
long result = 0L;
|
||||
int i = 16;
|
||||
for (; n != 0; n /= 10, i--) {
|
||||
result = (result >>> 4) + (((long) n % 10) << 60);
|
||||
}
|
||||
assert !usingBytes;
|
||||
bcdLong = result >>> (i * 4);
|
||||
scale = 0;
|
||||
precision = 16 - i;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readLongToBcd(long n) {
|
||||
assert n != 0;
|
||||
if (n >= 10000000000000000L) {
|
||||
ensureCapacity();
|
||||
int i = 0;
|
||||
for (; n != 0L; n /= 10L, i++) {
|
||||
bcdBytes[i] = (byte) (n % 10);
|
||||
}
|
||||
assert usingBytes;
|
||||
scale = 0;
|
||||
precision = i;
|
||||
} else {
|
||||
long result = 0L;
|
||||
int i = 16;
|
||||
for (; n != 0L; n /= 10L, i--) {
|
||||
result = (result >>> 4) + ((n % 10) << 60);
|
||||
}
|
||||
assert i >= 0;
|
||||
assert !usingBytes;
|
||||
bcdLong = result >>> (i * 4);
|
||||
scale = 0;
|
||||
precision = 16 - i;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readBigIntegerToBcd(BigInteger n) {
|
||||
assert n.signum() != 0;
|
||||
ensureCapacity(); // allocate initial byte array
|
||||
int i = 0;
|
||||
for (; n.signum() != 0; i++) {
|
||||
BigInteger[] temp = n.divideAndRemainder(BigInteger.TEN);
|
||||
ensureCapacity(i + 1);
|
||||
bcdBytes[i] = temp[1].byteValue();
|
||||
n = temp[0];
|
||||
}
|
||||
scale = 0;
|
||||
precision = i;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BigDecimal bcdToBigDecimal() {
|
||||
if (usingBytes) {
|
||||
// Converting to a string here is faster than doing BigInteger/BigDecimal arithmetic.
|
||||
BigDecimal result = new BigDecimal(toNumberString());
|
||||
if (isNegative()) {
|
||||
result = result.negate();
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
long tempLong = 0L;
|
||||
for (int shift = (precision - 1); shift >= 0; shift--) {
|
||||
tempLong = tempLong * 10 + getDigitPos(shift);
|
||||
}
|
||||
BigDecimal result = BigDecimal.valueOf(tempLong);
|
||||
result = result.scaleByPowerOfTen(scale);
|
||||
if (isNegative()) result = result.negate();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void compact() {
|
||||
if (usingBytes) {
|
||||
int delta = 0;
|
||||
for (; delta < precision && bcdBytes[delta] == 0; delta++) ;
|
||||
if (delta == precision) {
|
||||
// Number is zero
|
||||
public DecimalQuantity_DualStorageBCD() {
|
||||
setBcdToZero();
|
||||
return;
|
||||
} else {
|
||||
// Remove trailing zeros
|
||||
shiftRight(delta);
|
||||
}
|
||||
flags = 0;
|
||||
}
|
||||
|
||||
// Compute precision
|
||||
int leading = precision - 1;
|
||||
for (; leading >= 0 && bcdBytes[leading] == 0; leading--) ;
|
||||
precision = leading + 1;
|
||||
public DecimalQuantity_DualStorageBCD(long input) {
|
||||
setToLong(input);
|
||||
}
|
||||
|
||||
// Switch storage mechanism if possible
|
||||
if (precision <= 16) {
|
||||
switchStorage();
|
||||
}
|
||||
public DecimalQuantity_DualStorageBCD(int input) {
|
||||
setToInt(input);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (bcdLong == 0L) {
|
||||
// Number is zero
|
||||
public DecimalQuantity_DualStorageBCD(double input) {
|
||||
setToDouble(input);
|
||||
}
|
||||
|
||||
public DecimalQuantity_DualStorageBCD(BigInteger input) {
|
||||
setToBigInteger(input);
|
||||
}
|
||||
|
||||
public DecimalQuantity_DualStorageBCD(BigDecimal input) {
|
||||
setToBigDecimal(input);
|
||||
}
|
||||
|
||||
public DecimalQuantity_DualStorageBCD(DecimalQuantity_DualStorageBCD other) {
|
||||
copyFrom(other);
|
||||
}
|
||||
|
||||
public DecimalQuantity_DualStorageBCD(Number number) {
|
||||
if (number instanceof Long) {
|
||||
setToLong(number.longValue());
|
||||
} else if (number instanceof Integer) {
|
||||
setToInt(number.intValue());
|
||||
} else if (number instanceof Double) {
|
||||
setToDouble(number.doubleValue());
|
||||
} else if (number instanceof BigInteger) {
|
||||
setToBigInteger((BigInteger) number);
|
||||
} else if (number instanceof BigDecimal) {
|
||||
setToBigDecimal((BigDecimal) number);
|
||||
} else if (number instanceof com.ibm.icu.math.BigDecimal) {
|
||||
setToBigDecimal(((com.ibm.icu.math.BigDecimal) number).toBigDecimal());
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Number is of an unsupported type: " + number.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecimalQuantity createCopy() {
|
||||
return new DecimalQuantity_DualStorageBCD(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte getDigitPos(int position) {
|
||||
if (usingBytes) {
|
||||
if (position < 0 || position > precision)
|
||||
return 0;
|
||||
return bcdBytes[position];
|
||||
} else {
|
||||
if (position < 0 || position >= 16)
|
||||
return 0;
|
||||
return (byte) ((bcdLong >>> (position * 4)) & 0xf);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setDigitPos(int position, byte value) {
|
||||
assert position >= 0;
|
||||
if (usingBytes) {
|
||||
ensureCapacity(position + 1);
|
||||
bcdBytes[position] = value;
|
||||
} else if (position >= 16) {
|
||||
switchStorage();
|
||||
ensureCapacity(position + 1);
|
||||
bcdBytes[position] = value;
|
||||
} else {
|
||||
int shift = position * 4;
|
||||
bcdLong = bcdLong & ~(0xfL << shift) | ((long) value << shift);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void shiftLeft(int numDigits) {
|
||||
if (!usingBytes && precision + numDigits > 16) {
|
||||
switchStorage();
|
||||
}
|
||||
if (usingBytes) {
|
||||
ensureCapacity(precision + numDigits);
|
||||
int i = precision + numDigits - 1;
|
||||
for (; i >= numDigits; i--) {
|
||||
bcdBytes[i] = bcdBytes[i - numDigits];
|
||||
}
|
||||
for (; i >= 0; i--) {
|
||||
bcdBytes[i] = 0;
|
||||
}
|
||||
} else {
|
||||
bcdLong <<= (numDigits * 4);
|
||||
}
|
||||
scale -= numDigits;
|
||||
precision += numDigits;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void shiftRight(int numDigits) {
|
||||
if (usingBytes) {
|
||||
int i = 0;
|
||||
for (; i < precision - numDigits; i++) {
|
||||
bcdBytes[i] = bcdBytes[i + numDigits];
|
||||
}
|
||||
for (; i < precision; i++) {
|
||||
bcdBytes[i] = 0;
|
||||
}
|
||||
} else {
|
||||
bcdLong >>>= (numDigits * 4);
|
||||
}
|
||||
scale += numDigits;
|
||||
precision -= numDigits;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setBcdToZero() {
|
||||
if (usingBytes) {
|
||||
bcdBytes = null;
|
||||
usingBytes = false;
|
||||
}
|
||||
bcdLong = 0L;
|
||||
scale = 0;
|
||||
precision = 0;
|
||||
isApproximate = false;
|
||||
origDouble = 0;
|
||||
origDelta = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readIntToBcd(int n) {
|
||||
assert n != 0;
|
||||
// ints always fit inside the long implementation.
|
||||
long result = 0L;
|
||||
int i = 16;
|
||||
for (; n != 0; n /= 10, i--) {
|
||||
result = (result >>> 4) + (((long) n % 10) << 60);
|
||||
}
|
||||
assert !usingBytes;
|
||||
bcdLong = result >>> (i * 4);
|
||||
scale = 0;
|
||||
precision = 16 - i;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readLongToBcd(long n) {
|
||||
assert n != 0;
|
||||
if (n >= 10000000000000000L) {
|
||||
ensureCapacity();
|
||||
int i = 0;
|
||||
for (; n != 0L; n /= 10L, i++) {
|
||||
bcdBytes[i] = (byte) (n % 10);
|
||||
}
|
||||
assert usingBytes;
|
||||
scale = 0;
|
||||
precision = i;
|
||||
} else {
|
||||
long result = 0L;
|
||||
int i = 16;
|
||||
for (; n != 0L; n /= 10L, i--) {
|
||||
result = (result >>> 4) + ((n % 10) << 60);
|
||||
}
|
||||
assert i >= 0;
|
||||
assert !usingBytes;
|
||||
bcdLong = result >>> (i * 4);
|
||||
scale = 0;
|
||||
precision = 16 - i;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readBigIntegerToBcd(BigInteger n) {
|
||||
assert n.signum() != 0;
|
||||
ensureCapacity(); // allocate initial byte array
|
||||
int i = 0;
|
||||
for (; n.signum() != 0; i++) {
|
||||
BigInteger[] temp = n.divideAndRemainder(BigInteger.TEN);
|
||||
ensureCapacity(i + 1);
|
||||
bcdBytes[i] = temp[1].byteValue();
|
||||
n = temp[0];
|
||||
}
|
||||
scale = 0;
|
||||
precision = i;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BigDecimal bcdToBigDecimal() {
|
||||
if (usingBytes) {
|
||||
// Converting to a string here is faster than doing BigInteger/BigDecimal arithmetic.
|
||||
BigDecimal result = new BigDecimal(toNumberString());
|
||||
if (isNegative()) {
|
||||
result = result.negate();
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
long tempLong = 0L;
|
||||
for (int shift = (precision - 1); shift >= 0; shift--) {
|
||||
tempLong = tempLong * 10 + getDigitPos(shift);
|
||||
}
|
||||
BigDecimal result = BigDecimal.valueOf(tempLong);
|
||||
result = result.scaleByPowerOfTen(scale);
|
||||
if (isNegative())
|
||||
result = result.negate();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void compact() {
|
||||
if (usingBytes) {
|
||||
int delta = 0;
|
||||
for (; delta < precision && bcdBytes[delta] == 0; delta++)
|
||||
;
|
||||
if (delta == precision) {
|
||||
// Number is zero
|
||||
setBcdToZero();
|
||||
return;
|
||||
} else {
|
||||
// Remove trailing zeros
|
||||
shiftRight(delta);
|
||||
}
|
||||
|
||||
// Compute precision
|
||||
int leading = precision - 1;
|
||||
for (; leading >= 0 && bcdBytes[leading] == 0; leading--)
|
||||
;
|
||||
precision = leading + 1;
|
||||
|
||||
// Switch storage mechanism if possible
|
||||
if (precision <= 16) {
|
||||
switchStorage();
|
||||
}
|
||||
|
||||
} else {
|
||||
if (bcdLong == 0L) {
|
||||
// Number is zero
|
||||
setBcdToZero();
|
||||
return;
|
||||
}
|
||||
|
||||
// Compact the number (remove trailing zeros)
|
||||
int delta = Long.numberOfTrailingZeros(bcdLong) / 4;
|
||||
bcdLong >>>= delta * 4;
|
||||
scale += delta;
|
||||
|
||||
// Compute precision
|
||||
precision = 16 - (Long.numberOfLeadingZeros(bcdLong) / 4);
|
||||
}
|
||||
}
|
||||
|
||||
/** Ensure that a byte array of at least 40 digits is allocated. */
|
||||
private void ensureCapacity() {
|
||||
ensureCapacity(40);
|
||||
}
|
||||
|
||||
private void ensureCapacity(int capacity) {
|
||||
if (capacity == 0)
|
||||
return;
|
||||
int oldCapacity = usingBytes ? bcdBytes.length : 0;
|
||||
if (!usingBytes) {
|
||||
bcdBytes = new byte[capacity];
|
||||
} else if (oldCapacity < capacity) {
|
||||
byte[] bcd1 = new byte[capacity * 2];
|
||||
System.arraycopy(bcdBytes, 0, bcd1, 0, oldCapacity);
|
||||
bcdBytes = bcd1;
|
||||
}
|
||||
usingBytes = true;
|
||||
}
|
||||
|
||||
/** Switches the internal storage mechanism between the 64-bit long and the byte array. */
|
||||
private void switchStorage() {
|
||||
if (usingBytes) {
|
||||
// Change from bytes to long
|
||||
bcdLong = 0L;
|
||||
for (int i = precision - 1; i >= 0; i--) {
|
||||
bcdLong <<= 4;
|
||||
bcdLong |= bcdBytes[i];
|
||||
}
|
||||
bcdBytes = null;
|
||||
usingBytes = false;
|
||||
} else {
|
||||
// Change from long to bytes
|
||||
ensureCapacity();
|
||||
for (int i = 0; i < precision; i++) {
|
||||
bcdBytes[i] = (byte) (bcdLong & 0xf);
|
||||
bcdLong >>>= 4;
|
||||
}
|
||||
assert usingBytes;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void copyBcdFrom(DecimalQuantity _other) {
|
||||
DecimalQuantity_DualStorageBCD other = (DecimalQuantity_DualStorageBCD) _other;
|
||||
setBcdToZero();
|
||||
return;
|
||||
}
|
||||
|
||||
// Compact the number (remove trailing zeros)
|
||||
int delta = Long.numberOfTrailingZeros(bcdLong) / 4;
|
||||
bcdLong >>>= delta * 4;
|
||||
scale += delta;
|
||||
|
||||
// Compute precision
|
||||
precision = 16 - (Long.numberOfLeadingZeros(bcdLong) / 4);
|
||||
}
|
||||
}
|
||||
|
||||
/** Ensure that a byte array of at least 40 digits is allocated. */
|
||||
private void ensureCapacity() {
|
||||
ensureCapacity(40);
|
||||
}
|
||||
|
||||
private void ensureCapacity(int capacity) {
|
||||
if (capacity == 0) return;
|
||||
int oldCapacity = usingBytes ? bcdBytes.length : 0;
|
||||
if (!usingBytes) {
|
||||
bcdBytes = new byte[capacity];
|
||||
} else if (oldCapacity < capacity) {
|
||||
byte[] bcd1 = new byte[capacity * 2];
|
||||
System.arraycopy(bcdBytes, 0, bcd1, 0, oldCapacity);
|
||||
bcdBytes = bcd1;
|
||||
}
|
||||
usingBytes = true;
|
||||
}
|
||||
|
||||
/** Switches the internal storage mechanism between the 64-bit long and the byte array. */
|
||||
private void switchStorage() {
|
||||
if (usingBytes) {
|
||||
// Change from bytes to long
|
||||
bcdLong = 0L;
|
||||
for (int i = precision - 1; i >= 0; i--) {
|
||||
bcdLong <<= 4;
|
||||
bcdLong |= bcdBytes[i];
|
||||
}
|
||||
bcdBytes = null;
|
||||
usingBytes = false;
|
||||
} else {
|
||||
// Change from long to bytes
|
||||
ensureCapacity();
|
||||
for (int i = 0; i < precision; i++) {
|
||||
bcdBytes[i] = (byte) (bcdLong & 0xf);
|
||||
bcdLong >>>= 4;
|
||||
}
|
||||
assert usingBytes;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void copyBcdFrom(DecimalQuantity _other) {
|
||||
DecimalQuantity_DualStorageBCD other = (DecimalQuantity_DualStorageBCD) _other;
|
||||
setBcdToZero();
|
||||
if (other.usingBytes) {
|
||||
ensureCapacity(other.precision);
|
||||
System.arraycopy(other.bcdBytes, 0, bcdBytes, 0, other.precision);
|
||||
} else {
|
||||
bcdLong = other.bcdLong;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the bytes stored in this instance are all valid. For internal unit testing only.
|
||||
*
|
||||
* @return An error message if this instance is invalid, or null if this instance is healthy.
|
||||
* @internal
|
||||
* @deprecated This API is for ICU internal use only.
|
||||
*/
|
||||
@Deprecated
|
||||
public String checkHealth() {
|
||||
if (usingBytes) {
|
||||
if (bcdLong != 0) return "Value in bcdLong but we are in byte mode";
|
||||
if (precision == 0) return "Zero precision but we are in byte mode";
|
||||
if (precision > bcdBytes.length) return "Precision exceeds length of byte array";
|
||||
if (getDigitPos(precision - 1) == 0) return "Most significant digit is zero in byte mode";
|
||||
if (getDigitPos(0) == 0) return "Least significant digit is zero in long mode";
|
||||
for (int i = 0; i < precision; i++) {
|
||||
if (getDigitPos(i) >= 10) return "Digit exceeding 10 in byte array";
|
||||
if (getDigitPos(i) < 0) return "Digit below 0 in byte array";
|
||||
}
|
||||
for (int i = precision; i < bcdBytes.length; i++) {
|
||||
if (getDigitPos(i) != 0) return "Nonzero digits outside of range in byte array";
|
||||
}
|
||||
} else {
|
||||
if (bcdBytes != null) {
|
||||
for (int i = 0; i < bcdBytes.length; i++) {
|
||||
if (bcdBytes[i] != 0) return "Nonzero digits in byte array but we are in long mode";
|
||||
if (other.usingBytes) {
|
||||
ensureCapacity(other.precision);
|
||||
System.arraycopy(other.bcdBytes, 0, bcdBytes, 0, other.precision);
|
||||
} else {
|
||||
bcdLong = other.bcdLong;
|
||||
}
|
||||
}
|
||||
if (precision == 0 && bcdLong != 0) return "Value in bcdLong even though precision is zero";
|
||||
if (precision > 16) return "Precision exceeds length of long";
|
||||
if (precision != 0 && getDigitPos(precision - 1) == 0)
|
||||
return "Most significant digit is zero in long mode";
|
||||
if (precision != 0 && getDigitPos(0) == 0)
|
||||
return "Least significant digit is zero in long mode";
|
||||
for (int i = 0; i < precision; i++) {
|
||||
if (getDigitPos(i) >= 10) return "Digit exceeding 10 in long";
|
||||
if (getDigitPos(i) < 0) return "Digit below 0 in long (?!)";
|
||||
}
|
||||
for (int i = precision; i < 16; i++) {
|
||||
if (getDigitPos(i) != 0) return "Nonzero digits outside of range in long";
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this {@link DecimalQuantity_DualStorageBCD} is using its internal byte array storage mechanism.
|
||||
*
|
||||
* @return true if an internal byte array is being used; false if a long is being used.
|
||||
* @internal
|
||||
* @deprecated This API is ICU internal only.
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean isUsingBytes() {
|
||||
return usingBytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(
|
||||
"<DecimalQuantity %s:%d:%d:%s %s %s>",
|
||||
(lOptPos > 1000 ? "999" : String.valueOf(lOptPos)),
|
||||
lReqPos,
|
||||
rReqPos,
|
||||
(rOptPos < -1000 ? "-999" : String.valueOf(rOptPos)),
|
||||
(usingBytes ? "bytes" : "long"),
|
||||
toNumberString());
|
||||
}
|
||||
|
||||
public String toNumberString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (usingBytes) {
|
||||
for (int i = precision - 1; i >= 0; i--) {
|
||||
sb.append(bcdBytes[i]);
|
||||
/**
|
||||
* Checks whether the bytes stored in this instance are all valid. For internal unit testing only.
|
||||
*
|
||||
* @return An error message if this instance is invalid, or null if this instance is healthy.
|
||||
* @internal
|
||||
* @deprecated This API is for ICU internal use only.
|
||||
*/
|
||||
@Deprecated
|
||||
public String checkHealth() {
|
||||
if (usingBytes) {
|
||||
if (bcdLong != 0)
|
||||
return "Value in bcdLong but we are in byte mode";
|
||||
if (precision == 0)
|
||||
return "Zero precision but we are in byte mode";
|
||||
if (precision > bcdBytes.length)
|
||||
return "Precision exceeds length of byte array";
|
||||
if (getDigitPos(precision - 1) == 0)
|
||||
return "Most significant digit is zero in byte mode";
|
||||
if (getDigitPos(0) == 0)
|
||||
return "Least significant digit is zero in long mode";
|
||||
for (int i = 0; i < precision; i++) {
|
||||
if (getDigitPos(i) >= 10)
|
||||
return "Digit exceeding 10 in byte array";
|
||||
if (getDigitPos(i) < 0)
|
||||
return "Digit below 0 in byte array";
|
||||
}
|
||||
for (int i = precision; i < bcdBytes.length; i++) {
|
||||
if (getDigitPos(i) != 0)
|
||||
return "Nonzero digits outside of range in byte array";
|
||||
}
|
||||
} else {
|
||||
if (bcdBytes != null) {
|
||||
for (int i = 0; i < bcdBytes.length; i++) {
|
||||
if (bcdBytes[i] != 0)
|
||||
return "Nonzero digits in byte array but we are in long mode";
|
||||
}
|
||||
}
|
||||
if (precision == 0 && bcdLong != 0)
|
||||
return "Value in bcdLong even though precision is zero";
|
||||
if (precision > 16)
|
||||
return "Precision exceeds length of long";
|
||||
if (precision != 0 && getDigitPos(precision - 1) == 0)
|
||||
return "Most significant digit is zero in long mode";
|
||||
if (precision != 0 && getDigitPos(0) == 0)
|
||||
return "Least significant digit is zero in long mode";
|
||||
for (int i = 0; i < precision; i++) {
|
||||
if (getDigitPos(i) >= 10)
|
||||
return "Digit exceeding 10 in long";
|
||||
if (getDigitPos(i) < 0)
|
||||
return "Digit below 0 in long (?!)";
|
||||
}
|
||||
for (int i = precision; i < 16; i++) {
|
||||
if (getDigitPos(i) != 0)
|
||||
return "Nonzero digits outside of range in long";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sb.append(Long.toHexString(bcdLong));
|
||||
}
|
||||
sb.append("E");
|
||||
sb.append(scale);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this {@link DecimalQuantity_DualStorageBCD} is using its internal byte array
|
||||
* storage mechanism.
|
||||
*
|
||||
* @return true if an internal byte array is being used; false if a long is being used.
|
||||
* @internal
|
||||
* @deprecated This API is ICU internal only.
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean isUsingBytes() {
|
||||
return usingBytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("<DecimalQuantity %s:%d:%d:%s %s %s>",
|
||||
(lOptPos > 1000 ? "999" : String.valueOf(lOptPos)),
|
||||
lReqPos,
|
||||
rReqPos,
|
||||
(rOptPos < -1000 ? "-999" : String.valueOf(rOptPos)),
|
||||
(usingBytes ? "bytes" : "long"),
|
||||
toNumberString());
|
||||
}
|
||||
|
||||
public String toNumberString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (usingBytes) {
|
||||
for (int i = precision - 1; i >= 0; i--) {
|
||||
sb.append(bcdBytes[i]);
|
||||
}
|
||||
} else {
|
||||
sb.append(Long.toHexString(bcdLong));
|
||||
}
|
||||
sb.append("E");
|
||||
sb.append(scale);
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ package com.ibm.icu.impl.number;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import java.util.MissingResourceException;
|
||||
|
||||
import com.ibm.icu.impl.CurrencyData;
|
||||
import com.ibm.icu.impl.ICUData;
|
||||
@ -22,40 +23,70 @@ import com.ibm.icu.util.UResourceBundle;
|
||||
|
||||
public class LongNameHandler implements MicroPropsGenerator {
|
||||
|
||||
private static final int DNAM_INDEX = StandardPlural.COUNT;
|
||||
private static final int PER_INDEX = StandardPlural.COUNT + 1;
|
||||
private static final int ARRAY_LENGTH = StandardPlural.COUNT + 2;
|
||||
|
||||
private static int getIndex(String pluralKeyword) {
|
||||
// pluralKeyword can also be "dnam" or "per"
|
||||
if (pluralKeyword.equals("dnam")) {
|
||||
return DNAM_INDEX;
|
||||
} else if (pluralKeyword.equals("per")) {
|
||||
return PER_INDEX;
|
||||
} else {
|
||||
return StandardPlural.fromString(pluralKeyword).ordinal();
|
||||
}
|
||||
}
|
||||
|
||||
private static String getWithPlural(String[] strings, StandardPlural plural) {
|
||||
String result = strings[plural.ordinal()];
|
||||
if (result == null) {
|
||||
result = strings[StandardPlural.OTHER.ordinal()];
|
||||
}
|
||||
if (result == null) {
|
||||
// There should always be data in the "other" plural variant.
|
||||
throw new ICUException("Could not find data in 'other' plural variant");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
//////////////////////////
|
||||
/// BEGIN DATA LOADING ///
|
||||
//////////////////////////
|
||||
|
||||
private static final class PluralTableSink extends UResource.Sink {
|
||||
|
||||
Map<StandardPlural, String> output;
|
||||
String[] outArray;
|
||||
|
||||
public PluralTableSink(Map<StandardPlural, String> output) {
|
||||
this.output = output;
|
||||
public PluralTableSink(String[] outArray) {
|
||||
this.outArray = outArray;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
|
||||
UResource.Table pluralsTable = value.getTable();
|
||||
for (int i = 0; pluralsTable.getKeyAndValue(i, key, value); ++i) {
|
||||
if (key.contentEquals("dnam") || key.contentEquals("per")) {
|
||||
continue;
|
||||
}
|
||||
StandardPlural plural = StandardPlural.fromString(key);
|
||||
if (output.containsKey(plural)) {
|
||||
int index = getIndex(key.toString());
|
||||
if (outArray[index] != null) {
|
||||
continue;
|
||||
}
|
||||
String formatString = value.getString();
|
||||
output.put(plural, formatString);
|
||||
outArray[index] = formatString;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void getMeasureData(ULocale locale, MeasureUnit unit, UnitWidth width,
|
||||
Map<StandardPlural, String> output) {
|
||||
PluralTableSink sink = new PluralTableSink(output);
|
||||
// NOTE: outArray MUST have at least ARRAY_LENGTH entries. No bounds checking is performed.
|
||||
|
||||
private static void getMeasureData(
|
||||
ULocale locale,
|
||||
MeasureUnit unit,
|
||||
UnitWidth width,
|
||||
String[] outArray) {
|
||||
PluralTableSink sink = new PluralTableSink(outArray);
|
||||
ICUResourceBundle resource;
|
||||
resource = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_UNIT_BASE_NAME, locale);
|
||||
resource = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_UNIT_BASE_NAME,
|
||||
locale);
|
||||
StringBuilder key = new StringBuilder();
|
||||
key.append("units");
|
||||
if (width == UnitWidth.NARROW) {
|
||||
@ -67,24 +98,49 @@ public class LongNameHandler implements MicroPropsGenerator {
|
||||
key.append(unit.getType());
|
||||
key.append("/");
|
||||
key.append(unit.getSubtype());
|
||||
resource.getAllItemsWithFallback(key.toString(), sink);
|
||||
try {
|
||||
resource.getAllItemsWithFallback(key.toString(), sink);
|
||||
} catch (MissingResourceException e) {
|
||||
throw new IllegalArgumentException("No data for unit " + unit + ", width " + width, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void getCurrencyLongNameData(ULocale locale, Currency currency, Map<StandardPlural, String> output) {
|
||||
private static void getCurrencyLongNameData(ULocale locale, Currency currency, String[] outArray) {
|
||||
// In ICU4J, this method gets a CurrencyData from CurrencyData.provider.
|
||||
// TODO(ICU4J): Implement this without going through CurrencyData, like in ICU4C?
|
||||
Map<String, String> data = CurrencyData.provider.getInstance(locale, true).getUnitPatterns();
|
||||
for (Map.Entry<String, String> e : data.entrySet()) {
|
||||
String pluralKeyword = e.getKey();
|
||||
StandardPlural plural = StandardPlural.fromString(e.getKey());
|
||||
int index = getIndex(pluralKeyword);
|
||||
String longName = currency.getName(locale, Currency.PLURAL_LONG_NAME, pluralKeyword, null);
|
||||
String simpleFormat = e.getValue();
|
||||
// Example pattern from data: "{0} {1}"
|
||||
// Example output after find-and-replace: "{0} US dollars"
|
||||
simpleFormat = simpleFormat.replace("{1}", longName);
|
||||
// String compiled = SimpleFormatterImpl.compileToStringMinMaxArguments(simpleFormat, sb, 1, 1);
|
||||
// String compiled = SimpleFormatterImpl.compileToStringMinMaxArguments(simpleFormat, sb, 1,
|
||||
// 1);
|
||||
// SimpleModifier mod = new SimpleModifier(compiled, Field.CURRENCY, false);
|
||||
output.put(plural, simpleFormat);
|
||||
outArray[index] = simpleFormat;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getPerUnitFormat(ULocale locale, UnitWidth width) {
|
||||
ICUResourceBundle resource;
|
||||
resource = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_UNIT_BASE_NAME,
|
||||
locale);
|
||||
StringBuilder key = new StringBuilder();
|
||||
key.append("units");
|
||||
if (width == UnitWidth.NARROW) {
|
||||
key.append("Narrow");
|
||||
} else if (width == UnitWidth.SHORT) {
|
||||
key.append("Short");
|
||||
}
|
||||
key.append("/compound/per");
|
||||
try {
|
||||
return resource.getStringWithFallback(key.toString());
|
||||
} catch (MissingResourceException e) {
|
||||
throw new IllegalArgumentException(
|
||||
"Could not find x-per-y format for " + locale + ", width " + width);
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,16 +152,21 @@ public class LongNameHandler implements MicroPropsGenerator {
|
||||
private final PluralRules rules;
|
||||
private final MicroPropsGenerator parent;
|
||||
|
||||
private LongNameHandler(Map<StandardPlural, SimpleModifier> modifiers, PluralRules rules,
|
||||
private LongNameHandler(
|
||||
Map<StandardPlural, SimpleModifier> modifiers,
|
||||
PluralRules rules,
|
||||
MicroPropsGenerator parent) {
|
||||
this.modifiers = modifiers;
|
||||
this.rules = rules;
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public static LongNameHandler forCurrencyLongNames(ULocale locale, Currency currency, PluralRules rules,
|
||||
public static LongNameHandler forCurrencyLongNames(
|
||||
ULocale locale,
|
||||
Currency currency,
|
||||
PluralRules rules,
|
||||
MicroPropsGenerator parent) {
|
||||
Map<StandardPlural, String> simpleFormats = new EnumMap<StandardPlural, String>(StandardPlural.class);
|
||||
String[] simpleFormats = new String[ARRAY_LENGTH];
|
||||
getCurrencyLongNameData(locale, currency, simpleFormats);
|
||||
// TODO(ICU4J): Reduce the number of object creations here?
|
||||
Map<StandardPlural, SimpleModifier> modifiers = new EnumMap<StandardPlural, SimpleModifier>(
|
||||
@ -114,9 +175,25 @@ public class LongNameHandler implements MicroPropsGenerator {
|
||||
return new LongNameHandler(modifiers, rules, parent);
|
||||
}
|
||||
|
||||
public static LongNameHandler forMeasureUnit(ULocale locale, MeasureUnit unit, UnitWidth width, PluralRules rules,
|
||||
public static LongNameHandler forMeasureUnit(
|
||||
ULocale locale,
|
||||
MeasureUnit unit,
|
||||
MeasureUnit perUnit,
|
||||
UnitWidth width,
|
||||
PluralRules rules,
|
||||
MicroPropsGenerator parent) {
|
||||
Map<StandardPlural, String> simpleFormats = new EnumMap<StandardPlural, String>(StandardPlural.class);
|
||||
if (perUnit != null) {
|
||||
// Compound unit: first try to simplify (e.g., meters per second is its own unit).
|
||||
MeasureUnit simplified = MeasureUnit.resolveUnitPerUnit(unit, perUnit);
|
||||
if (simplified != null) {
|
||||
unit = simplified;
|
||||
} else {
|
||||
// No simplified form is available.
|
||||
return forCompoundUnit(locale, unit, perUnit, width, rules, parent);
|
||||
}
|
||||
}
|
||||
|
||||
String[] simpleFormats = new String[ARRAY_LENGTH];
|
||||
getMeasureData(locale, unit, width, simpleFormats);
|
||||
// TODO: What field to use for units?
|
||||
// TODO(ICU4J): Reduce the number of object creations here?
|
||||
@ -126,20 +203,66 @@ public class LongNameHandler implements MicroPropsGenerator {
|
||||
return new LongNameHandler(modifiers, rules, parent);
|
||||
}
|
||||
|
||||
private static void simpleFormatsToModifiers(Map<StandardPlural, String> simpleFormats, NumberFormat.Field field,
|
||||
private static LongNameHandler forCompoundUnit(
|
||||
ULocale locale,
|
||||
MeasureUnit unit,
|
||||
MeasureUnit perUnit,
|
||||
UnitWidth width,
|
||||
PluralRules rules,
|
||||
MicroPropsGenerator parent) {
|
||||
String[] primaryData = new String[ARRAY_LENGTH];
|
||||
getMeasureData(locale, unit, width, primaryData);
|
||||
String[] secondaryData = new String[ARRAY_LENGTH];
|
||||
getMeasureData(locale, perUnit, width, secondaryData);
|
||||
String perUnitFormat;
|
||||
if (secondaryData[PER_INDEX] != null) {
|
||||
perUnitFormat = secondaryData[PER_INDEX];
|
||||
} else {
|
||||
String rawPerUnitFormat = getPerUnitFormat(locale, width);
|
||||
// rawPerUnitFormat is something like "{0}/{1}"; we need to substitute in the secondary unit.
|
||||
// TODO: Lots of thrashing. Improve?
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String compiled = SimpleFormatterImpl
|
||||
.compileToStringMinMaxArguments(rawPerUnitFormat, sb, 2, 2);
|
||||
String secondaryFormat = getWithPlural(secondaryData, StandardPlural.ONE);
|
||||
String secondaryCompiled = SimpleFormatterImpl
|
||||
.compileToStringMinMaxArguments(secondaryFormat, sb, 1, 1);
|
||||
String secondaryString = SimpleFormatterImpl.getTextWithNoArguments(secondaryCompiled)
|
||||
.trim();
|
||||
perUnitFormat = SimpleFormatterImpl.formatCompiledPattern(compiled, "{0}", secondaryString);
|
||||
}
|
||||
// TODO: What field to use for units?
|
||||
Map<StandardPlural, SimpleModifier> modifiers = new EnumMap<StandardPlural, SimpleModifier>(
|
||||
StandardPlural.class);
|
||||
multiSimpleFormatsToModifiers(primaryData, perUnitFormat, null, modifiers);
|
||||
return new LongNameHandler(modifiers, rules, parent);
|
||||
}
|
||||
|
||||
private static void simpleFormatsToModifiers(
|
||||
String[] simpleFormats,
|
||||
NumberFormat.Field field,
|
||||
Map<StandardPlural, SimpleModifier> output) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (StandardPlural plural : StandardPlural.VALUES) {
|
||||
String simpleFormat = simpleFormats.get(plural);
|
||||
if (simpleFormat == null) {
|
||||
simpleFormat = simpleFormats.get(StandardPlural.OTHER);
|
||||
}
|
||||
if (simpleFormat == null) {
|
||||
// There should always be data in the "other" plural variant.
|
||||
throw new ICUException("Could not find data in 'other' plural variant with field " + field);
|
||||
}
|
||||
String simpleFormat = getWithPlural(simpleFormats, plural);
|
||||
String compiled = SimpleFormatterImpl.compileToStringMinMaxArguments(simpleFormat, sb, 1, 1);
|
||||
output.put(plural, new SimpleModifier(compiled, null, false));
|
||||
output.put(plural, new SimpleModifier(compiled, field, false));
|
||||
}
|
||||
}
|
||||
|
||||
private static void multiSimpleFormatsToModifiers(
|
||||
String[] leadFormats,
|
||||
String trailFormat,
|
||||
NumberFormat.Field field,
|
||||
Map<StandardPlural, SimpleModifier> output) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String trailCompiled = SimpleFormatterImpl.compileToStringMinMaxArguments(trailFormat, sb, 1, 1);
|
||||
for (StandardPlural plural : StandardPlural.VALUES) {
|
||||
String leadFormat = getWithPlural(leadFormats, plural);
|
||||
String compoundFormat = SimpleFormatterImpl.formatCompiledPattern(trailCompiled, leadFormat);
|
||||
String compoundCompiled = SimpleFormatterImpl
|
||||
.compileToStringMinMaxArguments(compoundFormat, sb, 1, 1);
|
||||
output.put(plural, new SimpleModifier(compoundCompiled, field, false));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,92 +15,114 @@ import com.ibm.icu.util.MeasureUnit;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
|
||||
public class MacroProps implements Cloneable {
|
||||
public Notation notation;
|
||||
public MeasureUnit unit;
|
||||
public Rounder rounder;
|
||||
public Grouper grouper;
|
||||
public Padder padder;
|
||||
public IntegerWidth integerWidth;
|
||||
public Object symbols;
|
||||
public UnitWidth unitWidth;
|
||||
public SignDisplay sign;
|
||||
public DecimalSeparatorDisplay decimal;
|
||||
public AffixPatternProvider affixProvider; // not in API; for JDK compatibility mode only
|
||||
public MultiplierImpl multiplier; // not in API; for JDK compatibility mode only
|
||||
public PluralRules rules; // not in API; could be made public in the future
|
||||
public Long threshold; // not in API; controls internal self-regulation threshold
|
||||
public ULocale loc;
|
||||
public Notation notation;
|
||||
public MeasureUnit unit;
|
||||
public MeasureUnit perUnit;
|
||||
public Rounder rounder;
|
||||
public Grouper grouper;
|
||||
public Padder padder;
|
||||
public IntegerWidth integerWidth;
|
||||
public Object symbols;
|
||||
public UnitWidth unitWidth;
|
||||
public SignDisplay sign;
|
||||
public DecimalSeparatorDisplay decimal;
|
||||
public AffixPatternProvider affixProvider; // not in API; for JDK compatibility mode only
|
||||
public MultiplierImpl multiplier; // not in API; for JDK compatibility mode only
|
||||
public PluralRules rules; // not in API; could be made public in the future
|
||||
public Long threshold; // not in API; controls internal self-regulation threshold
|
||||
public ULocale loc;
|
||||
|
||||
/**
|
||||
* Copies values from fallback into this instance if they are null in this instance.
|
||||
*
|
||||
* @param fallback The instance to copy from; not modified by this operation.
|
||||
*/
|
||||
public void fallback(MacroProps fallback) {
|
||||
if (notation == null) notation = fallback.notation;
|
||||
if (unit == null) unit = fallback.unit;
|
||||
if (rounder == null) rounder = fallback.rounder;
|
||||
if (grouper == null) grouper = fallback.grouper;
|
||||
if (padder == null) padder = fallback.padder;
|
||||
if (integerWidth == null) integerWidth = fallback.integerWidth;
|
||||
if (symbols == null) symbols = fallback.symbols;
|
||||
if (unitWidth == null) unitWidth = fallback.unitWidth;
|
||||
if (sign == null) sign = fallback.sign;
|
||||
if (decimal == null) decimal = fallback.decimal;
|
||||
if (affixProvider == null) affixProvider = fallback.affixProvider;
|
||||
if (multiplier == null) multiplier = fallback.multiplier;
|
||||
if (rules == null) rules = fallback.rules;
|
||||
if (loc == null) loc = fallback.loc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Utility.hash(
|
||||
notation,
|
||||
unit,
|
||||
rounder,
|
||||
grouper,
|
||||
padder,
|
||||
integerWidth,
|
||||
symbols,
|
||||
unitWidth,
|
||||
sign,
|
||||
decimal,
|
||||
affixProvider,
|
||||
multiplier,
|
||||
rules,
|
||||
loc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object _other) {
|
||||
if (_other == null) return false;
|
||||
if (this == _other) return true;
|
||||
if (!(_other instanceof MacroProps)) return false;
|
||||
MacroProps other = (MacroProps) _other;
|
||||
return Utility.equals(notation, other.notation)
|
||||
&& Utility.equals(unit, other.unit)
|
||||
&& Utility.equals(rounder, other.rounder)
|
||||
&& Utility.equals(grouper, other.grouper)
|
||||
&& Utility.equals(padder, other.padder)
|
||||
&& Utility.equals(integerWidth, other.integerWidth)
|
||||
&& Utility.equals(symbols, other.symbols)
|
||||
&& Utility.equals(unitWidth, other.unitWidth)
|
||||
&& Utility.equals(sign, other.sign)
|
||||
&& Utility.equals(decimal, other.decimal)
|
||||
&& Utility.equals(affixProvider, other.affixProvider)
|
||||
&& Utility.equals(multiplier, other.multiplier)
|
||||
&& Utility.equals(rules, other.rules)
|
||||
&& Utility.equals(loc, other.loc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone() {
|
||||
// TODO: Remove this method?
|
||||
try {
|
||||
return super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new AssertionError(e);
|
||||
/**
|
||||
* Copies values from fallback into this instance if they are null in this instance.
|
||||
*
|
||||
* @param fallback
|
||||
* The instance to copy from; not modified by this operation.
|
||||
*/
|
||||
public void fallback(MacroProps fallback) {
|
||||
if (notation == null)
|
||||
notation = fallback.notation;
|
||||
if (unit == null)
|
||||
unit = fallback.unit;
|
||||
if (perUnit == null)
|
||||
perUnit = fallback.perUnit;
|
||||
if (rounder == null)
|
||||
rounder = fallback.rounder;
|
||||
if (grouper == null)
|
||||
grouper = fallback.grouper;
|
||||
if (padder == null)
|
||||
padder = fallback.padder;
|
||||
if (integerWidth == null)
|
||||
integerWidth = fallback.integerWidth;
|
||||
if (symbols == null)
|
||||
symbols = fallback.symbols;
|
||||
if (unitWidth == null)
|
||||
unitWidth = fallback.unitWidth;
|
||||
if (sign == null)
|
||||
sign = fallback.sign;
|
||||
if (decimal == null)
|
||||
decimal = fallback.decimal;
|
||||
if (affixProvider == null)
|
||||
affixProvider = fallback.affixProvider;
|
||||
if (multiplier == null)
|
||||
multiplier = fallback.multiplier;
|
||||
if (rules == null)
|
||||
rules = fallback.rules;
|
||||
if (loc == null)
|
||||
loc = fallback.loc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Utility.hash(notation,
|
||||
unit,
|
||||
perUnit,
|
||||
rounder,
|
||||
grouper,
|
||||
padder,
|
||||
integerWidth,
|
||||
symbols,
|
||||
unitWidth,
|
||||
sign,
|
||||
decimal,
|
||||
affixProvider,
|
||||
multiplier,
|
||||
rules,
|
||||
loc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object _other) {
|
||||
if (_other == null)
|
||||
return false;
|
||||
if (this == _other)
|
||||
return true;
|
||||
if (!(_other instanceof MacroProps))
|
||||
return false;
|
||||
MacroProps other = (MacroProps) _other;
|
||||
return Utility.equals(notation, other.notation)
|
||||
&& Utility.equals(unit, other.unit)
|
||||
&& Utility.equals(perUnit, other.perUnit)
|
||||
&& Utility.equals(rounder, other.rounder)
|
||||
&& Utility.equals(grouper, other.grouper)
|
||||
&& Utility.equals(padder, other.padder)
|
||||
&& Utility.equals(integerWidth, other.integerWidth)
|
||||
&& Utility.equals(symbols, other.symbols)
|
||||
&& Utility.equals(unitWidth, other.unitWidth)
|
||||
&& Utility.equals(sign, other.sign)
|
||||
&& Utility.equals(decimal, other.decimal)
|
||||
&& Utility.equals(affixProvider, other.affixProvider)
|
||||
&& Utility.equals(multiplier, other.multiplier)
|
||||
&& Utility.equals(rules, other.rules)
|
||||
&& Utility.equals(loc, other.loc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone() {
|
||||
// TODO: Remove this method?
|
||||
try {
|
||||
return super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,8 +31,8 @@ public class MicroProps implements Cloneable, MicroPropsGenerator {
|
||||
|
||||
/**
|
||||
* @param immutable
|
||||
* Whether this MicroProps should behave as an immutable after construction with respect to the quantity
|
||||
* chain.
|
||||
* Whether this MicroProps should behave as an immutable after construction with respect
|
||||
* to the quantity chain.
|
||||
*/
|
||||
public MicroProps(boolean immutable) {
|
||||
this.immutable = immutable;
|
||||
|
@ -3,19 +3,21 @@
|
||||
package com.ibm.icu.impl.number;
|
||||
|
||||
/**
|
||||
* This interface is used when all number formatting settings, including the locale, are known, except for the quantity
|
||||
* itself. The {@link #processQuantity} method performs the final step in the number processing pipeline: it uses the
|
||||
* quantity to generate a finalized {@link MicroProps}, which can be used to render the number to output.
|
||||
* This interface is used when all number formatting settings, including the locale, are known, except
|
||||
* for the quantity itself. The {@link #processQuantity} method performs the final step in the number
|
||||
* processing pipeline: it uses the quantity to generate a finalized {@link MicroProps}, which can be
|
||||
* used to render the number to output.
|
||||
*
|
||||
* <p>
|
||||
* In other words, this interface is used for the parts of number processing that are <em>quantity-dependent</em>.
|
||||
* In other words, this interface is used for the parts of number processing that are
|
||||
* <em>quantity-dependent</em>.
|
||||
*
|
||||
* <p>
|
||||
* In order to allow for multiple different objects to all mutate the same MicroProps, a "chain" of MicroPropsGenerators
|
||||
* are linked together, and each one is responsible for manipulating a certain quantity-dependent part of the
|
||||
* MicroProps. At the top of the linked list is a base instance of {@link MicroProps} with properties that are not
|
||||
* quantity-dependent. Each element in the linked list calls {@link #processQuantity} on its "parent", then does its
|
||||
* work, and then returns the result.
|
||||
* In order to allow for multiple different objects to all mutate the same MicroProps, a "chain" of
|
||||
* MicroPropsGenerators are linked together, and each one is responsible for manipulating a certain
|
||||
* quantity-dependent part of the MicroProps. At the top of the linked list is a base instance of
|
||||
* {@link MicroProps} with properties that are not quantity-dependent. Each element in the linked list
|
||||
* calls {@link #processQuantity} on its "parent", then does its work, and then returns the result.
|
||||
*
|
||||
* <p>
|
||||
* A class implementing MicroPropsGenerator looks something like this:
|
||||
@ -42,7 +44,8 @@ package com.ibm.icu.impl.number;
|
||||
*/
|
||||
public interface MicroPropsGenerator {
|
||||
/**
|
||||
* Considers the given {@link DecimalQuantity}, optionally mutates it, and returns a {@link MicroProps}.
|
||||
* Considers the given {@link DecimalQuantity}, optionally mutates it, and returns a
|
||||
* {@link MicroProps}.
|
||||
*
|
||||
* @param quantity
|
||||
* The quantity for consideration and optional mutation.
|
||||
|
@ -3,12 +3,12 @@
|
||||
package com.ibm.icu.impl.number;
|
||||
|
||||
/**
|
||||
* A Modifier is an object that can be passed through the formatting pipeline until it is finally applied to the string
|
||||
* builder. A Modifier usually contains a prefix and a suffix that are applied, but it could contain something else,
|
||||
* like a {@link com.ibm.icu.text.SimpleFormatter} pattern.
|
||||
* A Modifier is an object that can be passed through the formatting pipeline until it is finally applied
|
||||
* to the string builder. A Modifier usually contains a prefix and a suffix that are applied, but it
|
||||
* could contain something else, like a {@link com.ibm.icu.text.SimpleFormatter} pattern.
|
||||
*
|
||||
* A Modifier is usually immutable, except in cases such as {@link MutablePatternModifier}, which are mutable for performance
|
||||
* reasons.
|
||||
* A Modifier is usually immutable, except in cases such as {@link MutablePatternModifier}, which are
|
||||
* mutable for performance reasons.
|
||||
*/
|
||||
public interface Modifier {
|
||||
|
||||
@ -18,17 +18,18 @@ public interface Modifier {
|
||||
* @param output
|
||||
* The string builder to which to apply this modifier.
|
||||
* @param leftIndex
|
||||
* The left index of the string within the builder. Equal to 0 when only one number is being formatted.
|
||||
* The left index of the string within the builder. Equal to 0 when only one number is
|
||||
* being formatted.
|
||||
* @param rightIndex
|
||||
* The right index of the string within the string builder. Equal to length when only one number is being
|
||||
* formatted.
|
||||
* The right index of the string within the string builder. Equal to length when only one
|
||||
* number is being formatted.
|
||||
* @return The number of characters (UTF-16 code units) that were added to the string builder.
|
||||
*/
|
||||
public int apply(NumberStringBuilder output, int leftIndex, int rightIndex);
|
||||
|
||||
/**
|
||||
* Gets the length of the prefix. This information can be used in combination with {@link #apply} to extract the
|
||||
* prefix and suffix strings.
|
||||
* Gets the length of the prefix. This information can be used in combination with {@link #apply} to
|
||||
* extract the prefix and suffix strings.
|
||||
*
|
||||
* @return The number of characters (UTF-16 code units) in the prefix.
|
||||
*/
|
||||
@ -40,9 +41,9 @@ public interface Modifier {
|
||||
public int getCodePointCount();
|
||||
|
||||
/**
|
||||
* Whether this modifier is strong. If a modifier is strong, it should always be applied immediately and not allowed
|
||||
* to bubble up. With regard to padding, strong modifiers are considered to be on the inside of the prefix and
|
||||
* suffix.
|
||||
* Whether this modifier is strong. If a modifier is strong, it should always be applied immediately
|
||||
* and not allowed to bubble up. With regard to padding, strong modifiers are considered to be on the
|
||||
* inside of the prefix and suffix.
|
||||
*
|
||||
* @return Whether the modifier is strong.
|
||||
*/
|
||||
|
@ -5,39 +5,39 @@ package com.ibm.icu.impl.number;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class MultiplierImpl implements MicroPropsGenerator {
|
||||
final int magnitudeMultiplier;
|
||||
final BigDecimal bigDecimalMultiplier;
|
||||
final MicroPropsGenerator parent;
|
||||
final int magnitudeMultiplier;
|
||||
final BigDecimal bigDecimalMultiplier;
|
||||
final MicroPropsGenerator parent;
|
||||
|
||||
public MultiplierImpl(int magnitudeMultiplier) {
|
||||
this.magnitudeMultiplier = magnitudeMultiplier;
|
||||
this.bigDecimalMultiplier = null;
|
||||
parent = null;
|
||||
}
|
||||
|
||||
public MultiplierImpl(BigDecimal bigDecimalMultiplier) {
|
||||
this.magnitudeMultiplier = 0;
|
||||
this.bigDecimalMultiplier = bigDecimalMultiplier;
|
||||
parent = null;
|
||||
}
|
||||
|
||||
private MultiplierImpl(MultiplierImpl base, MicroPropsGenerator parent) {
|
||||
this.magnitudeMultiplier = base.magnitudeMultiplier;
|
||||
this.bigDecimalMultiplier = base.bigDecimalMultiplier;
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public MicroPropsGenerator copyAndChain(MicroPropsGenerator parent) {
|
||||
return new MultiplierImpl(this, parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MicroProps processQuantity(DecimalQuantity quantity) {
|
||||
MicroProps micros = parent.processQuantity(quantity);
|
||||
quantity.adjustMagnitude(magnitudeMultiplier);
|
||||
if (bigDecimalMultiplier != null) {
|
||||
quantity.multiplyBy(bigDecimalMultiplier);
|
||||
public MultiplierImpl(int magnitudeMultiplier) {
|
||||
this.magnitudeMultiplier = magnitudeMultiplier;
|
||||
this.bigDecimalMultiplier = null;
|
||||
parent = null;
|
||||
}
|
||||
|
||||
public MultiplierImpl(BigDecimal bigDecimalMultiplier) {
|
||||
this.magnitudeMultiplier = 0;
|
||||
this.bigDecimalMultiplier = bigDecimalMultiplier;
|
||||
parent = null;
|
||||
}
|
||||
|
||||
private MultiplierImpl(MultiplierImpl base, MicroPropsGenerator parent) {
|
||||
this.magnitudeMultiplier = base.magnitudeMultiplier;
|
||||
this.bigDecimalMultiplier = base.bigDecimalMultiplier;
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public MicroPropsGenerator copyAndChain(MicroPropsGenerator parent) {
|
||||
return new MultiplierImpl(this, parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MicroProps processQuantity(DecimalQuantity quantity) {
|
||||
MicroProps micros = parent.processQuantity(quantity);
|
||||
quantity.adjustMagnitude(magnitudeMultiplier);
|
||||
if (bigDecimalMultiplier != null) {
|
||||
quantity.multiplyBy(bigDecimalMultiplier);
|
||||
}
|
||||
return micros;
|
||||
}
|
||||
return micros;
|
||||
}
|
||||
}
|
||||
|
@ -6,5 +6,14 @@ package com.ibm.icu.impl.number;
|
||||
* An interface used by compact notation and scientific notation to choose a multiplier while rounding.
|
||||
*/
|
||||
public interface MultiplierProducer {
|
||||
/**
|
||||
* Maps a magnitude to a multiplier in powers of ten. For example, in compact notation in English, a
|
||||
* magnitude of 5 (e.g., 100,000) should return a multiplier of -3, since the number is displayed in
|
||||
* thousands.
|
||||
*
|
||||
* @param magnitude
|
||||
* The power of ten of the input number.
|
||||
* @return The shift in powers of ten.
|
||||
*/
|
||||
int getMultiplier(int magnitude);
|
||||
}
|
||||
|
@ -11,25 +11,27 @@ import com.ibm.icu.text.PluralRules;
|
||||
import com.ibm.icu.util.Currency;
|
||||
|
||||
/**
|
||||
* This class is a {@link Modifier} that wraps a decimal format pattern. It applies the pattern's affixes in
|
||||
* {@link Modifier#apply}.
|
||||
* This class is a {@link Modifier} that wraps a decimal format pattern. It applies the pattern's affixes
|
||||
* in {@link Modifier#apply}.
|
||||
*
|
||||
* <p>
|
||||
* In addition to being a Modifier, this class contains the business logic for substituting the correct locale symbols
|
||||
* into the affixes of the decimal format pattern.
|
||||
* In addition to being a Modifier, this class contains the business logic for substituting the correct
|
||||
* locale symbols into the affixes of the decimal format pattern.
|
||||
*
|
||||
* <p>
|
||||
* In order to use this class, create a new instance and call the following four setters: {@link #setPatternInfo},
|
||||
* {@link #setPatternAttributes}, {@link #setSymbols}, and {@link #setNumberProperties}. After calling these four
|
||||
* setters, the instance will be ready for use as a Modifier.
|
||||
* In order to use this class, create a new instance and call the following four setters:
|
||||
* {@link #setPatternInfo}, {@link #setPatternAttributes}, {@link #setSymbols}, and
|
||||
* {@link #setNumberProperties}. After calling these four setters, the instance will be ready for use as
|
||||
* a Modifier.
|
||||
*
|
||||
* <p>
|
||||
* This is a MUTABLE, NON-THREAD-SAFE class designed for performance. Do NOT save references to this or attempt to use
|
||||
* it from multiple threads! Instead, you can obtain a safe, immutable decimal format pattern modifier by calling
|
||||
* {@link MutablePatternModifier#createImmutable}, in effect treating this instance as a builder for the immutable
|
||||
* variant.
|
||||
* This is a MUTABLE, NON-THREAD-SAFE class designed for performance. Do NOT save references to this or
|
||||
* attempt to use it from multiple threads! Instead, you can obtain a safe, immutable decimal format
|
||||
* pattern modifier by calling {@link MutablePatternModifier#createImmutable}, in effect treating this
|
||||
* instance as a builder for the immutable variant.
|
||||
*/
|
||||
public class MutablePatternModifier implements Modifier, SymbolProvider, CharSequence, MicroPropsGenerator {
|
||||
public class MutablePatternModifier
|
||||
implements Modifier, SymbolProvider, CharSequence, MicroPropsGenerator {
|
||||
|
||||
// Modifier details
|
||||
final boolean isStrong;
|
||||
@ -62,8 +64,8 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq
|
||||
/**
|
||||
* @param isStrong
|
||||
* Whether the modifier should be considered strong. For more information, see
|
||||
* {@link Modifier#isStrong()}. Most of the time, decimal format pattern modifiers should be considered
|
||||
* as non-strong.
|
||||
* {@link Modifier#isStrong()}. Most of the time, decimal format pattern modifiers should
|
||||
* be considered as non-strong.
|
||||
*/
|
||||
public MutablePatternModifier(boolean isStrong) {
|
||||
this.isStrong = isStrong;
|
||||
@ -71,8 +73,8 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq
|
||||
|
||||
/**
|
||||
* Sets a reference to the parsed decimal format pattern, usually obtained from
|
||||
* {@link PatternStringParser#parseToPatternInfo(String)}, but any implementation of {@link AffixPatternProvider} is
|
||||
* accepted.
|
||||
* {@link PatternStringParser#parseToPatternInfo(String)}, but any implementation of
|
||||
* {@link AffixPatternProvider} is accepted.
|
||||
*/
|
||||
public void setPatternInfo(AffixPatternProvider patternInfo) {
|
||||
this.patternInfo = patternInfo;
|
||||
@ -101,10 +103,14 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq
|
||||
* @param unitWidth
|
||||
* The width used to render currencies.
|
||||
* @param rules
|
||||
* Required if the triple currency sign, "¤¤¤", appears in the pattern, which can be determined from the
|
||||
* convenience method {@link #needsPlurals()}.
|
||||
* Required if the triple currency sign, "¤¤¤", appears in the pattern, which can be
|
||||
* determined from the convenience method {@link #needsPlurals()}.
|
||||
*/
|
||||
public void setSymbols(DecimalFormatSymbols symbols, Currency currency, UnitWidth unitWidth, PluralRules rules) {
|
||||
public void setSymbols(
|
||||
DecimalFormatSymbols symbols,
|
||||
Currency currency,
|
||||
UnitWidth unitWidth,
|
||||
PluralRules rules) {
|
||||
//assert (rules != null) == needsPlurals();
|
||||
this.symbols = symbols;
|
||||
this.currency = currency;
|
||||
@ -118,8 +124,8 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq
|
||||
* @param isNegative
|
||||
* Whether the number is negative.
|
||||
* @param plural
|
||||
* The plural form of the number, required only if the pattern contains the triple currency sign, "¤¤¤"
|
||||
* (and as indicated by {@link #needsPlurals()}).
|
||||
* The plural form of the number, required only if the pattern contains the triple
|
||||
* currency sign, "¤¤¤" (and as indicated by {@link #needsPlurals()}).
|
||||
*/
|
||||
public void setNumberProperties(boolean isNegative, StandardPlural plural) {
|
||||
assert (plural != null) == needsPlurals();
|
||||
@ -128,17 +134,18 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the pattern represented by this MurkyModifier requires a plural keyword in order to localize.
|
||||
* This is currently true only if there is a currency long name placeholder in the pattern ("¤¤¤").
|
||||
* Returns true if the pattern represented by this MurkyModifier requires a plural keyword in order
|
||||
* to localize. This is currently true only if there is a currency long name placeholder in the
|
||||
* pattern ("¤¤¤").
|
||||
*/
|
||||
public boolean needsPlurals() {
|
||||
return patternInfo.containsSymbolType(AffixUtils.TYPE_CURRENCY_TRIPLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new quantity-dependent Modifier that behaves the same as the current instance, but which is immutable
|
||||
* and can be saved for future use. The number properties in the current instance are mutated; all other properties
|
||||
* are left untouched.
|
||||
* Creates a new quantity-dependent Modifier that behaves the same as the current instance, but which
|
||||
* is immutable and can be saved for future use. The number properties in the current instance are
|
||||
* mutated; all other properties are left untouched.
|
||||
*
|
||||
* <p>
|
||||
* The resulting modifier cannot be used in a QuantityChain.
|
||||
@ -150,9 +157,9 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new quantity-dependent Modifier that behaves the same as the current instance, but which is immutable
|
||||
* and can be saved for future use. The number properties in the current instance are mutated; all other properties
|
||||
* are left untouched.
|
||||
* Creates a new quantity-dependent Modifier that behaves the same as the current instance, but which
|
||||
* is immutable and can be saved for future use. The number properties in the current instance are
|
||||
* mutated; all other properties are left untouched.
|
||||
*
|
||||
* @param parent
|
||||
* The QuantityChain to which to chain this immutable.
|
||||
@ -184,17 +191,19 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the current properties to create a single {@link ConstantMultiFieldModifier} with currency spacing support
|
||||
* if required.
|
||||
* Uses the current properties to create a single {@link ConstantMultiFieldModifier} with currency
|
||||
* spacing support if required.
|
||||
*
|
||||
* @param a
|
||||
* A working NumberStringBuilder object; passed from the outside to prevent the need to create many new
|
||||
* instances if this method is called in a loop.
|
||||
* A working NumberStringBuilder object; passed from the outside to prevent the need to
|
||||
* create many new instances if this method is called in a loop.
|
||||
* @param b
|
||||
* Another working NumberStringBuilder object.
|
||||
* @return The constant modifier object.
|
||||
*/
|
||||
private ConstantMultiFieldModifier createConstantModifier(NumberStringBuilder a, NumberStringBuilder b) {
|
||||
private ConstantMultiFieldModifier createConstantModifier(
|
||||
NumberStringBuilder a,
|
||||
NumberStringBuilder b) {
|
||||
insertPrefix(a.clear(), 0);
|
||||
insertSuffix(b.clear(), 0);
|
||||
if (patternInfo.hasCurrencySign()) {
|
||||
@ -209,7 +218,10 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq
|
||||
final PluralRules rules;
|
||||
final MicroPropsGenerator parent;
|
||||
|
||||
ImmutablePatternModifier(ParameterizedModifier pm, PluralRules rules, MicroPropsGenerator parent) {
|
||||
ImmutablePatternModifier(
|
||||
ParameterizedModifier pm,
|
||||
PluralRules rules,
|
||||
MicroPropsGenerator parent) {
|
||||
this.pm = pm;
|
||||
this.rules = rules;
|
||||
this.parent = parent;
|
||||
@ -260,8 +272,12 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq
|
||||
public int apply(NumberStringBuilder output, int leftIndex, int rightIndex) {
|
||||
int prefixLen = insertPrefix(output, leftIndex);
|
||||
int suffixLen = insertSuffix(output, rightIndex + prefixLen);
|
||||
CurrencySpacingEnabledModifier.applyCurrencySpacing(output, leftIndex, prefixLen, rightIndex + prefixLen,
|
||||
suffixLen, symbols);
|
||||
CurrencySpacingEnabledModifier.applyCurrencySpacing(output,
|
||||
leftIndex,
|
||||
prefixLen,
|
||||
rightIndex + prefixLen,
|
||||
suffixLen,
|
||||
symbols);
|
||||
return prefixLen + suffixLen;
|
||||
}
|
||||
|
||||
@ -269,7 +285,7 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq
|
||||
public int getPrefixLength() {
|
||||
// Enter and exit CharSequence Mode to get the length.
|
||||
enterCharSequenceMode(true);
|
||||
int result = AffixUtils.unescapedCodePointCount(this, this); // prefix length
|
||||
int result = AffixUtils.unescapedCodePointCount(this, this); // prefix length
|
||||
exitCharSequenceMode();
|
||||
return result;
|
||||
}
|
||||
@ -278,10 +294,10 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq
|
||||
public int getCodePointCount() {
|
||||
// Enter and exit CharSequence Mode to get the length.
|
||||
enterCharSequenceMode(true);
|
||||
int result = AffixUtils.unescapedCodePointCount(this, this); // prefix length
|
||||
int result = AffixUtils.unescapedCodePointCount(this, this); // prefix length
|
||||
exitCharSequenceMode();
|
||||
enterCharSequenceMode(false);
|
||||
result += AffixUtils.unescapedCodePointCount(this, this); // suffix length
|
||||
result += AffixUtils.unescapedCodePointCount(this, this); // suffix length
|
||||
exitCharSequenceMode();
|
||||
return result;
|
||||
}
|
||||
@ -337,7 +353,8 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq
|
||||
// Plural currencies set via the API are formatted in LongNameHandler.
|
||||
// This code path is used by DecimalFormat via CurrencyPluralInfo.
|
||||
assert plural != null;
|
||||
return currency.getName(symbols.getULocale(), Currency.PLURAL_LONG_NAME, plural.getKeyword(), null);
|
||||
return currency
|
||||
.getName(symbols.getULocale(), Currency.PLURAL_LONG_NAME, plural.getKeyword(), null);
|
||||
case AffixUtils.TYPE_CURRENCY_QUAD:
|
||||
return "\uFFFD";
|
||||
case AffixUtils.TYPE_CURRENCY_QUINT:
|
||||
@ -357,7 +374,8 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq
|
||||
&& (signDisplay == SignDisplay.ALWAYS || signDisplay == SignDisplay.ACCOUNTING_ALWAYS)
|
||||
&& patternInfo.positiveHasPlusSign() == false;
|
||||
|
||||
// Should we use the affix from the negative subpattern? (If not, we will use the positive subpattern.)
|
||||
// Should we use the affix from the negative subpattern? (If not, we will use the positive
|
||||
// subpattern.)
|
||||
boolean useNegativeAffixPattern = patternInfo.hasNegativeSubpattern()
|
||||
&& (isNegative || (patternInfo.negativeHasMinusSign() && plusReplacesMinusSign));
|
||||
|
||||
|
@ -13,8 +13,8 @@ import com.ibm.icu.text.NumberFormat;
|
||||
import com.ibm.icu.text.NumberFormat.Field;
|
||||
|
||||
/**
|
||||
* A StringBuilder optimized for number formatting. It implements the following key features beyond a normal JDK
|
||||
* StringBuilder:
|
||||
* A StringBuilder optimized for number formatting. It implements the following key features beyond a
|
||||
* normal JDK StringBuilder:
|
||||
*
|
||||
* <ol>
|
||||
* <li>Efficient prepend as well as append.
|
||||
@ -156,8 +156,8 @@ public class NumberStringBuilder implements CharSequence {
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the specified CharSequence at the specified index in the string, reading from the CharSequence from start
|
||||
* (inclusive) to end (exclusive).
|
||||
* Inserts the specified CharSequence at the specified index in the string, reading from the
|
||||
* CharSequence from start (inclusive) to end (exclusive).
|
||||
*
|
||||
* @return The number of chars added, which is the length of CharSequence.
|
||||
*/
|
||||
@ -172,8 +172,8 @@ public class NumberStringBuilder implements CharSequence {
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the chars in the specified char array to the end of the string, and associates them with the fields in
|
||||
* the specified field array, which must have the same length as chars.
|
||||
* Appends the chars in the specified char array to the end of the string, and associates them with
|
||||
* the fields in the specified field array, which must have the same length as chars.
|
||||
*
|
||||
* @return The number of chars added, which is the length of the char array.
|
||||
*/
|
||||
@ -182,8 +182,8 @@ public class NumberStringBuilder implements CharSequence {
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the chars in the specified char array at the specified index in the string, and associates them with the
|
||||
* fields in the specified field array, which must have the same length as chars.
|
||||
* Inserts the chars in the specified char array at the specified index in the string, and associates
|
||||
* them with the fields in the specified field array, which must have the same length as chars.
|
||||
*
|
||||
* @return The number of chars added, which is the length of the char array.
|
||||
*/
|
||||
@ -257,7 +257,8 @@ public class NumberStringBuilder implements CharSequence {
|
||||
}
|
||||
|
||||
private int prepareForInsertHelper(int index, int count) {
|
||||
// Java note: Keeping this code out of prepareForInsert() increases the speed of append operations.
|
||||
// Java note: Keeping this code out of prepareForInsert() increases the speed of append
|
||||
// operations.
|
||||
int oldCapacity = getCapacity();
|
||||
int oldZero = zero;
|
||||
char[] oldChars = chars;
|
||||
@ -272,9 +273,17 @@ public class NumberStringBuilder implements CharSequence {
|
||||
// First copy the prefix and then the suffix, leaving room for the new chars that the
|
||||
// caller wants to insert.
|
||||
System.arraycopy(oldChars, oldZero, newChars, newZero, index);
|
||||
System.arraycopy(oldChars, oldZero + index, newChars, newZero + index + count, length - index);
|
||||
System.arraycopy(oldChars,
|
||||
oldZero + index,
|
||||
newChars,
|
||||
newZero + index + count,
|
||||
length - index);
|
||||
System.arraycopy(oldFields, oldZero, newFields, newZero, index);
|
||||
System.arraycopy(oldFields, oldZero + index, newFields, newZero + index + count, length - index);
|
||||
System.arraycopy(oldFields,
|
||||
oldZero + index,
|
||||
newFields,
|
||||
newZero + index + count,
|
||||
length - index);
|
||||
|
||||
chars = newChars;
|
||||
fields = newFields;
|
||||
@ -286,9 +295,17 @@ public class NumberStringBuilder implements CharSequence {
|
||||
// First copy the entire string to the location of the prefix, and then move the suffix
|
||||
// to make room for the new chars that the caller wants to insert.
|
||||
System.arraycopy(oldChars, oldZero, oldChars, newZero, length);
|
||||
System.arraycopy(oldChars, newZero + index, oldChars, newZero + index + count, length - index);
|
||||
System.arraycopy(oldChars,
|
||||
newZero + index,
|
||||
oldChars,
|
||||
newZero + index + count,
|
||||
length - index);
|
||||
System.arraycopy(oldFields, oldZero, oldFields, newZero, length);
|
||||
System.arraycopy(oldFields, newZero + index, oldFields, newZero + index + count, length - index);
|
||||
System.arraycopy(oldFields,
|
||||
newZero + index,
|
||||
oldFields,
|
||||
newZero + index + count,
|
||||
length - index);
|
||||
|
||||
zero = newZero;
|
||||
length += count;
|
||||
@ -342,8 +359,8 @@ public class NumberStringBuilder implements CharSequence {
|
||||
* Returns a string that includes field information, for debugging purposes.
|
||||
*
|
||||
* <p>
|
||||
* For example, if the string is "-12.345", the debug string will be something like "<NumberStringBuilder
|
||||
* [-123.45] [-iii.ff]>"
|
||||
* For example, if the string is "-12.345", the debug string will be something like
|
||||
* "<NumberStringBuilder [-123.45] [-iii.ff]>"
|
||||
*
|
||||
* @return A string for debugging purposes.
|
||||
*/
|
||||
@ -374,7 +391,8 @@ public class NumberStringBuilder implements CharSequence {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether the contents and field values of this string builder are equal to the given chars and fields.
|
||||
* @return Whether the contents and field values of this string builder are equal to the given chars
|
||||
* and fields.
|
||||
* @see #toCharArray
|
||||
* @see #toFieldArray
|
||||
*/
|
||||
@ -455,7 +473,8 @@ public class NumberStringBuilder implements CharSequence {
|
||||
Field _field = (i < zero + length) ? fields[i] : null;
|
||||
if (seenStart && field != _field) {
|
||||
// Special case: GROUPING_SEPARATOR counts as an INTEGER.
|
||||
if (field == NumberFormat.Field.INTEGER && _field == NumberFormat.Field.GROUPING_SEPARATOR) {
|
||||
if (field == NumberFormat.Field.INTEGER
|
||||
&& _field == NumberFormat.Field.GROUPING_SEPARATOR) {
|
||||
continue;
|
||||
}
|
||||
fp.setEndIndex(i - zero + offset);
|
||||
@ -482,9 +501,13 @@ public class NumberStringBuilder implements CharSequence {
|
||||
int currentStart = -1;
|
||||
for (int i = 0; i < length; i++) {
|
||||
Field field = fields[i + zero];
|
||||
if (current == NumberFormat.Field.INTEGER && field == NumberFormat.Field.GROUPING_SEPARATOR) {
|
||||
if (current == NumberFormat.Field.INTEGER
|
||||
&& field == NumberFormat.Field.GROUPING_SEPARATOR) {
|
||||
// Special case: GROUPING_SEPARATOR counts as an INTEGER.
|
||||
as.addAttribute(NumberFormat.Field.GROUPING_SEPARATOR, NumberFormat.Field.GROUPING_SEPARATOR, i, i + 1);
|
||||
as.addAttribute(NumberFormat.Field.GROUPING_SEPARATOR,
|
||||
NumberFormat.Field.GROUPING_SEPARATOR,
|
||||
i,
|
||||
i + 1);
|
||||
} else if (current != field) {
|
||||
if (current != null) {
|
||||
as.addAttribute(current, current, currentStart, i);
|
||||
|
@ -6,40 +6,37 @@ public class Padder {
|
||||
public static final String FALLBACK_PADDING_STRING = "\u0020"; // i.e. a space
|
||||
|
||||
public enum PadPosition {
|
||||
BEFORE_PREFIX,
|
||||
AFTER_PREFIX,
|
||||
BEFORE_SUFFIX,
|
||||
AFTER_SUFFIX;
|
||||
BEFORE_PREFIX, AFTER_PREFIX, BEFORE_SUFFIX, AFTER_SUFFIX;
|
||||
|
||||
public static PadPosition fromOld(int old) {
|
||||
switch (old) {
|
||||
case com.ibm.icu.text.DecimalFormat.PAD_BEFORE_PREFIX:
|
||||
return PadPosition.BEFORE_PREFIX;
|
||||
case com.ibm.icu.text.DecimalFormat.PAD_AFTER_PREFIX:
|
||||
return PadPosition.AFTER_PREFIX;
|
||||
case com.ibm.icu.text.DecimalFormat.PAD_BEFORE_SUFFIX:
|
||||
return PadPosition.BEFORE_SUFFIX;
|
||||
case com.ibm.icu.text.DecimalFormat.PAD_AFTER_SUFFIX:
|
||||
return PadPosition.AFTER_SUFFIX;
|
||||
default:
|
||||
throw new IllegalArgumentException("Don't know how to map " + old);
|
||||
public static PadPosition fromOld(int old) {
|
||||
switch (old) {
|
||||
case com.ibm.icu.text.DecimalFormat.PAD_BEFORE_PREFIX:
|
||||
return PadPosition.BEFORE_PREFIX;
|
||||
case com.ibm.icu.text.DecimalFormat.PAD_AFTER_PREFIX:
|
||||
return PadPosition.AFTER_PREFIX;
|
||||
case com.ibm.icu.text.DecimalFormat.PAD_BEFORE_SUFFIX:
|
||||
return PadPosition.BEFORE_SUFFIX;
|
||||
case com.ibm.icu.text.DecimalFormat.PAD_AFTER_SUFFIX:
|
||||
return PadPosition.AFTER_SUFFIX;
|
||||
default:
|
||||
throw new IllegalArgumentException("Don't know how to map " + old);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int toOld() {
|
||||
switch (this) {
|
||||
case BEFORE_PREFIX:
|
||||
return com.ibm.icu.text.DecimalFormat.PAD_BEFORE_PREFIX;
|
||||
case AFTER_PREFIX:
|
||||
return com.ibm.icu.text.DecimalFormat.PAD_AFTER_PREFIX;
|
||||
case BEFORE_SUFFIX:
|
||||
return com.ibm.icu.text.DecimalFormat.PAD_BEFORE_SUFFIX;
|
||||
case AFTER_SUFFIX:
|
||||
return com.ibm.icu.text.DecimalFormat.PAD_AFTER_SUFFIX;
|
||||
default:
|
||||
return -1; // silence compiler errors
|
||||
public int toOld() {
|
||||
switch (this) {
|
||||
case BEFORE_PREFIX:
|
||||
return com.ibm.icu.text.DecimalFormat.PAD_BEFORE_PREFIX;
|
||||
case AFTER_PREFIX:
|
||||
return com.ibm.icu.text.DecimalFormat.PAD_AFTER_PREFIX;
|
||||
case BEFORE_SUFFIX:
|
||||
return com.ibm.icu.text.DecimalFormat.PAD_BEFORE_SUFFIX;
|
||||
case AFTER_SUFFIX:
|
||||
return com.ibm.icu.text.DecimalFormat.PAD_AFTER_SUFFIX;
|
||||
default:
|
||||
return -1; // silence compiler errors
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* like package-private */ public static final Padder NONE = new Padder(null, -1, null);
|
||||
@ -73,10 +70,16 @@ public class Padder {
|
||||
return targetWidth > 0;
|
||||
}
|
||||
|
||||
public int padAndApply(Modifier mod1, Modifier mod2, NumberStringBuilder string, int leftIndex, int rightIndex) {
|
||||
public int padAndApply(
|
||||
Modifier mod1,
|
||||
Modifier mod2,
|
||||
NumberStringBuilder string,
|
||||
int leftIndex,
|
||||
int rightIndex) {
|
||||
int modLength = mod1.getCodePointCount() + mod2.getCodePointCount();
|
||||
int requiredPadding = targetWidth - modLength - string.codePointCount();
|
||||
assert leftIndex == 0 && rightIndex == string.length(); // fix the previous line to remove this assertion
|
||||
assert leftIndex == 0 && rightIndex == string.length(); // fix the previous line to remove this
|
||||
// assertion
|
||||
|
||||
int length = 0;
|
||||
if (requiredPadding <= 0) {
|
||||
@ -102,7 +105,10 @@ public class Padder {
|
||||
return length;
|
||||
}
|
||||
|
||||
private static int addPaddingHelper(String paddingString, int requiredPadding, NumberStringBuilder string,
|
||||
private static int addPaddingHelper(
|
||||
String paddingString,
|
||||
int requiredPadding,
|
||||
NumberStringBuilder string,
|
||||
int index) {
|
||||
for (int i = 0; i < requiredPadding; i++) {
|
||||
// TODO: If appending to the end, this will cause actual insertion operations. Improve.
|
||||
|
@ -5,8 +5,8 @@ package com.ibm.icu.impl.number;
|
||||
import com.ibm.icu.impl.StandardPlural;
|
||||
|
||||
/**
|
||||
* A ParameterizedModifier by itself is NOT a Modifier. Rather, it wraps a data structure containing two or more
|
||||
* Modifiers and returns the modifier appropriate for the current situation.
|
||||
* A ParameterizedModifier by itself is NOT a Modifier. Rather, it wraps a data structure containing two
|
||||
* or more Modifiers and returns the modifier appropriate for the current situation.
|
||||
*/
|
||||
public class ParameterizedModifier {
|
||||
private final Modifier positive;
|
||||
@ -28,8 +28,8 @@ public class ParameterizedModifier {
|
||||
}
|
||||
|
||||
/**
|
||||
* This constructor prepares the ParameterizedModifier to be populated with a positive and negative Modifier for
|
||||
* multiple plural forms.
|
||||
* This constructor prepares the ParameterizedModifier to be populated with a positive and negative
|
||||
* Modifier for multiple plural forms.
|
||||
*
|
||||
* <p>
|
||||
* If this constructor is used, a plural form MUST be passed to {@link #getModifier}.
|
||||
|
@ -12,8 +12,8 @@ public class PatternStringParser {
|
||||
public static final int IGNORE_ROUNDING_ALWAYS = 2;
|
||||
|
||||
/**
|
||||
* Runs the recursive descent parser on the given pattern string, returning a data structure with raw information
|
||||
* about the pattern string.
|
||||
* Runs the recursive descent parser on the given pattern string, returning a data structure with raw
|
||||
* information about the pattern string.
|
||||
*
|
||||
* <p>
|
||||
* To obtain a more useful form of the data, consider using {@link #parseToProperties} instead.
|
||||
@ -35,9 +35,10 @@ public class PatternStringParser {
|
||||
* @param pattern
|
||||
* The pattern string, like "#,##0.00"
|
||||
* @param ignoreRounding
|
||||
* Whether to leave out rounding information (minFrac, maxFrac, and rounding increment) when parsing the
|
||||
* pattern. This may be desirable if a custom rounding mode, such as CurrencyUsage, is to be used
|
||||
* instead. One of {@link PatternStringParser#IGNORE_ROUNDING_ALWAYS},
|
||||
* Whether to leave out rounding information (minFrac, maxFrac, and rounding increment)
|
||||
* when parsing the pattern. This may be desirable if a custom rounding mode, such as
|
||||
* CurrencyUsage, is to be used instead. One of
|
||||
* {@link PatternStringParser#IGNORE_ROUNDING_ALWAYS},
|
||||
* {@link PatternStringParser#IGNORE_ROUNDING_IF_CURRENCY}, or
|
||||
* {@link PatternStringParser#IGNORE_ROUNDING_NEVER}.
|
||||
* @return A property bag object.
|
||||
@ -55,9 +56,10 @@ public class PatternStringParser {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a pattern string into an existing property bag. All properties that can be encoded into a pattern string
|
||||
* will be overwritten with either their default value or with the value coming from the pattern string. Properties
|
||||
* that cannot be encoded into a pattern string, such as rounding mode, are not modified.
|
||||
* Parses a pattern string into an existing property bag. All properties that can be encoded into a
|
||||
* pattern string will be overwritten with either their default value or with the value coming from
|
||||
* the pattern string. Properties that cannot be encoded into a pattern string, such as rounding
|
||||
* mode, are not modified.
|
||||
*
|
||||
* @param pattern
|
||||
* The pattern string, like "#,##0.00"
|
||||
@ -68,7 +70,9 @@ public class PatternStringParser {
|
||||
* @throws IllegalArgumentException
|
||||
* If there was a syntax error in the pattern string.
|
||||
*/
|
||||
public static void parseToExistingProperties(String pattern, DecimalFormatProperties properties,
|
||||
public static void parseToExistingProperties(
|
||||
String pattern,
|
||||
DecimalFormatProperties properties,
|
||||
int ignoreRounding) {
|
||||
parseToExistingPropertiesImpl(pattern, properties, ignoreRounding);
|
||||
}
|
||||
@ -263,7 +267,10 @@ public class PatternStringParser {
|
||||
consumePadding(state, result, PadPosition.AFTER_SUFFIX);
|
||||
}
|
||||
|
||||
private static void consumePadding(ParserState state, ParsedSubpatternInfo result, PadPosition paddingLocation) {
|
||||
private static void consumePadding(
|
||||
ParserState state,
|
||||
ParsedSubpatternInfo result,
|
||||
PadPosition paddingLocation) {
|
||||
if (state.peek() != '*') {
|
||||
return;
|
||||
}
|
||||
@ -505,7 +512,10 @@ public class PatternStringParser {
|
||||
/// END RECURSIVE DESCENT PARSER IMPLEMENTATION ///
|
||||
///////////////////////////////////////////////////
|
||||
|
||||
private static void parseToExistingPropertiesImpl(String pattern, DecimalFormatProperties properties, int ignoreRounding) {
|
||||
private static void parseToExistingPropertiesImpl(
|
||||
String pattern,
|
||||
DecimalFormatProperties properties,
|
||||
int ignoreRounding) {
|
||||
if (pattern == null || pattern.length() == 0) {
|
||||
// Backwards compatibility requires that we reset to the default values.
|
||||
// TODO: Only overwrite the properties that "saveToProperties" normally touches?
|
||||
@ -519,7 +529,9 @@ public class PatternStringParser {
|
||||
}
|
||||
|
||||
/** Finalizes the temporary data stored in the ParsedPatternInfo to the Properties. */
|
||||
private static void patternInfoToProperties(DecimalFormatProperties properties, ParsedPatternInfo patternInfo,
|
||||
private static void patternInfoToProperties(
|
||||
DecimalFormatProperties properties,
|
||||
ParsedPatternInfo patternInfo,
|
||||
int _ignoreRounding) {
|
||||
// Translate from PatternParseResult to Properties.
|
||||
// Note that most data from "negative" is ignored per the specification of DecimalFormat.
|
||||
@ -573,12 +585,14 @@ public class PatternStringParser {
|
||||
properties.setMaximumFractionDigits(-1);
|
||||
properties.setRoundingIncrement(null);
|
||||
properties.setMinimumSignificantDigits(positive.integerAtSigns);
|
||||
properties.setMaximumSignificantDigits(positive.integerAtSigns + positive.integerTrailingHashSigns);
|
||||
properties.setMaximumSignificantDigits(
|
||||
positive.integerAtSigns + positive.integerTrailingHashSigns);
|
||||
} else if (positive.rounding != null) {
|
||||
if (!ignoreRounding) {
|
||||
properties.setMinimumFractionDigits(minFrac);
|
||||
properties.setMaximumFractionDigits(positive.fractionTotal);
|
||||
properties.setRoundingIncrement(positive.rounding.toBigDecimal().setScale(positive.fractionNumerals));
|
||||
properties.setRoundingIncrement(
|
||||
positive.rounding.toBigDecimal().setScale(positive.fractionNumerals));
|
||||
} else {
|
||||
properties.setMinimumFractionDigits(-1);
|
||||
properties.setMaximumFractionDigits(-1);
|
||||
@ -634,7 +648,8 @@ public class PatternStringParser {
|
||||
// Padding settings
|
||||
if (positive.paddingLocation != null) {
|
||||
// The width of the positive prefix and suffix templates are included in the padding
|
||||
int paddingWidth = positive.widthExceptAffixes + AffixUtils.estimateLength(posPrefix)
|
||||
int paddingWidth = positive.widthExceptAffixes
|
||||
+ AffixUtils.estimateLength(posPrefix)
|
||||
+ AffixUtils.estimateLength(posSuffix);
|
||||
properties.setFormatWidth(paddingWidth);
|
||||
String rawPaddingString = patternInfo.getString(AffixPatternProvider.Flags.PADDING);
|
||||
@ -663,9 +678,10 @@ public class PatternStringParser {
|
||||
properties.setPositivePrefixPattern(posPrefix);
|
||||
properties.setPositiveSuffixPattern(posSuffix);
|
||||
if (patternInfo.negative != null) {
|
||||
properties.setNegativePrefixPattern(patternInfo
|
||||
.getString(AffixPatternProvider.Flags.NEGATIVE_SUBPATTERN | AffixPatternProvider.Flags.PREFIX));
|
||||
properties.setNegativeSuffixPattern(patternInfo.getString(AffixPatternProvider.Flags.NEGATIVE_SUBPATTERN));
|
||||
properties.setNegativePrefixPattern(patternInfo.getString(
|
||||
AffixPatternProvider.Flags.NEGATIVE_SUBPATTERN | AffixPatternProvider.Flags.PREFIX));
|
||||
properties.setNegativeSuffixPattern(
|
||||
patternInfo.getString(AffixPatternProvider.Flags.NEGATIVE_SUBPATTERN));
|
||||
} else {
|
||||
properties.setNegativePrefixPattern(null);
|
||||
properties.setNegativeSuffixPattern(null);
|
||||
|
@ -16,8 +16,9 @@ public class PatternStringUtils {
|
||||
* Creates a pattern string from a property bag.
|
||||
*
|
||||
* <p>
|
||||
* Since pattern strings support only a subset of the functionality available in a property bag, a new property bag
|
||||
* created from the string returned by this function may not be the same as the original property bag.
|
||||
* Since pattern strings support only a subset of the functionality available in a property bag, a
|
||||
* new property bag created from the string returned by this function may not be the same as the
|
||||
* original property bag.
|
||||
*
|
||||
* @param properties
|
||||
* The property bag to serialize.
|
||||
@ -61,7 +62,8 @@ public class PatternStringUtils {
|
||||
|
||||
// Figure out the grouping sizes.
|
||||
int grouping1, grouping2, grouping;
|
||||
if (groupingSize != Math.min(dosMax, -1) && firstGroupingSize != Math.min(dosMax, -1)
|
||||
if (groupingSize != Math.min(dosMax, -1)
|
||||
&& firstGroupingSize != Math.min(dosMax, -1)
|
||||
&& groupingSize != firstGroupingSize) {
|
||||
grouping = groupingSize;
|
||||
grouping1 = groupingSize;
|
||||
@ -184,7 +186,9 @@ public class PatternStringUtils {
|
||||
|
||||
// Negative affixes
|
||||
// Ignore if the negative prefix pattern is "-" and the negative suffix is empty
|
||||
if (np != null || ns != null || (npp == null && nsp != null)
|
||||
if (np != null
|
||||
|| ns != null
|
||||
|| (npp == null && nsp != null)
|
||||
|| (npp != null && (npp.length() != 1 || npp.charAt(0) != '-' || nsp.length() != 0))) {
|
||||
sb.append(';');
|
||||
if (npp != null)
|
||||
@ -232,14 +236,15 @@ public class PatternStringUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a pattern between standard notation and localized notation. Localized notation means that instead of
|
||||
* using generic placeholders in the pattern, you use the corresponding locale-specific characters instead. For
|
||||
* example, in locale <em>fr-FR</em>, the period in the pattern "0.000" means "decimal" in standard notation (as it
|
||||
* does in every other locale), but it means "grouping" in localized notation.
|
||||
* Converts a pattern between standard notation and localized notation. Localized notation means that
|
||||
* instead of using generic placeholders in the pattern, you use the corresponding locale-specific
|
||||
* characters instead. For example, in locale <em>fr-FR</em>, the period in the pattern "0.000" means
|
||||
* "decimal" in standard notation (as it does in every other locale), but it means "grouping" in
|
||||
* localized notation.
|
||||
*
|
||||
* <p>
|
||||
* A greedy string-substitution strategy is used to substitute locale symbols. If two symbols are ambiguous or have
|
||||
* the same prefix, the result is not well-defined.
|
||||
* A greedy string-substitution strategy is used to substitute locale symbols. If two symbols are
|
||||
* ambiguous or have the same prefix, the result is not well-defined.
|
||||
*
|
||||
* <p>
|
||||
* Locale symbols are not allowed to contain the ASCII quote character.
|
||||
@ -252,11 +257,14 @@ public class PatternStringUtils {
|
||||
* @param symbols
|
||||
* The symbols corresponding to the localized pattern.
|
||||
* @param toLocalized
|
||||
* true to convert from standard to localized notation; false to convert from localized to standard
|
||||
* notation.
|
||||
* true to convert from standard to localized notation; false to convert from localized to
|
||||
* standard notation.
|
||||
* @return The pattern expressed in the other notation.
|
||||
*/
|
||||
public static String convertLocalized(String input, DecimalFormatSymbols symbols, boolean toLocalized) {
|
||||
public static String convertLocalized(
|
||||
String input,
|
||||
DecimalFormatSymbols symbols,
|
||||
boolean toLocalized) {
|
||||
if (input == null)
|
||||
return null;
|
||||
|
||||
|
@ -8,8 +8,8 @@ import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* ICU 59 called the class DecimalFormatProperties as just Properties. We need to keep a thin implementation for the
|
||||
* purposes of serialization.
|
||||
* ICU 59 called the class DecimalFormatProperties as just Properties. We need to keep a thin
|
||||
* implementation for the purposes of serialization.
|
||||
*/
|
||||
public class Properties implements Serializable {
|
||||
|
||||
|
@ -9,177 +9,193 @@ import java.math.RoundingMode;
|
||||
/** @author sffc */
|
||||
public class RoundingUtils {
|
||||
|
||||
public static final int SECTION_LOWER = 1;
|
||||
public static final int SECTION_MIDPOINT = 2;
|
||||
public static final int SECTION_UPPER = 3;
|
||||
public static final int SECTION_LOWER = 1;
|
||||
public static final int SECTION_MIDPOINT = 2;
|
||||
public static final int SECTION_UPPER = 3;
|
||||
|
||||
/**
|
||||
* The default rounding mode.
|
||||
*/
|
||||
public static final RoundingMode DEFAULT_ROUNDING_MODE = RoundingMode.HALF_EVEN;
|
||||
/**
|
||||
* The default rounding mode.
|
||||
*/
|
||||
public static final RoundingMode DEFAULT_ROUNDING_MODE = RoundingMode.HALF_EVEN;
|
||||
|
||||
/**
|
||||
* The maximum number of fraction places, integer numerals, or significant digits.
|
||||
* TODO: This does not feel like the best home for this value.
|
||||
*/
|
||||
public static final int MAX_INT_FRAC_SIG = 100;
|
||||
/**
|
||||
* The maximum number of fraction places, integer numerals, or significant digits. TODO: This does
|
||||
* not feel like the best home for this value.
|
||||
*/
|
||||
public static final int MAX_INT_FRAC_SIG = 100;
|
||||
|
||||
/**
|
||||
* Converts a rounding mode and metadata about the quantity being rounded to a boolean determining
|
||||
* whether the value should be rounded toward infinity or toward zero.
|
||||
*
|
||||
* <p>The parameters are of type int because benchmarks on an x86-64 processor against OpenJDK
|
||||
* showed that ints were demonstrably faster than enums in switch statements.
|
||||
*
|
||||
* @param isEven Whether the digit immediately before the rounding magnitude is even.
|
||||
* @param isNegative Whether the quantity is negative.
|
||||
* @param section Whether the part of the quantity to the right of the rounding magnitude is
|
||||
* exactly halfway between two digits, whether it is in the lower part (closer to zero), or
|
||||
* whether it is in the upper part (closer to infinity). See {@link #SECTION_LOWER}, {@link
|
||||
* #SECTION_MIDPOINT}, and {@link #SECTION_UPPER}.
|
||||
* @param roundingMode The integer version of the {@link RoundingMode}, which you can get via
|
||||
* {@link RoundingMode#ordinal}.
|
||||
* @param reference A reference object to be used when throwing an ArithmeticException.
|
||||
* @return true if the number should be rounded toward zero; false if it should be rounded toward
|
||||
* infinity.
|
||||
*/
|
||||
public static boolean getRoundingDirection(
|
||||
boolean isEven, boolean isNegative, int section, int roundingMode, Object reference) {
|
||||
switch (roundingMode) {
|
||||
case BigDecimal.ROUND_UP:
|
||||
// round away from zero
|
||||
return false;
|
||||
|
||||
case BigDecimal.ROUND_DOWN:
|
||||
// round toward zero
|
||||
return true;
|
||||
|
||||
case BigDecimal.ROUND_CEILING:
|
||||
// round toward positive infinity
|
||||
return isNegative;
|
||||
|
||||
case BigDecimal.ROUND_FLOOR:
|
||||
// round toward negative infinity
|
||||
return !isNegative;
|
||||
|
||||
case BigDecimal.ROUND_HALF_UP:
|
||||
switch (section) {
|
||||
case SECTION_MIDPOINT:
|
||||
/**
|
||||
* Converts a rounding mode and metadata about the quantity being rounded to a boolean determining
|
||||
* whether the value should be rounded toward infinity or toward zero.
|
||||
*
|
||||
* <p>
|
||||
* The parameters are of type int because benchmarks on an x86-64 processor against OpenJDK showed
|
||||
* that ints were demonstrably faster than enums in switch statements.
|
||||
*
|
||||
* @param isEven
|
||||
* Whether the digit immediately before the rounding magnitude is even.
|
||||
* @param isNegative
|
||||
* Whether the quantity is negative.
|
||||
* @param section
|
||||
* Whether the part of the quantity to the right of the rounding magnitude is exactly
|
||||
* halfway between two digits, whether it is in the lower part (closer to zero), or
|
||||
* whether it is in the upper part (closer to infinity). See {@link #SECTION_LOWER},
|
||||
* {@link #SECTION_MIDPOINT}, and {@link #SECTION_UPPER}.
|
||||
* @param roundingMode
|
||||
* The integer version of the {@link RoundingMode}, which you can get via
|
||||
* {@link RoundingMode#ordinal}.
|
||||
* @param reference
|
||||
* A reference object to be used when throwing an ArithmeticException.
|
||||
* @return true if the number should be rounded toward zero; false if it should be rounded toward
|
||||
* infinity.
|
||||
*/
|
||||
public static boolean getRoundingDirection(
|
||||
boolean isEven,
|
||||
boolean isNegative,
|
||||
int section,
|
||||
int roundingMode,
|
||||
Object reference) {
|
||||
switch (roundingMode) {
|
||||
case BigDecimal.ROUND_UP:
|
||||
// round away from zero
|
||||
return false;
|
||||
case SECTION_LOWER:
|
||||
|
||||
case BigDecimal.ROUND_DOWN:
|
||||
// round toward zero
|
||||
return true;
|
||||
case SECTION_UPPER:
|
||||
return false;
|
||||
|
||||
case BigDecimal.ROUND_CEILING:
|
||||
// round toward positive infinity
|
||||
return isNegative;
|
||||
|
||||
case BigDecimal.ROUND_FLOOR:
|
||||
// round toward negative infinity
|
||||
return !isNegative;
|
||||
|
||||
case BigDecimal.ROUND_HALF_UP:
|
||||
switch (section) {
|
||||
case SECTION_MIDPOINT:
|
||||
return false;
|
||||
case SECTION_LOWER:
|
||||
return true;
|
||||
case SECTION_UPPER:
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case BigDecimal.ROUND_HALF_DOWN:
|
||||
switch (section) {
|
||||
case SECTION_MIDPOINT:
|
||||
return true;
|
||||
case SECTION_LOWER:
|
||||
return true;
|
||||
case SECTION_UPPER:
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case BigDecimal.ROUND_HALF_EVEN:
|
||||
switch (section) {
|
||||
case SECTION_MIDPOINT:
|
||||
return isEven;
|
||||
case SECTION_LOWER:
|
||||
return true;
|
||||
case SECTION_UPPER:
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case BigDecimal.ROUND_HALF_DOWN:
|
||||
switch (section) {
|
||||
case SECTION_MIDPOINT:
|
||||
return true;
|
||||
case SECTION_LOWER:
|
||||
return true;
|
||||
case SECTION_UPPER:
|
||||
// Rounding mode UNNECESSARY
|
||||
throw new ArithmeticException("Rounding is required on " + reference.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether the given rounding mode's rounding boundary is at the midpoint. The rounding boundary
|
||||
* is the point at which a number switches from being rounded down to being rounded up. For example,
|
||||
* with rounding mode HALF_EVEN, HALF_UP, or HALF_DOWN, the rounding boundary is at the midpoint, and
|
||||
* this function would return true. However, for UP, DOWN, CEILING, and FLOOR, the rounding boundary
|
||||
* is at the "edge", and this function would return false.
|
||||
*
|
||||
* @param roundingMode
|
||||
* The integer version of the {@link RoundingMode}.
|
||||
* @return true if rounding mode is HALF_EVEN, HALF_UP, or HALF_DOWN; false otherwise.
|
||||
*/
|
||||
public static boolean roundsAtMidpoint(int roundingMode) {
|
||||
switch (roundingMode) {
|
||||
case BigDecimal.ROUND_UP:
|
||||
case BigDecimal.ROUND_DOWN:
|
||||
case BigDecimal.ROUND_CEILING:
|
||||
case BigDecimal.ROUND_FLOOR:
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case BigDecimal.ROUND_HALF_EVEN:
|
||||
switch (section) {
|
||||
case SECTION_MIDPOINT:
|
||||
return isEven;
|
||||
case SECTION_LOWER:
|
||||
default:
|
||||
return true;
|
||||
case SECTION_UPPER:
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Rounding mode UNNECESSARY
|
||||
throw new ArithmeticException("Rounding is required on " + reference.toString());
|
||||
}
|
||||
private static final MathContext[] MATH_CONTEXT_BY_ROUNDING_MODE_UNLIMITED = new MathContext[RoundingMode
|
||||
.values().length];
|
||||
|
||||
/**
|
||||
* Gets whether the given rounding mode's rounding boundary is at the midpoint. The rounding
|
||||
* boundary is the point at which a number switches from being rounded down to being rounded up.
|
||||
* For example, with rounding mode HALF_EVEN, HALF_UP, or HALF_DOWN, the rounding boundary is at
|
||||
* the midpoint, and this function would return true. However, for UP, DOWN, CEILING, and FLOOR,
|
||||
* the rounding boundary is at the "edge", and this function would return false.
|
||||
*
|
||||
* @param roundingMode The integer version of the {@link RoundingMode}.
|
||||
* @return true if rounding mode is HALF_EVEN, HALF_UP, or HALF_DOWN; false otherwise.
|
||||
*/
|
||||
public static boolean roundsAtMidpoint(int roundingMode) {
|
||||
switch (roundingMode) {
|
||||
case BigDecimal.ROUND_UP:
|
||||
case BigDecimal.ROUND_DOWN:
|
||||
case BigDecimal.ROUND_CEILING:
|
||||
case BigDecimal.ROUND_FLOOR:
|
||||
return false;
|
||||
private static final MathContext[] MATH_CONTEXT_BY_ROUNDING_MODE_34_DIGITS = new MathContext[RoundingMode
|
||||
.values().length];
|
||||
|
||||
default:
|
||||
return true;
|
||||
static {
|
||||
for (int i = 0; i < MATH_CONTEXT_BY_ROUNDING_MODE_34_DIGITS.length; i++) {
|
||||
MATH_CONTEXT_BY_ROUNDING_MODE_UNLIMITED[i] = new MathContext(0, RoundingMode.valueOf(i));
|
||||
MATH_CONTEXT_BY_ROUNDING_MODE_34_DIGITS[i] = new MathContext(34);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final MathContext[] MATH_CONTEXT_BY_ROUNDING_MODE_UNLIMITED =
|
||||
new MathContext[RoundingMode.values().length];
|
||||
|
||||
private static final MathContext[] MATH_CONTEXT_BY_ROUNDING_MODE_34_DIGITS =
|
||||
new MathContext[RoundingMode.values().length];
|
||||
|
||||
static {
|
||||
for (int i = 0; i < MATH_CONTEXT_BY_ROUNDING_MODE_34_DIGITS.length; i++) {
|
||||
MATH_CONTEXT_BY_ROUNDING_MODE_UNLIMITED[i] = new MathContext(0, RoundingMode.valueOf(i));
|
||||
MATH_CONTEXT_BY_ROUNDING_MODE_34_DIGITS[i] = new MathContext(34);
|
||||
/**
|
||||
* Gets the user-specified math context out of the property bag. If there is none, falls back to a
|
||||
* math context with unlimited precision and the user-specified rounding mode, which defaults to
|
||||
* HALF_EVEN (the IEEE 754R default).
|
||||
*
|
||||
* @param properties
|
||||
* The property bag.
|
||||
* @return A {@link MathContext}. Never null.
|
||||
*/
|
||||
public static MathContext getMathContextOrUnlimited(DecimalFormatProperties properties) {
|
||||
MathContext mathContext = properties.getMathContext();
|
||||
if (mathContext == null) {
|
||||
RoundingMode roundingMode = properties.getRoundingMode();
|
||||
if (roundingMode == null)
|
||||
roundingMode = RoundingMode.HALF_EVEN;
|
||||
mathContext = MATH_CONTEXT_BY_ROUNDING_MODE_UNLIMITED[roundingMode.ordinal()];
|
||||
}
|
||||
return mathContext;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user-specified math context out of the property bag. If there is none, falls back to a
|
||||
* math context with unlimited precision and the user-specified rounding mode, which defaults to
|
||||
* HALF_EVEN (the IEEE 754R default).
|
||||
*
|
||||
* @param properties The property bag.
|
||||
* @return A {@link MathContext}. Never null.
|
||||
*/
|
||||
public static MathContext getMathContextOrUnlimited(DecimalFormatProperties properties) {
|
||||
MathContext mathContext = properties.getMathContext();
|
||||
if (mathContext == null) {
|
||||
RoundingMode roundingMode = properties.getRoundingMode();
|
||||
if (roundingMode == null) roundingMode = RoundingMode.HALF_EVEN;
|
||||
mathContext = MATH_CONTEXT_BY_ROUNDING_MODE_UNLIMITED[roundingMode.ordinal()];
|
||||
/**
|
||||
* Gets the user-specified math context out of the property bag. If there is none, falls back to a
|
||||
* math context with 34 digits of precision (the 128-bit IEEE 754R default) and the user-specified
|
||||
* rounding mode, which defaults to HALF_EVEN (the IEEE 754R default).
|
||||
*
|
||||
* @param properties
|
||||
* The property bag.
|
||||
* @return A {@link MathContext}. Never null.
|
||||
*/
|
||||
public static MathContext getMathContextOr34Digits(DecimalFormatProperties properties) {
|
||||
MathContext mathContext = properties.getMathContext();
|
||||
if (mathContext == null) {
|
||||
RoundingMode roundingMode = properties.getRoundingMode();
|
||||
if (roundingMode == null)
|
||||
roundingMode = RoundingMode.HALF_EVEN;
|
||||
mathContext = MATH_CONTEXT_BY_ROUNDING_MODE_34_DIGITS[roundingMode.ordinal()];
|
||||
}
|
||||
return mathContext;
|
||||
}
|
||||
return mathContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user-specified math context out of the property bag. If there is none, falls back to a
|
||||
* math context with 34 digits of precision (the 128-bit IEEE 754R default) and the user-specified
|
||||
* rounding mode, which defaults to HALF_EVEN (the IEEE 754R default).
|
||||
*
|
||||
* @param properties The property bag.
|
||||
* @return A {@link MathContext}. Never null.
|
||||
*/
|
||||
public static MathContext getMathContextOr34Digits(DecimalFormatProperties properties) {
|
||||
MathContext mathContext = properties.getMathContext();
|
||||
if (mathContext == null) {
|
||||
RoundingMode roundingMode = properties.getRoundingMode();
|
||||
if (roundingMode == null) roundingMode = RoundingMode.HALF_EVEN;
|
||||
mathContext = MATH_CONTEXT_BY_ROUNDING_MODE_34_DIGITS[roundingMode.ordinal()];
|
||||
/**
|
||||
* Gets a MathContext with unlimited precision and the specified RoundingMode. Equivalent to "new
|
||||
* MathContext(0, roundingMode)", but pulls from a singleton to prevent object thrashing.
|
||||
*
|
||||
* @param roundingMode
|
||||
* The {@link RoundingMode} to use.
|
||||
* @return The corresponding {@link MathContext}.
|
||||
*/
|
||||
public static MathContext mathContextUnlimited(RoundingMode roundingMode) {
|
||||
return MATH_CONTEXT_BY_ROUNDING_MODE_UNLIMITED[roundingMode.ordinal()];
|
||||
}
|
||||
return mathContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a MathContext with unlimited precision and the specified RoundingMode. Equivalent to "new
|
||||
* MathContext(0, roundingMode)", but pulls from a singleton to prevent object thrashing.
|
||||
*
|
||||
* @param roundingMode The {@link RoundingMode} to use.
|
||||
* @return The corresponding {@link MathContext}.
|
||||
*/
|
||||
public static MathContext mathContextUnlimited(RoundingMode roundingMode) {
|
||||
return MATH_CONTEXT_BY_ROUNDING_MODE_UNLIMITED[roundingMode.ordinal()];
|
||||
}
|
||||
}
|
||||
|
@ -6,8 +6,8 @@ import com.ibm.icu.impl.SimpleFormatterImpl;
|
||||
import com.ibm.icu.text.NumberFormat.Field;
|
||||
|
||||
/**
|
||||
* The second primary implementation of {@link Modifier}, this one consuming a {@link com.ibm.icu.text.SimpleFormatter}
|
||||
* pattern.
|
||||
* The second primary implementation of {@link Modifier}, this one consuming a
|
||||
* {@link com.ibm.icu.text.SimpleFormatter} pattern.
|
||||
*/
|
||||
public class SimpleModifier implements Modifier {
|
||||
private final String compiledPattern;
|
||||
@ -59,7 +59,8 @@ public class SimpleModifier implements Modifier {
|
||||
count += Character.codePointCount(compiledPattern, 2, 2 + prefixLength);
|
||||
}
|
||||
if (suffixLength > 0) {
|
||||
count += Character.codePointCount(compiledPattern, 1 + suffixOffset, 1 + suffixOffset + suffixLength);
|
||||
count += Character
|
||||
.codePointCount(compiledPattern, 1 + suffixOffset, 1 + suffixOffset + suffixLength);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
@ -71,12 +72,13 @@ public class SimpleModifier implements Modifier {
|
||||
|
||||
/**
|
||||
* TODO: This belongs in SimpleFormatterImpl. The only reason I haven't moved it there yet is because
|
||||
* DoubleSidedStringBuilder is an internal class and SimpleFormatterImpl feels like it should not depend on it.
|
||||
* DoubleSidedStringBuilder is an internal class and SimpleFormatterImpl feels like it should not
|
||||
* depend on it.
|
||||
*
|
||||
* <p>
|
||||
* Formats a value that is already stored inside the StringBuilder <code>result</code> between the indices
|
||||
* <code>startIndex</code> and <code>endIndex</code> by inserting characters before the start index and after the
|
||||
* end index.
|
||||
* Formats a value that is already stored inside the StringBuilder <code>result</code> between the
|
||||
* indices <code>startIndex</code> and <code>endIndex</code> by inserting characters before the start
|
||||
* index and after the end index.
|
||||
*
|
||||
* <p>
|
||||
* This is well-defined only for patterns with exactly one argument.
|
||||
@ -89,12 +91,19 @@ public class SimpleModifier implements Modifier {
|
||||
* The right index of the value within the string builder.
|
||||
* @return The number of characters (UTF-16 code points) that were added to the StringBuilder.
|
||||
*/
|
||||
public int formatAsPrefixSuffix(NumberStringBuilder result, int startIndex, int endIndex, Field field) {
|
||||
public int formatAsPrefixSuffix(
|
||||
NumberStringBuilder result,
|
||||
int startIndex,
|
||||
int endIndex,
|
||||
Field field) {
|
||||
if (prefixLength > 0) {
|
||||
result.insert(startIndex, compiledPattern, 2, 2 + prefixLength, field);
|
||||
}
|
||||
if (suffixLength > 0) {
|
||||
result.insert(endIndex + prefixLength, compiledPattern, 1 + suffixOffset, 1 + suffixOffset + suffixLength,
|
||||
result.insert(endIndex + prefixLength,
|
||||
compiledPattern,
|
||||
1 + suffixOffset,
|
||||
1 + suffixOffset + suffixLength,
|
||||
field);
|
||||
}
|
||||
return prefixLength + suffixLength;
|
||||
|
@ -21,13 +21,13 @@ import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
|
||||
import com.ibm.icu.text.PluralRules;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
|
||||
|
||||
/**
|
||||
* A class that defines the scientific notation style to be used when formatting numbers in NumberFormatter.
|
||||
* A class that defines the scientific notation style to be used when formatting numbers in
|
||||
* NumberFormatter.
|
||||
*
|
||||
* <p>
|
||||
* This class exposes no public functionality. To create a CompactNotation, use one of the factory methods in
|
||||
* {@link Notation}.
|
||||
* This class exposes no public functionality. To create a CompactNotation, use one of the factory
|
||||
* methods in {@link Notation}.
|
||||
*
|
||||
* @draft ICU 60
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
@ -48,8 +48,13 @@ public class CompactNotation extends Notation {
|
||||
this.compactCustomData = compactCustomData;
|
||||
}
|
||||
|
||||
/* package-private */ MicroPropsGenerator withLocaleData(ULocale locale, String nsName, CompactType compactType,
|
||||
PluralRules rules, MutablePatternModifier buildReference, MicroPropsGenerator parent) {
|
||||
/* package-private */ MicroPropsGenerator withLocaleData(
|
||||
ULocale locale,
|
||||
String nsName,
|
||||
CompactType compactType,
|
||||
PluralRules rules,
|
||||
MutablePatternModifier buildReference,
|
||||
MicroPropsGenerator parent) {
|
||||
// TODO: Add a data cache? It would be keyed by locale, nsName, compact type, and compact style.
|
||||
return new CompactHandler(this, locale, nsName, compactType, rules, buildReference, parent);
|
||||
}
|
||||
@ -66,8 +71,14 @@ public class CompactNotation extends Notation {
|
||||
final Map<String, CompactModInfo> precomputedMods;
|
||||
final CompactData data;
|
||||
|
||||
private CompactHandler(CompactNotation notation, ULocale locale, String nsName, CompactType compactType,
|
||||
PluralRules rules, MutablePatternModifier buildReference, MicroPropsGenerator parent) {
|
||||
private CompactHandler(
|
||||
CompactNotation notation,
|
||||
ULocale locale,
|
||||
String nsName,
|
||||
CompactType compactType,
|
||||
PluralRules rules,
|
||||
MutablePatternModifier buildReference,
|
||||
MicroPropsGenerator parent) {
|
||||
this.rules = rules;
|
||||
this.parent = parent;
|
||||
this.data = new CompactData();
|
||||
|
@ -5,8 +5,8 @@ package com.ibm.icu.number;
|
||||
import com.ibm.icu.util.Currency;
|
||||
|
||||
/**
|
||||
* A class that defines a rounding strategy parameterized by a currency to be used when formatting numbers in
|
||||
* NumberFormatter.
|
||||
* A class that defines a rounding strategy parameterized by a currency to be used when formatting
|
||||
* numbers in NumberFormatter.
|
||||
*
|
||||
* <p>
|
||||
* To create a CurrencyRounder, use one of the factory methods on Rounder.
|
||||
@ -24,13 +24,13 @@ public abstract class CurrencyRounder extends Rounder {
|
||||
* Associates a currency with this rounding strategy.
|
||||
*
|
||||
* <p>
|
||||
* <strong>Calling this method is <em>not required</em></strong>, because the currency specified in unit() or via a
|
||||
* CurrencyAmount passed into format(Measure) is automatically applied to currency rounding strategies. However,
|
||||
* this method enables you to override that automatic association.
|
||||
* <strong>Calling this method is <em>not required</em></strong>, because the currency specified in
|
||||
* unit() or via a CurrencyAmount passed into format(Measure) is automatically applied to currency
|
||||
* rounding strategies. However, this method enables you to override that automatic association.
|
||||
*
|
||||
* <p>
|
||||
* This method also enables numbers to be formatted using currency rounding rules without explicitly using a
|
||||
* currency format.
|
||||
* This method also enables numbers to be formatted using currency rounding rules without explicitly
|
||||
* using a currency format.
|
||||
*
|
||||
* @param currency
|
||||
* The currency to associate with this rounding strategy.
|
||||
|
@ -5,8 +5,8 @@ package com.ibm.icu.number;
|
||||
import com.ibm.icu.impl.number.RoundingUtils;
|
||||
|
||||
/**
|
||||
* A class that defines a rounding strategy based on a number of fraction places and optionally significant digits to be
|
||||
* used when formatting numbers in NumberFormatter.
|
||||
* A class that defines a rounding strategy based on a number of fraction places and optionally
|
||||
* significant digits to be used when formatting numbers in NumberFormatter.
|
||||
*
|
||||
* <p>
|
||||
* To create a FractionRounder, use one of the factory methods on Rounder.
|
||||
@ -21,15 +21,16 @@ public abstract class FractionRounder extends Rounder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that no less than this number of significant digits are retained when rounding according to fraction
|
||||
* rules.
|
||||
* Ensure that no less than this number of significant digits are retained when rounding according to
|
||||
* fraction rules.
|
||||
*
|
||||
* <p>
|
||||
* For example, with integer rounding, the number 3.141 becomes "3". However, with minimum figures set to 2, 3.141
|
||||
* becomes "3.1" instead.
|
||||
* For example, with integer rounding, the number 3.141 becomes "3". However, with minimum figures
|
||||
* set to 2, 3.141 becomes "3.1" instead.
|
||||
*
|
||||
* <p>
|
||||
* This setting does not affect the number of trailing zeros. For example, 3.01 would print as "3", not "3.0".
|
||||
* This setting does not affect the number of trailing zeros. For example, 3.01 would print as "3",
|
||||
* not "3.0".
|
||||
*
|
||||
* @param minSignificantDigits
|
||||
* The number of significant figures to guarantee.
|
||||
@ -39,25 +40,26 @@ public abstract class FractionRounder extends Rounder {
|
||||
* @see NumberFormatter
|
||||
*/
|
||||
public Rounder withMinDigits(int minSignificantDigits) {
|
||||
if (minSignificantDigits > 0 && minSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) {
|
||||
if (minSignificantDigits >= 1 && minSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) {
|
||||
return constructFractionSignificant(this, minSignificantDigits, -1);
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Significant digits must be between 0 and " + RoundingUtils.MAX_INT_FRAC_SIG);
|
||||
throw new IllegalArgumentException("Significant digits must be between 1 and "
|
||||
+ RoundingUtils.MAX_INT_FRAC_SIG
|
||||
+ " (inclusive)");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that no more than this number of significant digits are retained when rounding according to fraction
|
||||
* rules.
|
||||
* Ensure that no more than this number of significant digits are retained when rounding according to
|
||||
* fraction rules.
|
||||
*
|
||||
* <p>
|
||||
* For example, with integer rounding, the number 123.4 becomes "123". However, with maximum figures set to 2, 123.4
|
||||
* becomes "120" instead.
|
||||
* For example, with integer rounding, the number 123.4 becomes "123". However, with maximum figures
|
||||
* set to 2, 123.4 becomes "120" instead.
|
||||
*
|
||||
* <p>
|
||||
* This setting does not affect the number of trailing zeros. For example, with fixed fraction of 2, 123.4 would
|
||||
* become "120.00".
|
||||
* This setting does not affect the number of trailing zeros. For example, with fixed fraction of 2,
|
||||
* 123.4 would become "120.00".
|
||||
*
|
||||
* @param maxSignificantDigits
|
||||
* Round the number to no more than this number of significant figures.
|
||||
@ -67,11 +69,12 @@ public abstract class FractionRounder extends Rounder {
|
||||
* @see NumberFormatter
|
||||
*/
|
||||
public Rounder withMaxDigits(int maxSignificantDigits) {
|
||||
if (maxSignificantDigits > 0 && maxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) {
|
||||
if (maxSignificantDigits >= 1 && maxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) {
|
||||
return constructFractionSignificant(this, -1, maxSignificantDigits);
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Significant digits must be between 0 and " + RoundingUtils.MAX_INT_FRAC_SIG);
|
||||
throw new IllegalArgumentException("Significant digits must be between 1 and "
|
||||
+ RoundingUtils.MAX_INT_FRAC_SIG
|
||||
+ " (inclusive)");
|
||||
}
|
||||
}
|
||||
}
|
@ -108,7 +108,8 @@ public class Grouper {
|
||||
return false;
|
||||
}
|
||||
position -= grouping1;
|
||||
return position >= 0 && (position % grouping2) == 0
|
||||
return position >= 0
|
||||
&& (position % grouping2) == 0
|
||||
&& value.getUpperDisplayMagnitude() - grouping1 + 1 >= (min2 ? 2 : 1);
|
||||
}
|
||||
}
|
@ -27,7 +27,8 @@ public class IntegerWidth {
|
||||
}
|
||||
|
||||
/**
|
||||
* Pad numbers at the beginning with zeros to guarantee a certain number of numerals before the decimal separator.
|
||||
* Pad numbers at the beginning with zeros to guarantee a certain number of numerals before the
|
||||
* decimal separator.
|
||||
*
|
||||
* <p>
|
||||
* For example, with minInt=3, the number 55 will get printed as "055".
|
||||
@ -42,11 +43,12 @@ public class IntegerWidth {
|
||||
public static IntegerWidth zeroFillTo(int minInt) {
|
||||
if (minInt == 1) {
|
||||
return DEFAULT;
|
||||
} else if (minInt >= 0 && minInt < RoundingUtils.MAX_INT_FRAC_SIG) {
|
||||
} else if (minInt >= 0 && minInt <= RoundingUtils.MAX_INT_FRAC_SIG) {
|
||||
return new IntegerWidth(minInt, -1);
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Integer digits must be between 0 and " + RoundingUtils.MAX_INT_FRAC_SIG);
|
||||
throw new IllegalArgumentException("Integer digits must be between 0 and "
|
||||
+ RoundingUtils.MAX_INT_FRAC_SIG
|
||||
+ " (inclusive)");
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,7 +58,8 @@ public class IntegerWidth {
|
||||
* For example, with maxInt=3, the number 1234 will get printed as "234".
|
||||
*
|
||||
* @param maxInt
|
||||
* The maximum number of places before the decimal separator.
|
||||
* The maximum number of places before the decimal separator. maxInt == -1 means no
|
||||
* truncation.
|
||||
* @return An IntegerWidth for passing to the NumberFormatter integerWidth() setter.
|
||||
* @draft ICU 60
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
@ -65,13 +68,14 @@ public class IntegerWidth {
|
||||
public IntegerWidth truncateAt(int maxInt) {
|
||||
if (maxInt == this.maxInt) {
|
||||
return this;
|
||||
} else if (maxInt >= 0 && maxInt < RoundingUtils.MAX_INT_FRAC_SIG) {
|
||||
} else if (maxInt >= 0 && maxInt <= RoundingUtils.MAX_INT_FRAC_SIG && maxInt >= minInt) {
|
||||
return new IntegerWidth(minInt, maxInt);
|
||||
} else if (maxInt == -1) {
|
||||
return new IntegerWidth(minInt, maxInt);
|
||||
return new IntegerWidth(minInt, -1);
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Integer digits must be between 0 and " + RoundingUtils.MAX_INT_FRAC_SIG);
|
||||
throw new IllegalArgumentException("Integer digits must be between -1 and "
|
||||
+ RoundingUtils.MAX_INT_FRAC_SIG
|
||||
+ " (inclusive)");
|
||||
}
|
||||
}
|
||||
}
|
@ -38,8 +38,8 @@ public class LocalizedNumberFormatter extends NumberFormatterSettings<LocalizedN
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the given byte, short, int, or long to a string using the settings specified in the NumberFormatter fluent
|
||||
* setting chain.
|
||||
* Format the given byte, short, int, or long to a string using the settings specified in the
|
||||
* NumberFormatter fluent setting chain.
|
||||
*
|
||||
* @param input
|
||||
* The number to format.
|
||||
@ -53,8 +53,8 @@ public class LocalizedNumberFormatter extends NumberFormatterSettings<LocalizedN
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the given float or double to a string using the settings specified in the NumberFormatter fluent setting
|
||||
* chain.
|
||||
* Format the given float or double to a string using the settings specified in the NumberFormatter
|
||||
* fluent setting chain.
|
||||
*
|
||||
* @param input
|
||||
* The number to format.
|
||||
@ -68,8 +68,8 @@ public class LocalizedNumberFormatter extends NumberFormatterSettings<LocalizedN
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the given {@link BigInteger}, {@link BigDecimal}, or other {@link Number} to a string using the settings
|
||||
* specified in the NumberFormatter fluent setting chain.
|
||||
* Format the given {@link BigInteger}, {@link BigDecimal}, or other {@link Number} to a string using
|
||||
* the settings specified in the NumberFormatter fluent setting chain.
|
||||
*
|
||||
* @param input
|
||||
* The number to format.
|
||||
@ -83,12 +83,12 @@ public class LocalizedNumberFormatter extends NumberFormatterSettings<LocalizedN
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the given {@link Measure} or {@link CurrencyAmount} to a string using the settings specified in the
|
||||
* NumberFormatter fluent setting chain.
|
||||
* Format the given {@link Measure} or {@link CurrencyAmount} to a string using the settings
|
||||
* specified in the NumberFormatter fluent setting chain.
|
||||
*
|
||||
* <p>
|
||||
* The unit specified here overrides any unit that may have been specified in the setter chain. This method is
|
||||
* intended for cases when each input to the number formatter has a different unit.
|
||||
* The unit specified here overrides any unit that may have been specified in the setter chain. This
|
||||
* method is intended for cases when each input to the number formatter has a different unit.
|
||||
*
|
||||
* @param input
|
||||
* The number to format.
|
||||
@ -115,8 +115,9 @@ public class LocalizedNumberFormatter extends NumberFormatterSettings<LocalizedN
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the core entrypoint to the number formatting pipeline. It performs self-regulation: a static code path
|
||||
* for the first few calls, and compiling a more efficient data structure if called repeatedly.
|
||||
* This is the core entrypoint to the number formatting pipeline. It performs self-regulation: a
|
||||
* static code path for the first few calls, and compiling a more efficient data structure if called
|
||||
* repeatedly.
|
||||
*
|
||||
* <p>
|
||||
* This function is very hot, being called in every call to the number formatting pipeline.
|
||||
|
@ -15,8 +15,14 @@ import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
|
||||
public class Notation {
|
||||
|
||||
// TODO: Support engineering intervals other than 3?
|
||||
private static final ScientificNotation SCIENTIFIC = new ScientificNotation(1, false, 1, SignDisplay.AUTO);
|
||||
private static final ScientificNotation ENGINEERING = new ScientificNotation(3, false, 1, SignDisplay.AUTO);
|
||||
private static final ScientificNotation SCIENTIFIC = new ScientificNotation(1,
|
||||
false,
|
||||
1,
|
||||
SignDisplay.AUTO);
|
||||
private static final ScientificNotation ENGINEERING = new ScientificNotation(3,
|
||||
false,
|
||||
1,
|
||||
SignDisplay.AUTO);
|
||||
private static final CompactNotation COMPACT_SHORT = new CompactNotation(CompactStyle.SHORT);
|
||||
private static final CompactNotation COMPACT_LONG = new CompactNotation(CompactStyle.LONG);
|
||||
private static final SimpleNotation SIMPLE = new SimpleNotation();
|
||||
@ -25,10 +31,11 @@ public class Notation {
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the number using scientific notation (also known as scientific form, standard index form, or standard form
|
||||
* in the UK). The format for scientific notation varies by locale; for example, many Western locales display the
|
||||
* number in the form "#E0", where the number is displayed with one digit before the decimal separator, zero or more
|
||||
* digits after the decimal separator, and the corresponding power of 10 displayed after the "E".
|
||||
* Print the number using scientific notation (also known as scientific form, standard index form, or
|
||||
* standard form in the UK). The format for scientific notation varies by locale; for example, many
|
||||
* Western locales display the number in the form "#E0", where the number is displayed with one digit
|
||||
* before the decimal separator, zero or more digits after the decimal separator, and the
|
||||
* corresponding power of 10 displayed after the "E".
|
||||
*
|
||||
* <p>
|
||||
* Example outputs in <em>en-US</em> when printing 8.765E4 through 8.765E-3:
|
||||
@ -55,8 +62,8 @@ public class Notation {
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the number using engineering notation, a variant of scientific notation in which the exponent must be
|
||||
* divisible by 3.
|
||||
* Print the number using engineering notation, a variant of scientific notation in which the
|
||||
* exponent must be divisible by 3.
|
||||
*
|
||||
* <p>
|
||||
* Example outputs in <em>en-US</em> when printing 8.765E4 through 8.765E-3:
|
||||
@ -86,18 +93,18 @@ public class Notation {
|
||||
* Print the number using short-form compact notation.
|
||||
*
|
||||
* <p>
|
||||
* <em>Compact notation</em>, defined in Unicode Technical Standard #35 Part 3 Section 2.4.1, prints numbers with
|
||||
* localized prefixes or suffixes corresponding to different powers of ten. Compact notation is similar to
|
||||
* engineering notation in how it scales numbers.
|
||||
* <em>Compact notation</em>, defined in Unicode Technical Standard #35 Part 3 Section 2.4.1, prints
|
||||
* numbers with localized prefixes or suffixes corresponding to different powers of ten. Compact
|
||||
* notation is similar to engineering notation in how it scales numbers.
|
||||
*
|
||||
* <p>
|
||||
* Compact notation is ideal for displaying large numbers (over ~1000) to humans while at the same time minimizing
|
||||
* screen real estate.
|
||||
* Compact notation is ideal for displaying large numbers (over ~1000) to humans while at the same
|
||||
* time minimizing screen real estate.
|
||||
*
|
||||
* <p>
|
||||
* In short form, the powers of ten are abbreviated. In <em>en-US</em>, the abbreviations are "K" for thousands, "M"
|
||||
* for millions, "B" for billions, and "T" for trillions. Example outputs in <em>en-US</em> when printing 8.765E7
|
||||
* through 8.765E0:
|
||||
* In short form, the powers of ten are abbreviated. In <em>en-US</em>, the abbreviations are "K" for
|
||||
* thousands, "M" for millions, "B" for billions, and "T" for trillions. Example outputs in
|
||||
* <em>en-US</em> when printing 8.765E7 through 8.765E0:
|
||||
*
|
||||
* <pre>
|
||||
* 88M
|
||||
@ -111,10 +118,10 @@ public class Notation {
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* When compact notation is specified without an explicit rounding strategy, numbers are rounded off to the closest
|
||||
* integer after scaling the number by the corresponding power of 10, but with a digit shown after the decimal
|
||||
* separator if there is only one digit before the decimal separator. The default compact notation rounding strategy
|
||||
* is equivalent to:
|
||||
* When compact notation is specified without an explicit rounding strategy, numbers are rounded off
|
||||
* to the closest integer after scaling the number by the corresponding power of 10, but with a digit
|
||||
* shown after the decimal separator if there is only one digit before the decimal separator. The
|
||||
* default compact notation rounding strategy is equivalent to:
|
||||
*
|
||||
* <pre>
|
||||
* Rounder.integer().withMinDigits(2)
|
||||
@ -134,8 +141,8 @@ public class Notation {
|
||||
* {@link #compactShort}.
|
||||
*
|
||||
* <p>
|
||||
* In long form, the powers of ten are spelled out fully. Example outputs in <em>en-US</em> when printing 8.765E7
|
||||
* through 8.765E0:
|
||||
* In long form, the powers of ten are spelled out fully. Example outputs in <em>en-US</em> when
|
||||
* printing 8.765E7 through 8.765E0:
|
||||
*
|
||||
* <pre>
|
||||
* 88 million
|
||||
@ -158,11 +165,12 @@ public class Notation {
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the number using simple notation without any scaling by powers of ten. This is the default behavior.
|
||||
* Print the number using simple notation without any scaling by powers of ten. This is the default
|
||||
* behavior.
|
||||
*
|
||||
* <p>
|
||||
* Since this is the default behavior, this method needs to be called only when it is necessary to override a
|
||||
* previous setting.
|
||||
* Since this is the default behavior, this method needs to be called only when it is necessary to
|
||||
* override a previous setting.
|
||||
*
|
||||
* <p>
|
||||
* Example outputs in <em>en-US</em> when printing 8.765E7 through 8.765E0:
|
||||
|
@ -10,7 +10,8 @@ import com.ibm.icu.text.DecimalFormatSymbols;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
|
||||
/**
|
||||
* The main entrypoint to the localized number formatting library introduced in ICU 60. Basic usage examples:
|
||||
* The main entrypoint to the localized number formatting library introduced in ICU 60. Basic usage
|
||||
* examples:
|
||||
*
|
||||
* <pre>
|
||||
* // Most basic usage:
|
||||
@ -39,21 +40,25 @@ import com.ibm.icu.util.ULocale;
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* This API offers more features than {@link com.ibm.icu.text.DecimalFormat} and is geared toward new users of ICU.
|
||||
* This API offers more features than {@link com.ibm.icu.text.DecimalFormat} and is geared toward new
|
||||
* users of ICU.
|
||||
*
|
||||
* <p>
|
||||
* NumberFormatter instances are immutable and thread safe. This means that invoking a configuration method has no
|
||||
* effect on the receiving instance; you must store and use the new number formatter instance it returns instead.
|
||||
* NumberFormatter instances are immutable and thread safe. This means that invoking a configuration
|
||||
* method has no effect on the receiving instance; you must store and use the new number formatter
|
||||
* instance it returns instead.
|
||||
*
|
||||
* <pre>
|
||||
* UnlocalizedNumberFormatter formatter = UnlocalizedNumberFormatter.with().notation(Notation.scientific());
|
||||
* UnlocalizedNumberFormatter formatter = UnlocalizedNumberFormatter.with()
|
||||
* .notation(Notation.scientific());
|
||||
* formatter.rounding(Rounder.maxFraction(2)); // does nothing!
|
||||
* formatter.locale(ULocale.ENGLISH).format(9.8765).toString(); // prints "9.8765E0", not "9.88E0"
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* This API is based on the <em>fluent</em> design pattern popularized by libraries such as Google's Guava. For
|
||||
* extensive details on the design of this API, read <a href="https://goo.gl/szi5VB">the design doc</a>.
|
||||
* This API is based on the <em>fluent</em> design pattern popularized by libraries such as Google's
|
||||
* Guava. For extensive details on the design of this API, read <a href="https://goo.gl/szi5VB">the
|
||||
* design doc</a>.
|
||||
*
|
||||
* @author Shane Carr
|
||||
* @draft ICU 60
|
||||
@ -64,8 +69,8 @@ public final class NumberFormatter {
|
||||
private static final UnlocalizedNumberFormatter BASE = new UnlocalizedNumberFormatter();
|
||||
|
||||
/**
|
||||
* An enum declaring how to render units, including currencies. Example outputs when formatting 123 USD and 123
|
||||
* meters in <em>en-CA</em>:
|
||||
* An enum declaring how to render units, including currencies. Example outputs when formatting 123
|
||||
* USD and 123 meters in <em>en-CA</em>:
|
||||
*
|
||||
* <ul>
|
||||
* <li>NARROW: "$123.00" and "123 m"
|
||||
@ -84,13 +89,14 @@ public final class NumberFormatter {
|
||||
*/
|
||||
public static enum UnitWidth {
|
||||
/**
|
||||
* Print an abbreviated version of the unit name. Similar to SHORT, but always use the shortest available
|
||||
* abbreviation or symbol. This option can be used when the context hints at the identity of the unit. For more
|
||||
* information on the difference between NARROW and SHORT, see SHORT.
|
||||
* Print an abbreviated version of the unit name. Similar to SHORT, but always use the shortest
|
||||
* available abbreviation or symbol. This option can be used when the context hints at the
|
||||
* identity of the unit. For more information on the difference between NARROW and SHORT, see
|
||||
* SHORT.
|
||||
*
|
||||
* <p>
|
||||
* In CLDR, this option corresponds to the "Narrow" format for measure units and the "¤¤¤¤¤" placeholder for
|
||||
* currencies.
|
||||
* In CLDR, this option corresponds to the "Narrow" format for measure units and the "¤¤¤¤¤"
|
||||
* placeholder for currencies.
|
||||
*
|
||||
* @draft ICU 60
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
@ -99,16 +105,16 @@ public final class NumberFormatter {
|
||||
NARROW,
|
||||
|
||||
/**
|
||||
* Print an abbreviated version of the unit name. Similar to NARROW, but use a slightly wider abbreviation or
|
||||
* symbol when there may be ambiguity. This is the default behavior.
|
||||
* Print an abbreviated version of the unit name. Similar to NARROW, but use a slightly wider
|
||||
* abbreviation or symbol when there may be ambiguity. This is the default behavior.
|
||||
*
|
||||
* <p>
|
||||
* For example, in <em>es-US</em>, the SHORT form for Fahrenheit is "{0} °F", but the NARROW form is "{0}°",
|
||||
* since Fahrenheit is the customary unit for temperature in that locale.
|
||||
* For example, in <em>es-US</em>, the SHORT form for Fahrenheit is "{0} °F", but the NARROW form
|
||||
* is "{0}°", since Fahrenheit is the customary unit for temperature in that locale.
|
||||
*
|
||||
* <p>
|
||||
* In CLDR, this option corresponds to the "Short" format for measure units and the "¤" placeholder for
|
||||
* currencies.
|
||||
* In CLDR, this option corresponds to the "Short" format for measure units and the "¤"
|
||||
* placeholder for currencies.
|
||||
*
|
||||
* @draft ICU 60
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
@ -120,8 +126,8 @@ public final class NumberFormatter {
|
||||
* Print the full name of the unit, without any abbreviations.
|
||||
*
|
||||
* <p>
|
||||
* In CLDR, this option corresponds to the default format for measure units and the "¤¤¤" placeholder for
|
||||
* currencies.
|
||||
* In CLDR, this option corresponds to the default format for measure units and the "¤¤¤"
|
||||
* placeholder for currencies.
|
||||
*
|
||||
* @draft ICU 60
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
@ -130,8 +136,8 @@ public final class NumberFormatter {
|
||||
FULL_NAME,
|
||||
|
||||
/**
|
||||
* Use the three-digit ISO XXX code in place of the symbol for displaying currencies. The behavior of this
|
||||
* option is currently undefined for use with measure units.
|
||||
* Use the three-digit ISO XXX code in place of the symbol for displaying currencies. The
|
||||
* behavior of this option is currently undefined for use with measure units.
|
||||
*
|
||||
* <p>
|
||||
* In CLDR, this option corresponds to the "¤¤" placeholder for currencies.
|
||||
@ -143,9 +149,9 @@ public final class NumberFormatter {
|
||||
ISO_CODE,
|
||||
|
||||
/**
|
||||
* Format the number according to the specified unit, but do not display the unit. For currencies, apply
|
||||
* monetary symbols and formats as with SHORT, but omit the currency symbol. For measure units, the behavior is
|
||||
* equivalent to not specifying the unit at all.
|
||||
* Format the number according to the specified unit, but do not display the unit. For
|
||||
* currencies, apply monetary symbols and formats as with SHORT, but omit the currency symbol.
|
||||
* For measure units, the behavior is equivalent to not specifying the unit at all.
|
||||
*
|
||||
* @draft ICU 60
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
@ -155,8 +161,8 @@ public final class NumberFormatter {
|
||||
}
|
||||
|
||||
/**
|
||||
* An enum declaring how to denote positive and negative numbers. Example outputs when formatting 123 and -123 in
|
||||
* <em>en-US</em>:
|
||||
* An enum declaring how to denote positive and negative numbers. Example outputs when formatting 123
|
||||
* and -123 in <em>en-US</em>:
|
||||
*
|
||||
* <ul>
|
||||
* <li>AUTO: "123" and "-123"
|
||||
@ -175,8 +181,8 @@ public final class NumberFormatter {
|
||||
*/
|
||||
public static enum SignDisplay {
|
||||
/**
|
||||
* Show the minus sign on negative numbers, and do not show the sign on positive numbers. This is the default
|
||||
* behavior.
|
||||
* Show the minus sign on negative numbers, and do not show the sign on positive numbers. This is
|
||||
* the default behavior.
|
||||
*
|
||||
* @draft ICU 60
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
@ -203,16 +209,17 @@ public final class NumberFormatter {
|
||||
NEVER,
|
||||
|
||||
/**
|
||||
* Use the locale-dependent accounting format on negative numbers, and do not show the sign on positive numbers.
|
||||
* Use the locale-dependent accounting format on negative numbers, and do not show the sign on
|
||||
* positive numbers.
|
||||
*
|
||||
* <p>
|
||||
* The accounting format is defined in CLDR and varies by locale; in many Western locales, the format is a pair
|
||||
* of parentheses around the number.
|
||||
* The accounting format is defined in CLDR and varies by locale; in many Western locales, the
|
||||
* format is a pair of parentheses around the number.
|
||||
*
|
||||
* <p>
|
||||
* Note: Since CLDR defines the accounting format in the monetary context only, this option falls back to the
|
||||
* AUTO sign display strategy when formatting without a currency unit. This limitation may be lifted in the
|
||||
* future.
|
||||
* Note: Since CLDR defines the accounting format in the monetary context only, this option falls
|
||||
* back to the AUTO sign display strategy when formatting without a currency unit. This
|
||||
* limitation may be lifted in the future.
|
||||
*
|
||||
* @draft ICU 60
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
@ -221,8 +228,9 @@ public final class NumberFormatter {
|
||||
ACCOUNTING,
|
||||
|
||||
/**
|
||||
* Use the locale-dependent accounting format on negative numbers, and show the plus sign on positive numbers.
|
||||
* For more information on the accounting format, see the ACCOUNTING sign display strategy.
|
||||
* Use the locale-dependent accounting format on negative numbers, and show the plus sign on
|
||||
* positive numbers. For more information on the accounting format, see the ACCOUNTING sign
|
||||
* display strategy.
|
||||
*
|
||||
* @draft ICU 60
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
@ -232,8 +240,8 @@ public final class NumberFormatter {
|
||||
}
|
||||
|
||||
/**
|
||||
* An enum declaring how to render the decimal separator. Example outputs when formatting 1 and 1.1 in
|
||||
* <em>en-US</em>:
|
||||
* An enum declaring how to render the decimal separator. Example outputs when formatting 1 and 1.1
|
||||
* in <em>en-US</em>:
|
||||
*
|
||||
* <ul>
|
||||
* <li>AUTO: "1" and "1.1"
|
||||
@ -246,8 +254,8 @@ public final class NumberFormatter {
|
||||
*/
|
||||
public static enum DecimalSeparatorDisplay {
|
||||
/**
|
||||
* Show the decimal separator when there are one or more digits to display after the separator, and do not show
|
||||
* it otherwise. This is the default behavior.
|
||||
* Show the decimal separator when there are one or more digits to display after the separator,
|
||||
* and do not show it otherwise. This is the default behavior.
|
||||
*
|
||||
* @draft ICU 60
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
@ -266,8 +274,9 @@ public final class NumberFormatter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a default threshold of 3. This means that the third time .format() is called, the data structures get built
|
||||
* using the "safe" code path. The first two calls to .format() will trigger the unsafe code path.
|
||||
* Use a default threshold of 3. This means that the third time .format() is called, the data
|
||||
* structures get built using the "safe" code path. The first two calls to .format() will trigger the
|
||||
* unsafe code path.
|
||||
*/
|
||||
static final long DEFAULT_THRESHOLD = 3;
|
||||
|
||||
@ -278,8 +287,8 @@ public final class NumberFormatter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this method at the beginning of a NumberFormatter fluent chain in which the locale is not currently known at
|
||||
* the call site.
|
||||
* Call this method at the beginning of a NumberFormatter fluent chain in which the locale is not
|
||||
* currently known at the call site.
|
||||
*
|
||||
* @return An {@link UnlocalizedNumberFormatter}, to be used for chaining.
|
||||
* @draft ICU 60
|
||||
@ -290,8 +299,8 @@ public final class NumberFormatter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this method at the beginning of a NumberFormatter fluent chain in which the locale is known at the call
|
||||
* site.
|
||||
* Call this method at the beginning of a NumberFormatter fluent chain in which the locale is known
|
||||
* at the call site.
|
||||
*
|
||||
* @param locale
|
||||
* The locale from which to load formats and symbols for number formatting.
|
||||
@ -304,8 +313,8 @@ public final class NumberFormatter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this method at the beginning of a NumberFormatter fluent chain in which the locale is known at the call
|
||||
* site.
|
||||
* Call this method at the beginning of a NumberFormatter fluent chain in which the locale is known
|
||||
* at the call site.
|
||||
*
|
||||
* @param locale
|
||||
* The locale from which to load formats and symbols for number formatting.
|
||||
@ -322,8 +331,10 @@ public final class NumberFormatter {
|
||||
* @deprecated ICU 60 This API is ICU internal only.
|
||||
*/
|
||||
@Deprecated
|
||||
public static UnlocalizedNumberFormatter fromDecimalFormat(DecimalFormatProperties properties,
|
||||
DecimalFormatSymbols symbols, DecimalFormatProperties exportedProperties) {
|
||||
public static UnlocalizedNumberFormatter fromDecimalFormat(
|
||||
DecimalFormatProperties properties,
|
||||
DecimalFormatSymbols symbols,
|
||||
DecimalFormatProperties exportedProperties) {
|
||||
MacroProps macros = NumberPropertyMapper.oldToNew(properties, symbols, exportedProperties);
|
||||
return NumberFormatter.with().macros(macros);
|
||||
}
|
||||
|
@ -25,12 +25,12 @@ import com.ibm.icu.util.Currency;
|
||||
import com.ibm.icu.util.MeasureUnit;
|
||||
|
||||
/**
|
||||
* This is the "brain" of the number formatting pipeline. It ties all the pieces together, taking in a MacroProps and a
|
||||
* DecimalQuantity and outputting a properly formatted number string.
|
||||
* This is the "brain" of the number formatting pipeline. It ties all the pieces together, taking in a
|
||||
* MacroProps and a DecimalQuantity and outputting a properly formatted number string.
|
||||
*
|
||||
* <p>
|
||||
* This class, as well as NumberPropertyMapper, could go into the impl package, but they depend on too many
|
||||
* package-private members of the public APIs.
|
||||
* This class, as well as NumberPropertyMapper, could go into the impl package, but they depend on too
|
||||
* many package-private members of the public APIs.
|
||||
*/
|
||||
class NumberFormatterImpl {
|
||||
|
||||
@ -40,8 +40,13 @@ class NumberFormatterImpl {
|
||||
return new NumberFormatterImpl(microPropsGenerator);
|
||||
}
|
||||
|
||||
/** Builds and evaluates an "unsafe" MicroPropsGenerator, which is cheaper but can be used only once. */
|
||||
public static MicroProps applyStatic(MacroProps macros, DecimalQuantity inValue, NumberStringBuilder outString) {
|
||||
/**
|
||||
* Builds and evaluates an "unsafe" MicroPropsGenerator, which is cheaper but can be used only once.
|
||||
*/
|
||||
public static MicroProps applyStatic(
|
||||
MacroProps macros,
|
||||
DecimalQuantity inValue,
|
||||
NumberStringBuilder outString) {
|
||||
MicroPropsGenerator microPropsGenerator = macrosToMicroGenerator(macros, false);
|
||||
MicroProps micros = microPropsGenerator.processQuantity(inValue);
|
||||
microsToString(micros, inValue, outString);
|
||||
@ -84,17 +89,17 @@ class NumberFormatterImpl {
|
||||
}
|
||||
|
||||
/**
|
||||
* Synthesizes the MacroProps into a MicroPropsGenerator. All information, including the locale, is encoded into the
|
||||
* MicroPropsGenerator, except for the quantity itself, which is left abstract and must be provided to the returned
|
||||
* MicroPropsGenerator instance.
|
||||
* Synthesizes the MacroProps into a MicroPropsGenerator. All information, including the locale, is
|
||||
* encoded into the MicroPropsGenerator, except for the quantity itself, which is left abstract and
|
||||
* must be provided to the returned MicroPropsGenerator instance.
|
||||
*
|
||||
* @see MicroPropsGenerator
|
||||
* @param macros
|
||||
* The {@link MacroProps} to consume. This method does not mutate the MacroProps instance.
|
||||
* @param safe
|
||||
* If true, the returned MicroPropsGenerator will be thread-safe. If false, the returned value will
|
||||
* <em>not</em> be thread-safe, intended for a single "one-shot" use only. Building the thread-safe
|
||||
* object is more expensive.
|
||||
* If true, the returned MicroPropsGenerator will be thread-safe. If false, the returned
|
||||
* value will <em>not</em> be thread-safe, intended for a single "one-shot" use only.
|
||||
* Building the thread-safe object is more expensive.
|
||||
*/
|
||||
private static MicroPropsGenerator macrosToMicroGenerator(MacroProps macros, boolean safe) {
|
||||
MicroProps micros = new MicroProps(safe);
|
||||
@ -109,7 +114,8 @@ class NumberFormatterImpl {
|
||||
boolean isPercent = isNoUnit && unitIsPercent(macros.unit);
|
||||
boolean isPermille = isNoUnit && unitIsPermille(macros.unit);
|
||||
boolean isCldrUnit = !isCurrency && !isNoUnit;
|
||||
boolean isAccounting = macros.sign == SignDisplay.ACCOUNTING || macros.sign == SignDisplay.ACCOUNTING_ALWAYS;
|
||||
boolean isAccounting = macros.sign == SignDisplay.ACCOUNTING
|
||||
|| macros.sign == SignDisplay.ACCOUNTING_ALWAYS;
|
||||
Currency currency = isCurrency ? (Currency) macros.unit : DEFAULT_CURRENCY;
|
||||
UnitWidth unitWidth = UnitWidth.SHORT;
|
||||
if (macros.unitWidth != null) {
|
||||
@ -134,13 +140,15 @@ class NumberFormatterImpl {
|
||||
} else if (!isCurrency || unitWidth == UnitWidth.FULL_NAME) {
|
||||
patternStyle = NumberFormat.NUMBERSTYLE;
|
||||
} else if (isAccounting) {
|
||||
// NOTE: Although ACCOUNTING and ACCOUNTING_ALWAYS are only supported in currencies right now,
|
||||
// NOTE: Although ACCOUNTING and ACCOUNTING_ALWAYS are only supported in currencies right
|
||||
// now,
|
||||
// the API contract allows us to add support to other units in the future.
|
||||
patternStyle = NumberFormat.ACCOUNTINGCURRENCYSTYLE;
|
||||
} else {
|
||||
patternStyle = NumberFormat.CURRENCYSTYLE;
|
||||
}
|
||||
String pattern = NumberFormat.getPatternForStyleAndNumberingSystem(macros.loc, nsName, patternStyle);
|
||||
String pattern = NumberFormat
|
||||
.getPatternForStyleAndNumberingSystem(macros.loc, nsName, patternStyle);
|
||||
ParsedPatternInfo patternInfo = PatternStringParser.parseToPatternInfo(pattern);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -247,7 +255,8 @@ class NumberFormatterImpl {
|
||||
// Lazily create PluralRules
|
||||
rules = PluralRules.forLocale(macros.loc);
|
||||
}
|
||||
chain = LongNameHandler.forMeasureUnit(macros.loc, macros.unit, unitWidth, rules, chain);
|
||||
chain = LongNameHandler
|
||||
.forMeasureUnit(macros.loc, macros.unit, macros.perUnit, unitWidth, rules, chain);
|
||||
} else if (isCurrency && unitWidth == UnitWidth.FULL_NAME) {
|
||||
if (rules == null) {
|
||||
// Lazily create PluralRules
|
||||
@ -267,11 +276,15 @@ class NumberFormatterImpl {
|
||||
// Lazily create PluralRules
|
||||
rules = PluralRules.forLocale(macros.loc);
|
||||
}
|
||||
CompactType compactType = (macros.unit instanceof Currency && macros.unitWidth != UnitWidth.FULL_NAME)
|
||||
? CompactType.CURRENCY
|
||||
: CompactType.DECIMAL;
|
||||
chain = ((CompactNotation) macros.notation).withLocaleData(macros.loc, nsName, compactType, rules,
|
||||
safe ? patternMod : null, chain);
|
||||
CompactType compactType = (macros.unit instanceof Currency
|
||||
&& macros.unitWidth != UnitWidth.FULL_NAME) ? CompactType.CURRENCY
|
||||
: CompactType.DECIMAL;
|
||||
chain = ((CompactNotation) macros.notation).withLocaleData(macros.loc,
|
||||
nsName,
|
||||
compactType,
|
||||
rules,
|
||||
safe ? patternMod : null,
|
||||
chain);
|
||||
}
|
||||
|
||||
return chain;
|
||||
@ -289,7 +302,10 @@ class NumberFormatterImpl {
|
||||
* @param string
|
||||
* The output string. Will be mutated.
|
||||
*/
|
||||
private static void microsToString(MicroProps micros, DecimalQuantity quantity, NumberStringBuilder string) {
|
||||
private static void microsToString(
|
||||
MicroProps micros,
|
||||
DecimalQuantity quantity,
|
||||
NumberStringBuilder string) {
|
||||
micros.rounding.apply(quantity);
|
||||
if (micros.integerWidth.maxInt == -1) {
|
||||
quantity.setIntegerLength(micros.integerWidth.minInt, Integer.MAX_VALUE);
|
||||
@ -309,7 +325,10 @@ class NumberFormatterImpl {
|
||||
}
|
||||
}
|
||||
|
||||
private static int writeNumber(MicroProps micros, DecimalQuantity quantity, NumberStringBuilder string) {
|
||||
private static int writeNumber(
|
||||
MicroProps micros,
|
||||
DecimalQuantity quantity,
|
||||
NumberStringBuilder string) {
|
||||
int length = 0;
|
||||
if (quantity.isInfinite()) {
|
||||
length += string.insert(length, micros.symbols.getInfinity(), NumberFormat.Field.INTEGER);
|
||||
@ -322,9 +341,12 @@ class NumberFormatterImpl {
|
||||
length += writeIntegerDigits(micros, quantity, string);
|
||||
|
||||
// Add the decimal point
|
||||
if (quantity.getLowerDisplayMagnitude() < 0 || micros.decimal == DecimalSeparatorDisplay.ALWAYS) {
|
||||
length += string.insert(length, micros.useCurrency ? micros.symbols.getMonetaryDecimalSeparatorString()
|
||||
: micros.symbols.getDecimalSeparatorString(), NumberFormat.Field.DECIMAL_SEPARATOR);
|
||||
if (quantity.getLowerDisplayMagnitude() < 0
|
||||
|| micros.decimal == DecimalSeparatorDisplay.ALWAYS) {
|
||||
length += string.insert(length,
|
||||
micros.useCurrency ? micros.symbols.getMonetaryDecimalSeparatorString()
|
||||
: micros.symbols.getDecimalSeparatorString(),
|
||||
NumberFormat.Field.DECIMAL_SEPARATOR);
|
||||
}
|
||||
|
||||
// Add the fraction digits
|
||||
@ -334,30 +356,40 @@ class NumberFormatterImpl {
|
||||
return length;
|
||||
}
|
||||
|
||||
private static int writeIntegerDigits(MicroProps micros, DecimalQuantity quantity, NumberStringBuilder string) {
|
||||
private static int writeIntegerDigits(
|
||||
MicroProps micros,
|
||||
DecimalQuantity quantity,
|
||||
NumberStringBuilder string) {
|
||||
int length = 0;
|
||||
int integerCount = quantity.getUpperDisplayMagnitude() + 1;
|
||||
for (int i = 0; i < integerCount; i++) {
|
||||
// Add grouping separator
|
||||
if (micros.grouping.groupAtPosition(i, quantity)) {
|
||||
length += string.insert(0, micros.useCurrency ? micros.symbols.getMonetaryGroupingSeparatorString()
|
||||
: micros.symbols.getGroupingSeparatorString(), NumberFormat.Field.GROUPING_SEPARATOR);
|
||||
length += string.insert(0,
|
||||
micros.useCurrency ? micros.symbols.getMonetaryGroupingSeparatorString()
|
||||
: micros.symbols.getGroupingSeparatorString(),
|
||||
NumberFormat.Field.GROUPING_SEPARATOR);
|
||||
}
|
||||
|
||||
// Get and append the next digit value
|
||||
byte nextDigit = quantity.getDigit(i);
|
||||
if (micros.symbols.getCodePointZero() != -1) {
|
||||
length += string.insertCodePoint(0, micros.symbols.getCodePointZero() + nextDigit,
|
||||
length += string.insertCodePoint(0,
|
||||
micros.symbols.getCodePointZero() + nextDigit,
|
||||
NumberFormat.Field.INTEGER);
|
||||
} else {
|
||||
length += string.insert(0, micros.symbols.getDigitStringsLocal()[nextDigit],
|
||||
length += string.insert(0,
|
||||
micros.symbols.getDigitStringsLocal()[nextDigit],
|
||||
NumberFormat.Field.INTEGER);
|
||||
}
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
private static int writeFractionDigits(MicroProps micros, DecimalQuantity quantity, NumberStringBuilder string) {
|
||||
private static int writeFractionDigits(
|
||||
MicroProps micros,
|
||||
DecimalQuantity quantity,
|
||||
NumberStringBuilder string) {
|
||||
int length = 0;
|
||||
int fractionCount = -quantity.getLowerDisplayMagnitude();
|
||||
for (int i = 0; i < fractionCount; i++) {
|
||||
@ -367,7 +399,8 @@ class NumberFormatterImpl {
|
||||
length += string.appendCodePoint(micros.symbols.getCodePointZero() + nextDigit,
|
||||
NumberFormat.Field.FRACTION);
|
||||
} else {
|
||||
length += string.append(micros.symbols.getDigitStringsLocal()[nextDigit], NumberFormat.Field.FRACTION);
|
||||
length += string.append(micros.symbols.getDigitStringsLocal()[nextDigit],
|
||||
NumberFormat.Field.FRACTION);
|
||||
}
|
||||
}
|
||||
return length;
|
||||
|
@ -16,9 +16,9 @@ import com.ibm.icu.util.NoUnit;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
|
||||
/**
|
||||
* An abstract base class for specifying settings related to number formatting. This class is implemented by
|
||||
* {@link UnlocalizedNumberFormatter} and {@link LocalizedNumberFormatter}. This class is not intended for public
|
||||
* subclassing.
|
||||
* An abstract base class for specifying settings related to number formatting. This class is implemented
|
||||
* by {@link UnlocalizedNumberFormatter} and {@link LocalizedNumberFormatter}. This class is not intended
|
||||
* for public subclassing.
|
||||
*
|
||||
* @draft ICU 60
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
@ -39,7 +39,8 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
|
||||
static final int KEY_SIGN = 10;
|
||||
static final int KEY_DECIMAL = 11;
|
||||
static final int KEY_THRESHOLD = 12;
|
||||
static final int KEY_MAX = 13;
|
||||
static final int KEY_PER_UNIT = 13;
|
||||
static final int KEY_MAX = 14;
|
||||
|
||||
final NumberFormatterSettings<?> parent;
|
||||
final int key;
|
||||
@ -62,8 +63,8 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* All notation styles will be properly localized with locale data, and all notation styles are compatible with
|
||||
* units, rounding strategies, and other number formatter settings.
|
||||
* All notation styles will be properly localized with locale data, and all notation styles are
|
||||
* compatible with units, rounding strategies, and other number formatter settings.
|
||||
*
|
||||
* <p>
|
||||
* Pass this method the return value of a {@link Notation} factory method. For example:
|
||||
@ -96,13 +97,13 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
|
||||
*
|
||||
* <p>
|
||||
* <strong>Note:</strong> The unit can also be specified by passing a {@link Measure} to
|
||||
* {@link LocalizedNumberFormatter#format(Measure)}. Units specified via the format method take precedence over
|
||||
* units specified here. This setter is designed for situations when the unit is constant for the duration of the
|
||||
* number formatting process.
|
||||
* {@link LocalizedNumberFormatter#format(Measure)}. Units specified via the format method take
|
||||
* precedence over units specified here. This setter is designed for situations when the unit is
|
||||
* constant for the duration of the number formatting process.
|
||||
*
|
||||
* <p>
|
||||
* All units will be properly localized with locale data, and all units are compatible with notation styles,
|
||||
* rounding strategies, and other number formatter settings.
|
||||
* All units will be properly localized with locale data, and all units are compatible with notation
|
||||
* styles, rounding strategies, and other number formatter settings.
|
||||
*
|
||||
* <p>
|
||||
* Pass this method any instance of {@link MeasureUnit}. For units of measure:
|
||||
@ -123,6 +124,10 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
|
||||
* NumberFormatter.with().unit(NoUnit.PERCENT)
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* See {@link #perUnit} for information on how to format strings like "5 meters per second".
|
||||
*
|
||||
* <p>
|
||||
* The default is to render without units (equivalent to {@link NoUnit#BASE}).
|
||||
*
|
||||
* @param unit
|
||||
@ -131,6 +136,7 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
|
||||
* @see MeasureUnit
|
||||
* @see Currency
|
||||
* @see NoUnit
|
||||
* @see #perUnit
|
||||
* @draft ICU 60
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
@ -138,6 +144,34 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
|
||||
return create(KEY_UNIT, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a unit to be used in the denominator. For example, to format "3 m/s", pass METER to the unit
|
||||
* and SECOND to the perUnit.
|
||||
*
|
||||
* <p>
|
||||
* Pass this method any instance of {@link MeasureUnit}. For example:
|
||||
*
|
||||
* <pre>
|
||||
* NumberFormatter.with().unit(MeasureUnit.METER).perUnit(MeasureUnit.SECOND)
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* The default is not to display any unit in the denominator.
|
||||
*
|
||||
* <p>
|
||||
* If a per-unit is specified without a primary unit via {@link #unit}, the behavior is undefined.
|
||||
*
|
||||
* @param perUnit
|
||||
* The unit to render in the denominator.
|
||||
* @return The fluent chain
|
||||
* @see #unit
|
||||
* @draft ICU 61
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
public T perUnit(MeasureUnit perUnit) {
|
||||
return create(KEY_PER_UNIT, perUnit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the rounding strategy to use when formatting numbers.
|
||||
*
|
||||
@ -157,10 +191,10 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
|
||||
*
|
||||
* <p>
|
||||
* In most cases, the default rounding strategy is to round to 6 fraction places; i.e.,
|
||||
* <code>Rounder.maxFraction(6)</code>. The exceptions are if compact notation is being used, then the compact
|
||||
* notation rounding strategy is used (see {@link Notation#compactShort} for details), or if the unit is a currency,
|
||||
* then standard currency rounding is used, which varies from currency to currency (see {@link Rounder#currency} for
|
||||
* details).
|
||||
* <code>Rounder.maxFraction(6)</code>. The exceptions are if compact notation is being used, then
|
||||
* the compact notation rounding strategy is used (see {@link Notation#compactShort} for details), or
|
||||
* if the unit is a currency, then standard currency rounding is used, which varies from currency to
|
||||
* currency (see {@link Rounder#currency} for details).
|
||||
*
|
||||
* @param rounder
|
||||
* The rounding strategy to use.
|
||||
@ -237,8 +271,8 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the symbols (decimal separator, grouping separator, percent sign, numerals, etc.) to use when rendering
|
||||
* numbers.
|
||||
* Specifies the symbols (decimal separator, grouping separator, percent sign, numerals, etc.) to use
|
||||
* when rendering numbers.
|
||||
*
|
||||
* <ul>
|
||||
* <li><em>en_US</em> symbols: "12,345.67"
|
||||
@ -255,17 +289,17 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* <strong>Note:</strong> DecimalFormatSymbols automatically chooses the best numbering system based on the locale.
|
||||
* In the examples above, the first three are using the Latin numbering system, and the fourth is using the Myanmar
|
||||
* numbering system.
|
||||
* <strong>Note:</strong> DecimalFormatSymbols automatically chooses the best numbering system based
|
||||
* on the locale. In the examples above, the first three are using the Latin numbering system, and
|
||||
* the fourth is using the Myanmar numbering system.
|
||||
*
|
||||
* <p>
|
||||
* <strong>Note:</strong> The instance of DecimalFormatSymbols will be copied: changes made to the symbols object
|
||||
* after passing it into the fluent chain will not be seen.
|
||||
* <strong>Note:</strong> The instance of DecimalFormatSymbols will be copied: changes made to the
|
||||
* symbols object after passing it into the fluent chain will not be seen.
|
||||
*
|
||||
* <p>
|
||||
* <strong>Note:</strong> Calling this method will override the NumberingSystem previously specified in
|
||||
* {@link #symbols(NumberingSystem)}.
|
||||
* <strong>Note:</strong> Calling this method will override the NumberingSystem previously specified
|
||||
* in {@link #symbols(NumberingSystem)}.
|
||||
*
|
||||
* <p>
|
||||
* The default is to choose the symbols based on the locale specified in the fluent chain.
|
||||
@ -292,16 +326,16 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* Pass this method an instance of {@link NumberingSystem}. For example, to force the locale to always use the Latin
|
||||
* alphabet numbering system (ASCII digits):
|
||||
* Pass this method an instance of {@link NumberingSystem}. For example, to force the locale to
|
||||
* always use the Latin alphabet numbering system (ASCII digits):
|
||||
*
|
||||
* <pre>
|
||||
* NumberFormatter.with().symbols(NumberingSystem.LATIN)
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* <strong>Note:</strong> Calling this method will override the DecimalFormatSymbols previously specified in
|
||||
* {@link #symbols(DecimalFormatSymbols)}.
|
||||
* <strong>Note:</strong> Calling this method will override the DecimalFormatSymbols previously
|
||||
* specified in {@link #symbols(DecimalFormatSymbols)}.
|
||||
*
|
||||
* <p>
|
||||
* The default is to choose the best numbering system for the locale.
|
||||
@ -378,8 +412,8 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the decimal separator display strategy. This affects integer numbers with no fraction part. Most common
|
||||
* values:
|
||||
* Sets the decimal separator display strategy. This affects integer numbers with no fraction part.
|
||||
* Most common values:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Auto: "1"
|
||||
@ -430,8 +464,8 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal fluent setter to support a custom regulation threshold. A threshold of 1 causes the data structures to
|
||||
* be built right away. A threshold of 0 prevents the data structures from being built.
|
||||
* Internal fluent setter to support a custom regulation threshold. A threshold of 1 causes the data
|
||||
* structures to be built right away. A threshold of 0 prevents the data structures from being built.
|
||||
*
|
||||
* @internal
|
||||
* @deprecated ICU 60 This API is ICU internal only.
|
||||
@ -518,6 +552,11 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
|
||||
macros.threshold = (Long) current.value;
|
||||
}
|
||||
break;
|
||||
case KEY_PER_UNIT:
|
||||
if (macros.perUnit == null) {
|
||||
macros.perUnit = (MeasureUnit) current.value;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unknown key: " + current.key);
|
||||
}
|
||||
|
@ -28,20 +28,22 @@ import com.ibm.icu.util.ULocale;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* This class, as well as NumberFormatterImpl, could go into the impl package, but they depend on too many
|
||||
* package-private members of the public APIs.
|
||||
* This class, as well as NumberFormatterImpl, could go into the impl package, but they depend on too
|
||||
* many package-private members of the public APIs.
|
||||
*/
|
||||
final class NumberPropertyMapper {
|
||||
|
||||
/** Convenience method to create a NumberFormatter directly from Properties. */
|
||||
public static UnlocalizedNumberFormatter create(DecimalFormatProperties properties, DecimalFormatSymbols symbols) {
|
||||
public static UnlocalizedNumberFormatter create(
|
||||
DecimalFormatProperties properties,
|
||||
DecimalFormatSymbols symbols) {
|
||||
MacroProps macros = oldToNew(properties, symbols, null);
|
||||
return NumberFormatter.with().macros(macros);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to create a NumberFormatter directly from a pattern string. Something like this could become
|
||||
* public API if there is demand.
|
||||
* Convenience method to create a NumberFormatter directly from a pattern string. Something like this
|
||||
* could become public API if there is demand.
|
||||
*/
|
||||
public static UnlocalizedNumberFormatter create(String pattern, DecimalFormatSymbols symbols) {
|
||||
DecimalFormatProperties properties = PatternStringParser.parseToProperties(pattern);
|
||||
@ -49,9 +51,9 @@ final class NumberPropertyMapper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link MacroProps} object based on the content of a {@link DecimalFormatProperties} object. In
|
||||
* other words, maps Properties to MacroProps. This function is used by the JDK-compatibility API to call into the
|
||||
* ICU 60 fluent number formatting pipeline.
|
||||
* Creates a new {@link MacroProps} object based on the content of a {@link DecimalFormatProperties}
|
||||
* object. In other words, maps Properties to MacroProps. This function is used by the
|
||||
* JDK-compatibility API to call into the ICU 60 fluent number formatting pipeline.
|
||||
*
|
||||
* @param properties
|
||||
* The property bag to be mapped.
|
||||
@ -61,7 +63,9 @@ final class NumberPropertyMapper {
|
||||
* A property bag in which to store validated properties. Used by some DecimalFormat getters.
|
||||
* @return A new MacroProps containing all of the information in the Properties.
|
||||
*/
|
||||
public static MacroProps oldToNew(DecimalFormatProperties properties, DecimalFormatSymbols symbols,
|
||||
public static MacroProps oldToNew(
|
||||
DecimalFormatProperties properties,
|
||||
DecimalFormatSymbols symbols,
|
||||
DecimalFormatProperties exportedProperties) {
|
||||
MacroProps macros = new MacroProps();
|
||||
ULocale locale = symbols.getULocale();
|
||||
@ -94,8 +98,10 @@ final class NumberPropertyMapper {
|
||||
// UNITS //
|
||||
///////////
|
||||
|
||||
boolean useCurrency = ((properties.getCurrency() != null) || properties.getCurrencyPluralInfo() != null
|
||||
|| properties.getCurrencyUsage() != null || affixProvider.hasCurrencySign());
|
||||
boolean useCurrency = ((properties.getCurrency() != null)
|
||||
|| properties.getCurrencyPluralInfo() != null
|
||||
|| properties.getCurrencyUsage() != null
|
||||
|| affixProvider.hasCurrencySign());
|
||||
Currency currency = CustomSymbolCurrency.resolve(properties.getCurrency(), locale, symbols);
|
||||
CurrencyUsage currencyUsage = properties.getCurrencyUsage();
|
||||
boolean explicitCurrencyUsage = currencyUsage != null;
|
||||
@ -120,7 +126,8 @@ final class NumberPropertyMapper {
|
||||
MathContext mathContext = RoundingUtils.getMathContextOrUnlimited(properties);
|
||||
boolean explicitMinMaxFrac = minFrac != -1 || maxFrac != -1;
|
||||
boolean explicitMinMaxSig = minSig != -1 || maxSig != -1;
|
||||
// Resolve min/max frac for currencies, required for the validation logic and for when minFrac or maxFrac was
|
||||
// Resolve min/max frac for currencies, required for the validation logic and for when minFrac or
|
||||
// maxFrac was
|
||||
// set (but not both) on a currency instance.
|
||||
// NOTE: Increments are handled in "Rounder.constructCurrency()".
|
||||
if (useCurrency) {
|
||||
@ -149,7 +156,8 @@ final class NumberPropertyMapper {
|
||||
minFrac = minFrac < 0 ? 0 : minFrac;
|
||||
maxFrac = maxFrac < 0 ? Integer.MAX_VALUE : maxFrac < minFrac ? minFrac : maxFrac;
|
||||
minInt = minInt <= 0 ? 1 : minInt > RoundingUtils.MAX_INT_FRAC_SIG ? 1 : minInt;
|
||||
maxInt = maxInt < 0 ? -1 : maxInt < minInt ? minInt : maxInt > RoundingUtils.MAX_INT_FRAC_SIG ? -1 : maxInt;
|
||||
maxInt = maxInt < 0 ? -1
|
||||
: maxInt < minInt ? minInt : maxInt > RoundingUtils.MAX_INT_FRAC_SIG ? -1 : maxInt;
|
||||
}
|
||||
Rounder rounding = null;
|
||||
if (explicitCurrencyUsage) {
|
||||
@ -157,10 +165,12 @@ final class NumberPropertyMapper {
|
||||
} else if (roundingIncrement != null) {
|
||||
rounding = Rounder.constructIncrement(roundingIncrement);
|
||||
} else if (explicitMinMaxSig) {
|
||||
minSig = minSig < 1 ? 1 : minSig > RoundingUtils.MAX_INT_FRAC_SIG ? RoundingUtils.MAX_INT_FRAC_SIG : minSig;
|
||||
minSig = minSig < 1 ? 1
|
||||
: minSig > RoundingUtils.MAX_INT_FRAC_SIG ? RoundingUtils.MAX_INT_FRAC_SIG : minSig;
|
||||
maxSig = maxSig < 0 ? RoundingUtils.MAX_INT_FRAC_SIG
|
||||
: maxSig < minSig ? minSig
|
||||
: maxSig > RoundingUtils.MAX_INT_FRAC_SIG ? RoundingUtils.MAX_INT_FRAC_SIG : maxSig;
|
||||
: maxSig > RoundingUtils.MAX_INT_FRAC_SIG ? RoundingUtils.MAX_INT_FRAC_SIG
|
||||
: maxSig;
|
||||
rounding = Rounder.constructSignificant(minSig, maxSig);
|
||||
} else if (explicitMinMaxFrac) {
|
||||
rounding = Rounder.constructFraction(minFrac, maxFrac);
|
||||
@ -196,7 +206,8 @@ final class NumberPropertyMapper {
|
||||
/////////////
|
||||
|
||||
if (properties.getFormatWidth() != -1) {
|
||||
macros.padder = new Padder(properties.getPadString(), properties.getFormatWidth(),
|
||||
macros.padder = new Padder(properties.getPadString(),
|
||||
properties.getFormatWidth(),
|
||||
properties.getPadPosition());
|
||||
}
|
||||
|
||||
@ -244,7 +255,8 @@ final class NumberPropertyMapper {
|
||||
// Scientific notation also involves overriding the rounding mode.
|
||||
// TODO: Overriding here is a bit of a hack. Should this logic go earlier?
|
||||
if (macros.rounder instanceof FractionRounder) {
|
||||
// For the purposes of rounding, get the original min/max int/frac, since the local variables
|
||||
// For the purposes of rounding, get the original min/max int/frac, since the local
|
||||
// variables
|
||||
// have been manipulated for display purposes.
|
||||
int minInt_ = properties.getMinimumIntegerDigits();
|
||||
int minFrac_ = properties.getMinimumFractionDigits();
|
||||
|
@ -34,11 +34,12 @@ public abstract class Rounder implements Cloneable {
|
||||
* Show all available digits to full precision.
|
||||
*
|
||||
* <p>
|
||||
* <strong>NOTE:</strong> When formatting a <em>double</em>, this method, along with {@link #minFraction} and
|
||||
* {@link #minDigits}, will trigger complex algorithm similar to <em>Dragon4</em> to determine the low-order digits
|
||||
* and the number of digits to display based on the value of the double. If the number of fraction places or
|
||||
* significant digits can be bounded, consider using {@link #maxFraction} or {@link #maxDigits} instead to maximize
|
||||
* performance. For more information, read the following blog post.
|
||||
* <strong>NOTE:</strong> When formatting a <em>double</em>, this method, along with
|
||||
* {@link #minFraction} and {@link #minDigits}, will trigger complex algorithm similar to
|
||||
* <em>Dragon4</em> to determine the low-order digits and the number of digits to display based on
|
||||
* the value of the double. If the number of fraction places or significant digits can be bounded,
|
||||
* consider using {@link #maxFraction} or {@link #maxDigits} instead to maximize performance. For
|
||||
* more information, read the following blog post.
|
||||
*
|
||||
* <p>
|
||||
* http://www.serpentine.com/blog/2011/06/29/here-be-dragons-advances-in-problems-you-didnt-even-know-you-had/
|
||||
@ -65,8 +66,9 @@ public abstract class Rounder implements Cloneable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Show numbers rounded if necessary to a certain number of fraction places (numerals after the decimal separator).
|
||||
* Additionally, pad with zeros to ensure that this number of places are always shown.
|
||||
* Show numbers rounded if necessary to a certain number of fraction places (numerals after the
|
||||
* decimal separator). Additionally, pad with zeros to ensure that this number of places are always
|
||||
* shown.
|
||||
*
|
||||
* <p>
|
||||
* Example output with minMaxFractionPlaces = 3:
|
||||
@ -86,8 +88,8 @@ public abstract class Rounder implements Cloneable {
|
||||
* This method is equivalent to {@link #minMaxFraction} with both arguments equal.
|
||||
*
|
||||
* @param minMaxFractionPlaces
|
||||
* The minimum and maximum number of numerals to display after the decimal separator (rounding if too
|
||||
* long or padding with zeros if too short).
|
||||
* The minimum and maximum number of numerals to display after the decimal separator
|
||||
* (rounding if too long or padding with zeros if too short).
|
||||
* @return A FractionRounder for chaining or passing to the NumberFormatter rounding() setter.
|
||||
* @draft ICU 60
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
@ -97,111 +99,122 @@ public abstract class Rounder implements Cloneable {
|
||||
if (minMaxFractionPlaces >= 0 && minMaxFractionPlaces <= RoundingUtils.MAX_INT_FRAC_SIG) {
|
||||
return constructFraction(minMaxFractionPlaces, minMaxFractionPlaces);
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Fraction length must be between 0 and " + RoundingUtils.MAX_INT_FRAC_SIG);
|
||||
throw new IllegalArgumentException("Fraction length must be between 0 and "
|
||||
+ RoundingUtils.MAX_INT_FRAC_SIG
|
||||
+ " (inclusive)");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Always show at least a certain number of fraction places after the decimal separator, padding with zeros if
|
||||
* necessary. Do not perform rounding (display numbers to their full precision).
|
||||
* Always show at least a certain number of fraction places after the decimal separator, padding with
|
||||
* zeros if necessary. Do not perform rounding (display numbers to their full precision).
|
||||
*
|
||||
* <p>
|
||||
* <strong>NOTE:</strong> If you are formatting <em>doubles</em>, see the performance note in {@link #unlimited}.
|
||||
* <strong>NOTE:</strong> If you are formatting <em>doubles</em>, see the performance note in
|
||||
* {@link #unlimited}.
|
||||
*
|
||||
* @param minFractionPlaces
|
||||
* The minimum number of numerals to display after the decimal separator (padding with zeros if
|
||||
* necessary).
|
||||
* The minimum number of numerals to display after the decimal separator (padding with
|
||||
* zeros if necessary).
|
||||
* @return A FractionRounder for chaining or passing to the NumberFormatter rounding() setter.
|
||||
* @draft ICU 60
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberFormatter
|
||||
*/
|
||||
public static FractionRounder minFraction(int minFractionPlaces) {
|
||||
if (minFractionPlaces >= 0 && minFractionPlaces < RoundingUtils.MAX_INT_FRAC_SIG) {
|
||||
if (minFractionPlaces >= 0 && minFractionPlaces <= RoundingUtils.MAX_INT_FRAC_SIG) {
|
||||
return constructFraction(minFractionPlaces, -1);
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Fraction length must be between 0 and " + RoundingUtils.MAX_INT_FRAC_SIG);
|
||||
throw new IllegalArgumentException("Fraction length must be between 0 and "
|
||||
+ RoundingUtils.MAX_INT_FRAC_SIG
|
||||
+ " (inclusive)");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show numbers rounded if necessary to a certain number of fraction places (numerals after the decimal separator).
|
||||
* Unlike the other fraction rounding strategies, this strategy does <em>not</em> pad zeros to the end of the
|
||||
* number.
|
||||
* Show numbers rounded if necessary to a certain number of fraction places (numerals after the
|
||||
* decimal separator). Unlike the other fraction rounding strategies, this strategy does <em>not</em>
|
||||
* pad zeros to the end of the number.
|
||||
*
|
||||
* @param maxFractionPlaces
|
||||
* The maximum number of numerals to display after the decimal mark (rounding if necessary).
|
||||
* The maximum number of numerals to display after the decimal mark (rounding if
|
||||
* necessary).
|
||||
* @return A FractionRounder for chaining or passing to the NumberFormatter rounding() setter.
|
||||
* @draft ICU 60
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberFormatter
|
||||
*/
|
||||
public static FractionRounder maxFraction(int maxFractionPlaces) {
|
||||
if (maxFractionPlaces >= 0 && maxFractionPlaces < RoundingUtils.MAX_INT_FRAC_SIG) {
|
||||
if (maxFractionPlaces >= 0 && maxFractionPlaces <= RoundingUtils.MAX_INT_FRAC_SIG) {
|
||||
return constructFraction(0, maxFractionPlaces);
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Fraction length must be between 0 and " + RoundingUtils.MAX_INT_FRAC_SIG);
|
||||
throw new IllegalArgumentException("Fraction length must be between 0 and "
|
||||
+ RoundingUtils.MAX_INT_FRAC_SIG
|
||||
+ " (inclusive)");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show numbers rounded if necessary to a certain number of fraction places (numerals after the decimal separator);
|
||||
* in addition, always show at least a certain number of places after the decimal separator, padding with zeros if
|
||||
* necessary.
|
||||
* Show numbers rounded if necessary to a certain number of fraction places (numerals after the
|
||||
* decimal separator); in addition, always show at least a certain number of places after the decimal
|
||||
* separator, padding with zeros if necessary.
|
||||
*
|
||||
* @param minFractionPlaces
|
||||
* The minimum number of numerals to display after the decimal separator (padding with zeros if
|
||||
* necessary).
|
||||
* The minimum number of numerals to display after the decimal separator (padding with
|
||||
* zeros if necessary).
|
||||
* @param maxFractionPlaces
|
||||
* The maximum number of numerals to display after the decimal separator (rounding if necessary).
|
||||
* The maximum number of numerals to display after the decimal separator (rounding if
|
||||
* necessary).
|
||||
* @return A FractionRounder for chaining or passing to the NumberFormatter rounding() setter.
|
||||
* @draft ICU 60
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberFormatter
|
||||
*/
|
||||
public static FractionRounder minMaxFraction(int minFractionPlaces, int maxFractionPlaces) {
|
||||
if (minFractionPlaces >= 0 && maxFractionPlaces <= RoundingUtils.MAX_INT_FRAC_SIG
|
||||
if (minFractionPlaces >= 0
|
||||
&& maxFractionPlaces <= RoundingUtils.MAX_INT_FRAC_SIG
|
||||
&& minFractionPlaces <= maxFractionPlaces) {
|
||||
return constructFraction(minFractionPlaces, maxFractionPlaces);
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Fraction length must be between 0 and " + RoundingUtils.MAX_INT_FRAC_SIG);
|
||||
throw new IllegalArgumentException("Fraction length must be between 0 and "
|
||||
+ RoundingUtils.MAX_INT_FRAC_SIG
|
||||
+ " (inclusive)");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show numbers rounded if necessary to a certain number of significant digits or significant figures. Additionally,
|
||||
* pad with zeros to ensure that this number of significant digits/figures are always shown.
|
||||
* Show numbers rounded if necessary to a certain number of significant digits or significant
|
||||
* figures. Additionally, pad with zeros to ensure that this number of significant digits/figures are
|
||||
* always shown.
|
||||
*
|
||||
* <p>
|
||||
* This method is equivalent to {@link #minMaxDigits} with both arguments equal.
|
||||
*
|
||||
* @param minMaxSignificantDigits
|
||||
* The minimum and maximum number of significant digits to display (rounding if too long or padding with
|
||||
* zeros if too short).
|
||||
* The minimum and maximum number of significant digits to display (rounding if too long
|
||||
* or padding with zeros if too short).
|
||||
* @return A Rounder for chaining or passing to the NumberFormatter rounding() setter.
|
||||
* @draft ICU 60
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberFormatter
|
||||
*/
|
||||
public static Rounder fixedDigits(int minMaxSignificantDigits) {
|
||||
if (minMaxSignificantDigits > 0 && minMaxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) {
|
||||
if (minMaxSignificantDigits >= 1 && minMaxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) {
|
||||
return constructSignificant(minMaxSignificantDigits, minMaxSignificantDigits);
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Significant digits must be between 0 and " + RoundingUtils.MAX_INT_FRAC_SIG);
|
||||
throw new IllegalArgumentException("Significant digits must be between 1 and "
|
||||
+ RoundingUtils.MAX_INT_FRAC_SIG
|
||||
+ " (inclusive)");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Always show at least a certain number of significant digits/figures, padding with zeros if necessary. Do not
|
||||
* perform rounding (display numbers to their full precision).
|
||||
* Always show at least a certain number of significant digits/figures, padding with zeros if
|
||||
* necessary. Do not perform rounding (display numbers to their full precision).
|
||||
*
|
||||
* <p>
|
||||
* <strong>NOTE:</strong> If you are formatting <em>doubles</em>, see the performance note in {@link #unlimited}.
|
||||
* <strong>NOTE:</strong> If you are formatting <em>doubles</em>, see the performance note in
|
||||
* {@link #unlimited}.
|
||||
*
|
||||
* @param minSignificantDigits
|
||||
* The minimum number of significant digits to display (padding with zeros if too short).
|
||||
@ -211,11 +224,12 @@ public abstract class Rounder implements Cloneable {
|
||||
* @see NumberFormatter
|
||||
*/
|
||||
public static Rounder minDigits(int minSignificantDigits) {
|
||||
if (minSignificantDigits > 0 && minSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) {
|
||||
if (minSignificantDigits >= 1 && minSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) {
|
||||
return constructSignificant(minSignificantDigits, -1);
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Significant digits must be between 0 and " + RoundingUtils.MAX_INT_FRAC_SIG);
|
||||
throw new IllegalArgumentException("Significant digits must be between 1 and "
|
||||
+ RoundingUtils.MAX_INT_FRAC_SIG
|
||||
+ " (inclusive)");
|
||||
}
|
||||
}
|
||||
|
||||
@ -230,17 +244,18 @@ public abstract class Rounder implements Cloneable {
|
||||
* @see NumberFormatter
|
||||
*/
|
||||
public static Rounder maxDigits(int maxSignificantDigits) {
|
||||
if (maxSignificantDigits > 0 && maxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) {
|
||||
if (maxSignificantDigits >= 1 && maxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) {
|
||||
return constructSignificant(0, maxSignificantDigits);
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Significant digits must be between 0 and " + RoundingUtils.MAX_INT_FRAC_SIG);
|
||||
throw new IllegalArgumentException("Significant digits must be between 1 and "
|
||||
+ RoundingUtils.MAX_INT_FRAC_SIG
|
||||
+ " (inclusive)");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show numbers rounded if necessary to a certain number of significant digits/figures; in addition, always show at
|
||||
* least a certain number of significant digits, padding with zeros if necessary.
|
||||
* Show numbers rounded if necessary to a certain number of significant digits/figures; in addition,
|
||||
* always show at least a certain number of significant digits, padding with zeros if necessary.
|
||||
*
|
||||
* @param minSignificantDigits
|
||||
* The minimum number of significant digits to display (padding with zeros if necessary).
|
||||
@ -252,23 +267,26 @@ public abstract class Rounder implements Cloneable {
|
||||
* @see NumberFormatter
|
||||
*/
|
||||
public static Rounder minMaxDigits(int minSignificantDigits, int maxSignificantDigits) {
|
||||
if (minSignificantDigits > 0 && maxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG
|
||||
if (minSignificantDigits >= 1
|
||||
&& maxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG
|
||||
&& minSignificantDigits <= maxSignificantDigits) {
|
||||
return constructSignificant(minSignificantDigits, maxSignificantDigits);
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Significant digits must be between 0 and " + RoundingUtils.MAX_INT_FRAC_SIG);
|
||||
throw new IllegalArgumentException("Significant digits must be between 1 and "
|
||||
+ RoundingUtils.MAX_INT_FRAC_SIG
|
||||
+ " (inclusive)");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show numbers rounded if necessary to the closest multiple of a certain rounding increment. For example, if the
|
||||
* rounding increment is 0.5, then round 1.2 to 1 and round 1.3 to 1.5.
|
||||
* Show numbers rounded if necessary to the closest multiple of a certain rounding increment. For
|
||||
* example, if the rounding increment is 0.5, then round 1.2 to 1 and round 1.3 to 1.5.
|
||||
*
|
||||
* <p>
|
||||
* In order to ensure that numbers are padded to the appropriate number of fraction places, set the scale on the
|
||||
* rounding increment BigDecimal. For example, to round to the nearest 0.5 and always display 2 numerals after the
|
||||
* decimal separator (to display 1.2 as "1.00" and 1.3 as "1.50"), you can run:
|
||||
* In order to ensure that numbers are padded to the appropriate number of fraction places, set the
|
||||
* scale on the rounding increment BigDecimal. For example, to round to the nearest 0.5 and always
|
||||
* display 2 numerals after the decimal separator (to display 1.2 as "1.00" and 1.3 as "1.50"), you
|
||||
* can run:
|
||||
*
|
||||
* <pre>
|
||||
* Rounder.increment(new BigDecimal("0.50"))
|
||||
@ -293,18 +311,20 @@ public abstract class Rounder implements Cloneable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Show numbers rounded and padded according to the rules for the currency unit. The most common rounding settings
|
||||
* for currencies include <code>Rounder.fixedFraction(2)</code>, <code>Rounder.integer()</code>, and
|
||||
* <code>Rounder.increment(0.05)</code> for cash transactions ("nickel rounding").
|
||||
* Show numbers rounded and padded according to the rules for the currency unit. The most common
|
||||
* rounding settings for currencies include <code>Rounder.fixedFraction(2)</code>,
|
||||
* <code>Rounder.integer()</code>, and <code>Rounder.increment(0.05)</code> for cash transactions
|
||||
* ("nickel rounding").
|
||||
*
|
||||
* <p>
|
||||
* The exact rounding details will be resolved at runtime based on the currency unit specified in the
|
||||
* NumberFormatter chain. To round according to the rules for one currency while displaying the symbol for another
|
||||
* currency, the withCurrency() method can be called on the return value of this method.
|
||||
* NumberFormatter chain. To round according to the rules for one currency while displaying the
|
||||
* symbol for another currency, the withCurrency() method can be called on the return value of this
|
||||
* method.
|
||||
*
|
||||
* @param currencyUsage
|
||||
* Either STANDARD (for digital transactions) or CASH (for transactions where the rounding increment may
|
||||
* be limited by the available denominations of cash or coins).
|
||||
* Either STANDARD (for digital transactions) or CASH (for transactions where the rounding
|
||||
* increment may be limited by the available denominations of cash or coins).
|
||||
* @return A CurrencyRounder for chaining or passing to the NumberFormatter rounding() setter.
|
||||
* @draft ICU 60
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
@ -319,8 +339,8 @@ public abstract class Rounder implements Cloneable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link java.math.RoundingMode} to use when picking the direction to round (up or down). Common values
|
||||
* include HALF_EVEN, HALF_UP, and FLOOR. The default is HALF_EVEN.
|
||||
* Sets the {@link java.math.RoundingMode} to use when picking the direction to round (up or down).
|
||||
* Common values include HALF_EVEN, HALF_UP, and FLOOR. The default is HALF_EVEN.
|
||||
*
|
||||
* @param roundingMode
|
||||
* The RoundingMode to use.
|
||||
@ -470,8 +490,8 @@ public abstract class Rounder implements Cloneable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a valid working Rounder. If the Rounder is a CurrencyRounder, applies the given currency. Otherwise,
|
||||
* simply passes through the argument.
|
||||
* Returns a valid working Rounder. If the Rounder is a CurrencyRounder, applies the given currency.
|
||||
* Otherwise, simply passes through the argument.
|
||||
*
|
||||
* @param currency
|
||||
* A currency object to use in case the input object needs it.
|
||||
@ -485,29 +505,58 @@ public abstract class Rounder implements Cloneable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rounding endpoint used by Engineering and Compact notation. Chooses the most appropriate
|
||||
* multiplier (magnitude adjustment), applies the adjustment, rounds, and returns the chosen
|
||||
* multiplier.
|
||||
*
|
||||
* <p>
|
||||
* In most cases, this is simple. However, when rounding the number causes it to cross a multiplier
|
||||
* boundary, we need to re-do the rounding. For example, to display 999,999 in Engineering notation
|
||||
* with 2 sigfigs, first you guess the multiplier to be -3. However, then you end up getting 1000E3,
|
||||
* which is not the correct output. You then change your multiplier to be -6, and you get 1.0E6,
|
||||
* which is correct.
|
||||
*
|
||||
* @param input
|
||||
* The quantity to process.
|
||||
* @param producer
|
||||
* Function to call to return a multiplier based on a magnitude.
|
||||
* @return The number of orders of magnitude the input was adjusted by this method.
|
||||
*/
|
||||
int chooseMultiplierAndApply(DecimalQuantity input, MultiplierProducer producer) {
|
||||
// TODO: Make a better and more efficient implementation.
|
||||
// TODO: Avoid the object creation here.
|
||||
DecimalQuantity copy = input.createCopy();
|
||||
|
||||
// Do not call this method with zero.
|
||||
assert !input.isZero();
|
||||
|
||||
// Perform the first attempt at rounding.
|
||||
int magnitude = input.getMagnitude();
|
||||
int multiplier = producer.getMultiplier(magnitude);
|
||||
input.adjustMagnitude(multiplier);
|
||||
apply(input);
|
||||
|
||||
// If the number turned to zero when rounding, do not re-attempt the rounding.
|
||||
if (!input.isZero() && input.getMagnitude() == magnitude + multiplier + 1) {
|
||||
magnitude += 1;
|
||||
input.copyFrom(copy);
|
||||
multiplier = producer.getMultiplier(magnitude);
|
||||
input.adjustMagnitude(multiplier);
|
||||
assert input.getMagnitude() == magnitude + multiplier - 1;
|
||||
apply(input);
|
||||
assert input.getMagnitude() == magnitude + multiplier;
|
||||
// If the number rounded to zero, exit.
|
||||
if (input.isZero()) {
|
||||
return multiplier;
|
||||
}
|
||||
|
||||
return multiplier;
|
||||
// If the new magnitude after rounding is the same as it was before rounding, then we are done.
|
||||
// This case applies to most numbers.
|
||||
if (input.getMagnitude() == magnitude + multiplier) {
|
||||
return multiplier;
|
||||
}
|
||||
|
||||
// If the above case DIDN'T apply, then we have a case like 99.9 -> 100 or 999.9 -> 1000:
|
||||
// The number rounded up to the next magnitude. Check if the multiplier changes; if it doesn't,
|
||||
// we do not need to make any more adjustments.
|
||||
int _multiplier = producer.getMultiplier(magnitude + 1);
|
||||
if (multiplier == _multiplier) {
|
||||
return multiplier;
|
||||
}
|
||||
|
||||
// We have a case like 999.9 -> 1000, where the correct output is "1K", not "1000".
|
||||
// Fix the magnitude and re-apply the rounding strategy.
|
||||
input.adjustMagnitude(_multiplier - multiplier);
|
||||
apply(input);
|
||||
return _multiplier;
|
||||
}
|
||||
|
||||
///////////////
|
||||
@ -538,7 +587,8 @@ public abstract class Rounder implements Cloneable {
|
||||
@Override
|
||||
public void apply(DecimalQuantity value) {
|
||||
value.roundToMagnitude(getRoundingMagnitudeFraction(maxFrac), mathContext);
|
||||
value.setFractionLength(Math.max(0, -getDisplayMagnitudeFraction(minFrac)), Integer.MAX_VALUE);
|
||||
value.setFractionLength(Math.max(0, -getDisplayMagnitudeFraction(minFrac)),
|
||||
Integer.MAX_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
@ -554,10 +604,14 @@ public abstract class Rounder implements Cloneable {
|
||||
@Override
|
||||
public void apply(DecimalQuantity value) {
|
||||
value.roundToMagnitude(getRoundingMagnitudeSignificant(value, maxSig), mathContext);
|
||||
value.setFractionLength(Math.max(0, -getDisplayMagnitudeSignificant(value, minSig)), Integer.MAX_VALUE);
|
||||
value.setFractionLength(Math.max(0, -getDisplayMagnitudeSignificant(value, minSig)),
|
||||
Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
/** Version of {@link #apply} that obeys minInt constraints. Used for scientific notation compatibility mode. */
|
||||
/**
|
||||
* Version of {@link #apply} that obeys minInt constraints. Used for scientific notation
|
||||
* compatibility mode.
|
||||
*/
|
||||
public void apply(DecimalQuantity quantity, int minInt) {
|
||||
assert quantity.isZero();
|
||||
quantity.setFractionLength(minSig - minInt, Integer.MAX_VALUE);
|
||||
|
@ -15,7 +15,8 @@ import com.ibm.icu.text.DecimalFormatSymbols;
|
||||
import com.ibm.icu.text.NumberFormat;
|
||||
|
||||
/**
|
||||
* A class that defines the scientific notation style to be used when formatting numbers in NumberFormatter.
|
||||
* A class that defines the scientific notation style to be used when formatting numbers in
|
||||
* NumberFormatter.
|
||||
*
|
||||
* <p>
|
||||
* To create a ScientificNotation, use one of the factory methods in {@link Notation}.
|
||||
@ -31,7 +32,10 @@ public class ScientificNotation extends Notation implements Cloneable {
|
||||
int minExponentDigits;
|
||||
SignDisplay exponentSignDisplay;
|
||||
|
||||
/* package-private */ ScientificNotation(int engineeringInterval, boolean requireMinInt, int minExponentDigits,
|
||||
/* package-private */ ScientificNotation(
|
||||
int engineeringInterval,
|
||||
boolean requireMinInt,
|
||||
int minExponentDigits,
|
||||
SignDisplay exponentSignDisplay) {
|
||||
this.engineeringInterval = engineeringInterval;
|
||||
this.requireMinInt = requireMinInt;
|
||||
@ -40,12 +44,12 @@ public class ScientificNotation extends Notation implements Cloneable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum number of digits to show in the exponent of scientific notation, padding with zeros if
|
||||
* necessary. Useful for fixed-width display.
|
||||
* Sets the minimum number of digits to show in the exponent of scientific notation, padding with
|
||||
* zeros if necessary. Useful for fixed-width display.
|
||||
*
|
||||
* <p>
|
||||
* For example, with minExponentDigits=2, the number 123 will be printed as "1.23E02" in <em>en-US</em> instead of
|
||||
* the default "1.23E2".
|
||||
* For example, with minExponentDigits=2, the number 123 will be printed as "1.23E02" in
|
||||
* <em>en-US</em> instead of the default "1.23E2".
|
||||
*
|
||||
* @param minExponentDigits
|
||||
* The minimum number of digits to show in the exponent.
|
||||
@ -55,23 +59,24 @@ public class ScientificNotation extends Notation implements Cloneable {
|
||||
* @see NumberFormatter
|
||||
*/
|
||||
public ScientificNotation withMinExponentDigits(int minExponentDigits) {
|
||||
if (minExponentDigits >= 0 && minExponentDigits < RoundingUtils.MAX_INT_FRAC_SIG) {
|
||||
if (minExponentDigits >= 1 && minExponentDigits <= RoundingUtils.MAX_INT_FRAC_SIG) {
|
||||
ScientificNotation other = (ScientificNotation) this.clone();
|
||||
other.minExponentDigits = minExponentDigits;
|
||||
return other;
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Integer digits must be between 0 and " + RoundingUtils.MAX_INT_FRAC_SIG);
|
||||
throw new IllegalArgumentException("Integer digits must be between 1 and "
|
||||
+ RoundingUtils.MAX_INT_FRAC_SIG
|
||||
+ " (inclusive)");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to show the sign on positive and negative exponents in scientific notation. The default is AUTO,
|
||||
* showing the minus sign but not the plus sign.
|
||||
* Sets whether to show the sign on positive and negative exponents in scientific notation. The
|
||||
* default is AUTO, showing the minus sign but not the plus sign.
|
||||
*
|
||||
* <p>
|
||||
* For example, with exponentSignDisplay=ALWAYS, the number 123 will be printed as "1.23E+2" in <em>en-US</em>
|
||||
* instead of the default "1.23E2".
|
||||
* For example, with exponentSignDisplay=ALWAYS, the number 123 will be printed as "1.23E+2" in
|
||||
* <em>en-US</em> instead of the default "1.23E2".
|
||||
*
|
||||
* @param exponentSignDisplay
|
||||
* The strategy for displaying the sign in the exponent.
|
||||
@ -101,21 +106,27 @@ public class ScientificNotation extends Notation implements Cloneable {
|
||||
}
|
||||
}
|
||||
|
||||
/* package-private */ MicroPropsGenerator withLocaleData(DecimalFormatSymbols symbols, boolean build,
|
||||
/* package-private */ MicroPropsGenerator withLocaleData(
|
||||
DecimalFormatSymbols symbols,
|
||||
boolean build,
|
||||
MicroPropsGenerator parent) {
|
||||
return new ScientificHandler(this, symbols, build, parent);
|
||||
}
|
||||
|
||||
// NOTE: The object lifecycle of ScientificModifier and ScientificHandler differ greatly in Java and C++.
|
||||
// NOTE: The object lifecycle of ScientificModifier and ScientificHandler differ greatly in Java and
|
||||
// C++.
|
||||
//
|
||||
// During formatting, we need to provide an object with state (the exponent) as the inner modifier.
|
||||
//
|
||||
// In Java, where the priority is put on reducing object creations, the unsafe code path re-uses the
|
||||
// ScientificHandler as a ScientificModifier, and the safe code path pre-computes 25 ScientificModifier
|
||||
// ScientificHandler as a ScientificModifier, and the safe code path pre-computes 25
|
||||
// ScientificModifier
|
||||
// instances. This scheme reduces the number of object creations by 1 in both safe and unsafe.
|
||||
//
|
||||
// In C++, MicroProps provides a pre-allocated ScientificModifier, and ScientificHandler simply populates
|
||||
// the state (the exponent) into that ScientificModifier. There is no difference between safe and unsafe.
|
||||
// In C++, MicroProps provides a pre-allocated ScientificModifier, and ScientificHandler simply
|
||||
// populates
|
||||
// the state (the exponent) into that ScientificModifier. There is no difference between safe and
|
||||
// unsafe.
|
||||
|
||||
private static class ScientificHandler implements MicroPropsGenerator, MultiplierProducer, Modifier {
|
||||
|
||||
@ -125,7 +136,10 @@ public class ScientificNotation extends Notation implements Cloneable {
|
||||
final MicroPropsGenerator parent;
|
||||
/* unsafe */ int exponent;
|
||||
|
||||
private ScientificHandler(ScientificNotation notation, DecimalFormatSymbols symbols, boolean safe,
|
||||
private ScientificHandler(
|
||||
ScientificNotation notation,
|
||||
DecimalFormatSymbols symbols,
|
||||
boolean safe,
|
||||
MicroPropsGenerator parent) {
|
||||
this.notation = notation;
|
||||
this.symbols = symbols;
|
||||
@ -152,7 +166,8 @@ public class ScientificNotation extends Notation implements Cloneable {
|
||||
if (quantity.isZero()) {
|
||||
if (notation.requireMinInt && micros.rounding instanceof SignificantRounderImpl) {
|
||||
// Show "00.000E0" on pattern "00.000E0"
|
||||
((SignificantRounderImpl) micros.rounding).apply(quantity, notation.engineeringInterval);
|
||||
((SignificantRounderImpl) micros.rounding).apply(quantity,
|
||||
notation.engineeringInterval);
|
||||
exponent = 0;
|
||||
} else {
|
||||
micros.rounding.apply(quantity);
|
||||
|
@ -6,8 +6,8 @@ package com.ibm.icu.number;
|
||||
* A class that defines the simple notation style to be used when formatting numbers in NumberFormatter.
|
||||
*
|
||||
* <p>
|
||||
* This class exposes no public functionality. To create a SimpleNotation, use one of the factory methods in
|
||||
* {@link Notation}.
|
||||
* This class exposes no public functionality. To create a SimpleNotation, use one of the factory methods
|
||||
* in {@link Notation}.
|
||||
*
|
||||
* @draft ICU 60
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
|
@ -7,7 +7,8 @@ import java.util.Locale;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
|
||||
/**
|
||||
* A NumberFormatter that does not yet have a locale. In order to format numbers, a locale must be specified.
|
||||
* A NumberFormatter that does not yet have a locale. In order to format numbers, a locale must be
|
||||
* specified.
|
||||
*
|
||||
* @see NumberFormatter
|
||||
* @draft ICU 60
|
||||
@ -25,8 +26,8 @@ public class UnlocalizedNumberFormatter extends NumberFormatterSettings<Unlocali
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate the given locale with the number formatter. The locale is used for picking the appropriate symbols,
|
||||
* formats, and other data for number display.
|
||||
* Associate the given locale with the number formatter. The locale is used for picking the
|
||||
* appropriate symbols, formats, and other data for number display.
|
||||
*
|
||||
* <p>
|
||||
* To use the Java default locale, call Locale.getDefault():
|
||||
|
@ -97,7 +97,7 @@ abstract class DictionaryBreakEngine implements LanguageBreakEngine {
|
||||
@Override
|
||||
public Object clone() throws CloneNotSupportedException {
|
||||
DequeI result = (DequeI)super.clone();
|
||||
data = data.clone();
|
||||
result.data = data.clone();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -171,7 +171,7 @@ public class MeasureUnit implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a MeasureUnit instance (creates a singleton instance).
|
||||
* Creates a MeasureUnit instance (creates a singleton instance) or returns one from the cache.
|
||||
* <p>
|
||||
* Normally this method should not be used, since there will be no formatting data
|
||||
* available for it, and it may not be returned by getAvailable().
|
||||
|
@ -192,6 +192,28 @@ public final class UCharacterCaseTest extends TestFmwk
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void TestInvalidCodePointFolding() {
|
||||
int[] invalidCodePoints = {
|
||||
0xD800, // lead surrogate
|
||||
0xDFFF, // trail surrogate
|
||||
0xFDD0, // noncharacter
|
||||
0xFFFF, // noncharacter
|
||||
0x110000, // out of range
|
||||
-1 // negative
|
||||
};
|
||||
for (int cp : invalidCodePoints) {
|
||||
assertEquals("Invalid code points should be echoed back",
|
||||
cp, UCharacter.foldCase(cp, true));
|
||||
assertEquals("Invalid code points should be echoed back",
|
||||
cp, UCharacter.foldCase(cp, false));
|
||||
assertEquals("Invalid code points should be echoed back",
|
||||
cp, UCharacter.foldCase(cp, UCharacter.FOLD_CASE_DEFAULT));
|
||||
assertEquals("Invalid code points should be echoed back",
|
||||
cp, UCharacter.foldCase(cp, UCharacter.FOLD_CASE_EXCLUDE_SPECIAL_I));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Testing the strings case mapping methods
|
||||
*/
|
||||
|
@ -14,209 +14,200 @@ import com.ibm.icu.text.UnicodeSet;
|
||||
|
||||
public class AffixUtilsTest {
|
||||
|
||||
private static final SymbolProvider DEFAULT_SYMBOL_PROVIDER =
|
||||
new SymbolProvider() {
|
||||
@Override
|
||||
public CharSequence getSymbol(int type) {
|
||||
private static final SymbolProvider DEFAULT_SYMBOL_PROVIDER = new SymbolProvider() {
|
||||
@Override
|
||||
public CharSequence getSymbol(int type) {
|
||||
// Use interesting symbols where possible. The symbols are from ar_SA but are hard-coded
|
||||
// here to make the test independent of locale data changes.
|
||||
switch (type) {
|
||||
case AffixUtils.TYPE_MINUS_SIGN:
|
||||
case AffixUtils.TYPE_MINUS_SIGN:
|
||||
return "−";
|
||||
case AffixUtils.TYPE_PLUS_SIGN:
|
||||
case AffixUtils.TYPE_PLUS_SIGN:
|
||||
return "\u061C+";
|
||||
case AffixUtils.TYPE_PERCENT:
|
||||
case AffixUtils.TYPE_PERCENT:
|
||||
return "٪\u061C";
|
||||
case AffixUtils.TYPE_PERMILLE:
|
||||
case AffixUtils.TYPE_PERMILLE:
|
||||
return "؉";
|
||||
case AffixUtils.TYPE_CURRENCY_SINGLE:
|
||||
case AffixUtils.TYPE_CURRENCY_SINGLE:
|
||||
return "$";
|
||||
case AffixUtils.TYPE_CURRENCY_DOUBLE:
|
||||
case AffixUtils.TYPE_CURRENCY_DOUBLE:
|
||||
return "XXX";
|
||||
case AffixUtils.TYPE_CURRENCY_TRIPLE:
|
||||
case AffixUtils.TYPE_CURRENCY_TRIPLE:
|
||||
return "long name";
|
||||
case AffixUtils.TYPE_CURRENCY_QUAD:
|
||||
case AffixUtils.TYPE_CURRENCY_QUAD:
|
||||
return "\uFFFD";
|
||||
case AffixUtils.TYPE_CURRENCY_QUINT:
|
||||
case AffixUtils.TYPE_CURRENCY_QUINT:
|
||||
return "@";
|
||||
case AffixUtils.TYPE_CURRENCY_OVERFLOW:
|
||||
case AffixUtils.TYPE_CURRENCY_OVERFLOW:
|
||||
return "\uFFFD";
|
||||
default:
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Test
|
||||
public void testEscape() {
|
||||
Object[][] cases = {
|
||||
{ "", "" },
|
||||
{ "abc", "abc" },
|
||||
{ "-", "'-'" },
|
||||
{ "-!", "'-'!" },
|
||||
{ "−", "−" },
|
||||
{ "---", "'---'" },
|
||||
{ "-%-", "'-%-'" },
|
||||
{ "'", "''" },
|
||||
{ "-'", "'-'''" },
|
||||
{ "-'-", "'-''-'" },
|
||||
{ "a-'-", "a'-''-'" } };
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Object[] cas : cases) {
|
||||
String input = (String) cas[0];
|
||||
String expected = (String) cas[1];
|
||||
sb.setLength(0);
|
||||
AffixUtils.escape(input, sb);
|
||||
assertEquals(expected, sb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnescape() {
|
||||
Object[][] cases = {
|
||||
{ "", false, 0, "" },
|
||||
{ "abc", false, 3, "abc" },
|
||||
{ "-", false, 1, "−" },
|
||||
{ "-!", false, 2, "−!" },
|
||||
{ "+", false, 1, "\u061C+" },
|
||||
{ "+!", false, 2, "\u061C+!" },
|
||||
{ "‰", false, 1, "؉" },
|
||||
{ "‰!", false, 2, "؉!" },
|
||||
{ "-x", false, 2, "−x" },
|
||||
{ "'-'x", false, 2, "-x" },
|
||||
{ "'--''-'-x", false, 6, "--'-−x" },
|
||||
{ "''", false, 1, "'" },
|
||||
{ "''''", false, 2, "''" },
|
||||
{ "''''''", false, 3, "'''" },
|
||||
{ "''x''", false, 3, "'x'" },
|
||||
{ "¤", true, 1, "$" },
|
||||
{ "¤¤", true, 2, "XXX" },
|
||||
{ "¤¤¤", true, 3, "long name" },
|
||||
{ "¤¤¤¤", true, 4, "\uFFFD" },
|
||||
{ "¤¤¤¤¤", true, 5, "@" },
|
||||
{ "¤¤¤¤¤¤", true, 6, "\uFFFD" },
|
||||
{ "¤¤¤a¤¤¤¤", true, 8, "long namea\uFFFD" },
|
||||
{ "a¤¤¤¤b¤¤¤¤¤c", true, 12, "a\uFFFDb@c" },
|
||||
{ "¤!", true, 2, "$!" },
|
||||
{ "¤¤!", true, 3, "XXX!" },
|
||||
{ "¤¤¤!", true, 4, "long name!" },
|
||||
{ "-¤¤", true, 3, "−XXX" },
|
||||
{ "¤¤-", true, 3, "XXX−" },
|
||||
{ "'¤'", false, 1, "¤" },
|
||||
{ "%", false, 1, "٪\u061C" },
|
||||
{ "'%'", false, 1, "%" },
|
||||
{ "¤'-'%", true, 3, "$-٪\u061C" },
|
||||
{ "#0#@#*#;#", false, 9, "#0#@#*#;#" } };
|
||||
|
||||
for (Object[] cas : cases) {
|
||||
String input = (String) cas[0];
|
||||
boolean curr = (Boolean) cas[1];
|
||||
int length = (Integer) cas[2];
|
||||
String output = (String) cas[3];
|
||||
|
||||
assertEquals("Currency on <" + input + ">", curr, AffixUtils.hasCurrencySymbols(input));
|
||||
assertEquals("Length on <" + input + ">", length, AffixUtils.estimateLength(input));
|
||||
|
||||
String actual = unescapeWithDefaults(input);
|
||||
assertEquals("Output on <" + input + ">", output, actual);
|
||||
|
||||
int ulength = AffixUtils.unescapedCodePointCount(input, DEFAULT_SYMBOL_PROVIDER);
|
||||
assertEquals("Unescaped length on <" + input + ">", output.length(), ulength);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContainsReplaceType() {
|
||||
Object[][] cases = {
|
||||
{ "", false, "" },
|
||||
{ "-", true, "+" },
|
||||
{ "-a", true, "+a" },
|
||||
{ "a-", true, "a+" },
|
||||
{ "a-b", true, "a+b" },
|
||||
{ "--", true, "++" },
|
||||
{ "x", false, "x" } };
|
||||
|
||||
for (Object[] cas : cases) {
|
||||
String input = (String) cas[0];
|
||||
boolean hasMinusSign = (Boolean) cas[1];
|
||||
String output = (String) cas[2];
|
||||
|
||||
assertEquals("Contains on input " + input,
|
||||
hasMinusSign,
|
||||
AffixUtils.containsType(input, AffixUtils.TYPE_MINUS_SIGN));
|
||||
assertEquals("Replace on input" + input,
|
||||
output,
|
||||
AffixUtils.replaceType(input, AffixUtils.TYPE_MINUS_SIGN, '+'));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalid() {
|
||||
String[] invalidExamples = { "'", "x'", "'x", "'x''", "''x'" };
|
||||
|
||||
for (String str : invalidExamples) {
|
||||
try {
|
||||
AffixUtils.hasCurrencySymbols(str);
|
||||
fail("No exception was thrown on an invalid string");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// OK
|
||||
}
|
||||
try {
|
||||
AffixUtils.estimateLength(str);
|
||||
fail("No exception was thrown on an invalid string");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// OK
|
||||
}
|
||||
try {
|
||||
unescapeWithDefaults(str);
|
||||
fail("No exception was thrown on an invalid string");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// OK
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnescapeWithSymbolProvider() {
|
||||
String[][] cases = {
|
||||
{ "", "" },
|
||||
{ "-", "1" },
|
||||
{ "'-'", "-" },
|
||||
{ "- + % ‰ ¤ ¤¤ ¤¤¤ ¤¤¤¤ ¤¤¤¤¤", "1 2 3 4 5 6 7 8 9" },
|
||||
{ "'¤¤¤¤¤¤'", "¤¤¤¤¤¤" },
|
||||
{ "¤¤¤¤¤¤", "\uFFFD" } };
|
||||
|
||||
SymbolProvider provider = new SymbolProvider() {
|
||||
@Override
|
||||
public CharSequence getSymbol(int type) {
|
||||
return Integer.toString(Math.abs(type));
|
||||
}
|
||||
};
|
||||
|
||||
@Test
|
||||
public void testEscape() {
|
||||
Object[][] cases = {
|
||||
{"", ""},
|
||||
{"abc", "abc"},
|
||||
{"-", "'-'"},
|
||||
{"-!", "'-'!"},
|
||||
{"−", "−"},
|
||||
{"---", "'---'"},
|
||||
{"-%-", "'-%-'"},
|
||||
{"'", "''"},
|
||||
{"-'", "'-'''"},
|
||||
{"-'-", "'-''-'"},
|
||||
{"a-'-", "a'-''-'"}
|
||||
};
|
||||
NumberStringBuilder sb = new NumberStringBuilder();
|
||||
for (String[] cas : cases) {
|
||||
String input = cas[0];
|
||||
String expected = cas[1];
|
||||
sb.clear();
|
||||
AffixUtils.unescape(input, sb, 0, provider);
|
||||
assertEquals("With symbol provider on <" + input + ">", expected, sb.toString());
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Object[] cas : cases) {
|
||||
String input = (String) cas[0];
|
||||
String expected = (String) cas[1];
|
||||
sb.setLength(0);
|
||||
AffixUtils.escape(input, sb);
|
||||
assertEquals(expected, sb.toString());
|
||||
// Test insertion position
|
||||
sb.clear();
|
||||
sb.append("abcdefg", null);
|
||||
AffixUtils.unescape("-+%", sb, 4, provider);
|
||||
assertEquals("Symbol provider into middle", "abcd123efg", sb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnescape() {
|
||||
Object[][] cases = {
|
||||
{"", false, 0, ""},
|
||||
{"abc", false, 3, "abc"},
|
||||
{"-", false, 1, "−"},
|
||||
{"-!", false, 2, "−!"},
|
||||
{"+", false, 1, "\u061C+"},
|
||||
{"+!", false, 2, "\u061C+!"},
|
||||
{"‰", false, 1, "؉"},
|
||||
{"‰!", false, 2, "؉!"},
|
||||
{"-x", false, 2, "−x"},
|
||||
{"'-'x", false, 2, "-x"},
|
||||
{"'--''-'-x", false, 6, "--'-−x"},
|
||||
{"''", false, 1, "'"},
|
||||
{"''''", false, 2, "''"},
|
||||
{"''''''", false, 3, "'''"},
|
||||
{"''x''", false, 3, "'x'"},
|
||||
{"¤", true, 1, "$"},
|
||||
{"¤¤", true, 2, "XXX"},
|
||||
{"¤¤¤", true, 3, "long name"},
|
||||
{"¤¤¤¤", true, 4, "\uFFFD"},
|
||||
{"¤¤¤¤¤", true, 5, "@"},
|
||||
{"¤¤¤¤¤¤", true, 6, "\uFFFD"},
|
||||
{"¤¤¤a¤¤¤¤", true, 8, "long namea\uFFFD"},
|
||||
{"a¤¤¤¤b¤¤¤¤¤c", true, 12, "a\uFFFDb@c"},
|
||||
{"¤!", true, 2, "$!"},
|
||||
{"¤¤!", true, 3, "XXX!"},
|
||||
{"¤¤¤!", true, 4, "long name!"},
|
||||
{"-¤¤", true, 3, "−XXX"},
|
||||
{"¤¤-", true, 3, "XXX−"},
|
||||
{"'¤'", false, 1, "¤"},
|
||||
{"%", false, 1, "٪\u061C"},
|
||||
{"'%'", false, 1, "%"},
|
||||
{"¤'-'%", true, 3, "$-٪\u061C"},
|
||||
{"#0#@#*#;#", false, 9, "#0#@#*#;#"}
|
||||
};
|
||||
|
||||
for (Object[] cas : cases) {
|
||||
String input = (String) cas[0];
|
||||
boolean curr = (Boolean) cas[1];
|
||||
int length = (Integer) cas[2];
|
||||
String output = (String) cas[3];
|
||||
|
||||
assertEquals(
|
||||
"Currency on <" + input + ">", curr, AffixUtils.hasCurrencySymbols(input));
|
||||
assertEquals("Length on <" + input + ">", length, AffixUtils.estimateLength(input));
|
||||
|
||||
String actual = unescapeWithDefaults(input);
|
||||
assertEquals("Output on <" + input + ">", output, actual);
|
||||
|
||||
int ulength = AffixUtils.unescapedCodePointCount(input, DEFAULT_SYMBOL_PROVIDER);
|
||||
assertEquals("Unescaped length on <" + input + ">", output.length(), ulength);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContainsReplaceType() {
|
||||
Object[][] cases = {
|
||||
{"", false, ""},
|
||||
{"-", true, "+"},
|
||||
{"-a", true, "+a"},
|
||||
{"a-", true, "a+"},
|
||||
{"a-b", true, "a+b"},
|
||||
{"--", true, "++"},
|
||||
{"x", false, "x"}
|
||||
};
|
||||
|
||||
for (Object[] cas : cases) {
|
||||
String input = (String) cas[0];
|
||||
boolean hasMinusSign = (Boolean) cas[1];
|
||||
String output = (String) cas[2];
|
||||
|
||||
assertEquals(
|
||||
"Contains on input " + input,
|
||||
hasMinusSign,
|
||||
AffixUtils.containsType(input, AffixUtils.TYPE_MINUS_SIGN));
|
||||
assertEquals(
|
||||
"Replace on input" + input,
|
||||
output,
|
||||
AffixUtils.replaceType(input, AffixUtils.TYPE_MINUS_SIGN, '+'));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalid() {
|
||||
String[] invalidExamples = {"'", "x'", "'x", "'x''", "''x'"};
|
||||
|
||||
for (String str : invalidExamples) {
|
||||
try {
|
||||
AffixUtils.hasCurrencySymbols(str);
|
||||
fail("No exception was thrown on an invalid string");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// OK
|
||||
}
|
||||
try {
|
||||
AffixUtils.estimateLength(str);
|
||||
fail("No exception was thrown on an invalid string");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// OK
|
||||
}
|
||||
try {
|
||||
unescapeWithDefaults(str);
|
||||
fail("No exception was thrown on an invalid string");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// OK
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnescapeWithSymbolProvider() {
|
||||
String[][] cases = {
|
||||
{"", ""},
|
||||
{"-", "1"},
|
||||
{"'-'", "-"},
|
||||
{"- + % ‰ ¤ ¤¤ ¤¤¤ ¤¤¤¤ ¤¤¤¤¤", "1 2 3 4 5 6 7 8 9"},
|
||||
{"'¤¤¤¤¤¤'", "¤¤¤¤¤¤"},
|
||||
{"¤¤¤¤¤¤", "\uFFFD"}
|
||||
};
|
||||
|
||||
SymbolProvider provider =
|
||||
new SymbolProvider() {
|
||||
@Override
|
||||
public CharSequence getSymbol(int type) {
|
||||
return Integer.toString(Math.abs(type));
|
||||
}
|
||||
};
|
||||
|
||||
NumberStringBuilder sb = new NumberStringBuilder();
|
||||
for (String[] cas : cases) {
|
||||
String input = cas[0];
|
||||
String expected = cas[1];
|
||||
sb.clear();
|
||||
AffixUtils.unescape(input, sb, 0, provider);
|
||||
assertEquals("With symbol provider on <" + input + ">", expected, sb.toString());
|
||||
}
|
||||
|
||||
// Test insertion position
|
||||
sb.clear();
|
||||
sb.append("abcdefg", null);
|
||||
AffixUtils.unescape("-+%", sb, 4, provider);
|
||||
assertEquals("Symbol provider into middle", "abcd123efg", sb.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithoutSymbolsOrIgnorables() {
|
||||
@ -240,10 +231,10 @@ public class AffixUtilsTest {
|
||||
}
|
||||
}
|
||||
|
||||
private static String unescapeWithDefaults(String input) {
|
||||
NumberStringBuilder nsb = new NumberStringBuilder();
|
||||
int length = AffixUtils.unescape(input, nsb, 0, DEFAULT_SYMBOL_PROVIDER);
|
||||
assertEquals("Return value of unescape", nsb.length(), length);
|
||||
return nsb.toString();
|
||||
}
|
||||
private static String unescapeWithDefaults(String input) {
|
||||
NumberStringBuilder nsb = new NumberStringBuilder();
|
||||
int length = AffixUtils.unescape(input, nsb, 0, DEFAULT_SYMBOL_PROVIDER);
|
||||
assertEquals("Return value of unescape", nsb.length(), length);
|
||||
return nsb.toString();
|
||||
}
|
||||
}
|
||||
|
@ -32,484 +32,469 @@ import com.ibm.icu.util.ULocale;
|
||||
@RunWith(JUnit4.class)
|
||||
public class DecimalQuantityTest extends TestFmwk {
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
public void testBehavior() throws ParseException {
|
||||
@Ignore
|
||||
@Test
|
||||
public void testBehavior() throws ParseException {
|
||||
|
||||
// Make a list of several formatters to test the behavior of DecimalQuantity.
|
||||
List<LocalizedNumberFormatter> formats = new ArrayList<LocalizedNumberFormatter>();
|
||||
// Make a list of several formatters to test the behavior of DecimalQuantity.
|
||||
List<LocalizedNumberFormatter> formats = new ArrayList<LocalizedNumberFormatter>();
|
||||
|
||||
DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(ULocale.ENGLISH);
|
||||
DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(ULocale.ENGLISH);
|
||||
|
||||
DecimalFormatProperties properties = new DecimalFormatProperties();
|
||||
formats.add(NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH));
|
||||
DecimalFormatProperties properties = new DecimalFormatProperties();
|
||||
formats.add(
|
||||
NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH));
|
||||
|
||||
properties =
|
||||
new DecimalFormatProperties()
|
||||
.setMinimumSignificantDigits(3)
|
||||
.setMaximumSignificantDigits(3)
|
||||
.setCompactStyle(CompactStyle.LONG);
|
||||
formats.add(NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH));
|
||||
properties = new DecimalFormatProperties().setMinimumSignificantDigits(3)
|
||||
.setMaximumSignificantDigits(3).setCompactStyle(CompactStyle.LONG);
|
||||
formats.add(
|
||||
NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH));
|
||||
|
||||
properties =
|
||||
new DecimalFormatProperties()
|
||||
.setMinimumExponentDigits(1)
|
||||
.setMaximumIntegerDigits(3)
|
||||
.setMaximumFractionDigits(1);
|
||||
formats.add(NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH));
|
||||
properties = new DecimalFormatProperties().setMinimumExponentDigits(1).setMaximumIntegerDigits(3)
|
||||
.setMaximumFractionDigits(1);
|
||||
formats.add(
|
||||
NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH));
|
||||
|
||||
properties = new DecimalFormatProperties().setRoundingIncrement(new BigDecimal("0.5"));
|
||||
formats.add(NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH));
|
||||
properties = new DecimalFormatProperties().setRoundingIncrement(new BigDecimal("0.5"));
|
||||
formats.add(
|
||||
NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH));
|
||||
|
||||
String[] cases = {
|
||||
"1.0",
|
||||
"2.01",
|
||||
"1234.56",
|
||||
"3000.0",
|
||||
"0.00026418",
|
||||
"0.01789261",
|
||||
"468160.0",
|
||||
"999000.0",
|
||||
"999900.0",
|
||||
"999990.0",
|
||||
"0.0",
|
||||
"12345678901.0",
|
||||
"-5193.48",
|
||||
};
|
||||
String[] cases = {
|
||||
"1.0",
|
||||
"2.01",
|
||||
"1234.56",
|
||||
"3000.0",
|
||||
"0.00026418",
|
||||
"0.01789261",
|
||||
"468160.0",
|
||||
"999000.0",
|
||||
"999900.0",
|
||||
"999990.0",
|
||||
"0.0",
|
||||
"12345678901.0",
|
||||
"-5193.48", };
|
||||
|
||||
String[] hardCases = {
|
||||
"9999999999999900.0",
|
||||
"789000000000000000000000.0",
|
||||
"789123123567853156372158.0",
|
||||
"987654321987654321987654321987654321987654311987654321.0",
|
||||
};
|
||||
String[] hardCases = {
|
||||
"9999999999999900.0",
|
||||
"789000000000000000000000.0",
|
||||
"789123123567853156372158.0",
|
||||
"987654321987654321987654321987654321987654311987654321.0", };
|
||||
|
||||
String[] doubleCases = {
|
||||
"512.0000000000017",
|
||||
"4095.9999999999977",
|
||||
"4095.999999999998",
|
||||
"4095.9999999999986",
|
||||
"4095.999999999999",
|
||||
"4095.9999999999995",
|
||||
"4096.000000000001",
|
||||
"4096.000000000002",
|
||||
"4096.000000000003",
|
||||
"4096.000000000004",
|
||||
"4096.000000000005",
|
||||
"4096.0000000000055",
|
||||
"4096.000000000006",
|
||||
"4096.000000000007",
|
||||
};
|
||||
String[] doubleCases = {
|
||||
"512.0000000000017",
|
||||
"4095.9999999999977",
|
||||
"4095.999999999998",
|
||||
"4095.9999999999986",
|
||||
"4095.999999999999",
|
||||
"4095.9999999999995",
|
||||
"4096.000000000001",
|
||||
"4096.000000000002",
|
||||
"4096.000000000003",
|
||||
"4096.000000000004",
|
||||
"4096.000000000005",
|
||||
"4096.0000000000055",
|
||||
"4096.000000000006",
|
||||
"4096.000000000007", };
|
||||
|
||||
int i = 0;
|
||||
for (String str : cases) {
|
||||
testDecimalQuantity(i++, str, formats, 0);
|
||||
int i = 0;
|
||||
for (String str : cases) {
|
||||
testDecimalQuantity(i++, str, formats, 0);
|
||||
}
|
||||
|
||||
i = 0;
|
||||
for (String str : hardCases) {
|
||||
testDecimalQuantity(i++, str, formats, 1);
|
||||
}
|
||||
|
||||
i = 0;
|
||||
for (String str : doubleCases) {
|
||||
testDecimalQuantity(i++, str, formats, 2);
|
||||
}
|
||||
}
|
||||
|
||||
i = 0;
|
||||
for (String str : hardCases) {
|
||||
testDecimalQuantity(i++, str, formats, 1);
|
||||
static void testDecimalQuantity(
|
||||
int t,
|
||||
String str,
|
||||
List<LocalizedNumberFormatter> formats,
|
||||
int mode) {
|
||||
if (mode == 2) {
|
||||
assertEquals("Double is not valid", Double.toString(Double.parseDouble(str)), str);
|
||||
}
|
||||
|
||||
List<DecimalQuantity> qs = new ArrayList<DecimalQuantity>();
|
||||
BigDecimal d = new BigDecimal(str);
|
||||
qs.add(new DecimalQuantity_SimpleStorage(d));
|
||||
if (mode == 0)
|
||||
qs.add(new DecimalQuantity_64BitBCD(d));
|
||||
qs.add(new DecimalQuantity_ByteArrayBCD(d));
|
||||
qs.add(new DecimalQuantity_DualStorageBCD(d));
|
||||
|
||||
if (new BigDecimal(Double.toString(d.doubleValue())).compareTo(d) == 0) {
|
||||
double dv = d.doubleValue();
|
||||
qs.add(new DecimalQuantity_SimpleStorage(dv));
|
||||
if (mode == 0)
|
||||
qs.add(new DecimalQuantity_64BitBCD(dv));
|
||||
qs.add(new DecimalQuantity_ByteArrayBCD(dv));
|
||||
qs.add(new DecimalQuantity_DualStorageBCD(dv));
|
||||
}
|
||||
|
||||
if (new BigDecimal(Long.toString(d.longValue())).compareTo(d) == 0) {
|
||||
double lv = d.longValue();
|
||||
qs.add(new DecimalQuantity_SimpleStorage(lv));
|
||||
if (mode == 0)
|
||||
qs.add(new DecimalQuantity_64BitBCD(lv));
|
||||
qs.add(new DecimalQuantity_ByteArrayBCD(lv));
|
||||
qs.add(new DecimalQuantity_DualStorageBCD(lv));
|
||||
}
|
||||
|
||||
testDecimalQuantityExpectedOutput(qs.get(0), str);
|
||||
|
||||
if (qs.size() == 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 1; i < qs.size(); i++) {
|
||||
DecimalQuantity q0 = qs.get(0);
|
||||
DecimalQuantity q1 = qs.get(i);
|
||||
testDecimalQuantityExpectedOutput(q1, str);
|
||||
testDecimalQuantityRounding(q0, q1);
|
||||
testDecimalQuantityRoundingInterval(q0, q1);
|
||||
testDecimalQuantityMath(q0, q1);
|
||||
testDecimalQuantityWithFormats(q0, q1, formats);
|
||||
}
|
||||
}
|
||||
|
||||
i = 0;
|
||||
for (String str : doubleCases) {
|
||||
testDecimalQuantity(i++, str, formats, 2);
|
||||
}
|
||||
}
|
||||
|
||||
static void testDecimalQuantity(int t, String str, List<LocalizedNumberFormatter> formats, int mode) {
|
||||
if (mode == 2) {
|
||||
assertEquals("Double is not valid", Double.toString(Double.parseDouble(str)), str);
|
||||
private static void testDecimalQuantityExpectedOutput(DecimalQuantity rq, String expected) {
|
||||
DecimalQuantity q0 = rq.createCopy();
|
||||
// Force an accurate double
|
||||
q0.roundToInfinity();
|
||||
q0.setIntegerLength(1, Integer.MAX_VALUE);
|
||||
q0.setFractionLength(1, Integer.MAX_VALUE);
|
||||
String actual = q0.toPlainString();
|
||||
assertEquals("Unexpected output from simple string conversion (" + q0 + ")", expected, actual);
|
||||
}
|
||||
|
||||
List<DecimalQuantity> qs = new ArrayList<DecimalQuantity>();
|
||||
BigDecimal d = new BigDecimal(str);
|
||||
qs.add(new DecimalQuantity_SimpleStorage(d));
|
||||
if (mode == 0) qs.add(new DecimalQuantity_64BitBCD(d));
|
||||
qs.add(new DecimalQuantity_ByteArrayBCD(d));
|
||||
qs.add(new DecimalQuantity_DualStorageBCD(d));
|
||||
private static final MathContext MATH_CONTEXT_HALF_EVEN = new MathContext(0, RoundingMode.HALF_EVEN);
|
||||
private static final MathContext MATH_CONTEXT_CEILING = new MathContext(0, RoundingMode.CEILING);
|
||||
private static final MathContext MATH_CONTEXT_FLOOR = new MathContext(0, RoundingMode.FLOOR);
|
||||
private static final MathContext MATH_CONTEXT_PRECISION = new MathContext(3, RoundingMode.HALF_UP);
|
||||
|
||||
if (new BigDecimal(Double.toString(d.doubleValue())).compareTo(d) == 0) {
|
||||
double dv = d.doubleValue();
|
||||
qs.add(new DecimalQuantity_SimpleStorage(dv));
|
||||
if (mode == 0) qs.add(new DecimalQuantity_64BitBCD(dv));
|
||||
qs.add(new DecimalQuantity_ByteArrayBCD(dv));
|
||||
qs.add(new DecimalQuantity_DualStorageBCD(dv));
|
||||
private static void testDecimalQuantityRounding(DecimalQuantity rq0, DecimalQuantity rq1) {
|
||||
DecimalQuantity q0 = rq0.createCopy();
|
||||
DecimalQuantity q1 = rq1.createCopy();
|
||||
q0.roundToMagnitude(-1, MATH_CONTEXT_HALF_EVEN);
|
||||
q1.roundToMagnitude(-1, MATH_CONTEXT_HALF_EVEN);
|
||||
testDecimalQuantityBehavior(q0, q1);
|
||||
|
||||
q0 = rq0.createCopy();
|
||||
q1 = rq1.createCopy();
|
||||
q0.roundToMagnitude(-1, MATH_CONTEXT_CEILING);
|
||||
q1.roundToMagnitude(-1, MATH_CONTEXT_CEILING);
|
||||
testDecimalQuantityBehavior(q0, q1);
|
||||
|
||||
q0 = rq0.createCopy();
|
||||
q1 = rq1.createCopy();
|
||||
q0.roundToMagnitude(-1, MATH_CONTEXT_PRECISION);
|
||||
q1.roundToMagnitude(-1, MATH_CONTEXT_PRECISION);
|
||||
testDecimalQuantityBehavior(q0, q1);
|
||||
}
|
||||
|
||||
if (new BigDecimal(Long.toString(d.longValue())).compareTo(d) == 0) {
|
||||
double lv = d.longValue();
|
||||
qs.add(new DecimalQuantity_SimpleStorage(lv));
|
||||
if (mode == 0) qs.add(new DecimalQuantity_64BitBCD(lv));
|
||||
qs.add(new DecimalQuantity_ByteArrayBCD(lv));
|
||||
qs.add(new DecimalQuantity_DualStorageBCD(lv));
|
||||
private static void testDecimalQuantityRoundingInterval(DecimalQuantity rq0, DecimalQuantity rq1) {
|
||||
DecimalQuantity q0 = rq0.createCopy();
|
||||
DecimalQuantity q1 = rq1.createCopy();
|
||||
q0.roundToIncrement(new BigDecimal("0.05"), MATH_CONTEXT_HALF_EVEN);
|
||||
q1.roundToIncrement(new BigDecimal("0.05"), MATH_CONTEXT_HALF_EVEN);
|
||||
testDecimalQuantityBehavior(q0, q1);
|
||||
|
||||
q0 = rq0.createCopy();
|
||||
q1 = rq1.createCopy();
|
||||
q0.roundToIncrement(new BigDecimal("0.05"), MATH_CONTEXT_CEILING);
|
||||
q1.roundToIncrement(new BigDecimal("0.05"), MATH_CONTEXT_CEILING);
|
||||
testDecimalQuantityBehavior(q0, q1);
|
||||
}
|
||||
|
||||
testDecimalQuantityExpectedOutput(qs.get(0), str);
|
||||
private static void testDecimalQuantityMath(DecimalQuantity rq0, DecimalQuantity rq1) {
|
||||
DecimalQuantity q0 = rq0.createCopy();
|
||||
DecimalQuantity q1 = rq1.createCopy();
|
||||
q0.adjustMagnitude(-3);
|
||||
q1.adjustMagnitude(-3);
|
||||
testDecimalQuantityBehavior(q0, q1);
|
||||
|
||||
if (qs.size() == 1) {
|
||||
return;
|
||||
q0 = rq0.createCopy();
|
||||
q1 = rq1.createCopy();
|
||||
q0.multiplyBy(new BigDecimal("3.14159"));
|
||||
q1.multiplyBy(new BigDecimal("3.14159"));
|
||||
testDecimalQuantityBehavior(q0, q1);
|
||||
}
|
||||
|
||||
for (int i = 1; i < qs.size(); i++) {
|
||||
DecimalQuantity q0 = qs.get(0);
|
||||
DecimalQuantity q1 = qs.get(i);
|
||||
testDecimalQuantityExpectedOutput(q1, str);
|
||||
testDecimalQuantityRounding(q0, q1);
|
||||
testDecimalQuantityRoundingInterval(q0, q1);
|
||||
testDecimalQuantityMath(q0, q1);
|
||||
testDecimalQuantityWithFormats(q0, q1, formats);
|
||||
}
|
||||
}
|
||||
|
||||
private static void testDecimalQuantityExpectedOutput(DecimalQuantity rq, String expected) {
|
||||
DecimalQuantity q0 = rq.createCopy();
|
||||
// Force an accurate double
|
||||
q0.roundToInfinity();
|
||||
q0.setIntegerLength(1, Integer.MAX_VALUE);
|
||||
q0.setFractionLength(1, Integer.MAX_VALUE);
|
||||
String actual = q0.toPlainString();
|
||||
assertEquals("Unexpected output from simple string conversion (" + q0 + ")", expected, actual);
|
||||
}
|
||||
|
||||
private static final MathContext MATH_CONTEXT_HALF_EVEN =
|
||||
new MathContext(0, RoundingMode.HALF_EVEN);
|
||||
private static final MathContext MATH_CONTEXT_CEILING = new MathContext(0, RoundingMode.CEILING);
|
||||
private static final MathContext MATH_CONTEXT_FLOOR = new MathContext(0, RoundingMode.FLOOR);
|
||||
private static final MathContext MATH_CONTEXT_PRECISION =
|
||||
new MathContext(3, RoundingMode.HALF_UP);
|
||||
|
||||
private static void testDecimalQuantityRounding(DecimalQuantity rq0, DecimalQuantity rq1) {
|
||||
DecimalQuantity q0 = rq0.createCopy();
|
||||
DecimalQuantity q1 = rq1.createCopy();
|
||||
q0.roundToMagnitude(-1, MATH_CONTEXT_HALF_EVEN);
|
||||
q1.roundToMagnitude(-1, MATH_CONTEXT_HALF_EVEN);
|
||||
testDecimalQuantityBehavior(q0, q1);
|
||||
|
||||
q0 = rq0.createCopy();
|
||||
q1 = rq1.createCopy();
|
||||
q0.roundToMagnitude(-1, MATH_CONTEXT_CEILING);
|
||||
q1.roundToMagnitude(-1, MATH_CONTEXT_CEILING);
|
||||
testDecimalQuantityBehavior(q0, q1);
|
||||
|
||||
q0 = rq0.createCopy();
|
||||
q1 = rq1.createCopy();
|
||||
q0.roundToMagnitude(-1, MATH_CONTEXT_PRECISION);
|
||||
q1.roundToMagnitude(-1, MATH_CONTEXT_PRECISION);
|
||||
testDecimalQuantityBehavior(q0, q1);
|
||||
|
||||
q0 = rq0.createCopy();
|
||||
q1 = rq1.createCopy();
|
||||
q0.roundToMagnitude(0, MATH_CONTEXT_FLOOR);
|
||||
q1.truncate();
|
||||
testDecimalQuantityBehavior(q0, q1);
|
||||
|
||||
q0 = rq0.createCopy();
|
||||
q1 = rq1.createCopy();
|
||||
q0.truncate();
|
||||
q1.roundToMagnitude(0, MATH_CONTEXT_FLOOR);
|
||||
testDecimalQuantityBehavior(q0, q1);
|
||||
}
|
||||
|
||||
private static void testDecimalQuantityRoundingInterval(DecimalQuantity rq0, DecimalQuantity rq1) {
|
||||
DecimalQuantity q0 = rq0.createCopy();
|
||||
DecimalQuantity q1 = rq1.createCopy();
|
||||
q0.roundToIncrement(new BigDecimal("0.05"), MATH_CONTEXT_HALF_EVEN);
|
||||
q1.roundToIncrement(new BigDecimal("0.05"), MATH_CONTEXT_HALF_EVEN);
|
||||
testDecimalQuantityBehavior(q0, q1);
|
||||
|
||||
q0 = rq0.createCopy();
|
||||
q1 = rq1.createCopy();
|
||||
q0.roundToIncrement(new BigDecimal("0.05"), MATH_CONTEXT_CEILING);
|
||||
q1.roundToIncrement(new BigDecimal("0.05"), MATH_CONTEXT_CEILING);
|
||||
testDecimalQuantityBehavior(q0, q1);
|
||||
}
|
||||
|
||||
private static void testDecimalQuantityMath(DecimalQuantity rq0, DecimalQuantity rq1) {
|
||||
DecimalQuantity q0 = rq0.createCopy();
|
||||
DecimalQuantity q1 = rq1.createCopy();
|
||||
q0.adjustMagnitude(-3);
|
||||
q1.adjustMagnitude(-3);
|
||||
testDecimalQuantityBehavior(q0, q1);
|
||||
|
||||
q0 = rq0.createCopy();
|
||||
q1 = rq1.createCopy();
|
||||
q0.multiplyBy(new BigDecimal("3.14159"));
|
||||
q1.multiplyBy(new BigDecimal("3.14159"));
|
||||
testDecimalQuantityBehavior(q0, q1);
|
||||
}
|
||||
|
||||
private static void testDecimalQuantityWithFormats(
|
||||
DecimalQuantity rq0, DecimalQuantity rq1, List<LocalizedNumberFormatter> formats) {
|
||||
for (LocalizedNumberFormatter format : formats) {
|
||||
DecimalQuantity q0 = rq0.createCopy();
|
||||
DecimalQuantity q1 = rq1.createCopy();
|
||||
String s1 = format.format(q0).toString();
|
||||
String s2 = format.format(q1).toString();
|
||||
assertEquals("Different output from formatter (" + q0 + ", " + q1 + ")", s1, s2);
|
||||
}
|
||||
}
|
||||
|
||||
private static void testDecimalQuantityBehavior(DecimalQuantity rq0, DecimalQuantity rq1) {
|
||||
DecimalQuantity q0 = rq0.createCopy();
|
||||
DecimalQuantity q1 = rq1.createCopy();
|
||||
|
||||
assertEquals("Different sign (" + q0 + ", " + q1 + ")", q0.isNegative(), q1.isNegative());
|
||||
|
||||
assertEquals(
|
||||
"Different fingerprint (" + q0 + ", " + q1 + ")",
|
||||
q0.getPositionFingerprint(),
|
||||
q1.getPositionFingerprint());
|
||||
|
||||
assertDoubleEquals(
|
||||
"Different double values (" + q0 + ", " + q1 + ")", q0.toDouble(), q1.toDouble());
|
||||
|
||||
assertBigDecimalEquals(
|
||||
"Different BigDecimal values (" + q0 + ", " + q1 + ")",
|
||||
q0.toBigDecimal(),
|
||||
q1.toBigDecimal());
|
||||
|
||||
q0.roundToInfinity();
|
||||
q1.roundToInfinity();
|
||||
|
||||
assertEquals(
|
||||
"Different lower display magnitude",
|
||||
q0.getLowerDisplayMagnitude(),
|
||||
q1.getLowerDisplayMagnitude());
|
||||
assertEquals(
|
||||
"Different upper display magnitude",
|
||||
q0.getUpperDisplayMagnitude(),
|
||||
q1.getUpperDisplayMagnitude());
|
||||
|
||||
for (int m = q0.getUpperDisplayMagnitude(); m >= q0.getLowerDisplayMagnitude(); m--) {
|
||||
assertEquals(
|
||||
"Different digit at magnitude " + m + " (" + q0 + ", " + q1 + ")",
|
||||
q0.getDigit(m),
|
||||
q1.getDigit(m));
|
||||
private static void testDecimalQuantityWithFormats(
|
||||
DecimalQuantity rq0,
|
||||
DecimalQuantity rq1,
|
||||
List<LocalizedNumberFormatter> formats) {
|
||||
for (LocalizedNumberFormatter format : formats) {
|
||||
DecimalQuantity q0 = rq0.createCopy();
|
||||
DecimalQuantity q1 = rq1.createCopy();
|
||||
String s1 = format.format(q0).toString();
|
||||
String s2 = format.format(q1).toString();
|
||||
assertEquals("Different output from formatter (" + q0 + ", " + q1 + ")", s1, s2);
|
||||
}
|
||||
}
|
||||
|
||||
if (rq0 instanceof DecimalQuantity_DualStorageBCD) {
|
||||
String message = ((DecimalQuantity_DualStorageBCD) rq0).checkHealth();
|
||||
if (message != null) errln(message);
|
||||
}
|
||||
if (rq1 instanceof DecimalQuantity_DualStorageBCD) {
|
||||
String message = ((DecimalQuantity_DualStorageBCD) rq1).checkHealth();
|
||||
if (message != null) errln(message);
|
||||
}
|
||||
}
|
||||
private static void testDecimalQuantityBehavior(DecimalQuantity rq0, DecimalQuantity rq1) {
|
||||
DecimalQuantity q0 = rq0.createCopy();
|
||||
DecimalQuantity q1 = rq1.createCopy();
|
||||
|
||||
@Test
|
||||
public void testSwitchStorage() {
|
||||
DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD();
|
||||
assertEquals("Different sign (" + q0 + ", " + q1 + ")", q0.isNegative(), q1.isNegative());
|
||||
|
||||
fq.setToLong(1234123412341234L);
|
||||
assertFalse("Should not be using byte array", fq.isUsingBytes());
|
||||
assertEquals("Failed on initialize", "1234123412341234E0", fq.toNumberString());
|
||||
assertNull("Failed health check", fq.checkHealth());
|
||||
// Long -> Bytes
|
||||
fq.appendDigit((byte) 5, 0, true);
|
||||
assertTrue("Should be using byte array", fq.isUsingBytes());
|
||||
assertEquals("Failed on multiply", "12341234123412345E0", fq.toNumberString());
|
||||
assertNull("Failed health check", fq.checkHealth());
|
||||
// Bytes -> Long
|
||||
fq.roundToMagnitude(5, MATH_CONTEXT_HALF_EVEN);
|
||||
assertFalse("Should not be using byte array", fq.isUsingBytes());
|
||||
assertEquals("Failed on round", "123412341234E5", fq.toNumberString());
|
||||
assertNull("Failed health check", fq.checkHealth());
|
||||
}
|
||||
assertEquals("Different fingerprint (" + q0 + ", " + q1 + ")",
|
||||
q0.getPositionFingerprint(),
|
||||
q1.getPositionFingerprint());
|
||||
|
||||
@Test
|
||||
public void testAppend() {
|
||||
DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD();
|
||||
fq.appendDigit((byte) 1, 0, true);
|
||||
assertEquals("Failed on append", "1E0", fq.toNumberString());
|
||||
assertNull("Failed health check", fq.checkHealth());
|
||||
fq.appendDigit((byte) 2, 0, true);
|
||||
assertEquals("Failed on append", "12E0", fq.toNumberString());
|
||||
assertNull("Failed health check", fq.checkHealth());
|
||||
fq.appendDigit((byte) 3, 1, true);
|
||||
assertEquals("Failed on append", "1203E0", fq.toNumberString());
|
||||
assertNull("Failed health check", fq.checkHealth());
|
||||
fq.appendDigit((byte) 0, 1, true);
|
||||
assertEquals("Failed on append", "1203E2", fq.toNumberString());
|
||||
assertNull("Failed health check", fq.checkHealth());
|
||||
fq.appendDigit((byte) 4, 0, true);
|
||||
assertEquals("Failed on append", "1203004E0", fq.toNumberString());
|
||||
assertNull("Failed health check", fq.checkHealth());
|
||||
fq.appendDigit((byte) 0, 0, true);
|
||||
assertEquals("Failed on append", "1203004E1", fq.toNumberString());
|
||||
assertNull("Failed health check", fq.checkHealth());
|
||||
fq.appendDigit((byte) 5, 0, false);
|
||||
assertEquals("Failed on append", "120300405E-1", fq.toNumberString());
|
||||
assertNull("Failed health check", fq.checkHealth());
|
||||
fq.appendDigit((byte) 6, 0, false);
|
||||
assertEquals("Failed on append", "1203004056E-2", fq.toNumberString());
|
||||
assertNull("Failed health check", fq.checkHealth());
|
||||
fq.appendDigit((byte) 7, 3, false);
|
||||
assertEquals("Failed on append", "12030040560007E-6", fq.toNumberString());
|
||||
assertNull("Failed health check", fq.checkHealth());
|
||||
StringBuilder baseExpected = new StringBuilder("12030040560007");
|
||||
for (int i = 0; i < 10; i++) {
|
||||
fq.appendDigit((byte) 8, 0, false);
|
||||
baseExpected.append('8');
|
||||
StringBuilder expected = new StringBuilder(baseExpected);
|
||||
expected.append("E");
|
||||
expected.append(-7 - i);
|
||||
assertEquals("Failed on append", expected.toString(), fq.toNumberString());
|
||||
assertNull("Failed health check", fq.checkHealth());
|
||||
}
|
||||
fq.appendDigit((byte) 9, 2, false);
|
||||
baseExpected.append("009");
|
||||
StringBuilder expected = new StringBuilder(baseExpected);
|
||||
expected.append('E');
|
||||
expected.append("-19");
|
||||
assertEquals("Failed on append", expected.toString(), fq.toNumberString());
|
||||
assertNull("Failed health check", fq.checkHealth());
|
||||
}
|
||||
assertDoubleEquals("Different double values (" + q0 + ", " + q1 + ")",
|
||||
q0.toDouble(),
|
||||
q1.toDouble());
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
public void testConvertToAccurateDouble() {
|
||||
// based on https://github.com/google/double-conversion/issues/28
|
||||
double[] hardDoubles = {
|
||||
1651087494906221570.0,
|
||||
-5074790912492772E-327,
|
||||
83602530019752571E-327,
|
||||
2.207817077636718750000000000000,
|
||||
1.818351745605468750000000000000,
|
||||
3.941719055175781250000000000000,
|
||||
3.738609313964843750000000000000,
|
||||
3.967735290527343750000000000000,
|
||||
1.328025817871093750000000000000,
|
||||
3.920967102050781250000000000000,
|
||||
1.015235900878906250000000000000,
|
||||
1.335227966308593750000000000000,
|
||||
1.344520568847656250000000000000,
|
||||
2.879127502441406250000000000000,
|
||||
3.695838928222656250000000000000,
|
||||
1.845344543457031250000000000000,
|
||||
3.793952941894531250000000000000,
|
||||
3.211402893066406250000000000000,
|
||||
2.565971374511718750000000000000,
|
||||
0.965156555175781250000000000000,
|
||||
2.700004577636718750000000000000,
|
||||
0.767097473144531250000000000000,
|
||||
1.780448913574218750000000000000,
|
||||
2.624839782714843750000000000000,
|
||||
1.305290222167968750000000000000,
|
||||
3.834922790527343750000000000000,
|
||||
};
|
||||
assertBigDecimalEquals("Different BigDecimal values (" + q0 + ", " + q1 + ")",
|
||||
q0.toBigDecimal(),
|
||||
q1.toBigDecimal());
|
||||
|
||||
double[] integerDoubles = {
|
||||
51423,
|
||||
51423e10,
|
||||
4.503599627370496E15,
|
||||
6.789512076111555E15,
|
||||
9.007199254740991E15,
|
||||
9.007199254740992E15
|
||||
};
|
||||
q0.roundToInfinity();
|
||||
q1.roundToInfinity();
|
||||
|
||||
for (double d : hardDoubles) {
|
||||
checkDoubleBehavior(d, true, "");
|
||||
assertEquals("Different lower display magnitude",
|
||||
q0.getLowerDisplayMagnitude(),
|
||||
q1.getLowerDisplayMagnitude());
|
||||
assertEquals("Different upper display magnitude",
|
||||
q0.getUpperDisplayMagnitude(),
|
||||
q1.getUpperDisplayMagnitude());
|
||||
|
||||
for (int m = q0.getUpperDisplayMagnitude(); m >= q0.getLowerDisplayMagnitude(); m--) {
|
||||
assertEquals("Different digit at magnitude " + m + " (" + q0 + ", " + q1 + ")",
|
||||
q0.getDigit(m),
|
||||
q1.getDigit(m));
|
||||
}
|
||||
|
||||
if (rq0 instanceof DecimalQuantity_DualStorageBCD) {
|
||||
String message = ((DecimalQuantity_DualStorageBCD) rq0).checkHealth();
|
||||
if (message != null)
|
||||
errln(message);
|
||||
}
|
||||
if (rq1 instanceof DecimalQuantity_DualStorageBCD) {
|
||||
String message = ((DecimalQuantity_DualStorageBCD) rq1).checkHealth();
|
||||
if (message != null)
|
||||
errln(message);
|
||||
}
|
||||
}
|
||||
|
||||
for (double d : integerDoubles) {
|
||||
checkDoubleBehavior(d, false, "");
|
||||
@Test
|
||||
public void testSwitchStorage() {
|
||||
DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD();
|
||||
|
||||
fq.setToLong(1234123412341234L);
|
||||
assertFalse("Should not be using byte array", fq.isUsingBytes());
|
||||
assertEquals("Failed on initialize", "1234123412341234E0", fq.toNumberString());
|
||||
assertNull("Failed health check", fq.checkHealth());
|
||||
// Long -> Bytes
|
||||
fq.appendDigit((byte) 5, 0, true);
|
||||
assertTrue("Should be using byte array", fq.isUsingBytes());
|
||||
assertEquals("Failed on multiply", "12341234123412345E0", fq.toNumberString());
|
||||
assertNull("Failed health check", fq.checkHealth());
|
||||
// Bytes -> Long
|
||||
fq.roundToMagnitude(5, MATH_CONTEXT_HALF_EVEN);
|
||||
assertFalse("Should not be using byte array", fq.isUsingBytes());
|
||||
assertEquals("Failed on round", "123412341234E5", fq.toNumberString());
|
||||
assertNull("Failed health check", fq.checkHealth());
|
||||
}
|
||||
|
||||
assertEquals("NaN check failed", Double.NaN, new DecimalQuantity_DualStorageBCD(Double.NaN).toDouble());
|
||||
assertEquals(
|
||||
"Inf check failed",
|
||||
Double.POSITIVE_INFINITY,
|
||||
new DecimalQuantity_DualStorageBCD(Double.POSITIVE_INFINITY).toDouble());
|
||||
assertEquals(
|
||||
"-Inf check failed",
|
||||
Double.NEGATIVE_INFINITY,
|
||||
new DecimalQuantity_DualStorageBCD(Double.NEGATIVE_INFINITY).toDouble());
|
||||
|
||||
// Generate random doubles
|
||||
String alert = "UNEXPECTED FAILURE: PLEASE REPORT THIS MESSAGE TO THE ICU TEAM: ";
|
||||
Random rnd = new Random();
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
double d = Double.longBitsToDouble(rnd.nextLong());
|
||||
if (Double.isNaN(d) || Double.isInfinite(d)) continue;
|
||||
checkDoubleBehavior(d, false, alert);
|
||||
@Test
|
||||
public void testAppend() {
|
||||
DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD();
|
||||
fq.appendDigit((byte) 1, 0, true);
|
||||
assertEquals("Failed on append", "1E0", fq.toNumberString());
|
||||
assertNull("Failed health check", fq.checkHealth());
|
||||
fq.appendDigit((byte) 2, 0, true);
|
||||
assertEquals("Failed on append", "12E0", fq.toNumberString());
|
||||
assertNull("Failed health check", fq.checkHealth());
|
||||
fq.appendDigit((byte) 3, 1, true);
|
||||
assertEquals("Failed on append", "1203E0", fq.toNumberString());
|
||||
assertNull("Failed health check", fq.checkHealth());
|
||||
fq.appendDigit((byte) 0, 1, true);
|
||||
assertEquals("Failed on append", "1203E2", fq.toNumberString());
|
||||
assertNull("Failed health check", fq.checkHealth());
|
||||
fq.appendDigit((byte) 4, 0, true);
|
||||
assertEquals("Failed on append", "1203004E0", fq.toNumberString());
|
||||
assertNull("Failed health check", fq.checkHealth());
|
||||
fq.appendDigit((byte) 0, 0, true);
|
||||
assertEquals("Failed on append", "1203004E1", fq.toNumberString());
|
||||
assertNull("Failed health check", fq.checkHealth());
|
||||
fq.appendDigit((byte) 5, 0, false);
|
||||
assertEquals("Failed on append", "120300405E-1", fq.toNumberString());
|
||||
assertNull("Failed health check", fq.checkHealth());
|
||||
fq.appendDigit((byte) 6, 0, false);
|
||||
assertEquals("Failed on append", "1203004056E-2", fq.toNumberString());
|
||||
assertNull("Failed health check", fq.checkHealth());
|
||||
fq.appendDigit((byte) 7, 3, false);
|
||||
assertEquals("Failed on append", "12030040560007E-6", fq.toNumberString());
|
||||
assertNull("Failed health check", fq.checkHealth());
|
||||
StringBuilder baseExpected = new StringBuilder("12030040560007");
|
||||
for (int i = 0; i < 10; i++) {
|
||||
fq.appendDigit((byte) 8, 0, false);
|
||||
baseExpected.append('8');
|
||||
StringBuilder expected = new StringBuilder(baseExpected);
|
||||
expected.append("E");
|
||||
expected.append(-7 - i);
|
||||
assertEquals("Failed on append", expected.toString(), fq.toNumberString());
|
||||
assertNull("Failed health check", fq.checkHealth());
|
||||
}
|
||||
fq.appendDigit((byte) 9, 2, false);
|
||||
baseExpected.append("009");
|
||||
StringBuilder expected = new StringBuilder(baseExpected);
|
||||
expected.append('E');
|
||||
expected.append("-19");
|
||||
assertEquals("Failed on append", expected.toString(), fq.toNumberString());
|
||||
assertNull("Failed health check", fq.checkHealth());
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkDoubleBehavior(double d, boolean explicitRequired, String alert) {
|
||||
DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD(d);
|
||||
if (explicitRequired) {
|
||||
assertTrue(alert + "Should be using approximate double", !fq.explicitExactDouble);
|
||||
@Ignore
|
||||
@Test
|
||||
public void testConvertToAccurateDouble() {
|
||||
// based on https://github.com/google/double-conversion/issues/28
|
||||
double[] hardDoubles = {
|
||||
1651087494906221570.0,
|
||||
-5074790912492772E-327,
|
||||
83602530019752571E-327,
|
||||
2.207817077636718750000000000000,
|
||||
1.818351745605468750000000000000,
|
||||
3.941719055175781250000000000000,
|
||||
3.738609313964843750000000000000,
|
||||
3.967735290527343750000000000000,
|
||||
1.328025817871093750000000000000,
|
||||
3.920967102050781250000000000000,
|
||||
1.015235900878906250000000000000,
|
||||
1.335227966308593750000000000000,
|
||||
1.344520568847656250000000000000,
|
||||
2.879127502441406250000000000000,
|
||||
3.695838928222656250000000000000,
|
||||
1.845344543457031250000000000000,
|
||||
3.793952941894531250000000000000,
|
||||
3.211402893066406250000000000000,
|
||||
2.565971374511718750000000000000,
|
||||
0.965156555175781250000000000000,
|
||||
2.700004577636718750000000000000,
|
||||
0.767097473144531250000000000000,
|
||||
1.780448913574218750000000000000,
|
||||
2.624839782714843750000000000000,
|
||||
1.305290222167968750000000000000,
|
||||
3.834922790527343750000000000000, };
|
||||
|
||||
double[] integerDoubles = {
|
||||
51423,
|
||||
51423e10,
|
||||
4.503599627370496E15,
|
||||
6.789512076111555E15,
|
||||
9.007199254740991E15,
|
||||
9.007199254740992E15 };
|
||||
|
||||
for (double d : hardDoubles) {
|
||||
checkDoubleBehavior(d, true, "");
|
||||
}
|
||||
|
||||
for (double d : integerDoubles) {
|
||||
checkDoubleBehavior(d, false, "");
|
||||
}
|
||||
|
||||
assertEquals("NaN check failed",
|
||||
Double.NaN,
|
||||
new DecimalQuantity_DualStorageBCD(Double.NaN).toDouble());
|
||||
assertEquals("Inf check failed",
|
||||
Double.POSITIVE_INFINITY,
|
||||
new DecimalQuantity_DualStorageBCD(Double.POSITIVE_INFINITY).toDouble());
|
||||
assertEquals("-Inf check failed",
|
||||
Double.NEGATIVE_INFINITY,
|
||||
new DecimalQuantity_DualStorageBCD(Double.NEGATIVE_INFINITY).toDouble());
|
||||
|
||||
// Generate random doubles
|
||||
String alert = "UNEXPECTED FAILURE: PLEASE REPORT THIS MESSAGE TO THE ICU TEAM: ";
|
||||
Random rnd = new Random();
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
double d = Double.longBitsToDouble(rnd.nextLong());
|
||||
if (Double.isNaN(d) || Double.isInfinite(d))
|
||||
continue;
|
||||
checkDoubleBehavior(d, false, alert);
|
||||
}
|
||||
}
|
||||
assertEquals(alert + "Initial construction from hard double", d, fq.toDouble());
|
||||
fq.roundToInfinity();
|
||||
if (explicitRequired) {
|
||||
assertTrue(alert + "Should not be using approximate double", fq.explicitExactDouble);
|
||||
|
||||
private static void checkDoubleBehavior(double d, boolean explicitRequired, String alert) {
|
||||
DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD(d);
|
||||
if (explicitRequired) {
|
||||
assertTrue(alert + "Should be using approximate double", !fq.explicitExactDouble);
|
||||
}
|
||||
assertEquals(alert + "Initial construction from hard double", d, fq.toDouble());
|
||||
fq.roundToInfinity();
|
||||
if (explicitRequired) {
|
||||
assertTrue(alert + "Should not be using approximate double", fq.explicitExactDouble);
|
||||
}
|
||||
assertDoubleEquals(alert + "After conversion to exact BCD (double)", d, fq.toDouble());
|
||||
assertBigDecimalEquals(alert + "After conversion to exact BCD (BigDecimal)",
|
||||
new BigDecimal(Double.toString(d)),
|
||||
fq.toBigDecimal());
|
||||
}
|
||||
assertDoubleEquals(alert + "After conversion to exact BCD (double)", d, fq.toDouble());
|
||||
assertBigDecimalEquals(
|
||||
alert + "After conversion to exact BCD (BigDecimal)",
|
||||
new BigDecimal(Double.toString(d)),
|
||||
fq.toBigDecimal());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUseApproximateDoubleWhenAble() {
|
||||
Object[][] cases = {
|
||||
{1.2345678, 1, MATH_CONTEXT_HALF_EVEN, false},
|
||||
{1.2345678, 7, MATH_CONTEXT_HALF_EVEN, false},
|
||||
{1.2345678, 12, MATH_CONTEXT_HALF_EVEN, false},
|
||||
{1.2345678, 13, MATH_CONTEXT_HALF_EVEN, true},
|
||||
{1.235, 1, MATH_CONTEXT_HALF_EVEN, false},
|
||||
{1.235, 2, MATH_CONTEXT_HALF_EVEN, true},
|
||||
{1.235, 3, MATH_CONTEXT_HALF_EVEN, false},
|
||||
{1.000000000000001, 0, MATH_CONTEXT_HALF_EVEN, false},
|
||||
{1.000000000000001, 0, MATH_CONTEXT_CEILING, true},
|
||||
{1.235, 1, MATH_CONTEXT_CEILING, false},
|
||||
{1.235, 2, MATH_CONTEXT_CEILING, false},
|
||||
{1.235, 3, MATH_CONTEXT_CEILING, true}
|
||||
};
|
||||
@Test
|
||||
public void testUseApproximateDoubleWhenAble() {
|
||||
Object[][] cases = {
|
||||
{ 1.2345678, 1, MATH_CONTEXT_HALF_EVEN, false },
|
||||
{ 1.2345678, 7, MATH_CONTEXT_HALF_EVEN, false },
|
||||
{ 1.2345678, 12, MATH_CONTEXT_HALF_EVEN, false },
|
||||
{ 1.2345678, 13, MATH_CONTEXT_HALF_EVEN, true },
|
||||
{ 1.235, 1, MATH_CONTEXT_HALF_EVEN, false },
|
||||
{ 1.235, 2, MATH_CONTEXT_HALF_EVEN, true },
|
||||
{ 1.235, 3, MATH_CONTEXT_HALF_EVEN, false },
|
||||
{ 1.000000000000001, 0, MATH_CONTEXT_HALF_EVEN, false },
|
||||
{ 1.000000000000001, 0, MATH_CONTEXT_CEILING, true },
|
||||
{ 1.235, 1, MATH_CONTEXT_CEILING, false },
|
||||
{ 1.235, 2, MATH_CONTEXT_CEILING, false },
|
||||
{ 1.235, 3, MATH_CONTEXT_CEILING, true } };
|
||||
|
||||
for (Object[] cas : cases) {
|
||||
double d = (Double) cas[0];
|
||||
int maxFrac = (Integer) cas[1];
|
||||
MathContext mc = (MathContext) cas[2];
|
||||
boolean usesExact = (Boolean) cas[3];
|
||||
for (Object[] cas : cases) {
|
||||
double d = (Double) cas[0];
|
||||
int maxFrac = (Integer) cas[1];
|
||||
MathContext mc = (MathContext) cas[2];
|
||||
boolean usesExact = (Boolean) cas[3];
|
||||
|
||||
DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD(d);
|
||||
assertTrue("Should be using approximate double", !fq.explicitExactDouble);
|
||||
fq.roundToMagnitude(-maxFrac, mc);
|
||||
assertEquals(
|
||||
"Using approximate double after rounding: " + d + " maxFrac=" + maxFrac + " " + mc,
|
||||
usesExact,
|
||||
fq.explicitExactDouble);
|
||||
DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD(d);
|
||||
assertTrue("Should be using approximate double", !fq.explicitExactDouble);
|
||||
fq.roundToMagnitude(-maxFrac, mc);
|
||||
assertEquals(
|
||||
"Using approximate double after rounding: " + d + " maxFrac=" + maxFrac + " " + mc,
|
||||
usesExact,
|
||||
fq.explicitExactDouble);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecimalQuantityBehaviorStandalone() {
|
||||
DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD();
|
||||
assertToStringAndHealth(fq, "<DecimalQuantity 999:0:0:-999 long 0E0>");
|
||||
fq.setToInt(51423);
|
||||
assertToStringAndHealth(fq, "<DecimalQuantity 999:0:0:-999 long 51423E0>");
|
||||
fq.adjustMagnitude(-3);
|
||||
assertToStringAndHealth(fq, "<DecimalQuantity 999:0:0:-999 long 51423E-3>");
|
||||
fq.setToLong(999999999999000L);
|
||||
assertToStringAndHealth(fq, "<DecimalQuantity 999:0:0:-999 long 999999999999E3>");
|
||||
fq.setIntegerLength(2, 5);
|
||||
assertToStringAndHealth(fq, "<DecimalQuantity 5:2:0:-999 long 999999999999E3>");
|
||||
fq.setFractionLength(3, 6);
|
||||
assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 999999999999E3>");
|
||||
fq.setToDouble(987.654321);
|
||||
assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 987654321E-6>");
|
||||
fq.roundToInfinity();
|
||||
assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 987654321E-6>");
|
||||
fq.roundToIncrement(new BigDecimal("0.005"), MATH_CONTEXT_HALF_EVEN);
|
||||
assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 987655E-3>");
|
||||
fq.roundToMagnitude(-2, MATH_CONTEXT_HALF_EVEN);
|
||||
assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 98766E-2>");
|
||||
}
|
||||
@Test
|
||||
public void testDecimalQuantityBehaviorStandalone() {
|
||||
DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD();
|
||||
assertToStringAndHealth(fq, "<DecimalQuantity 999:0:0:-999 long 0E0>");
|
||||
fq.setToInt(51423);
|
||||
assertToStringAndHealth(fq, "<DecimalQuantity 999:0:0:-999 long 51423E0>");
|
||||
fq.adjustMagnitude(-3);
|
||||
assertToStringAndHealth(fq, "<DecimalQuantity 999:0:0:-999 long 51423E-3>");
|
||||
fq.setToLong(999999999999000L);
|
||||
assertToStringAndHealth(fq, "<DecimalQuantity 999:0:0:-999 long 999999999999E3>");
|
||||
fq.setIntegerLength(2, 5);
|
||||
assertToStringAndHealth(fq, "<DecimalQuantity 5:2:0:-999 long 999999999999E3>");
|
||||
fq.setFractionLength(3, 6);
|
||||
assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 999999999999E3>");
|
||||
fq.setToDouble(987.654321);
|
||||
assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 987654321E-6>");
|
||||
fq.roundToInfinity();
|
||||
assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 987654321E-6>");
|
||||
fq.roundToIncrement(new BigDecimal("0.005"), MATH_CONTEXT_HALF_EVEN);
|
||||
assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 987655E-3>");
|
||||
fq.roundToMagnitude(-2, MATH_CONTEXT_HALF_EVEN);
|
||||
assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 98766E-2>");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFitsInLong() {
|
||||
@ -542,24 +527,24 @@ public class DecimalQuantityTest extends TestFmwk {
|
||||
assertFalse("10^20 should not fit", quantity.fitsInLong());
|
||||
}
|
||||
|
||||
static void assertDoubleEquals(String message, double d1, double d2) {
|
||||
boolean equal = (Math.abs(d1 - d2) < 1e-6) || (Math.abs((d1 - d2) / d1) < 1e-6);
|
||||
handleAssert(equal, message, d1, d2, null, false);
|
||||
}
|
||||
static void assertDoubleEquals(String message, double d1, double d2) {
|
||||
boolean equal = (Math.abs(d1 - d2) < 1e-6) || (Math.abs((d1 - d2) / d1) < 1e-6);
|
||||
handleAssert(equal, message, d1, d2, null, false);
|
||||
}
|
||||
|
||||
static void assertBigDecimalEquals(String message, String d1, BigDecimal d2) {
|
||||
assertBigDecimalEquals(message, new BigDecimal(d1), d2);
|
||||
}
|
||||
static void assertBigDecimalEquals(String message, String d1, BigDecimal d2) {
|
||||
assertBigDecimalEquals(message, new BigDecimal(d1), d2);
|
||||
}
|
||||
|
||||
static void assertBigDecimalEquals(String message, BigDecimal d1, BigDecimal d2) {
|
||||
boolean equal = d1.compareTo(d2) == 0;
|
||||
handleAssert(equal, message, d1, d2, null, false);
|
||||
}
|
||||
static void assertBigDecimalEquals(String message, BigDecimal d1, BigDecimal d2) {
|
||||
boolean equal = d1.compareTo(d2) == 0;
|
||||
handleAssert(equal, message, d1, d2, null, false);
|
||||
}
|
||||
|
||||
static void assertToStringAndHealth(DecimalQuantity_DualStorageBCD fq, String expected) {
|
||||
String actual = fq.toString();
|
||||
assertEquals("DecimalQuantity toString", expected, actual);
|
||||
String health = fq.checkHealth();
|
||||
assertNull("DecimalQuantity health", health);
|
||||
}
|
||||
static void assertToStringAndHealth(DecimalQuantity_DualStorageBCD fq, String expected) {
|
||||
String actual = fq.toString();
|
||||
assertEquals("DecimalQuantity toString", expected, actual);
|
||||
String health = fq.checkHealth();
|
||||
assertNull("DecimalQuantity health", health);
|
||||
}
|
||||
}
|
||||
|
@ -46,11 +46,20 @@ public class ModifierTest {
|
||||
@Test
|
||||
public void testSimpleModifier() {
|
||||
String[] patterns = { "{0}", "X{0}Y", "XX{0}YYY", "{0}YY", "XX📺XX{0}" };
|
||||
Object[][] outputs = { { "", 0, 0 }, { "a📻bcde", 0, 0 }, { "a📻bcde", 4, 4 }, { "a📻bcde", 3, 5 } };
|
||||
Object[][] outputs = {
|
||||
{ "", 0, 0 },
|
||||
{ "a📻bcde", 0, 0 },
|
||||
{ "a📻bcde", 4, 4 },
|
||||
{ "a📻bcde", 3, 5 } };
|
||||
int[] prefixLens = { 0, 1, 2, 0, 6 };
|
||||
String[][] expectedCharFields = { { "|", "n" }, { "X|Y", "%n%" }, { "XX|YYY", "%%n%%%" }, { "|YY", "n%%" },
|
||||
String[][] expectedCharFields = {
|
||||
{ "|", "n" },
|
||||
{ "X|Y", "%n%" },
|
||||
{ "XX|YYY", "%%n%%%" },
|
||||
{ "|YY", "n%%" },
|
||||
{ "XX📺XX|", "%%%%%%n" } };
|
||||
String[][] expecteds = { { "", "XY", "XXYYY", "YY", "XX📺XX" },
|
||||
String[][] expecteds = {
|
||||
{ "", "XY", "XXYYY", "YY", "XX📺XX" },
|
||||
{ "a📻bcde", "XYa📻bcde", "XXYYYa📻bcde", "YYa📻bcde", "XX📺XXa📻bcde" },
|
||||
{ "a📻bcde", "a📻bXYcde", "a📻bXXYYYcde", "a📻bYYcde", "a📻bXX📺XXcde" },
|
||||
{ "a📻bcde", "a📻XbcYde", "a📻XXbcYYYde", "a📻bcYYde", "a📻XX📺XXbcde" } };
|
||||
@ -59,7 +68,11 @@ public class ModifierTest {
|
||||
String compiledPattern = SimpleFormatterImpl
|
||||
.compileToStringMinMaxArguments(pattern, new StringBuilder(), 1, 1);
|
||||
Modifier mod = new SimpleModifier(compiledPattern, NumberFormat.Field.PERCENT, false);
|
||||
assertModifierEquals(mod, prefixLens[i], false, expectedCharFields[i][0], expectedCharFields[i][1]);
|
||||
assertModifierEquals(mod,
|
||||
prefixLens[i],
|
||||
false,
|
||||
expectedCharFields[i][0],
|
||||
expectedCharFields[i][1]);
|
||||
|
||||
// Test strange insertion positions
|
||||
for (int j = 0; j < outputs.length; j++) {
|
||||
@ -99,7 +112,9 @@ public class ModifierTest {
|
||||
|
||||
// Test custom patterns
|
||||
// The following line means that the last char of the number should be a | (rather than a digit)
|
||||
symbols.setPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_SURROUNDING_MATCH, true, "[|]");
|
||||
symbols.setPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_SURROUNDING_MATCH,
|
||||
true,
|
||||
"[|]");
|
||||
suffix.append("XYZ", NumberFormat.Field.CURRENCY);
|
||||
Modifier mod3 = new CurrencySpacingEnabledModifier(prefix, suffix, true, symbols);
|
||||
assertModifierEquals(mod3, 3, true, "USD|\u00A0XYZ", "$$$nn$$$");
|
||||
@ -112,18 +127,18 @@ public class ModifierTest {
|
||||
// If this test starts failing, please update the method #getUnicodeSet() in
|
||||
// BOTH CurrencySpacingEnabledModifier.java AND in C++.
|
||||
DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(new ULocale("en-US"));
|
||||
assertEquals(
|
||||
"[:^S:]",
|
||||
dfs.getPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_CURRENCY_MATCH, true));
|
||||
assertEquals(
|
||||
"[:^S:]",
|
||||
dfs.getPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_CURRENCY_MATCH, false));
|
||||
assertEquals(
|
||||
"[:digit:]",
|
||||
dfs.getPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_SURROUNDING_MATCH, true));
|
||||
assertEquals(
|
||||
"[:digit:]",
|
||||
dfs.getPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_SURROUNDING_MATCH, false));
|
||||
assertEquals("[:^S:]",
|
||||
dfs.getPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_CURRENCY_MATCH,
|
||||
true));
|
||||
assertEquals("[:^S:]",
|
||||
dfs.getPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_CURRENCY_MATCH,
|
||||
false));
|
||||
assertEquals("[:digit:]",
|
||||
dfs.getPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_SURROUNDING_MATCH,
|
||||
true));
|
||||
assertEquals("[:digit:]",
|
||||
dfs.getPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_SURROUNDING_MATCH,
|
||||
false));
|
||||
}
|
||||
|
||||
private void assertModifierEquals(
|
||||
@ -134,7 +149,12 @@ public class ModifierTest {
|
||||
String expectedFields) {
|
||||
NumberStringBuilder sb = new NumberStringBuilder();
|
||||
sb.appendCodePoint('|', null);
|
||||
assertModifierEquals(mod, sb, expectedPrefixLength, expectedStrong, expectedChars, expectedFields);
|
||||
assertModifierEquals(mod,
|
||||
sb,
|
||||
expectedPrefixLength,
|
||||
expectedStrong,
|
||||
expectedChars,
|
||||
expectedFields);
|
||||
}
|
||||
|
||||
private void assertModifierEquals(
|
||||
@ -150,8 +170,10 @@ public class ModifierTest {
|
||||
assertEquals("Strong on " + sb, expectedStrong, mod.isStrong());
|
||||
if (!(mod instanceof CurrencySpacingEnabledModifier)) {
|
||||
assertEquals("Code point count equals actual code point count",
|
||||
sb.codePointCount() - oldCount, mod.getCodePointCount());
|
||||
sb.codePointCount() - oldCount,
|
||||
mod.getCodePointCount());
|
||||
}
|
||||
assertEquals("<NumberStringBuilder [" + expectedChars + "] [" + expectedFields + "]>", sb.toDebugString());
|
||||
assertEquals("<NumberStringBuilder [" + expectedChars + "] [" + expectedFields + "]>",
|
||||
sb.toDebugString());
|
||||
}
|
||||
}
|
||||
|
@ -27,8 +27,7 @@ public class MutablePatternModifierTest {
|
||||
MutablePatternModifier mod = new MutablePatternModifier(false);
|
||||
mod.setPatternInfo(PatternStringParser.parseToPatternInfo("a0b"));
|
||||
mod.setPatternAttributes(SignDisplay.AUTO, false);
|
||||
mod.setSymbols(
|
||||
DecimalFormatSymbols.getInstance(ULocale.ENGLISH),
|
||||
mod.setSymbols(DecimalFormatSymbols.getInstance(ULocale.ENGLISH),
|
||||
Currency.getInstance("USD"),
|
||||
UnitWidth.SHORT,
|
||||
null);
|
||||
|
@ -4,10 +4,17 @@ package com.ibm.icu.dev.test.number;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
@ -16,6 +23,7 @@ import com.ibm.icu.impl.number.Padder;
|
||||
import com.ibm.icu.impl.number.Padder.PadPosition;
|
||||
import com.ibm.icu.impl.number.PatternStringParser;
|
||||
import com.ibm.icu.number.FormattedNumber;
|
||||
import com.ibm.icu.number.FractionRounder;
|
||||
import com.ibm.icu.number.Grouper;
|
||||
import com.ibm.icu.number.IntegerWidth;
|
||||
import com.ibm.icu.number.LocalizedNumberFormatter;
|
||||
@ -25,6 +33,7 @@ import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay;
|
||||
import com.ibm.icu.number.NumberFormatter.SignDisplay;
|
||||
import com.ibm.icu.number.NumberFormatter.UnitWidth;
|
||||
import com.ibm.icu.number.Rounder;
|
||||
import com.ibm.icu.number.ScientificNotation;
|
||||
import com.ibm.icu.number.UnlocalizedNumberFormatter;
|
||||
import com.ibm.icu.text.DecimalFormatSymbols;
|
||||
import com.ibm.icu.text.NumberingSystem;
|
||||
@ -466,6 +475,54 @@ public class NumberFormatterApiTest {
|
||||
"5.43 °F");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unitCompoundMeasure() {
|
||||
assertFormatDescending(
|
||||
"Meters Per Second Short (unit that simplifies)",
|
||||
"",
|
||||
NumberFormatter.with().unit(MeasureUnit.METER).perUnit(MeasureUnit.SECOND),
|
||||
ULocale.ENGLISH,
|
||||
"87,650 m/s",
|
||||
"8,765 m/s",
|
||||
"876.5 m/s",
|
||||
"87.65 m/s",
|
||||
"8.765 m/s",
|
||||
"0.8765 m/s",
|
||||
"0.08765 m/s",
|
||||
"0.008765 m/s",
|
||||
"0 m/s");
|
||||
|
||||
assertFormatDescending(
|
||||
"Pounds Per Square Mile Short (secondary unit has per-format)",
|
||||
"",
|
||||
NumberFormatter.with().unit(MeasureUnit.POUND).perUnit(MeasureUnit.SQUARE_MILE),
|
||||
ULocale.ENGLISH,
|
||||
"87,650 lb/mi²",
|
||||
"8,765 lb/mi²",
|
||||
"876.5 lb/mi²",
|
||||
"87.65 lb/mi²",
|
||||
"8.765 lb/mi²",
|
||||
"0.8765 lb/mi²",
|
||||
"0.08765 lb/mi²",
|
||||
"0.008765 lb/mi²",
|
||||
"0 lb/mi²");
|
||||
|
||||
assertFormatDescending(
|
||||
"Joules Per Furlong Short (unit with no simplifications or special patterns)",
|
||||
"",
|
||||
NumberFormatter.with().unit(MeasureUnit.JOULE).perUnit(MeasureUnit.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");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unitCurrency() {
|
||||
assertFormatDescending(
|
||||
@ -1560,6 +1617,113 @@ public class NumberFormatterApiTest {
|
||||
"1.00 US dollars");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validRanges() throws NoSuchMethodException, IllegalAccessException {
|
||||
Method[] methodsWithOneArgument = new Method[] { Rounder.class.getDeclaredMethod("fixedFraction", Integer.TYPE),
|
||||
Rounder.class.getDeclaredMethod("minFraction", Integer.TYPE),
|
||||
Rounder.class.getDeclaredMethod("maxFraction", Integer.TYPE),
|
||||
Rounder.class.getDeclaredMethod("fixedDigits", Integer.TYPE),
|
||||
Rounder.class.getDeclaredMethod("minDigits", Integer.TYPE),
|
||||
Rounder.class.getDeclaredMethod("maxDigits", Integer.TYPE),
|
||||
FractionRounder.class.getDeclaredMethod("withMinDigits", Integer.TYPE),
|
||||
FractionRounder.class.getDeclaredMethod("withMaxDigits", Integer.TYPE),
|
||||
ScientificNotation.class.getDeclaredMethod("withMinExponentDigits", Integer.TYPE),
|
||||
IntegerWidth.class.getDeclaredMethod("zeroFillTo", Integer.TYPE),
|
||||
IntegerWidth.class.getDeclaredMethod("truncateAt", Integer.TYPE), };
|
||||
Method[] methodsWithTwoArguments = new Method[] {
|
||||
Rounder.class.getDeclaredMethod("minMaxFraction", Integer.TYPE, Integer.TYPE),
|
||||
Rounder.class.getDeclaredMethod("minMaxDigits", Integer.TYPE, Integer.TYPE), };
|
||||
|
||||
final int EXPECTED_MAX_INT_FRAC_SIG = 100;
|
||||
final String expectedSubstring0 = "between 0 and 100 (inclusive)";
|
||||
final String expectedSubstring1 = "between 1 and 100 (inclusive)";
|
||||
final String expectedSubstringN1 = "between -1 and 100 (inclusive)";
|
||||
|
||||
// We require that the upper bounds all be 100 inclusive.
|
||||
// The lower bound may be either -1, 0, or 1.
|
||||
Set<String> methodsWithLowerBound1 = new HashSet();
|
||||
methodsWithLowerBound1.add("fixedDigits");
|
||||
methodsWithLowerBound1.add("minDigits");
|
||||
methodsWithLowerBound1.add("maxDigits");
|
||||
methodsWithLowerBound1.add("minMaxDigits");
|
||||
methodsWithLowerBound1.add("withMinDigits");
|
||||
methodsWithLowerBound1.add("withMaxDigits");
|
||||
methodsWithLowerBound1.add("withMinExponentDigits");
|
||||
Set<String> methodsWithLowerBoundN1 = new HashSet();
|
||||
methodsWithLowerBoundN1.add("truncateAt");
|
||||
|
||||
// Some of the methods require an object to be called upon.
|
||||
Map<String, Object> targets = new HashMap<String, Object>();
|
||||
targets.put("withMinDigits", Rounder.integer());
|
||||
targets.put("withMaxDigits", Rounder.integer());
|
||||
targets.put("withMinExponentDigits", Notation.scientific());
|
||||
targets.put("truncateAt", IntegerWidth.zeroFillTo(0));
|
||||
|
||||
for (int argument = -2; argument <= EXPECTED_MAX_INT_FRAC_SIG + 2; argument++) {
|
||||
for (Method method : methodsWithOneArgument) {
|
||||
String message = "i = " + argument + "; method = " + method.getName();
|
||||
int lowerBound = methodsWithLowerBound1.contains(method.getName()) ? 1
|
||||
: methodsWithLowerBoundN1.contains(method.getName()) ? -1 : 0;
|
||||
String expectedSubstring = lowerBound == 0 ? expectedSubstring0
|
||||
: lowerBound == 1 ? expectedSubstring1 : expectedSubstringN1;
|
||||
Object target = targets.get(method.getName());
|
||||
try {
|
||||
method.invoke(target, argument);
|
||||
assertTrue(message, argument >= lowerBound && argument <= EXPECTED_MAX_INT_FRAC_SIG);
|
||||
} catch (InvocationTargetException e) {
|
||||
assertTrue(message, argument < lowerBound || argument > EXPECTED_MAX_INT_FRAC_SIG);
|
||||
// Ensure the exception message contains the expected substring
|
||||
String actualMessage = e.getCause().getMessage();
|
||||
assertNotEquals(message + ": " + actualMessage, -1, actualMessage.indexOf(expectedSubstring));
|
||||
}
|
||||
}
|
||||
for (Method method : methodsWithTwoArguments) {
|
||||
String message = "i = " + argument + "; method = " + method.getName();
|
||||
int lowerBound = methodsWithLowerBound1.contains(method.getName()) ? 1
|
||||
: methodsWithLowerBoundN1.contains(method.getName()) ? -1 : 0;
|
||||
String expectedSubstring = lowerBound == 0 ? expectedSubstring0 : expectedSubstring1;
|
||||
Object target = targets.get(method.getName());
|
||||
// Check range on the first argument
|
||||
try {
|
||||
// Pass EXPECTED_MAX_INT_FRAC_SIG as the second argument so arg1 <= arg2 in expected cases
|
||||
method.invoke(target, argument, EXPECTED_MAX_INT_FRAC_SIG);
|
||||
assertTrue(message, argument >= lowerBound && argument <= EXPECTED_MAX_INT_FRAC_SIG);
|
||||
} catch (InvocationTargetException e) {
|
||||
assertTrue(message, argument < lowerBound || argument > EXPECTED_MAX_INT_FRAC_SIG);
|
||||
// Ensure the exception message contains the expected substring
|
||||
String actualMessage = e.getCause().getMessage();
|
||||
assertNotEquals(message + ": " + actualMessage, -1, actualMessage.indexOf(expectedSubstring));
|
||||
}
|
||||
// Check range on the second argument
|
||||
try {
|
||||
// Pass lowerBound as the first argument so arg1 <= arg2 in expected cases
|
||||
method.invoke(target, lowerBound, argument);
|
||||
assertTrue(message, argument >= lowerBound && argument <= EXPECTED_MAX_INT_FRAC_SIG);
|
||||
} catch (InvocationTargetException e) {
|
||||
assertTrue(message, argument < lowerBound || argument > EXPECTED_MAX_INT_FRAC_SIG);
|
||||
// Ensure the exception message contains the expected substring
|
||||
String actualMessage = e.getCause().getMessage();
|
||||
assertNotEquals(message + ": " + actualMessage, -1, actualMessage.indexOf(expectedSubstring));
|
||||
}
|
||||
// Check that first argument must be less than or equal to second argument
|
||||
try {
|
||||
method.invoke(target, argument, argument - 1);
|
||||
org.junit.Assert.fail();
|
||||
} catch (InvocationTargetException e) {
|
||||
// Pass
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check first argument less than or equal to second argument on IntegerWidth
|
||||
try {
|
||||
IntegerWidth.zeroFillTo(4).truncateAt(2);
|
||||
org.junit.Assert.fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Pass
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertFormatDescending(
|
||||
String message,
|
||||
String skeleton,
|
||||
|
@ -17,208 +17,207 @@ import com.ibm.icu.text.NumberFormat;
|
||||
|
||||
/** @author sffc */
|
||||
public class NumberStringBuilderTest {
|
||||
private static final String[] EXAMPLE_STRINGS = {
|
||||
"",
|
||||
"xyz",
|
||||
"The quick brown fox jumps over the lazy dog",
|
||||
"😁",
|
||||
"mixed 😇 and ASCII",
|
||||
"with combining characters like 🇦🇧🇨🇩",
|
||||
"A very very very very very very very very very very long string to force heap"
|
||||
};
|
||||
private static final String[] EXAMPLE_STRINGS = {
|
||||
"",
|
||||
"xyz",
|
||||
"The quick brown fox jumps over the lazy dog",
|
||||
"😁",
|
||||
"mixed 😇 and ASCII",
|
||||
"with combining characters like 🇦🇧🇨🇩",
|
||||
"A very very very very very very very very very very long string to force heap" };
|
||||
|
||||
@Test
|
||||
public void testInsertAppendCharSequence() {
|
||||
@Test
|
||||
public void testInsertAppendCharSequence() {
|
||||
|
||||
StringBuilder sb1 = new StringBuilder();
|
||||
NumberStringBuilder sb2 = new NumberStringBuilder();
|
||||
for (String str : EXAMPLE_STRINGS) {
|
||||
NumberStringBuilder sb3 = new NumberStringBuilder();
|
||||
sb1.append(str);
|
||||
sb2.append(str, null);
|
||||
sb3.append(str, null);
|
||||
assertCharSequenceEquals(sb1, sb2);
|
||||
assertCharSequenceEquals(sb3, str);
|
||||
StringBuilder sb1 = new StringBuilder();
|
||||
NumberStringBuilder sb2 = new NumberStringBuilder();
|
||||
for (String str : EXAMPLE_STRINGS) {
|
||||
NumberStringBuilder sb3 = new NumberStringBuilder();
|
||||
sb1.append(str);
|
||||
sb2.append(str, null);
|
||||
sb3.append(str, null);
|
||||
assertCharSequenceEquals(sb1, sb2);
|
||||
assertCharSequenceEquals(sb3, str);
|
||||
|
||||
StringBuilder sb4 = new StringBuilder();
|
||||
NumberStringBuilder sb5 = new NumberStringBuilder();
|
||||
sb4.append("😇");
|
||||
sb4.append(str);
|
||||
sb4.append("xx");
|
||||
sb5.append("😇xx", null);
|
||||
sb5.insert(2, str, null);
|
||||
assertCharSequenceEquals(sb4, sb5);
|
||||
StringBuilder sb4 = new StringBuilder();
|
||||
NumberStringBuilder sb5 = new NumberStringBuilder();
|
||||
sb4.append("😇");
|
||||
sb4.append(str);
|
||||
sb4.append("xx");
|
||||
sb5.append("😇xx", null);
|
||||
sb5.insert(2, str, null);
|
||||
assertCharSequenceEquals(sb4, sb5);
|
||||
|
||||
int start = Math.min(1, str.length());
|
||||
int end = Math.min(10, str.length());
|
||||
sb4.insert(3, str, start, end);
|
||||
sb5.insert(3, str, start, end, null);
|
||||
assertCharSequenceEquals(sb4, sb5);
|
||||
int start = Math.min(1, str.length());
|
||||
int end = Math.min(10, str.length());
|
||||
sb4.insert(3, str, start, end);
|
||||
sb5.insert(3, str, start, end, null);
|
||||
assertCharSequenceEquals(sb4, sb5);
|
||||
|
||||
sb4.append(str.toCharArray());
|
||||
sb5.append(str.toCharArray(), null);
|
||||
assertCharSequenceEquals(sb4, sb5);
|
||||
sb4.append(str.toCharArray());
|
||||
sb5.append(str.toCharArray(), null);
|
||||
assertCharSequenceEquals(sb4, sb5);
|
||||
|
||||
sb4.insert(4, str.toCharArray());
|
||||
sb5.insert(4, str.toCharArray(), null);
|
||||
assertCharSequenceEquals(sb4, sb5);
|
||||
sb4.insert(4, str.toCharArray());
|
||||
sb5.insert(4, str.toCharArray(), null);
|
||||
assertCharSequenceEquals(sb4, sb5);
|
||||
|
||||
sb4.append(sb4.toString());
|
||||
sb5.append(new NumberStringBuilder(sb5));
|
||||
assertCharSequenceEquals(sb4, sb5);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsertAppendCodePoint() {
|
||||
int[] cases = {0, 1, 60, 127, 128, 0x7fff, 0x8000, 0xffff, 0x10000, 0x1f000, 0x10ffff};
|
||||
|
||||
StringBuilder sb1 = new StringBuilder();
|
||||
NumberStringBuilder sb2 = new NumberStringBuilder();
|
||||
for (int cas : cases) {
|
||||
NumberStringBuilder sb3 = new NumberStringBuilder();
|
||||
sb1.appendCodePoint(cas);
|
||||
sb2.appendCodePoint(cas, null);
|
||||
sb3.appendCodePoint(cas, null);
|
||||
assertCharSequenceEquals(sb1, sb2);
|
||||
assertEquals(Character.codePointAt(sb3, 0), cas);
|
||||
|
||||
StringBuilder sb4 = new StringBuilder();
|
||||
NumberStringBuilder sb5 = new NumberStringBuilder();
|
||||
sb4.append("😇");
|
||||
sb4.appendCodePoint(cas); // Java StringBuilder has no insertCodePoint()
|
||||
sb4.append("xx");
|
||||
sb5.append("😇xx", null);
|
||||
sb5.insertCodePoint(2, cas, null);
|
||||
assertCharSequenceEquals(sb4, sb5);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopy() {
|
||||
for (String str : EXAMPLE_STRINGS) {
|
||||
NumberStringBuilder sb1 = new NumberStringBuilder();
|
||||
sb1.append(str, null);
|
||||
NumberStringBuilder sb2 = new NumberStringBuilder(sb1);
|
||||
assertCharSequenceEquals(sb1, sb2);
|
||||
assertTrue(sb1.contentEquals(sb2));
|
||||
|
||||
sb1.append("12345", null);
|
||||
assertNotEquals(sb1.length(), sb2.length());
|
||||
assertFalse(sb1.contentEquals(sb2));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFields() {
|
||||
for (String str : EXAMPLE_STRINGS) {
|
||||
NumberStringBuilder sb = new NumberStringBuilder();
|
||||
sb.append(str, null);
|
||||
sb.append(str, NumberFormat.Field.CURRENCY);
|
||||
Field[] fields = sb.toFieldArray();
|
||||
assertEquals(str.length() * 2, fields.length);
|
||||
for (int i = 0; i < str.length(); i++) {
|
||||
assertEquals(null, fields[i]);
|
||||
assertEquals(null, sb.fieldAt(i));
|
||||
assertEquals(NumberFormat.Field.CURRENCY, fields[i + str.length()]);
|
||||
assertEquals(NumberFormat.Field.CURRENCY, sb.fieldAt(i + str.length()));
|
||||
}
|
||||
|
||||
// Very basic FieldPosition test. More robust tests happen in NumberFormatTest.
|
||||
// Let NumberFormatTest also take care of AttributedCharacterIterator material.
|
||||
FieldPosition fp = new FieldPosition(NumberFormat.Field.CURRENCY);
|
||||
sb.populateFieldPosition(fp, 0);
|
||||
assertEquals(str.length(), fp.getBeginIndex());
|
||||
assertEquals(str.length() * 2, fp.getEndIndex());
|
||||
|
||||
if (str.length() > 0) {
|
||||
sb.insertCodePoint(2, 100, NumberFormat.Field.INTEGER);
|
||||
fields = sb.toFieldArray();
|
||||
assertEquals(str.length() * 2 + 1, fields.length);
|
||||
assertEquals(fields[2], NumberFormat.Field.INTEGER);
|
||||
}
|
||||
|
||||
sb.append(new NumberStringBuilder(sb));
|
||||
sb.append(sb.toCharArray(), sb.toFieldArray());
|
||||
int numNull = 0;
|
||||
int numCurr = 0;
|
||||
int numInt = 0;
|
||||
Field[] oldFields = fields;
|
||||
fields = sb.toFieldArray();
|
||||
for (int i = 0; i < sb.length(); i++) {
|
||||
assertEquals(oldFields[i % oldFields.length], fields[i]);
|
||||
if (fields[i] == null) {
|
||||
numNull++;
|
||||
} else if (fields[i] == NumberFormat.Field.CURRENCY) {
|
||||
numCurr++;
|
||||
} else if (fields[i] == NumberFormat.Field.INTEGER) {
|
||||
numInt++;
|
||||
} else {
|
||||
throw new AssertionError("Encountered unknown field in " + str);
|
||||
sb4.append(sb4.toString());
|
||||
sb5.append(new NumberStringBuilder(sb5));
|
||||
assertCharSequenceEquals(sb4, sb5);
|
||||
}
|
||||
}
|
||||
assertEquals(str.length() * 4, numNull);
|
||||
assertEquals(numNull, numCurr);
|
||||
assertEquals(str.length() > 0 ? 4 : 0, numInt);
|
||||
|
||||
NumberStringBuilder sb2 = new NumberStringBuilder();
|
||||
sb2.append(sb);
|
||||
assertTrue(sb.contentEquals(sb2));
|
||||
assertTrue(sb.contentEquals(sb2.toCharArray(), sb2.toFieldArray()));
|
||||
|
||||
sb2.insertCodePoint(0, 50, NumberFormat.Field.FRACTION);
|
||||
assertTrue(!sb.contentEquals(sb2));
|
||||
assertTrue(!sb.contentEquals(sb2.toCharArray(), sb2.toFieldArray()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnlimitedCapacity() {
|
||||
NumberStringBuilder builder = new NumberStringBuilder();
|
||||
// The builder should never fail upon repeated appends.
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
assertEquals(builder.length(), i);
|
||||
builder.appendCodePoint('x', null);
|
||||
assertEquals(builder.length(), i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCodePoints() {
|
||||
NumberStringBuilder nsb = new NumberStringBuilder();
|
||||
assertEquals("First is -1 on empty string", -1, nsb.getFirstCodePoint());
|
||||
assertEquals("Last is -1 on empty string", -1, nsb.getLastCodePoint());
|
||||
assertEquals("Length is 0 on empty string", 0, nsb.codePointCount());
|
||||
|
||||
nsb.append("q", null);
|
||||
assertEquals("First is q", 'q', nsb.getFirstCodePoint());
|
||||
assertEquals("Last is q", 'q', nsb.getLastCodePoint());
|
||||
assertEquals("0th is q", 'q', nsb.codePointAt(0));
|
||||
assertEquals("Before 1st is q", 'q', nsb.codePointBefore(1));
|
||||
assertEquals("Code point count is 1", 1, nsb.codePointCount());
|
||||
|
||||
// 🚀 is two char16s
|
||||
nsb.append("🚀", null);
|
||||
assertEquals("First is still q", 'q', nsb.getFirstCodePoint());
|
||||
assertEquals("Last is space ship", 128640, nsb.getLastCodePoint());
|
||||
assertEquals("1st is space ship", 128640, nsb.codePointAt(1));
|
||||
assertEquals("Before 1st is q", 'q', nsb.codePointBefore(1));
|
||||
assertEquals("Before 3rd is space ship", 128640, nsb.codePointBefore(3));
|
||||
assertEquals("Code point count is 2", 2, nsb.codePointCount());
|
||||
}
|
||||
|
||||
private static void assertCharSequenceEquals(CharSequence a, CharSequence b) {
|
||||
assertEquals(a.toString(), b.toString());
|
||||
|
||||
assertEquals(a.length(), b.length());
|
||||
for (int i = 0; i < a.length(); i++) {
|
||||
assertEquals(a.charAt(i), b.charAt(i));
|
||||
}
|
||||
|
||||
int start = Math.min(2, a.length());
|
||||
int end = Math.min(12, a.length());
|
||||
if (start != end) {
|
||||
assertCharSequenceEquals(a.subSequence(start, end), b.subSequence(start, end));
|
||||
@Test
|
||||
public void testInsertAppendCodePoint() {
|
||||
int[] cases = { 0, 1, 60, 127, 128, 0x7fff, 0x8000, 0xffff, 0x10000, 0x1f000, 0x10ffff };
|
||||
|
||||
StringBuilder sb1 = new StringBuilder();
|
||||
NumberStringBuilder sb2 = new NumberStringBuilder();
|
||||
for (int cas : cases) {
|
||||
NumberStringBuilder sb3 = new NumberStringBuilder();
|
||||
sb1.appendCodePoint(cas);
|
||||
sb2.appendCodePoint(cas, null);
|
||||
sb3.appendCodePoint(cas, null);
|
||||
assertCharSequenceEquals(sb1, sb2);
|
||||
assertEquals(Character.codePointAt(sb3, 0), cas);
|
||||
|
||||
StringBuilder sb4 = new StringBuilder();
|
||||
NumberStringBuilder sb5 = new NumberStringBuilder();
|
||||
sb4.append("😇");
|
||||
sb4.appendCodePoint(cas); // Java StringBuilder has no insertCodePoint()
|
||||
sb4.append("xx");
|
||||
sb5.append("😇xx", null);
|
||||
sb5.insertCodePoint(2, cas, null);
|
||||
assertCharSequenceEquals(sb4, sb5);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopy() {
|
||||
for (String str : EXAMPLE_STRINGS) {
|
||||
NumberStringBuilder sb1 = new NumberStringBuilder();
|
||||
sb1.append(str, null);
|
||||
NumberStringBuilder sb2 = new NumberStringBuilder(sb1);
|
||||
assertCharSequenceEquals(sb1, sb2);
|
||||
assertTrue(sb1.contentEquals(sb2));
|
||||
|
||||
sb1.append("12345", null);
|
||||
assertNotEquals(sb1.length(), sb2.length());
|
||||
assertFalse(sb1.contentEquals(sb2));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFields() {
|
||||
for (String str : EXAMPLE_STRINGS) {
|
||||
NumberStringBuilder sb = new NumberStringBuilder();
|
||||
sb.append(str, null);
|
||||
sb.append(str, NumberFormat.Field.CURRENCY);
|
||||
Field[] fields = sb.toFieldArray();
|
||||
assertEquals(str.length() * 2, fields.length);
|
||||
for (int i = 0; i < str.length(); i++) {
|
||||
assertEquals(null, fields[i]);
|
||||
assertEquals(null, sb.fieldAt(i));
|
||||
assertEquals(NumberFormat.Field.CURRENCY, fields[i + str.length()]);
|
||||
assertEquals(NumberFormat.Field.CURRENCY, sb.fieldAt(i + str.length()));
|
||||
}
|
||||
|
||||
// Very basic FieldPosition test. More robust tests happen in NumberFormatTest.
|
||||
// Let NumberFormatTest also take care of AttributedCharacterIterator material.
|
||||
FieldPosition fp = new FieldPosition(NumberFormat.Field.CURRENCY);
|
||||
sb.populateFieldPosition(fp, 0);
|
||||
assertEquals(str.length(), fp.getBeginIndex());
|
||||
assertEquals(str.length() * 2, fp.getEndIndex());
|
||||
|
||||
if (str.length() > 0) {
|
||||
sb.insertCodePoint(2, 100, NumberFormat.Field.INTEGER);
|
||||
fields = sb.toFieldArray();
|
||||
assertEquals(str.length() * 2 + 1, fields.length);
|
||||
assertEquals(fields[2], NumberFormat.Field.INTEGER);
|
||||
}
|
||||
|
||||
sb.append(new NumberStringBuilder(sb));
|
||||
sb.append(sb.toCharArray(), sb.toFieldArray());
|
||||
int numNull = 0;
|
||||
int numCurr = 0;
|
||||
int numInt = 0;
|
||||
Field[] oldFields = fields;
|
||||
fields = sb.toFieldArray();
|
||||
for (int i = 0; i < sb.length(); i++) {
|
||||
assertEquals(oldFields[i % oldFields.length], fields[i]);
|
||||
if (fields[i] == null) {
|
||||
numNull++;
|
||||
} else if (fields[i] == NumberFormat.Field.CURRENCY) {
|
||||
numCurr++;
|
||||
} else if (fields[i] == NumberFormat.Field.INTEGER) {
|
||||
numInt++;
|
||||
} else {
|
||||
throw new AssertionError("Encountered unknown field in " + str);
|
||||
}
|
||||
}
|
||||
assertEquals(str.length() * 4, numNull);
|
||||
assertEquals(numNull, numCurr);
|
||||
assertEquals(str.length() > 0 ? 4 : 0, numInt);
|
||||
|
||||
NumberStringBuilder sb2 = new NumberStringBuilder();
|
||||
sb2.append(sb);
|
||||
assertTrue(sb.contentEquals(sb2));
|
||||
assertTrue(sb.contentEquals(sb2.toCharArray(), sb2.toFieldArray()));
|
||||
|
||||
sb2.insertCodePoint(0, 50, NumberFormat.Field.FRACTION);
|
||||
assertTrue(!sb.contentEquals(sb2));
|
||||
assertTrue(!sb.contentEquals(sb2.toCharArray(), sb2.toFieldArray()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnlimitedCapacity() {
|
||||
NumberStringBuilder builder = new NumberStringBuilder();
|
||||
// The builder should never fail upon repeated appends.
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
assertEquals(builder.length(), i);
|
||||
builder.appendCodePoint('x', null);
|
||||
assertEquals(builder.length(), i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCodePoints() {
|
||||
NumberStringBuilder nsb = new NumberStringBuilder();
|
||||
assertEquals("First is -1 on empty string", -1, nsb.getFirstCodePoint());
|
||||
assertEquals("Last is -1 on empty string", -1, nsb.getLastCodePoint());
|
||||
assertEquals("Length is 0 on empty string", 0, nsb.codePointCount());
|
||||
|
||||
nsb.append("q", null);
|
||||
assertEquals("First is q", 'q', nsb.getFirstCodePoint());
|
||||
assertEquals("Last is q", 'q', nsb.getLastCodePoint());
|
||||
assertEquals("0th is q", 'q', nsb.codePointAt(0));
|
||||
assertEquals("Before 1st is q", 'q', nsb.codePointBefore(1));
|
||||
assertEquals("Code point count is 1", 1, nsb.codePointCount());
|
||||
|
||||
// 🚀 is two char16s
|
||||
nsb.append("🚀", null);
|
||||
assertEquals("First is still q", 'q', nsb.getFirstCodePoint());
|
||||
assertEquals("Last is space ship", 128640, nsb.getLastCodePoint());
|
||||
assertEquals("1st is space ship", 128640, nsb.codePointAt(1));
|
||||
assertEquals("Before 1st is q", 'q', nsb.codePointBefore(1));
|
||||
assertEquals("Before 3rd is space ship", 128640, nsb.codePointBefore(3));
|
||||
assertEquals("Code point count is 2", 2, nsb.codePointCount());
|
||||
}
|
||||
|
||||
private static void assertCharSequenceEquals(CharSequence a, CharSequence b) {
|
||||
assertEquals(a.toString(), b.toString());
|
||||
|
||||
assertEquals(a.length(), b.length());
|
||||
for (int i = 0; i < a.length(); i++) {
|
||||
assertEquals(a.charAt(i), b.charAt(i));
|
||||
}
|
||||
|
||||
int start = Math.min(2, a.length());
|
||||
int end = Math.min(12, a.length());
|
||||
if (start != end) {
|
||||
assertCharSequenceEquals(a.subSequence(start, end), b.subSequence(start, end));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,104 +16,113 @@ import com.ibm.icu.util.ULocale;
|
||||
/** @author sffc */
|
||||
public class PatternStringTest {
|
||||
|
||||
@Test
|
||||
public void testLocalized() {
|
||||
DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(ULocale.ENGLISH);
|
||||
symbols.setDecimalSeparatorString("a");
|
||||
symbols.setPercentString("b");
|
||||
symbols.setMinusSignString(".");
|
||||
symbols.setPlusSignString("'");
|
||||
@Test
|
||||
public void testLocalized() {
|
||||
DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(ULocale.ENGLISH);
|
||||
symbols.setDecimalSeparatorString("a");
|
||||
symbols.setPercentString("b");
|
||||
symbols.setMinusSignString(".");
|
||||
symbols.setPlusSignString("'");
|
||||
|
||||
String standard = "+-abcb''a''#,##0.0%'a%'";
|
||||
String localized = "’.'ab'c'b''a'''#,##0a0b'a%'";
|
||||
String toStandard = "+-'ab'c'b''a'''#,##0.0%'a%'";
|
||||
String standard = "+-abcb''a''#,##0.0%'a%'";
|
||||
String localized = "’.'ab'c'b''a'''#,##0a0b'a%'";
|
||||
String toStandard = "+-'ab'c'b''a'''#,##0.0%'a%'";
|
||||
|
||||
assertEquals(localized, PatternStringUtils.convertLocalized(standard, symbols, true));
|
||||
assertEquals(toStandard, PatternStringUtils.convertLocalized(localized, symbols, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToPatternSimple() {
|
||||
String[][] cases = {
|
||||
{"#", "0"},
|
||||
{"0", "0"},
|
||||
{"#0", "0"},
|
||||
{"###", "0"},
|
||||
{"0.##", "0.##"},
|
||||
{"0.00", "0.00"},
|
||||
{"0.00#", "0.00#"},
|
||||
{"#E0", "#E0"},
|
||||
{"0E0", "0E0"},
|
||||
{"#00E00", "#00E00"},
|
||||
{"#,##0", "#,##0"},
|
||||
{"#;#", "0;0"},
|
||||
{"#;-#", "0"}, // ignore a negative prefix pattern of '-' since that is the default
|
||||
{"**##0", "**##0"},
|
||||
{"*'x'##0", "*x##0"},
|
||||
{"a''b0", "a''b0"},
|
||||
{"*''##0", "*''##0"},
|
||||
{"*📺##0", "*'📺'##0"},
|
||||
{"*'நி'##0", "*'நி'##0"},
|
||||
};
|
||||
|
||||
for (String[] cas : cases) {
|
||||
String input = cas[0];
|
||||
String output = cas[1];
|
||||
|
||||
DecimalFormatProperties properties = PatternStringParser.parseToProperties(input);
|
||||
String actual = PatternStringUtils.propertiesToPatternString(properties);
|
||||
assertEquals(
|
||||
"Failed on input pattern '" + input + "', properties " + properties, output, actual);
|
||||
assertEquals(localized, PatternStringUtils.convertLocalized(standard, symbols, true));
|
||||
assertEquals(toStandard, PatternStringUtils.convertLocalized(localized, symbols, false));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToPatternWithProperties() {
|
||||
Object[][] cases = {
|
||||
{new DecimalFormatProperties().setPositivePrefix("abc"), "abc#"},
|
||||
{new DecimalFormatProperties().setPositiveSuffix("abc"), "#abc"},
|
||||
{new DecimalFormatProperties().setPositivePrefixPattern("abc"), "abc#"},
|
||||
{new DecimalFormatProperties().setPositiveSuffixPattern("abc"), "#abc"},
|
||||
{new DecimalFormatProperties().setNegativePrefix("abc"), "#;abc#"},
|
||||
{new DecimalFormatProperties().setNegativeSuffix("abc"), "#;#abc"},
|
||||
{new DecimalFormatProperties().setNegativePrefixPattern("abc"), "#;abc#"},
|
||||
{new DecimalFormatProperties().setNegativeSuffixPattern("abc"), "#;#abc"},
|
||||
{new DecimalFormatProperties().setPositivePrefix("+"), "'+'#"},
|
||||
{new DecimalFormatProperties().setPositivePrefixPattern("+"), "+#"},
|
||||
{new DecimalFormatProperties().setPositivePrefix("+'"), "'+'''#"},
|
||||
{new DecimalFormatProperties().setPositivePrefix("'+"), "'''+'#"},
|
||||
{new DecimalFormatProperties().setPositivePrefix("'"), "''#"},
|
||||
{new DecimalFormatProperties().setPositivePrefixPattern("+''"), "+''#"},
|
||||
};
|
||||
@Test
|
||||
public void testToPatternSimple() {
|
||||
String[][] cases = {
|
||||
{ "#", "0" },
|
||||
{ "0", "0" },
|
||||
{ "#0", "0" },
|
||||
{ "###", "0" },
|
||||
{ "0.##", "0.##" },
|
||||
{ "0.00", "0.00" },
|
||||
{ "0.00#", "0.00#" },
|
||||
{ "#E0", "#E0" },
|
||||
{ "0E0", "0E0" },
|
||||
{ "#00E00", "#00E00" },
|
||||
{ "#,##0", "#,##0" },
|
||||
{ "#;#", "0;0" },
|
||||
{ "#;-#", "0" }, // ignore a negative prefix pattern of '-' since that is the default
|
||||
{ "**##0", "**##0" },
|
||||
{ "*'x'##0", "*x##0" },
|
||||
{ "a''b0", "a''b0" },
|
||||
{ "*''##0", "*''##0" },
|
||||
{ "*📺##0", "*'📺'##0" },
|
||||
{ "*'நி'##0", "*'நி'##0" }, };
|
||||
|
||||
for (Object[] cas : cases) {
|
||||
DecimalFormatProperties input = (DecimalFormatProperties) cas[0];
|
||||
String output = (String) cas[1];
|
||||
for (String[] cas : cases) {
|
||||
String input = cas[0];
|
||||
String output = cas[1];
|
||||
|
||||
String actual = PatternStringUtils.propertiesToPatternString(input);
|
||||
assertEquals("Failed on input properties " + input, output, actual);
|
||||
DecimalFormatProperties properties = PatternStringParser.parseToProperties(input);
|
||||
String actual = PatternStringUtils.propertiesToPatternString(properties);
|
||||
assertEquals("Failed on input pattern '" + input + "', properties " + properties,
|
||||
output,
|
||||
actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExceptionOnInvalid() {
|
||||
String[] invalidPatterns = {
|
||||
"#.#.#", "0#", "0#.", ".#0", "0#.#0", "@0", "0@", "0,", "0,,", "0,,0", "0,,0,", "#,##0E0"
|
||||
};
|
||||
@Test
|
||||
public void testToPatternWithProperties() {
|
||||
Object[][] cases = {
|
||||
{ new DecimalFormatProperties().setPositivePrefix("abc"), "abc#" },
|
||||
{ new DecimalFormatProperties().setPositiveSuffix("abc"), "#abc" },
|
||||
{ new DecimalFormatProperties().setPositivePrefixPattern("abc"), "abc#" },
|
||||
{ new DecimalFormatProperties().setPositiveSuffixPattern("abc"), "#abc" },
|
||||
{ new DecimalFormatProperties().setNegativePrefix("abc"), "#;abc#" },
|
||||
{ new DecimalFormatProperties().setNegativeSuffix("abc"), "#;#abc" },
|
||||
{ new DecimalFormatProperties().setNegativePrefixPattern("abc"), "#;abc#" },
|
||||
{ new DecimalFormatProperties().setNegativeSuffixPattern("abc"), "#;#abc" },
|
||||
{ new DecimalFormatProperties().setPositivePrefix("+"), "'+'#" },
|
||||
{ new DecimalFormatProperties().setPositivePrefixPattern("+"), "+#" },
|
||||
{ new DecimalFormatProperties().setPositivePrefix("+'"), "'+'''#" },
|
||||
{ new DecimalFormatProperties().setPositivePrefix("'+"), "'''+'#" },
|
||||
{ new DecimalFormatProperties().setPositivePrefix("'"), "''#" },
|
||||
{ new DecimalFormatProperties().setPositivePrefixPattern("+''"), "+''#" }, };
|
||||
|
||||
for (String pattern : invalidPatterns) {
|
||||
try {
|
||||
PatternStringParser.parseToProperties(pattern);
|
||||
fail("Didn't throw IllegalArgumentException when parsing pattern: " + pattern);
|
||||
} catch (IllegalArgumentException e) {
|
||||
}
|
||||
for (Object[] cas : cases) {
|
||||
DecimalFormatProperties input = (DecimalFormatProperties) cas[0];
|
||||
String output = (String) cas[1];
|
||||
|
||||
String actual = PatternStringUtils.propertiesToPatternString(input);
|
||||
assertEquals("Failed on input properties " + input, output, actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug13117() {
|
||||
DecimalFormatProperties expected = PatternStringParser.parseToProperties("0");
|
||||
DecimalFormatProperties actual = PatternStringParser.parseToProperties("0;");
|
||||
assertEquals("Should not consume negative subpattern", expected, actual);
|
||||
}
|
||||
@Test
|
||||
public void testExceptionOnInvalid() {
|
||||
String[] invalidPatterns = {
|
||||
"#.#.#",
|
||||
"0#",
|
||||
"0#.",
|
||||
".#0",
|
||||
"0#.#0",
|
||||
"@0",
|
||||
"0@",
|
||||
"0,",
|
||||
"0,,",
|
||||
"0,,0",
|
||||
"0,,0,",
|
||||
"#,##0E0" };
|
||||
|
||||
for (String pattern : invalidPatterns) {
|
||||
try {
|
||||
PatternStringParser.parseToProperties(pattern);
|
||||
fail("Didn't throw IllegalArgumentException when parsing pattern: " + pattern);
|
||||
} catch (IllegalArgumentException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug13117() {
|
||||
DecimalFormatProperties expected = PatternStringParser.parseToProperties("0");
|
||||
DecimalFormatProperties actual = PatternStringParser.parseToProperties("0;");
|
||||
assertEquals("Should not consume negative subpattern", expected, actual);
|
||||
}
|
||||
}
|
||||
|
@ -45,322 +45,341 @@ import com.ibm.icu.util.ULocale;
|
||||
|
||||
public class PropertiesTest {
|
||||
|
||||
@Test
|
||||
public void testBasicEquals() {
|
||||
DecimalFormatProperties p1 = new DecimalFormatProperties();
|
||||
DecimalFormatProperties p2 = new DecimalFormatProperties();
|
||||
assertEquals(p1, p2);
|
||||
@Test
|
||||
public void testBasicEquals() {
|
||||
DecimalFormatProperties p1 = new DecimalFormatProperties();
|
||||
DecimalFormatProperties p2 = new DecimalFormatProperties();
|
||||
assertEquals(p1, p2);
|
||||
|
||||
p1.setPositivePrefix("abc");
|
||||
assertNotEquals(p1, p2);
|
||||
p2.setPositivePrefix("xyz");
|
||||
assertNotEquals(p1, p2);
|
||||
p1.setPositivePrefix("xyz");
|
||||
assertEquals(p1, p2);
|
||||
}
|
||||
p1.setPositivePrefix("abc");
|
||||
assertNotEquals(p1, p2);
|
||||
p2.setPositivePrefix("xyz");
|
||||
assertNotEquals(p1, p2);
|
||||
p1.setPositivePrefix("xyz");
|
||||
assertEquals(p1, p2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFieldCoverage() {
|
||||
DecimalFormatProperties p0 = new DecimalFormatProperties();
|
||||
DecimalFormatProperties p1 = new DecimalFormatProperties();
|
||||
DecimalFormatProperties p2 = new DecimalFormatProperties();
|
||||
DecimalFormatProperties p3 = new DecimalFormatProperties();
|
||||
DecimalFormatProperties p4 = new DecimalFormatProperties();
|
||||
@Test
|
||||
public void testFieldCoverage() {
|
||||
DecimalFormatProperties p0 = new DecimalFormatProperties();
|
||||
DecimalFormatProperties p1 = new DecimalFormatProperties();
|
||||
DecimalFormatProperties p2 = new DecimalFormatProperties();
|
||||
DecimalFormatProperties p3 = new DecimalFormatProperties();
|
||||
DecimalFormatProperties p4 = new DecimalFormatProperties();
|
||||
|
||||
Set<Integer> hashCodes = new HashSet<Integer>();
|
||||
Field[] fields = DecimalFormatProperties.class.getDeclaredFields();
|
||||
for (Field field : fields) {
|
||||
if (Modifier.isStatic(field.getModifiers())) {
|
||||
continue;
|
||||
}
|
||||
Set<Integer> hashCodes = new HashSet<Integer>();
|
||||
Field[] fields = DecimalFormatProperties.class.getDeclaredFields();
|
||||
for (Field field : fields) {
|
||||
if (Modifier.isStatic(field.getModifiers())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for getters and setters
|
||||
String fieldNamePascalCase =
|
||||
Character.toUpperCase(field.getName().charAt(0)) + field.getName().substring(1);
|
||||
String getterName = "get" + fieldNamePascalCase;
|
||||
String setterName = "set" + fieldNamePascalCase;
|
||||
Method getter, setter;
|
||||
try {
|
||||
getter = DecimalFormatProperties.class.getMethod(getterName);
|
||||
assertEquals(
|
||||
"Getter does not return correct type", field.getType(), getter.getReturnType());
|
||||
} catch (NoSuchMethodException e) {
|
||||
fail("Could not find method " + getterName + " for field " + field);
|
||||
continue;
|
||||
} catch (SecurityException e) {
|
||||
fail("Could not access method " + getterName + " for field " + field);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
setter = DecimalFormatProperties.class.getMethod(setterName, field.getType());
|
||||
assertEquals(
|
||||
"Method " + setterName + " does not return correct type",
|
||||
DecimalFormatProperties.class,
|
||||
setter.getReturnType());
|
||||
} catch (NoSuchMethodException e) {
|
||||
fail("Could not find method " + setterName + " for field " + field);
|
||||
continue;
|
||||
} catch (SecurityException e) {
|
||||
fail("Could not access method " + setterName + " for field " + field);
|
||||
continue;
|
||||
}
|
||||
// Check for getters and setters
|
||||
String fieldNamePascalCase = Character.toUpperCase(field.getName().charAt(0))
|
||||
+ field.getName().substring(1);
|
||||
String getterName = "get" + fieldNamePascalCase;
|
||||
String setterName = "set" + fieldNamePascalCase;
|
||||
Method getter, setter;
|
||||
try {
|
||||
getter = DecimalFormatProperties.class.getMethod(getterName);
|
||||
assertEquals("Getter does not return correct type",
|
||||
field.getType(),
|
||||
getter.getReturnType());
|
||||
} catch (NoSuchMethodException e) {
|
||||
fail("Could not find method " + getterName + " for field " + field);
|
||||
continue;
|
||||
} catch (SecurityException e) {
|
||||
fail("Could not access method " + getterName + " for field " + field);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
setter = DecimalFormatProperties.class.getMethod(setterName, field.getType());
|
||||
assertEquals("Method " + setterName + " does not return correct type",
|
||||
DecimalFormatProperties.class,
|
||||
setter.getReturnType());
|
||||
} catch (NoSuchMethodException e) {
|
||||
fail("Could not find method " + setterName + " for field " + field);
|
||||
continue;
|
||||
} catch (SecurityException e) {
|
||||
fail("Could not access method " + setterName + " for field " + field);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for parameter name equality.
|
||||
// The parameter name is not always available, depending on compiler settings.
|
||||
// TODO: Enable in Java 8
|
||||
/*
|
||||
Parameter param = setter.getParameters()[0];
|
||||
if (!param.getName().subSequence(0, 3).equals("arg")) {
|
||||
assertEquals("Parameter name should equal field name", field.getName(), param.getName());
|
||||
}
|
||||
*/
|
||||
// Check for parameter name equality.
|
||||
// The parameter name is not always available, depending on compiler settings.
|
||||
// TODO: Enable in Java 8
|
||||
/*
|
||||
* Parameter param = setter.getParameters()[0]; if (!param.getName().subSequence(0,
|
||||
* 3).equals("arg")) { assertEquals("Parameter name should equal field name",
|
||||
* field.getName(), param.getName()); }
|
||||
*/
|
||||
|
||||
try {
|
||||
// Check for default value (should be null for objects)
|
||||
if (field.getType() != Integer.TYPE && field.getType() != Boolean.TYPE) {
|
||||
Object default0 = getter.invoke(p0);
|
||||
assertEquals("Field " + field + " has non-null default value:", null, default0);
|
||||
try {
|
||||
// Check for default value (should be null for objects)
|
||||
if (field.getType() != Integer.TYPE && field.getType() != Boolean.TYPE) {
|
||||
Object default0 = getter.invoke(p0);
|
||||
assertEquals("Field " + field + " has non-null default value:", null, default0);
|
||||
}
|
||||
|
||||
// Check for getter, equals, and hash code behavior
|
||||
Object val0 = getSampleValueForType(field.getType(), 0);
|
||||
Object val1 = getSampleValueForType(field.getType(), 1);
|
||||
Object val2 = getSampleValueForType(field.getType(), 2);
|
||||
assertNotEquals(val0, val1);
|
||||
setter.invoke(p1, val0);
|
||||
setter.invoke(p2, val0);
|
||||
assertEquals(p1, p2);
|
||||
assertEquals(p1.hashCode(), p2.hashCode());
|
||||
assertEquals(getter.invoke(p1), getter.invoke(p2));
|
||||
assertEquals(getter.invoke(p1), val0);
|
||||
assertNotEquals(getter.invoke(p1), val1);
|
||||
hashCodes.add(p1.hashCode());
|
||||
setter.invoke(p1, val1);
|
||||
assertNotEquals("Field " + field + " is missing from equals()", p1, p2);
|
||||
assertNotEquals(getter.invoke(p1), getter.invoke(p2));
|
||||
assertNotEquals(getter.invoke(p1), val0);
|
||||
assertEquals(getter.invoke(p1), val1);
|
||||
setter.invoke(p1, val0);
|
||||
assertEquals("Field " + field + " setter might have side effects", p1, p2);
|
||||
assertEquals(p1.hashCode(), p2.hashCode());
|
||||
assertEquals(getter.invoke(p1), getter.invoke(p2));
|
||||
setter.invoke(p1, val1);
|
||||
setter.invoke(p2, val1);
|
||||
assertEquals(p1, p2);
|
||||
assertEquals(p1.hashCode(), p2.hashCode());
|
||||
assertEquals(getter.invoke(p1), getter.invoke(p2));
|
||||
setter.invoke(p1, val2);
|
||||
setter.invoke(p1, val1);
|
||||
assertEquals("Field " + field + " setter might have side effects", p1, p2);
|
||||
assertEquals(p1.hashCode(), p2.hashCode());
|
||||
assertEquals(getter.invoke(p1), getter.invoke(p2));
|
||||
hashCodes.add(p1.hashCode());
|
||||
|
||||
// Check for clone behavior
|
||||
DecimalFormatProperties copy = p1.clone();
|
||||
assertEquals("Field " + field + " did not get copied in clone", p1, copy);
|
||||
assertEquals(p1.hashCode(), copy.hashCode());
|
||||
assertEquals(getter.invoke(p1), getter.invoke(copy));
|
||||
|
||||
// Check for copyFrom behavior
|
||||
setter.invoke(p1, val0);
|
||||
assertNotEquals(p1, p2);
|
||||
assertNotEquals(getter.invoke(p1), getter.invoke(p2));
|
||||
p2.copyFrom(p1);
|
||||
assertEquals("Field " + field + " is missing from copyFrom()", p1, p2);
|
||||
assertEquals(p1.hashCode(), p2.hashCode());
|
||||
assertEquals(getter.invoke(p1), getter.invoke(p2));
|
||||
|
||||
// Load values into p3 and p4 for clear() behavior test
|
||||
setter.invoke(p3, getSampleValueForType(field.getType(), 3));
|
||||
hashCodes.add(p3.hashCode());
|
||||
setter.invoke(p4, getSampleValueForType(field.getType(), 4));
|
||||
hashCodes.add(p4.hashCode());
|
||||
} catch (IllegalAccessException e) {
|
||||
fail("Could not access method for field " + field);
|
||||
} catch (IllegalArgumentException e) {
|
||||
fail("Could call method for field " + field);
|
||||
} catch (InvocationTargetException e) {
|
||||
fail("Could invoke method on target for field " + field);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for getter, equals, and hash code behavior
|
||||
Object val0 = getSampleValueForType(field.getType(), 0);
|
||||
Object val1 = getSampleValueForType(field.getType(), 1);
|
||||
Object val2 = getSampleValueForType(field.getType(), 2);
|
||||
assertNotEquals(val0, val1);
|
||||
setter.invoke(p1, val0);
|
||||
setter.invoke(p2, val0);
|
||||
assertEquals(p1, p2);
|
||||
assertEquals(p1.hashCode(), p2.hashCode());
|
||||
assertEquals(getter.invoke(p1), getter.invoke(p2));
|
||||
assertEquals(getter.invoke(p1), val0);
|
||||
assertNotEquals(getter.invoke(p1), val1);
|
||||
hashCodes.add(p1.hashCode());
|
||||
setter.invoke(p1, val1);
|
||||
assertNotEquals("Field " + field + " is missing from equals()", p1, p2);
|
||||
assertNotEquals(getter.invoke(p1), getter.invoke(p2));
|
||||
assertNotEquals(getter.invoke(p1), val0);
|
||||
assertEquals(getter.invoke(p1), val1);
|
||||
setter.invoke(p1, val0);
|
||||
assertEquals("Field " + field + " setter might have side effects", p1, p2);
|
||||
assertEquals(p1.hashCode(), p2.hashCode());
|
||||
assertEquals(getter.invoke(p1), getter.invoke(p2));
|
||||
setter.invoke(p1, val1);
|
||||
setter.invoke(p2, val1);
|
||||
assertEquals(p1, p2);
|
||||
assertEquals(p1.hashCode(), p2.hashCode());
|
||||
assertEquals(getter.invoke(p1), getter.invoke(p2));
|
||||
setter.invoke(p1, val2);
|
||||
setter.invoke(p1, val1);
|
||||
assertEquals("Field " + field + " setter might have side effects", p1, p2);
|
||||
assertEquals(p1.hashCode(), p2.hashCode());
|
||||
assertEquals(getter.invoke(p1), getter.invoke(p2));
|
||||
hashCodes.add(p1.hashCode());
|
||||
// Check for clear() behavior
|
||||
assertNotEquals(p3, p4);
|
||||
p3.clear();
|
||||
p4.clear();
|
||||
assertEquals("A field is missing from the clear() function", p3, p4);
|
||||
|
||||
// Check for clone behavior
|
||||
DecimalFormatProperties copy = p1.clone();
|
||||
assertEquals("Field " + field + " did not get copied in clone", p1, copy);
|
||||
assertEquals(p1.hashCode(), copy.hashCode());
|
||||
assertEquals(getter.invoke(p1), getter.invoke(copy));
|
||||
|
||||
// Check for copyFrom behavior
|
||||
setter.invoke(p1, val0);
|
||||
assertNotEquals(p1, p2);
|
||||
assertNotEquals(getter.invoke(p1), getter.invoke(p2));
|
||||
p2.copyFrom(p1);
|
||||
assertEquals("Field " + field + " is missing from copyFrom()", p1, p2);
|
||||
assertEquals(p1.hashCode(), p2.hashCode());
|
||||
assertEquals(getter.invoke(p1), getter.invoke(p2));
|
||||
|
||||
// Load values into p3 and p4 for clear() behavior test
|
||||
setter.invoke(p3, getSampleValueForType(field.getType(), 3));
|
||||
hashCodes.add(p3.hashCode());
|
||||
setter.invoke(p4, getSampleValueForType(field.getType(), 4));
|
||||
hashCodes.add(p4.hashCode());
|
||||
} catch (IllegalAccessException e) {
|
||||
fail("Could not access method for field " + field);
|
||||
} catch (IllegalArgumentException e) {
|
||||
fail("Could call method for field " + field);
|
||||
} catch (InvocationTargetException e) {
|
||||
fail("Could invoke method on target for field " + field);
|
||||
}
|
||||
// A good hashCode() implementation should produce very few collisions. We added at most
|
||||
// 4*fields.length codes to the set. We'll say the implementation is good if we had at least
|
||||
// fields.length unique values.
|
||||
// TODO: Should the requirement be stronger than this?
|
||||
assertTrue(
|
||||
"Too many hash code collisions: " + hashCodes.size() + " out of " + (fields.length * 4),
|
||||
hashCodes.size() >= fields.length);
|
||||
}
|
||||
|
||||
// Check for clear() behavior
|
||||
assertNotEquals(p3, p4);
|
||||
p3.clear();
|
||||
p4.clear();
|
||||
assertEquals("A field is missing from the clear() function", p3, p4);
|
||||
/**
|
||||
* Creates a valid sample instance of the given type. Used to simulate getters and setters.
|
||||
*
|
||||
* @param type
|
||||
* The type to generate.
|
||||
* @param seed
|
||||
* An integer seed, guaranteed to be positive. The same seed should generate two instances
|
||||
* that are equal. A different seed should in general generate two instances that are not
|
||||
* equal; this might not always be possible, such as with booleans or enums where there
|
||||
* are limited possible values.
|
||||
* @return An instance of the specified type.
|
||||
*/
|
||||
Object getSampleValueForType(Class<?> type, int seed) {
|
||||
if (type == Integer.TYPE) {
|
||||
return seed * 1000001;
|
||||
|
||||
// A good hashCode() implementation should produce very few collisions. We added at most
|
||||
// 4*fields.length codes to the set. We'll say the implementation is good if we had at least
|
||||
// fields.length unique values.
|
||||
// TODO: Should the requirement be stronger than this?
|
||||
assertTrue(
|
||||
"Too many hash code collisions: " + hashCodes.size() + " out of " + (fields.length * 4),
|
||||
hashCodes.size() >= fields.length);
|
||||
}
|
||||
} else if (type == Boolean.TYPE) {
|
||||
return (seed % 2) == 0;
|
||||
|
||||
/**
|
||||
* Creates a valid sample instance of the given type. Used to simulate getters and setters.
|
||||
*
|
||||
* @param type The type to generate.
|
||||
* @param seed An integer seed, guaranteed to be positive. The same seed should generate two
|
||||
* instances that are equal. A different seed should in general generate two instances that
|
||||
* are not equal; this might not always be possible, such as with booleans or enums where
|
||||
* there are limited possible values.
|
||||
* @return An instance of the specified type.
|
||||
*/
|
||||
Object getSampleValueForType(Class<?> type, int seed) {
|
||||
if (type == Integer.TYPE) {
|
||||
return seed * 1000001;
|
||||
} else if (type == BigDecimal.class) {
|
||||
if (seed == 0)
|
||||
return null;
|
||||
return new BigDecimal(seed * 1000002);
|
||||
|
||||
} else if (type == Boolean.TYPE) {
|
||||
return (seed % 2) == 0;
|
||||
} else if (type == String.class) {
|
||||
if (seed == 0)
|
||||
return null;
|
||||
return BigInteger.valueOf(seed * 1000003).toString(32);
|
||||
|
||||
} else if (type == BigDecimal.class) {
|
||||
if (seed == 0) return null;
|
||||
return new BigDecimal(seed * 1000002);
|
||||
} else if (type == CompactStyle.class) {
|
||||
if (seed == 0)
|
||||
return null;
|
||||
CompactStyle[] values = CompactStyle.values();
|
||||
return values[seed % values.length];
|
||||
|
||||
} else if (type == String.class) {
|
||||
if (seed == 0) return null;
|
||||
return BigInteger.valueOf(seed * 1000003).toString(32);
|
||||
} else if (type == Currency.class) {
|
||||
if (seed == 0)
|
||||
return null;
|
||||
Object[] currencies = Currency.getAvailableCurrencies().toArray();
|
||||
return currencies[seed % currencies.length];
|
||||
|
||||
} else if (type == CompactStyle.class) {
|
||||
if (seed == 0) return null;
|
||||
CompactStyle[] values = CompactStyle.values();
|
||||
return values[seed % values.length];
|
||||
} else if (type == CurrencyPluralInfo.class) {
|
||||
if (seed == 0)
|
||||
return null;
|
||||
ULocale[] locales = ULocale.getAvailableLocales();
|
||||
return CurrencyPluralInfo.getInstance(locales[seed % locales.length]);
|
||||
|
||||
} else if (type == Currency.class) {
|
||||
if (seed == 0) return null;
|
||||
Object[] currencies = Currency.getAvailableCurrencies().toArray();
|
||||
return currencies[seed % currencies.length];
|
||||
} else if (type == CurrencyUsage.class) {
|
||||
if (seed == 0)
|
||||
return null;
|
||||
CurrencyUsage[] values = CurrencyUsage.values();
|
||||
return values[seed % values.length];
|
||||
|
||||
} else if (type == CurrencyPluralInfo.class) {
|
||||
if (seed == 0) return null;
|
||||
ULocale[] locales = ULocale.getAvailableLocales();
|
||||
return CurrencyPluralInfo.getInstance(locales[seed % locales.length]);
|
||||
} else if (type == GroupingMode.class) {
|
||||
if (seed == 0)
|
||||
return null;
|
||||
GroupingMode[] values = GroupingMode.values();
|
||||
return values[seed % values.length];
|
||||
|
||||
} else if (type == CurrencyUsage.class) {
|
||||
if (seed == 0) return null;
|
||||
CurrencyUsage[] values = CurrencyUsage.values();
|
||||
return values[seed % values.length];
|
||||
} else if (type == FormatWidth.class) {
|
||||
if (seed == 0)
|
||||
return null;
|
||||
FormatWidth[] values = FormatWidth.values();
|
||||
return values[seed % values.length];
|
||||
|
||||
} else if (type == GroupingMode.class) {
|
||||
if (seed == 0) return null;
|
||||
GroupingMode[] values = GroupingMode.values();
|
||||
return values[seed % values.length];
|
||||
} else if (type == Map.class) {
|
||||
// Map<String,Map<String,String>> for compactCustomData property
|
||||
if (seed == 0)
|
||||
return null;
|
||||
Map<String, Map<String, String>> outer = new HashMap<String, Map<String, String>>();
|
||||
Map<String, String> inner = new HashMap<String, String>();
|
||||
inner.put("one", "0 thousand");
|
||||
StringBuilder magnitudeKey = new StringBuilder();
|
||||
magnitudeKey.append("1000");
|
||||
for (int i = 0; i < seed % 9; i++) {
|
||||
magnitudeKey.append("0");
|
||||
}
|
||||
outer.put(magnitudeKey.toString(), inner);
|
||||
return outer;
|
||||
|
||||
} else if (type == FormatWidth.class) {
|
||||
if (seed == 0) return null;
|
||||
FormatWidth[] values = FormatWidth.values();
|
||||
return values[seed % values.length];
|
||||
} else if (type == MathContext.class) {
|
||||
if (seed == 0)
|
||||
return null;
|
||||
RoundingMode[] modes = RoundingMode.values();
|
||||
return new MathContext(seed, modes[seed % modes.length]);
|
||||
|
||||
} else if (type == Map.class) {
|
||||
// Map<String,Map<String,String>> for compactCustomData property
|
||||
if (seed == 0) return null;
|
||||
Map<String, Map<String, String>> outer = new HashMap<String, Map<String, String>>();
|
||||
Map<String, String> inner = new HashMap<String, String>();
|
||||
inner.put("one", "0 thousand");
|
||||
StringBuilder magnitudeKey = new StringBuilder();
|
||||
magnitudeKey.append("1000");
|
||||
for (int i = 0; i < seed % 9; i++) {
|
||||
magnitudeKey.append("0");
|
||||
}
|
||||
outer.put(magnitudeKey.toString(), inner);
|
||||
return outer;
|
||||
} else if (type == MeasureUnit.class) {
|
||||
if (seed == 0)
|
||||
return null;
|
||||
Object[] units = MeasureUnit.getAvailable().toArray();
|
||||
return units[seed % units.length];
|
||||
|
||||
} else if (type == MathContext.class) {
|
||||
if (seed == 0) return null;
|
||||
RoundingMode[] modes = RoundingMode.values();
|
||||
return new MathContext(seed, modes[seed % modes.length]);
|
||||
} else if (type == PadPosition.class) {
|
||||
if (seed == 0)
|
||||
return null;
|
||||
PadPosition[] values = PadPosition.values();
|
||||
return values[seed % values.length];
|
||||
|
||||
} else if (type == MeasureUnit.class) {
|
||||
if (seed == 0) return null;
|
||||
Object[] units = MeasureUnit.getAvailable().toArray();
|
||||
return units[seed % units.length];
|
||||
} else if (type == ParseMode.class) {
|
||||
if (seed == 0)
|
||||
return null;
|
||||
ParseMode[] values = ParseMode.values();
|
||||
return values[seed % values.length];
|
||||
|
||||
} else if (type == PadPosition.class) {
|
||||
if (seed == 0) return null;
|
||||
PadPosition[] values = PadPosition.values();
|
||||
return values[seed % values.length];
|
||||
} else if (type == PluralRules.class) {
|
||||
if (seed == 0)
|
||||
return null;
|
||||
ULocale[] locales = PluralRules.getAvailableULocales();
|
||||
return PluralRules.forLocale(locales[seed % locales.length]);
|
||||
|
||||
} else if (type == ParseMode.class) {
|
||||
if (seed == 0) return null;
|
||||
ParseMode[] values = ParseMode.values();
|
||||
return values[seed % values.length];
|
||||
} else if (type == RoundingMode.class) {
|
||||
if (seed == 0)
|
||||
return null;
|
||||
RoundingMode[] values = RoundingMode.values();
|
||||
return values[seed % values.length];
|
||||
|
||||
} else if (type == PluralRules.class) {
|
||||
if (seed == 0) return null;
|
||||
ULocale[] locales = PluralRules.getAvailableULocales();
|
||||
return PluralRules.forLocale(locales[seed % locales.length]);
|
||||
|
||||
} else if (type == RoundingMode.class) {
|
||||
if (seed == 0) return null;
|
||||
RoundingMode[] values = RoundingMode.values();
|
||||
return values[seed % values.length];
|
||||
|
||||
} else {
|
||||
fail("Don't know how to handle type " + type + ". Please add it to getSampleValueForType().");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void TestBasicSerializationRoundTrip() throws IOException, ClassNotFoundException {
|
||||
DecimalFormatProperties props0 = new DecimalFormatProperties();
|
||||
|
||||
// Write values to some of the fields
|
||||
PatternStringParser.parseToExistingProperties("A-**####,#00.00#b¤", props0);
|
||||
|
||||
// Write to byte stream
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ObjectOutputStream oos = new ObjectOutputStream(baos);
|
||||
oos.writeObject(props0);
|
||||
oos.flush();
|
||||
baos.close();
|
||||
byte[] bytes = baos.toByteArray();
|
||||
|
||||
// Read from byte stream
|
||||
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
|
||||
Object obj = ois.readObject();
|
||||
ois.close();
|
||||
DecimalFormatProperties props1 = (DecimalFormatProperties) obj;
|
||||
|
||||
// Test equality
|
||||
assertEquals("Did not round-trip through serialization", props0, props1);
|
||||
}
|
||||
|
||||
/** Handler for serialization compatibility test suite. */
|
||||
public static class PropertiesHandler implements SerializableTestUtility.Handler {
|
||||
|
||||
@Override
|
||||
public Object[] getTestObjects() {
|
||||
return new Object[] {
|
||||
new DecimalFormatProperties(),
|
||||
PatternStringParser.parseToProperties("x#,##0.00%"),
|
||||
new DecimalFormatProperties().setCompactStyle(CompactStyle.LONG).setMinimumExponentDigits(2)
|
||||
};
|
||||
} else {
|
||||
fail("Don't know how to handle type "
|
||||
+ type
|
||||
+ ". Please add it to getSampleValueForType().");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSameBehavior(Object a, Object b) {
|
||||
return a.equals(b);
|
||||
}
|
||||
}
|
||||
@Test
|
||||
public void TestBasicSerializationRoundTrip() throws IOException, ClassNotFoundException {
|
||||
DecimalFormatProperties props0 = new DecimalFormatProperties();
|
||||
|
||||
/** Handler for the ICU 59 class named "Properties" before it was renamed to "DecimalFormatProperties". */
|
||||
public static class ICU59PropertiesHandler implements SerializableTestUtility.Handler {
|
||||
// Write values to some of the fields
|
||||
PatternStringParser.parseToExistingProperties("A-**####,#00.00#b¤", props0);
|
||||
|
||||
@Override
|
||||
public Object[] getTestObjects() {
|
||||
return new Object[] {
|
||||
new com.ibm.icu.impl.number.Properties()
|
||||
};
|
||||
// Write to byte stream
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ObjectOutputStream oos = new ObjectOutputStream(baos);
|
||||
oos.writeObject(props0);
|
||||
oos.flush();
|
||||
baos.close();
|
||||
byte[] bytes = baos.toByteArray();
|
||||
|
||||
// Read from byte stream
|
||||
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
|
||||
Object obj = ois.readObject();
|
||||
ois.close();
|
||||
DecimalFormatProperties props1 = (DecimalFormatProperties) obj;
|
||||
|
||||
// Test equality
|
||||
assertEquals("Did not round-trip through serialization", props0, props1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSameBehavior(Object a, Object b) {
|
||||
return true;
|
||||
/** Handler for serialization compatibility test suite. */
|
||||
public static class PropertiesHandler implements SerializableTestUtility.Handler {
|
||||
|
||||
@Override
|
||||
public Object[] getTestObjects() {
|
||||
return new Object[] {
|
||||
new DecimalFormatProperties(),
|
||||
PatternStringParser.parseToProperties("x#,##0.00%"),
|
||||
new DecimalFormatProperties().setCompactStyle(CompactStyle.LONG)
|
||||
.setMinimumExponentDigits(2) };
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSameBehavior(Object a, Object b) {
|
||||
return a.equals(b);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for the ICU 59 class named "Properties" before it was renamed to
|
||||
* "DecimalFormatProperties".
|
||||
*/
|
||||
public static class ICU59PropertiesHandler implements SerializableTestUtility.Handler {
|
||||
|
||||
@Override
|
||||
public Object[] getTestObjects() {
|
||||
return new Object[] { new com.ibm.icu.impl.number.Properties() };
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSameBehavior(Object a, Object b) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -488,4 +488,64 @@ public class RBBITest extends TestFmwk {
|
||||
assertEquals("", biFr, cloneFr);
|
||||
assertEquals("", ULocale.FRENCH, cloneFr.getLocale(ULocale.VALID_LOCALE));
|
||||
}
|
||||
|
||||
static class T13512Thread extends Thread {
|
||||
private String fText;
|
||||
public List fBoundaries;
|
||||
public List fExpectedBoundaries;
|
||||
|
||||
T13512Thread(String text) {
|
||||
fText = text;
|
||||
fExpectedBoundaries = getBoundary(fText);
|
||||
}
|
||||
@Override
|
||||
public void run() {
|
||||
for (int i= 0; i<10000; ++i) {
|
||||
fBoundaries = getBoundary(fText);
|
||||
if (!fBoundaries.equals(fExpectedBoundaries)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private static final BreakIterator BREAK_ITERATOR_CACHE = BreakIterator.getWordInstance(ULocale.ROOT);
|
||||
public static List<Integer> getBoundary(String toParse) {
|
||||
List<Integer> retVal = new ArrayList<Integer>();
|
||||
BreakIterator bi = (BreakIterator) BREAK_ITERATOR_CACHE.clone();
|
||||
bi.setText(toParse);
|
||||
for (int boundary=bi.first(); boundary != BreakIterator.DONE; boundary = bi.next()) {
|
||||
retVal.add(boundary);
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void TestBug13512() {
|
||||
String japanese = "コンピューターは、本質的には数字しか扱うことができません。コンピューターは、文字や記号などのそれぞれに番号を割り振る"
|
||||
+ "ことによって扱えるようにします。ユニコードが出来るまでは、これらの番号を割り振る仕組みが何百種類も存在しました。どの一つをとっても、十分な"
|
||||
+ "文字を含んではいませんでした。例えば、欧州連合一つを見ても、そのすべての言語をカバーするためには、いくつかの異なる符号化の仕"
|
||||
+ "組みが必要でした。英語のような一つの言語に限っても、一つだけの符号化の仕組みでは、一般的に使われるすべての文字、句読点、技術"
|
||||
+ "的な記号などを扱うには不十分でした。";
|
||||
|
||||
String thai = "โดยพื้นฐานแล้ว, คอมพิวเตอร์จะเกี่ยวข้องกับเรื่องของตัวเลข. คอมพิวเตอร์จัดเก็บตัวอักษรและอักขระอื่นๆ"
|
||||
+ " โดยการกำหนดหมายเลขให้สำหรับแต่ละตัว. ก่อนหน้าที่๊ Unicode จะถูกสร้างขึ้น, ได้มีระบบ encoding "
|
||||
+ "อยู่หลายร้อยระบบสำหรับการกำหนดหมายเลขเหล่านี้. ไม่มี encoding ใดที่มีจำนวนตัวอักขระมากเพียงพอ: ยกตัวอย่างเช่น, "
|
||||
+ "เฉพาะในกลุ่มสหภาพยุโรปเพียงแห่งเดียว ก็ต้องการหลาย encoding ในการครอบคลุมทุกภาษาในกลุ่ม. "
|
||||
+ "หรือแม้แต่ในภาษาเดี่ยว เช่น ภาษาอังกฤษ ก็ไม่มี encoding ใดที่เพียงพอสำหรับทุกตัวอักษร, "
|
||||
+ "เครื่องหมายวรรคตอน และสัญลักษณ์ทางเทคนิคที่ใช้กันอยู่ทั่วไป.\n" +
|
||||
"ระบบ encoding เหล่านี้ยังขัดแย้งซึ่งกันและกัน. นั่นก็คือ, ในสอง encoding สามารถใช้หมายเลขเดียวกันสำหรับตัวอักขระสองตัวที่แตกต่างกัน,"
|
||||
+ "หรือใช้หมายเลขต่างกันสำหรับอักขระตัวเดียวกัน. ในระบบคอมพิวเตอร์ (โดยเฉพาะเซิร์ฟเวอร์) ต้องมีการสนับสนุนหลาย"
|
||||
+ " encoding; และเมื่อข้อมูลที่ผ่านไปมาระหว่างการเข้ารหัสหรือแพล็ตฟอร์มที่ต่างกัน, ข้อมูลนั้นจะเสี่ยงต่อการผิดพลาดเสียหาย.";
|
||||
|
||||
T13512Thread t1 = new T13512Thread(thai);
|
||||
T13512Thread t2 = new T13512Thread(japanese);
|
||||
try {
|
||||
t1.start(); t2.start();
|
||||
t1.join(); t2.join();
|
||||
} catch (Exception e) {
|
||||
fail(e.toString());
|
||||
}
|
||||
assertEquals("", t1.fExpectedBoundaries, t1.fBoundaries);
|
||||
assertEquals("", t2.fExpectedBoundaries, t2.fBoundaries);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user