ICU-13513 Merging trunk to branch (includes the big reformatting commit).

X-SVN-Rev: 40751
This commit is contained in:
Shane Carr 2017-12-23 03:13:03 +00:00
commit bdb19075f8
77 changed files with 5494 additions and 4218 deletions

View File

@ -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 *)

View File

@ -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.) */
};

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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;
/**

View File

@ -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){

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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) {

View File

@ -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;
}

View File

@ -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);

View File

@ -308,6 +308,7 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps &macros, bool safe,
LongNameHandler::forMeasureUnit(
macros.locale,
macros.unit,
macros.perUnit,
unitWidth,
resolvePluralRules(macros.rules, macros.locale, status),
chain,

View File

@ -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 &currency,
}
}
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 &currency,
} // 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 &currency,
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 &micros,
UErrorCode &status) const {
parent->processQuantity(quantity, micros, status);

View File

@ -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 &micros, 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

View File

@ -251,28 +251,39 @@ void Rounder::setLocaleData(const CurrencyUnit &currency, 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;

View File

@ -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;
};

View File

@ -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

View File

@ -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

View File

@ -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.
*

View File

@ -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;

View File

@ -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",

View File

@ -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));
}
}

View File

@ -42,6 +42,7 @@ public:
void TestBidiPairedBracketType();
void TestEmojiProperties();
void TestDefaultScriptExtensions();
void TestInvalidCodePointFolding();
private:

View File

@ -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);
}

View File

@ -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;

View File

@ -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.

View File

@ -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()));
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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
* &lt;Properties&gt;.
* Appends a string containing properties that differ from the default, but without being surrounded
* by &lt;Properties&gt;.
*/
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);

View File

@ -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);
}

View File

@ -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();
}
}

View File

@ -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));
}
}

View File

@ -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);
}
}
}
}

View File

@ -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;

View File

@ -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.

View File

@ -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.
*/

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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));

View File

@ -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 "&lt;NumberStringBuilder
* [-123.45] [-iii.ff]&gt;"
* For example, if the string is "-12.345", the debug string will be something like
* "&lt;NumberStringBuilder [-123.45] [-iii.ff]&gt;"
*
* @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);

View File

@ -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.

View File

@ -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}.

View File

@ -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);

View File

@ -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;

View File

@ -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 {

View File

@ -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()];
}
}

View File

@ -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;

View File

@ -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();

View File

@ -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.

View File

@ -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)");
}
}
}

View File

@ -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);
}
}

View File

@ -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)");
}
}
}

View File

@ -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.

View File

@ -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:

View File

@ -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);
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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();

View File

@ -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);

View File

@ -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);

View File

@ -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.

View File

@ -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():

View File

@ -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;
}

View File

@ -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().

View File

@ -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
*/

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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);

View File

@ -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,

View File

@ -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));
}
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}
}

View File

@ -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);
}
}