ICU-13177 Merging NumberFormatter to trunk for ICU 60

X-SVN-Rev: 40492
This commit is contained in:
Shane Carr 2017-09-27 23:46:15 +00:00
commit 131f416755
181 changed files with 25480 additions and 8577 deletions

View file

@ -140,7 +140,7 @@
* <tr>
* <td>Number Formatting</td>
* <td>unum.h</td>
* <td>icu::NumberFormat</td>
* <td>icu::number::NumberFormatter (ICU 60+) or icu::NumberFormat (older versions)</td>
* </tr>
* <tr>
* <td>Number Spellout<br/>(Rule Based Number Formatting)</td>

View file

@ -21,6 +21,13 @@
U_NAMESPACE_BEGIN
// Forward declaration:
namespace number {
namespace impl {
class SimpleModifier;
}
}
/**
* Formats simple patterns like "{1} was born in {0}".
* Minimal subset of MessageFormat; fast, simple, minimal dependencies.
@ -286,6 +293,9 @@ private:
UnicodeString &result, const UnicodeString *resultCopy, UBool forbidResultAsValue,
int32_t *offsets, int32_t offsetsLength,
UErrorCode &errorCode);
// Give access to internals to SimpleModifier for number formatting
friend class number::impl::SimpleModifier;
};
U_NAMESPACE_END

View file

@ -101,7 +101,14 @@ sharedbreakiterator.o scientificnumberformatter.o digitgrouping.o \
digitinterval.o digitformatter.o digitaffix.o valueformatter.o \
digitaffixesandpadding.o pluralaffix.o precision.o \
affixpatternparser.o smallintformatter.o decimfmtimpl.o \
visibledigits.o dayperiodrules.o
visibledigits.o dayperiodrules.o \
nounit.o \
number_affixutils.o number_compact.o number_decimalquantity.o \
number_decimfmtprops.o number_fluent.o number_formatimpl.o number_grouping.o \
number_integerwidth.o number_longnames.o number_modifiers.o number_notation.o \
number_padding.o number_patternmodifier.o number_patternstring.o \
number_rounding.o number_scientific.o number_stringbuilder.o
## Header files to install
HEADERS = $(srcdir)/unicode/*.h

View file

@ -16,6 +16,7 @@
#include "unicode/currunit.h"
#include "unicode/ustring.h"
#include "cstring.h"
U_NAMESPACE_BEGIN
@ -33,11 +34,30 @@ CurrencyUnit::CurrencyUnit(ConstChar16Ptr _isoCode, UErrorCode& ec) {
}
}
CurrencyUnit::CurrencyUnit(const CurrencyUnit& other) :
MeasureUnit(other) {
CurrencyUnit::CurrencyUnit(const CurrencyUnit& other) : MeasureUnit(other) {
u_strcpy(isoCode, other.isoCode);
}
CurrencyUnit::CurrencyUnit(const MeasureUnit& other, UErrorCode& ec) : MeasureUnit(other) {
// Make sure this is a currency.
// OK to hard-code the string because we are comparing against another hard-coded string.
if (uprv_strcmp("currency", getType()) != 0) {
ec = U_ILLEGAL_ARGUMENT_ERROR;
isoCode[0] = 0;
} else {
// Get the ISO Code from the subtype field.
u_charsToUChars(getSubtype(), isoCode, 4);
isoCode[3] = 0; // make 100% sure it is NUL-terminated
}
}
CurrencyUnit::CurrencyUnit() : MeasureUnit() {
u_strcpy(isoCode, u"XXX");
char simpleIsoCode[4];
u_UCharsToChars(isoCode, simpleIsoCode, 4);
initCurrency(simpleIsoCode);
}
CurrencyUnit& CurrencyUnit::operator=(const CurrencyUnit& other) {
if (this == &other) {
return *this;

View file

@ -521,7 +521,8 @@ static FixedDecimal &initFixedDecimal(
const VisibleDigits &digits, FixedDecimal &result) {
result.source = 0.0;
result.isNegative = digits.isNegative();
result.isNanOrInfinity = digits.isNaNOrInfinity();
result._isNaN = digits.isNaN();
result._isInfinite = digits.isInfinite();
digits.getFixedDecimal(
result.source, result.intValue, result.decimalDigits,
result.decimalDigitsWithoutTrailingZeros,

View file

@ -352,6 +352,24 @@
<ClCompile Include="nfrs.cpp" />
<ClCompile Include="nfrule.cpp" />
<ClCompile Include="nfsubs.cpp" />
<ClCompile Include="nounit.cpp" />
<ClCompile Include="number_affixutils.cpp" />
<ClCompile Include="number_compact.cpp" />
<ClCompile Include="number_decimalquantity.cpp" />
<ClCompile Include="number_decimfmtprops.cpp" />
<ClCompile Include="number_fluent.cpp" />
<ClCompile Include="number_formatimpl.cpp" />
<ClCompile Include="number_grouping.cpp" />
<ClCompile Include="number_integerwidth.cpp" />
<ClCompile Include="number_longnames.cpp" />
<ClCompile Include="number_modifiers.cpp" />
<ClCompile Include="number_notation.cpp" />
<ClCompile Include="number_padding.cpp" />
<ClCompile Include="number_patternmodifier.cpp" />
<ClCompile Include="number_patternstring.cpp" />
<ClCompile Include="number_rounding.cpp" />
<ClCompile Include="number_scientific.cpp" />
<ClCompile Include="number_stringbuilder.cpp" />
<ClCompile Include="numfmt.cpp" />
<ClCompile Include="numsys.cpp" />
<ClCompile Include="olsontz.cpp" />
@ -1692,6 +1710,28 @@
<ClInclude Include="scriptset.h" />
<ClInclude Include="uspoof_conf.h" />
<ClInclude Include="uspoof_impl.h" />
<ClInclude Include="number_affixutils.h" />
<ClInclude Include="number_compact.h" />
<ClInclude Include="number_decimalquantity.h" />
<ClInclude Include="number_decimfmtprops.h" />
<ClInclude Include="number_formatimpl.h" />
<ClInclude Include="number_longnames.h" />
<ClInclude Include="number_modifiers.h" />
<ClInclude Include="number_patternmodifier.h" />
<ClInclude Include="number_patternstring.h" />
<ClInclude Include="number_roundingutils.h" />
<ClInclude Include="number_scientific.h" />
<ClInclude Include="number_stringbuilder.h" />
<ClInclude Include="number_types.h" />
<ClInclude Include="number_utils.h" />
<CustomBuild Include="unicode\nounit.h">
<Command>copy "%(FullPath)" ..\..\include\unicode </Command>
<Outputs>..\..\include\unicode\%(Filename)%(Extension);%(Outputs)</Outputs>
</CustomBuild>
<CustomBuild Include="unicode\numberformatter.h">
<Command>copy "%(FullPath)" ..\..\include\unicode </Command>
<Outputs>..\..\include\unicode\%(Filename)%(Extension);%(Outputs)</Outputs>
</CustomBuild>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="i18n.rc" />

View file

@ -754,6 +754,55 @@
<ClInclude Include="nfsubs.h">
<Filter>formatting</Filter>
</ClInclude>
<ClInclude Include="number_affixutils.h">
<Filter>formatting</Filter>
</ClInclude>
<ClInclude Include="number_compact.h">
<Filter>formatting</Filter>
</ClInclude>
<ClInclude Include="number_decimalquantity.h">
<Filter>formatting</Filter>
</ClInclude>
<ClInclude Include="number_decimfmtprops.h">
<Filter>formatting</Filter>
</ClInclude>
<ClInclude Include="number_formatimpl.h">
<Filter>formatting</Filter>
</ClInclude>
<ClInclude Include="number_longnames.h">
<Filter>formatting</Filter>
</ClInclude>
<ClInclude Include="number_modifiers.h">
<Filter>formatting</Filter>
</ClInclude>
<ClInclude Include="number_patternmodifier.h">
<Filter>formatting</Filter>
</ClInclude>
<ClInclude Include="">
<Filter>formatting</Filter>
</ClInclude>
<ClInclude Include="number_patternstring.h">
<Filter>formatting</Filter>
</ClInclude>
<ClInclude Include="number_roundingutils.h">
<Filter>formatting</Filter>
</ClInclude>
<ClInclude Include="number_scientific.h">
<Filter>formatting</Filter>
</ClInclude>
<ClInclude Include="number_stringbuilder.h">
<Filter>formatting</Filter>
</ClInclude>
<ClInclude Include="number_types.h">
<Filter>formatting</Filter>
</ClInclude>
<ClInclude Include="number_utils.h">
<Filter>formatting</Filter>
</ClInclude>
<ClInclude Include="olsontz.h">
<Filter>formatting</Filter>
</ClInclude>
@ -1133,6 +1182,12 @@
<CustomBuild Include="unicode\msgfmt.h">
<Filter>formatting</Filter>
</CustomBuild>
<CustomBuild Include="unicode/nounit.h">
<Filter>formatting</Filter>
</CustomBuild>
<CustomBuild Include="/numberformatter.h">
<Filter>formatting</Filter>
</CustomBuild>
<CustomBuild Include="unicode\numfmt.h">
<Filter>formatting</Filter>
</CustomBuild>

View file

@ -33,6 +33,7 @@ UOBJECT_DEFINE_RTTI_IMPLEMENTATION(MeasureUnit)
//
// Start generated code
static const int32_t gOffsets[] = {
0,
2,
@ -49,11 +50,12 @@ static const int32_t gOffsets[] = {
340,
341,
352,
358,
363,
367,
371,
396
355,
361,
366,
370,
374,
399
};
static const int32_t gIndexes[] = {
@ -72,11 +74,12 @@ static const int32_t gIndexes[] = {
79,
80,
91,
97,
102,
106,
110,
135
94,
100,
105,
109,
113,
138
};
// Must be sorted alphabetically.
@ -95,6 +98,7 @@ static const char * const gTypes[] = {
"length",
"light",
"mass",
"none",
"power",
"pressure",
"speed",
@ -456,6 +460,9 @@ static const char * const gSubTypes[] = {
"pound",
"stone",
"ton",
"base",
"percent",
"permille",
"gigawatt",
"horsepower",
"kilowatt",
@ -504,14 +511,14 @@ static const char * const gSubTypes[] = {
// Must be sorted by first value and then second value.
static int32_t unitPerUnitToSingleUnit[][4] = {
{327, 297, 16, 0},
{329, 303, 16, 2},
{331, 297, 16, 3},
{331, 385, 4, 2},
{331, 386, 4, 3},
{346, 383, 3, 1},
{349, 11, 15, 4},
{388, 327, 4, 1}
{327, 297, 17, 0},
{329, 303, 17, 2},
{331, 297, 17, 3},
{331, 388, 4, 2},
{331, 389, 4, 3},
{346, 386, 3, 1},
{349, 11, 16, 4},
{391, 327, 4, 1}
};
MeasureUnit *MeasureUnit::createGForce(UErrorCode &status) {
@ -610,14 +617,6 @@ MeasureUnit *MeasureUnit::createMilePerGallonImperial(UErrorCode &status) {
return MeasureUnit::create(4, 3, status);
}
// MeasureUnit *MeasureUnit::createEast(UErrorCode &status) {...}
// MeasureUnit *MeasureUnit::createNorth(UErrorCode &status) {...}
// MeasureUnit *MeasureUnit::createSouth(UErrorCode &status) {...}
// MeasureUnit *MeasureUnit::createWest(UErrorCode &status) {...}
MeasureUnit *MeasureUnit::createBit(UErrorCode &status) {
return MeasureUnit::create(6, 0, status);
}
@ -887,179 +886,179 @@ MeasureUnit *MeasureUnit::createTon(UErrorCode &status) {
}
MeasureUnit *MeasureUnit::createGigawatt(UErrorCode &status) {
return MeasureUnit::create(14, 0, status);
}
MeasureUnit *MeasureUnit::createHorsepower(UErrorCode &status) {
return MeasureUnit::create(14, 1, status);
}
MeasureUnit *MeasureUnit::createKilowatt(UErrorCode &status) {
return MeasureUnit::create(14, 2, status);
}
MeasureUnit *MeasureUnit::createMegawatt(UErrorCode &status) {
return MeasureUnit::create(14, 3, status);
}
MeasureUnit *MeasureUnit::createMilliwatt(UErrorCode &status) {
return MeasureUnit::create(14, 4, status);
}
MeasureUnit *MeasureUnit::createWatt(UErrorCode &status) {
return MeasureUnit::create(14, 5, status);
}
MeasureUnit *MeasureUnit::createHectopascal(UErrorCode &status) {
return MeasureUnit::create(15, 0, status);
}
MeasureUnit *MeasureUnit::createInchHg(UErrorCode &status) {
MeasureUnit *MeasureUnit::createHorsepower(UErrorCode &status) {
return MeasureUnit::create(15, 1, status);
}
MeasureUnit *MeasureUnit::createMillibar(UErrorCode &status) {
MeasureUnit *MeasureUnit::createKilowatt(UErrorCode &status) {
return MeasureUnit::create(15, 2, status);
}
MeasureUnit *MeasureUnit::createMillimeterOfMercury(UErrorCode &status) {
MeasureUnit *MeasureUnit::createMegawatt(UErrorCode &status) {
return MeasureUnit::create(15, 3, status);
}
MeasureUnit *MeasureUnit::createPoundPerSquareInch(UErrorCode &status) {
MeasureUnit *MeasureUnit::createMilliwatt(UErrorCode &status) {
return MeasureUnit::create(15, 4, status);
}
MeasureUnit *MeasureUnit::createKilometerPerHour(UErrorCode &status) {
MeasureUnit *MeasureUnit::createWatt(UErrorCode &status) {
return MeasureUnit::create(15, 5, status);
}
MeasureUnit *MeasureUnit::createHectopascal(UErrorCode &status) {
return MeasureUnit::create(16, 0, status);
}
MeasureUnit *MeasureUnit::createKnot(UErrorCode &status) {
MeasureUnit *MeasureUnit::createInchHg(UErrorCode &status) {
return MeasureUnit::create(16, 1, status);
}
MeasureUnit *MeasureUnit::createMeterPerSecond(UErrorCode &status) {
MeasureUnit *MeasureUnit::createMillibar(UErrorCode &status) {
return MeasureUnit::create(16, 2, status);
}
MeasureUnit *MeasureUnit::createMilePerHour(UErrorCode &status) {
MeasureUnit *MeasureUnit::createMillimeterOfMercury(UErrorCode &status) {
return MeasureUnit::create(16, 3, status);
}
MeasureUnit *MeasureUnit::createCelsius(UErrorCode &status) {
MeasureUnit *MeasureUnit::createPoundPerSquareInch(UErrorCode &status) {
return MeasureUnit::create(16, 4, status);
}
MeasureUnit *MeasureUnit::createKilometerPerHour(UErrorCode &status) {
return MeasureUnit::create(17, 0, status);
}
MeasureUnit *MeasureUnit::createFahrenheit(UErrorCode &status) {
MeasureUnit *MeasureUnit::createKnot(UErrorCode &status) {
return MeasureUnit::create(17, 1, status);
}
MeasureUnit *MeasureUnit::createGenericTemperature(UErrorCode &status) {
MeasureUnit *MeasureUnit::createMeterPerSecond(UErrorCode &status) {
return MeasureUnit::create(17, 2, status);
}
MeasureUnit *MeasureUnit::createKelvin(UErrorCode &status) {
MeasureUnit *MeasureUnit::createMilePerHour(UErrorCode &status) {
return MeasureUnit::create(17, 3, status);
}
MeasureUnit *MeasureUnit::createAcreFoot(UErrorCode &status) {
MeasureUnit *MeasureUnit::createCelsius(UErrorCode &status) {
return MeasureUnit::create(18, 0, status);
}
MeasureUnit *MeasureUnit::createBushel(UErrorCode &status) {
MeasureUnit *MeasureUnit::createFahrenheit(UErrorCode &status) {
return MeasureUnit::create(18, 1, status);
}
MeasureUnit *MeasureUnit::createCentiliter(UErrorCode &status) {
MeasureUnit *MeasureUnit::createGenericTemperature(UErrorCode &status) {
return MeasureUnit::create(18, 2, status);
}
MeasureUnit *MeasureUnit::createCubicCentimeter(UErrorCode &status) {
MeasureUnit *MeasureUnit::createKelvin(UErrorCode &status) {
return MeasureUnit::create(18, 3, status);
}
MeasureUnit *MeasureUnit::createAcreFoot(UErrorCode &status) {
return MeasureUnit::create(19, 0, status);
}
MeasureUnit *MeasureUnit::createBushel(UErrorCode &status) {
return MeasureUnit::create(19, 1, status);
}
MeasureUnit *MeasureUnit::createCentiliter(UErrorCode &status) {
return MeasureUnit::create(19, 2, status);
}
MeasureUnit *MeasureUnit::createCubicCentimeter(UErrorCode &status) {
return MeasureUnit::create(19, 3, status);
}
MeasureUnit *MeasureUnit::createCubicFoot(UErrorCode &status) {
return MeasureUnit::create(18, 4, status);
return MeasureUnit::create(19, 4, status);
}
MeasureUnit *MeasureUnit::createCubicInch(UErrorCode &status) {
return MeasureUnit::create(18, 5, status);
return MeasureUnit::create(19, 5, status);
}
MeasureUnit *MeasureUnit::createCubicKilometer(UErrorCode &status) {
return MeasureUnit::create(18, 6, status);
return MeasureUnit::create(19, 6, status);
}
MeasureUnit *MeasureUnit::createCubicMeter(UErrorCode &status) {
return MeasureUnit::create(18, 7, status);
return MeasureUnit::create(19, 7, status);
}
MeasureUnit *MeasureUnit::createCubicMile(UErrorCode &status) {
return MeasureUnit::create(18, 8, status);
return MeasureUnit::create(19, 8, status);
}
MeasureUnit *MeasureUnit::createCubicYard(UErrorCode &status) {
return MeasureUnit::create(18, 9, status);
return MeasureUnit::create(19, 9, status);
}
MeasureUnit *MeasureUnit::createCup(UErrorCode &status) {
return MeasureUnit::create(18, 10, status);
return MeasureUnit::create(19, 10, status);
}
MeasureUnit *MeasureUnit::createCupMetric(UErrorCode &status) {
return MeasureUnit::create(18, 11, status);
return MeasureUnit::create(19, 11, status);
}
MeasureUnit *MeasureUnit::createDeciliter(UErrorCode &status) {
return MeasureUnit::create(18, 12, status);
return MeasureUnit::create(19, 12, status);
}
MeasureUnit *MeasureUnit::createFluidOunce(UErrorCode &status) {
return MeasureUnit::create(18, 13, status);
return MeasureUnit::create(19, 13, status);
}
MeasureUnit *MeasureUnit::createGallon(UErrorCode &status) {
return MeasureUnit::create(18, 14, status);
return MeasureUnit::create(19, 14, status);
}
MeasureUnit *MeasureUnit::createGallonImperial(UErrorCode &status) {
return MeasureUnit::create(18, 15, status);
return MeasureUnit::create(19, 15, status);
}
MeasureUnit *MeasureUnit::createHectoliter(UErrorCode &status) {
return MeasureUnit::create(18, 16, status);
return MeasureUnit::create(19, 16, status);
}
MeasureUnit *MeasureUnit::createLiter(UErrorCode &status) {
return MeasureUnit::create(18, 17, status);
return MeasureUnit::create(19, 17, status);
}
MeasureUnit *MeasureUnit::createMegaliter(UErrorCode &status) {
return MeasureUnit::create(18, 18, status);
return MeasureUnit::create(19, 18, status);
}
MeasureUnit *MeasureUnit::createMilliliter(UErrorCode &status) {
return MeasureUnit::create(18, 19, status);
return MeasureUnit::create(19, 19, status);
}
MeasureUnit *MeasureUnit::createPint(UErrorCode &status) {
return MeasureUnit::create(18, 20, status);
return MeasureUnit::create(19, 20, status);
}
MeasureUnit *MeasureUnit::createPintMetric(UErrorCode &status) {
return MeasureUnit::create(18, 21, status);
return MeasureUnit::create(19, 21, status);
}
MeasureUnit *MeasureUnit::createQuart(UErrorCode &status) {
return MeasureUnit::create(18, 22, status);
return MeasureUnit::create(19, 22, status);
}
MeasureUnit *MeasureUnit::createTablespoon(UErrorCode &status) {
return MeasureUnit::create(18, 23, status);
return MeasureUnit::create(19, 23, status);
}
MeasureUnit *MeasureUnit::createTeaspoon(UErrorCode &status) {
return MeasureUnit::create(18, 24, status);
return MeasureUnit::create(19, 24, status);
}
// End generated code
@ -1080,7 +1079,12 @@ static int32_t binarySearch(
}
return -1;
}
MeasureUnit::MeasureUnit() {
fCurrency[0] = 0;
initNoUnit("base");
}
MeasureUnit::MeasureUnit(const MeasureUnit &other)
: fTypeId(other.fTypeId), fSubTypeId(other.fSubTypeId) {
uprv_strcpy(fCurrency, other.fCurrency);
@ -1269,6 +1273,15 @@ void MeasureUnit::initCurrency(const char *isoCurrency) {
}
}
void MeasureUnit::initNoUnit(const char *subtype) {
int32_t result = binarySearch(gTypes, 0, UPRV_LENGTHOF(gTypes), "none");
U_ASSERT(result != -1);
fTypeId = result;
result = binarySearch(gSubTypes, gOffsets[fTypeId], gOffsets[fTypeId + 1], subtype);
U_ASSERT(result != -1);
fSubTypeId = result - gOffsets[fTypeId];
}
void MeasureUnit::setTo(int32_t typeId, int32_t subTypeId) {
fTypeId = typeId;
fSubTypeId = subTypeId;

View file

@ -0,0 +1,42 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#include "unicode/nounit.h"
#include "uassert.h"
#if !UCONFIG_NO_FORMATTING
U_NAMESPACE_BEGIN
UOBJECT_DEFINE_RTTI_IMPLEMENTATION(NoUnit)
NoUnit U_EXPORT2 NoUnit::base() {
return NoUnit("base");
}
NoUnit U_EXPORT2 NoUnit::percent() {
return NoUnit("percent");
}
NoUnit U_EXPORT2 NoUnit::permille() {
return NoUnit("permille");
}
NoUnit::NoUnit(const char* subtype) {
initNoUnit(subtype);
}
NoUnit::NoUnit(const NoUnit& other) : MeasureUnit(other) {
}
UObject* NoUnit::clone() const {
return new NoUnit(*this);
}
NoUnit::~NoUnit() {
}
U_NAMESPACE_END
#endif

View file

@ -0,0 +1,400 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#include "number_affixutils.h"
#include "unicode/utf16.h"
using namespace icu::number::impl;
using namespace icu;
int32_t AffixUtils::estimateLength(const CharSequence &patternString, UErrorCode &status) {
AffixPatternState state = STATE_BASE;
int32_t offset = 0;
int32_t length = 0;
for (; offset < patternString.length();) {
UChar32 cp = patternString.codePointAt(offset);
switch (state) {
case STATE_BASE:
if (cp == '\'') {
// First quote
state = STATE_FIRST_QUOTE;
} else {
// Unquoted symbol
length++;
}
break;
case STATE_FIRST_QUOTE:
if (cp == '\'') {
// Repeated quote
length++;
state = STATE_BASE;
} else {
// Quoted code point
length++;
state = STATE_INSIDE_QUOTE;
}
break;
case STATE_INSIDE_QUOTE:
if (cp == '\'') {
// End of quoted sequence
state = STATE_AFTER_QUOTE;
} else {
// Quoted code point
length++;
}
break;
case STATE_AFTER_QUOTE:
if (cp == '\'') {
// Double quote inside of quoted sequence
length++;
state = STATE_INSIDE_QUOTE;
} else {
// Unquoted symbol
length++;
}
break;
default:
U_ASSERT(false);
}
offset += U16_LENGTH(cp);
}
switch (state) {
case STATE_FIRST_QUOTE:
case STATE_INSIDE_QUOTE:
status = U_ILLEGAL_ARGUMENT_ERROR;
default:
break;
}
return length;
}
UnicodeString AffixUtils::escape(const CharSequence &input) {
AffixPatternState state = STATE_BASE;
int32_t offset = 0;
UnicodeString output;
for (; offset < input.length();) {
int32_t cp = input.codePointAt(offset);
switch (cp) {
case '\'':
output.append(u"''", -1);
break;
case '-':
case '+':
case '%':
case u'':
case u'¤':
if (state == STATE_BASE) {
output.append('\'');
output.append(cp);
state = STATE_INSIDE_QUOTE;
} else {
output.append(cp);
}
break;
default:
if (state == STATE_INSIDE_QUOTE) {
output.append('\'');
output.append(cp);
state = STATE_BASE;
} else {
output.append(cp);
}
break;
}
offset += U16_LENGTH(cp);
}
if (state == STATE_INSIDE_QUOTE) {
output.append('\'');
}
return output;
}
Field AffixUtils::getFieldForType(AffixPatternType type) {
switch (type) {
case TYPE_MINUS_SIGN:
return Field::UNUM_SIGN_FIELD;
case TYPE_PLUS_SIGN:
return Field::UNUM_SIGN_FIELD;
case TYPE_PERCENT:
return Field::UNUM_PERCENT_FIELD;
case TYPE_PERMILLE:
return Field::UNUM_PERMILL_FIELD;
case TYPE_CURRENCY_SINGLE:
return Field::UNUM_CURRENCY_FIELD;
case TYPE_CURRENCY_DOUBLE:
return Field::UNUM_CURRENCY_FIELD;
case TYPE_CURRENCY_TRIPLE:
return Field::UNUM_CURRENCY_FIELD;
case TYPE_CURRENCY_QUAD:
return Field::UNUM_CURRENCY_FIELD;
case TYPE_CURRENCY_QUINT:
return Field::UNUM_CURRENCY_FIELD;
case TYPE_CURRENCY_OVERFLOW:
return Field::UNUM_CURRENCY_FIELD;
default:
U_ASSERT(false);
return Field::UNUM_FIELD_COUNT; // suppress "control reaches end of non-void function"
}
}
int32_t
AffixUtils::unescape(const CharSequence &affixPattern, NumberStringBuilder &output, int32_t position,
const SymbolProvider &provider, UErrorCode &status) {
int32_t length = 0;
AffixTag tag;
while (hasNext(tag, affixPattern)) {
tag = nextToken(tag, affixPattern, status);
if (U_FAILURE(status)) { return length; }
if (tag.type == TYPE_CURRENCY_OVERFLOW) {
// Don't go to the provider for this special case
length += output.insertCodePoint(position + length, 0xFFFD, UNUM_CURRENCY_FIELD, status);
} else if (tag.type < 0) {
length += output.insert(
position + length, provider.getSymbol(tag.type), getFieldForType(tag.type), status);
} else {
length += output.insertCodePoint(position + length, tag.codePoint, UNUM_FIELD_COUNT, status);
}
}
return length;
}
int32_t AffixUtils::unescapedCodePointCount(const CharSequence &affixPattern,
const SymbolProvider &provider, UErrorCode &status) {
int32_t length = 0;
AffixTag tag;
while (hasNext(tag, affixPattern)) {
tag = nextToken(tag, affixPattern, status);
if (U_FAILURE(status)) { return length; }
if (tag.type == TYPE_CURRENCY_OVERFLOW) {
length += 1;
} else if (tag.type < 0) {
length += provider.getSymbol(tag.type).length();
} else {
length += U16_LENGTH(tag.codePoint);
}
}
return length;
}
bool
AffixUtils::containsType(const CharSequence &affixPattern, AffixPatternType type, UErrorCode &status) {
if (affixPattern.length() == 0) {
return false;
}
AffixTag tag;
while (hasNext(tag, affixPattern)) {
tag = nextToken(tag, affixPattern, status);
if (U_FAILURE(status)) { return false; }
if (tag.type == type) {
return true;
}
}
return false;
}
bool AffixUtils::hasCurrencySymbols(const CharSequence &affixPattern, UErrorCode &status) {
if (affixPattern.length() == 0) {
return false;
}
AffixTag tag;
while (hasNext(tag, affixPattern)) {
tag = nextToken(tag, affixPattern, status);
if (U_FAILURE(status)) { return false; }
if (tag.type < 0 && getFieldForType(tag.type) == UNUM_CURRENCY_FIELD) {
return true;
}
}
return false;
}
UnicodeString AffixUtils::replaceType(const CharSequence &affixPattern, AffixPatternType type,
char16_t replacementChar, UErrorCode &status) {
UnicodeString output = affixPattern.toUnicodeString();
if (affixPattern.length() == 0) {
return output;
};
AffixTag tag;
while (hasNext(tag, affixPattern)) {
tag = nextToken(tag, affixPattern, status);
if (U_FAILURE(status)) { return output; }
if (tag.type == type) {
output.replace(tag.offset - 1, 1, replacementChar);
}
}
return output;
}
AffixTag AffixUtils::nextToken(AffixTag tag, const CharSequence &patternString, UErrorCode &status) {
int32_t offset = tag.offset;
int32_t state = tag.state;
for (; offset < patternString.length();) {
UChar32 cp = patternString.codePointAt(offset);
int32_t count = U16_LENGTH(cp);
switch (state) {
case STATE_BASE:
switch (cp) {
case '\'':
state = STATE_FIRST_QUOTE;
offset += count;
// continue to the next code point
break;
case '-':
return makeTag(offset + count, TYPE_MINUS_SIGN, STATE_BASE, 0);
case '+':
return makeTag(offset + count, TYPE_PLUS_SIGN, STATE_BASE, 0);
case u'%':
return makeTag(offset + count, TYPE_PERCENT, STATE_BASE, 0);
case u'':
return makeTag(offset + count, TYPE_PERMILLE, STATE_BASE, 0);
case u'¤':
state = STATE_FIRST_CURR;
offset += count;
// continue to the next code point
break;
default:
return makeTag(offset + count, TYPE_CODEPOINT, STATE_BASE, cp);
}
break;
case STATE_FIRST_QUOTE:
if (cp == '\'') {
return makeTag(offset + count, TYPE_CODEPOINT, STATE_BASE, cp);
} else {
return makeTag(offset + count, TYPE_CODEPOINT, STATE_INSIDE_QUOTE, cp);
}
case STATE_INSIDE_QUOTE:
if (cp == '\'') {
state = STATE_AFTER_QUOTE;
offset += count;
// continue to the next code point
break;
} else {
return makeTag(offset + count, TYPE_CODEPOINT, STATE_INSIDE_QUOTE, cp);
}
case STATE_AFTER_QUOTE:
if (cp == '\'') {
return makeTag(offset + count, TYPE_CODEPOINT, STATE_INSIDE_QUOTE, cp);
} else {
state = STATE_BASE;
// re-evaluate this code point
break;
}
case STATE_FIRST_CURR:
if (cp == u'¤') {
state = STATE_SECOND_CURR;
offset += count;
// continue to the next code point
break;
} else {
return makeTag(offset, TYPE_CURRENCY_SINGLE, STATE_BASE, 0);
}
case STATE_SECOND_CURR:
if (cp == u'¤') {
state = STATE_THIRD_CURR;
offset += count;
// continue to the next code point
break;
} else {
return makeTag(offset, TYPE_CURRENCY_DOUBLE, STATE_BASE, 0);
}
case STATE_THIRD_CURR:
if (cp == u'¤') {
state = STATE_FOURTH_CURR;
offset += count;
// continue to the next code point
break;
} else {
return makeTag(offset, TYPE_CURRENCY_TRIPLE, STATE_BASE, 0);
}
case STATE_FOURTH_CURR:
if (cp == u'¤') {
state = STATE_FIFTH_CURR;
offset += count;
// continue to the next code point
break;
} else {
return makeTag(offset, TYPE_CURRENCY_QUAD, STATE_BASE, 0);
}
case STATE_FIFTH_CURR:
if (cp == u'¤') {
state = STATE_OVERFLOW_CURR;
offset += count;
// continue to the next code point
break;
} else {
return makeTag(offset, TYPE_CURRENCY_QUINT, STATE_BASE, 0);
}
case STATE_OVERFLOW_CURR:
if (cp == u'¤') {
offset += count;
// continue to the next code point and loop back to this state
break;
} else {
return makeTag(offset, TYPE_CURRENCY_OVERFLOW, STATE_BASE, 0);
}
default:
U_ASSERT(false);
}
}
// End of string
switch (state) {
case STATE_BASE:
// No more tokens in string.
return {-1};
case STATE_FIRST_QUOTE:
case STATE_INSIDE_QUOTE:
// For consistent behavior with the JDK and ICU 58, set an error here.
status = U_ILLEGAL_ARGUMENT_ERROR;
return {-1};
case STATE_AFTER_QUOTE:
// No more tokens in string.
return {-1};
case STATE_FIRST_CURR:
return makeTag(offset, TYPE_CURRENCY_SINGLE, STATE_BASE, 0);
case STATE_SECOND_CURR:
return makeTag(offset, TYPE_CURRENCY_DOUBLE, STATE_BASE, 0);
case STATE_THIRD_CURR:
return makeTag(offset, TYPE_CURRENCY_TRIPLE, STATE_BASE, 0);
case STATE_FOURTH_CURR:
return makeTag(offset, TYPE_CURRENCY_QUAD, STATE_BASE, 0);
case STATE_FIFTH_CURR:
return makeTag(offset, TYPE_CURRENCY_QUINT, STATE_BASE, 0);
case STATE_OVERFLOW_CURR:
return makeTag(offset, TYPE_CURRENCY_OVERFLOW, STATE_BASE, 0);
default:
U_ASSERT(false);
return {-1}; // suppress "control reaches end of non-void function"
}
}
bool AffixUtils::hasNext(const AffixTag &tag, const CharSequence &string) {
// First check for the {-1} and default initializer syntax.
if (tag.offset < 0) {
return false;
} else if (tag.offset == 0) {
return string.length() > 0;
}
// The rest of the fields are safe to use now.
// Special case: the last character in string is an end quote.
if (tag.state == STATE_INSIDE_QUOTE && tag.offset == string.length() - 1 &&
string.charAt(tag.offset) == '\'') {
return false;
} else if (tag.state != STATE_BASE) {
return true;
} else {
return tag.offset < string.length();
}
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,219 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#ifndef __NUMBER_AFFIXUTILS_H__
#define __NUMBER_AFFIXUTILS_H__
#include <cstdint>
#include "number_types.h"
#include "unicode/stringpiece.h"
#include "unicode/unistr.h"
#include "number_stringbuilder.h"
U_NAMESPACE_BEGIN namespace number {
namespace impl {
enum AffixPatternState {
STATE_BASE = 0,
STATE_FIRST_QUOTE = 1,
STATE_INSIDE_QUOTE = 2,
STATE_AFTER_QUOTE = 3,
STATE_FIRST_CURR = 4,
STATE_SECOND_CURR = 5,
STATE_THIRD_CURR = 6,
STATE_FOURTH_CURR = 7,
STATE_FIFTH_CURR = 8,
STATE_OVERFLOW_CURR = 9
};
// enum AffixPatternType defined in internals.h
struct AffixTag {
int32_t offset;
UChar32 codePoint;
AffixPatternState state;
AffixPatternType type;
AffixTag() : offset(0), state(STATE_BASE) {}
AffixTag(int32_t offset) : offset(offset) {}
AffixTag(int32_t offset, UChar32 codePoint, AffixPatternState state, AffixPatternType type)
: offset(offset), codePoint(codePoint), state(state), type(type)
{}
};
class SymbolProvider {
public:
// TODO: Could this be more efficient if it returned by reference?
virtual UnicodeString getSymbol(AffixPatternType type) const = 0;
};
/**
* Performs manipulations on affix patterns: the prefix and suffix strings associated with a decimal
* format pattern. For example:
*
* <table>
* <tr><th>Affix Pattern</th><th>Example Unescaped (Formatted) String</th></tr>
* <tr><td>abc</td><td>abc</td></tr>
* <tr><td>ab-</td><td>ab</td></tr>
* <tr><td>ab'-'</td><td>ab-</td></tr>
* <tr><td>ab''</td><td>ab'</td></tr>
* </table>
*
* To manually iterate over tokens in a literal string, use the following pattern, which is designed
* to be efficient.
*
* <pre>
* long tag = 0L;
* while (AffixPatternUtils.hasNext(tag, patternString)) {
* tag = AffixPatternUtils.nextToken(tag, patternString);
* int typeOrCp = AffixPatternUtils.getTypeOrCp(tag);
* switch (typeOrCp) {
* case AffixPatternUtils.TYPE_MINUS_SIGN:
* // Current token is a minus sign.
* break;
* case AffixPatternUtils.TYPE_PLUS_SIGN:
* // Current token is a plus sign.
* break;
* case AffixPatternUtils.TYPE_PERCENT:
* // Current token is a percent sign.
* break;
* // ... other types ...
* default:
* // Current token is an arbitrary code point.
* // The variable typeOrCp is the code point.
* break;
* }
* }
* </pre>
*/
class U_I18N_API AffixUtils {
public:
/**
* Estimates the number of code points present in an unescaped version of the affix pattern string
* (one that would be returned by {@link #unescape}), assuming that all interpolated symbols
* consume one code point and that currencies consume as many code points as their symbol width.
* Used for computing padding width.
*
* @param patternString The original string whose width will be estimated.
* @return The length of the unescaped string.
*/
static int32_t estimateLength(const CharSequence &patternString, UErrorCode &status);
/**
* Takes a string and escapes (quotes) characters that have special meaning in the affix pattern
* syntax. This function does not reverse-lookup symbols.
*
* <p>Example input: "-$x"; example output: "'-'$x"
*
* @param input The string to be escaped.
* @return The resulting UnicodeString.
*/
static UnicodeString escape(const CharSequence &input);
static Field getFieldForType(AffixPatternType type);
/**
* Executes the unescape state machine. Replaces the unquoted characters "-", "+", "%", "", and
* "¤" with the corresponding symbols provided by the {@link SymbolProvider}, and inserts the
* result into the NumberStringBuilder at the requested location.
*
* <p>Example input: "'-'¤x"; example output: "-$x"
*
* @param affixPattern The original string to be unescaped.
* @param output The NumberStringBuilder to mutate with the result.
* @param position The index into the NumberStringBuilder to insert the the string.
* @param provider An object to generate locale symbols.
*/
static int32_t
unescape(const CharSequence &affixPattern, NumberStringBuilder &output, int32_t position,
const SymbolProvider &provider, UErrorCode &status);
/**
* Sames as {@link #unescape}, but only calculates the code point count. More efficient than {@link #unescape}
* if you only need the length but not the string itself.
*
* @param affixPattern The original string to be unescaped.
* @param provider An object to generate locale symbols.
* @return The same return value as if you called {@link #unescape}.
*/
static int32_t unescapedCodePointCount(const CharSequence &affixPattern,
const SymbolProvider &provider, UErrorCode &status);
/**
* Checks whether the given affix pattern contains at least one token of the given type, which is
* one of the constants "TYPE_" in {@link AffixPatternUtils}.
*
* @param affixPattern The affix pattern to check.
* @param type The token type.
* @return true if the affix pattern contains the given token type; false otherwise.
*/
static bool
containsType(const CharSequence &affixPattern, AffixPatternType type, UErrorCode &status);
/**
* Checks whether the specified affix pattern has any unquoted currency symbols ("¤").
*
* @param affixPattern The string to check for currency symbols.
* @return true if the literal has at least one unquoted currency symbol; false otherwise.
*/
static bool hasCurrencySymbols(const CharSequence &affixPattern, UErrorCode &status);
/**
* Replaces all occurrences of tokens with the given type with the given replacement char.
*
* @param affixPattern The source affix pattern (does not get modified).
* @param type The token type.
* @param replacementChar The char to substitute in place of chars of the given token type.
* @return A string containing the new affix pattern.
*/
static UnicodeString
replaceType(const CharSequence &affixPattern, AffixPatternType type, char16_t replacementChar,
UErrorCode &status);
/**
* Returns the next token from the affix pattern.
*
* @param tag A bitmask used for keeping track of state from token to token. The initial value
* should be 0L.
* @param patternString The affix pattern.
* @return The bitmask tag to pass to the next call of this method to retrieve the following token
* (never negative), or -1 if there were no more tokens in the affix pattern.
* @see #hasNext
*/
static AffixTag nextToken(AffixTag tag, const CharSequence &patternString, UErrorCode &status);
/**
* Returns whether the affix pattern string has any more tokens to be retrieved from a call to
* {@link #nextToken}.
*
* @param tag The bitmask tag of the previous token, as returned by {@link #nextToken}.
* @param string The affix pattern.
* @return true if there are more tokens to consume; false otherwise.
*/
static bool hasNext(const AffixTag &tag, const CharSequence &string);
private:
/**
* Encodes the given values into a tag struct.
* The order of the arguments is consistent with Java, but the order of the stored
* fields is not necessarily the same.
*/
static inline AffixTag
makeTag(int32_t offset, AffixPatternType type, AffixPatternState state, UChar32 cp) {
return {offset, cp, state, type};
}
};
} // namespace impl
} // namespace number
U_NAMESPACE_END
#endif //__NUMBER_AFFIXUTILS_H__
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,320 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#include "resource.h"
#include "number_compact.h"
#include "unicode/ustring.h"
#include "unicode/ures.h"
#include "cstring.h"
#include "charstr.h"
#include "uresimp.h"
using namespace icu::number::impl;
using namespace icu;
namespace {
// A dummy object used when a "0" compact decimal entry is encountered. This is necessary
// in order to prevent falling back to root. Object equality ("==") is intended.
const UChar *USE_FALLBACK = u"<USE FALLBACK>";
/** Produces a string like "NumberElements/latn/patternsShort/decimalFormat". */
void getResourceBundleKey(const char *nsName, CompactStyle compactStyle, CompactType compactType,
CharString &sb, UErrorCode &status) {
sb.clear();
sb.append("NumberElements/", status);
sb.append(nsName, status);
sb.append(compactStyle == CompactStyle::UNUM_SHORT ? "/patternsShort" : "/patternsLong", status);
sb.append(compactType == CompactType::TYPE_DECIMAL ? "/decimalFormat" : "/currencyFormat", status);
}
int32_t getIndex(int32_t magnitude, StandardPlural::Form plural) {
return magnitude * StandardPlural::COUNT + plural;
}
int32_t countZeros(const UChar *patternString, int32_t patternLength) {
// NOTE: This strategy for computing the number of zeros is a hack for efficiency.
// It could break if there are any 0s that aren't part of the main pattern.
int32_t numZeros = 0;
for (int32_t i = 0; i < patternLength; i++) {
if (patternString[i] == u'0') {
numZeros++;
} else if (numZeros > 0) {
break; // zeros should always be contiguous
}
}
return numZeros;
}
} // namespace
// NOTE: patterns and multipliers both get zero-initialized.
CompactData::CompactData() : patterns(), multipliers(), largestMagnitude(0), isEmpty(TRUE) {
}
void CompactData::populate(const Locale &locale, const char *nsName, CompactStyle compactStyle,
CompactType compactType, UErrorCode &status) {
CompactDataSink sink(*this);
LocalUResourceBundlePointer rb(ures_open(nullptr, locale.getName(), &status));
bool nsIsLatn = strcmp(nsName, "latn") == 0;
bool compactIsShort = compactStyle == CompactStyle::UNUM_SHORT;
// Fall back to latn numbering system and/or short compact style.
CharString resourceKey;
getResourceBundleKey(nsName, compactStyle, compactType, resourceKey, status);
UErrorCode localStatus = U_ZERO_ERROR;
ures_getAllItemsWithFallback(rb.getAlias(), resourceKey.data(), sink, localStatus);
if (isEmpty && !nsIsLatn) {
getResourceBundleKey("latn", compactStyle, compactType, resourceKey, status);
localStatus = U_ZERO_ERROR;
ures_getAllItemsWithFallback(rb.getAlias(), resourceKey.data(), sink, localStatus);
}
if (isEmpty && !compactIsShort) {
getResourceBundleKey(nsName, CompactStyle::UNUM_SHORT, compactType, resourceKey, status);
localStatus = U_ZERO_ERROR;
ures_getAllItemsWithFallback(rb.getAlias(), resourceKey.data(), sink, localStatus);
}
if (isEmpty && !nsIsLatn && !compactIsShort) {
getResourceBundleKey("latn", CompactStyle::UNUM_SHORT, compactType, resourceKey, status);
localStatus = U_ZERO_ERROR;
ures_getAllItemsWithFallback(rb.getAlias(), resourceKey.data(), sink, localStatus);
}
// The last fallback should be guaranteed to return data.
if (isEmpty) {
status = U_INTERNAL_PROGRAM_ERROR;
}
}
int32_t CompactData::getMultiplier(int32_t magnitude) const {
if (magnitude < 0) {
return 0;
}
if (magnitude > largestMagnitude) {
magnitude = largestMagnitude;
}
return multipliers[magnitude];
}
const UChar *CompactData::getPattern(int32_t magnitude, StandardPlural::Form plural) const {
if (magnitude < 0) {
return nullptr;
}
if (magnitude > largestMagnitude) {
magnitude = largestMagnitude;
}
const UChar *patternString = patterns[getIndex(magnitude, plural)];
if (patternString == nullptr && plural != StandardPlural::OTHER) {
// Fall back to "other" plural variant
patternString = patterns[getIndex(magnitude, StandardPlural::OTHER)];
}
if (patternString == USE_FALLBACK) { // == is intended
// Return null if USE_FALLBACK is present
patternString = nullptr;
}
return patternString;
}
void CompactData::getUniquePatterns(UVector &output, UErrorCode &status) const {
U_ASSERT(output.isEmpty());
// NOTE: In C++, this is done more manually with a UVector.
// In Java, we can take advantage of JDK HashSet.
for (auto pattern : patterns) {
if (pattern == nullptr || pattern == USE_FALLBACK) {
continue;
}
// Insert pattern into the UVector if the UVector does not already contain the pattern.
// Search the UVector from the end since identical patterns are likely to be adjacent.
for (int32_t i = output.size() - 1; i >= 0; i--) {
if (u_strcmp(pattern, static_cast<const UChar *>(output[i])) == 0) {
goto continue_outer;
}
}
// The string was not found; add it to the UVector.
// ANDY: This requires a const_cast. Why?
output.addElement(const_cast<UChar *>(pattern), status);
continue_outer:
continue;
}
}
void CompactData::CompactDataSink::put(const char *key, ResourceValue &value, UBool /*noFallback*/,
UErrorCode &status) {
// traverse into the table of powers of ten
ResourceTable powersOfTenTable = value.getTable(status);
if (U_FAILURE(status)) { return; }
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.
auto magnitude = static_cast<int8_t> (strlen(key) - 1);
int8_t multiplier = data.multipliers[magnitude];
U_ASSERT(magnitude < COMPACT_MAX_DIGITS);
// Iterate over the plural variants ("one", "other", etc)
ResourceTable pluralVariantsTable = value.getTable(status);
if (U_FAILURE(status)) { return; }
for (int i4 = 0; pluralVariantsTable.getKeyAndValue(i4, key, value); ++i4) {
// Skip this magnitude/plural if we already have it from a child locale.
// Note: This also skips USE_FALLBACK entries.
StandardPlural::Form plural = StandardPlural::fromString(key, status);
if (U_FAILURE(status)) { return; }
if (data.patterns[getIndex(magnitude, plural)] != nullptr) {
continue;
}
// The value "0" means that we need to use the default pattern and not fall back
// to parent locales. Example locale where this is relevant: 'it'.
int32_t patternLength;
const UChar *patternString = value.getString(patternLength, status);
if (U_FAILURE(status)) { return; }
if (u_strcmp(patternString, u"0") == 0) {
patternString = USE_FALLBACK;
patternLength = 0;
}
// Save the pattern string. We will parse it lazily.
data.patterns[getIndex(magnitude, plural)] = patternString;
// If necessary, compute the multiplier: the difference between the magnitude
// and the number of zeros in the pattern.
if (multiplier == 0) {
int32_t numZeros = countZeros(patternString, patternLength);
if (numZeros > 0) { // numZeros==0 in certain cases, like Somali "Kun"
multiplier = static_cast<int8_t> (numZeros - magnitude - 1);
}
}
}
// Save the multiplier.
if (data.multipliers[magnitude] == 0) {
data.multipliers[magnitude] = multiplier;
if (magnitude > data.largestMagnitude) {
data.largestMagnitude = magnitude;
}
data.isEmpty = false;
} else {
U_ASSERT(data.multipliers[magnitude] == multiplier);
}
}
}
///////////////////////////////////////////////////////////
/// END OF CompactData.java; BEGIN CompactNotation.java ///
///////////////////////////////////////////////////////////
CompactHandler::CompactHandler(CompactStyle compactStyle, const Locale &locale, const char *nsName,
CompactType compactType, const PluralRules *rules,
MutablePatternModifier *buildReference, const MicroPropsGenerator *parent,
UErrorCode &status)
: rules(rules), parent(parent) {
data.populate(locale, nsName, compactStyle, compactType, status);
if (U_FAILURE(status)) { return; }
if (buildReference != nullptr) {
// Safe code path
precomputeAllModifiers(*buildReference, status);
safe = TRUE;
} else {
// Unsafe code path
safe = FALSE;
}
}
CompactHandler::~CompactHandler() {
for (int32_t i = 0; i < precomputedModsLength; i++) {
delete precomputedMods[i].mod;
}
}
void CompactHandler::precomputeAllModifiers(MutablePatternModifier &buildReference, UErrorCode &status) {
// Initial capacity of 12 for 0K, 00K, 000K, ...M, ...B, and ...T
UVector allPatterns(12, status);
if (U_FAILURE(status)) { return; }
data.getUniquePatterns(allPatterns, status);
if (U_FAILURE(status)) { return; }
// C++ only: ensure that precomputedMods has room.
precomputedModsLength = allPatterns.size();
if (precomputedMods.getCapacity() < precomputedModsLength) {
precomputedMods.resize(allPatterns.size(), status);
if (U_FAILURE(status)) { return; }
}
for (int32_t i = 0; i < precomputedModsLength; i++) {
auto patternString = static_cast<const UChar *>(allPatterns[i]);
UnicodeString hello(patternString);
CompactModInfo &info = precomputedMods[i];
ParsedPatternInfo patternInfo;
PatternParser::parseToPatternInfo(UnicodeString(patternString), patternInfo, status);
if (U_FAILURE(status)) { return; }
buildReference.setPatternInfo(&patternInfo);
info.mod = buildReference.createImmutable(status);
if (U_FAILURE(status)) { return; }
info.numDigits = patternInfo.positive.integerTotal;
info.patternString = patternString;
}
}
void CompactHandler::processQuantity(DecimalQuantity &quantity, MicroProps &micros,
UErrorCode &status) const {
parent->processQuantity(quantity, micros, status);
if (U_FAILURE(status)) { return; }
// Treat zero as if it had magnitude 0
int magnitude;
if (quantity.isZero()) {
magnitude = 0;
micros.rounding.apply(quantity, status);
} else {
// TODO: Revisit chooseMultiplierAndApply
int multiplier = micros.rounding.chooseMultiplierAndApply(quantity, data, status);
magnitude = quantity.isZero() ? 0 : quantity.getMagnitude();
magnitude -= multiplier;
}
StandardPlural::Form plural = quantity.getStandardPlural(rules);
const UChar *patternString = data.getPattern(magnitude, plural);
int numDigits = -1;
if (patternString == nullptr) {
// Use the default (non-compact) modifier.
// No need to take any action.
} else if (safe) {
// Safe code path.
// Java uses a hash set here for O(1) lookup. C++ uses a linear search.
// TODO: Benchmark this and maybe change to a binary search or hash table.
int32_t i = 0;
for (; i < precomputedModsLength; i++) {
const CompactModInfo &info = precomputedMods[i];
if (u_strcmp(patternString, info.patternString) == 0) {
info.mod->applyToMicros(micros, quantity);
numDigits = info.numDigits;
break;
}
}
// It should be guaranteed that we found the entry.
U_ASSERT(i < precomputedModsLength);
} else {
// Unsafe code path.
// Overwrite the PatternInfo in the existing modMiddle.
// C++ Note: Use unsafePatternInfo for proper lifecycle.
ParsedPatternInfo &patternInfo = const_cast<CompactHandler *>(this)->unsafePatternInfo;
PatternParser::parseToPatternInfo(UnicodeString(patternString), patternInfo, status);
dynamic_cast<MutablePatternModifier *>(const_cast<Modifier *>(micros.modMiddle))->setPatternInfo(
&patternInfo);
numDigits = patternInfo.positive.integerTotal;
}
// FIXME: Deal with numDigits == 0 (Awaiting a test case)
// We already performed rounding. Do not perform it again.
micros.rounding = Rounder::constructPassThrough();
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,89 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#ifndef __NUMBER_COMPACT_H__
#define __NUMBER_COMPACT_H__
#include "standardplural.h"
#include "number_types.h"
#include "unicode/unum.h"
#include "uvector.h"
#include "resource.h"
#include "number_patternmodifier.h"
U_NAMESPACE_BEGIN namespace number {
namespace impl {
static const int32_t COMPACT_MAX_DIGITS = 15;
class CompactData : public MultiplierProducer {
public:
CompactData();
void populate(const Locale &locale, const char *nsName, CompactStyle compactStyle,
CompactType compactType, UErrorCode &status);
int32_t getMultiplier(int32_t magnitude) const override;
const UChar *getPattern(int32_t magnitude, StandardPlural::Form plural) const;
void getUniquePatterns(UVector &output, UErrorCode &status) const;
private:
const UChar *patterns[(COMPACT_MAX_DIGITS + 1) * StandardPlural::COUNT];
int8_t multipliers[COMPACT_MAX_DIGITS + 1];
int8_t largestMagnitude;
UBool isEmpty;
class CompactDataSink : public ResourceSink {
public:
explicit CompactDataSink(CompactData &data) : data(data) {}
void put(const char *key, ResourceValue &value, UBool /*noFallback*/, UErrorCode &status) override;
private:
CompactData &data;
};
};
struct CompactModInfo {
const ImmutablePatternModifier *mod;
const UChar* patternString;
int32_t numDigits;
};
class CompactHandler : public MicroPropsGenerator, public UMemory {
public:
CompactHandler(CompactStyle compactStyle, const Locale &locale, const char *nsName,
CompactType compactType, const PluralRules *rules,
MutablePatternModifier *buildReference, const MicroPropsGenerator *parent,
UErrorCode &status);
~CompactHandler() override;
void
processQuantity(DecimalQuantity &quantity, MicroProps &micros, UErrorCode &status) const override;
private:
const PluralRules *rules;
const MicroPropsGenerator *parent;
// Initial capacity of 12 for 0K, 00K, 000K, ...M, ...B, and ...T
MaybeStackArray<CompactModInfo, 12> precomputedMods;
int32_t precomputedModsLength = 0;
CompactData data;
ParsedPatternInfo unsafePatternInfo;
UBool safe;
/** Used by the safe code path */
void precomputeAllModifiers(MutablePatternModifier &buildReference, UErrorCode &status);
};
} // namespace impl
} // namespace number
U_NAMESPACE_END
#endif //__NUMBER_COMPACT_H__
#endif /* #if !UCONFIG_NO_FORMATTING */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,436 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#ifndef __NUMBER_DECIMALQUANTITY_H__
#define __NUMBER_DECIMALQUANTITY_H__
#include <cstdint>
#include "unicode/umachine.h"
#include "decNumber.h"
#include "standardplural.h"
#include "plurrule_impl.h"
#include "number_types.h"
U_NAMESPACE_BEGIN namespace number {
namespace impl {
/**
* An class for 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>Represents numbers and digit display properties using Binary Coded Decimal (BCD).
*
* <p>Java has multiple implementations for testing, but C++ has only one implementation.
*/
class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory {
public:
/** Copy constructor. */
DecimalQuantity(const DecimalQuantity &other);
DecimalQuantity();
~DecimalQuantity();
/**
* Sets this instance to be equal to another instance.
*
* @param other The instance to copy from.
*/
DecimalQuantity &operator=(const DecimalQuantity &other);
/**
* 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.
*/
void setIntegerLength(int32_t minInt, int32_t 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.
*/
void setFractionLength(int32_t minFrac, int32_t 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 roundingIncrement The increment to which to round.
* @param mathContext The {@link RoundingMode} to use if rounding is necessary.
*/
void roundToIncrement(double roundingIncrement, RoundingMode roundingMode,
int32_t minMaxFrac, UErrorCode& status);
/**
* 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 RoundingMode} to use if rounding is necessary.
*/
void roundToMagnitude(int32_t magnitude, RoundingMode roundingMode, UErrorCode& status);
/**
* 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.
*/
void roundToInfinity();
/**
* Multiply the internal value.
*
* @param multiplicand The value by which to multiply.
*/
void multiplyBy(int32_t 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.
*/
void adjustMagnitude(int32_t delta);
/**
* @return The power of ten corresponding to the most significant nonzero digit.
* The number must not be zero.
*/
int32_t getMagnitude() const;
/** @return Whether the value represented by this {@link DecimalQuantity} is zero. */
bool isZero() const;
/** @return Whether the value represented by this {@link DecimalQuantity} is less than zero. */
bool isNegative() const;
/** @return Whether the value represented by this {@link DecimalQuantity} is infinite. */
bool isInfinite() const override;
/** @return Whether the value represented by this {@link DecimalQuantity} is not a number. */
bool isNaN() const override;
int64_t toLong() const;
int64_t toFractionLong(bool includeTrailingZeros) const;
/** @return The value contained in this {@link DecimalQuantity} approximated as a double. */
double toDouble() const;
DecimalQuantity &setToInt(int32_t n);
DecimalQuantity &setToLong(int64_t n);
DecimalQuantity &setToDouble(double n);
/** decNumber is similar to BigDecimal in Java. */
DecimalQuantity &setToDecNumber(StringPiece n);
/**
* Appends a digit, optionally with one or more leading zeros, to the end of the value represented
* by this DecimalQuantity.
*
* <p>The primary use of this method is to construct numbers during a parsing loop. It allows
* parsing to take advantage of the digit list infrastructure primarily designed for formatting.
*
* @param value The digit to append.
* @param leadingZeros The number of zeros to append before the digit. For example, if the value
* in this instance starts as 12.3, and you append a 4 with 1 leading zero, the value becomes
* 12.304.
* @param appendAsInteger If true, increase the magnitude of existing digits to make room for the
* new digit. If false, append to the end like a fraction digit. If true, there must not be
* any fraction digits already in the number.
* @internal
* @deprecated This API is ICU internal only.
*/
void appendDigit(int8_t value, int32_t leadingZeros, bool appendAsInteger);
/**
* 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.
*/
StandardPlural::Form getStandardPlural(const PluralRules *rules) const;
double getPluralOperand(PluralOperand operand) const override;
/**
* 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.
*/
int8_t getDigit(int32_t magnitude) const;
/**
* 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.
*/
int32_t getUpperDisplayMagnitude() const;
/**
* 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.
*/
int32_t getLowerDisplayMagnitude() const;
int32_t fractionCount() const;
int32_t fractionCountWithoutTrailingZeros() const;
void clear();
/** This method is for internal testing only. */
uint64_t getPositionFingerprint() const;
// /**
// * 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.
// */
// void populateUFieldPosition(FieldPosition fp);
/**
* 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.
*/
const char16_t* checkHealth() const;
UnicodeString toString() const;
/* Returns the string in exponential notation. */
UnicodeString toNumberString() const;
/* Returns the string without exponential notation. Slightly slower than toNumberString(). */
UnicodeString toPlainString() const;
/** Visible for testing */
inline bool isUsingBytes() { return usingBytes; }
/** Visible for testing */
inline bool isExplicitExactDouble() { return explicitExactDouble; };
private:
/**
* The power of ten corresponding to the least significant digit in the BCD. For example, if this
* object represents the number "3.14", the BCD will be "0x314" and the scale will be -2.
*
* <p>Note that in {@link java.math.BigDecimal}, the scale is defined differently: the number of
* digits after the decimal place, which is the negative of our definition of scale.
*/
int32_t scale;
/**
* The number of digits in the BCD. For example, "1007" has BCD "0x1007" and precision 4. The
* maximum precision is 16 since a long can hold only 16 digits.
*
* <p>This value must be re-calculated whenever the value in bcd changes by using {@link
* #computePrecisionAndCompact()}.
*/
int32_t precision;
/**
* A bitmask of properties relating to the number represented by this object.
*
* @see #NEGATIVE_FLAG
* @see #INFINITY_FLAG
* @see #NAN_FLAG
*/
int8_t flags;
// The following three fields relate to the double-to-ascii fast path algorithm.
// When a double is given to DecimalQuantityBCD, it is converted to using a fast algorithm. The
// fast algorithm guarantees correctness to only the first ~12 digits of the double. The process
// of rounding the number ensures that the converted digits are correct, falling back to a slow-
// path algorithm if required. Therefore, if a DecimalQuantity is constructed from a double, it
// is *required* that roundToMagnitude(), roundToIncrement(), or roundToInfinity() is called. If
// you don't round, assertions will fail in certain other methods if you try calling them.
/**
* Whether the value in the BCD comes from the double fast path without having been rounded to
* ensure correctness
*/
UBool isApproximate;
/**
* The original number provided by the user and which is represented in BCD. Used when we need to
* re-compute the BCD for an exact double representation.
*/
double origDouble;
/**
* The change in magnitude relative to the original double. Used when we need to re-compute the
* BCD for an exact double representation.
*/
int32_t origDelta;
// Four positions: left optional '(', left required '[', right required ']', right optional ')'.
// These four positions determine which digits are displayed in the output string. They do NOT
// affect rounding. These positions are internal-only and can be specified only by the public
// endpoints like setFractionLength, setIntegerLength, and setSignificantDigits, among others.
//
// * Digits between lReqPos and rReqPos are in the "required zone" and are always displayed.
// * Digits between lOptPos and rOptPos but outside the required zone are in the "optional zone"
// and are displayed unless they are trailing off the left or right edge of the number and
// have a numerical value of zero. In order to be "trailing", the digits need to be beyond
// the decimal point in their respective directions.
// * Digits outside of the "optional zone" are never displayed.
//
// See the table below for illustrative examples.
//
// +---------+---------+---------+---------+------------+------------------------+--------------+
// | lOptPos | lReqPos | rReqPos | rOptPos | number | positions | en-US string |
// +---------+---------+---------+---------+------------+------------------------+--------------+
// | 5 | 2 | -1 | -5 | 1234.567 | ( 12[34.5]67 ) | 1,234.567 |
// | 3 | 2 | -1 | -5 | 1234.567 | 1(2[34.5]67 ) | 234.567 |
// | 3 | 2 | -1 | -2 | 1234.567 | 1(2[34.5]6)7 | 234.56 |
// | 6 | 4 | 2 | -5 | 123456789. | 123(45[67]89. ) | 456,789. |
// | 6 | 4 | 2 | 1 | 123456789. | 123(45[67]8)9. | 456,780. |
// | -1 | -1 | -3 | -4 | 0.123456 | 0.1([23]4)56 | .0234 |
// | 6 | 4 | -2 | -2 | 12.3 | ( [ 12.3 ]) | 0012.30 |
// +---------+---------+---------+---------+------------+------------------------+--------------+
//
int32_t lOptPos = INT32_MAX;
int32_t lReqPos = 0;
int32_t rReqPos = 0;
int32_t rOptPos = INT32_MIN;
/**
* 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.
*/
union {
struct {
int8_t *ptr;
int32_t len;
} bcdBytes;
uint64_t bcdLong;
} fBCD;
bool usingBytes = false;
/**
* Whether this {@link DecimalQuantity} has been explicitly converted to an exact double. true if
* backed by a double that was explicitly converted via convertToAccurateDouble; false otherwise.
* Used for testing.
*/
bool explicitExactDouble = false;
/**
* Returns a single digit from the BCD list. No internal state is changed by calling this method.
*
* @param position The position of the digit to pop, counted in BCD units from the least
* significant digit. If outside the range supported by the implementation, zero is returned.
* @return The digit at the specified location.
*/
int8_t getDigitPos(int32_t position) const;
/**
* Sets the digit in the BCD list. This method only sets the digit; it is the caller's
* responsibility to call {@link #compact} after setting the digit.
*
* @param position The position of the digit to pop, counted in BCD units from the least
* significant digit. If outside the range supported by the implementation, an AssertionError
* is thrown.
* @param value The digit to set at the specified location.
*/
void setDigitPos(int32_t position, int8_t value);
/**
* Adds zeros to the end of the BCD list. This will result in an invalid BCD representation; it is
* the caller's responsibility to do further manipulation and then call {@link #compact}.
*
* @param numDigits The number of zeros to add.
*/
void shiftLeft(int32_t numDigits);
void shiftRight(int32_t numDigits);
/**
* Sets the internal representation to zero. Clears any values stored in scale, precision,
* hasDouble, origDouble, origDelta, and BCD data.
*/
void setBcdToZero();
/**
* Sets the internal BCD state to represent the value in the given int. The int is guaranteed to
* be either positive. The internal state is guaranteed to be empty when this method is called.
*
* @param n The value to consume.
*/
void readIntToBcd(int32_t n);
/**
* Sets the internal BCD state to represent the value in the given long. The long is guaranteed to
* be either positive. The internal state is guaranteed to be empty when this method is called.
*
* @param n The value to consume.
*/
void readLongToBcd(int64_t n);
void readDecNumberToBcd(decNumber *dn);
void copyBcdFrom(const DecimalQuantity &other);
/**
* Removes trailing zeros from the BCD (adjusting the scale as required) and then computes the
* precision. The precision is the number of digits in the number up through the greatest nonzero
* digit.
*
* <p>This method must always be called when bcd changes in order for assumptions to be correct in
* methods like {@link #fractionCount()}.
*/
void compact();
void _setToInt(int32_t n);
void _setToLong(int64_t n);
void _setToDoubleFast(double n);
void _setToDecNumber(decNumber *n);
void convertToAccurateDouble();
double toDoubleFromOriginal() const;
/** Ensure that a byte array of at least 40 digits is allocated. */
void ensureCapacity();
void ensureCapacity(int32_t capacity);
/** Switches the internal storage mechanism between the 64-bit long and the byte array. */
void switchStorage();
};
} // namespace impl
} // namespace number
U_NAMESPACE_END
#endif //__NUMBER_DECIMALQUANTITY_H__
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,98 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#include "number_decimfmtprops.h"
using namespace icu::number::impl;
DecimalFormatProperties::DecimalFormatProperties() {
clear();
}
void DecimalFormatProperties::clear() {
compactStyle.nullify();
currency.nullify();
currencyPluralInfo.fPtr.adoptInstead(nullptr);
currencyUsage.nullify();
decimalPatternMatchRequired = false;
decimalSeparatorAlwaysShown = false;
exponentSignAlwaysShown = false;
formatWidth = -1;
groupingSize = -1;
magnitudeMultiplier = 0;
maximumFractionDigits = -1;
maximumIntegerDigits = -1;
maximumSignificantDigits = -1;
minimumExponentDigits = -1;
minimumFractionDigits = -1;
minimumGroupingDigits = -1;
minimumIntegerDigits = -1;
minimumSignificantDigits = -1;
multiplier = 0;
negativePrefix.setToBogus();
negativePrefixPattern.setToBogus();
negativeSuffix.setToBogus();
negativeSuffixPattern.setToBogus();
padPosition.nullify();
padString.setToBogus();
parseCaseSensitive = false;
parseIntegerOnly = false;
parseLenient = false;
parseNoExponent = false;
parseToBigDecimal = false;
positivePrefix.setToBogus();
positivePrefixPattern.setToBogus();
positiveSuffix.setToBogus();
positiveSuffixPattern.setToBogus();
roundingIncrement = 0.0;
roundingMode.nullify();
secondaryGroupingSize = -1;
signAlwaysShown = false;
}
bool DecimalFormatProperties::operator==(const DecimalFormatProperties &other) const {
bool eq = true;
eq = eq && compactStyle == other.compactStyle;
eq = eq && currency == other.currency;
eq = eq && currencyPluralInfo.fPtr.getAlias() == other.currencyPluralInfo.fPtr.getAlias();
eq = eq && currencyUsage == other.currencyUsage;
eq = eq && decimalPatternMatchRequired == other.decimalPatternMatchRequired;
eq = eq && decimalSeparatorAlwaysShown == other.decimalSeparatorAlwaysShown;
eq = eq && exponentSignAlwaysShown == other.exponentSignAlwaysShown;
eq = eq && formatWidth == other.formatWidth;
eq = eq && groupingSize == other.groupingSize;
eq = eq && magnitudeMultiplier == other.magnitudeMultiplier;
eq = eq && maximumFractionDigits == other.maximumFractionDigits;
eq = eq && maximumIntegerDigits == other.maximumIntegerDigits;
eq = eq && maximumSignificantDigits == other.maximumSignificantDigits;
eq = eq && minimumExponentDigits == other.minimumExponentDigits;
eq = eq && minimumFractionDigits == other.minimumFractionDigits;
eq = eq && minimumGroupingDigits == other.minimumGroupingDigits;
eq = eq && minimumIntegerDigits == other.minimumIntegerDigits;
eq = eq && minimumSignificantDigits == other.minimumSignificantDigits;
eq = eq && multiplier == other.multiplier;
eq = eq && negativePrefix == other.negativePrefix;
eq = eq && negativePrefixPattern == other.negativePrefixPattern;
eq = eq && negativeSuffix == other.negativeSuffix;
eq = eq && negativeSuffixPattern == other.negativeSuffixPattern;
eq = eq && padPosition == other.padPosition;
eq = eq && padString == other.padString;
eq = eq && parseCaseSensitive == other.parseCaseSensitive;
eq = eq && parseIntegerOnly == other.parseIntegerOnly;
eq = eq && parseLenient == other.parseLenient;
eq = eq && parseNoExponent == other.parseNoExponent;
eq = eq && parseToBigDecimal == other.parseToBigDecimal;
eq = eq && positivePrefix == other.positivePrefix;
eq = eq && positivePrefixPattern == other.positivePrefixPattern;
eq = eq && positiveSuffix == other.positiveSuffix;
eq = eq && positiveSuffixPattern == other.positiveSuffixPattern;
eq = eq && roundingIncrement == other.roundingIncrement;
eq = eq && roundingMode == other.roundingMode;
eq = eq && secondaryGroupingSize == other.secondaryGroupingSize;
eq = eq && signAlwaysShown == other.signAlwaysShown;
return eq;
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,92 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#ifndef __NUMBER_DECIMFMTPROPS_H__
#define __NUMBER_DECIMFMTPROPS_H__
#include "unicode/unistr.h"
#include <cstdint>
#include "unicode/plurrule.h"
#include "unicode/currpinf.h"
#include "unicode/unum.h"
#include "number_types.h"
U_NAMESPACE_BEGIN
namespace number {
namespace impl {
// TODO: Figure out a nicer way to deal with CurrencyPluralInfo.
struct CurrencyPluralInfoWrapper {
LocalPointer<CurrencyPluralInfo> fPtr;
CurrencyPluralInfoWrapper() {}
CurrencyPluralInfoWrapper(const CurrencyPluralInfoWrapper& other) {
if (!other.fPtr.isNull()) {
fPtr.adoptInstead(new CurrencyPluralInfo(*other.fPtr));
}
}
};
struct U_I18N_API DecimalFormatProperties {
public:
NullableValue<UNumberCompactStyle> compactStyle;
NullableValue<CurrencyUnit> currency;
CurrencyPluralInfoWrapper currencyPluralInfo;
NullableValue<UCurrencyUsage> currencyUsage;
bool decimalPatternMatchRequired;
bool decimalSeparatorAlwaysShown;
bool exponentSignAlwaysShown;
int32_t formatWidth;
int32_t groupingSize;
int32_t magnitudeMultiplier;
int32_t maximumFractionDigits;
int32_t maximumIntegerDigits;
int32_t maximumSignificantDigits;
int32_t minimumExponentDigits;
int32_t minimumFractionDigits;
int32_t minimumGroupingDigits;
int32_t minimumIntegerDigits;
int32_t minimumSignificantDigits;
int32_t multiplier;
UnicodeString negativePrefix;
UnicodeString negativePrefixPattern;
UnicodeString negativeSuffix;
UnicodeString negativeSuffixPattern;
NullableValue<PadPosition> padPosition;
UnicodeString padString;
bool parseCaseSensitive;
bool parseIntegerOnly;
bool parseLenient;
bool parseNoExponent;
bool parseToBigDecimal;
//PluralRules pluralRules;
UnicodeString positivePrefix;
UnicodeString positivePrefixPattern;
UnicodeString positiveSuffix;
UnicodeString positiveSuffixPattern;
double roundingIncrement;
NullableValue<RoundingMode> roundingMode;
int32_t secondaryGroupingSize;
bool signAlwaysShown;
DecimalFormatProperties();
//DecimalFormatProperties(const DecimalFormatProperties &other) = default;
DecimalFormatProperties &operator=(const DecimalFormatProperties &other) = default;
bool operator==(const DecimalFormatProperties &other) const;
void clear();
};
} // namespace impl
} // namespace number
U_NAMESPACE_END
#endif //__NUMBER_DECIMFMTPROPS_H__
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,322 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#include "uassert.h"
#include "unicode/numberformatter.h"
#include "number_decimalquantity.h"
#include "number_formatimpl.h"
using namespace icu;
using namespace icu::number;
using namespace icu::number::impl;
template<typename Derived>
Derived NumberFormatterSettings<Derived>::notation(const Notation &notation) const {
Derived copy(*this);
// NOTE: Slicing is OK.
copy.fMacros.notation = notation;
return copy;
}
template<typename Derived>
Derived NumberFormatterSettings<Derived>::unit(const icu::MeasureUnit &unit) const {
Derived copy(*this);
// NOTE: Slicing occurs here. However, CurrencyUnit can be restored from MeasureUnit.
// TimeUnit may be affected, but TimeUnit is not as relevant to number formatting.
copy.fMacros.unit = unit;
return copy;
}
template<typename Derived>
Derived NumberFormatterSettings<Derived>::adoptUnit(const icu::MeasureUnit *unit) const {
Derived copy(*this);
// Just copy the unit into the MacroProps by value, and delete it since we have ownership.
// NOTE: Slicing occurs here. However, CurrencyUnit can be restored from MeasureUnit.
// TimeUnit may be affected, but TimeUnit is not as relevant to number formatting.
if (unit != nullptr) {
copy.fMacros.unit = *unit;
delete unit;
}
return copy;
}
template<typename Derived>
Derived NumberFormatterSettings<Derived>::rounding(const Rounder &rounder) const {
Derived copy(*this);
// NOTE: Slicing is OK.
copy.fMacros.rounder = rounder;
return copy;
}
template<typename Derived>
Derived NumberFormatterSettings<Derived>::grouping(const Grouper &grouper) const {
Derived copy(*this);
copy.fMacros.grouper = grouper;
return copy;
}
template<typename Derived>
Derived NumberFormatterSettings<Derived>::integerWidth(const IntegerWidth &style) const {
Derived copy(*this);
copy.fMacros.integerWidth = style;
return copy;
}
template<typename Derived>
Derived NumberFormatterSettings<Derived>::symbols(const DecimalFormatSymbols &symbols) const {
Derived copy(*this);
copy.fMacros.symbols.setTo(symbols);
return copy;
}
template<typename Derived>
Derived NumberFormatterSettings<Derived>::adoptSymbols(const NumberingSystem *ns) const {
Derived copy(*this);
copy.fMacros.symbols.setTo(ns);
return copy;
}
template<typename Derived>
Derived NumberFormatterSettings<Derived>::unitWidth(const UNumberUnitWidth &width) const {
Derived copy(*this);
copy.fMacros.unitWidth = width;
return copy;
}
template<typename Derived>
Derived NumberFormatterSettings<Derived>::sign(const UNumberSignDisplay &style) const {
Derived copy(*this);
copy.fMacros.sign = style;
return copy;
}
template<typename Derived>
Derived NumberFormatterSettings<Derived>::decimal(const UNumberDecimalSeparatorDisplay &style) const {
Derived copy(*this);
copy.fMacros.decimal = style;
return copy;
}
template<typename Derived>
Derived NumberFormatterSettings<Derived>::padding(const Padder &padder) const {
Derived copy(*this);
copy.fMacros.padder = padder;
return copy;
}
template<typename Derived>
Derived NumberFormatterSettings<Derived>::threshold(uint32_t threshold) const {
Derived copy(*this);
copy.fMacros.threshold = threshold;
return copy;
}
// Declare all classes that implement NumberFormatterSettings
// See https://stackoverflow.com/a/495056/1407170
template
class icu::number::NumberFormatterSettings<icu::number::UnlocalizedNumberFormatter>;
template
class icu::number::NumberFormatterSettings<icu::number::LocalizedNumberFormatter>;
UnlocalizedNumberFormatter NumberFormatter::with() {
UnlocalizedNumberFormatter result;
return result;
}
LocalizedNumberFormatter NumberFormatter::withLocale(const Locale &locale) {
return with().locale(locale);
}
// Make the child class constructor that takes the parent class call the parent class's copy constructor
UnlocalizedNumberFormatter::UnlocalizedNumberFormatter(
const NumberFormatterSettings <UnlocalizedNumberFormatter> &other)
: NumberFormatterSettings<UnlocalizedNumberFormatter>(other) {
}
// Make the child class constructor that takes the parent class call the parent class's copy constructor
// For LocalizedNumberFormatter, also copy over the extra fields
LocalizedNumberFormatter::LocalizedNumberFormatter(
const NumberFormatterSettings <LocalizedNumberFormatter> &other)
: NumberFormatterSettings<LocalizedNumberFormatter>(other) {
// No additional copies required
}
LocalizedNumberFormatter::LocalizedNumberFormatter(const MacroProps &macros, const Locale &locale) {
fMacros = macros;
fMacros.locale = locale;
}
LocalizedNumberFormatter UnlocalizedNumberFormatter::locale(const Locale &locale) const {
return LocalizedNumberFormatter(fMacros, locale);
}
SymbolsWrapper::SymbolsWrapper(const SymbolsWrapper &other) {
doCopyFrom(other);
}
SymbolsWrapper &SymbolsWrapper::operator=(const SymbolsWrapper &other) {
if (this == &other) {
return *this;
}
doCleanup();
doCopyFrom(other);
return *this;
}
SymbolsWrapper::~SymbolsWrapper() {
doCleanup();
}
void SymbolsWrapper::setTo(const DecimalFormatSymbols &dfs) {
doCleanup();
fType = SYMPTR_DFS;
fPtr.dfs = new DecimalFormatSymbols(dfs);
}
void SymbolsWrapper::setTo(const NumberingSystem *ns) {
doCleanup();
fType = SYMPTR_NS;
fPtr.ns = ns;
}
void SymbolsWrapper::doCopyFrom(const SymbolsWrapper &other) {
fType = other.fType;
switch (fType) {
case SYMPTR_NONE:
// No action necessary
break;
case SYMPTR_DFS:
// Memory allocation failures are exposed in copyErrorTo()
if (other.fPtr.dfs != nullptr) {
fPtr.dfs = new DecimalFormatSymbols(*other.fPtr.dfs);
} else {
fPtr.dfs = nullptr;
}
break;
case SYMPTR_NS:
// Memory allocation failures are exposed in copyErrorTo()
if (other.fPtr.ns != nullptr) {
fPtr.ns = new NumberingSystem(*other.fPtr.ns);
} else {
fPtr.ns = nullptr;
}
break;
}
}
void SymbolsWrapper::doCleanup() {
switch (fType) {
case SYMPTR_NONE:
// No action necessary
break;
case SYMPTR_DFS:
delete fPtr.dfs;
break;
case SYMPTR_NS:
delete fPtr.ns;
break;
}
}
bool SymbolsWrapper::isDecimalFormatSymbols() const {
return fType == SYMPTR_DFS;
}
bool SymbolsWrapper::isNumberingSystem() const {
return fType == SYMPTR_NS;
}
const DecimalFormatSymbols* SymbolsWrapper::getDecimalFormatSymbols() const {
U_ASSERT(fType == SYMPTR_DFS);
return fPtr.dfs;
}
const NumberingSystem* SymbolsWrapper::getNumberingSystem() const {
U_ASSERT(fType == SYMPTR_NS);
return fPtr.ns;
}
LocalizedNumberFormatter::~LocalizedNumberFormatter() {
delete fCompiled.load();
}
FormattedNumber LocalizedNumberFormatter::formatInt(int64_t value, UErrorCode &status) const {
if (U_FAILURE(status)) { return FormattedNumber(); }
auto results = new NumberFormatterResults();
if (results == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return FormattedNumber();
}
results->quantity.setToLong(value);
return formatImpl(results, status);
}
FormattedNumber LocalizedNumberFormatter::formatDouble(double value, UErrorCode &status) const {
if (U_FAILURE(status)) { return FormattedNumber(); }
auto results = new NumberFormatterResults();
if (results == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return FormattedNumber();
}
results->quantity.setToDouble(value);
return formatImpl(results, status);
}
FormattedNumber LocalizedNumberFormatter::formatDecimal(StringPiece value, UErrorCode &status) const {
if (U_FAILURE(status)) { return FormattedNumber(); }
auto results = new NumberFormatterResults();
if (results == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return FormattedNumber();
}
results->quantity.setToDecNumber(value);
return formatImpl(results, status);
}
FormattedNumber
LocalizedNumberFormatter::formatImpl(impl::NumberFormatterResults *results, UErrorCode &status) const {
uint32_t currentCount = fCallCount.load();
if (currentCount <= fMacros.threshold && fMacros.threshold > 0) {
currentCount = const_cast<LocalizedNumberFormatter *>(this)->fCallCount.fetch_add(1) + 1;
}
const NumberFormatterImpl *compiled;
if (currentCount == fMacros.threshold && fMacros.threshold > 0) {
compiled = NumberFormatterImpl::fromMacros(fMacros, status);
U_ASSERT(fCompiled.load() == nullptr);
const_cast<LocalizedNumberFormatter *>(this)->fCompiled.store(compiled);
compiled->apply(results->quantity, results->string, status);
} else if ((compiled = fCompiled.load()) != nullptr) {
compiled->apply(results->quantity, results->string, status);
} else {
NumberFormatterImpl::applyStatic(fMacros, results->quantity, results->string, status);
}
return FormattedNumber(results);
}
UnicodeString FormattedNumber::toString() const {
return fResults->string.toUnicodeString();
}
Appendable &FormattedNumber::appendTo(Appendable &appendable) {
appendable.appendString(fResults->string.chars(), fResults->string.length());
return appendable;
}
void FormattedNumber::populateFieldPosition(FieldPosition &fieldPosition, UErrorCode &status) {
fResults->string.populateFieldPosition(fieldPosition, 0, status);
}
void
FormattedNumber::populateFieldPositionIterator(FieldPositionIterator &iterator, UErrorCode &status) {
fResults->string.populateFieldPositionIterator(iterator, status);
}
FormattedNumber::~FormattedNumber() {
delete fResults;
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,460 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#include "cstring.h"
#include "unicode/ures.h"
#include "uresimp.h"
#include "charstr.h"
#include "number_formatimpl.h"
#include "unicode/numfmt.h"
#include "number_patternstring.h"
#include "number_utils.h"
#include "unicode/numberformatter.h"
#include "unicode/dcfmtsym.h"
#include "number_scientific.h"
#include "number_compact.h"
using namespace icu;
using namespace icu::number::impl;
namespace {
// NOTE: In Java, the method to get a pattern from the resource bundle exists in NumberFormat.
// In C++, we have to implement that logic here.
// TODO: Make Java and C++ consistent?
enum CldrPatternStyle {
CLDR_PATTERN_STYLE_DECIMAL,
CLDR_PATTERN_STYLE_CURRENCY,
CLDR_PATTERN_STYLE_ACCOUNTING,
CLDR_PATTERN_STYLE_PERCENT
// TODO: Consider scientific format.
};
const char16_t *
doGetPattern(UResourceBundle *res, const char *nsName, const char *patternKey, UErrorCode &publicStatus,
UErrorCode &localStatus) {
// Construct the path into the resource bundle
CharString key;
key.append("NumberElements/", publicStatus);
key.append(nsName, publicStatus);
key.append("/patterns/", publicStatus);
key.append(patternKey, publicStatus);
if (U_FAILURE(publicStatus)) {
return u"";
}
return ures_getStringByKeyWithFallback(res, key.data(), nullptr, &localStatus);
}
const char16_t *getPatternForStyle(const Locale &locale, const char *nsName, CldrPatternStyle style,
UErrorCode &status) {
const char *patternKey;
switch (style) {
case CLDR_PATTERN_STYLE_DECIMAL:
patternKey = "decimalFormat";
break;
case CLDR_PATTERN_STYLE_CURRENCY:
patternKey = "currencyFormat";
break;
case CLDR_PATTERN_STYLE_ACCOUNTING:
patternKey = "accountingFormat";
break;
case CLDR_PATTERN_STYLE_PERCENT:
default:
patternKey = "percentFormat";
break;
}
LocalUResourceBundlePointer res(ures_open(nullptr, locale.getName(), &status));
// Attempt to get the pattern with the native numbering system.
UErrorCode localStatus = U_ZERO_ERROR;
const char16_t *pattern;
pattern = doGetPattern(res.getAlias(), nsName, patternKey, status, localStatus);
if (U_FAILURE(status)) { return u""; }
// Fall back to latn if native numbering system does not have the right pattern
if (U_FAILURE(localStatus) && uprv_strcmp("latn", nsName) != 0) {
localStatus = U_ZERO_ERROR;
pattern = doGetPattern(res.getAlias(), "latn", patternKey, status, localStatus);
if (U_FAILURE(status)) { return u""; }
}
return pattern;
}
inline bool unitIsCurrency(const MeasureUnit &unit) {
return uprv_strcmp("currency", unit.getType()) == 0;
}
inline bool unitIsNoUnit(const MeasureUnit &unit) {
return uprv_strcmp("none", unit.getType()) == 0;
}
inline bool unitIsPercent(const MeasureUnit &unit) {
return uprv_strcmp("percent", unit.getSubtype()) == 0;
}
inline bool unitIsPermille(const MeasureUnit &unit) {
return uprv_strcmp("permille", unit.getSubtype()) == 0;
}
} // namespace
NumberFormatterImpl *NumberFormatterImpl::fromMacros(const MacroProps &macros, UErrorCode &status) {
return new NumberFormatterImpl(macros, true, status);
}
void NumberFormatterImpl::applyStatic(const MacroProps &macros, DecimalQuantity &inValue,
NumberStringBuilder &outString, UErrorCode &status) {
NumberFormatterImpl impl(macros, false, status);
impl.applyUnsafe(inValue, outString, status);
}
// NOTE: C++ SPECIFIC DIFFERENCE FROM JAVA:
// The "safe" apply method uses a new MicroProps. In the MicroPropsGenerator, fMicros is copied into the new instance.
// The "unsafe" method simply re-uses fMicros, eliminating the extra copy operation.
// See MicroProps::processQuantity() for details.
void NumberFormatterImpl::apply(DecimalQuantity &inValue, NumberStringBuilder &outString,
UErrorCode &status) const {
if (U_FAILURE(status)) { return; }
MicroProps micros;
fMicroPropsGenerator->processQuantity(inValue, micros, status);
if (U_FAILURE(status)) { return; }
microsToString(micros, inValue, outString, status);
}
void NumberFormatterImpl::applyUnsafe(DecimalQuantity &inValue, NumberStringBuilder &outString,
UErrorCode &status) {
if (U_FAILURE(status)) { return; }
fMicroPropsGenerator->processQuantity(inValue, fMicros, status);
if (U_FAILURE(status)) { return; }
microsToString(fMicros, inValue, outString, status);
}
NumberFormatterImpl::NumberFormatterImpl(const MacroProps &macros, bool safe, UErrorCode &status) {
fMicroPropsGenerator = macrosToMicroGenerator(macros, safe, status);
}
//////////
const MicroPropsGenerator *
NumberFormatterImpl::macrosToMicroGenerator(const MacroProps &macros, bool safe, UErrorCode &status) {
const MicroPropsGenerator *chain = &fMicros;
// Check that macros is error-free before continuing.
if (macros.copyErrorTo(status)) {
return nullptr;
}
// TODO: Accept currency symbols from DecimalFormatSymbols?
// Pre-compute a few values for efficiency.
bool isCurrency = unitIsCurrency(macros.unit);
bool isNoUnit = unitIsNoUnit(macros.unit);
bool isPercent = isNoUnit && unitIsPercent(macros.unit);
bool isPermille = isNoUnit && unitIsPermille(macros.unit);
bool isCldrUnit = !isCurrency && !isNoUnit;
bool isAccounting =
macros.sign == UNUM_SIGN_ACCOUNTING || macros.sign == UNUM_SIGN_ACCOUNTING_ALWAYS;
CurrencyUnit currency(kDefaultCurrency, status);
if (isCurrency) {
currency = CurrencyUnit(macros.unit, status); // Restore CurrencyUnit from MeasureUnit
}
UNumberUnitWidth unitWidth = UNUM_UNIT_WIDTH_SHORT;
if (macros.unitWidth != UNUM_UNIT_WIDTH_COUNT) {
unitWidth = macros.unitWidth;
}
// Select the numbering system.
LocalPointer<const NumberingSystem> nsLocal;
const NumberingSystem *ns;
if (macros.symbols.isNumberingSystem()) {
ns = macros.symbols.getNumberingSystem();
} else {
// TODO: Is there a way to avoid creating the NumberingSystem object?
ns = NumberingSystem::createInstance(macros.locale, status);
// Give ownership to the function scope.
nsLocal.adoptInstead(ns);
}
const char *nsName = ns->getName();
// Load and parse the pattern string. It is used for grouping sizes and affixes only.
CldrPatternStyle patternStyle;
if (isPercent || isPermille) {
patternStyle = CLDR_PATTERN_STYLE_PERCENT;
} else if (!isCurrency || unitWidth == UNUM_UNIT_WIDTH_FULL_NAME) {
patternStyle = CLDR_PATTERN_STYLE_DECIMAL;
} else if (isAccounting) {
// 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 = CLDR_PATTERN_STYLE_ACCOUNTING;
} else {
patternStyle = CLDR_PATTERN_STYLE_CURRENCY;
}
const char16_t *pattern = getPatternForStyle(macros.locale, nsName, patternStyle, status);
auto patternInfo = new ParsedPatternInfo();
fPatternInfo.adoptInstead(patternInfo);
PatternParser::parseToPatternInfo(UnicodeString(pattern), *patternInfo, status);
/////////////////////////////////////////////////////////////////////////////////////
/// START POPULATING THE DEFAULT MICROPROPS AND BUILDING THE MICROPROPS GENERATOR ///
/////////////////////////////////////////////////////////////////////////////////////
// Symbols
if (macros.symbols.isDecimalFormatSymbols()) {
fMicros.symbols = macros.symbols.getDecimalFormatSymbols();
} else {
fMicros.symbols = new DecimalFormatSymbols(macros.locale, *ns, status);
// Give ownership to the NumberFormatterImpl.
fSymbols.adoptInstead(fMicros.symbols);
}
// Rounding strategy
if (!macros.rounder.isBogus()) {
fMicros.rounding = macros.rounder;
} else if (macros.notation.fType == Notation::NTN_COMPACT) {
fMicros.rounding = Rounder::integer().withMinDigits(2);
} else if (isCurrency) {
fMicros.rounding = Rounder::currency(UCURR_USAGE_STANDARD);
} else {
fMicros.rounding = Rounder::maxFraction(6);
}
fMicros.rounding.setLocaleData(currency, status);
// Grouping strategy
if (!macros.grouper.isBogus()) {
fMicros.grouping = macros.grouper;
} else if (macros.notation.fType == Notation::NTN_COMPACT) {
// Compact notation uses minGrouping by default since ICU 59
fMicros.grouping = Grouper::minTwoDigits();
} else {
fMicros.grouping = Grouper::defaults();
}
fMicros.grouping.setLocaleData(*fPatternInfo);
// Padding strategy
if (!macros.padder.isBogus()) {
fMicros.padding = macros.padder;
} else {
fMicros.padding = Padder::none();
}
// Integer width
if (!macros.integerWidth.isBogus()) {
fMicros.integerWidth = macros.integerWidth;
} else {
fMicros.integerWidth = IntegerWidth::zeroFillTo(1);
}
// Sign display
if (macros.sign != UNUM_SIGN_COUNT) {
fMicros.sign = macros.sign;
} else {
fMicros.sign = UNUM_SIGN_AUTO;
}
// Decimal mark display
if (macros.decimal != UNUM_DECIMAL_SEPARATOR_COUNT) {
fMicros.decimal = macros.decimal;
} else {
fMicros.decimal = UNUM_DECIMAL_SEPARATOR_AUTO;
}
// Use monetary separator symbols
fMicros.useCurrency = isCurrency;
// Inner modifier (scientific notation)
if (macros.notation.fType == Notation::NTN_SCIENTIFIC) {
fScientificHandler.adoptInstead(new ScientificHandler(&macros.notation, fMicros.symbols, chain));
chain = fScientificHandler.getAlias();
} else {
// No inner modifier required
fMicros.modInner = &fMicros.helpers.emptyStrongModifier;
}
// Middle modifier (patterns, positive/negative, currency symbols, percent)
auto patternModifier = new MutablePatternModifier(false);
fPatternModifier.adoptInstead(patternModifier);
patternModifier->setPatternInfo(fPatternInfo.getAlias());
patternModifier->setPatternAttributes(fMicros.sign, isPermille);
if (patternModifier->needsPlurals()) {
patternModifier->setSymbols(
fMicros.symbols,
currency,
unitWidth,
resolvePluralRules(macros.rules, macros.locale, status));
} else {
patternModifier->setSymbols(fMicros.symbols, currency, unitWidth, nullptr);
}
if (safe) {
fImmutablePatternModifier.adoptInstead(patternModifier->createImmutableAndChain(chain, status));
chain = fImmutablePatternModifier.getAlias();
} else {
patternModifier->addToChain(chain);
chain = patternModifier;
}
// Outer modifier (CLDR units and currency long names)
if (isCldrUnit) {
fLongNameHandler.adoptInstead(
new LongNameHandler(
LongNameHandler::forMeasureUnit(
macros.locale,
macros.unit,
unitWidth,
resolvePluralRules(macros.rules, macros.locale, status),
chain,
status)));
chain = fLongNameHandler.getAlias();
} else if (isCurrency && unitWidth == UNUM_UNIT_WIDTH_FULL_NAME) {
fLongNameHandler.adoptInstead(
new LongNameHandler(
LongNameHandler::forCurrencyLongNames(
macros.locale,
currency,
resolvePluralRules(macros.rules, macros.locale, status),
chain,
status)));
chain = fLongNameHandler.getAlias();
} else {
// No outer modifier required
fMicros.modOuter = &fMicros.helpers.emptyWeakModifier;
}
// Compact notation
// NOTE: Compact notation can (but might not) override the middle modifier and rounding.
// It therefore needs to go at the end of the chain.
if (macros.notation.fType == Notation::NTN_COMPACT) {
CompactType compactType = (isCurrency && unitWidth != UNUM_UNIT_WIDTH_FULL_NAME)
? CompactType::TYPE_CURRENCY : CompactType::TYPE_DECIMAL;
fCompactHandler.adoptInstead(
new CompactHandler(
macros.notation.fUnion.compactStyle,
macros.locale,
nsName,
compactType,
resolvePluralRules(macros.rules, macros.locale, status),
safe ? patternModifier : nullptr,
chain,
status));
chain = fCompactHandler.getAlias();
}
return chain;
}
const PluralRules *
NumberFormatterImpl::resolvePluralRules(const PluralRules *rulesPtr, const Locale &locale,
UErrorCode &status) {
if (rulesPtr != nullptr) {
return rulesPtr;
}
// Lazily create PluralRules
if (fRules.isNull()) {
fRules.adoptInstead(PluralRules::forLocale(locale, status));
}
return fRules.getAlias();
}
int32_t NumberFormatterImpl::microsToString(const MicroProps &micros, DecimalQuantity &quantity,
NumberStringBuilder &string, UErrorCode &status) {
micros.rounding.apply(quantity, status);
micros.integerWidth.apply(quantity, status);
int32_t length = writeNumber(micros, quantity, string, status);
// NOTE: When range formatting is added, these modifiers can bubble up.
// For now, apply them all here at once.
// Always apply the inner modifier (which is "strong").
length += micros.modInner->apply(string, 0, length, status);
if (micros.padding.isValid()) {
length += micros.padding
.padAndApply(*micros.modMiddle, *micros.modOuter, string, 0, length, status);
} else {
length += micros.modMiddle->apply(string, 0, length, status);
length += micros.modOuter->apply(string, 0, length, status);
}
return length;
}
int32_t NumberFormatterImpl::writeNumber(const MicroProps &micros, DecimalQuantity &quantity,
NumberStringBuilder &string, UErrorCode &status) {
int32_t length = 0;
if (quantity.isInfinite()) {
length += string.insert(
length,
micros.symbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kInfinitySymbol),
UNUM_INTEGER_FIELD,
status);
} else if (quantity.isNaN()) {
length += string.insert(
length,
micros.symbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kNaNSymbol),
UNUM_INTEGER_FIELD,
status);
} else {
// Add the integer digits
length += writeIntegerDigits(micros, quantity, string, status);
// Add the decimal point
if (quantity.getLowerDisplayMagnitude() < 0 || micros.decimal == UNUM_DECIMAL_SEPARATOR_ALWAYS) {
length += string.insert(
length,
micros.useCurrency ? micros.symbols->getSymbol(
DecimalFormatSymbols::ENumberFormatSymbol::kMonetarySeparatorSymbol) : micros
.symbols
->getSymbol(
DecimalFormatSymbols::ENumberFormatSymbol::kDecimalSeparatorSymbol),
UNUM_DECIMAL_SEPARATOR_FIELD,
status);
}
// Add the fraction digits
length += writeFractionDigits(micros, quantity, string, status);
}
return length;
}
int32_t NumberFormatterImpl::writeIntegerDigits(const MicroProps &micros, DecimalQuantity &quantity,
NumberStringBuilder &string, UErrorCode &status) {
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->getSymbol(
DecimalFormatSymbols::ENumberFormatSymbol::kMonetaryGroupingSeparatorSymbol)
: micros.symbols->getSymbol(
DecimalFormatSymbols::ENumberFormatSymbol::kGroupingSeparatorSymbol),
UNUM_GROUPING_SEPARATOR_FIELD,
status);
}
// Get and append the next digit value
int8_t nextDigit = quantity.getDigit(i);
length += string.insert(
0, getDigitFromSymbols(nextDigit, *micros.symbols), UNUM_INTEGER_FIELD, status);
}
return length;
}
int32_t NumberFormatterImpl::writeFractionDigits(const MicroProps &micros, DecimalQuantity &quantity,
NumberStringBuilder &string, UErrorCode &status) {
int length = 0;
int fractionCount = -quantity.getLowerDisplayMagnitude();
for (int i = 0; i < fractionCount; i++) {
// Get and append the next digit value
int8_t nextDigit = quantity.getDigit(-i - 1);
length += string.append(
getDigitFromSymbols(nextDigit, *micros.symbols), UNUM_FRACTION_FIELD, status);
}
return length;
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,123 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#ifndef __NUMBER_FORMATIMPL_H__
#define __NUMBER_FORMATIMPL_H__
#include "number_types.h"
#include "number_stringbuilder.h"
#include "number_patternstring.h"
#include "number_utils.h"
#include "number_patternmodifier.h"
#include "number_longnames.h"
#include "number_compact.h"
U_NAMESPACE_BEGIN namespace number {
namespace impl {
/**
* 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.
*/
class NumberFormatterImpl {
public:
/**
* Builds a "safe" MicroPropsGenerator, which is thread-safe and can be used repeatedly.
* The caller owns the returned NumberFormatterImpl.
*/
static NumberFormatterImpl *fromMacros(const MacroProps &macros, UErrorCode &status);
/**
* Builds and evaluates an "unsafe" MicroPropsGenerator, which is cheaper but can be used only once.
*/
static void
applyStatic(const MacroProps &macros, DecimalQuantity &inValue, NumberStringBuilder &outString,
UErrorCode &status);
/**
* Evaluates the "safe" MicroPropsGenerator created by "fromMacros".
*/
void apply(DecimalQuantity &inValue, NumberStringBuilder &outString, UErrorCode &status) const;
private:
// Head of the MicroPropsGenerator linked list:
const MicroPropsGenerator *fMicroPropsGenerator = nullptr;
// Tail of the list:
MicroProps fMicros;
// Other fields possibly used by the number formatting pipeline:
// TODO: Convert some of these LocalPointers to value objects to reduce the number of news?
LocalPointer<const DecimalFormatSymbols> fSymbols;
LocalPointer<const PluralRules> fRules;
LocalPointer<const ParsedPatternInfo> fPatternInfo;
LocalPointer<const ScientificHandler> fScientificHandler;
LocalPointer<const MutablePatternModifier> fPatternModifier;
LocalPointer<const ImmutablePatternModifier> fImmutablePatternModifier;
LocalPointer<const LongNameHandler> fLongNameHandler;
LocalPointer<const CompactHandler> fCompactHandler;
NumberFormatterImpl(const MacroProps &macros, bool safe, UErrorCode &status);
void applyUnsafe(DecimalQuantity &inValue, NumberStringBuilder &outString, UErrorCode &status);
/**
* If rulesPtr is non-null, return it. Otherwise, return a PluralRules owned by this object for the
* specified locale, creating it if necessary.
*/
const PluralRules *
resolvePluralRules(const PluralRules *rulesPtr, const Locale &locale, UErrorCode &status);
/**
* 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.
*/
const MicroPropsGenerator *
macrosToMicroGenerator(const MacroProps &macros, bool safe, UErrorCode &status);
/**
* Synthesizes the output string from a MicroProps and DecimalQuantity.
*
* @param micros
* The MicroProps after the quantity has been consumed. Will not be mutated.
* @param quantity
* The DecimalQuantity to be rendered. May be mutated.
* @param string
* The output string. Will be mutated.
*/
static int32_t
microsToString(const MicroProps &micros, DecimalQuantity &quantity, NumberStringBuilder &string,
UErrorCode &status);
static int32_t
writeNumber(const MicroProps &micros, DecimalQuantity &quantity, NumberStringBuilder &string,
UErrorCode &status);
static int32_t
writeIntegerDigits(const MicroProps &micros, DecimalQuantity &quantity, NumberStringBuilder &string,
UErrorCode &status);
static int32_t
writeFractionDigits(const MicroProps &micros, DecimalQuantity &quantity, NumberStringBuilder &string,
UErrorCode &status);
};
} // namespace impl
} // namespace number
U_NAMESPACE_END
#endif //__NUMBER_FORMATIMPL_H__
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,51 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#include "unicode/numberformatter.h"
#include "number_patternstring.h"
using namespace icu::number;
Grouper Grouper::defaults() {
return {-2, -2, false};
}
Grouper Grouper::minTwoDigits() {
return {-2, -2, true};
}
Grouper Grouper::none() {
return {-1, -1, false};
}
void Grouper::setLocaleData(const impl::ParsedPatternInfo &patternInfo) {
if (fGrouping1 != -2) {
return;
}
auto grouping1 = static_cast<int8_t> (patternInfo.positive.groupingSizes & 0xffff);
auto grouping2 = static_cast<int8_t> ((patternInfo.positive.groupingSizes >> 16) & 0xffff);
auto grouping3 = static_cast<int8_t> ((patternInfo.positive.groupingSizes >> 32) & 0xffff);
if (grouping2 == -1) {
grouping1 = -1;
}
if (grouping3 == -1) {
grouping2 = grouping1;
}
fGrouping1 = grouping1;
fGrouping2 = grouping2;
}
bool Grouper::groupAtPosition(int32_t position, const impl::DecimalQuantity &value) const {
U_ASSERT(fGrouping1 > -2);
if (fGrouping1 == -1 || fGrouping1 == 0) {
// Either -1 or 0 means "no grouping"
return false;
}
position -= fGrouping1;
return position >= 0 && (position % fGrouping2) == 0
&& value.getUpperDisplayMagnitude() - fGrouping1 + 1 >= (fMin2 ? 2 : 1);
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,45 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#include "unicode/numberformatter.h"
#include "number_types.h"
#include "number_decimalquantity.h"
using namespace icu::number;
using namespace icu::number::impl;
IntegerWidth::IntegerWidth(int8_t minInt, int8_t maxInt) {
fUnion.minMaxInt.fMinInt = minInt;
fUnion.minMaxInt.fMaxInt = maxInt;
}
IntegerWidth IntegerWidth::zeroFillTo(int32_t minInt) {
if (minInt >= 0 && minInt <= kMaxIntFracSig) {
return {static_cast<int8_t>(minInt), -1};
} else {
return {U_NUMBER_DIGIT_WIDTH_OUT_OF_RANGE_ERROR};
}
}
IntegerWidth IntegerWidth::truncateAt(int32_t maxInt) {
if (fHasError) { return *this; } // No-op on error
if (maxInt >= 0 && maxInt <= kMaxIntFracSig) {
return {fUnion.minMaxInt.fMinInt, static_cast<int8_t>(maxInt)};
} else {
return {U_NUMBER_DIGIT_WIDTH_OUT_OF_RANGE_ERROR};
}
}
void IntegerWidth::apply(impl::DecimalQuantity &quantity, UErrorCode &status) const {
if (fHasError) {
status = U_ILLEGAL_ARGUMENT_ERROR;
} else if (fUnion.minMaxInt.fMaxInt == -1) {
quantity.setIntegerLength(fUnion.minMaxInt.fMinInt, INT32_MAX);
} else {
quantity.setIntegerLength(fUnion.minMaxInt.fMinInt, fUnion.minMaxInt.fMaxInt);
}
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,162 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#include "unicode/ures.h"
#include "ureslocs.h"
#include "charstr.h"
#include "uresimp.h"
#include "number_longnames.h"
#include <algorithm>
#include "cstring.h"
using namespace icu;
using namespace icu::number::impl;
namespace {
//////////////////////////
/// BEGIN DATA LOADING ///
//////////////////////////
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++) {
outArray[i].setToBogus();
}
}
void put(const char *key, ResourceValue &value, UBool /*noFallback*/, UErrorCode &status) 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);
if (U_FAILURE(status)) { return; }
if (!outArray[plural].isBogus()) {
continue;
}
outArray[plural] = value.getUnicodeString(status);
if (U_FAILURE(status)) { return; }
}
}
private:
UnicodeString *outArray;
};
// NOTE: outArray MUST have room for all StandardPlural values. No bounds checking is performed.
void getMeasureData(const Locale &locale, const MeasureUnit &unit, const UNumberUnitWidth &width,
UnicodeString *outArray, UErrorCode &status) {
PluralTableSink sink(outArray);
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("/", status);
key.append(unit.getType(), status);
key.append("/", status);
key.append(unit.getSubtype(), status);
ures_getAllItemsWithFallback(unitsBundle.getAlias(), key.data(), sink, status);
}
void getCurrencyLongNameData(const Locale &locale, const CurrencyUnit &currency, UnicodeString *outArray,
UErrorCode &status) {
// In ICU4J, this method gets a CurrencyData from CurrencyData.provider.
// TODO(ICU4J): Implement this without going through CurrencyData, like in ICU4C?
PluralTableSink sink(outArray);
LocalUResourceBundlePointer unitsBundle(ures_open(U_ICUDATA_CURR, locale.getName(), &status));
if (U_FAILURE(status)) { return; }
ures_getAllItemsWithFallback(unitsBundle.getAlias(), "CurrencyUnitPatterns", sink, status);
if (U_FAILURE(status)) { return; }
for (int32_t i = 0; i < StandardPlural::Form::COUNT; i++) {
UnicodeString &pattern = outArray[i];
if (pattern.isBogus()) {
continue;
}
UBool isChoiceFormat = FALSE;
int32_t longNameLen = 0;
const char16_t *longName = ucurr_getPluralName(
currency.getISOCurrency(),
locale.getName(),
&isChoiceFormat,
StandardPlural::getKeyword(static_cast<StandardPlural::Form>(i)),
&longNameLen,
&status);
// Example pattern from data: "{0} {1}"
// Example output after find-and-replace: "{0} US dollars"
pattern.findAndReplace(UnicodeString(u"{1}"), UnicodeString(longName, longNameLen));
}
}
////////////////////////
/// END DATA LOADING ///
////////////////////////
} // namespace
LongNameHandler
LongNameHandler::forMeasureUnit(const Locale &loc, const MeasureUnit &unit, const UNumberUnitWidth &width,
const PluralRules *rules, const MicroPropsGenerator *parent,
UErrorCode &status) {
LongNameHandler result(rules, parent);
UnicodeString simpleFormats[StandardPlural::Form::COUNT];
getMeasureData(loc, unit, width, simpleFormats, status);
if (U_FAILURE(status)) { return result; }
// TODO: What field to use for units?
simpleFormatsToModifiers(simpleFormats, 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];
getCurrencyLongNameData(loc, currency, simpleFormats, status);
if (U_FAILURE(status)) { return result; }
simpleFormatsToModifiers(simpleFormats, UNUM_CURRENCY_FIELD, result.fModifiers, status);
return result;
}
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;
}
SimpleFormatter compiledFormatter(simpleFormat, 1, 1, status);
output[i] = SimpleModifier(compiledFormatter, field, false);
}
}
void LongNameHandler::processQuantity(DecimalQuantity &quantity, MicroProps &micros,
UErrorCode &status) const {
parent->processQuantity(quantity, micros, status);
// TODO: Avoid the copy here?
DecimalQuantity copy(quantity);
micros.rounding.apply(copy, status);
micros.modOuter = &fModifiers[copy.getStandardPlural(rules)];
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,46 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#ifndef __NUMBER_LONGNAMES_H__
#define __NUMBER_LONGNAMES_H__
#include "unicode/uversion.h"
#include "number_utils.h"
#include "number_modifiers.h"
U_NAMESPACE_BEGIN namespace number {
namespace impl {
class LongNameHandler : public MicroPropsGenerator, public UObject {
public:
static LongNameHandler
forCurrencyLongNames(const Locale &loc, const CurrencyUnit &currency, const PluralRules *rules,
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);
void
processQuantity(DecimalQuantity &quantity, MicroProps &micros, UErrorCode &status) const override;
private:
SimpleModifier fModifiers[StandardPlural::Form::COUNT];
const PluralRules *rules;
const MicroPropsGenerator *parent;
LongNameHandler(const PluralRules *rules, const MicroPropsGenerator *parent)
: rules(rules), parent(parent) {}
static void simpleFormatsToModifiers(const UnicodeString *simpleFormats, Field field,
SimpleModifier *output, UErrorCode &status);
};
} // namespace impl
} // namespace number
U_NAMESPACE_END
#endif //__NUMBER_LONGNAMES_H__
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,299 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#include "umutex.h"
#include "ucln_cmn.h"
#include "ucln_in.h"
#include "number_modifiers.h"
using namespace icu;
using namespace icu::number::impl;
namespace {
// TODO: This is copied from simpleformatter.cpp
const int32_t ARG_NUM_LIMIT = 0x100;
// These are the default currency spacing UnicodeSets in CLDR.
// Pre-compute them for performance.
// The Java unit test testCurrencySpacingPatternStability() will start failing if these change in CLDR.
icu::UInitOnce gDefaultCurrencySpacingInitOnce = U_INITONCE_INITIALIZER;
UnicodeSet *UNISET_DIGIT = nullptr;
UnicodeSet *UNISET_NOTS = nullptr;
UBool U_CALLCONV cleanupDefaultCurrencySpacing() {
delete UNISET_DIGIT;
UNISET_DIGIT = nullptr;
delete UNISET_NOTS;
UNISET_NOTS = nullptr;
return TRUE;
}
void U_CALLCONV initDefaultCurrencySpacing(UErrorCode &status) {
ucln_i18n_registerCleanup(UCLN_I18N_CURRENCY_SPACING, cleanupDefaultCurrencySpacing);
UNISET_DIGIT = new UnicodeSet(UnicodeString(u"[:digit:]"), status);
UNISET_NOTS = new UnicodeSet(UnicodeString(u"[:^S:]"), status);
if (UNISET_DIGIT == nullptr || UNISET_NOTS == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return;
}
UNISET_DIGIT->freeze();
UNISET_NOTS->freeze();
}
} // namespace
int32_t ConstantAffixModifier::apply(NumberStringBuilder &output, int leftIndex, int rightIndex,
UErrorCode &status) const {
// Insert the suffix first since inserting the prefix will change the rightIndex
int length = output.insert(rightIndex, fSuffix, fField, status);
length += output.insert(leftIndex, fPrefix, fField, status);
return length;
}
int32_t ConstantAffixModifier::getPrefixLength(UErrorCode &status) const {
(void)status;
return fPrefix.length();
}
int32_t ConstantAffixModifier::getCodePointCount(UErrorCode &status) const {
(void)status;
return fPrefix.countChar32() + fSuffix.countChar32();
}
bool ConstantAffixModifier::isStrong() const {
return fStrong;
}
SimpleModifier::SimpleModifier(const SimpleFormatter &simpleFormatter, Field field, bool strong)
: fCompiledPattern(simpleFormatter.compiledPattern), fField(field), fStrong(strong) {
U_ASSERT(1 ==
SimpleFormatter::getArgumentLimit(fCompiledPattern.getBuffer(), fCompiledPattern.length()));
if (fCompiledPattern.charAt(1) != 0) {
fPrefixLength = fCompiledPattern.charAt(1) - ARG_NUM_LIMIT;
fSuffixOffset = 3 + fPrefixLength;
} else {
fPrefixLength = 0;
fSuffixOffset = 2;
}
if (3 + fPrefixLength < fCompiledPattern.length()) {
fSuffixLength = fCompiledPattern.charAt(fSuffixOffset) - ARG_NUM_LIMIT;
} else {
fSuffixLength = 0;
}
}
SimpleModifier::SimpleModifier() : fStrong(false), fPrefixLength(0), fSuffixLength(0) {
}
int32_t SimpleModifier::apply(NumberStringBuilder &output, int leftIndex, int rightIndex,
UErrorCode &status) const {
return formatAsPrefixSuffix(output, leftIndex, rightIndex, fField, status);
}
int32_t SimpleModifier::getPrefixLength(UErrorCode &status) const {
(void)status;
return fPrefixLength;
}
int32_t SimpleModifier::getCodePointCount(UErrorCode &status) const {
(void)status;
int32_t count = 0;
if (fPrefixLength > 0) {
count += fCompiledPattern.countChar32(2, fPrefixLength);
}
if (fSuffixLength > 0) {
count += fCompiledPattern.countChar32(1 + fSuffixOffset, fSuffixLength);
}
return count;
}
bool SimpleModifier::isStrong() const {
return fStrong;
}
int32_t
SimpleModifier::formatAsPrefixSuffix(NumberStringBuilder &result, int32_t startIndex, int32_t endIndex,
Field field, UErrorCode &status) const {
if (fPrefixLength > 0) {
result.insert(startIndex, fCompiledPattern, 2, 2 + fPrefixLength, field, status);
}
if (fSuffixLength > 0) {
result.insert(
endIndex + fPrefixLength,
fCompiledPattern,
1 + fSuffixOffset,
1 + fSuffixOffset + fSuffixLength,
field,
status);
}
return fPrefixLength + fSuffixLength;
}
int32_t ConstantMultiFieldModifier::apply(NumberStringBuilder &output, int leftIndex, int rightIndex,
UErrorCode &status) const {
// Insert the suffix first since inserting the prefix will change the rightIndex
int32_t length = output.insert(rightIndex, fSuffix, status);
length += output.insert(leftIndex, fPrefix, status);
return length;
}
int32_t ConstantMultiFieldModifier::getPrefixLength(UErrorCode &status) const {
(void)status;
return fPrefix.length();
}
int32_t ConstantMultiFieldModifier::getCodePointCount(UErrorCode &status) const {
(void)status;
return fPrefix.codePointCount() + fSuffix.codePointCount();
}
bool ConstantMultiFieldModifier::isStrong() const {
return fStrong;
}
CurrencySpacingEnabledModifier::CurrencySpacingEnabledModifier(const NumberStringBuilder &prefix,
const NumberStringBuilder &suffix,
bool strong,
const DecimalFormatSymbols &symbols,
UErrorCode &status)
: ConstantMultiFieldModifier(prefix, suffix, strong) {
// Check for currency spacing. Do not build the UnicodeSets unless there is
// a currency code point at a boundary.
if (prefix.length() > 0 && prefix.fieldAt(prefix.length() - 1) == UNUM_CURRENCY_FIELD) {
int prefixCp = prefix.getLastCodePoint();
UnicodeSet prefixUnicodeSet = getUnicodeSet(symbols, IN_CURRENCY, PREFIX, status);
if (prefixUnicodeSet.contains(prefixCp)) {
fAfterPrefixUnicodeSet = getUnicodeSet(symbols, IN_NUMBER, PREFIX, status);
fAfterPrefixUnicodeSet.freeze();
fAfterPrefixInsert = getInsertString(symbols, PREFIX, status);
} else {
fAfterPrefixUnicodeSet.setToBogus();
fAfterPrefixInsert.setToBogus();
}
} else {
fAfterPrefixUnicodeSet.setToBogus();
fAfterPrefixInsert.setToBogus();
}
if (suffix.length() > 0 && suffix.fieldAt(0) == UNUM_CURRENCY_FIELD) {
int suffixCp = suffix.getLastCodePoint();
UnicodeSet suffixUnicodeSet = getUnicodeSet(symbols, IN_CURRENCY, SUFFIX, status);
if (suffixUnicodeSet.contains(suffixCp)) {
fBeforeSuffixUnicodeSet = getUnicodeSet(symbols, IN_NUMBER, SUFFIX, status);
fBeforeSuffixUnicodeSet.freeze();
fBeforeSuffixInsert = getInsertString(symbols, SUFFIX, status);
} else {
fBeforeSuffixUnicodeSet.setToBogus();
fBeforeSuffixInsert.setToBogus();
}
} else {
fBeforeSuffixUnicodeSet.setToBogus();
fBeforeSuffixInsert.setToBogus();
}
}
int32_t CurrencySpacingEnabledModifier::apply(NumberStringBuilder &output, int leftIndex, int rightIndex,
UErrorCode &status) const {
// Currency spacing logic
int length = 0;
if (rightIndex - leftIndex > 0 && !fAfterPrefixUnicodeSet.isBogus() &&
fAfterPrefixUnicodeSet.contains(output.codePointAt(leftIndex))) {
// TODO: Should we use the CURRENCY field here?
length += output.insert(leftIndex, fAfterPrefixInsert, UNUM_FIELD_COUNT, status);
}
if (rightIndex - leftIndex > 0 && !fBeforeSuffixUnicodeSet.isBogus() &&
fBeforeSuffixUnicodeSet.contains(output.codePointBefore(rightIndex))) {
// TODO: Should we use the CURRENCY field here?
length += output.insert(rightIndex + length, fBeforeSuffixInsert, UNUM_FIELD_COUNT, status);
}
// Call super for the remaining logic
length += ConstantMultiFieldModifier::apply(output, leftIndex, rightIndex + length, status);
return length;
}
int32_t
CurrencySpacingEnabledModifier::applyCurrencySpacing(NumberStringBuilder &output, int32_t prefixStart,
int32_t prefixLen, int32_t suffixStart,
int32_t suffixLen,
const DecimalFormatSymbols &symbols,
UErrorCode &status) {
int length = 0;
bool hasPrefix = (prefixLen > 0);
bool hasSuffix = (suffixLen > 0);
bool hasNumber = (suffixStart - prefixStart - prefixLen > 0); // could be empty string
if (hasPrefix && hasNumber) {
length += applyCurrencySpacingAffix(output, prefixStart + prefixLen, PREFIX, symbols, status);
}
if (hasSuffix && hasNumber) {
length += applyCurrencySpacingAffix(output, suffixStart + length, SUFFIX, symbols, status);
}
return length;
}
int32_t
CurrencySpacingEnabledModifier::applyCurrencySpacingAffix(NumberStringBuilder &output, int32_t index,
EAffix affix,
const DecimalFormatSymbols &symbols,
UErrorCode &status) {
// 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.
Field affixField = (affix == PREFIX) ? output.fieldAt(index - 1) : output.fieldAt(index);
if (affixField != UNUM_CURRENCY_FIELD) {
return 0;
}
int affixCp = (affix == PREFIX) ? output.codePointBefore(index) : output.codePointAt(index);
UnicodeSet affixUniset = getUnicodeSet(symbols, IN_CURRENCY, affix, status);
if (!affixUniset.contains(affixCp)) {
return 0;
}
int numberCp = (affix == PREFIX) ? output.codePointAt(index) : output.codePointBefore(index);
UnicodeSet numberUniset = getUnicodeSet(symbols, IN_NUMBER, affix, status);
if (!numberUniset.contains(numberCp)) {
return 0;
}
UnicodeString spacingString = getInsertString(symbols, affix, status);
// NOTE: This next line *inserts* the spacing string, triggering an arraycopy.
// It would be more efficient if this could be done before affixes were attached,
// so that it could be prepended/appended instead of inserted.
// However, the build code path is more efficient, and this is the most natural
// place to put currency spacing in the non-build code path.
// TODO: Should we use the CURRENCY field here?
return output.insert(index, spacingString, UNUM_FIELD_COUNT, status);
}
UnicodeSet
CurrencySpacingEnabledModifier::getUnicodeSet(const DecimalFormatSymbols &symbols, EPosition position,
EAffix affix, UErrorCode &status) {
// Ensure the static defaults are initialized:
umtx_initOnce(gDefaultCurrencySpacingInitOnce, &initDefaultCurrencySpacing, status);
if (U_FAILURE(status)) {
return UnicodeSet();
}
const UnicodeString& pattern = symbols.getPatternForCurrencySpacing(
position == IN_CURRENCY ? UNUM_CURRENCY_MATCH : UNUM_CURRENCY_SURROUNDING_MATCH,
affix == SUFFIX,
status);
if (pattern.compare(u"[:digit:]", -1) == 0) {
return *UNISET_DIGIT;
} else if (pattern.compare(u"[:^S:]", -1) == 0) {
return *UNISET_NOTS;
} else {
return UnicodeSet(pattern, status);
}
}
UnicodeString
CurrencySpacingEnabledModifier::getInsertString(const DecimalFormatSymbols &symbols, EAffix affix,
UErrorCode &status) {
return symbols.getPatternForCurrencySpacing(UNUM_CURRENCY_INSERT, affix == SUFFIX, status);
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,252 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#ifndef __NUMBER_MODIFIERS_H__
#define __NUMBER_MODIFIERS_H__
#include <algorithm>
#include <cstdint>
#include "unicode/uniset.h"
#include "unicode/simpleformatter.h"
#include "standardplural.h"
#include "number_stringbuilder.h"
#include "number_types.h"
U_NAMESPACE_BEGIN namespace number {
namespace impl {
/**
* The canonical implementation of {@link Modifier}, containing a prefix and suffix string.
* TODO: This is not currently being used by real code and could be removed.
*/
class U_I18N_API ConstantAffixModifier : public Modifier, public UObject {
public:
ConstantAffixModifier(const UnicodeString &prefix, const UnicodeString &suffix, Field field,
bool strong)
: fPrefix(prefix), fSuffix(suffix), fField(field), fStrong(strong) {}
int32_t apply(NumberStringBuilder &output, int32_t leftIndex, int32_t rightIndex,
UErrorCode &status) const override;
int32_t getPrefixLength(UErrorCode &status) const override;
int32_t getCodePointCount(UErrorCode &status) const override;
bool isStrong() const override;
private:
UnicodeString fPrefix;
UnicodeString fSuffix;
Field fField;
bool fStrong;
};
/**
* The second primary implementation of {@link Modifier}, this one consuming a {@link SimpleFormatter}
* pattern.
*/
class U_I18N_API SimpleModifier : public Modifier, public UMemory {
public:
SimpleModifier(const SimpleFormatter &simpleFormatter, Field field, bool strong);
// Default constructor for LongNameHandler.h
SimpleModifier();
int32_t apply(NumberStringBuilder &output, int32_t leftIndex, int32_t rightIndex,
UErrorCode &status) const override;
int32_t getPrefixLength(UErrorCode &status) const override;
int32_t getCodePointCount(UErrorCode &status) const override;
bool isStrong() const override;
/**
* 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.
*
* <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.
*
* <p>
* This is well-defined only for patterns with exactly one argument.
*
* @param result
* The StringBuilder containing the value argument.
* @param startIndex
* The left index of the value within the string builder.
* @param endIndex
* 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.
*/
int32_t
formatAsPrefixSuffix(NumberStringBuilder &result, int32_t startIndex, int32_t endIndex, Field field,
UErrorCode &status) const;
private:
UnicodeString fCompiledPattern;
Field fField;
bool fStrong;
int32_t fPrefixLength;
int32_t fSuffixOffset;
int32_t fSuffixLength;
};
/**
* 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).
*/
class U_I18N_API ConstantMultiFieldModifier : public Modifier, public UMemory {
public:
ConstantMultiFieldModifier(const NumberStringBuilder &prefix, const NumberStringBuilder &suffix,
bool strong) : fPrefix(prefix), fSuffix(suffix), fStrong(strong) {}
int32_t apply(NumberStringBuilder &output, int32_t leftIndex, int32_t rightIndex,
UErrorCode &status) const override;
int32_t getPrefixLength(UErrorCode &status) const override;
int32_t getCodePointCount(UErrorCode &status) const override;
bool isStrong() const override;
protected:
// NOTE: In Java, these are stored as array pointers. In C++, the NumberStringBuilder is stored by
// value and is treated internally as immutable.
NumberStringBuilder fPrefix;
NumberStringBuilder fSuffix;
bool fStrong;
};
/** Identical to {@link ConstantMultiFieldModifier}, but supports currency spacing. */
class U_I18N_API CurrencySpacingEnabledModifier : public ConstantMultiFieldModifier {
public:
/** Safe code path */
CurrencySpacingEnabledModifier(const NumberStringBuilder &prefix, const NumberStringBuilder &suffix,
bool strong, const DecimalFormatSymbols &symbols, UErrorCode &status);
int32_t apply(NumberStringBuilder &output, int32_t leftIndex, int32_t rightIndex,
UErrorCode &status) const override;
/** Unsafe code path */
static int32_t
applyCurrencySpacing(NumberStringBuilder &output, int32_t prefixStart, int32_t prefixLen,
int32_t suffixStart, int32_t suffixLen, const DecimalFormatSymbols &symbols,
UErrorCode &status);
private:
UnicodeSet fAfterPrefixUnicodeSet;
UnicodeString fAfterPrefixInsert;
UnicodeSet fBeforeSuffixUnicodeSet;
UnicodeString fBeforeSuffixInsert;
enum EAffix {
PREFIX, SUFFIX
};
enum EPosition {
IN_CURRENCY, IN_NUMBER
};
/** Unsafe code path */
static int32_t applyCurrencySpacingAffix(NumberStringBuilder &output, int32_t index, EAffix affix,
const DecimalFormatSymbols &symbols, UErrorCode &status);
static UnicodeSet
getUnicodeSet(const DecimalFormatSymbols &symbols, EPosition position, EAffix affix,
UErrorCode &status);
static UnicodeString
getInsertString(const DecimalFormatSymbols &symbols, EAffix affix, UErrorCode &status);
};
/** A Modifier that does not do anything. */
class U_I18N_API EmptyModifier : public Modifier, public UMemory {
public:
explicit EmptyModifier(bool isStrong) : fStrong(isStrong) {}
int32_t apply(NumberStringBuilder &output, int32_t leftIndex, int32_t rightIndex,
UErrorCode &status) const override {
(void)output;
(void)leftIndex;
(void)rightIndex;
(void)status;
return 0;
}
int32_t getPrefixLength(UErrorCode &status) const override {
(void)status;
return 0;
}
int32_t getCodePointCount(UErrorCode &status) const override {
(void)status;
return 0;
}
bool isStrong() const override {
return fStrong;
}
private:
bool fStrong;
};
/**
* 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.
*/
class U_I18N_API ParameterizedModifier : public UMemory {
public:
// NOTE: mods is zero-initialized (to nullptr)
ParameterizedModifier() : mods() {
}
// No copying!
ParameterizedModifier(const ParameterizedModifier &other) = delete;
~ParameterizedModifier() {
for (const Modifier *mod : mods) {
delete mod;
}
}
void adoptPositiveNegativeModifiers(const Modifier *positive, const Modifier *negative) {
mods[0] = positive;
mods[1] = negative;
}
/** The modifier is ADOPTED. */
void adoptSignPluralModifier(bool isNegative, StandardPlural::Form plural, const Modifier *mod) {
mods[getModIndex(isNegative, plural)] = mod;
}
/** Returns a reference to the modifier; no ownership change. */
const Modifier *getModifier(bool isNegative) const {
return mods[isNegative ? 1 : 0];
}
/** Returns a reference to the modifier; no ownership change. */
const Modifier *getModifier(bool isNegative, StandardPlural::Form plural) const {
return mods[getModIndex(isNegative, plural)];
}
private:
const Modifier *mods[2 * StandardPlural::COUNT];
inline static int32_t getModIndex(bool isNegative, StandardPlural::Form plural) {
return static_cast<int32_t>(plural) * 2 + (isNegative ? 1 : 0);
}
};
} // namespace impl
} // namespace number
U_NAMESPACE_END
#endif //__NUMBER_MODIFIERS_H__
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,72 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#include "unicode/numberformatter.h"
#include "number_types.h"
using namespace icu::number;
using namespace icu::number::impl;
ScientificNotation Notation::scientific() {
// NOTE: ISO C++ does not allow C99 designated initializers.
ScientificSettings settings;
settings.fEngineeringInterval = 1;
settings.fRequireMinInt = false;
settings.fMinExponentDigits = 1;
settings.fExponentSignDisplay = UNUM_SIGN_AUTO;
NotationUnion union_;
union_.scientific = settings;
return {NTN_SCIENTIFIC, union_};
}
ScientificNotation Notation::engineering() {
ScientificSettings settings;
settings.fEngineeringInterval = 3;
settings.fRequireMinInt = false;
settings.fMinExponentDigits = 1;
settings.fExponentSignDisplay = UNUM_SIGN_AUTO;
NotationUnion union_;
union_.scientific = settings;
return {NTN_SCIENTIFIC, union_};
}
Notation Notation::compactShort() {
NotationUnion union_;
union_.compactStyle = CompactStyle::UNUM_SHORT;
return {NTN_COMPACT, union_};
}
Notation Notation::compactLong() {
NotationUnion union_;
union_.compactStyle = CompactStyle::UNUM_LONG;
return {NTN_COMPACT, union_};
}
Notation Notation::simple() {
return {};
}
ScientificNotation
ScientificNotation::withMinExponentDigits(int32_t minExponentDigits) const {
if (minExponentDigits >= 0 && minExponentDigits < kMaxIntFracSig) {
ScientificSettings settings = fUnion.scientific;
settings.fMinExponentDigits = (int8_t) minExponentDigits;
NotationUnion union_ = {settings};
return {NTN_SCIENTIFIC, union_};
} else {
return {U_NUMBER_DIGIT_WIDTH_OUT_OF_RANGE_ERROR};
}
}
ScientificNotation
ScientificNotation::withExponentSignDisplay(UNumberSignDisplay exponentSignDisplay) const {
ScientificSettings settings = fUnion.scientific;
settings.fExponentSignDisplay = exponentSignDisplay;
NotationUnion union_ = {settings};
return {NTN_SCIENTIFIC, union_};
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,80 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#include "unicode/numberformatter.h"
#include "number_types.h"
#include "number_stringbuilder.h"
using namespace icu::number::impl;
namespace {
int32_t
addPaddingHelper(UChar32 paddingCp, int32_t requiredPadding, NumberStringBuilder &string, int32_t index,
UErrorCode &status) {
for (int32_t i = 0; i < requiredPadding; i++) {
// TODO: If appending to the end, this will cause actual insertion operations. Improve.
string.insertCodePoint(index, paddingCp, UNUM_FIELD_COUNT, status);
}
return U16_LENGTH(paddingCp) * requiredPadding;
}
}
Padder::Padder(UChar32 cp, int32_t width, UNumberFormatPadPosition position) : fWidth(width) {
fUnion.padding.fCp = cp;
fUnion.padding.fPosition = position;
}
Padder::Padder(int32_t width) : fWidth(width) {}
Padder Padder::none() {
return {-1};
}
Padder Padder::codePoints(UChar32 cp, int32_t targetWidth, UNumberFormatPadPosition position) {
// TODO: Validate the code point?
if (targetWidth >= 0) {
return {cp, targetWidth, position};
} else {
return {U_NUMBER_PADDING_WIDTH_OUT_OF_RANGE_ERROR};
}
}
int32_t Padder::padAndApply(const Modifier &mod1, const Modifier &mod2,
NumberStringBuilder &string, int32_t leftIndex, int32_t rightIndex,
UErrorCode &status) const {
int32_t modLength = mod1.getCodePointCount(status) + mod2.getCodePointCount(status);
int32_t requiredPadding = fWidth - modLength - string.codePointCount();
U_ASSERT(leftIndex == 0 &&
rightIndex == string.length()); // fix the previous line to remove this assertion
int length = 0;
if (requiredPadding <= 0) {
// Padding is not required.
length += mod1.apply(string, leftIndex, rightIndex, status);
length += mod2.apply(string, leftIndex, rightIndex + length, status);
return length;
}
PadPosition position = fUnion.padding.fPosition;
UChar32 paddingCp = fUnion.padding.fCp;
if (position == UNUM_PAD_AFTER_PREFIX) {
length += addPaddingHelper(paddingCp, requiredPadding, string, leftIndex, status);
} else if (position == UNUM_PAD_BEFORE_SUFFIX) {
length += addPaddingHelper(paddingCp, requiredPadding, string, rightIndex + length, status);
}
length += mod1.apply(string, leftIndex, rightIndex + length, status);
length += mod2.apply(string, leftIndex, rightIndex + length, status);
if (position == UNUM_PAD_BEFORE_PREFIX) {
length += addPaddingHelper(paddingCp, requiredPadding, string, leftIndex, status);
} else if (position == UNUM_PAD_AFTER_SUFFIX) {
length += addPaddingHelper(paddingCp, requiredPadding, string, rightIndex + length, status);
}
return length;
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,348 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#include "cstring.h"
#include "number_patternmodifier.h"
#include "unicode/dcfmtsym.h"
#include "unicode/ucurr.h"
#include "unicode/unistr.h"
using namespace icu;
using namespace icu::number::impl;
MutablePatternModifier::MutablePatternModifier(bool isStrong) : fStrong(isStrong) {}
void MutablePatternModifier::setPatternInfo(const AffixPatternProvider *patternInfo) {
this->patternInfo = patternInfo;
}
void MutablePatternModifier::setPatternAttributes(UNumberSignDisplay signDisplay, bool perMille) {
this->signDisplay = signDisplay;
this->perMilleReplacesPercent = perMille;
}
void
MutablePatternModifier::setSymbols(const DecimalFormatSymbols *symbols, const CurrencyUnit &currency,
const UNumberUnitWidth unitWidth, const PluralRules *rules) {
U_ASSERT((rules != nullptr) == needsPlurals());
this->symbols = symbols;
uprv_memcpy(static_cast<char16_t *>(this->currencyCode),
currency.getISOCurrency(),
sizeof(char16_t) * 4);
this->unitWidth = unitWidth;
this->rules = rules;
}
void MutablePatternModifier::setNumberProperties(bool isNegative, StandardPlural::Form plural) {
this->isNegative = isNegative;
this->plural = plural;
}
bool MutablePatternModifier::needsPlurals() const {
UErrorCode statusLocal = U_ZERO_ERROR;
return patternInfo->containsSymbolType(AffixPatternType::TYPE_CURRENCY_TRIPLE, statusLocal);
// Silently ignore any error codes.
}
ImmutablePatternModifier *MutablePatternModifier::createImmutable(UErrorCode &status) {
return createImmutableAndChain(nullptr, status);
}
ImmutablePatternModifier *
MutablePatternModifier::createImmutableAndChain(const MicroPropsGenerator *parent, UErrorCode &status) {
// TODO: Move StandardPlural VALUES to standardplural.h
static const StandardPlural::Form STANDARD_PLURAL_VALUES[] = {
StandardPlural::Form::ZERO,
StandardPlural::Form::ONE,
StandardPlural::Form::TWO,
StandardPlural::Form::FEW,
StandardPlural::Form::MANY,
StandardPlural::Form::OTHER};
auto pm = new ParameterizedModifier();
if (pm == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return nullptr;
}
if (needsPlurals()) {
// Slower path when we require the plural keyword.
for (StandardPlural::Form plural : STANDARD_PLURAL_VALUES) {
setNumberProperties(false, plural);
pm->adoptSignPluralModifier(false, plural, createConstantModifier(status));
setNumberProperties(true, plural);
pm->adoptSignPluralModifier(true, plural, createConstantModifier(status));
}
if (U_FAILURE(status)) {
delete pm;
return nullptr;
}
return new ImmutablePatternModifier(pm, rules, parent); // adopts pm
} else {
// Faster path when plural keyword is not needed.
setNumberProperties(false, StandardPlural::Form::COUNT);
Modifier *positive = createConstantModifier(status);
setNumberProperties(true, StandardPlural::Form::COUNT);
Modifier *negative = createConstantModifier(status);
pm->adoptPositiveNegativeModifiers(positive, negative);
if (U_FAILURE(status)) {
delete pm;
return nullptr;
}
return new ImmutablePatternModifier(pm, nullptr, parent); // adopts pm
}
}
ConstantMultiFieldModifier *MutablePatternModifier::createConstantModifier(UErrorCode &status) {
NumberStringBuilder a;
NumberStringBuilder b;
insertPrefix(a, 0, status);
insertSuffix(b, 0, status);
if (patternInfo->hasCurrencySign()) {
return new CurrencySpacingEnabledModifier(a, b, fStrong, *symbols, status);
} else {
return new ConstantMultiFieldModifier(a, b, fStrong);
}
}
ImmutablePatternModifier::ImmutablePatternModifier(ParameterizedModifier *pm, const PluralRules *rules,
const MicroPropsGenerator *parent)
: pm(pm), rules(rules), parent(parent) {}
void ImmutablePatternModifier::processQuantity(DecimalQuantity &quantity, MicroProps &micros,
UErrorCode &status) const {
parent->processQuantity(quantity, micros, status);
applyToMicros(micros, quantity);
}
void ImmutablePatternModifier::applyToMicros(MicroProps &micros, DecimalQuantity &quantity) const {
if (rules == nullptr) {
micros.modMiddle = pm->getModifier(quantity.isNegative());
} else {
// TODO: Fix this. Avoid the copy.
DecimalQuantity copy(quantity);
copy.roundToInfinity();
StandardPlural::Form plural = copy.getStandardPlural(rules);
micros.modMiddle = pm->getModifier(quantity.isNegative(), plural);
}
}
/** Used by the unsafe code path. */
MicroPropsGenerator &MutablePatternModifier::addToChain(const MicroPropsGenerator *parent) {
this->parent = parent;
return *this;
}
void MutablePatternModifier::processQuantity(DecimalQuantity &fq, MicroProps &micros,
UErrorCode &status) const {
parent->processQuantity(fq, micros, status);
// The unsafe code path performs self-mutation, so we need a const_cast.
// This method needs to be const because it overrides a const method in the parent class.
auto nonConstThis = const_cast<MutablePatternModifier *>(this);
if (needsPlurals()) {
// TODO: Fix this. Avoid the copy.
DecimalQuantity copy(fq);
micros.rounding.apply(copy, status);
nonConstThis->setNumberProperties(fq.isNegative(), copy.getStandardPlural(rules));
} else {
nonConstThis->setNumberProperties(fq.isNegative(), StandardPlural::Form::COUNT);
}
micros.modMiddle = this;
}
int32_t MutablePatternModifier::apply(NumberStringBuilder &output, int32_t leftIndex, int32_t rightIndex,
UErrorCode &status) const {
// The unsafe code path performs self-mutation, so we need a const_cast.
// This method needs to be const because it overrides a const method in the parent class.
auto nonConstThis = const_cast<MutablePatternModifier *>(this);
int32_t prefixLen = nonConstThis->insertPrefix(output, leftIndex, status);
int32_t suffixLen = nonConstThis->insertSuffix(output, rightIndex + prefixLen, status);
CurrencySpacingEnabledModifier::applyCurrencySpacing(
output, leftIndex, prefixLen, rightIndex + prefixLen, suffixLen, *symbols, status);
return prefixLen + suffixLen;
}
int32_t MutablePatternModifier::getPrefixLength(UErrorCode &status) const {
// The unsafe code path performs self-mutation, so we need a const_cast.
// This method needs to be const because it overrides a const method in the parent class.
auto nonConstThis = const_cast<MutablePatternModifier *>(this);
// Enter and exit CharSequence Mode to get the length.
nonConstThis->enterCharSequenceMode(true);
int result = AffixUtils::unescapedCodePointCount(*this, *this, status); // prefix length
nonConstThis->exitCharSequenceMode();
return result;
}
int32_t MutablePatternModifier::getCodePointCount(UErrorCode &status) const {
// The unsafe code path performs self-mutation, so we need a const_cast.
// This method needs to be const because it overrides a const method in the parent class.
auto nonConstThis = const_cast<MutablePatternModifier *>(this);
// Enter and exit CharSequence Mode to get the length.
nonConstThis->enterCharSequenceMode(true);
int result = AffixUtils::unescapedCodePointCount(*this, *this, status); // prefix length
nonConstThis->exitCharSequenceMode();
nonConstThis->enterCharSequenceMode(false);
result += AffixUtils::unescapedCodePointCount(*this, *this, status); // suffix length
nonConstThis->exitCharSequenceMode();
return result;
}
bool MutablePatternModifier::isStrong() const {
return fStrong;
}
int32_t MutablePatternModifier::insertPrefix(NumberStringBuilder &sb, int position, UErrorCode &status) {
enterCharSequenceMode(true);
int length = AffixUtils::unescape(*this, sb, position, *this, status);
exitCharSequenceMode();
return length;
}
int32_t MutablePatternModifier::insertSuffix(NumberStringBuilder &sb, int position, UErrorCode &status) {
enterCharSequenceMode(false);
int length = AffixUtils::unescape(*this, sb, position, *this, status);
exitCharSequenceMode();
return length;
}
UnicodeString MutablePatternModifier::getSymbol(AffixPatternType type) const {
switch (type) {
case AffixPatternType::TYPE_MINUS_SIGN:
return symbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kMinusSignSymbol);
case AffixPatternType::TYPE_PLUS_SIGN:
return symbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kPlusSignSymbol);
case AffixPatternType::TYPE_PERCENT:
return symbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kPercentSymbol);
case AffixPatternType::TYPE_PERMILLE:
return symbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kPerMillSymbol);
case AffixPatternType::TYPE_CURRENCY_SINGLE: {
// UnitWidth ISO and HIDDEN overrides the singular currency symbol.
if (unitWidth == UNumberUnitWidth::UNUM_UNIT_WIDTH_ISO_CODE) {
return UnicodeString(currencyCode, 3);
} else if (unitWidth == UNumberUnitWidth::UNUM_UNIT_WIDTH_HIDDEN) {
return UnicodeString();
} else {
UErrorCode status = U_ZERO_ERROR;
UBool isChoiceFormat = FALSE;
int32_t symbolLen = 0;
const char16_t *symbol = ucurr_getName(
currencyCode,
symbols->getLocale().getName(),
UCurrNameStyle::UCURR_SYMBOL_NAME,
&isChoiceFormat,
&symbolLen,
&status);
return UnicodeString(symbol, symbolLen);
}
}
case AffixPatternType::TYPE_CURRENCY_DOUBLE:
return UnicodeString(currencyCode, 3);
case AffixPatternType::TYPE_CURRENCY_TRIPLE: {
// NOTE: This is the code path only for patterns containing "¤¤¤".
// Plural currencies set via the API are formatted in LongNameHandler.
// This code path is used by DecimalFormat via CurrencyPluralInfo.
U_ASSERT(plural != StandardPlural::Form::COUNT);
UErrorCode status = U_ZERO_ERROR;
UBool isChoiceFormat = FALSE;
int32_t symbolLen = 0;
const char16_t *symbol = ucurr_getPluralName(
currencyCode,
symbols->getLocale().getName(),
&isChoiceFormat,
StandardPlural::getKeyword(plural),
&symbolLen,
&status);
return UnicodeString(symbol, symbolLen);
}
case AffixPatternType::TYPE_CURRENCY_QUAD:
return UnicodeString(u"\uFFFD");
case AffixPatternType::TYPE_CURRENCY_QUINT:
return UnicodeString(u"\uFFFD");
default:
U_ASSERT(false);
return UnicodeString();
}
}
/** This method contains the heart of the logic for rendering LDML affix strings. */
void MutablePatternModifier::enterCharSequenceMode(bool isPrefix) {
U_ASSERT(!inCharSequenceMode);
inCharSequenceMode = true;
// Should the output render '+' where '-' would normally appear in the pattern?
plusReplacesMinusSign = !isNegative && (
signDisplay == UNUM_SIGN_ALWAYS ||
signDisplay == UNUM_SIGN_ACCOUNTING_ALWAYS) &&
patternInfo->positiveHasPlusSign() == false;
// Should we use the affix from the negative subpattern? (If not, we will use the positive subpattern.)
bool useNegativeAffixPattern = patternInfo->hasNegativeSubpattern() && (
isNegative || (patternInfo->negativeHasMinusSign() && plusReplacesMinusSign));
// Resolve the flags for the affix pattern.
fFlags = 0;
if (useNegativeAffixPattern) {
fFlags |= AffixPatternProvider::AFFIX_NEGATIVE_SUBPATTERN;
}
if (isPrefix) {
fFlags |= AffixPatternProvider::AFFIX_PREFIX;
}
if (plural != StandardPlural::Form::COUNT) {
U_ASSERT(plural == (AffixPatternProvider::AFFIX_PLURAL_MASK & plural));
fFlags |= plural;
}
// Should we prepend a sign to the pattern?
if (!isPrefix || useNegativeAffixPattern) {
prependSign = false;
} else if (isNegative) {
prependSign = signDisplay != UNUM_SIGN_NEVER;
} else {
prependSign = plusReplacesMinusSign;
}
// Finally, compute the length of the affix pattern.
fLength = patternInfo->length(fFlags) + (prependSign ? 1 : 0);
}
void MutablePatternModifier::exitCharSequenceMode() {
U_ASSERT(inCharSequenceMode);
inCharSequenceMode = false;
}
int32_t MutablePatternModifier::length() const {
U_ASSERT(inCharSequenceMode);
return fLength;
}
char16_t MutablePatternModifier::charAt(int32_t index) const {
U_ASSERT(inCharSequenceMode);
char16_t candidate;
if (prependSign && index == 0) {
candidate = '-';
} else if (prependSign) {
candidate = patternInfo->charAt(fFlags, index - 1);
} else {
candidate = patternInfo->charAt(fFlags, index);
}
if (plusReplacesMinusSign && candidate == '-') {
return '+';
}
if (perMilleReplacesPercent && candidate == '%') {
return u'';
}
return candidate;
}
UnicodeString MutablePatternModifier::toUnicodeString() const {
// Never called by AffixUtils
U_ASSERT(false);
return UnicodeString();
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,237 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#ifndef __NUMBER_PATTERNMODIFIER_H__
#define __NUMBER_PATTERNMODIFIER_H__
#include "standardplural.h"
#include "unicode/numberformatter.h"
#include "number_patternstring.h"
#include "number_types.h"
#include "number_modifiers.h"
#include "number_utils.h"
U_NAMESPACE_BEGIN
namespace number {
namespace impl {
class U_I18N_API ImmutablePatternModifier : public MicroPropsGenerator {
public:
ImmutablePatternModifier(ParameterizedModifier *pm, const PluralRules *rules,
const MicroPropsGenerator *parent);
~ImmutablePatternModifier() override = default;
void processQuantity(DecimalQuantity &, MicroProps &micros, UErrorCode &status) const override;
void applyToMicros(MicroProps &micros, DecimalQuantity &quantity) const;
private:
const LocalPointer<ParameterizedModifier> pm;
const PluralRules *rules;
const MicroPropsGenerator *parent;
};
/**
* 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.
*
* <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.
*
* <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.
*/
class U_I18N_API MutablePatternModifier
: public MicroPropsGenerator, public Modifier, public SymbolProvider, public CharSequence {
public:
~MutablePatternModifier() override = default;
/**
* @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.
*/
explicit MutablePatternModifier(bool isStrong);
/**
* Sets a reference to the parsed decimal format pattern, usually obtained from
* {@link PatternStringParser#parseToPatternInfo(String)}, but any implementation of {@link AffixPatternProvider} is
* accepted.
*/
void setPatternInfo(const AffixPatternProvider *patternInfo);
/**
* Sets attributes that imply changes to the literal interpretation of the pattern string affixes.
*
* @param signDisplay
* Whether to force a plus sign on positive numbers.
* @param perMille
* Whether to substitute the percent sign in the pattern with a permille sign.
*/
void setPatternAttributes(UNumberSignDisplay signDisplay, bool perMille);
/**
* Sets locale-specific details that affect the symbols substituted into the pattern string affixes.
*
* @param symbols
* The desired instance of DecimalFormatSymbols.
* @param currency
* The currency to be used when substituting currency values into the affixes.
* @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()}.
*/
void
setSymbols(const DecimalFormatSymbols *symbols, const CurrencyUnit &currency, UNumberUnitWidth unitWidth,
const PluralRules *rules);
/**
* Sets attributes of the current number being processed.
*
* @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()}).
*/
void setNumberProperties(bool isNegative, StandardPlural::Form plural);
/**
* 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 ("¤¤¤").
*/
bool needsPlurals() const;
/**
* 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.
*
* <p>
* CREATES A NEW HEAP OBJECT; THE CALLER GETS OWNERSHIP.
*
* @return An immutable that supports both positive and negative numbers.
*/
ImmutablePatternModifier *createImmutable(UErrorCode &status);
/**
* 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>
* CREATES A NEW HEAP OBJECT; THE CALLER GETS OWNERSHIP.
*
* @param parent
* The QuantityChain to which to chain this immutable.
* @return An immutable that supports both positive and negative numbers.
*/
ImmutablePatternModifier *
createImmutableAndChain(const MicroPropsGenerator *parent, UErrorCode &status);
MicroPropsGenerator &addToChain(const MicroPropsGenerator *parent);
void processQuantity(DecimalQuantity &, MicroProps &micros, UErrorCode &status) const override;
int32_t apply(NumberStringBuilder &output, int32_t leftIndex, int32_t rightIndex,
UErrorCode &status) const override;
int32_t getPrefixLength(UErrorCode &status) const override;
int32_t getCodePointCount(UErrorCode &status) const override;
bool isStrong() const override;
/**
* Returns the string that substitutes a given symbol type in a pattern.
*/
UnicodeString getSymbol(AffixPatternType type) const override;
int32_t length() const override;
char16_t charAt(int32_t index) const override;
// Use default implementation of codePointAt
UnicodeString toUnicodeString() const override;
private:
// Modifier details
const bool fStrong;
// Pattern details
const AffixPatternProvider *patternInfo;
UNumberSignDisplay signDisplay;
bool perMilleReplacesPercent;
// Symbol details
const DecimalFormatSymbols *symbols;
UNumberUnitWidth unitWidth;
char16_t currencyCode[4];
const PluralRules *rules;
// Number details
bool isNegative;
StandardPlural::Form plural;
// QuantityChain details
const MicroPropsGenerator *parent;
// Transient CharSequence fields
bool inCharSequenceMode = false;
int32_t fFlags;
int32_t fLength;
bool prependSign;
bool plusReplacesMinusSign;
/**
* Uses the current properties to create a single {@link ConstantMultiFieldModifier} with currency spacing support
* if required.
*
* <p>
* CREATES A NEW HEAP OBJECT; THE CALLER GETS OWNERSHIP.
*
* @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.
* @param b
* Another working NumberStringBuilder object.
* @return The constant modifier object.
*/
ConstantMultiFieldModifier *createConstantModifier(UErrorCode &status);
int32_t insertPrefix(NumberStringBuilder &sb, int position, UErrorCode &status);
int32_t insertSuffix(NumberStringBuilder &sb, int position, UErrorCode &status);
void enterCharSequenceMode(bool isPrefix);
void exitCharSequenceMode();
};
} // namespace impl
} // namespace number
U_NAMESPACE_END
#endif //__NUMBER_PATTERNMODIFIER_H__
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,836 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#include "uassert.h"
#include "number_patternstring.h"
#include "unicode/utf16.h"
#include "number_utils.h"
using namespace icu;
using namespace icu::number::impl;
void PatternParser::parseToPatternInfo(const UnicodeString& patternString, ParsedPatternInfo& patternInfo, UErrorCode &status) {
patternInfo.consumePattern(patternString, status);
}
DecimalFormatProperties
PatternParser::parseToProperties(const UnicodeString& pattern, IgnoreRounding ignoreRounding,
UErrorCode &status) {
DecimalFormatProperties properties;
parseToExistingPropertiesImpl(pattern, properties, ignoreRounding, status);
return properties;
}
void PatternParser::parseToExistingProperties(const UnicodeString& pattern, DecimalFormatProperties properties,
IgnoreRounding ignoreRounding, UErrorCode &status) {
parseToExistingPropertiesImpl(pattern, properties, ignoreRounding, status);
}
char16_t ParsedPatternInfo::charAt(int32_t flags, int32_t index) const {
const Endpoints &endpoints = getEndpoints(flags);
if (index < 0 || index >= endpoints.end - endpoints.start) {
U_ASSERT(false);
}
return pattern.charAt(endpoints.start + index);
}
int32_t ParsedPatternInfo::length(int32_t flags) const {
return getLengthFromEndpoints(getEndpoints(flags));
}
int32_t ParsedPatternInfo::getLengthFromEndpoints(const Endpoints &endpoints) {
return endpoints.end - endpoints.start;
}
UnicodeString ParsedPatternInfo::getString(int32_t flags) const {
const Endpoints &endpoints = getEndpoints(flags);
if (endpoints.start == endpoints.end) {
return UnicodeString();
}
// Create a new UnicodeString
return UnicodeString(pattern, endpoints.start, endpoints.end - endpoints.start);
}
const Endpoints &ParsedPatternInfo::getEndpoints(int32_t flags) const {
bool prefix = (flags & AFFIX_PREFIX) != 0;
bool isNegative = (flags & AFFIX_NEGATIVE_SUBPATTERN) != 0;
bool padding = (flags & AFFIX_PADDING) != 0;
if (isNegative && padding) {
return negative.paddingEndpoints;
} else if (padding) {
return positive.paddingEndpoints;
} else if (prefix && isNegative) {
return negative.prefixEndpoints;
} else if (prefix) {
return positive.prefixEndpoints;
} else if (isNegative) {
return negative.suffixEndpoints;
} else {
return positive.suffixEndpoints;
}
}
bool ParsedPatternInfo::positiveHasPlusSign() const {
return positive.hasPlusSign;
}
bool ParsedPatternInfo::hasNegativeSubpattern() const {
return fHasNegativeSubpattern;
}
bool ParsedPatternInfo::negativeHasMinusSign() const {
return negative.hasMinusSign;
}
bool ParsedPatternInfo::hasCurrencySign() const {
return positive.hasCurrencySign || (fHasNegativeSubpattern && negative.hasCurrencySign);
}
bool ParsedPatternInfo::containsSymbolType(AffixPatternType type, UErrorCode &status) const {
return AffixUtils::containsType(UnicodeStringCharSequence(pattern), type, status);
}
/////////////////////////////////////////////////////
/// BEGIN RECURSIVE DESCENT PARSER IMPLEMENTATION ///
/////////////////////////////////////////////////////
UChar32 ParsedPatternInfo::ParserState::peek() {
if (offset == pattern.length()) {
return -1;
} else {
return pattern.char32At(offset);
}
}
UChar32 ParsedPatternInfo::ParserState::next() {
int codePoint = peek();
offset += U16_LENGTH(codePoint);
return codePoint;
}
void ParsedPatternInfo::consumePattern(const UnicodeString& patternString, UErrorCode &status) {
if (U_FAILURE(status)) { return; }
this->pattern = patternString;
// pattern := subpattern (';' subpattern)?
currentSubpattern = &positive;
consumeSubpattern(status);
if (U_FAILURE(status)) { return; }
if (state.peek() == ';') {
state.next(); // consume the ';'
// Don't consume the negative subpattern if it is empty (trailing ';')
if (state.peek() != -1) {
fHasNegativeSubpattern = true;
currentSubpattern = &negative;
consumeSubpattern(status);
if (U_FAILURE(status)) { return; }
}
}
if (state.peek() != -1) {
state.toParseException(u"Found unquoted special character");
status = U_UNQUOTED_SPECIAL;
}
}
void ParsedPatternInfo::consumeSubpattern(UErrorCode &status) {
// subpattern := literals? number exponent? literals?
consumePadding(PadPosition::UNUM_PAD_BEFORE_PREFIX, status);
if (U_FAILURE(status)) { return; }
consumeAffix(currentSubpattern->prefixEndpoints, status);
if (U_FAILURE(status)) { return; }
consumePadding(PadPosition::UNUM_PAD_AFTER_PREFIX, status);
if (U_FAILURE(status)) { return; }
consumeFormat(status);
if (U_FAILURE(status)) { return; }
consumeExponent(status);
if (U_FAILURE(status)) { return; }
consumePadding(PadPosition::UNUM_PAD_BEFORE_SUFFIX, status);
if (U_FAILURE(status)) { return; }
consumeAffix(currentSubpattern->suffixEndpoints, status);
if (U_FAILURE(status)) { return; }
consumePadding(PadPosition::UNUM_PAD_AFTER_SUFFIX, status);
if (U_FAILURE(status)) { return; }
}
void ParsedPatternInfo::consumePadding(PadPosition paddingLocation, UErrorCode &status) {
if (state.peek() != '*') {
return;
}
if (!currentSubpattern->paddingLocation.isNull()) {
state.toParseException(u"Cannot have multiple pad specifiers");
status = U_MULTIPLE_PAD_SPECIFIERS;
return;
}
currentSubpattern->paddingLocation = paddingLocation;
state.next(); // consume the '*'
currentSubpattern->paddingEndpoints.start = state.offset;
consumeLiteral(status);
currentSubpattern->paddingEndpoints.end = state.offset;
}
void ParsedPatternInfo::consumeAffix(Endpoints &endpoints, UErrorCode &status) {
// literals := { literal }
endpoints.start = state.offset;
while (true) {
switch (state.peek()) {
case '#':
case '@':
case ';':
case '*':
case '.':
case ',':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case -1:
// Characters that cannot appear unquoted in a literal
// break outer;
goto after_outer;
case '%':
currentSubpattern->hasPercentSign = true;
break;
case u'':
currentSubpattern->hasPerMilleSign = true;
break;
case u'¤':
currentSubpattern->hasCurrencySign = true;
break;
case '-':
currentSubpattern->hasMinusSign = true;
break;
case '+':
currentSubpattern->hasPlusSign = true;
break;
default:
break;
}
consumeLiteral(status);
if (U_FAILURE(status)) { return; }
}
after_outer:
endpoints.end = state.offset;
}
void ParsedPatternInfo::consumeLiteral(UErrorCode &status) {
if (state.peek() == -1) {
state.toParseException(u"Expected unquoted literal but found EOL");
status = U_PATTERN_SYNTAX_ERROR;
return;
} else if (state.peek() == '\'') {
state.next(); // consume the starting quote
while (state.peek() != '\'') {
if (state.peek() == -1) {
state.toParseException(u"Expected quoted literal but found EOL");
status = U_PATTERN_SYNTAX_ERROR;
return;
} else {
state.next(); // consume a quoted character
}
}
state.next(); // consume the ending quote
} else {
// consume a non-quoted literal character
state.next();
}
}
void ParsedPatternInfo::consumeFormat(UErrorCode &status) {
consumeIntegerFormat(status);
if (U_FAILURE(status)) { return; }
if (state.peek() == '.') {
state.next(); // consume the decimal point
currentSubpattern->hasDecimal = true;
currentSubpattern->widthExceptAffixes += 1;
consumeFractionFormat(status);
if (U_FAILURE(status)) { return; }
}
}
void ParsedPatternInfo::consumeIntegerFormat(UErrorCode &status) {
// Convenience reference:
ParsedSubpatternInfo &result = *currentSubpattern;
while (true) {
switch (state.peek()) {
case ',':
result.widthExceptAffixes += 1;
result.groupingSizes <<= 16;
break;
case '#':
if (result.integerNumerals > 0) {
state.toParseException(u"# cannot follow 0 before decimal point");
status = U_UNEXPECTED_TOKEN;
return;
}
result.widthExceptAffixes += 1;
result.groupingSizes += 1;
if (result.integerAtSigns > 0) {
result.integerTrailingHashSigns += 1;
} else {
result.integerLeadingHashSigns += 1;
}
result.integerTotal += 1;
break;
case '@':
if (result.integerNumerals > 0) {
state.toParseException(u"Cannot mix 0 and @");
status = U_UNEXPECTED_TOKEN;
return;
}
if (result.integerTrailingHashSigns > 0) {
state.toParseException(u"Cannot nest # inside of a run of @");
status = U_UNEXPECTED_TOKEN;
return;
}
result.widthExceptAffixes += 1;
result.groupingSizes += 1;
result.integerAtSigns += 1;
result.integerTotal += 1;
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
if (result.integerAtSigns > 0) {
state.toParseException(u"Cannot mix @ and 0");
status = U_UNEXPECTED_TOKEN;
return;
}
result.widthExceptAffixes += 1;
result.groupingSizes += 1;
result.integerNumerals += 1;
result.integerTotal += 1;
if (!result.rounding.isZero() || state.peek() != '0') {
result.rounding.appendDigit(static_cast<int8_t>(state.peek() - '0'), 0, true);
}
break;
default:
goto after_outer;
}
state.next(); // consume the symbol
}
after_outer:
// Disallow patterns with a trailing ',' or with two ',' next to each other
auto grouping1 = static_cast<int16_t> (result.groupingSizes & 0xffff);
auto grouping2 = static_cast<int16_t> ((result.groupingSizes >> 16) & 0xffff);
auto grouping3 = static_cast<int16_t> ((result.groupingSizes >> 32) & 0xffff);
if (grouping1 == 0 && grouping2 != -1) {
state.toParseException(u"Trailing grouping separator is invalid");
status = U_UNEXPECTED_TOKEN;
return;
}
if (grouping2 == 0 && grouping3 != -1) {
state.toParseException(u"Grouping width of zero is invalid");
status = U_PATTERN_SYNTAX_ERROR;
return;
}
}
void ParsedPatternInfo::consumeFractionFormat(UErrorCode &status) {
// Convenience reference:
ParsedSubpatternInfo &result = *currentSubpattern;
int32_t zeroCounter = 0;
while (true) {
switch (state.peek()) {
case '#':
result.widthExceptAffixes += 1;
result.fractionHashSigns += 1;
result.fractionTotal += 1;
zeroCounter++;
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
if (result.fractionHashSigns > 0) {
state.toParseException(u"0 cannot follow # after decimal point");
status = U_UNEXPECTED_TOKEN;
return;
}
result.widthExceptAffixes += 1;
result.fractionNumerals += 1;
result.fractionTotal += 1;
if (state.peek() == '0') {
zeroCounter++;
} else {
result.rounding
.appendDigit(static_cast<int8_t>(state.peek() - '0'), zeroCounter, false);
zeroCounter = 0;
}
break;
default:
return;
}
state.next(); // consume the symbol
}
}
void ParsedPatternInfo::consumeExponent(UErrorCode &status) {
// Convenience reference:
ParsedSubpatternInfo &result = *currentSubpattern;
if (state.peek() != 'E') {
return;
}
if ((result.groupingSizes & 0xffff0000L) != 0xffff0000L) {
state.toParseException(u"Cannot have grouping separator in scientific notation");
status = U_MALFORMED_EXPONENTIAL_PATTERN;
return;
}
state.next(); // consume the E
result.widthExceptAffixes++;
if (state.peek() == '+') {
state.next(); // consume the +
result.exponentHasPlusSign = true;
result.widthExceptAffixes++;
}
while (state.peek() == '0') {
state.next(); // consume the 0
result.exponentZeros += 1;
result.widthExceptAffixes++;
}
}
///////////////////////////////////////////////////
/// END RECURSIVE DESCENT PARSER IMPLEMENTATION ///
///////////////////////////////////////////////////
void
PatternParser::parseToExistingPropertiesImpl(const UnicodeString& pattern, DecimalFormatProperties &properties,
IgnoreRounding ignoreRounding, UErrorCode &status) {
if (pattern.length() == 0) {
// Backwards compatibility requires that we reset to the default values.
// TODO: Only overwrite the properties that "saveToProperties" normally touches?
properties.clear();
return;
}
ParsedPatternInfo patternInfo;
parseToPatternInfo(pattern, patternInfo, status);
if (U_FAILURE(status)) { return; }
patternInfoToProperties(properties, patternInfo, ignoreRounding, status);
}
void PatternParser::patternInfoToProperties(DecimalFormatProperties &properties,
ParsedPatternInfo patternInfo,
IgnoreRounding _ignoreRounding, UErrorCode &status) {
// Translate from PatternParseResult to Properties.
// Note that most data from "negative" is ignored per the specification of DecimalFormat.
const ParsedSubpatternInfo &positive = patternInfo.positive;
bool ignoreRounding;
if (_ignoreRounding == IGNORE_ROUNDING_NEVER) {
ignoreRounding = false;
} else if (_ignoreRounding == IGNORE_ROUNDING_IF_CURRENCY) {
ignoreRounding = positive.hasCurrencySign;
} else {
U_ASSERT(_ignoreRounding == IGNORE_ROUNDING_ALWAYS);
ignoreRounding = true;
}
// Grouping settings
auto grouping1 = static_cast<int16_t> (positive.groupingSizes & 0xffff);
auto grouping2 = static_cast<int16_t> ((positive.groupingSizes >> 16) & 0xffff);
auto grouping3 = static_cast<int16_t> ((positive.groupingSizes >> 32) & 0xffff);
if (grouping2 != -1) {
properties.groupingSize = grouping1;
} else {
properties.groupingSize = -1;
}
if (grouping3 != -1) {
properties.secondaryGroupingSize = grouping2;
} else {
properties.secondaryGroupingSize = -1;
}
// For backwards compatibility, require that the pattern emit at least one min digit.
int minInt, minFrac;
if (positive.integerTotal == 0 && positive.fractionTotal > 0) {
// patterns like ".##"
minInt = 0;
minFrac = uprv_max(1, positive.fractionNumerals);
} else if (positive.integerNumerals == 0 && positive.fractionNumerals == 0) {
// patterns like "#.##"
minInt = 1;
minFrac = 0;
} else {
minInt = positive.integerNumerals;
minFrac = positive.fractionNumerals;
}
// Rounding settings
// Don't set basic rounding when there is a currency sign; defer to CurrencyUsage
if (positive.integerAtSigns > 0) {
properties.minimumFractionDigits = -1;
properties.maximumFractionDigits = -1;
properties.roundingIncrement = 0.0;
properties.minimumSignificantDigits = positive.integerAtSigns;
properties.maximumSignificantDigits =
positive.integerAtSigns + positive.integerTrailingHashSigns;
} else if (!positive.rounding.isZero()) {
if (!ignoreRounding) {
properties.minimumFractionDigits = minFrac;
properties.maximumFractionDigits = positive.fractionTotal;
properties.roundingIncrement = positive.rounding.toDouble();
} else {
properties.minimumFractionDigits = -1;
properties.maximumFractionDigits = -1;
properties.roundingIncrement = 0.0;
}
properties.minimumSignificantDigits = -1;
properties.maximumSignificantDigits = -1;
} else {
if (!ignoreRounding) {
properties.minimumFractionDigits = minFrac;
properties.maximumFractionDigits = positive.fractionTotal;
properties.roundingIncrement = 0.0;
} else {
properties.minimumFractionDigits = -1;
properties.maximumFractionDigits = -1;
properties.roundingIncrement = 0.0;
}
properties.minimumSignificantDigits = -1;
properties.maximumSignificantDigits = -1;
}
// If the pattern ends with a '.' then force the decimal point.
if (positive.hasDecimal && positive.fractionTotal == 0) {
properties.decimalSeparatorAlwaysShown = true;
} else {
properties.decimalSeparatorAlwaysShown = false;
}
// Scientific notation settings
if (positive.exponentZeros > 0) {
properties.exponentSignAlwaysShown = positive.exponentHasPlusSign;
properties.minimumExponentDigits = positive.exponentZeros;
if (positive.integerAtSigns == 0) {
// patterns without '@' can define max integer digits, used for engineering notation
properties.minimumIntegerDigits = positive.integerNumerals;
properties.maximumIntegerDigits = positive.integerTotal;
} else {
// patterns with '@' cannot define max integer digits
properties.minimumIntegerDigits = 1;
properties.maximumIntegerDigits = -1;
}
} else {
properties.exponentSignAlwaysShown = false;
properties.minimumExponentDigits = -1;
properties.minimumIntegerDigits = minInt;
properties.maximumIntegerDigits = -1;
}
// Compute the affix patterns (required for both padding and affixes)
UnicodeString posPrefix = patternInfo.getString(AffixPatternProvider::AFFIX_PREFIX);
UnicodeString posSuffix = patternInfo.getString(0);
// Padding settings
if (!positive.paddingLocation.isNull()) {
// The width of the positive prefix and suffix templates are included in the padding
int paddingWidth =
positive.widthExceptAffixes + AffixUtils::estimateLength(UnicodeStringCharSequence(posPrefix), status) +
AffixUtils::estimateLength(UnicodeStringCharSequence(posSuffix), status);
properties.formatWidth = paddingWidth;
UnicodeString rawPaddingString = patternInfo.getString(AffixPatternProvider::AFFIX_PADDING);
if (rawPaddingString.length() == 1) {
properties.padString = rawPaddingString;
} else if (rawPaddingString.length() == 2) {
if (rawPaddingString.charAt(0) == '\'') {
properties.padString.setTo(u"'", -1);
} else {
properties.padString = rawPaddingString;
}
} else {
properties.padString = UnicodeString(rawPaddingString, 1, rawPaddingString.length() - 2);
}
properties.padPosition = positive.paddingLocation;
} else {
properties.formatWidth = -1;
properties.padString.setToBogus();
properties.padPosition.nullify();
}
// Set the affixes
// Always call the setter, even if the prefixes are empty, especially in the case of the
// negative prefix pattern, to prevent default values from overriding the pattern.
properties.positivePrefixPattern = posPrefix;
properties.positiveSuffixPattern = posSuffix;
if (patternInfo.fHasNegativeSubpattern) {
properties.negativePrefixPattern = patternInfo.getString(
AffixPatternProvider::AFFIX_NEGATIVE_SUBPATTERN | AffixPatternProvider::AFFIX_PREFIX);
properties.negativeSuffixPattern = patternInfo.getString(
AffixPatternProvider::AFFIX_NEGATIVE_SUBPATTERN);
} else {
properties.negativePrefixPattern.setToBogus();
properties.negativeSuffixPattern.setToBogus();
}
// Set the magnitude multiplier
if (positive.hasPercentSign) {
properties.magnitudeMultiplier = 2;
} else if (positive.hasPerMilleSign) {
properties.magnitudeMultiplier = 3;
} else {
properties.magnitudeMultiplier = 0;
}
}
///////////////////////////////////////////////////////////////////
/// End PatternStringParser.java; begin PatternStringUtils.java ///
///////////////////////////////////////////////////////////////////
UnicodeString PatternStringUtils::propertiesToPatternString(const DecimalFormatProperties &properties,
UErrorCode &status) {
UnicodeString sb;
// Convenience references
// The uprv_min() calls prevent DoS
int dosMax = 100;
int groupingSize = uprv_min(properties.secondaryGroupingSize, dosMax);
int firstGroupingSize = uprv_min(properties.groupingSize, dosMax);
int paddingWidth = uprv_min(properties.formatWidth, dosMax);
NullableValue<PadPosition> paddingLocation = properties.padPosition;
UnicodeString paddingString = properties.padString;
int minInt = uprv_max(uprv_min(properties.minimumIntegerDigits, dosMax), 0);
int maxInt = uprv_min(properties.maximumIntegerDigits, dosMax);
int minFrac = uprv_max(uprv_min(properties.minimumFractionDigits, dosMax), 0);
int maxFrac = uprv_min(properties.maximumFractionDigits, dosMax);
int minSig = uprv_min(properties.minimumSignificantDigits, dosMax);
int maxSig = uprv_min(properties.maximumSignificantDigits, dosMax);
bool alwaysShowDecimal = properties.decimalSeparatorAlwaysShown;
int exponentDigits = uprv_min(properties.minimumExponentDigits, dosMax);
bool exponentShowPlusSign = properties.exponentSignAlwaysShown;
UnicodeString pp = properties.positivePrefix;
UnicodeString ppp = properties.positivePrefixPattern;
UnicodeString ps = properties.positiveSuffix;
UnicodeString psp = properties.positiveSuffixPattern;
UnicodeString np = properties.negativePrefix;
UnicodeString npp = properties.negativePrefixPattern;
UnicodeString ns = properties.negativeSuffix;
UnicodeString nsp = properties.negativeSuffixPattern;
// Prefixes
if (!ppp.isBogus()) {
sb.append(ppp);
}
sb.append(AffixUtils::escape(UnicodeStringCharSequence(pp)));
int afterPrefixPos = sb.length();
// Figure out the grouping sizes.
int grouping1, grouping2, grouping;
if (groupingSize != uprv_min(dosMax, -1) && firstGroupingSize != uprv_min(dosMax, -1) &&
groupingSize != firstGroupingSize) {
grouping = groupingSize;
grouping1 = groupingSize;
grouping2 = firstGroupingSize;
} else if (groupingSize != uprv_min(dosMax, -1)) {
grouping = groupingSize;
grouping1 = 0;
grouping2 = groupingSize;
} else if (firstGroupingSize != uprv_min(dosMax, -1)) {
grouping = groupingSize;
grouping1 = 0;
grouping2 = firstGroupingSize;
} else {
grouping = 0;
grouping1 = 0;
grouping2 = 0;
}
int groupingLength = grouping1 + grouping2 + 1;
// Figure out the digits we need to put in the pattern.
double roundingInterval = properties.roundingIncrement;
UnicodeString digitsString;
int digitsStringScale = 0;
if (maxSig != uprv_min(dosMax, -1)) {
// Significant Digits.
while (digitsString.length() < minSig) {
digitsString.append('@');
}
while (digitsString.length() < maxSig) {
digitsString.append('#');
}
} else if (roundingInterval != 0.0) {
// Rounding Interval.
digitsStringScale = minFrac;
// TODO: Check for DoS here?
DecimalQuantity incrementQuantity;
incrementQuantity.setToDouble(roundingInterval);
incrementQuantity.adjustMagnitude(minFrac);
incrementQuantity.roundToMagnitude(0, kDefaultMode, status);
UnicodeString str = incrementQuantity.toPlainString();
if (str.charAt(0) == '-') {
// TODO: Unsupported operation exception or fail silently?
digitsString.append(str, 1, str.length() - 1);
} else {
digitsString.append(str);
}
}
while (digitsString.length() + digitsStringScale < minInt) {
digitsString.insert(0, '0');
}
while (-digitsStringScale < minFrac) {
digitsString.append('0');
digitsStringScale--;
}
// Write the digits to the string builder
int m0 = uprv_max(groupingLength, digitsString.length() + digitsStringScale);
m0 = (maxInt != dosMax) ? uprv_max(maxInt, m0) - 1 : m0 - 1;
int mN = (maxFrac != dosMax) ? uprv_min(-maxFrac, digitsStringScale) : digitsStringScale;
for (int magnitude = m0; magnitude >= mN; magnitude--) {
int di = digitsString.length() + digitsStringScale - magnitude - 1;
if (di < 0 || di >= digitsString.length()) {
sb.append('#');
} else {
sb.append(digitsString.charAt(di));
}
if (magnitude > grouping2 && grouping > 0 && (magnitude - grouping2) % grouping == 0) {
sb.append(',');
} else if (magnitude > 0 && magnitude == grouping2) {
sb.append(',');
} else if (magnitude == 0 && (alwaysShowDecimal || mN < 0)) {
sb.append('.');
}
}
// Exponential notation
if (exponentDigits != uprv_min(dosMax, -1)) {
sb.append('E');
if (exponentShowPlusSign) {
sb.append('+');
}
for (int i = 0; i < exponentDigits; i++) {
sb.append('0');
}
}
// Suffixes
int beforeSuffixPos = sb.length();
if (!psp.isBogus()) {
sb.append(psp);
}
sb.append(AffixUtils::escape(UnicodeStringCharSequence(ps)));
// Resolve Padding
if (paddingWidth != -1 && !paddingLocation.isNull()) {
while (paddingWidth - sb.length() > 0) {
sb.insert(afterPrefixPos, '#');
beforeSuffixPos++;
}
int addedLength;
switch (paddingLocation.get(status)) {
case PadPosition::UNUM_PAD_BEFORE_PREFIX:
addedLength = escapePaddingString(paddingString, sb, 0, status);
sb.insert(0, '*');
afterPrefixPos += addedLength + 1;
beforeSuffixPos += addedLength + 1;
break;
case PadPosition::UNUM_PAD_AFTER_PREFIX:
addedLength = escapePaddingString(paddingString, sb, afterPrefixPos, status);
sb.insert(afterPrefixPos, '*');
afterPrefixPos += addedLength + 1;
beforeSuffixPos += addedLength + 1;
break;
case PadPosition::UNUM_PAD_BEFORE_SUFFIX:
escapePaddingString(paddingString, sb, beforeSuffixPos, status);
sb.insert(beforeSuffixPos, '*');
break;
case PadPosition::UNUM_PAD_AFTER_SUFFIX:
sb.append('*');
escapePaddingString(paddingString, sb, sb.length(), status);
break;
}
if (U_FAILURE(status)) { return sb; }
}
// Negative affixes
// Ignore if the negative prefix pattern is "-" and the negative suffix is empty
if (!np.isBogus() || !ns.isBogus() || (npp.isBogus() && !nsp.isBogus()) ||
(!npp.isBogus() && (npp.length() != 1 || npp.charAt(0) != '-' || nsp.length() != 0))) {
sb.append(';');
if (!npp.isBogus()) {
sb.append(npp);
}
sb.append(AffixUtils::escape(UnicodeStringCharSequence(np)));
// Copy the positive digit format into the negative.
// This is optional; the pattern is the same as if '#' were appended here instead.
sb.append(sb, afterPrefixPos, beforeSuffixPos);
if (!nsp.isBogus()) {
sb.append(nsp);
}
sb.append(AffixUtils::escape(UnicodeStringCharSequence(ns)));
}
return sb;
}
int PatternStringUtils::escapePaddingString(UnicodeString input, UnicodeString& output, int startIndex,
UErrorCode &status) {
(void)status;
if (input.length() == 0) {
input.setTo(kFallbackPaddingString, -1);
}
int startLength = output.length();
if (input.length() == 1) {
if (input.compare(u"'", -1) == 0) {
output.insert(startIndex, u"''", -1);
} else {
output.insert(startIndex, input);
}
} else {
output.insert(startIndex, '\'');
int offset = 1;
for (int i = 0; i < input.length(); i++) {
// it's okay to deal in chars here because the quote mark is the only interesting thing.
char16_t ch = input.charAt(i);
if (ch == '\'') {
output.insert(startIndex + offset, u"''", -1);
offset += 2;
} else {
output.insert(startIndex + offset, ch);
offset += 1;
}
}
output.insert(startIndex + offset, '\'');
}
return output.length() - startLength;
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,260 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#ifndef __NUMBER_PATTERNSTRING_H__
#define __NUMBER_PATTERNSTRING_H__
#include <cstdint>
#include "unicode/unum.h"
#include "unicode/unistr.h"
#include "number_types.h"
#include "number_decimalquantity.h"
#include "number_decimfmtprops.h"
#include "number_affixutils.h"
U_NAMESPACE_BEGIN namespace number {
namespace impl {
// Forward declaration
class PatternParser;
struct Endpoints {
int32_t start = 0;
int32_t end = 0;
};
struct ParsedSubpatternInfo {
int64_t groupingSizes = 0x0000ffffffff0000L;
int32_t integerLeadingHashSigns = 0;
int32_t integerTrailingHashSigns = 0;
int32_t integerNumerals = 0;
int32_t integerAtSigns = 0;
int32_t integerTotal = 0; // for convenience
int32_t fractionNumerals = 0;
int32_t fractionHashSigns = 0;
int32_t fractionTotal = 0; // for convenience
bool hasDecimal = false;
int32_t widthExceptAffixes = 0;
NullableValue<UNumberFormatPadPosition> paddingLocation;
DecimalQuantity rounding;
bool exponentHasPlusSign = false;
int32_t exponentZeros = 0;
bool hasPercentSign = false;
bool hasPerMilleSign = false;
bool hasCurrencySign = false;
bool hasMinusSign = false;
bool hasPlusSign = false;
Endpoints prefixEndpoints;
Endpoints suffixEndpoints;
Endpoints paddingEndpoints;
};
struct U_I18N_API ParsedPatternInfo : public AffixPatternProvider {
UnicodeString pattern;
ParsedSubpatternInfo positive;
ParsedSubpatternInfo negative;
ParsedPatternInfo() : state(this->pattern), currentSubpattern(nullptr) {}
~ParsedPatternInfo() override = default;
static int32_t getLengthFromEndpoints(const Endpoints &endpoints);
char16_t charAt(int32_t flags, int32_t index) const override;
int32_t length(int32_t flags) const override;
UnicodeString getString(int32_t flags) const;
bool positiveHasPlusSign() const override;
bool hasNegativeSubpattern() const override;
bool negativeHasMinusSign() const override;
bool hasCurrencySign() const override;
bool containsSymbolType(AffixPatternType type, UErrorCode &status) const override;
private:
struct ParserState {
const UnicodeString &pattern; // reference to the parent
int32_t offset = 0;
explicit ParserState(const UnicodeString &_pattern) : pattern(_pattern) {};
UChar32 peek();
UChar32 next();
// TODO: We don't currently do anything with the message string.
// This method is here as a shell for Java compatibility.
inline void toParseException(const char16_t *message) { (void)message; }
} state;
// NOTE: In Java, these are written as pure functions.
// In C++, they're written as methods.
// The behavior is the same.
// Mutable transient pointer:
ParsedSubpatternInfo *currentSubpattern;
// In Java, "negative == null" tells us whether or not we had a negative subpattern.
// In C++, we need to remember in another boolean.
bool fHasNegativeSubpattern = false;
const Endpoints &getEndpoints(int32_t flags) const;
/** Run the recursive descent parser. */
void consumePattern(const UnicodeString &patternString, UErrorCode &status);
void consumeSubpattern(UErrorCode &status);
void consumePadding(PadPosition paddingLocation, UErrorCode &status);
void consumeAffix(Endpoints &endpoints, UErrorCode &status);
void consumeLiteral(UErrorCode &status);
void consumeFormat(UErrorCode &status);
void consumeIntegerFormat(UErrorCode &status);
void consumeFractionFormat(UErrorCode &status);
void consumeExponent(UErrorCode &status);
friend class PatternParser;
};
class U_I18N_API PatternParser {
public:
/**
* 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.
*
* TODO: Change argument type to const char16_t* instead of UnicodeString?
*
* @param patternString
* The LDML decimal format pattern (Excel-style pattern) to parse.
* @return The results of the parse.
*/
static void
parseToPatternInfo(const UnicodeString& patternString, ParsedPatternInfo &patternInfo, UErrorCode &status);
enum IgnoreRounding {
IGNORE_ROUNDING_NEVER = 0, IGNORE_ROUNDING_IF_CURRENCY = 1, IGNORE_ROUNDING_ALWAYS = 2
};
/**
* Parses a pattern string into a new property bag.
*
* @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.
* @return A property bag object.
* @throws IllegalArgumentException
* If there is a syntax error in the pattern string.
*/
static DecimalFormatProperties
parseToProperties(const UnicodeString& pattern, IgnoreRounding ignoreRounding, UErrorCode &status);
/**
* 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"
* @param properties
* The property bag object to overwrite.
* @param ignoreRounding
* See {@link #parseToProperties(String pattern, int ignoreRounding)}.
* @throws IllegalArgumentException
* If there was a syntax error in the pattern string.
*/
static void parseToExistingProperties(const UnicodeString& pattern, DecimalFormatProperties properties,
IgnoreRounding ignoreRounding, UErrorCode &status);
private:
static void
parseToExistingPropertiesImpl(const UnicodeString& pattern, DecimalFormatProperties &properties,
IgnoreRounding ignoreRounding, UErrorCode &status);
/** Finalizes the temporary data stored in the ParsedPatternInfo to the Properties. */
static void
patternInfoToProperties(DecimalFormatProperties &properties, ParsedPatternInfo patternInfo,
IgnoreRounding _ignoreRounding, UErrorCode &status);
};
class U_I18N_API PatternStringUtils {
public:
/**
* 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.
*
* @param properties
* The property bag to serialize.
* @return A pattern string approximately serializing the property bag.
*/
static UnicodeString
propertiesToPatternString(const DecimalFormatProperties &properties, UErrorCode &status);
/**
* 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.
*
* <p>
* Locale symbols are not allowed to contain the ASCII quote character.
*
* <p>
* This method is provided for backwards compatibility and should not be used in any new code.
*
* TODO(C++): This method is not yet implemented.
*
* @param input
* The pattern to convert.
* @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.
* @return The pattern expressed in the other notation.
*/
static UnicodeString
convertLocalized(UnicodeString input, DecimalFormatSymbols symbols, bool toLocalized,
UErrorCode &status);
private:
/** @return The number of chars inserted. */
static int
escapePaddingString(UnicodeString input, UnicodeString &output, int startIndex, UErrorCode &status);
};
} // namespace impl
} // namespace number
U_NAMESPACE_END
#endif //__NUMBER_PATTERNSTRING_H__
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,344 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#include "uassert.h"
#include "unicode/numberformatter.h"
#include "number_types.h"
#include "number_decimalquantity.h"
using namespace icu::number;
using namespace icu::number::impl;
namespace {
int32_t getRoundingMagnitudeFraction(int maxFrac) {
if (maxFrac == -1) {
return INT32_MIN;
}
return -maxFrac;
}
int32_t getRoundingMagnitudeSignificant(const DecimalQuantity &value, int maxSig) {
if (maxSig == -1) {
return INT32_MIN;
}
int magnitude = value.isZero() ? 0 : value.getMagnitude();
return magnitude - maxSig + 1;
}
int32_t getDisplayMagnitudeFraction(int minFrac) {
if (minFrac == 0) {
return INT32_MAX;
}
return -minFrac;
}
int32_t getDisplayMagnitudeSignificant(const DecimalQuantity &value, int minSig) {
int magnitude = value.isZero() ? 0 : value.getMagnitude();
return magnitude - minSig + 1;
}
}
Rounder Rounder::unlimited() {
return Rounder(RND_NONE, {}, kDefaultMode);
}
FractionRounder Rounder::integer() {
return constructFraction(0, 0);
}
FractionRounder Rounder::fixedFraction(int32_t minMaxFractionPlaces) {
if (minMaxFractionPlaces >= 0 && minMaxFractionPlaces <= kMaxIntFracSig) {
return constructFraction(minMaxFractionPlaces, minMaxFractionPlaces);
} else {
return {U_NUMBER_DIGIT_WIDTH_OUT_OF_RANGE_ERROR};
}
}
FractionRounder Rounder::minFraction(int32_t minFractionPlaces) {
if (minFractionPlaces >= 0 && minFractionPlaces <= kMaxIntFracSig) {
return constructFraction(minFractionPlaces, -1);
} else {
return {U_NUMBER_DIGIT_WIDTH_OUT_OF_RANGE_ERROR};
}
}
FractionRounder Rounder::maxFraction(int32_t maxFractionPlaces) {
if (maxFractionPlaces >= 0 && maxFractionPlaces <= kMaxIntFracSig) {
return constructFraction(0, maxFractionPlaces);
} else {
return {U_NUMBER_DIGIT_WIDTH_OUT_OF_RANGE_ERROR};
}
}
FractionRounder Rounder::minMaxFraction(int32_t minFractionPlaces, int32_t maxFractionPlaces) {
if (minFractionPlaces >= 0 && maxFractionPlaces <= kMaxIntFracSig &&
minFractionPlaces <= maxFractionPlaces) {
return constructFraction(minFractionPlaces, maxFractionPlaces);
} else {
return {U_NUMBER_DIGIT_WIDTH_OUT_OF_RANGE_ERROR};
}
}
Rounder Rounder::fixedDigits(int32_t minMaxSignificantDigits) {
if (minMaxSignificantDigits >= 0 && minMaxSignificantDigits <= kMaxIntFracSig) {
return constructSignificant(minMaxSignificantDigits, minMaxSignificantDigits);
} else {
return {U_NUMBER_DIGIT_WIDTH_OUT_OF_RANGE_ERROR};
}
}
Rounder Rounder::minDigits(int32_t minSignificantDigits) {
if (minSignificantDigits >= 0 && minSignificantDigits <= kMaxIntFracSig) {
return constructSignificant(minSignificantDigits, -1);
} else {
return {U_NUMBER_DIGIT_WIDTH_OUT_OF_RANGE_ERROR};
}
}
Rounder Rounder::maxDigits(int32_t maxSignificantDigits) {
if (maxSignificantDigits >= 0 && maxSignificantDigits <= kMaxIntFracSig) {
return constructSignificant(0, maxSignificantDigits);
} else {
return {U_NUMBER_DIGIT_WIDTH_OUT_OF_RANGE_ERROR};
}
}
Rounder Rounder::minMaxDigits(int32_t minSignificantDigits, int32_t maxSignificantDigits) {
if (minSignificantDigits >= 0 && maxSignificantDigits <= kMaxIntFracSig &&
minSignificantDigits <= maxSignificantDigits) {
return constructSignificant(minSignificantDigits, maxSignificantDigits);
} else {
return {U_NUMBER_DIGIT_WIDTH_OUT_OF_RANGE_ERROR};
}
}
IncrementRounder Rounder::increment(double roundingIncrement) {
if (roundingIncrement > 0.0) {
return constructIncrement(roundingIncrement, 0);
} else {
return {U_NUMBER_DIGIT_WIDTH_OUT_OF_RANGE_ERROR};
}
}
CurrencyRounder Rounder::currency(UCurrencyUsage currencyUsage) {
return constructCurrency(currencyUsage);
}
Rounder Rounder::withMode(RoundingMode roundingMode) const {
if (fType == RND_ERROR) { return *this; } // no-op in error state
return {fType, fUnion, roundingMode};
}
Rounder FractionRounder::withMinDigits(int32_t minSignificantDigits) const {
if (fType == RND_ERROR) { return *this; } // no-op in error state
if (minSignificantDigits >= 0 && minSignificantDigits <= kMaxIntFracSig) {
return constructFractionSignificant(*this, minSignificantDigits, -1);
} else {
return {U_NUMBER_DIGIT_WIDTH_OUT_OF_RANGE_ERROR};
}
}
Rounder FractionRounder::withMaxDigits(int32_t maxSignificantDigits) const {
if (fType == RND_ERROR) { return *this; } // no-op in error state
if (maxSignificantDigits >= 0 && maxSignificantDigits <= kMaxIntFracSig) {
return constructFractionSignificant(*this, -1, maxSignificantDigits);
} else {
return {U_NUMBER_DIGIT_WIDTH_OUT_OF_RANGE_ERROR};
}
}
// Private method on base class
Rounder Rounder::withCurrency(const CurrencyUnit &currency, UErrorCode &status) const {
if (fType == RND_ERROR) { return *this; } // no-op in error state
U_ASSERT(fType == RND_CURRENCY);
const char16_t *isoCode = currency.getISOCurrency();
double increment = ucurr_getRoundingIncrementForUsage(isoCode, fUnion.currencyUsage, &status);
int32_t minMaxFrac = ucurr_getDefaultFractionDigitsForUsage(
isoCode, fUnion.currencyUsage, &status);
if (increment != 0.0) {
return constructIncrement(increment, minMaxFrac);
} else {
return constructFraction(minMaxFrac, minMaxFrac);
}
}
// Public method on CurrencyRounder subclass
Rounder CurrencyRounder::withCurrency(const CurrencyUnit &currency) const {
UErrorCode localStatus = U_ZERO_ERROR;
Rounder result = Rounder::withCurrency(currency, localStatus);
if (U_FAILURE(localStatus)) {
return {localStatus};
}
return result;
}
Rounder IncrementRounder::withMinFraction(int32_t minFrac) const {
if (fType == RND_ERROR) { return *this; } // no-op in error state
if (minFrac >= 0 && minFrac <= kMaxIntFracSig) {
return constructIncrement(fUnion.increment.fIncrement, minFrac);
} else {
return {U_NUMBER_DIGIT_WIDTH_OUT_OF_RANGE_ERROR};
}
}
FractionRounder Rounder::constructFraction(int32_t minFrac, int32_t maxFrac) {
FractionSignificantSettings settings;
settings.fMinFrac = static_cast<int8_t> (minFrac);
settings.fMaxFrac = static_cast<int8_t> (maxFrac);
settings.fMinSig = -1;
settings.fMaxSig = -1;
RounderUnion union_;
union_.fracSig = settings;
return {RND_FRACTION, union_, kDefaultMode};
}
Rounder Rounder::constructSignificant(int32_t minSig, int32_t maxSig) {
FractionSignificantSettings settings;
settings.fMinFrac = -1;
settings.fMaxFrac = -1;
settings.fMinSig = static_cast<int8_t>(minSig);
settings.fMaxSig = static_cast<int8_t>(maxSig);
RounderUnion union_;
union_.fracSig = settings;
return {RND_SIGNIFICANT, union_, kDefaultMode};
}
Rounder
Rounder::constructFractionSignificant(const FractionRounder &base, int32_t minSig, int32_t maxSig) {
FractionSignificantSettings settings = base.fUnion.fracSig;
settings.fMinSig = static_cast<int8_t>(minSig);
settings.fMaxSig = static_cast<int8_t>(maxSig);
RounderUnion union_;
union_.fracSig = settings;
return {RND_FRACTION_SIGNIFICANT, union_, kDefaultMode};
}
IncrementRounder Rounder::constructIncrement(double increment, int32_t minFrac) {
IncrementSettings settings;
settings.fIncrement = increment;
settings.fMinFrac = minFrac;
RounderUnion union_;
union_.increment = settings;
return {RND_INCREMENT, union_, kDefaultMode};
}
CurrencyRounder Rounder::constructCurrency(UCurrencyUsage usage) {
RounderUnion union_;
union_.currencyUsage = usage;
return {RND_CURRENCY, union_, kDefaultMode};
}
Rounder Rounder::constructPassThrough() {
RounderUnion union_;
union_.errorCode = U_ZERO_ERROR; // initialize the variable
return {RND_PASS_THROUGH, union_, kDefaultMode};
}
void Rounder::setLocaleData(const CurrencyUnit &currency, UErrorCode &status) {
if (fType == RND_CURRENCY) {
*this = withCurrency(currency, 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);
U_ASSERT(!input.isZero());
int32_t magnitude = input.getMagnitude();
int32_t 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);
}
return multiplier;
}
/** This is the method that contains the actual rounding logic. */
void Rounder::apply(impl::DecimalQuantity &value, UErrorCode& status) const {
switch (fType) {
case RND_BOGUS:
case RND_ERROR:
// Errors should be caught before the apply() method is called
status = U_INTERNAL_PROGRAM_ERROR;
break;
case RND_NONE:
value.roundToInfinity();
break;
case RND_FRACTION:
value.roundToMagnitude(
getRoundingMagnitudeFraction(fUnion.fracSig.fMaxFrac), fRoundingMode, status);
value.setFractionLength(
uprv_max(0, -getDisplayMagnitudeFraction(fUnion.fracSig.fMinFrac)), INT32_MAX);
break;
case RND_SIGNIFICANT:
value.roundToMagnitude(
getRoundingMagnitudeSignificant(value, fUnion.fracSig.fMaxSig),
fRoundingMode,
status);
value.setFractionLength(
uprv_max(0, -getDisplayMagnitudeSignificant(value, fUnion.fracSig.fMinSig)),
INT32_MAX);
break;
case RND_FRACTION_SIGNIFICANT: {
int32_t displayMag = getDisplayMagnitudeFraction(fUnion.fracSig.fMinFrac);
int32_t roundingMag = getRoundingMagnitudeFraction(fUnion.fracSig.fMaxFrac);
if (fUnion.fracSig.fMinSig == -1) {
// Max Sig override
int32_t candidate = getRoundingMagnitudeSignificant(value, fUnion.fracSig.fMaxSig);
roundingMag = uprv_max(roundingMag, candidate);
} else {
// Min Sig override
int32_t candidate = getDisplayMagnitudeSignificant(value, fUnion.fracSig.fMinSig);
roundingMag = uprv_min(roundingMag, candidate);
}
value.roundToMagnitude(roundingMag, fRoundingMode, status);
value.setFractionLength(uprv_max(0, -displayMag), INT32_MAX);
break;
}
case RND_INCREMENT:
value.roundToIncrement(
fUnion.increment.fIncrement, fRoundingMode, fUnion.increment.fMinFrac, status);
value.setFractionLength(fUnion.increment.fMinFrac, fUnion.increment.fMinFrac);
break;
case RND_CURRENCY:
// Call .withCurrency() before .apply()!
U_ASSERT(false);
case RND_PASS_THROUGH:
break;
}
}
void Rounder::apply(impl::DecimalQuantity &value, int32_t minInt, UErrorCode /*status*/) {
// This method is intended for the one specific purpose of helping print "00.000E0".
U_ASSERT(fType == RND_SIGNIFICANT);
U_ASSERT(value.isZero());
value.setFractionLength(fUnion.fracSig.fMinSig - minInt, INT32_MAX);
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,139 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#ifndef __NUMBER_ROUNDINGUTILS_H__
#define __NUMBER_ROUNDINGUTILS_H__
#include "number_types.h"
U_NAMESPACE_BEGIN
namespace number {
namespace impl {
namespace roundingutils {
enum Section {
SECTION_LOWER_EDGE = -1,
SECTION_UPPER_EDGE = -2,
SECTION_LOWER = 1,
SECTION_MIDPOINT = 2,
SECTION_UPPER = 3
};
/**
* 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 status Error code, set to U_FORMAT_INEXACT_ERROR if the rounding mode is kRoundUnnecessary.
* @return true if the number should be rounded toward zero; false if it should be rounded toward
* infinity.
*/
inline bool
getRoundingDirection(bool isEven, bool isNegative, Section section, RoundingMode roundingMode,
UErrorCode &status) {
switch (roundingMode) {
case RoundingMode::UNUM_ROUND_UP:
// round away from zero
return false;
case RoundingMode::UNUM_ROUND_DOWN:
// round toward zero
return true;
case RoundingMode::UNUM_ROUND_CEILING:
// round toward positive infinity
return isNegative;
case RoundingMode::UNUM_ROUND_FLOOR:
// round toward negative infinity
return !isNegative;
case RoundingMode::UNUM_ROUND_HALFUP:
switch (section) {
case SECTION_MIDPOINT:
return false;
case SECTION_LOWER:
return true;
case SECTION_UPPER:
return false;
default:
break;
}
break;
case RoundingMode::UNUM_ROUND_HALFDOWN:
switch (section) {
case SECTION_MIDPOINT:
return true;
case SECTION_LOWER:
return true;
case SECTION_UPPER:
return false;
default:
break;
}
break;
case RoundingMode::UNUM_ROUND_HALFEVEN:
switch (section) {
case SECTION_MIDPOINT:
return isEven;
case SECTION_LOWER:
return true;
case SECTION_UPPER:
return false;
default:
break;
}
break;
default:
break;
}
status = U_FORMAT_INEXACT_ERROR;
return false;
}
/**
* 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.
*/
inline bool roundsAtMidpoint(int roundingMode) {
switch (roundingMode) {
case RoundingMode::UNUM_ROUND_UP:
case RoundingMode::UNUM_ROUND_DOWN:
case RoundingMode::UNUM_ROUND_CEILING:
case RoundingMode::UNUM_ROUND_FLOOR:
return false;
default:
return true;
}
}
} // namespace roundingutils
} // namespace impl
} // namespace number
U_NAMESPACE_END
#endif //__NUMBER_ROUNDINGUTILS_H__
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,134 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#include <cstdlib>
#include "number_scientific.h"
#include "number_utils.h"
#include "number_stringbuilder.h"
#include "unicode/unum.h"
using namespace icu::number::impl;
// 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
// 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.
ScientificModifier::ScientificModifier() : fExponent(0), fHandler(nullptr) {}
void ScientificModifier::set(int32_t exponent, const ScientificHandler *handler) {
// ScientificModifier should be set only once.
U_ASSERT(fHandler == nullptr);
fExponent = exponent;
fHandler = handler;
}
int32_t ScientificModifier::apply(NumberStringBuilder &output, int32_t /*leftIndex*/, int32_t rightIndex,
UErrorCode &status) const {
// FIXME: Localized exponent separator location.
int i = rightIndex;
// Append the exponent separator and sign
i += output.insert(
i,
fHandler->fSymbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kExponentialSymbol),
UNUM_EXPONENT_SYMBOL_FIELD,
status);
if (fExponent < 0 && fHandler->fSettings.fExponentSignDisplay != UNUM_SIGN_NEVER) {
i += output.insert(
i,
fHandler->fSymbols
->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kMinusSignSymbol),
UNUM_EXPONENT_SIGN_FIELD,
status);
} else if (fExponent >= 0 && fHandler->fSettings.fExponentSignDisplay == UNUM_SIGN_ALWAYS) {
i += output.insert(
i,
fHandler->fSymbols
->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kPlusSignSymbol),
UNUM_EXPONENT_SIGN_FIELD,
status);
}
// Append the exponent digits (using a simple inline algorithm)
int32_t disp = std::abs(fExponent);
for (int j = 0; j < fHandler->fSettings.fMinExponentDigits || disp > 0; j++, disp /= 10) {
auto d = static_cast<int8_t>(disp % 10);
const UnicodeString &digitString = getDigitFromSymbols(d, *fHandler->fSymbols);
i += output.insert(i - j, digitString, UNUM_EXPONENT_FIELD, status);
}
return i - rightIndex;
}
int32_t ScientificModifier::getPrefixLength(UErrorCode &status) const {
(void)status;
// TODO: Localized exponent separator location.
return 0;
}
int32_t ScientificModifier::getCodePointCount(UErrorCode &status) const {
(void)status;
// This method is not used for strong modifiers.
U_ASSERT(false);
return 0;
}
bool ScientificModifier::isStrong() const {
// Scientific is always strong
return true;
}
// Note: Visual Studio does not compile this function without full name space. Why?
icu::number::impl::ScientificHandler::ScientificHandler(const Notation *notation, const DecimalFormatSymbols *symbols,
const MicroPropsGenerator *parent) :
fSettings(notation->fUnion.scientific), fSymbols(symbols), fParent(parent) {}
void ScientificHandler::processQuantity(DecimalQuantity &quantity, MicroProps &micros,
UErrorCode &status) const {
fParent->processQuantity(quantity, micros, status);
if (U_FAILURE(status)) { return; }
// Treat zero as if it had magnitude 0
int32_t exponent;
if (quantity.isZero()) {
if (fSettings.fRequireMinInt && micros.rounding.fType == Rounder::RND_SIGNIFICANT) {
// Show "00.000E0" on pattern "00.000E0"
micros.rounding.apply(quantity, fSettings.fEngineeringInterval, status);
exponent = 0;
} else {
micros.rounding.apply(quantity, status);
exponent = 0;
}
} else {
exponent = -micros.rounding.chooseMultiplierAndApply(quantity, *this, status);
}
// Use MicroProps's helper ScientificModifier and save it as the modInner.
ScientificModifier &mod = micros.helpers.scientificModifier;
mod.set(exponent, this);
micros.modInner = &mod;
}
int32_t ScientificHandler::getMultiplier(int32_t magnitude) const {
int32_t interval = fSettings.fEngineeringInterval;
int32_t digitsShown;
if (fSettings.fRequireMinInt) {
// For patterns like "000.00E0" and ".00E0"
digitsShown = interval;
} else if (interval <= 1) {
// For patterns like "0.00E0" and "@@@E0"
digitsShown = 1;
} else {
// For patterns like "##0.00"
digitsShown = ((magnitude % interval + interval) % interval) + 1;
}
return digitsShown - magnitude - 1;
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,60 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#ifndef __NUMBER_SCIENTIFIC_H__
#define __NUMBER_SCIENTIFIC_H__
#include "number_types.h"
U_NAMESPACE_BEGIN namespace number {
namespace impl {
// Forward-declare
class ScientificHandler;
class U_I18N_API ScientificModifier : public UMemory, public Modifier {
public:
ScientificModifier();
void set(int32_t exponent, const ScientificHandler *handler);
int32_t apply(NumberStringBuilder &output, int32_t leftIndex, int32_t rightIndex,
UErrorCode &status) const override;
int32_t getPrefixLength(UErrorCode &status) const override;
int32_t getCodePointCount(UErrorCode &status) const override;
bool isStrong() const override;
private:
int32_t fExponent;
const ScientificHandler *fHandler;
};
class U_I18N_API ScientificHandler : public UMemory, public MicroPropsGenerator, public MultiplierProducer {
public:
ScientificHandler(const Notation *notation, const DecimalFormatSymbols *symbols,
const MicroPropsGenerator *parent);
void
processQuantity(DecimalQuantity &quantity, MicroProps &micros, UErrorCode &status) const override;
int32_t getMultiplier(int32_t magnitude) const override;
private:
const Notation::ScientificSettings& fSettings;
const DecimalFormatSymbols *fSymbols;
const MicroPropsGenerator *fParent;
friend class ScientificModifier;
};
} // namespace impl
} // namespace number
U_NAMESPACE_END
#endif //__NUMBER_SCIENTIFIC_H__
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,457 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#include "number_stringbuilder.h"
#include "unicode/utf16.h"
#include "uvectr32.h"
using namespace icu;
using namespace icu::number::impl;
namespace {
// A version of uprv_memcpy that checks for length 0.
// By default, uprv_memcpy requires a length of at least 1.
inline void uprv_memcpy2(void* dest, const void* src, size_t len) {
if (len > 0) {
uprv_memcpy(dest, src, len);
}
}
// A version of uprv_memmove that checks for length 0.
// By default, uprv_memmove requires a length of at least 1.
inline void uprv_memmove2(void* dest, const void* src, size_t len) {
if (len > 0) {
uprv_memmove(dest, src, len);
}
}
} // namespace
NumberStringBuilder::NumberStringBuilder() = default;
NumberStringBuilder::~NumberStringBuilder() {
if (fUsingHeap) {
uprv_free(fChars.heap.ptr);
uprv_free(fFields.heap.ptr);
}
}
NumberStringBuilder::NumberStringBuilder(const NumberStringBuilder &other) {
*this = other;
}
NumberStringBuilder &NumberStringBuilder::operator=(const NumberStringBuilder &other) {
// Check for self-assignment
if (this == &other) {
return *this;
}
// Continue with deallocation and copying
if (fUsingHeap) {
uprv_free(fChars.heap.ptr);
uprv_free(fFields.heap.ptr);
fUsingHeap = false;
}
int32_t capacity = other.getCapacity();
if (capacity > DEFAULT_CAPACITY) {
// FIXME: uprv_malloc
// C++ note: malloc appears in two places: here and in prepareForInsertHelper.
auto newChars = static_cast<char16_t *> (uprv_malloc(sizeof(char16_t) * capacity));
auto newFields = static_cast<Field *>(uprv_malloc(sizeof(Field) * capacity));
if (newChars == nullptr || newFields == nullptr) {
// UErrorCode is not available; fail silently.
uprv_free(newChars);
uprv_free(newFields);
*this = NumberStringBuilder(); // can't fail
return *this;
}
fUsingHeap = true;
fChars.heap.capacity = capacity;
fChars.heap.ptr = newChars;
fFields.heap.capacity = capacity;
fFields.heap.ptr = newFields;
}
uprv_memcpy2(getCharPtr(), other.getCharPtr(), sizeof(char16_t) * capacity);
uprv_memcpy2(getFieldPtr(), other.getFieldPtr(), sizeof(Field) * capacity);
fZero = other.fZero;
fLength = other.fLength;
return *this;
}
int32_t NumberStringBuilder::length() const {
return fLength;
}
int32_t NumberStringBuilder::codePointCount() const {
return u_countChar32(getCharPtr() + fZero, fLength);
}
UChar32 NumberStringBuilder::getFirstCodePoint() const {
if (fLength == 0) {
return -1;
}
UChar32 cp;
U16_GET(getCharPtr() + fZero, 0, 0, fLength, cp);
return cp;
}
UChar32 NumberStringBuilder::getLastCodePoint() const {
if (fLength == 0) {
return -1;
}
int32_t offset = fLength;
U16_BACK_1(getCharPtr() + fZero, 0, offset);
UChar32 cp;
U16_GET(getCharPtr() + fZero, 0, offset, fLength, cp);
return cp;
}
UChar32 NumberStringBuilder::codePointAt(int32_t index) const {
UChar32 cp;
U16_GET(getCharPtr() + fZero, 0, index, fLength, cp);
return cp;
}
UChar32 NumberStringBuilder::codePointBefore(int32_t index) const {
int32_t offset = index;
U16_BACK_1(getCharPtr() + fZero, 0, offset);
UChar32 cp;
U16_GET(getCharPtr() + fZero, 0, offset, fLength, cp);
return cp;
}
NumberStringBuilder &NumberStringBuilder::clear() {
// TODO: Reset the heap here?
fZero = getCapacity() / 2;
fLength = 0;
return *this;
}
int32_t NumberStringBuilder::appendCodePoint(UChar32 codePoint, Field field, UErrorCode &status) {
return insertCodePoint(fLength, codePoint, field, status);
}
int32_t
NumberStringBuilder::insertCodePoint(int32_t index, UChar32 codePoint, Field field, UErrorCode &status) {
int32_t count = U16_LENGTH(codePoint);
int32_t position = prepareForInsert(index, count, status);
if (U_FAILURE(status)) {
return count;
}
if (count == 1) {
getCharPtr()[position] = (char16_t) codePoint;
getFieldPtr()[position] = field;
} else {
getCharPtr()[position] = U16_LEAD(codePoint);
getCharPtr()[position + 1] = U16_TRAIL(codePoint);
getFieldPtr()[position] = getFieldPtr()[position + 1] = field;
}
return count;
}
int32_t NumberStringBuilder::append(const UnicodeString &unistr, Field field, UErrorCode &status) {
return insert(fLength, unistr, field, status);
}
int32_t NumberStringBuilder::insert(int32_t index, const UnicodeString &unistr, Field field,
UErrorCode &status) {
if (unistr.length() == 0) {
// Nothing to insert.
return 0;
} else if (unistr.length() == 1) {
// Fast path: insert using insertCodePoint.
return insertCodePoint(index, unistr.charAt(0), field, status);
} else {
return insert(index, unistr, 0, unistr.length(), field, status);
}
}
int32_t
NumberStringBuilder::insert(int32_t index, const UnicodeString &unistr, int32_t start, int32_t end,
Field field, UErrorCode &status) {
int32_t count = end - start;
int32_t position = prepareForInsert(index, count, status);
if (U_FAILURE(status)) {
return count;
}
for (int32_t i = 0; i < count; i++) {
getCharPtr()[position + i] = unistr.charAt(start + i);
getFieldPtr()[position + i] = field;
}
return count;
}
int32_t NumberStringBuilder::append(const NumberStringBuilder &other, UErrorCode &status) {
return insert(fLength, other, status);
}
int32_t
NumberStringBuilder::insert(int32_t index, const NumberStringBuilder &other, UErrorCode &status) {
if (this == &other) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return 0;
}
int32_t count = other.fLength;
if (count == 0) {
// Nothing to insert.
return 0;
}
int32_t position = prepareForInsert(index, count, status);
if (U_FAILURE(status)) {
return count;
}
for (int32_t i = 0; i < count; i++) {
getCharPtr()[position + i] = other.charAt(i);
getFieldPtr()[position + i] = other.fieldAt(i);
}
return count;
}
int32_t NumberStringBuilder::prepareForInsert(int32_t index, int32_t count, UErrorCode &status) {
if (index == 0 && fZero - count >= 0) {
// Append to start
fZero -= count;
fLength += count;
return fZero;
} else if (index == fLength && fZero + fLength + count < getCapacity()) {
// Append to end
fLength += count;
return fZero + fLength - count;
} else {
// Move chars around and/or allocate more space
return prepareForInsertHelper(index, count, status);
}
}
int32_t NumberStringBuilder::prepareForInsertHelper(int32_t index, int32_t count, UErrorCode &status) {
int32_t oldCapacity = getCapacity();
int32_t oldZero = fZero;
char16_t *oldChars = getCharPtr();
Field *oldFields = getFieldPtr();
if (fLength + count > oldCapacity) {
int32_t newCapacity = (fLength + count) * 2;
int32_t newZero = newCapacity / 2 - (fLength + count) / 2;
// C++ note: malloc appears in two places: here and in the assignment operator.
auto newChars = static_cast<char16_t *> (uprv_malloc(sizeof(char16_t) * newCapacity));
auto newFields = static_cast<Field *>(uprv_malloc(sizeof(Field) * newCapacity));
if (newChars == nullptr || newFields == nullptr) {
uprv_free(newChars);
uprv_free(newFields);
status = U_MEMORY_ALLOCATION_ERROR;
return -1;
}
// First copy the prefix and then the suffix, leaving room for the new chars that the
// caller wants to insert.
// C++ note: memcpy is OK because the src and dest do not overlap.
uprv_memcpy2(newChars + newZero, oldChars + oldZero, sizeof(char16_t) * index);
uprv_memcpy2(newChars + newZero + index + count,
oldChars + oldZero + index,
sizeof(char16_t) * (fLength - index));
uprv_memcpy2(newFields + newZero, oldFields + oldZero, sizeof(Field) * index);
uprv_memcpy2(newFields + newZero + index + count,
oldFields + oldZero + index,
sizeof(Field) * (fLength - index));
if (fUsingHeap) {
uprv_free(oldChars);
uprv_free(oldFields);
}
fUsingHeap = true;
fChars.heap.ptr = newChars;
fChars.heap.capacity = newCapacity;
fFields.heap.ptr = newFields;
fFields.heap.capacity = newCapacity;
fZero = newZero;
fLength += count;
} else {
int32_t newZero = oldCapacity / 2 - (fLength + count) / 2;
// C++ note: memmove is required because src and dest may overlap.
// 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.
uprv_memmove2(oldChars + newZero, oldChars + oldZero, sizeof(char16_t) * fLength);
uprv_memmove2(oldChars + newZero + index + count,
oldChars + newZero + index,
sizeof(char16_t) * (fLength - index));
uprv_memmove2(oldFields + newZero, oldFields + oldZero, sizeof(Field) * fLength);
uprv_memmove2(oldFields + newZero + index + count,
oldFields + newZero + index,
sizeof(Field) * (fLength - index));
fZero = newZero;
fLength += count;
}
return fZero + index;
}
UnicodeString NumberStringBuilder::toUnicodeString() const {
return UnicodeString(getCharPtr() + fZero, fLength);
}
UnicodeString NumberStringBuilder::toDebugString() const {
UnicodeString sb;
sb.append(u"<NumberStringBuilder [", -1);
sb.append(toUnicodeString());
sb.append(u"] [", -1);
for (int i = 0; i < fLength; i++) {
if (fieldAt(i) == UNUM_FIELD_COUNT) {
sb.append(u'n');
} else {
char16_t c;
switch (fieldAt(i)) {
case UNUM_SIGN_FIELD:
c = u'-';
break;
case UNUM_INTEGER_FIELD:
c = u'i';
break;
case UNUM_FRACTION_FIELD:
c = u'f';
break;
case UNUM_EXPONENT_FIELD:
c = u'e';
break;
case UNUM_EXPONENT_SIGN_FIELD:
c = u'+';
break;
case UNUM_EXPONENT_SYMBOL_FIELD:
c = u'E';
break;
case UNUM_DECIMAL_SEPARATOR_FIELD:
c = u'.';
break;
case UNUM_GROUPING_SEPARATOR_FIELD:
c = u',';
break;
case UNUM_PERCENT_FIELD:
c = u'%';
break;
case UNUM_PERMILL_FIELD:
c = u'';
break;
case UNUM_CURRENCY_FIELD:
c = u'$';
break;
default:
c = u'?';
break;
}
sb.append(c);
}
}
sb.append(u"]>", -1);
return sb;
}
const char16_t *NumberStringBuilder::chars() const {
return getCharPtr() + fZero;
}
bool NumberStringBuilder::contentEquals(const NumberStringBuilder &other) const {
if (fLength != other.fLength) {
return false;
}
for (int32_t i = 0; i < fLength; i++) {
if (charAt(i) != other.charAt(i) || fieldAt(i) != other.fieldAt(i)) {
return false;
}
}
return true;
}
void NumberStringBuilder::populateFieldPosition(FieldPosition &fp, int32_t offset, UErrorCode &status) const {
int32_t rawField = fp.getField();
if (rawField == FieldPosition::DONT_CARE) {
return;
}
if (rawField < 0 || rawField >= UNUM_FIELD_COUNT) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return;
}
auto field = static_cast<Field>(rawField);
bool seenStart = false;
int32_t fractionStart = -1;
for (int i = fZero; i <= fZero + fLength; i++) {
Field _field = UNUM_FIELD_COUNT;
if (i < fZero + fLength) {
_field = getFieldPtr()[i];
}
if (seenStart && field != _field) {
// Special case: GROUPING_SEPARATOR counts as an INTEGER.
if (field == UNUM_INTEGER_FIELD && _field == UNUM_GROUPING_SEPARATOR_FIELD) {
continue;
}
fp.setEndIndex(i - fZero + offset);
break;
} else if (!seenStart && field == _field) {
fp.setBeginIndex(i - fZero + offset);
seenStart = true;
}
if (_field == UNUM_INTEGER_FIELD || _field == UNUM_DECIMAL_SEPARATOR_FIELD) {
fractionStart = i - fZero + 1;
}
}
// Backwards compatibility: FRACTION needs to start after INTEGER if empty
if (field == UNUM_FRACTION_FIELD && !seenStart) {
fp.setBeginIndex(fractionStart + offset);
fp.setEndIndex(fractionStart + offset);
}
}
void NumberStringBuilder::populateFieldPositionIterator(FieldPositionIterator &fpi, UErrorCode &status) const {
// TODO: Set an initial capacity on uvec?
LocalPointer <UVector32> uvec(new UVector32(status));
if (U_FAILURE(status)) {
return;
}
Field current = UNUM_FIELD_COUNT;
int32_t currentStart = -1;
for (int32_t i = 0; i < fLength; i++) {
Field field = fieldAt(i);
if (current == UNUM_INTEGER_FIELD && field == UNUM_GROUPING_SEPARATOR_FIELD) {
// Special case: GROUPING_SEPARATOR counts as an INTEGER.
// Add the field, followed by the start index, followed by the end index to uvec.
uvec->addElement(UNUM_GROUPING_SEPARATOR_FIELD, status);
uvec->addElement(i, status);
uvec->addElement(i + 1, status);
} else if (current != field) {
if (current != UNUM_FIELD_COUNT) {
// Add the field, followed by the start index, followed by the end index to uvec.
uvec->addElement(current, status);
uvec->addElement(currentStart, status);
uvec->addElement(i, status);
}
current = field;
currentStart = i;
}
if (U_FAILURE(status)) {
return;
}
}
if (current != UNUM_FIELD_COUNT) {
// Add the field, followed by the start index, followed by the end index to uvec.
uvec->addElement(current, status);
uvec->addElement(currentStart, status);
uvec->addElement(fLength, status);
}
// Give uvec to the FieldPositionIterator, which adopts it.
fpi.setData(uvec.orphan(), status);
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,133 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#ifndef __NUMBER_STRINGBUILDER_H__
#define __NUMBER_STRINGBUILDER_H__
#include <cstdint>
#include "unicode/numfmt.h"
#include "unicode/ustring.h"
#include "cstring.h"
#include "uassert.h"
#include "number_types.h"
U_NAMESPACE_BEGIN namespace number {
namespace impl {
class U_I18N_API NumberStringBuilder : public UMemory {
private:
static const int32_t DEFAULT_CAPACITY = 40;
template<typename T>
union ValueOrHeapArray {
T value[DEFAULT_CAPACITY];
struct {
T *ptr;
int32_t capacity;
} heap;
};
public:
NumberStringBuilder();
~NumberStringBuilder();
NumberStringBuilder(const NumberStringBuilder &other);
NumberStringBuilder &operator=(const NumberStringBuilder &other);
int32_t length() const;
int32_t codePointCount() const;
inline char16_t charAt(int32_t index) const {
U_ASSERT(index >= 0);
U_ASSERT(index < fLength);
return getCharPtr()[fZero + index];
}
inline Field fieldAt(int32_t index) const {
U_ASSERT(index >= 0);
U_ASSERT(index < fLength);
return getFieldPtr()[fZero + index];
}
UChar32 getFirstCodePoint() const;
UChar32 getLastCodePoint() const;
UChar32 codePointAt(int32_t index) const;
UChar32 codePointBefore(int32_t index) const;
NumberStringBuilder &clear();
int32_t appendCodePoint(UChar32 codePoint, Field field, UErrorCode &status);
int32_t insertCodePoint(int32_t index, UChar32 codePoint, Field field, UErrorCode &status);
int32_t append(const UnicodeString &unistr, Field field, UErrorCode &status);
int32_t insert(int32_t index, const UnicodeString &unistr, Field field, UErrorCode &status);
int32_t insert(int32_t index, const UnicodeString &unistr, int32_t start, int32_t end, Field field,
UErrorCode &status);
int32_t append(const NumberStringBuilder &other, UErrorCode &status);
int32_t insert(int32_t index, const NumberStringBuilder &other, UErrorCode &status);
UnicodeString toUnicodeString() const;
UnicodeString toDebugString() const;
const char16_t *chars() const;
bool contentEquals(const NumberStringBuilder &other) const;
void populateFieldPosition(FieldPosition &fp, int32_t offset, UErrorCode &status) const;
void populateFieldPositionIterator(FieldPositionIterator &fpi, UErrorCode &status) const;
private:
bool fUsingHeap = false;
ValueOrHeapArray<char16_t> fChars;
ValueOrHeapArray<Field> fFields;
int32_t fZero = DEFAULT_CAPACITY / 2;
int32_t fLength = 0;
inline char16_t *getCharPtr() {
return fUsingHeap ? fChars.heap.ptr : fChars.value;
}
inline const char16_t *getCharPtr() const {
return fUsingHeap ? fChars.heap.ptr : fChars.value;
}
inline Field *getFieldPtr() {
return fUsingHeap ? fFields.heap.ptr : fFields.value;
}
inline const Field *getFieldPtr() const {
return fUsingHeap ? fFields.heap.ptr : fFields.value;
}
inline int32_t getCapacity() const {
return fUsingHeap ? fChars.heap.capacity : DEFAULT_CAPACITY;
}
int32_t prepareForInsert(int32_t index, int32_t count, UErrorCode &status);
int32_t prepareForInsertHelper(int32_t index, int32_t count, UErrorCode &status);
};
} // namespace impl
} // namespace number
U_NAMESPACE_END
#endif //__NUMBER_STRINGBUILDER_H__
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,285 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#ifndef __NUMBER_TYPES_H__
#define __NUMBER_TYPES_H__
#include <cstdint>
#include "unicode/decimfmt.h"
#include "unicode/unum.h"
#include "unicode/numsys.h"
#include "unicode/numberformatter.h"
#include "unicode/utf16.h"
#include "uassert.h"
U_NAMESPACE_BEGIN
namespace number {
namespace impl {
// Typedef several enums for brevity and for easier comparison to Java.
typedef UNumberFormatFields Field;
typedef UNumberFormatRoundingMode RoundingMode;
typedef UNumberFormatPadPosition PadPosition;
typedef UNumberCompactStyle CompactStyle;
// ICU4J Equivalent: RoundingUtils.MAX_INT_FRAC_SIG
static constexpr int32_t kMaxIntFracSig = 100;
// ICU4J Equivalent: RoundingUtils.DEFAULT_ROUNDING_MODE
static constexpr RoundingMode kDefaultMode = RoundingMode::UNUM_FOUND_HALFEVEN;
// ICU4J Equivalent: Padder.FALLBACK_PADDING_STRING
static constexpr char16_t kFallbackPaddingString[] = u" ";
// ICU4J Equivalent: NumberFormatterImpl.DEFAULT_CURRENCY
static constexpr char16_t kDefaultCurrency[] = u"XXX";
// FIXME: New error codes:
static constexpr UErrorCode U_NUMBER_DIGIT_WIDTH_OUT_OF_RANGE_ERROR = U_ILLEGAL_ARGUMENT_ERROR;
static constexpr UErrorCode U_NUMBER_PADDING_WIDTH_OUT_OF_RANGE_ERROR = U_ILLEGAL_ARGUMENT_ERROR;
// Forward declarations:
class Modifier;
class MutablePatternModifier;
class DecimalQuantity;
class NumberStringBuilder;
struct MicroProps;
enum AffixPatternType {
// Represents a literal character; the value is stored in the code point field.
TYPE_CODEPOINT = 0,
// Represents a minus sign symbol '-'.
TYPE_MINUS_SIGN = -1,
// Represents a plus sign symbol '+'.
TYPE_PLUS_SIGN = -2,
// Represents a percent sign symbol '%'.
TYPE_PERCENT = -3,
// Represents a permille sign symbol '‰'.
TYPE_PERMILLE = -4,
// Represents a single currency symbol '¤'.
TYPE_CURRENCY_SINGLE = -5,
// Represents a double currency symbol '¤¤'.
TYPE_CURRENCY_DOUBLE = -6,
// Represents a triple currency symbol '¤¤¤'.
TYPE_CURRENCY_TRIPLE = -7,
// Represents a quadruple currency symbol '¤¤¤¤'.
TYPE_CURRENCY_QUAD = -8,
// Represents a quintuple currency symbol '¤¤¤¤¤'.
TYPE_CURRENCY_QUINT = -9,
// Represents a sequence of six or more currency symbols.
TYPE_CURRENCY_OVERFLOW = -15
};
enum CompactType {
TYPE_DECIMAL,
TYPE_CURRENCY
};
// TODO: Should this be moved somewhere else, maybe where other ICU classes can use it?
class CharSequence {
public:
virtual ~CharSequence() = default;
virtual int32_t length() const = 0;
virtual char16_t charAt(int32_t index) const = 0;
virtual UChar32 codePointAt(int32_t index) const {
// Default implementation; can be overriden with a more efficient version
char16_t leading = charAt(index);
if (U16_IS_LEAD(leading) && length() > index + 1) {
char16_t trailing = charAt(index + 1);
return U16_GET_SUPPLEMENTARY(leading, trailing);
} else {
return leading;
}
}
virtual UnicodeString toUnicodeString() const = 0;
};
class U_I18N_API AffixPatternProvider {
public:
static const int32_t AFFIX_PLURAL_MASK = 0xff;
static const int32_t AFFIX_PREFIX = 0x100;
static const int32_t AFFIX_NEGATIVE_SUBPATTERN = 0x200;
static const int32_t AFFIX_PADDING = 0x400;
virtual ~AffixPatternProvider() = default;
virtual char16_t charAt(int flags, int i) const = 0;
virtual int length(int flags) const = 0;
virtual bool hasCurrencySign() const = 0;
virtual bool positiveHasPlusSign() const = 0;
virtual bool hasNegativeSubpattern() const = 0;
virtual bool negativeHasMinusSign() const = 0;
virtual bool containsSymbolType(AffixPatternType, UErrorCode &) const = 0;
};
/**
* 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 MurkyModifier}, which are mutable for performance
* reasons.
*/
class U_I18N_API Modifier {
public:
virtual ~Modifier() = default;
/**
* Apply this Modifier to the string builder.
*
* @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.
* @param rightIndex
* 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.
*/
virtual int32_t
apply(NumberStringBuilder &output, int leftIndex, int rightIndex, UErrorCode &status) const = 0;
/**
* 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.
*/
virtual int32_t getPrefixLength(UErrorCode& status) const = 0;
/**
* Returns the number of code points in the modifier, prefix plus suffix.
*/
virtual int32_t getCodePointCount(UErrorCode &status) const = 0;
/**
* 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.
*/
virtual bool isStrong() const = 0;
};
/**
* 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>.
*
* <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 tail 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.
*
* @author sffc
*
*/
class U_I18N_API MicroPropsGenerator {
public:
virtual ~MicroPropsGenerator() = default;
/**
* Considers the given {@link DecimalQuantity}, optionally mutates it, and returns a {@link MicroProps}.
*
* @param quantity
* The quantity for consideration and optional mutation.
* @param micros
* The MicroProps instance to populate.
* @return A MicroProps instance resolved for the quantity.
*/
virtual void processQuantity(DecimalQuantity& quantity, MicroProps& micros, UErrorCode& status) const = 0;
};
class MultiplierProducer {
public:
virtual ~MultiplierProducer() = default;
virtual int32_t getMultiplier(int32_t magnitude) const = 0;
};
template<typename T>
class NullableValue {
public:
NullableValue() : fNull(true) {}
NullableValue(const NullableValue<T> &other) = default;
explicit NullableValue(const T &other) {
fValue = other;
fNull = false;
}
NullableValue<T> &operator=(const NullableValue<T> &other) = default;
NullableValue<T> &operator=(const T &other) {
fValue = other;
fNull = false;
return *this;
}
bool operator==(const NullableValue &other) const {
return fNull ? other.fNull : fValue == other.fValue;
}
void nullify() {
// TODO: It might be nice to call the destructor here.
fNull = true;
}
bool isNull() const {
return fNull;
}
T get(UErrorCode &status) const {
if (fNull) {
status = U_UNDEFINED_VARIABLE;
}
return fValue;
}
private:
bool fNull;
T fValue;
};
} // namespace impl
} // namespace number
U_NAMESPACE_END
#endif //__NUMBER_TYPES_H__
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,128 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#ifndef __NUMBER_UTILS_H__
#define __NUMBER_UTILS_H__
#include "unicode/numberformatter.h"
#include "number_types.h"
#include "number_decimalquantity.h"
#include "number_scientific.h"
#include "number_patternstring.h"
#include "number_modifiers.h"
U_NAMESPACE_BEGIN namespace number {
namespace impl {
class UnicodeStringCharSequence : public CharSequence {
public:
explicit UnicodeStringCharSequence(const UnicodeString &other) {
fStr = other;
}
~UnicodeStringCharSequence() override = default;
int32_t length() const override {
return fStr.length();
}
char16_t charAt(int32_t index) const override {
return fStr.charAt(index);
}
UChar32 codePointAt(int32_t index) const override {
return fStr.char32At(index);
}
UnicodeString toUnicodeString() const override {
// Allocate a UnicodeString of the correct length
UnicodeString output(length(), 0, -1);
for (int32_t i = 0; i < length(); i++) {
output.append(charAt(i));
}
return output;
}
private:
UnicodeString fStr;
};
struct MicroProps : public MicroPropsGenerator {
// NOTE: All of these fields are properly initialized in NumberFormatterImpl.
Rounder rounding;
Grouper grouping;
Padder padding;
IntegerWidth integerWidth;
UNumberSignDisplay sign;
UNumberDecimalSeparatorDisplay decimal;
bool useCurrency;
// Note: This struct has no direct ownership of the following pointers.
const DecimalFormatSymbols *symbols;
const Modifier *modOuter;
const Modifier *modMiddle;
const Modifier *modInner;
// The following "helper" fields may optionally be used during the MicroPropsGenerator.
// They live here to retain memory.
struct {
ScientificModifier scientificModifier;
EmptyModifier emptyWeakModifier{false};
EmptyModifier emptyStrongModifier{true};
} helpers;
MicroProps() = default;
MicroProps(const MicroProps &other) = default;
MicroProps &operator=(const MicroProps &other) = default;
void processQuantity(DecimalQuantity &, MicroProps &micros, UErrorCode &status) const override {
(void)status;
if (this == &micros) {
// Unsafe path: no need to perform a copy.
U_ASSERT(!exhausted);
micros.exhausted = true;
U_ASSERT(exhausted);
} else {
// Safe path: copy self into the output micros.
micros = *this;
}
}
private:
// Internal fields:
bool exhausted = false;
};
/**
* This struct provides the result of the number formatting pipeline to FormattedNumber.
*
* The DecimalQuantity is not currently being used by FormattedNumber, but at some point it could be used
* to add a toDecNumber() or similar method.
*/
struct NumberFormatterResults : public UMemory {
DecimalQuantity quantity;
NumberStringBuilder string;
};
inline const UnicodeString getDigitFromSymbols(int8_t digit, const DecimalFormatSymbols &symbols) {
// TODO: Implement DecimalFormatSymbols.getCodePointZero()?
if (digit == 0) {
return symbols.getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kZeroDigitSymbol);
} else {
return symbols.getSymbol(static_cast<DecimalFormatSymbols::ENumberFormatSymbol>(
DecimalFormatSymbols::ENumberFormatSymbol::kOneDigitSymbol + digit - 1));
}
}
} // namespace impl
} // namespace number
U_NAMESPACE_END
#endif //__NUMBER_UTILS_H__
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -268,7 +268,7 @@ PluralRules::select(const Formattable& obj, const NumberFormat& fmt, UErrorCode&
}
UnicodeString
PluralRules::select(const FixedDecimal &number) const {
PluralRules::select(const IFixedDecimal &number) const {
if (mRules == NULL) {
return UnicodeString(TRUE, PLURAL_DEFAULT_RULE, -1);
}
@ -783,15 +783,15 @@ AndConstraint::~AndConstraint() {
UBool
AndConstraint::isFulfilled(const FixedDecimal &number) {
AndConstraint::isFulfilled(const IFixedDecimal &number) {
UBool result = TRUE;
if (digitsType == none) {
// An empty AndConstraint, created by a rule with a keyword but no following expression.
return TRUE;
}
double n = number.get(digitsType); // pulls n | i | v | f value for the number.
// Will always be positive.
// May be non-integer (n option only)
double n = number.getPluralOperand(digitsType); // pulls n | i | v | f value for the number.
// Will always be positive.
// May be non-integer (n option only)
do {
if (integerOnly && n != uprv_floor(n)) {
result = FALSE;
@ -873,7 +873,7 @@ OrConstraint::add()
}
UBool
OrConstraint::isFulfilled(const FixedDecimal &number) {
OrConstraint::isFulfilled(const IFixedDecimal &number) {
OrConstraint* orRule=this;
UBool result=FALSE;
@ -914,8 +914,8 @@ RuleChain::~RuleChain() {
UnicodeString
RuleChain::select(const FixedDecimal &number) const {
if (!number.isNanOrInfinity) {
RuleChain::select(const IFixedDecimal &number) const {
if (!number.isNaN() && !number.isInfinite()) {
for (const RuleChain *rules = this; rules != NULL; rules = rules->fNext) {
if (rules->ruleHeader->isFulfilled(number)) {
return rules->fKeyword;
@ -1411,7 +1411,8 @@ FixedDecimal::FixedDecimal(const VisibleDigits &digits) {
decimalDigitsWithoutTrailingZeros,
visibleDecimalDigitCount, hasIntegerValue);
isNegative = digits.isNegative();
isNanOrInfinity = digits.isNaNOrInfinity();
_isNaN = digits.isNaN();
_isInfinite = digits.isInfinite();
}
FixedDecimal::FixedDecimal(double n, int32_t v, int64_t f) {
@ -1476,7 +1477,8 @@ FixedDecimal::FixedDecimal(const FixedDecimal &other) {
intValue = other.intValue;
hasIntegerValue = other.hasIntegerValue;
isNegative = other.isNegative;
isNanOrInfinity = other.isNanOrInfinity;
_isNaN = other._isNaN;
_isInfinite = other._isInfinite;
}
@ -1489,8 +1491,9 @@ void FixedDecimal::init(double n) {
void FixedDecimal::init(double n, int32_t v, int64_t f) {
isNegative = n < 0.0;
source = fabs(n);
isNanOrInfinity = uprv_isNaN(source) || uprv_isPositiveInfinity(source);
if (isNanOrInfinity) {
_isNaN = uprv_isNaN(source);
_isInfinite = uprv_isInfinite(source);
if (_isNaN || _isInfinite) {
v = 0;
f = 0;
intValue = 0;
@ -1610,19 +1613,31 @@ void FixedDecimal::adjustForMinFractionDigits(int32_t minFractionDigits) {
}
double FixedDecimal::get(tokenType operand) const {
double FixedDecimal::getPluralOperand(PluralOperand operand) const {
switch(operand) {
case tVariableN: return source;
case tVariableI: return (double)intValue;
case tVariableF: return (double)decimalDigits;
case tVariableT: return (double)decimalDigitsWithoutTrailingZeros;
case tVariableV: return visibleDecimalDigitCount;
case PLURAL_OPERAND_N: return source;
case PLURAL_OPERAND_I: return intValue;
case PLURAL_OPERAND_F: return decimalDigits;
case PLURAL_OPERAND_T: return decimalDigitsWithoutTrailingZeros;
case PLURAL_OPERAND_V: return visibleDecimalDigitCount;
default:
U_ASSERT(FALSE); // unexpected.
return source;
}
}
bool FixedDecimal::isNaN() const {
return _isNaN;
}
bool FixedDecimal::isInfinite() const {
return _isInfinite;
}
bool FixedDecimal::isNanOrInfinity() const {
return _isNaN || _isInfinite;
}
int32_t FixedDecimal::getVisibleFractionDigitCount() const {
return visibleDecimalDigitCount;
}

View file

@ -28,6 +28,7 @@
#include "unicode/ures.h"
#include "uvector.h"
#include "hash.h"
#include "uassert.h"
class PluralRulesTest;
@ -177,6 +178,87 @@ private:
};
enum PluralOperand {
/**
* The double value of the entire number.
*/
PLURAL_OPERAND_N,
/**
* The integer value, with the fraction digits truncated off.
*/
PLURAL_OPERAND_I,
/**
* All visible fraction digits as an integer, including trailing zeros.
*/
PLURAL_OPERAND_F,
/**
* Visible fraction digits as an integer, not including trailing zeros.
*/
PLURAL_OPERAND_T,
/**
* Number of visible fraction digits.
*/
PLURAL_OPERAND_V,
/**
* Number of visible fraction digits, not including trailing zeros.
*/
PLURAL_OPERAND_W,
/**
* THIS OPERAND IS DEPRECATED AND HAS BEEN REMOVED FROM THE SPEC.
*
* <p>Returns the integer value, but will fail if the number has fraction digits.
* That is, using "j" instead of "i" is like implicitly adding "v is 0".
*
* <p>For example, "j is 3" is equivalent to "i is 3 and v is 0": it matches
* "3" but not "3.1" or "3.0".
*/
PLURAL_OPERAND_J
};
/**
* An interface to FixedDecimal, allowing for other implementations.
* @internal
*/
class U_I18N_API IFixedDecimal {
public:
virtual ~IFixedDecimal() = default;
/**
* Returns the value corresponding to the specified operand (n, i, f, t, v, or w).
* If the operand is 'n', returns a double; otherwise, returns an integer.
*/
virtual double getPluralOperand(PluralOperand operand) const = 0;
/** Converts from the tokenType enum to PluralOperand. */
virtual double getPluralOperand(tokenType tt) const {
switch(tt) {
case tVariableN:
return getPluralOperand(PLURAL_OPERAND_N);
case tVariableI:
return getPluralOperand(PLURAL_OPERAND_I);
case tVariableF:
return getPluralOperand(PLURAL_OPERAND_F);
case tVariableV:
return getPluralOperand(PLURAL_OPERAND_V);
case tVariableT:
return getPluralOperand(PLURAL_OPERAND_T);
default:
U_ASSERT(FALSE); // unexpected.
return 0.0;
}
}
virtual bool isNaN() const = 0;
virtual bool isInfinite() const = 0;
};
/**
* class FixedDecimal serves to communicate the properties
* of a formatted number from a decimal formatter to PluralRules::select()
@ -184,7 +266,7 @@ private:
* see DecimalFormat::getFixedDecimal()
* @internal
*/
class U_I18N_API FixedDecimal: public UMemory {
class U_I18N_API FixedDecimal: public IFixedDecimal, public UObject {
public:
/**
* @param n the number, e.g. 12.345
@ -196,10 +278,16 @@ class U_I18N_API FixedDecimal: public UMemory {
explicit FixedDecimal(double n);
explicit FixedDecimal(const VisibleDigits &n);
FixedDecimal();
~FixedDecimal() override = default;
FixedDecimal(const UnicodeString &s, UErrorCode &ec);
FixedDecimal(const FixedDecimal &other);
double get(tokenType operand) const;
double getPluralOperand(PluralOperand operand) const override;
bool isNaN() const override;
bool isInfinite() const override;
bool isNanOrInfinity() const; // used in decimfmtimpl.cpp
int32_t getVisibleFractionDigitCount() const;
void init(double n, int32_t v, int64_t f);
@ -217,7 +305,8 @@ class U_I18N_API FixedDecimal: public UMemory {
int64_t intValue;
UBool hasIntegerValue;
UBool isNegative;
UBool isNanOrInfinity;
UBool _isNaN;
UBool _isInfinite;
};
class AndConstraint : public UMemory {
@ -240,7 +329,7 @@ public:
virtual ~AndConstraint();
AndConstraint* add();
// UBool isFulfilled(double number);
UBool isFulfilled(const FixedDecimal &number);
UBool isFulfilled(const IFixedDecimal &number);
};
class OrConstraint : public UMemory {
@ -253,7 +342,7 @@ public:
virtual ~OrConstraint();
AndConstraint* add();
// UBool isFulfilled(double number);
UBool isFulfilled(const FixedDecimal &number);
UBool isFulfilled(const IFixedDecimal &number);
};
class RuleChain : public UMemory {
@ -271,7 +360,7 @@ public:
RuleChain(const RuleChain& other);
virtual ~RuleChain();
UnicodeString select(const FixedDecimal &number) const;
UnicodeString select(const IFixedDecimal &number) const;
void dumpRules(UnicodeString& result);
UErrorCode getKeywords(int32_t maxArraySize, UnicodeString *keywords, int32_t& arraySize) const;
UBool isKeyword(const UnicodeString& keyword) const;

View file

@ -26,6 +26,7 @@ as the functions are suppose to be called.
It's usually best to have child dependencies called first. */
typedef enum ECleanupI18NType {
UCLN_I18N_START = -1,
UCLN_I18N_CURRENCY_SPACING,
UCLN_I18N_SPOOF,
UCLN_I18N_SPOOFDATA,
UCLN_I18N_TRANSLITERATOR,

View file

@ -36,6 +36,12 @@ U_NAMESPACE_BEGIN
*/
class U_I18N_API CurrencyUnit: public MeasureUnit {
public:
/**
* Default constructor. Initializes currency code to "XXX" (no currency).
* @draft ICU 60
*/
CurrencyUnit();
/**
* Construct an object with the given ISO currency code.
* @param isoCode the 3-letter ISO 4217 currency code; must not be
@ -52,6 +58,16 @@ class U_I18N_API CurrencyUnit: public MeasureUnit {
*/
CurrencyUnit(const CurrencyUnit& other);
/**
* Copy constructor from MeasureUnit. This constructor allows you to
* restore a CurrencyUnit that was sliced to MeasureUnit.
*
* @param measureUnit The MeasureUnit to copy from.
* @param ec Set to a failing value if the MeasureUnit is not a currency.
* @draft ICU 60
*/
CurrencyUnit(const MeasureUnit& measureUnit, UErrorCode &ec);
/**
* Assignment operator
* @stable ICU 3.0

View file

@ -47,6 +47,13 @@ U_NAMESPACE_BEGIN
class UVector32;
// Forward declaration for number formatting:
namespace number {
namespace impl {
class NumberStringBuilder;
}
}
/**
* FieldPositionIterator returns the field ids and their start/limit positions generated
* by a call to Format::format. See Format, NumberFormat, DecimalFormat.
@ -99,8 +106,6 @@ public:
UBool next(FieldPosition& fp);
private:
friend class FieldPositionIteratorHandler;
/**
* Sets the data used by the iterator, and resets the position.
* Returns U_ILLEGAL_ARGUMENT_ERROR in status if the data is not valid
@ -108,6 +113,9 @@ private:
*/
void setData(UVector32 *adopt, UErrorCode& status);
friend class FieldPositionIteratorHandler;
friend class number::impl::NumberStringBuilder;
UVector32 *data;
int32_t pos;
};

View file

@ -40,11 +40,10 @@ class U_I18N_API MeasureUnit: public UObject {
/**
* Default constructor.
* Populates the instance with the base dimensionless unit.
* @stable ICU 3.0
*/
MeasureUnit() : fTypeId(0), fSubTypeId(0) {
fCurrency[0] = 0;
}
MeasureUnit();
/**
* Copy constructor.
@ -1317,6 +1316,12 @@ class U_I18N_API MeasureUnit: public UObject {
*/
void initCurrency(const char *isoCurrency);
/**
* For ICU use only.
* @internal
*/
void initNoUnit(const char *subtype);
#endif /* U_HIDE_INTERNAL_API */
private:

View file

@ -0,0 +1,110 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*
*******************************************************************************
* Copyright (C) 2009-2017, International Business Machines Corporation, *
* Google, and others. All Rights Reserved. *
*******************************************************************************
*/
#ifndef __NOUNIT_H__
#define __NOUNIT_H__
/**
* \file
* \brief C++ API: units for percent and permille
*/
#include "unicode/measunit.h"
#if !UCONFIG_NO_FORMATTING
U_NAMESPACE_BEGIN
/**
* Dimensionless unit for percent and permille.
* @see NumberFormatter
* @draft ICU 60
*/
class U_I18N_API NoUnit: public MeasureUnit {
public:
/**
* Returns an instance for the base unit (dimensionless and no scaling).
*
* @return a NoUnit instance
* @draft ICU 60
*/
static NoUnit U_EXPORT2 base();
/**
* Returns an instance for percent, or 1/100 of a base unit.
*
* @return a NoUnit instance
* @draft ICU 60
*/
static NoUnit U_EXPORT2 percent();
/**
* Returns an instance for permille, or 1/1000 of a base unit.
*
* @return a NoUnit instance
* @draft ICU 60
*/
static NoUnit U_EXPORT2 permille();
/**
* Copy operator.
* @draft ICU 60
*/
NoUnit(const NoUnit& other);
/**
* Return a polymorphic clone of this object. The result will
* have the same class as returned by getDynamicClassID().
* @stable ICU 3.0
*/
virtual UObject* clone() const;
/**
* Returns a unique class ID for this object POLYMORPHICALLY.
* This method implements a simple form of RTTI used by ICU.
* @return The class ID for this object. All objects of a given
* class have the same class ID. Objects of other classes have
* different class IDs.
* @draft ICU 60
*/
virtual UClassID getDynamicClassID() const;
/**
* Returns the class ID for this class. This is used to compare to
* the return value of getDynamicClassID().
* @return The class ID for all objects of this class.
* @draft ICU 60
*/
static UClassID U_EXPORT2 getStaticClassID();
/**
* Destructor.
* @draft ICU 60
*/
virtual ~NoUnit();
private:
/**
* Constructor
* @internal (private)
*/
NoUnit(const char* subtype);
};
U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */
#endif // __NOUNIT_H__
//eof
//

File diff suppressed because it is too large Load diff

View file

@ -58,6 +58,11 @@ class StringEnumeration;
* formatting and parsing a number. Also provides methods for
* determining which locales have number formats, and what their names
* are.
*
* <p><strong>NOTE:</strong> Starting in ICU 60, there is a new set of APIs for localized number
* formatting that are designed to be an improvement over DecimalFormat. New users are discouraged
* from using DecimalFormat. For more information, see numberformatter.h.
*
* \headerfile unicode/numfmt.h "unicode/numfmt.h"
* <P>
* NumberFormat helps you to format and parse numbers for any locale.
@ -170,6 +175,11 @@ class U_I18N_API NumberFormat : public Format {
public:
/**
* Rounding mode.
*
* <p>
* For more detail on rounding modes, see:
* http://userguide.icu-project.org/formatparse/numbers/rounding-modes
*
* @stable ICU 2.4
*/
enum ERoundingMode {

View file

@ -43,7 +43,7 @@
U_NAMESPACE_BEGIN
class Hashtable;
class FixedDecimal;
class IFixedDecimal;
class VisibleDigitsWithExponent;
class RuleChain;
class PluralRuleParser;
@ -367,7 +367,7 @@ public:
/**
* @internal
*/
UnicodeString select(const FixedDecimal &number) const;
UnicodeString select(const IFixedDecimal &number) const;
/**
* @internal
*/

View file

@ -264,8 +264,13 @@ typedef enum UNumberFormatStyle {
UNUM_IGNORE = UNUM_PATTERN_DECIMAL
} UNumberFormatStyle;
/** The possible number format rounding modes.
* @stable ICU 2.0
/** The possible number format rounding modes.
*
* <p>
* For more detail on rounding modes, see:
* http://userguide.icu-project.org/formatparse/numbers/rounding-modes
*
* @stable ICU 2.0
*/
typedef enum UNumberFormatRoundingMode {
UNUM_ROUND_CEILING,

View file

@ -61,7 +61,10 @@ windttst.o winnmtst.o winutil.o csdetest.o tzrulets.o tzoffloc.o tzfmttst.o ssea
tufmtts.o itspoof.o simplethread.o bidiconf.o locnmtst.o dcfmtest.o alphaindextst.o listformattertest.o genderinfotest.o compactdecimalformattest.o regiontst.o \
reldatefmttest.o simpleformattertest.o measfmttest.o numfmtspectest.o unifiedcachetest.o quantityformattertest.o \
scientificnumberformattertest.o datadrivennumberformattestsuite.o \
numberformattesttuple.o numberformat2test.o pluralmaptest.o
numberformattesttuple.o numberformat2test.o pluralmaptest.o \
numbertest_affixutils.o numbertest_api.o numbertest_decimalquantity.o \
numbertest_modifiers.o numbertest_patternmodifier.o numbertest_patternstring.o \
numbertest_stringbuilder.o
DEPS = $(OBJECTS:.o=.d)

View file

@ -773,11 +773,11 @@ void IntlTestDecimalFormatAPI::TestFixedDecimal() {
fd = df->getFixedDecimal(uprv_getInfinity(), status);
TEST_ASSERT_STATUS(status);
ASSERT_EQUAL(TRUE, fd.isNanOrInfinity);
ASSERT_EQUAL(TRUE, fd.isNanOrInfinity());
fd = df->getFixedDecimal(0.0, status);
ASSERT_EQUAL(FALSE, fd.isNanOrInfinity);
ASSERT_EQUAL(FALSE, fd.isNanOrInfinity());
fd = df->getFixedDecimal(uprv_getNaN(), status);
ASSERT_EQUAL(TRUE, fd.isNanOrInfinity);
ASSERT_EQUAL(TRUE, fd.isNanOrInfinity());
TEST_ASSERT_STATUS(status);
// Test Big Decimal input.

View file

@ -109,6 +109,18 @@ Int64ToUnicodeString(int64_t num)
return buffer;
}
UnicodeString
DoubleToUnicodeString(double num)
{
char buffer[64]; // nos changed from 10 to 64
char danger = 'p'; // guard against overrunning the buffer (rtg)
sprintf(buffer, "%1.14e", num);
assert(danger == 'p');
return buffer;
}
// [LIU] Just to get things working
UnicodeString
operator+(const UnicodeString& left,

View file

@ -30,6 +30,7 @@ U_NAMESPACE_USE
//string-concatenation operator (moved from findword test by rtg)
UnicodeString UCharToUnicodeString(UChar c);
UnicodeString Int64ToUnicodeString(int64_t num);
UnicodeString DoubleToUnicodeString(double num);
//UnicodeString operator+(const UnicodeString& left, int64_t num); // Some compilers don't allow this because of the long type.
UnicodeString operator+(const UnicodeString& left, long num);
UnicodeString operator+(const UnicodeString& left, unsigned long num);

View file

@ -324,6 +324,13 @@
<ClCompile Include="nmfmapts.cpp" />
<ClCompile Include="nmfmtrt.cpp" />
<ClCompile Include="numberformattesttuple.cpp" />
<ClCompile Include="numbertest_affixutils.cpp" />
<ClCompile Include="numbertest_api.cpp" />
<ClCompile Include="numbertest_decimalquantity.cpp" />
<ClCompile Include="numbertest_modifiers.cpp" />
<ClCompile Include="numbertest_patternmodifier.cpp" />
<ClCompile Include="numbertest_patternstring.cpp" />
<ClCompile Include="numbertest_stringbuilder.cpp" />
<ClCompile Include="numberformat2test.cpp" />
<ClCompile Include="numfmtst.cpp" />
<ClCompile Include="numrgts.cpp" />
@ -495,6 +502,7 @@
<ClInclude Include="msfmrgts.h" />
<ClInclude Include="nmfmapts.h" />
<ClInclude Include="nmfmtrt.h" />
<ClInclude Include="numbertest.h" />
<ClInclude Include="numberformattesttuple.h" />
<ClInclude Include="numfmtst.h" />
<ClInclude Include="numrgts.h" />

View file

@ -259,6 +259,27 @@
<ClCompile Include="numberformat2test.cpp">
<Filter>formatting</Filter>
</ClCompile>
<ClCompile Include="numbertest_affixutils.cpp">
<Filter>formatting</Filter>
</ClCompile>
<ClCompile Include="numbertest_api.cpp">
<Filter>formatting</Filter>
</ClCompile>
<ClCompile Include="numbertest_decimalquantity.cpp">
<Filter>formatting</Filter>
</ClCompile>
<ClCompile Include="numbertest_modifiers.cpp">
<Filter>formatting</Filter>
</ClCompile>
<ClCompile Include="numbertest_patternmodifier.cpp">
<Filter>formatting</Filter>
</ClCompile>
<ClCompile Include="numbertest_patternstring.cpp">
<Filter>formatting</Filter>
</ClCompile>
<ClCompile Include="numbertest_stringbuilder.cpp">
<Filter>formatting</Filter>
</ClCompile>
<ClCompile Include="numfmtst.cpp">
<Filter>formatting</Filter>
</ClCompile>
@ -681,6 +702,9 @@
<ClInclude Include="numberformattesttuple.h">
<Filter>formatting</Filter>
</ClInclude>
<ClInclude Include="numbertest.h">
<Filter>formatting</Filter>
</ClInclude>
<ClInclude Include="numfmtst.h">
<Filter>formatting</Filter>
</ClInclude>

View file

@ -59,6 +59,7 @@
#include "dcfmtest.h" // DecimalFormatTest
#include "listformattertest.h" // ListFormatterTest
#include "regiontst.h" // RegionTest
#include "numbertest.h" // All NumberFormatter tests
extern IntlTest *createCompactDecimalFormatTest();
extern IntlTest *createGenderInfoTest();
@ -204,7 +205,7 @@ void IntlTestFormat::runIndexedTest( int32_t index, UBool exec, const char* &nam
callTest(*test, par);
}
break;
case 49:
case 49:
name = "ScientificNumberFormatterTest";
if (exec) {
logln("ScientificNumberFormatterTest test---");
@ -213,15 +214,16 @@ void IntlTestFormat::runIndexedTest( int32_t index, UBool exec, const char* &nam
callTest(*test, par);
}
break;
case 50:
name = "NumberFormat2Test";
case 50:
name = "NumberFormat2Test";
if (exec) {
logln("NumberFormat2Test test---");
logln((UnicodeString)"");
LocalPointer<IntlTest> test(createNumberFormat2Test());
callTest(*test, par);
}
break;
break;
TESTCLASS(51,NumberTest);
default: name = ""; break; //needed to end loop
}
if (exec) {

View file

@ -0,0 +1,201 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#pragma once
#include "number_stringbuilder.h"
#include "intltest.h"
#include "number_affixutils.h"
using namespace icu::number;
using namespace icu::number::impl;
////////////////////////////////////////////////////////////////////////////////////////
// INSTRUCTIONS: //
// To add new NumberFormat unit test classes, create a new class like the ones below, //
// and then add it as a switch statement in NumberTest at the bottom of this file. /////////
// To add new methods to existing unit test classes, add the method to the class declaration //
// below, and also add it to the class's implementation of runIndexedTest(). //
///////////////////////////////////////////////////////////////////////////////////////////////
class AffixUtilsTest : public IntlTest {
public:
void testEscape();
void testUnescape();
void testContainsReplaceType();
void testInvalid();
void testUnescapeWithSymbolProvider();
void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0);
private:
UnicodeString unescapeWithDefaults(const SymbolProvider &defaultProvider, UnicodeString input,
UErrorCode &status);
};
class NumberFormatterApiTest : public IntlTest {
public:
NumberFormatterApiTest();
NumberFormatterApiTest(UErrorCode &status);
void notationSimple();
void notationScientific();
void notationCompact();
void unitMeasure();
void unitCurrency();
void unitPercent();
void roundingFraction();
void roundingFigures();
void roundingFractionFigures();
void roundingOther();
void grouping();
void padding();
void integerWidth();
void symbols();
// TODO: Add this method if currency symbols override support is added.
//void symbolsOverride();
void sign();
void decimal();
void locale();
void formatTypes();
void errors();
void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0);
private:
CurrencyUnit USD;
CurrencyUnit GBP;
CurrencyUnit CZK;
CurrencyUnit CAD;
MeasureUnit METER;
MeasureUnit DAY;
MeasureUnit SQUARE_METER;
MeasureUnit FAHRENHEIT;
NumberingSystem MATHSANB;
NumberingSystem LATN;
DecimalFormatSymbols FRENCH_SYMBOLS;
DecimalFormatSymbols SWISS_SYMBOLS;
DecimalFormatSymbols MYANMAR_SYMBOLS;
void assertFormatDescending(const UnicodeString &message, const UnlocalizedNumberFormatter &f,
Locale locale, ...);
void assertFormatDescendingBig(const UnicodeString &message, const UnlocalizedNumberFormatter &f,
Locale locale, ...);
void assertFormatSingle(const UnicodeString &message, const UnlocalizedNumberFormatter &f,
Locale locale, double input, const UnicodeString &expected);
};
class DecimalQuantityTest : public IntlTest {
public:
void testDecimalQuantityBehaviorStandalone();
void testSwitchStorage();
void testAppend();
void testConvertToAccurateDouble();
void testUseApproximateDoubleWhenAble();
void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0);
private:
void assertDoubleEquals(UnicodeString message, double a, double b);
void assertHealth(const DecimalQuantity &fq);
void assertToStringAndHealth(const DecimalQuantity &fq, const UnicodeString &expected);
void checkDoubleBehavior(double d, bool explicitRequired);
};
class ModifiersTest : public IntlTest {
public:
void testConstantAffixModifier();
void testConstantMultiFieldModifier();
void testSimpleModifier();
void testCurrencySpacingEnabledModifier();
void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0);
private:
void assertModifierEquals(const Modifier &mod, int32_t expectedPrefixLength, bool expectedStrong,
UnicodeString expectedChars, UnicodeString expectedFields,
UErrorCode &status);
void assertModifierEquals(const Modifier &mod, NumberStringBuilder &sb, int32_t expectedPrefixLength,
bool expectedStrong, UnicodeString expectedChars,
UnicodeString expectedFields, UErrorCode &status);
};
class PatternModifierTest : public IntlTest {
public:
void testBasic();
void testMutableEqualsImmutable();
void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0);
private:
UnicodeString getPrefix(const MutablePatternModifier &mod, UErrorCode &status);
UnicodeString getSuffix(const MutablePatternModifier &mod, UErrorCode &status);
};
class PatternStringTest : public IntlTest {
public:
void testToPatternSimple();
void testExceptionOnInvalid();
void testBug13117();
void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0);
private:
};
class NumberStringBuilderTest : public IntlTest {
public:
void testInsertAppendUnicodeString();
void testInsertAppendCodePoint();
void testCopy();
void testFields();
void testUnlimitedCapacity();
void testCodePoints();
void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0);
private:
void assertEqualsImpl(const UnicodeString &a, const NumberStringBuilder &b);
};
// NOTE: This macro is identical to the one in itformat.cpp
#define TESTCLASS(id, TestClass) \
case id: \
name = #TestClass; \
if (exec) { \
logln(#TestClass " test---"); \
logln((UnicodeString)""); \
TestClass test; \
callTest(test, par); \
} \
break
class NumberTest : public IntlTest {
public:
void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0) {
if (exec) {
logln("TestSuite NumberTest: ");
}
switch (index) {
TESTCLASS(0, AffixUtilsTest);
TESTCLASS(1, NumberFormatterApiTest);
TESTCLASS(2, DecimalQuantityTest);
TESTCLASS(3, ModifiersTest);
TESTCLASS(4, PatternModifierTest);
TESTCLASS(5, PatternStringTest);
TESTCLASS(6, NumberStringBuilderTest);
default: name = ""; break; // needed to end loop
}
}
};
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,246 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#include "putilimp.h"
#include "unicode/dcfmtsym.h"
#include "numbertest.h"
#include "number_utils.h"
using namespace icu::number::impl;
class DefaultSymbolProvider : public SymbolProvider {
DecimalFormatSymbols fSymbols;
public:
DefaultSymbolProvider(UErrorCode &status) : fSymbols(Locale("ar_SA"), status) {}
virtual UnicodeString getSymbol(AffixPatternType type) const {
switch (type) {
case TYPE_MINUS_SIGN:
return u"";
case TYPE_PLUS_SIGN:
return fSymbols.getConstSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kPlusSignSymbol);
case TYPE_PERCENT:
return fSymbols.getConstSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kPercentSymbol);
case TYPE_PERMILLE:
return fSymbols.getConstSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kPerMillSymbol);
case TYPE_CURRENCY_SINGLE:
return u"$";
case TYPE_CURRENCY_DOUBLE:
return u"XXX";
case TYPE_CURRENCY_TRIPLE:
return u"long name";
case TYPE_CURRENCY_QUAD:
return u"\uFFFD";
case TYPE_CURRENCY_QUINT:
// TODO: Add support for narrow currency symbols here.
return u"\uFFFD";
case TYPE_CURRENCY_OVERFLOW:
return u"\uFFFD";
default:
U_ASSERT(false);
return 0; // silence compiler warnings
}
}
};
void AffixUtilsTest::runIndexedTest(int32_t index, UBool exec, const char *&name, char *) {
if (exec) {
logln("TestSuite AffixUtilsTest: ");
}
TESTCASE_AUTO_BEGIN;
TESTCASE_AUTO(testEscape);
TESTCASE_AUTO(testUnescape);
TESTCASE_AUTO(testContainsReplaceType);
TESTCASE_AUTO(testInvalid);
TESTCASE_AUTO(testUnescapeWithSymbolProvider);
TESTCASE_AUTO_END;
}
void AffixUtilsTest::testEscape() {
static const char16_t *cases[][2] = {{u"", u""},
{u"abc", u"abc"},
{u"-", u"'-'"},
{u"-!", u"'-'!"},
{u"", u""},
{u"---", u"'---'"},
{u"-%-", u"'-%-'"},
{u"'", u"''"},
{u"-'", u"'-'''"},
{u"-'-", u"'-''-'"},
{u"a-'-", u"a'-''-'"}};
for (auto &cas : cases) {
UnicodeString input(cas[0]);
UnicodeString expected(cas[1]);
UnicodeString result = AffixUtils::escape(UnicodeStringCharSequence(input));
assertEquals(input, expected, result);
}
}
void AffixUtilsTest::testUnescape() {
static struct TestCase {
const char16_t *input;
bool currency;
int32_t expectedLength;
const char16_t *output;
} cases[] = {{u"", false, 0, u""},
{u"abc", false, 3, u"abc"},
{u"-", false, 1, u""},
{u"-!", false, 2, u"!"},
{u"+", false, 1, u"\u061C+"},
{u"+!", false, 2, u"\u061C+!"},
{u"", false, 1, u"؉"},
{u"‰!", false, 2, u"؉!"},
{u"-x", false, 2, u"x"},
{u"'-'x", false, 2, u"-x"},
{u"'--''-'-x", false, 6, u"--'-x"},
{u"''", false, 1, u"'"},
{u"''''", false, 2, u"''"},
{u"''''''", false, 3, u"'''"},
{u"''x''", false, 3, u"'x'"},
{u"¤", true, 1, u"$"},
{u"¤¤", true, 2, u"XXX"},
{u"¤¤¤", true, 3, u"long name"},
{u"¤¤¤¤", true, 4, u"\uFFFD"},
{u"¤¤¤¤¤", true, 5, u"\uFFFD"},
{u"¤¤¤¤¤¤", true, 6, u"\uFFFD"},
{u"¤¤¤a¤¤¤¤", true, 8, u"long namea\uFFFD"},
{u"a¤¤¤¤b¤¤¤¤¤c", true, 12, u"a\uFFFDb\uFFFDc"},
{u"¤!", true, 2, u"$!"},
{u"¤¤!", true, 3, u"XXX!"},
{u"¤¤¤!", true, 4, u"long name!"},
{u"-¤¤", true, 3, u"XXX"},
{u"¤¤-", true, 3, u"XXX"},
{u"'¤'", false, 1, u"¤"},
{u"%", false, 1, u"٪\u061C"},
{u"'%'", false, 1, u"%"},
{u"¤'-'%", true, 3, u"$-٪\u061C"},
{u"#0#@#*#;#", false, 9, u"#0#@#*#;#"}};
UErrorCode status = U_ZERO_ERROR;
DefaultSymbolProvider defaultProvider(status);
assertSuccess("Constructing DefaultSymbolProvider", status);
for (TestCase cas : cases) {
UnicodeString input(cas.input);
UnicodeString output(cas.output);
assertEquals(input, cas.currency, AffixUtils::hasCurrencySymbols(UnicodeStringCharSequence(input), status));
assertSuccess("Spot 1", status);
assertEquals(input, cas.expectedLength, AffixUtils::estimateLength(UnicodeStringCharSequence(input), status));
assertSuccess("Spot 2", status);
UnicodeString actual = unescapeWithDefaults(defaultProvider, input, status);
assertSuccess("Spot 3", status);
assertEquals(input, output, actual);
int32_t ulength = AffixUtils::unescapedCodePointCount(UnicodeStringCharSequence(input), defaultProvider, status);
assertSuccess("Spot 4", status);
assertEquals(input, output.countChar32(), ulength);
}
}
void AffixUtilsTest::testContainsReplaceType() {
static struct TestCase {
const char16_t *input;
bool hasMinusSign;
const char16_t *output;
} cases[] = {{u"", false, u""},
{u"-", true, u"+"},
{u"-a", true, u"+a"},
{u"a-", true, u"a+"},
{u"a-b", true, u"a+b"},
{u"--", true, u"++"},
{u"x", false, u"x"}};
UErrorCode status = U_ZERO_ERROR;
for (TestCase cas : cases) {
UnicodeString input(cas.input);
bool hasMinusSign = cas.hasMinusSign;
UnicodeString output(cas.output);
assertEquals(
input, hasMinusSign, AffixUtils::containsType(UnicodeStringCharSequence(input), TYPE_MINUS_SIGN, status));
assertSuccess("Spot 1", status);
assertEquals(
input, output, AffixUtils::replaceType(UnicodeStringCharSequence(input), TYPE_MINUS_SIGN, u'+', status));
assertSuccess("Spot 2", status);
}
}
void AffixUtilsTest::testInvalid() {
static const char16_t *invalidExamples[] = {
u"'", u"x'", u"'x", u"'x''", u"''x'"};
UErrorCode status = U_ZERO_ERROR;
DefaultSymbolProvider defaultProvider(status);
assertSuccess("Constructing DefaultSymbolProvider", status);
for (const char16_t *strPtr : invalidExamples) {
UnicodeString str(strPtr);
status = U_ZERO_ERROR;
AffixUtils::hasCurrencySymbols(UnicodeStringCharSequence(str), status);
assertEquals("Should set error code spot 1", status, U_ILLEGAL_ARGUMENT_ERROR);
status = U_ZERO_ERROR;
AffixUtils::estimateLength(UnicodeStringCharSequence(str), status);
assertEquals("Should set error code spot 2", status, U_ILLEGAL_ARGUMENT_ERROR);
status = U_ZERO_ERROR;
unescapeWithDefaults(defaultProvider, str, status);
assertEquals("Should set error code spot 3", status, U_ILLEGAL_ARGUMENT_ERROR);
}
}
class NumericSymbolProvider : public SymbolProvider {
public:
virtual UnicodeString getSymbol(AffixPatternType type) const {
return Int64ToUnicodeString(type < 0 ? -type : type);
}
};
void AffixUtilsTest::testUnescapeWithSymbolProvider() {
static const char16_t* cases[][2] = {
{u"", u""},
{u"-", u"1"},
{u"'-'", u"-"},
{u"- + % ‰ ¤ ¤¤ ¤¤¤ ¤¤¤¤ ¤¤¤¤¤", u"1 2 3 4 5 6 7 8 9"},
{u"'¤¤¤¤¤¤'", u"¤¤¤¤¤¤"},
{u"¤¤¤¤¤¤", u"\uFFFD"}
};
NumericSymbolProvider provider;
UErrorCode status = U_ZERO_ERROR;
NumberStringBuilder sb;
for (auto cas : cases) {
UnicodeString input(cas[0]);
UnicodeString expected(cas[1]);
sb.clear();
AffixUtils::unescape(UnicodeStringCharSequence(input), sb, 0, provider, status);
assertSuccess("Spot 1", status);
assertEquals(input, expected, sb.toUnicodeString());
}
// Test insertion position
sb.clear();
sb.append(u"abcdefg", UNUM_FIELD_COUNT, status);
assertSuccess("Spot 2", status);
AffixUtils::unescape(UnicodeStringCharSequence(UnicodeString(u"-+%")), sb, 4, provider, status);
assertSuccess("Spot 3", status);
assertEquals(u"Symbol provider into middle", u"abcd123efg", sb.toUnicodeString());
}
UnicodeString AffixUtilsTest::unescapeWithDefaults(const SymbolProvider &defaultProvider,
UnicodeString input, UErrorCode &status) {
NumberStringBuilder nsb;
int32_t length = AffixUtils::unescape(UnicodeStringCharSequence(input), nsb, 0, defaultProvider, status);
assertEquals("Return value of unescape", nsb.length(), length);
return nsb.toUnicodeString();
}
#endif /* #if !UCONFIG_NO_FORMATTING */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,265 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#include "number_decimalquantity.h"
#include "math.h"
#include <cmath>
#include "numbertest.h"
void DecimalQuantityTest::runIndexedTest(int32_t index, UBool exec, const char *&name, char *) {
if (exec) {
logln("TestSuite DecimalQuantityTest: ");
}
TESTCASE_AUTO_BEGIN;
TESTCASE_AUTO(testDecimalQuantityBehaviorStandalone);
TESTCASE_AUTO(testSwitchStorage);
TESTCASE_AUTO(testAppend);
TESTCASE_AUTO(testConvertToAccurateDouble);
TESTCASE_AUTO(testUseApproximateDoubleWhenAble);
TESTCASE_AUTO_END;
}
void DecimalQuantityTest::assertDoubleEquals(UnicodeString message, double a, double b) {
if (a == b) {
return;
}
double diff = a - b;
diff = diff < 0 ? -diff : diff;
double bound = a < 0 ? -a * 1e-6 : a * 1e-6;
if (diff > bound) {
errln(message + u": " + DoubleToUnicodeString(a) + u" vs " + DoubleToUnicodeString(b) + u" differ by " + DoubleToUnicodeString(diff));
}
}
void DecimalQuantityTest::assertHealth(const DecimalQuantity &fq) {
const char16_t* health = fq.checkHealth();
if (health != nullptr) {
errln(UnicodeString(u"HEALTH FAILURE: ") + UnicodeString(health) + u": " + fq.toString());
}
}
void
DecimalQuantityTest::assertToStringAndHealth(const DecimalQuantity &fq, const UnicodeString &expected) {
UnicodeString actual = fq.toString();
assertEquals("DecimalQuantity toString failed", expected, actual);
assertHealth(fq);
}
void DecimalQuantityTest::checkDoubleBehavior(double d, bool explicitRequired) {
DecimalQuantity fq;
fq.setToDouble(d);
if (explicitRequired) {
assertTrue("Should be using approximate double", !fq.isExplicitExactDouble());
}
UnicodeString baseStr = fq.toString();
assertDoubleEquals(
UnicodeString(u"Initial construction from hard double: ") + baseStr,
d, fq.toDouble());
fq.roundToInfinity();
UnicodeString newStr = fq.toString();
if (explicitRequired) {
assertTrue("Should not be using approximate double", fq.isExplicitExactDouble());
}
assertDoubleEquals(
UnicodeString(u"After conversion to exact BCD (double): ") + baseStr + u" vs " + newStr,
d, fq.toDouble());
}
void DecimalQuantityTest::testDecimalQuantityBehaviorStandalone() {
UErrorCode status = U_ZERO_ERROR;
DecimalQuantity fq;
assertToStringAndHealth(fq, u"<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(0.005, RoundingMode::UNUM_ROUND_HALFEVEN, 3, status);
assertSuccess("Rounding to increment", status);
assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 987655E-3>");
fq.roundToMagnitude(-2, RoundingMode::UNUM_ROUND_HALFEVEN, status);
assertSuccess("Rounding to magnitude", status);
assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 98766E-2>");
}
void DecimalQuantityTest::testSwitchStorage() {
UErrorCode status = U_ZERO_ERROR;
DecimalQuantity fq;
fq.setToLong(1234123412341234L);
assertFalse("Should not be using byte array", fq.isUsingBytes());
assertEquals("Failed on initialize", "1234123412341234E0", fq.toNumberString());
assertHealth(fq);
// Long -> Bytes
fq.appendDigit(5, 0, true);
assertTrue("Should be using byte array", fq.isUsingBytes());
assertEquals("Failed on multiply", "12341234123412345E0", fq.toNumberString());
assertHealth(fq);
// Bytes -> Long
fq.roundToMagnitude(5, RoundingMode::UNUM_ROUND_HALFEVEN, status);
assertSuccess("Rounding to magnitude", status);
assertFalse("Should not be using byte array", fq.isUsingBytes());
assertEquals("Failed on round", "123412341234E5", fq.toNumberString());
assertHealth(fq);
}
void DecimalQuantityTest::testAppend() {
DecimalQuantity fq;
fq.appendDigit(1, 0, true);
assertEquals("Failed on append", "1E0", fq.toNumberString());
assertHealth(fq);
fq.appendDigit(2, 0, true);
assertEquals("Failed on append", "12E0", fq.toNumberString());
assertHealth(fq);
fq.appendDigit(3, 1, true);
assertEquals("Failed on append", "1203E0", fq.toNumberString());
assertHealth(fq);
fq.appendDigit(0, 1, true);
assertEquals("Failed on append", "1203E2", fq.toNumberString());
assertHealth(fq);
fq.appendDigit(4, 0, true);
assertEquals("Failed on append", "1203004E0", fq.toNumberString());
assertHealth(fq);
fq.appendDigit(0, 0, true);
assertEquals("Failed on append", "1203004E1", fq.toNumberString());
assertHealth(fq);
fq.appendDigit(5, 0, false);
assertEquals("Failed on append", "120300405E-1", fq.toNumberString());
assertHealth(fq);
fq.appendDigit(6, 0, false);
assertEquals("Failed on append", "1203004056E-2", fq.toNumberString());
assertHealth(fq);
fq.appendDigit(7, 3, false);
assertEquals("Failed on append", "12030040560007E-6", fq.toNumberString());
assertHealth(fq);
UnicodeString baseExpected("12030040560007");
for (int i = 0; i < 10; i++) {
fq.appendDigit(8, 0, false);
baseExpected.append('8');
UnicodeString expected(baseExpected);
expected.append("E-");
if (i >= 3) {
expected.append('1');
}
expected.append(((7 + i) % 10) + '0');
assertEquals("Failed on append", expected, fq.toNumberString());
assertHealth(fq);
}
fq.appendDigit(9, 2, false);
baseExpected.append("009");
UnicodeString expected(baseExpected);
expected.append("E-19");
assertEquals("Failed on append", expected, fq.toNumberString());
assertHealth(fq);
}
void DecimalQuantityTest::testConvertToAccurateDouble() {
// based on https://github.com/google/double-conversion/issues/28
static 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,};
static 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);
}
assertDoubleEquals(u"NaN check failed", NAN, DecimalQuantity().setToDouble(NAN).toDouble());
assertDoubleEquals(
u"Inf check failed", INFINITY, DecimalQuantity().setToDouble(INFINITY).toDouble());
assertDoubleEquals(
u"-Inf check failed", -INFINITY, DecimalQuantity().setToDouble(-INFINITY).toDouble());
// Generate random doubles
for (int32_t i = 0; i < 10000; i++) {
uint8_t bytes[8];
for (int32_t j = 0; j < 8; j++) {
bytes[j] = static_cast<uint8_t>(rand() % 256);
}
double d;
uprv_memcpy(&d, bytes, 8);
if (std::isnan(d) || !std::isfinite(d)) { continue; }
checkDoubleBehavior(d, false);
}
}
void DecimalQuantityTest::testUseApproximateDoubleWhenAble() {
struct TestCase {
double d;
int32_t maxFrac;
RoundingMode roundingMode;
bool usesExact;
} cases[] = {{1.2345678, 1, RoundingMode::UNUM_ROUND_HALFEVEN, false},
{1.2345678, 7, RoundingMode::UNUM_ROUND_HALFEVEN, false},
{1.2345678, 12, RoundingMode::UNUM_ROUND_HALFEVEN, false},
{1.2345678, 13, RoundingMode::UNUM_ROUND_HALFEVEN, true},
{1.235, 1, RoundingMode::UNUM_ROUND_HALFEVEN, false},
{1.235, 2, RoundingMode::UNUM_ROUND_HALFEVEN, true},
{1.235, 3, RoundingMode::UNUM_ROUND_HALFEVEN, false},
{1.000000000000001, 0, RoundingMode::UNUM_ROUND_HALFEVEN, false},
{1.000000000000001, 0, RoundingMode::UNUM_ROUND_CEILING, true},
{1.235, 1, RoundingMode::UNUM_ROUND_CEILING, false},
{1.235, 2, RoundingMode::UNUM_ROUND_CEILING, false},
{1.235, 3, RoundingMode::UNUM_ROUND_CEILING, true}};
UErrorCode status = U_ZERO_ERROR;
for (TestCase cas : cases) {
DecimalQuantity fq;
fq.setToDouble(cas.d);
assertTrue("Should be using approximate double", !fq.isExplicitExactDouble());
fq.roundToMagnitude(-cas.maxFrac, cas.roundingMode, status);
assertSuccess("Rounding to magnitude", status);
if (cas.usesExact != fq.isExplicitExactDouble()) {
errln(UnicodeString(u"Using approximate double after rounding: ") + fq.toString());
}
}
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,178 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#include "putilimp.h"
#include "intltest.h"
#include "number_stringbuilder.h"
#include "number_modifiers.h"
#include "numbertest.h"
void ModifiersTest::runIndexedTest(int32_t index, UBool exec, const char *&name, char *) {
if (exec) {
logln("TestSuite ModifiersTest: ");
}
TESTCASE_AUTO_BEGIN;
TESTCASE_AUTO(testConstantAffixModifier);
TESTCASE_AUTO(testConstantMultiFieldModifier);
TESTCASE_AUTO(testSimpleModifier);
TESTCASE_AUTO(testCurrencySpacingEnabledModifier);
TESTCASE_AUTO_END;
}
void ModifiersTest::testConstantAffixModifier() {
UErrorCode status = U_ZERO_ERROR;
ConstantAffixModifier mod0(u"", u"", UNUM_PERCENT_FIELD, true);
assertModifierEquals(mod0, 0, true, u"|", u"n", status);
assertSuccess("Spot 1", status);
ConstantAffixModifier mod1(u"a📻", u"b", UNUM_PERCENT_FIELD, true);
assertModifierEquals(mod1, 3, true, u"a📻|b", u"%%%n%", status);
assertSuccess("Spot 2", status);
}
void ModifiersTest::testConstantMultiFieldModifier() {
UErrorCode status = U_ZERO_ERROR;
NumberStringBuilder prefix;
NumberStringBuilder suffix;
ConstantMultiFieldModifier mod1(prefix, suffix, true);
assertModifierEquals(mod1, 0, true, u"|", u"n", status);
assertSuccess("Spot 1", status);
prefix.append(u"a📻", UNUM_PERCENT_FIELD, status);
suffix.append(u"b", UNUM_CURRENCY_FIELD, status);
ConstantMultiFieldModifier mod2(prefix, suffix, true);
assertModifierEquals(mod2, 3, true, u"a📻|b", u"%%%n$", status);
assertSuccess("Spot 2", status);
// Make sure the first modifier is still the same (that it stayed constant)
assertModifierEquals(mod1, 0, true, u"|", u"n", status);
assertSuccess("Spot 3", status);
}
void ModifiersTest::testSimpleModifier() {
static const int32_t NUM_CASES = 5;
static const int32_t NUM_OUTPUTS = 4;
static const char16_t *patterns[] = {u"{0}", u"X{0}Y", u"XX{0}YYY", u"{0}YY", u"XX📺XX{0}"};
static const struct {
const char16_t *baseString;
int32_t leftIndex;
int32_t rightIndex;
} outputs[NUM_OUTPUTS] = {{u"", 0, 0}, {u"a📻bcde", 0, 0}, {u"a📻bcde", 4, 4}, {u"a📻bcde", 3, 5}};
static const int32_t prefixLens[] = {0, 1, 2, 0, 6};
static const char16_t *expectedCharFields[][2] = {{u"|", u"n"},
{u"X|Y", u"%n%"},
{u"XX|YYY", u"%%n%%%"},
{u"|YY", u"n%%"},
{u"XX📺XX|", u"%%%%%%n"}};
static const char16_t *expecteds[][NUM_CASES] = // force auto-format line break
{{
u"", u"XY", u"XXYYY", u"YY", u"XX📺XX"}, {
u"a📻bcde", u"XYa📻bcde", u"XXYYYa📻bcde", u"YYa📻bcde", u"XX📺XXa📻bcde"}, {
u"a📻bcde", u"a📻bXYcde", u"a📻bXXYYYcde", u"a📻bYYcde", u"a📻bXX📺XXcde"}, {
u"a📻bcde", u"a📻XbcYde", u"a📻XXbcYYYde", u"a📻bcYYde", u"a📻XX📺XXbcde"}};
UErrorCode status = U_ZERO_ERROR;
for (int32_t i = 0; i < NUM_CASES; i++) {
const UnicodeString pattern(patterns[i]);
SimpleFormatter compiledFormatter(pattern, 1, 1, status);
assertSuccess("Spot 1", status);
SimpleModifier mod(compiledFormatter, UNUM_PERCENT_FIELD, false);
assertModifierEquals(
mod, prefixLens[i], false, expectedCharFields[i][0], expectedCharFields[i][1], status);
assertSuccess("Spot 2", status);
// Test strange insertion positions
for (int32_t j = 0; j < NUM_OUTPUTS; j++) {
NumberStringBuilder output;
output.append(outputs[j].baseString, UNUM_FIELD_COUNT, status);
mod.apply(output, outputs[j].leftIndex, outputs[j].rightIndex, status);
UnicodeString expected = expecteds[j][i];
UnicodeString actual = output.toUnicodeString();
assertEquals("Strange insertion position", expected, actual);
assertSuccess("Spot 3", status);
}
}
}
void ModifiersTest::testCurrencySpacingEnabledModifier() {
UErrorCode status = U_ZERO_ERROR;
DecimalFormatSymbols symbols(Locale("en"), status);
assertSuccess("Spot 1", status);
NumberStringBuilder prefix;
NumberStringBuilder suffix;
CurrencySpacingEnabledModifier mod1(prefix, suffix, true, symbols, status);
assertSuccess("Spot 2", status);
assertModifierEquals(mod1, 0, true, u"|", u"n", status);
assertSuccess("Spot 3", status);
prefix.append(u"USD", UNUM_CURRENCY_FIELD, status);
assertSuccess("Spot 4", status);
CurrencySpacingEnabledModifier mod2(prefix, suffix, true, symbols, status);
assertSuccess("Spot 5", status);
assertModifierEquals(mod2, 3, true, u"USD|", u"$$$n", status);
assertSuccess("Spot 6", status);
// Test the default currency spacing rules
NumberStringBuilder sb;
sb.append("123", UNUM_INTEGER_FIELD, status);
assertSuccess("Spot 7", status);
NumberStringBuilder sb1(sb);
assertModifierEquals(mod2, sb1, 3, true, u"USD\u00A0123", u"$$$niii", status);
assertSuccess("Spot 8", status);
// Compare with the unsafe code path
NumberStringBuilder sb2(sb);
sb2.insert(0, "USD", UNUM_CURRENCY_FIELD, status);
assertSuccess("Spot 9", status);
CurrencySpacingEnabledModifier::applyCurrencySpacing(sb2, 0, 3, 6, 0, symbols, status);
assertSuccess("Spot 10", status);
assertTrue(sb1.toDebugString() + " vs " + sb2.toDebugString(), sb1.contentEquals(sb2));
// Test custom patterns
// The following line means that the last char of the number should be a | (rather than a digit)
symbols.setPatternForCurrencySpacing(UNUM_CURRENCY_SURROUNDING_MATCH, true, u"[|]");
suffix.append("XYZ", UNUM_CURRENCY_FIELD, status);
assertSuccess("Spot 11", status);
CurrencySpacingEnabledModifier mod3(prefix, suffix, true, symbols, status);
assertSuccess("Spot 12", status);
assertModifierEquals(mod3, 3, true, u"USD|\u00A0XYZ", u"$$$nn$$$", status);
assertSuccess("Spot 13", status);
}
void ModifiersTest::assertModifierEquals(const Modifier &mod, int32_t expectedPrefixLength,
bool expectedStrong, UnicodeString expectedChars,
UnicodeString expectedFields, UErrorCode &status) {
NumberStringBuilder sb;
sb.appendCodePoint('|', UNUM_FIELD_COUNT, status);
assertModifierEquals(
mod, sb, expectedPrefixLength, expectedStrong, expectedChars, expectedFields, status);
}
void ModifiersTest::assertModifierEquals(const Modifier &mod, NumberStringBuilder &sb,
int32_t expectedPrefixLength, bool expectedStrong,
UnicodeString expectedChars, UnicodeString expectedFields,
UErrorCode &status) {
int32_t oldCount = sb.codePointCount();
mod.apply(sb, 0, sb.length(), status);
assertEquals("Prefix length", expectedPrefixLength, mod.getPrefixLength(status));
assertEquals("Strong", expectedStrong, mod.isStrong());
if (dynamic_cast<const CurrencySpacingEnabledModifier*>(&mod) == nullptr) {
// i.e., if mod is not a CurrencySpacingEnabledModifier
assertEquals("Code point count equals actual code point count",
sb.codePointCount() - oldCount, mod.getCodePointCount(status));
}
UnicodeString debugString;
debugString.append(u"<NumberStringBuilder [");
debugString.append(expectedChars);
debugString.append(u"] [");
debugString.append(expectedFields);
debugString.append(u"]>");
assertEquals("Debug string", debugString, sb.toDebugString());
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,124 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#include "numbertest.h"
#include "number_patternmodifier.h"
void PatternModifierTest::runIndexedTest(int32_t index, UBool exec, const char *&name, char *) {
if (exec) {
logln("TestSuite PatternModifierTest: ");
}
TESTCASE_AUTO_BEGIN;
TESTCASE_AUTO(testBasic);
TESTCASE_AUTO(testMutableEqualsImmutable);
TESTCASE_AUTO_END;
}
void PatternModifierTest::testBasic() {
UErrorCode status = U_ZERO_ERROR;
MutablePatternModifier mod(false);
ParsedPatternInfo patternInfo;
PatternParser::parseToPatternInfo(u"a0b", patternInfo, status);
assertSuccess("Spot 1", status);
mod.setPatternInfo(&patternInfo);
mod.setPatternAttributes(UNUM_SIGN_AUTO, false);
DecimalFormatSymbols symbols(Locale::getEnglish(), status);
CurrencyUnit currency(u"USD", status);
assertSuccess("Spot 2", status);
mod.setSymbols(&symbols, currency, UNUM_UNIT_WIDTH_SHORT, nullptr);
mod.setNumberProperties(false, StandardPlural::Form::COUNT);
assertEquals("Pattern a0b", u"a", getPrefix(mod, status));
assertEquals("Pattern a0b", u"b", getSuffix(mod, status));
mod.setPatternAttributes(UNUM_SIGN_ALWAYS, false);
assertEquals("Pattern a0b", u"+a", getPrefix(mod, status));
assertEquals("Pattern a0b", u"b", getSuffix(mod, status));
mod.setNumberProperties(true, StandardPlural::Form::COUNT);
assertEquals("Pattern a0b", u"-a", getPrefix(mod, status));
assertEquals("Pattern a0b", u"b", getSuffix(mod, status));
mod.setPatternAttributes(UNUM_SIGN_NEVER, false);
assertEquals("Pattern a0b", u"a", getPrefix(mod, status));
assertEquals("Pattern a0b", u"b", getSuffix(mod, status));
assertSuccess("Spot 3", status);
ParsedPatternInfo patternInfo2;
PatternParser::parseToPatternInfo(u"a0b;c-0d", patternInfo2, status);
assertSuccess("Spot 4", status);
mod.setPatternInfo(&patternInfo2);
mod.setPatternAttributes(UNUM_SIGN_AUTO, false);
mod.setNumberProperties(false, StandardPlural::Form::COUNT);
assertEquals("Pattern a0b;c-0d", u"a", getPrefix(mod, status));
assertEquals("Pattern a0b;c-0d", u"b", getSuffix(mod, status));
mod.setPatternAttributes(UNUM_SIGN_ALWAYS, false);
assertEquals("Pattern a0b;c-0d", u"c+", getPrefix(mod, status));
assertEquals("Pattern a0b;c-0d", u"d", getSuffix(mod, status));
mod.setNumberProperties(true, StandardPlural::Form::COUNT);
assertEquals("Pattern a0b;c-0d", u"c-", getPrefix(mod, status));
assertEquals("Pattern a0b;c-0d", u"d", getSuffix(mod, status));
mod.setPatternAttributes(UNUM_SIGN_NEVER, false);
assertEquals(
"Pattern a0b;c-0d",
u"c-",
getPrefix(mod, status)); // TODO: What should this behavior be?
assertEquals("Pattern a0b;c-0d", u"d", getSuffix(mod, status));
assertSuccess("Spot 5", status);
}
void PatternModifierTest::testMutableEqualsImmutable() {
UErrorCode status = U_ZERO_ERROR;
MutablePatternModifier mod(false);
ParsedPatternInfo patternInfo;
PatternParser::parseToPatternInfo("a0b;c-0d", patternInfo, status);
assertSuccess("Spot 1", status);
mod.setPatternInfo(&patternInfo);
mod.setPatternAttributes(UNUM_SIGN_AUTO, false);
DecimalFormatSymbols symbols(Locale::getEnglish(), status);
CurrencyUnit currency(u"USD", status);
assertSuccess("Spot 2", status);
mod.setSymbols(&symbols, currency, UNUM_UNIT_WIDTH_SHORT, nullptr);
DecimalQuantity fq;
fq.setToInt(1);
NumberStringBuilder nsb1;
MicroProps micros1;
mod.addToChain(&micros1);
mod.processQuantity(fq, micros1, status);
micros1.modMiddle->apply(nsb1, 0, 0, status);
assertSuccess("Spot 3", status);
NumberStringBuilder nsb2;
MicroProps micros2;
LocalPointer<ImmutablePatternModifier> immutable(mod.createImmutable(status));
immutable->applyToMicros(micros2, fq);
micros2.modMiddle->apply(nsb2, 0, 0, status);
assertSuccess("Spot 4", status);
NumberStringBuilder nsb3;
MicroProps micros3;
mod.addToChain(&micros3);
mod.setPatternAttributes(UNUM_SIGN_ALWAYS, false);
mod.processQuantity(fq, micros3, status);
micros3.modMiddle->apply(nsb3, 0, 0, status);
assertSuccess("Spot 5", status);
assertTrue(nsb1.toUnicodeString() + " vs " + nsb2.toUnicodeString(), nsb1.contentEquals(nsb2));
assertFalse(nsb1.toUnicodeString() + " vs " + nsb3.toUnicodeString(), nsb1.contentEquals(nsb3));
}
UnicodeString PatternModifierTest::getPrefix(const MutablePatternModifier &mod, UErrorCode &status) {
NumberStringBuilder nsb;
mod.apply(nsb, 0, 0, status);
int32_t prefixLength = mod.getPrefixLength(status);
return UnicodeString(nsb.toUnicodeString(), 0, prefixLength);
}
UnicodeString PatternModifierTest::getSuffix(const MutablePatternModifier &mod, UErrorCode &status) {
NumberStringBuilder nsb;
mod.apply(nsb, 0, 0, status);
int32_t prefixLength = mod.getPrefixLength(status);
return UnicodeString(nsb.toUnicodeString(), prefixLength, nsb.length() - prefixLength);
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,92 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#include "numbertest.h"
#include "number_patternstring.h"
void PatternStringTest::runIndexedTest(int32_t index, UBool exec, const char *&name, char *) {
if (exec) {
logln("TestSuite PatternStringTest: ");
}
TESTCASE_AUTO_BEGIN;
TESTCASE_AUTO(testToPatternSimple);
TESTCASE_AUTO(testExceptionOnInvalid);
TESTCASE_AUTO(testBug13117);
TESTCASE_AUTO_END;
}
void PatternStringTest::testToPatternSimple() {
const char16_t *cases[][2] = {{u"#", u"0"},
{u"0", u"0"},
{u"#0", u"0"},
{u"###", u"0"},
{u"0.##", u"0.##"},
{u"0.00", u"0.00"},
{u"0.00#", u"0.00#"},
{u"#E0", u"#E0"},
{u"0E0", u"0E0"},
{u"#00E00", u"#00E00"},
{u"#,##0", u"#,##0"},
{u"#;#", u"0;0"},
// ignore a negative prefix pattern of '-' since that is the default:
{u"#;-#", u"0"},
{u"**##0", u"**##0"},
{u"*'x'##0", u"*x##0"},
{u"a''b0", u"a''b0"},
{u"*''##0", u"*''##0"},
{u"*📺##0", u"*'📺'##0"},
{u"*'நி'##0", u"*'நி'##0"},};
UErrorCode status = U_ZERO_ERROR;
for (const char16_t **cas : cases) {
UnicodeString input(cas[0]);
UnicodeString output(cas[1]);
DecimalFormatProperties properties = PatternParser::parseToProperties(
input, PatternParser::IGNORE_ROUNDING_NEVER, status);
assertSuccess(input, status);
UnicodeString actual = PatternStringUtils::propertiesToPatternString(properties, status);
assertEquals(input, output, actual);
}
}
void PatternStringTest::testExceptionOnInvalid() {
static const char16_t *invalidPatterns[] = {
u"#.#.#",
u"0#",
u"0#.",
u".#0",
u"0#.#0",
u"@0",
u"0@",
u"0,",
u"0,,",
u"0,,0",
u"0,,0,",
u"#,##0E0"};
for (auto pattern : invalidPatterns) {
UErrorCode status = U_ZERO_ERROR;
ParsedPatternInfo patternInfo;
PatternParser::parseToPatternInfo(pattern, patternInfo, status);
assertTrue(pattern, U_FAILURE(status));
}
}
void PatternStringTest::testBug13117() {
UErrorCode status = U_ZERO_ERROR;
DecimalFormatProperties expected = PatternParser::parseToProperties(
u"0",
PatternParser::IGNORE_ROUNDING_NEVER,
status);
DecimalFormatProperties actual = PatternParser::parseToProperties(
u"0;",
PatternParser::IGNORE_ROUNDING_NEVER,
status);
assertSuccess("Spot 1", status);
assertTrue("Should not consume negative subpattern", expected == actual);
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,235 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#include "putilimp.h"
#include "numbertest.h"
static const char16_t *EXAMPLE_STRINGS[] = {
u"",
u"xyz",
u"The quick brown fox jumps over the lazy dog",
u"😁",
u"mixed 😇 and ASCII",
u"with combining characters like 🇦🇧🇨🇩",
u"A very very very very very very very very very very long string to force heap"};
void NumberStringBuilderTest::runIndexedTest(int32_t index, UBool exec, const char *&name, char *) {
if (exec) {
logln("TestSuite NumberStringBuilderTest: ");
}
TESTCASE_AUTO_BEGIN;
TESTCASE_AUTO(testInsertAppendUnicodeString);
TESTCASE_AUTO(testInsertAppendCodePoint);
TESTCASE_AUTO(testCopy);
TESTCASE_AUTO(testFields);
TESTCASE_AUTO(testUnlimitedCapacity);
TESTCASE_AUTO(testCodePoints);
TESTCASE_AUTO_END;
}
void NumberStringBuilderTest::testInsertAppendUnicodeString() {
UErrorCode status = U_ZERO_ERROR;
UnicodeString sb1;
NumberStringBuilder sb2;
for (const char16_t* strPtr : EXAMPLE_STRINGS) {
UnicodeString str(strPtr);
NumberStringBuilder sb3;
sb1.append(str);
// Note: UNUM_FIELD_COUNT is like passing null in Java
sb2.append(str, UNUM_FIELD_COUNT, status);
assertSuccess("Appending to sb2", status);
sb3.append(str, UNUM_FIELD_COUNT, status);
assertSuccess("Appending to sb3", status);
assertEqualsImpl(sb1, sb2);
assertEqualsImpl(str, sb3);
UnicodeString sb4;
NumberStringBuilder sb5;
sb4.append(u"😇");
sb4.append(str);
sb4.append(u"xx");
sb5.append(u"😇xx", UNUM_FIELD_COUNT, status);
assertSuccess("Appending to sb5", status);
sb5.insert(2, str, UNUM_FIELD_COUNT, status);
assertSuccess("Inserting into sb5", status);
assertEqualsImpl(sb4, sb5);
int start = uprv_min(1, str.length());
int end = uprv_min(10, str.length());
sb4.insert(3, str, start, end - start); // UnicodeString uses length instead of end index
sb5.insert(3, str, start, end, UNUM_FIELD_COUNT, status);
assertSuccess("Inserting into sb5 again", status);
assertEqualsImpl(sb4, sb5);
UnicodeString sb4cp(sb4);
NumberStringBuilder sb5cp(sb5);
sb4.append(sb4cp);
sb5.append(sb5cp, status);
assertSuccess("Appending again to sb5", status);
assertEqualsImpl(sb4, sb5);
}
}
void NumberStringBuilderTest::testInsertAppendCodePoint() {
static const UChar32 cases[] = {
0, 1, 60, 127, 128, 0x7fff, 0x8000, 0xffff, 0x10000, 0x1f000, 0x10ffff};
UErrorCode status = U_ZERO_ERROR;
UnicodeString sb1;
NumberStringBuilder sb2;
for (UChar32 cas : cases) {
NumberStringBuilder sb3;
sb1.append(cas);
sb2.appendCodePoint(cas, UNUM_FIELD_COUNT, status);
assertSuccess("Appending to sb2", status);
sb3.appendCodePoint(cas, UNUM_FIELD_COUNT, status);
assertSuccess("Appending to sb3", status);
assertEqualsImpl(sb1, sb2);
assertEquals("Length of sb3", U16_LENGTH(cas), sb3.length());
assertEquals("Code point count of sb3", 1, sb3.codePointCount());
assertEquals(
"First code unit in sb3",
!U_IS_SUPPLEMENTARY(cas) ? (char16_t) cas : U16_LEAD(cas),
sb3.charAt(0));
UnicodeString sb4;
NumberStringBuilder sb5;
sb4.append(u"😇xx");
sb4.insert(2, cas);
sb5.append(u"😇xx", UNUM_FIELD_COUNT, status);
assertSuccess("Appending to sb5", status);
sb5.insertCodePoint(2, cas, UNUM_FIELD_COUNT, status);
assertSuccess("Inserting into sb5", status);
assertEqualsImpl(sb4, sb5);
}
}
void NumberStringBuilderTest::testCopy() {
UErrorCode status = U_ZERO_ERROR;
for (UnicodeString str : EXAMPLE_STRINGS) {
NumberStringBuilder sb1;
sb1.append(str, UNUM_FIELD_COUNT, status);
assertSuccess("Appending to sb1 first time", status);
NumberStringBuilder sb2(sb1);
assertTrue("Content should equal itself", sb1.contentEquals(sb2));
sb1.append("12345", UNUM_FIELD_COUNT, status);
assertSuccess("Appending to sb1 second time", status);
assertFalse("Content should no longer equal itself", sb1.contentEquals(sb2));
}
}
void NumberStringBuilderTest::testFields() {
UErrorCode status = U_ZERO_ERROR;
// Note: This is a C++11 for loop that calls the UnicodeString constructor on each iteration.
for (UnicodeString str : EXAMPLE_STRINGS) {
NumberStringBuilder sb;
sb.append(str, UNUM_FIELD_COUNT, status);
assertSuccess("Appending to sb", status);
sb.append(str, UNUM_CURRENCY_FIELD, status);
assertSuccess("Appending to sb", status);
assertEquals("Reference string copied twice", str.length() * 2, sb.length());
for (int32_t i = 0; i < str.length(); i++) {
assertEquals("Null field first", UNUM_FIELD_COUNT, sb.fieldAt(i));
assertEquals("Currency field second", UNUM_CURRENCY_FIELD, sb.fieldAt(i + str.length()));
}
// Very basic FieldPosition test. More robust tests happen in NumberFormatTest.
// Let NumberFormatTest also take care of FieldPositionIterator material.
FieldPosition fp(UNUM_CURRENCY_FIELD);
sb.populateFieldPosition(fp, 0, status);
assertSuccess("Populating the FieldPosition", status);
assertEquals("Currency start position", str.length(), fp.getBeginIndex());
assertEquals("Currency end position", str.length() * 2, fp.getEndIndex());
if (str.length() > 0) {
sb.insertCodePoint(2, 100, UNUM_INTEGER_FIELD, status);
assertSuccess("Inserting code point into sb", status);
assertEquals("New length", str.length() * 2 + 1, sb.length());
assertEquals("Integer field", UNUM_INTEGER_FIELD, sb.fieldAt(2));
}
NumberStringBuilder old(sb);
sb.append(old, status);
assertSuccess("Appending to myself", status);
int32_t numNull = 0;
int32_t numCurr = 0;
int32_t numInt = 0;
for (int32_t i = 0; i < sb.length(); i++) {
UNumberFormatFields field = sb.fieldAt(i);
assertEquals("Field should equal location in old", old.fieldAt(i % old.length()), field);
if (field == UNUM_FIELD_COUNT) {
numNull++;
} else if (field == UNUM_CURRENCY_FIELD) {
numCurr++;
} else if (field == UNUM_INTEGER_FIELD) {
numInt++;
} else {
errln("Encountered unknown field");
}
}
assertEquals("Number of null fields", str.length() * 2, numNull);
assertEquals("Number of currency fields", numNull, numCurr);
assertEquals("Number of integer fields", str.length() > 0 ? 2 : 0, numInt);
}
}
void NumberStringBuilderTest::testUnlimitedCapacity() {
UErrorCode status = U_ZERO_ERROR;
NumberStringBuilder builder;
// The builder should never fail upon repeated appends.
for (int i = 0; i < 1000; i++) {
UnicodeString message("Iteration #");
message += Int64ToUnicodeString(i);
assertEquals(message, builder.length(), i);
builder.appendCodePoint('x', UNUM_FIELD_COUNT, status);
assertSuccess(message, status);
assertEquals(message, builder.length(), i + 1);
}
}
void NumberStringBuilderTest::testCodePoints() {
UErrorCode status = U_ZERO_ERROR;
NumberStringBuilder nsb;
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(u"q", UNUM_FIELD_COUNT, status);
assertSuccess("Spot 1", status);
assertEquals("First is q", u'q', nsb.getFirstCodePoint());
assertEquals("Last is q", u'q', nsb.getLastCodePoint());
assertEquals("0th is q", u'q', nsb.codePointAt(0));
assertEquals("Before 1st is q", u'q', nsb.codePointBefore(1));
assertEquals("Code point count is 1", 1, nsb.codePointCount());
// 🚀 is two char16s
nsb.append(u"🚀", UNUM_FIELD_COUNT, status);
assertSuccess("Spot 2" ,status);
assertEquals("First is still q", u'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", u'q', nsb.codePointBefore(1));
assertEquals("Before 3rd is space ship", 128640, nsb.codePointBefore(3));
assertEquals("Code point count is 2", 2, nsb.codePointCount());
}
void NumberStringBuilderTest::assertEqualsImpl(const UnicodeString &a, const NumberStringBuilder &b) {
// TODO: Why won't this compile without the IntlTest:: qualifier?
IntlTest::assertEquals("Lengths should be the same", a.length(), b.length());
IntlTest::assertEquals("Code point counts should be the same", a.countChar32(), b.codePointCount());
if (a.length() != b.length()) {
return;
}
for (int32_t i = 0; i < a.length(); i++) {
IntlTest::assertEquals(
UnicodeString("Char at position ") + Int64ToUnicodeString(i) +
UnicodeString(" in string ") + a, a.charAt(i), b.charAt(i));
}
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -2388,13 +2388,16 @@ void NumberFormatTest::TestCurrencyNames(void) {
void NumberFormatTest::TestCurrencyUnit(void){
UErrorCode ec = U_ZERO_ERROR;
static const UChar USD[] = {85, 83, 68, 0}; /*USD*/
static const char USD8[] = {85, 83, 68, 0};
static const UChar BAD[] = {63, 63, 63, 0}; /*???*/
static const UChar BAD2[] = {63, 63, 65, 0}; /*???*/
static const UChar XXX[] = u"XXX";
static const char XXX8[] = {88, 88, 88};
CurrencyUnit cu(USD, ec);
assertSuccess("CurrencyUnit", ec);
const UChar * r = cu.getISOCurrency(); // who is the buffer owner ?
assertEquals("getISOCurrency()", USD, r);
assertEquals("getISOCurrency()", USD, cu.getISOCurrency());
assertEquals("getSubtype()", USD8, cu.getSubtype());
CurrencyUnit cu2(cu);
if (!(cu2 == cu)){
@ -2423,6 +2426,31 @@ void NumberFormatTest::TestCurrencyUnit(void){
errln("Currency unit assignment should be the same.");
}
delete cu3;
// Test default constructor
CurrencyUnit def;
assertEquals("Default currency", XXX, def.getISOCurrency());
assertEquals("Default currency as subtype", XXX8, def.getSubtype());
// Test slicing
MeasureUnit sliced1 = cu;
MeasureUnit sliced2 = cu;
assertEquals("Subtype after slicing 1", USD8, sliced1.getSubtype());
assertEquals("Subtype after slicing 2", USD8, sliced2.getSubtype());
CurrencyUnit restored1(sliced1, ec);
CurrencyUnit restored2(sliced2, ec);
assertSuccess("Restoring from MeasureUnit", ec);
assertEquals("Subtype after restoring 1", USD8, restored1.getSubtype());
assertEquals("Subtype after restoring 2", USD8, restored2.getSubtype());
assertEquals("ISO Code after restoring 1", USD, restored1.getISOCurrency());
assertEquals("ISO Code after restoring 2", USD, restored2.getISOCurrency());
// Test copy constructor failure
LocalPointer<MeasureUnit> meter(MeasureUnit::createMeter(ec));
assertSuccess("Creating meter", ec);
CurrencyUnit failure(*meter, ec);
assertEquals("Copying from meter should fail", ec, U_ILLEGAL_ARGUMENT_ERROR);
assertEquals("Copying should not give uninitialized ISO code", u"", failure.getISOCurrency());
}
void NumberFormatTest::TestCurrencyAmount(void){

View file

@ -259,8 +259,8 @@ $**####,##0 1234 $***1\u00a0234 K
// In J ICU adds padding as if 'EUR' is only 2 chars (2 * 0xa4)
\u00a4\u00a4 **####0.00 433.0 EUR *433,00 JK
// In J ICU adds padding as if 'EUR' is only 2 chars (2 * 0xa4)
// S fails this one because the test code bypasses CurrencyUsage
\u00a4\u00a4 **#######0 433.0 EUR *433,00 JKS
// S and Q fail this one because the test code bypasses CurrencyUsage
\u00a4\u00a4 **#######0 433.0 EUR *433,00 JKSQ
test padding and currencies
begin
@ -338,8 +338,8 @@ minIntegerDigits maxIntegerDigits minFractionDigits maxFractionDigits output bre
// JDK gives E0 instead of allowing for unlimited precision
// S obeys the maximum integer digits and returns .299792458E9
0 0 0 0 2.99792458E8 KS
// JDK and S give .299792E9
0 1 0 5 2.9979E8 KS
// JDK and S give .299792E9; Q gives 2.99792E8
0 1 0 5 2.9979E8 KSQ
// JDK gives 300E6
0 3 0 0 299.792458E6 K
// JDK gives 299.8E6 (maybe maxInt + maxFrac instead of minInt + maxFrac)?
@ -356,11 +356,10 @@ minIntegerDigits maxIntegerDigits minFractionDigits maxFractionDigits output bre
// JDK gives E0
// S obeys the maximum integer digits
0 0 1 0 2.99792458E8 KS
// JDK gives .2998E9
0 0 0 4 2.998E8 KS
// According to the spec, if maxInt>minInt and minInt>1, then set
// minInt to 1 for the purposes of engineering notation; see #13289
// JDK and S give .2998E9
0 0 0 4 2.998E8 KSQ
// JDK uses 8 + 6 for significant digits instead of 2 + 6
// Context: #13289
2 8 1 6 2.9979246E8 K
// Treat max int digits > 8 as being the same as min int digits.
// This behavior is not spelled out in the specification.
@ -406,12 +405,12 @@ begin
format maxIntegerDigits output breaks
123 1 3
0 0 0
// S ignores max integer if it is less than zero and prints "123"
123 -2147483648 0 S
// S and Q ignore max integer if it is less than zero and prints "123"
123 -2147483648 0 SQ
12345 1 5
12345 -2147483648 0 S
12345 -2147483648 0 SQ
5.3 1 5.3
5.3 -2147483648 .3 S
5.3 -2147483648 .3 SQ
test patterns with zero
set locale en
@ -490,8 +489,9 @@ output grouping breaks grouping2 minGroupingDigits
1,2345,6789 4
1,23,45,6789 4 K 2
1,23,45,6789 4 K 2 2
// Q only supports minGrouping<=2
123,456789 6 6 3
123456789 6 JK 6 4
123456789 6 JKQ 6 4
test multiplier setters
set locale en_US
@ -499,8 +499,10 @@ begin
format multiplier output breaks
23 -12 -276
23 -1 -23
// ICU4J and JDK throw exception on zero multiplier. ICU4C does not.
23 0 23 JKS
// ICU4J and JDK throw exception on zero multiplier.
// ICU4C and S print 23.
// Q multiplies by zero and prints 0.
23 0 0 CJKS
23 1 23
23 12 276
-23 12 -276
@ -514,8 +516,9 @@ begin
format output breaks
-0.35 -0.25 K
0.35 0.25 K
0.39 0.5 K
0.62 0.5 K
// Q doesn't support mixing minFrac with roundingIncrement (prints 0.50).
0.39 0.5 KQ
0.62 0.5 KQ
0.63 0.75 K
test padding setters
@ -540,20 +543,19 @@ output breaks useScientific
test rounding mode setters
set locale en_US
set pattern 0.#
set roundingIncrement 0.5
set pattern 0.5
begin
format roundingMode output breaks
1.24 halfUp 1 K
1.24 halfUp 1.0 K
1.25 halfUp 1.5 K
1.25 halfDown 1 K
1.25 halfDown 1.0 K
1.26 halfDown 1.5 K
1.25 halfEven 1 K
1.25 halfEven 1.0 K
-1.01 up -1.5 K
-1.49 down -1 K
-1.49 down -1.0 K
1.01 up 1.5 K
1.49 down 1 K
-1.01 ceiling -1
1.49 down 1.0 K
-1.01 ceiling -1.0
-1.49 floor -1.5
test currency usage setters
@ -585,7 +587,7 @@ set locale en
set currency USD
begin
pattern format output breaks
# 123 123 S
# 123 123 SQ
// Currency rounding should always override the pattern.
// K prints the currency in ISO format for some reason.
\u00a4# 123 $123.00 K
@ -660,7 +662,8 @@ begin
format output breaks
Inf [\u221e]
-Inf (\u221e) K
NaN NaN K
// Q prints the affixes
NaN NaN KQ
test nan and infinity with multiplication
set locale en
@ -681,10 +684,11 @@ Inf beforePrefix $$$\u221e$ K
Inf afterPrefix $$$ \u221e$ K
Inf beforeSuffix $$$\u221e $ K
Inf afterSuffix $$$\u221e$ K
NaN beforePrefix NaN K
NaN afterPrefix NaN K
NaN beforeSuffix NaN K
NaN afterSuffix NaN K
// Q gets $$$NaN$
NaN beforePrefix NaN KQ
NaN afterPrefix NaN KQ
NaN beforeSuffix NaN KQ
NaN afterSuffix NaN KQ
test apply formerly localized patterns
begin
@ -1541,8 +1545,9 @@ set maxSigDigits 2
begin
format output breaks
// C and J get "1"
// Q gets "1.0"
// K gets "1.1" (??)
0.975 0.98 CJK
0.975 0.98 CJKQ
test lenient parse currency match
// This test is for #13112

View file

@ -1325,6 +1325,7 @@
<packageset dir="${icu4j.core.dir}/src">
<include name="com/ibm/icu/lang/**"/>
<include name="com/ibm/icu/math/**"/>
<include name="com/ibm/icu/number/**"/>
<include name="com/ibm/icu/text/**"/>
<include name="com/ibm/icu/util/**"/>
</packageset>
@ -1359,6 +1360,7 @@
<packageset dir="${icu4j.core.dir}/src">
<include name="com/ibm/icu/lang/**"/>
<include name="com/ibm/icu/math/**"/>
<include name="com/ibm/icu/number/**"/>
<include name="com/ibm/icu/text/**"/>
<include name="com/ibm/icu/util/**"/>
</packageset>
@ -1404,6 +1406,7 @@
<packageset dir="${icu4j.core.dir}/src">
<include name="com/ibm/icu/lang/**"/>
<include name="com/ibm/icu/math/**"/>
<include name="com/ibm/icu/number/**"/>
<include name="com/ibm/icu/text/**"/>
<include name="com/ibm/icu/util/**"/>
</packageset>
@ -1608,6 +1611,7 @@
<packageset dir="${icu4j.core.dir}/src">
<include name="com/ibm/icu/lang/**"/>
<include name="com/ibm/icu/math/**"/>
<include name="com/ibm/icu/number/**"/>
<include name="com/ibm/icu/text/**"/>
<include name="com/ibm/icu/util/**"/>
</packageset>
@ -1629,6 +1633,7 @@
<packageset dir="${icu4j.core.dir}/src">
<include name="com/ibm/icu/lang/**"/>
<include name="com/ibm/icu/math/**"/>
<include name="com/ibm/icu/number/**"/>
<include name="com/ibm/icu/text/**"/>
<include name="com/ibm/icu/util/**"/>
</packageset>
@ -1674,6 +1679,7 @@
<packageset dir="${icu4j.core.dir}/src">
<include name="com/ibm/icu/lang/**"/>
<include name="com/ibm/icu/math/**"/>
<include name="com/ibm/icu/number/**"/>
<include name="com/ibm/icu/text/**"/>
<include name="com/ibm/icu/util/**"/>
</packageset>

View file

@ -364,6 +364,14 @@ public class ICUResourceBundle extends UResourceBundle {
return result;
}
public void getAllItemsWithFallbackNoFail(String path, UResource.Sink sink) {
try {
getAllItemsWithFallback(path, sink);
} catch (MissingResourceException e) {
// Quietly ignore the exception.
}
}
public void getAllItemsWithFallback(String path, UResource.Sink sink)
throws MissingResourceException {
// Collect existing and parsed key objects into an array of keys,

View file

@ -0,0 +1,26 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
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 char charAt(int flags, int i);
public int length(int flags);
public boolean hasCurrencySign();
public boolean positiveHasPlusSign();
public boolean hasNegativeSubpattern();
public boolean negativeHasMinusSign();
public boolean containsSymbolType(int type);
}

View file

@ -2,8 +2,7 @@
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number;
import com.ibm.icu.text.DecimalFormatSymbols;
import com.ibm.icu.text.NumberFormat.Field;
import com.ibm.icu.text.NumberFormat;
/**
* Performs manipulations on affix patterns: the prefix and suffix strings associated with a decimal
@ -35,21 +34,7 @@ import com.ibm.icu.text.NumberFormat.Field;
* case AffixPatternUtils.TYPE_PERCENT:
* // Current token is a percent sign.
* break;
* case AffixPatternUtils.TYPE_PERMILLE:
* // Current token is a permille sign.
* break;
* case AffixPatternUtils.TYPE_CURRENCY_SINGLE:
* // Current token is a single currency sign.
* break;
* case AffixPatternUtils.TYPE_CURRENCY_DOUBLE:
* // Current token is a double currency sign.
* break;
* case AffixPatternUtils.TYPE_CURRENCY_TRIPLE:
* // Current token is a triple currency sign.
* break;
* case AffixPatternUtils.TYPE_CURRENCY_OVERFLOW:
* // Current token has four or more currency signs.
* break;
* // ... other types ...
* default:
* // Current token is an arbitrary code point.
* // The variable typeOrCp is the code point.
@ -58,7 +43,7 @@ import com.ibm.icu.text.NumberFormat.Field;
* }
* </pre>
*/
public class AffixPatternUtils {
public class AffixUtils {
private static final int STATE_BASE = 0;
private static final int STATE_FIRST_QUOTE = 1;
@ -67,8 +52,11 @@ public class AffixPatternUtils {
private static final int STATE_FIRST_CURR = 4;
private static final int STATE_SECOND_CURR = 5;
private static final int STATE_THIRD_CURR = 6;
private static final int STATE_OVERFLOW_CURR = 7;
private static final int STATE_FOURTH_CURR = 7;
private static final int STATE_FIFTH_CURR = 8;
private static final int STATE_OVERFLOW_CURR = 9;
/** Represents a literal character; the value is stored in the code point field. */
private static final int TYPE_CODEPOINT = 0;
/** Represents a minus sign symbol '-'. */
@ -92,9 +80,19 @@ public class AffixPatternUtils {
/** Represents a triple currency symbol '¤¤¤'. */
public static final int TYPE_CURRENCY_TRIPLE = -7;
/** Represents a sequence of four or more currency symbols. */
/** Represents a quadruple currency symbol '¤¤¤¤'. */
public static final int TYPE_CURRENCY_QUAD = -8;
/** Represents a quintuple currency symbol '¤¤¤¤¤'. */
public static final int TYPE_CURRENCY_QUINT = -9;
/** Represents a sequence of six or more currency symbols. */
public static final int TYPE_CURRENCY_OVERFLOW = -15;
public static interface SymbolProvider {
public CharSequence getSymbol(int type);
}
/**
* Estimates the number of code points present in an unescaped version of the affix pattern string
* (one that would be returned by {@link #unescape}), assuming that all interpolated symbols
@ -104,7 +102,7 @@ public class AffixPatternUtils {
* @param patternString The original string whose width will be estimated.
* @return The length of the unescaped string.
*/
public static int unescapedLength(CharSequence patternString) {
public static int estimateLength(CharSequence patternString) {
if (patternString == null) return 0;
int state = STATE_BASE;
int offset = 0;
@ -227,77 +225,114 @@ public class AffixPatternUtils {
return output.length() - startLength;
}
/**
* Executes the unescape state machine. Replaces the unquoted characters "-", "+", "%", and ""
* with their localized equivalents. Replaces "¤", "¤¤", and "¤¤¤" with the three argument
* strings.
*
* <p>Example input: "'-'¤x"; example output: "-$x"
*
* @param affixPattern The original string to be unescaped.
* @param symbols An instance of {@link DecimalFormatSymbols} for the locale of interest.
* @param currency1 The string to replace "¤".
* @param currency2 The string to replace "¤¤".
* @param currency3 The string to replace "¤¤¤".
* @param minusSign The string to replace "-". If null, symbols.getMinusSignString() is used.
* @param output The {@link NumberStringBuilder} to which the result will be appended.
*/
public static void unescape(
CharSequence affixPattern,
DecimalFormatSymbols symbols,
String currency1,
String currency2,
String currency3,
String minusSign,
NumberStringBuilder output) {
if (affixPattern == null || affixPattern.length() == 0) return;
if (minusSign == null) minusSign = symbols.getMinusSignString();
long tag = 0L;
while (hasNext(tag, affixPattern)) {
tag = nextToken(tag, affixPattern);
int typeOrCp = getTypeOrCp(tag);
switch (typeOrCp) {
case TYPE_MINUS_SIGN:
output.append(minusSign, Field.SIGN);
break;
case TYPE_PLUS_SIGN:
output.append(symbols.getPlusSignString(), Field.SIGN);
break;
case TYPE_PERCENT:
output.append(symbols.getPercentString(), Field.PERCENT);
break;
case TYPE_PERMILLE:
output.append(symbols.getPerMillString(), Field.PERMILLE);
break;
case TYPE_CURRENCY_SINGLE:
output.append(currency1, Field.CURRENCY);
break;
case TYPE_CURRENCY_DOUBLE:
output.append(currency2, Field.CURRENCY);
break;
case TYPE_CURRENCY_TRIPLE:
output.append(currency3, Field.CURRENCY);
break;
case TYPE_CURRENCY_OVERFLOW:
output.append("\uFFFD", Field.CURRENCY);
break;
default:
output.appendCodePoint(typeOrCp, null);
break;
}
/** Version of {@link #escape} that returns a String. */
public static String escape(CharSequence input) {
StringBuilder sb = new StringBuilder();
escape(input, sb);
return sb.toString();
}
public static final NumberFormat.Field getFieldForType(int type) {
switch (type) {
case TYPE_MINUS_SIGN:
return NumberFormat.Field.SIGN;
case TYPE_PLUS_SIGN:
return NumberFormat.Field.SIGN;
case TYPE_PERCENT:
return NumberFormat.Field.PERCENT;
case TYPE_PERMILLE:
return NumberFormat.Field.PERMILLE;
case TYPE_CURRENCY_SINGLE:
return NumberFormat.Field.CURRENCY;
case TYPE_CURRENCY_DOUBLE:
return NumberFormat.Field.CURRENCY;
case TYPE_CURRENCY_TRIPLE:
return NumberFormat.Field.CURRENCY;
case TYPE_CURRENCY_QUAD:
return NumberFormat.Field.CURRENCY;
case TYPE_CURRENCY_QUINT:
return NumberFormat.Field.CURRENCY;
case TYPE_CURRENCY_OVERFLOW:
return NumberFormat.Field.CURRENCY;
default:
throw new AssertionError();
}
}
/**
* Executes the unescape state machine. Replaces the unquoted characters "-", "+", "%", "", and
* "¤" with the corresponding symbols provided by the {@link SymbolProvider}, and inserts the
* result into the NumberStringBuilder at the requested location.
*
* <p>Example input: "'-'¤x"; example output: "-$x"
*
* @param affixPattern The original string to be unescaped.
* @param output The NumberStringBuilder to mutate with the result.
* @param position The index into the NumberStringBuilder to insert the the string.
* @param provider An object to generate locale symbols.
* @return The length of the string added to affixPattern.
*/
public static int unescape(
CharSequence affixPattern,
NumberStringBuilder output,
int position,
SymbolProvider provider) {
assert affixPattern != null;
int length = 0;
long tag = 0L;
while (hasNext(tag, affixPattern)) {
tag = nextToken(tag, affixPattern);
int typeOrCp = getTypeOrCp(tag);
if (typeOrCp == TYPE_CURRENCY_OVERFLOW) {
// Don't go to the provider for this special case
length += output.insertCodePoint(position + length, 0xFFFD, NumberFormat.Field.CURRENCY);
} else if (typeOrCp < 0) {
length += output.insert(position + length, provider.getSymbol(typeOrCp), getFieldForType(typeOrCp));
} else {
length += output.insertCodePoint(position + length, typeOrCp, null);
}
}
return length;
}
/**
* Sames as {@link #unescape}, but only calculates the code point count. More efficient than {@link #unescape}
* if you only need the length but not the string itself.
*
* @param affixPattern The original string to be unescaped.
* @param provider An object to generate locale symbols.
* @return The number of code points in the unescaped string.
*/
public static int unescapedCodePointCount(CharSequence affixPattern, SymbolProvider provider) {
int length = 0;
long tag = 0L;
while (hasNext(tag, affixPattern)) {
tag = nextToken(tag, affixPattern);
int typeOrCp = getTypeOrCp(tag);
if (typeOrCp == TYPE_CURRENCY_OVERFLOW) {
length += 1;
} else if (typeOrCp < 0) {
CharSequence symbol = provider.getSymbol(typeOrCp);
length += Character.codePointCount(symbol, 0, symbol.length());
} else {
length += 1;
}
}
return length;
}
/**
* Checks whether the given affix pattern contains at least one token of the given type, which is
* one of the constants "TYPE_" in {@link AffixPatternUtils}.
* one of the constants "TYPE_" in {@link AffixUtils}.
*
* @param affixPattern The affix pattern to check.
* @param type The token type.
* @return true if the affix pattern contains the given token type; false otherwise.
*/
public static boolean containsType(CharSequence affixPattern, int type) {
if (affixPattern == null || affixPattern.length() == 0) return false;
if (affixPattern == null || affixPattern.length() == 0) {
return false;
}
long tag = 0L;
while (hasNext(tag, affixPattern)) {
tag = nextToken(tag, affixPattern);
@ -320,10 +355,7 @@ public class AffixPatternUtils {
while (hasNext(tag, affixPattern)) {
tag = nextToken(tag, affixPattern);
int typeOrCp = getTypeOrCp(tag);
if (typeOrCp == AffixPatternUtils.TYPE_CURRENCY_SINGLE
|| typeOrCp == AffixPatternUtils.TYPE_CURRENCY_DOUBLE
|| typeOrCp == AffixPatternUtils.TYPE_CURRENCY_TRIPLE
|| typeOrCp == AffixPatternUtils.TYPE_CURRENCY_OVERFLOW) {
if (typeOrCp < 0 && getFieldForType(typeOrCp) == NumberFormat.Field.CURRENCY) {
return true;
}
}
@ -437,13 +469,31 @@ public class AffixPatternUtils {
}
case STATE_THIRD_CURR:
if (cp == '¤') {
state = STATE_OVERFLOW_CURR;
state = STATE_FOURTH_CURR;
offset += count;
// continue to the next code point
break;
} else {
return makeTag(offset, TYPE_CURRENCY_TRIPLE, STATE_BASE, 0);
}
case STATE_FOURTH_CURR:
if (cp == '¤') {
state = STATE_FIFTH_CURR;
offset += count;
// continue to the next code point
break;
} else {
return makeTag(offset, TYPE_CURRENCY_QUAD, STATE_BASE, 0);
}
case STATE_FIFTH_CURR:
if (cp == '¤') {
state = STATE_OVERFLOW_CURR;
offset += count;
// continue to the next code point
break;
} else {
return makeTag(offset, TYPE_CURRENCY_QUINT, STATE_BASE, 0);
}
case STATE_OVERFLOW_CURR:
if (cp == '¤') {
offset += count;
@ -475,6 +525,10 @@ public class AffixPatternUtils {
return makeTag(offset, TYPE_CURRENCY_DOUBLE, STATE_BASE, 0);
case STATE_THIRD_CURR:
return makeTag(offset, TYPE_CURRENCY_TRIPLE, STATE_BASE, 0);
case STATE_FOURTH_CURR:
return makeTag(offset, TYPE_CURRENCY_QUAD, STATE_BASE, 0);
case STATE_FIFTH_CURR:
return makeTag(offset, TYPE_CURRENCY_QUINT, STATE_BASE, 0);
case STATE_OVERFLOW_CURR:
return makeTag(offset, TYPE_CURRENCY_OVERFLOW, STATE_BASE, 0);
default:
@ -519,9 +573,20 @@ public class AffixPatternUtils {
public static int getTypeOrCp(long tag) {
assert tag >= 0;
int type = getType(tag);
return (type == 0) ? getCodePoint(tag) : -type;
return (type == TYPE_CODEPOINT) ? getCodePoint(tag) : -type;
}
/**
* Encodes the given values into a 64-bit tag.
*
* <ul>
* <li>Bits 0-31 => offset (int32)
* <li>Bits 32-35 => type (uint4)
* <li>Bits 36-39 => state (uint4)
* <li>Bits 40-60 => code point (uint21)
* <li>Bits 61-63 => unused
* </ul>
*/
private static long makeTag(int offset, int type, int state, int cp) {
long tag = 0L;
tag |= offset;

View file

@ -0,0 +1,225 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import com.ibm.icu.impl.ICUData;
import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.impl.UResource;
import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
import com.ibm.icu.util.ICUException;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.UResourceBundle;
public class CompactData implements MultiplierProducer {
public enum CompactType {
DECIMAL, CURRENCY
}
// A dummy object used when a "0" compact decimal entry is encountered. This is necessary
// in order to prevent falling back to root. Object equality ("==") is intended.
private static final String USE_FALLBACK = "<USE FALLBACK>";
private final String[] patterns;
private final byte[] multipliers;
private byte largestMagnitude;
private boolean isEmpty;
private static final int COMPACT_MAX_DIGITS = 15;
public CompactData() {
patterns = new String[(CompactData.COMPACT_MAX_DIGITS + 1) * StandardPlural.COUNT];
multipliers = new byte[CompactData.COMPACT_MAX_DIGITS + 1];
largestMagnitude = 0;
isEmpty = true;
}
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);
boolean nsIsLatn = nsName.equals("latn");
boolean compactIsShort = compactStyle == CompactStyle.SHORT;
// Fall back to latn numbering system and/or short compact style.
StringBuilder resourceKey = new StringBuilder();
getResourceBundleKey(nsName, compactStyle, compactType, resourceKey);
rb.getAllItemsWithFallbackNoFail(resourceKey.toString(), sink);
if (isEmpty && !nsIsLatn) {
getResourceBundleKey("latn", compactStyle, compactType, resourceKey);
rb.getAllItemsWithFallbackNoFail(resourceKey.toString(), sink);
}
if (isEmpty && !compactIsShort) {
getResourceBundleKey(nsName, CompactStyle.SHORT, compactType, resourceKey);
rb.getAllItemsWithFallbackNoFail(resourceKey.toString(), sink);
}
if (isEmpty && !nsIsLatn && !compactIsShort) {
getResourceBundleKey("latn", CompactStyle.SHORT, compactType, resourceKey);
rb.getAllItemsWithFallbackNoFail(resourceKey.toString(), sink);
}
// The last fallback should be guaranteed to return data.
if (isEmpty) {
throw new ICUException("Could not load compact decimal data for locale " + locale);
}
}
/** Produces a string like "NumberElements/latn/patternsShort/decimalFormat". */
private static void getResourceBundleKey(String nsName, CompactStyle compactStyle, CompactType compactType, StringBuilder sb) {
sb.setLength(0);
sb.append("NumberElements/");
sb.append(nsName);
sb.append(compactStyle == CompactStyle.SHORT ? "/patternsShort" : "/patternsLong");
sb.append(compactType == CompactType.DECIMAL ? "/decimalFormat" : "/currencyFormat");
}
/** 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()) {
byte magnitude = (byte) (magnitudeEntry.getKey().length() - 1);
for (Map.Entry<String, String> pluralEntry : magnitudeEntry.getValue().entrySet()) {
StandardPlural plural = StandardPlural.fromString(pluralEntry.getKey().toString());
String patternString = pluralEntry.getValue().toString();
patterns[getIndex(magnitude, plural)] = patternString;
int numZeros = countZeros(patternString);
if (numZeros > 0) { // numZeros==0 in certain cases, like Somali "Kun"
// Save the multiplier.
multipliers[magnitude] = (byte) (numZeros - magnitude - 1);
if (magnitude > largestMagnitude) {
largestMagnitude = magnitude;
}
isEmpty = false;
}
}
}
}
@Override
public int getMultiplier(int magnitude) {
if (magnitude < 0) {
return 0;
}
if (magnitude > largestMagnitude) {
magnitude = largestMagnitude;
}
return multipliers[magnitude];
}
public String getPattern(int magnitude, StandardPlural plural) {
if (magnitude < 0) {
return null;
}
if (magnitude > largestMagnitude) {
magnitude = largestMagnitude;
}
String patternString = patterns[getIndex(magnitude, plural)];
if (patternString == null && plural != StandardPlural.OTHER) {
// Fall back to "other" plural variant
patternString = patterns[getIndex(magnitude, StandardPlural.OTHER)];
}
if (patternString == USE_FALLBACK) { // == is intended
// Return null if USE_FALLBACK is present
patternString = null;
}
return patternString;
}
public void getUniquePatterns(Set<String> output) {
assert output.isEmpty();
// NOTE: In C++, this is done more manually with a UVector.
// In Java, we can take advantage of JDK HashSet.
output.addAll(Arrays.asList(patterns));
output.remove(USE_FALLBACK);
output.remove(null);
}
private static final class CompactDataSink extends UResource.Sink {
CompactData data;
public CompactDataSink(CompactData data) {
this.data = data;
}
@Override
public void put(UResource.Key key, UResource.Value value, boolean isRoot) {
// traverse into the table of powers of ten
UResource.Table powersOfTenTable = value.getTable();
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.
byte magnitude = (byte) (key.length() - 1);
byte multiplier = data.multipliers[magnitude];
assert magnitude < COMPACT_MAX_DIGITS;
// Iterate over the plural variants ("one", "other", etc)
UResource.Table pluralVariantsTable = value.getTable();
for (int i4 = 0; pluralVariantsTable.getKeyAndValue(i4, key, value); ++i4) {
// Skip this magnitude/plural if we already have it from a child locale.
// Note: This also skips USE_FALLBACK entries.
StandardPlural plural = StandardPlural.fromString(key.toString());
if (data.patterns[getIndex(magnitude, plural)] != null) {
continue;
}
// The value "0" means that we need to use the default pattern and not fall back
// to parent locales. Example locale where this is relevant: 'it'.
String patternString = value.toString();
if (patternString.equals("0")) {
patternString = USE_FALLBACK;
}
// Save the pattern string. We will parse it lazily.
data.patterns[getIndex(magnitude, plural)] = patternString;
// If necessary, compute the multiplier: the difference between the magnitude
// and the number of zeros in the pattern.
if (multiplier == 0) {
int numZeros = countZeros(patternString);
if (numZeros > 0) { // numZeros==0 in certain cases, like Somali "Kun"
multiplier = (byte) (numZeros - magnitude - 1);
}
}
}
// Save the multiplier.
if (data.multipliers[magnitude] == 0) {
data.multipliers[magnitude] = multiplier;
if (magnitude > data.largestMagnitude) {
data.largestMagnitude = magnitude;
}
data.isEmpty = false;
} else {
assert data.multipliers[magnitude] == multiplier;
}
}
}
}
private static final int getIndex(int magnitude, StandardPlural plural) {
return magnitude * StandardPlural.COUNT + plural.ordinal();
}
private static final int countZeros(String patternString) {
// NOTE: This strategy for computing the number of zeros is a hack for efficiency.
// It could break if there are any 0s that aren't part of the main pattern.
int numZeros = 0;
for (int i = 0; i < patternString.length(); i++) {
if (patternString.charAt(i) == '0') {
numZeros++;
} else if (numZeros > 0) {
break; // zeros should always be contiguous
}
}
return numZeros;
}
}

View file

@ -0,0 +1,80 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number;
import com.ibm.icu.text.NumberFormat.Field;
/**
* The canonical implementation of {@link Modifier}, containing a prefix and suffix string.
*/
public class ConstantAffixModifier implements Modifier {
// TODO: Avoid making a new instance by default if prefix and suffix are empty
public static final ConstantAffixModifier EMPTY = new ConstantAffixModifier();
private final String prefix;
private final String suffix;
private final Field field;
private final boolean strong;
/**
* Constructs an instance with the given strings.
*
* <p>
* The arguments need to be Strings, not CharSequences, because Strings are immutable but CharSequences are not.
*
* @param prefix
* The prefix string.
* @param suffix
* The suffix string.
* @param field
* The field type to be associated with this modifier. Can be null.
* @param strong
* Whether this modifier should be strongly applied.
* @see Field
*/
public ConstantAffixModifier(String prefix, String suffix, Field field, boolean strong) {
// Use an empty string instead of null if we are given null
// TODO: Consider returning a null modifier if both prefix and suffix are empty.
this.prefix = (prefix == null ? "" : prefix);
this.suffix = (suffix == null ? "" : suffix);
this.field = field;
this.strong = strong;
}
/** Constructs a new instance with an empty prefix, suffix, and field. */
public ConstantAffixModifier() {
prefix = "";
suffix = "";
field = null;
strong = false;
}
@Override
public int apply(NumberStringBuilder output, int leftIndex, int rightIndex) {
// Insert the suffix first since inserting the prefix will change the rightIndex
int length = output.insert(rightIndex, suffix, field);
length += output.insert(leftIndex, prefix, field);
return length;
}
@Override
public int getPrefixLength() {
return prefix.length();
}
@Override
public int getCodePointCount() {
return prefix.codePointCount(0, prefix.length()) + suffix.codePointCount(0, suffix.length());
}
@Override
public boolean isStrong() {
return strong;
}
@Override
public String toString() {
return String.format("<ConstantAffixModifier prefix:'%s' suffix:'%s'>", prefix, suffix);
}
}

View file

@ -0,0 +1,61 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
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).
*/
public class ConstantMultiFieldModifier implements Modifier {
// NOTE: In Java, these are stored as array pointers. In C++, the NumberStringBuilder is stored by
// value and is treated internally as immutable.
protected final char[] prefixChars;
protected final char[] suffixChars;
protected final Field[] prefixFields;
protected final Field[] suffixFields;
private final boolean strong;
public ConstantMultiFieldModifier(NumberStringBuilder prefix, NumberStringBuilder suffix, boolean strong) {
prefixChars = prefix.toCharArray();
suffixChars = suffix.toCharArray();
prefixFields = prefix.toFieldArray();
suffixFields = suffix.toFieldArray();
this.strong = strong;
}
@Override
public int apply(NumberStringBuilder output, int leftIndex, int rightIndex) {
// Insert the suffix first since inserting the prefix will change the rightIndex
int length = output.insert(rightIndex, suffixChars, suffixFields);
length += output.insert(leftIndex, prefixChars, prefixFields);
return length;
}
@Override
public int getPrefixLength() {
return prefixChars.length;
}
@Override
public int getCodePointCount() {
return Character.codePointCount(prefixChars, 0, prefixChars.length)
+ Character.codePointCount(suffixChars, 0, suffixChars.length);
}
@Override
public boolean isStrong() {
return strong;
}
@Override
public String toString() {
NumberStringBuilder temp = new NumberStringBuilder();
apply(temp, 0, 0);
int prefixLength = getPrefixLength();
return String.format("<ConstantMultiFieldModifier prefix:'%s' suffix:'%s'>", temp.subSequence(0, prefixLength),
temp.subSequence(prefixLength, temp.length()));
}
}

View file

@ -0,0 +1,152 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number;
import com.ibm.icu.text.DecimalFormatSymbols;
import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.text.UnicodeSet;
/** Identical to {@link ConstantMultiFieldModifier}, but supports currency spacing. */
public class CurrencySpacingEnabledModifier extends ConstantMultiFieldModifier {
// These are the default currency spacing UnicodeSets in CLDR.
// Pre-compute them for performance.
// The unit test testCurrencySpacingPatternStability() will start failing if these change in CLDR.
private static final UnicodeSet UNISET_DIGIT = new UnicodeSet("[:digit:]").freeze();
private static final UnicodeSet UNISET_NOTS = new UnicodeSet("[:^S:]").freeze();
// Constants for better readability. Types are for compiler checking.
static final byte PREFIX = 0;
static final byte SUFFIX = 1;
static final short IN_CURRENCY = 0;
static final short IN_NUMBER = 1;
private final UnicodeSet afterPrefixUnicodeSet;
private final String afterPrefixInsert;
private final UnicodeSet beforeSuffixUnicodeSet;
private final String beforeSuffixInsert;
/** Safe code path */
public CurrencySpacingEnabledModifier(NumberStringBuilder prefix, NumberStringBuilder suffix, boolean strong,
DecimalFormatSymbols symbols) {
super(prefix, suffix, strong);
// Check for currency spacing. Do not build the UnicodeSets unless there is
// a currency code point at a boundary.
if (prefix.length() > 0 && prefix.fieldAt(prefix.length() - 1) == NumberFormat.Field.CURRENCY) {
int prefixCp = prefix.getLastCodePoint();
UnicodeSet prefixUnicodeSet = getUnicodeSet(symbols, IN_CURRENCY, PREFIX);
if (prefixUnicodeSet.contains(prefixCp)) {
afterPrefixUnicodeSet = getUnicodeSet(symbols, IN_NUMBER, PREFIX);
afterPrefixUnicodeSet.freeze(); // no-op if set is already frozen
afterPrefixInsert = getInsertString(symbols, PREFIX);
} else {
afterPrefixUnicodeSet = null;
afterPrefixInsert = null;
}
} else {
afterPrefixUnicodeSet = null;
afterPrefixInsert = null;
}
if (suffix.length() > 0 && suffix.fieldAt(0) == NumberFormat.Field.CURRENCY) {
int suffixCp = suffix.getLastCodePoint();
UnicodeSet suffixUnicodeSet = getUnicodeSet(symbols, IN_CURRENCY, SUFFIX);
if (suffixUnicodeSet.contains(suffixCp)) {
beforeSuffixUnicodeSet = getUnicodeSet(symbols, IN_NUMBER, SUFFIX);
beforeSuffixUnicodeSet.freeze(); // no-op if set is already frozen
beforeSuffixInsert = getInsertString(symbols, SUFFIX);
} else {
beforeSuffixUnicodeSet = null;
beforeSuffixInsert = null;
}
} else {
beforeSuffixUnicodeSet = null;
beforeSuffixInsert = null;
}
}
/** Safe code path */
@Override
public int apply(NumberStringBuilder output, int leftIndex, int rightIndex) {
// Currency spacing logic
int length = 0;
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
&& beforeSuffixUnicodeSet.contains(output.codePointBefore(rightIndex))) {
// TODO: Should we use the CURRENCY field here?
length += output.insert(rightIndex + length, beforeSuffixInsert, null);
}
// Call super for the remaining logic
length += super.apply(output, leftIndex, rightIndex + length);
return length;
}
/** Unsafe code path */
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);
boolean hasNumber = (suffixStart - prefixStart - prefixLen > 0); // could be empty string
if (hasPrefix && hasNumber) {
length += applyCurrencySpacingAffix(output, prefixStart + prefixLen, PREFIX, symbols);
}
if (hasSuffix && hasNumber) {
length += applyCurrencySpacingAffix(output, suffixStart + length, SUFFIX, symbols);
}
return length;
}
/** Unsafe code path */
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);
if (affixField != NumberFormat.Field.CURRENCY) {
return 0;
}
int affixCp = (affix == PREFIX) ? output.codePointBefore(index) : output.codePointAt(index);
UnicodeSet affixUniset = getUnicodeSet(symbols, IN_CURRENCY, affix);
if (!affixUniset.contains(affixCp)) {
return 0;
}
int numberCp = (affix == PREFIX) ? output.codePointAt(index) : output.codePointBefore(index);
UnicodeSet numberUniset = getUnicodeSet(symbols, IN_NUMBER, affix);
if (!numberUniset.contains(numberCp)) {
return 0;
}
String spacingString = getInsertString(symbols, affix);
// NOTE: This next line *inserts* the spacing string, triggering an arraycopy.
// It would be more efficient if this could be done before affixes were attached,
// so that it could be prepended/appended instead of inserted.
// However, the build code path is more efficient, and this is the most natural
// place to put currency spacing in the non-build code path.
// TODO: Should we use the CURRENCY field here?
return output.insert(index, spacingString, null);
}
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);
if (pattern.equals("[:digit:]")) {
return UNISET_DIGIT;
} else if (pattern.equals("[:^S:]")) {
return UNISET_NOTS;
} else {
return new UnicodeSet(pattern);
}
}
private static String getInsertString(DecimalFormatSymbols symbols, byte affix) {
return symbols.getPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_INSERT, affix == SUFFIX);
}
}

View file

@ -0,0 +1,61 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number;
import com.ibm.icu.text.DecimalFormatSymbols;
import com.ibm.icu.util.Currency;
import com.ibm.icu.util.ULocale;
public class CustomSymbolCurrency extends Currency {
private String symbol1;
private String symbol2;
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;
}
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;
}
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;
}
return super.getName(locale, nameStyle, pluralCount, isChoiceFormat);
}
@Override
public String getCurrencyCode() {
return symbol2;
}
}

File diff suppressed because it is too large Load diff

View file

@ -4,9 +4,11 @@ package com.ibm.icu.impl.number;
import java.math.BigDecimal;
import java.math.MathContext;
import java.text.FieldPosition;
import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.text.PluralRules;
import com.ibm.icu.text.UFieldPosition;
/**
* An interface representing a number to be processed by the decimal formatting pipeline. Includes
@ -20,18 +22,24 @@ import com.ibm.icu.text.PluralRules;
* <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 FormatQuantity extends PluralRules.IFixedDecimal {
public interface DecimalQuantity extends PluralRules.IFixedDecimal {
/**
* Sets the minimum and maximum digits that this {@link FormatQuantity} should generate. This
* method does not perform rounding.
* 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 setIntegerFractionLength(int minInt, int maxInt, int minFrac, int maxFrac);
public void setFractionLength(int minFrac, int maxFrac);
/**
* Rounds the number to a specified interval, such as 0.05.
@ -56,7 +64,7 @@ public interface FormatQuantity extends PluralRules.IFixedDecimal {
/**
* Rounds the number to an infinite number of decimal points. This has no effect except for
* forcing the double in {@link FormatQuantityBCD} to adopt its exact representation.
* forcing the double in {@link DecimalQuantity_AbstractBCD} to adopt its exact representation.
*/
public void roundToInfinity();
@ -81,28 +89,30 @@ public interface FormatQuantity extends PluralRules.IFixedDecimal {
*/
public int getMagnitude() throws ArithmeticException;
/** @return Whether the value represented by this {@link FormatQuantity} is zero. */
/** @return Whether the value represented by this {@link DecimalQuantity} is zero. */
public boolean isZero();
/** @return Whether the value represented by this {@link FormatQuantity} is less than zero. */
/** @return Whether the value represented by this {@link DecimalQuantity} is less than zero. */
public boolean isNegative();
/** @return Whether the value represented by this {@link FormatQuantity} is infinite. */
/** @return Whether the value represented by this {@link DecimalQuantity} is infinite. */
@Override
public boolean isInfinite();
/** @return Whether the value represented by this {@link FormatQuantity} is not a number. */
/** @return Whether the value represented by this {@link DecimalQuantity} is not a number. */
@Override
public boolean isNaN();
/** @return The value contained in this {@link FormatQuantity} approximated as a double. */
/** @return The value contained in this {@link DecimalQuantity} approximated as a double. */
public double toDouble();
public BigDecimal toBigDecimal();
public void setToBigDecimal(BigDecimal input);
public int maxRepresentableDigits();
// TODO: Should this method be removed, since FormatQuantity implements IFixedDecimal now?
// 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.
*
@ -112,36 +122,6 @@ public interface FormatQuantity extends PluralRules.IFixedDecimal {
*/
public StandardPlural getStandardPlural(PluralRules rules);
// /**
// * @return The number of fraction digits, always in the closed interval [minFrac, maxFrac].
// * @see #setIntegerFractionLength(int, int, int, int)
// */
// public int fractionCount();
//
// /**
// * @return The number of integer digits, always in the closed interval [minInt, maxInt].
// * @see #setIntegerFractionLength(int, int, int, int)
// */
// public int integerCount();
//
// /**
// * @param index The index of the fraction digit relative to the decimal place, or 1 minus the
// * digit's power of ten.
// * @return The digit at the specified index. Undefined if index is greater than maxInt or less
// * than 0.
// * @see #fractionCount()
// */
// public byte getFractionDigit(int index);
//
// /**
// * @param index The index of the integer digit relative to the decimal place, or the digit's power
// * of ten.
// * @return The digit at the specified index. Undefined if index is greater than maxInt or less
// * than 0.
// * @see #integerCount()
// */
// public byte getIntegerDigit(int index);
/**
* 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.
@ -167,17 +147,34 @@ public interface FormatQuantity extends PluralRules.IFixedDecimal {
*/
public int getLowerDisplayMagnitude();
/**
* 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 FormatQuantity createCopy();
public void copyFrom(FormatQuantity other);
public DecimalQuantity createCopy();
/**
* This method is for internal testing only.
* 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();
/**
* 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

@ -15,9 +15,9 @@ import com.ibm.icu.text.UFieldPosition;
/**
* Represents numbers and digit display properties using Binary Coded Decimal (BCD).
*
* @implements {@link FormatQuantity}
* @implements {@link DecimalQuantity}
*/
public abstract class FormatQuantityBCD implements FormatQuantity {
public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
/**
* The power of ten corresponding to the least significant digit in the BCD. For example, if this
@ -44,17 +44,17 @@ public abstract class FormatQuantityBCD implements FormatQuantity {
* @see #INFINITY_FLAG
* @see #NAN_FLAG
*/
protected int flags;
protected byte flags;
protected static final int NEGATIVE_FLAG = 1;
protected static final int INFINITY_FLAG = 2;
protected static final int NAN_FLAG = 4;
// The following three fields relate to the double-to-ascii fast path algorithm.
// When a double is given to FormatQuantityBCD, it is converted to using a fast algorithm. The
// When a double is given to DecimalQuantityBCD, it is converted to using a fast algorithm. The
// fast algorithm guarantees correctness to only the first ~12 digits of the double. The process
// of rounding the number ensures that the converted digits are correct, falling back to a slow-
// path algorithm if required. Therefore, if a FormatQuantity is constructed from a double, it
// path algorithm if required. Therefore, if a DecimalQuantity is constructed from a double, it
// is *required* that roundToMagnitude(), roundToIncrement(), or roundToInfinity() is called. If
// you don't round, assertions will fail in certain other methods if you try calling them.
@ -108,9 +108,9 @@ public abstract class FormatQuantityBCD implements FormatQuantity {
protected int rOptPos = Integer.MIN_VALUE;
@Override
public void copyFrom(FormatQuantity _other) {
public void copyFrom(DecimalQuantity _other) {
copyBcdFrom(_other);
FormatQuantityBCD other = (FormatQuantityBCD) _other;
DecimalQuantity_AbstractBCD other = (DecimalQuantity_AbstractBCD) _other;
lOptPos = other.lOptPos;
lReqPos = other.lReqPos;
rReqPos = other.rReqPos;
@ -123,7 +123,7 @@ public abstract class FormatQuantityBCD implements FormatQuantity {
isApproximate = other.isApproximate;
}
public FormatQuantityBCD clear() {
public DecimalQuantity_AbstractBCD clear() {
lOptPos = Integer.MAX_VALUE;
lReqPos = 0;
rReqPos = 0;
@ -134,17 +134,25 @@ public abstract class FormatQuantityBCD implements FormatQuantity {
}
@Override
public void setIntegerFractionLength(int minInt, int maxInt, int minFrac, int maxFrac) {
// Validation should happen outside of FormatQuantity, e.g., in the Rounder class.
public void setIntegerLength(int minInt, int maxInt) {
// Validation should happen outside of DecimalQuantity, e.g., in the Rounder class.
assert minInt >= 0;
assert maxInt >= minInt;
assert minFrac >= 0;
assert maxFrac >= minFrac;
// Save values into internal state
// Negation is safe for minFrac/maxFrac because -Integer.MAX_VALUE > Integer.MIN_VALUE
lOptPos = maxInt;
lReqPos = minInt;
}
@Override
public void setFractionLength(int minFrac, int maxFrac) {
// Validation should happen outside of DecimalQuantity, e.g., in the Rounder class.
assert minFrac >= 0;
assert maxFrac >= minFrac;
// Save values into internal state
// Negation is safe for minFrac/maxFrac because -Integer.MAX_VALUE > Integer.MIN_VALUE
rReqPos = -minFrac;
rOptPos = -maxFrac;
}
@ -160,12 +168,12 @@ public abstract class FormatQuantityBCD implements FormatQuantity {
}
@Override
public void roundToIncrement(BigDecimal roundingInterval, MathContext mathContext) {
public void roundToIncrement(BigDecimal roundingIncrement, MathContext mathContext) {
// TODO: Avoid converting back and forth to BigDecimal.
BigDecimal temp = toBigDecimal();
temp =
temp.divide(roundingInterval, 0, mathContext.getRoundingMode())
.multiply(roundingInterval)
temp.divide(roundingIncrement, 0, mathContext.getRoundingMode())
.multiply(roundingIncrement)
.round(mathContext);
if (temp.signum() == 0) {
setBcdToZero(); // keeps negative flag for -0.0
@ -176,6 +184,9 @@ public abstract class FormatQuantityBCD implements FormatQuantity {
@Override
public void multiplyBy(BigDecimal multiplicand) {
if (isInfinite() || isZero() || isNaN()) {
return;
}
BigDecimal temp = toBigDecimal();
temp = temp.multiply(multiplicand);
setToBigDecimal(temp);
@ -232,13 +243,7 @@ public abstract class FormatQuantityBCD implements FormatQuantity {
}
}
/**
* 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.
*/
@Override
public void populateUFieldPosition(FieldPosition fp) {
if (fp instanceof UFieldPosition) {
((UFieldPosition) fp)
@ -305,19 +310,6 @@ public abstract class FormatQuantityBCD implements FormatQuantity {
return precision == 0;
}
@Override
public FormatQuantity createCopy() {
if (this instanceof FormatQuantity2) {
return new FormatQuantity2((FormatQuantity2) this);
} else if (this instanceof FormatQuantity3) {
return new FormatQuantity3((FormatQuantity3) this);
} else if (this instanceof FormatQuantity4) {
return new FormatQuantity4((FormatQuantity4) this);
} else {
throw new IllegalArgumentException("Don't know how to copy " + this.getClass());
}
}
public void setToInt(int n) {
setBcdToZero();
flags = 0;
@ -418,6 +410,11 @@ public abstract class FormatQuantityBCD implements FormatQuantity {
* to digits. Since double arithmetic is inexact, the resulting digits may not be accurate.
*/
private void _setToDoubleFast(double n) {
isApproximate = true;
origDouble = n;
origDelta = 0;
// NOTE: Unlike ICU4C, doubles are always IEEE 754 doubles.
long ieeeBits = Double.doubleToLongBits(n);
int exponent = (int) ((ieeeBits & 0x7ff0000000000000L) >> 52) - 0x3ff;
@ -427,10 +424,6 @@ public abstract class FormatQuantityBCD implements FormatQuantity {
return;
}
isApproximate = true;
origDouble = n;
origDelta = 0;
// 3.3219... is log2(10)
int fracLength = (int) ((52 - exponent) / 3.32192809489);
if (fracLength >= 0) {
@ -462,41 +455,42 @@ public abstract class FormatQuantityBCD implements FormatQuantity {
int delta = origDelta;
setBcdToZero();
// Call the slow oracle function
String temp = Double.toString(n);
// Call the slow oracle function (Double.toString in Java, sprintf in C++).
String dstr = Double.toString(n);
if (temp.indexOf('E') != -1) {
if (dstr.indexOf('E') != -1) {
// Case 1: Exponential notation.
assert temp.indexOf('.') == 1;
int expPos = temp.indexOf('E');
_setToLong(Long.parseLong(temp.charAt(0) + temp.substring(2, expPos)));
scale += Integer.parseInt(temp.substring(expPos + 1)) - (expPos - 1) + 1;
} else if (temp.charAt(0) == '0') {
assert dstr.indexOf('.') == 1;
int expPos = dstr.indexOf('E');
_setToLong(Long.parseLong(dstr.charAt(0) + dstr.substring(2, expPos)));
scale += Integer.parseInt(dstr.substring(expPos + 1)) - (expPos - 1) + 1;
} else if (dstr.charAt(0) == '0') {
// Case 2: Fraction-only number.
assert temp.indexOf('.') == 1;
_setToLong(Long.parseLong(temp.substring(2)));
scale += 2 - temp.length();
} else if (temp.charAt(temp.length() - 1) == '0') {
assert dstr.indexOf('.') == 1;
_setToLong(Long.parseLong(dstr.substring(2)));
scale += 2 - dstr.length();
} else if (dstr.charAt(dstr.length() - 1) == '0') {
// Case 3: Integer-only number.
// Note: this path should not normally happen, because integer-only numbers are captured
// before the approximate double logic is performed.
assert temp.indexOf('.') == temp.length() - 2;
assert temp.length() - 2 <= 18;
_setToLong(Long.parseLong(temp.substring(0, temp.length() - 2)));
assert dstr.indexOf('.') == dstr.length() - 2;
assert dstr.length() - 2 <= 18;
_setToLong(Long.parseLong(dstr.substring(0, dstr.length() - 2)));
// no need to adjust scale
} else {
// Case 4: Number with both a fraction and an integer.
int decimalPos = temp.indexOf('.');
_setToLong(Long.parseLong(temp.substring(0, decimalPos) + temp.substring(decimalPos + 1)));
scale += decimalPos - temp.length() + 1;
int decimalPos = dstr.indexOf('.');
_setToLong(Long.parseLong(dstr.substring(0, decimalPos) + dstr.substring(decimalPos + 1)));
scale += decimalPos - dstr.length() + 1;
}
scale += delta;
compact();
explicitExactDouble = true;
}
/**
* Whether this {@link FormatQuantity4} has been explicitly converted to an exact double. true if
* Whether this {@link DecimalQuantity_DualStorageBCD} has been explicitly converted to an exact double. true if
* backed by a double that was explicitly converted via convertToAccurateDouble; false otherwise.
* Used for testing.
*
@ -510,6 +504,7 @@ public abstract class FormatQuantityBCD implements FormatQuantity {
*
* @param n The value to consume.
*/
@Override
public void setToBigDecimal(BigDecimal n) {
setBcdToZero();
flags = 0;
@ -634,6 +629,9 @@ public abstract class FormatQuantityBCD implements FormatQuantity {
return diff;
}
private static final int SECTION_LOWER_EDGE = -1;
private static final int SECTION_UPPER_EDGE = -2;
@Override
public void roundToMagnitude(int magnitude, MathContext mathContext) {
// The position in the BCD at which rounding will be performed; digits to the right of position
@ -683,7 +681,7 @@ public abstract class FormatQuantityBCD implements FormatQuantity {
int p = safeSubtract(position, 2);
int minP = Math.max(0, precision - 14);
if (leadingDigit == 0) {
section = -1;
section = SECTION_LOWER_EDGE;
for (; p >= minP; p--) {
if (getDigitPos(p) != 0) {
section = RoundingUtils.SECTION_LOWER;
@ -705,7 +703,7 @@ public abstract class FormatQuantityBCD implements FormatQuantity {
}
}
} else if (leadingDigit == 9) {
section = -2;
section = SECTION_UPPER_EDGE;
for (; p >= minP; p--) {
if (getDigitPos(p) != 9) {
section = RoundingUtils.SECTION_UPPER;
@ -741,8 +739,8 @@ public abstract class FormatQuantityBCD implements FormatQuantity {
}
// Good to continue rounding.
if (section == -1) section = RoundingUtils.SECTION_LOWER;
if (section == -2) section = RoundingUtils.SECTION_UPPER;
if (section == SECTION_LOWER_EDGE) section = RoundingUtils.SECTION_LOWER;
if (section == SECTION_UPPER_EDGE) section = RoundingUtils.SECTION_UPPER;
}
boolean roundDown =
@ -789,7 +787,7 @@ public abstract class FormatQuantityBCD implements FormatQuantity {
/**
* Appends a digit, optionally with one or more leading zeros, to the end of the value represented
* by this FormatQuantity.
* by this DecimalQuantity.
*
* <p>The primary use of this method is to construct numbers during a parsing loop. It allows
* parsing to take advantage of the digit list infrastructure primarily designed for formatting.
@ -835,6 +833,20 @@ public abstract class FormatQuantityBCD implements FormatQuantity {
}
}
@Override
public String toPlainString() {
// NOTE: This logic is duplicated between here and DecimalQuantity_SimpleStorage.
StringBuilder sb = new StringBuilder();
if (isNegative()) {
sb.append('-');
}
for (int m = getUpperDisplayMagnitude(); m >= getLowerDisplayMagnitude(); m--) {
sb.append(getDigit(m));
if (m == 0) sb.append('.');
}
return sb.toString();
}
/**
* Returns a single digit from the BCD list. No internal state is changed by calling this method.
*
@ -903,7 +915,7 @@ public abstract class FormatQuantityBCD implements FormatQuantity {
*/
protected abstract BigDecimal bcdToBigDecimal();
protected abstract void copyBcdFrom(FormatQuantity _other);
protected abstract void copyBcdFrom(DecimalQuantity _other);
/**
* Removes trailing zeros from the BCD (adjusting the scale as required) and then computes the

View file

@ -5,7 +5,11 @@ package com.ibm.icu.impl.number;
import java.math.BigDecimal;
import java.math.BigInteger;
public final class FormatQuantity4 extends FormatQuantityBCD {
/**
* 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
@ -25,35 +29,35 @@ public final class FormatQuantity4 extends FormatQuantityBCD {
return Integer.MAX_VALUE;
}
public FormatQuantity4() {
public DecimalQuantity_DualStorageBCD() {
setBcdToZero();
}
public FormatQuantity4(long input) {
public DecimalQuantity_DualStorageBCD(long input) {
setToLong(input);
}
public FormatQuantity4(int input) {
public DecimalQuantity_DualStorageBCD(int input) {
setToInt(input);
}
public FormatQuantity4(double input) {
public DecimalQuantity_DualStorageBCD(double input) {
setToDouble(input);
}
public FormatQuantity4(BigInteger input) {
public DecimalQuantity_DualStorageBCD(BigInteger input) {
setToBigInteger(input);
}
public FormatQuantity4(BigDecimal input) {
public DecimalQuantity_DualStorageBCD(BigDecimal input) {
setToBigDecimal(input);
}
public FormatQuantity4(FormatQuantity4 other) {
public DecimalQuantity_DualStorageBCD(DecimalQuantity_DualStorageBCD other) {
copyFrom(other);
}
public FormatQuantity4(Number number) {
public DecimalQuantity_DualStorageBCD(Number number) {
if (number instanceof Long) {
setToLong(number.longValue());
} else if (number instanceof Integer) {
@ -72,6 +76,11 @@ public final class FormatQuantity4 extends FormatQuantityBCD {
}
}
@Override
public DecimalQuantity createCopy() {
return new DecimalQuantity_DualStorageBCD(this);
}
@Override
protected byte getDigitPos(int position) {
if (usingBytes) {
@ -140,11 +149,9 @@ public final class FormatQuantity4 extends FormatQuantityBCD {
@Override
protected void setBcdToZero() {
if (usingBytes) {
for (int i = 0; i < precision; i++) {
bcdBytes[i] = (byte) 0;
}
bcdBytes = null;
usingBytes = false;
}
usingBytes = false;
bcdLong = 0L;
scale = 0;
precision = 0;
@ -162,7 +169,7 @@ public final class FormatQuantity4 extends FormatQuantityBCD {
for (; n != 0; n /= 10, i--) {
result = (result >>> 4) + (((long) n % 10) << 60);
}
usingBytes = false;
assert !usingBytes;
bcdLong = result >>> (i * 4);
scale = 0;
precision = 16 - i;
@ -177,7 +184,7 @@ public final class FormatQuantity4 extends FormatQuantityBCD {
for (; n != 0L; n /= 10L, i++) {
bcdBytes[i] = (byte) (n % 10);
}
usingBytes = true;
assert usingBytes;
scale = 0;
precision = i;
} else {
@ -187,7 +194,7 @@ public final class FormatQuantity4 extends FormatQuantityBCD {
result = (result >>> 4) + ((n % 10) << 60);
}
assert i >= 0;
usingBytes = false;
assert !usingBytes;
bcdLong = result >>> (i * 4);
scale = 0;
precision = 16 - i;
@ -205,7 +212,6 @@ public final class FormatQuantity4 extends FormatQuantityBCD {
bcdBytes[i] = temp[1].byteValue();
n = temp[0];
}
usingBytes = true;
scale = 0;
precision = i;
}
@ -214,17 +220,11 @@ public final class FormatQuantity4 extends FormatQuantityBCD {
protected BigDecimal bcdToBigDecimal() {
if (usingBytes) {
// Converting to a string here is faster than doing BigInteger/BigDecimal arithmetic.
StringBuilder sb = new StringBuilder();
if (isNegative()) sb.append('-');
assert precision > 0;
for (int i = precision - 1; i >= 0; i--) {
sb.append(getDigitPos(i));
BigDecimal result = new BigDecimal(toNumberString());
if (isNegative()) {
result = result.negate();
}
if (scale != 0) {
sb.append('E');
sb.append(scale);
}
return new BigDecimal(sb.toString());
return result;
} else {
long tempLong = 0L;
for (int shift = (precision - 1); shift >= 0; shift--) {
@ -285,13 +285,15 @@ public final class FormatQuantity4 extends FormatQuantityBCD {
private void ensureCapacity(int capacity) {
if (capacity == 0) return;
if (bcdBytes == null) {
int oldCapacity = usingBytes ? bcdBytes.length : 0;
if (!usingBytes) {
bcdBytes = new byte[capacity];
} else if (bcdBytes.length < capacity) {
} else if (oldCapacity < capacity) {
byte[] bcd1 = new byte[capacity * 2];
System.arraycopy(bcdBytes, 0, bcd1, 0, bcdBytes.length);
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. */
@ -302,8 +304,8 @@ public final class FormatQuantity4 extends FormatQuantityBCD {
for (int i = precision - 1; i >= 0; i--) {
bcdLong <<= 4;
bcdLong |= bcdBytes[i];
bcdBytes[i] = 0;
}
bcdBytes = null;
usingBytes = false;
} else {
// Change from long to bytes
@ -312,19 +314,18 @@ public final class FormatQuantity4 extends FormatQuantityBCD {
bcdBytes[i] = (byte) (bcdLong & 0xf);
bcdLong >>>= 4;
}
usingBytes = true;
assert usingBytes;
}
}
@Override
protected void copyBcdFrom(FormatQuantity _other) {
FormatQuantity4 other = (FormatQuantity4) _other;
protected void copyBcdFrom(DecimalQuantity _other) {
DecimalQuantity_DualStorageBCD other = (DecimalQuantity_DualStorageBCD) _other;
setBcdToZero();
if (other.usingBytes) {
usingBytes = true;
ensureCapacity(other.precision);
System.arraycopy(other.bcdBytes, 0, bcdBytes, 0, other.precision);
} else {
usingBytes = false;
bcdLong = other.bcdLong;
}
}
@ -376,36 +377,40 @@ public final class FormatQuantity4 extends FormatQuantityBCD {
}
/**
* Checks whether this {@link FormatQuantity4} is using its internal byte array storage mechanism.
* 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 usingBytes() {
public boolean isUsingBytes() {
return usingBytes;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (usingBytes) {
for (int i = precision - 1; i >= 0; i--) {
sb.append(bcdBytes[i]);
}
} else {
sb.append(Long.toHexString(bcdLong));
}
return String.format(
"<FormatQuantity4 %s:%d:%d:%s %s %s%s%d>",
(lOptPos > 1000 ? "max" : String.valueOf(lOptPos)),
"<DecimalQuantity %s:%d:%d:%s %s %s>",
(lOptPos > 1000 ? "999" : String.valueOf(lOptPos)),
lReqPos,
rReqPos,
(rOptPos < -1000 ? "min" : String.valueOf(rOptPos)),
(rOptPos < -1000 ? "-999" : String.valueOf(rOptPos)),
(usingBytes ? "bytes" : "long"),
sb,
"E",
scale);
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

@ -1,298 +0,0 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import com.ibm.icu.impl.number.Format.BeforeTargetAfterFormat;
import com.ibm.icu.impl.number.Format.SingularFormat;
import com.ibm.icu.impl.number.Format.TargetFormat;
import com.ibm.icu.impl.number.formatters.BigDecimalMultiplier;
import com.ibm.icu.impl.number.formatters.CompactDecimalFormat;
import com.ibm.icu.impl.number.formatters.CurrencyFormat;
import com.ibm.icu.impl.number.formatters.MagnitudeMultiplier;
import com.ibm.icu.impl.number.formatters.MeasureFormat;
import com.ibm.icu.impl.number.formatters.PaddingFormat;
import com.ibm.icu.impl.number.formatters.PositiveDecimalFormat;
import com.ibm.icu.impl.number.formatters.PositiveNegativeAffixFormat;
import com.ibm.icu.impl.number.formatters.RoundingFormat;
import com.ibm.icu.impl.number.formatters.ScientificFormat;
import com.ibm.icu.text.DecimalFormatSymbols;
import com.ibm.icu.text.PluralRules;
import com.ibm.icu.util.ULocale;
public class Endpoint {
// public static Format from(DecimalFormatSymbols symbols, Properties properties)
// throws ParseException {
// Format format = new PositiveIntegerFormat(symbols, properties);
// // TODO: integer-only format
// format = new PositiveDecimalFormat((SelfContainedFormat) format, symbols, properties);
// if (properties.useCompactDecimalFormat()) {
// format = CompactDecimalFormat.getInstance((SingularFormat) format, symbols, properties);
// } else {
// format =
// PositiveNegativeAffixFormat.getInstance((SingularFormat) format, symbols, properties);
// }
// if (properties.useRoundingInterval()) {
// format = new IntervalRoundingFormat((SingularFormat) format, properties);
// } else if (properties.useSignificantDigits()) {
// format = new SignificantDigitsFormat((SingularFormat) format, properties);
// } else if (properties.useFractionFormat()) {
// format = new RoundingFormat((SingularFormat) format, properties);
// }
// return format;
// }
public static interface IProperties {
static PluralRules DEFAULT_PLURAL_RULES = null;
public PluralRules getPluralRules();
public IProperties setPluralRules(PluralRules pluralRules);
}
public static Format fromBTA(Properties properties) {
return fromBTA(properties, getSymbols());
}
public static SingularFormat fromBTA(Properties properties, Locale locale) {
return fromBTA(properties, getSymbols(locale));
}
public static SingularFormat fromBTA(Properties properties, ULocale uLocale) {
return fromBTA(properties, getSymbols(uLocale));
}
public static SingularFormat fromBTA(String pattern) {
return fromBTA(getProperties(pattern), getSymbols());
}
public static SingularFormat fromBTA(String pattern, Locale locale) {
return fromBTA(getProperties(pattern), getSymbols(locale));
}
public static SingularFormat fromBTA(String pattern, ULocale uLocale) {
return fromBTA(getProperties(pattern), getSymbols(uLocale));
}
public static SingularFormat fromBTA(String pattern, DecimalFormatSymbols symbols) {
return fromBTA(getProperties(pattern), symbols);
}
public static SingularFormat fromBTA(Properties properties, DecimalFormatSymbols symbols) {
if (symbols == null) throw new IllegalArgumentException("symbols must not be null");
// TODO: This fast track results in an improvement of about 10ns during formatting. See if
// there is a way to implement it more elegantly.
boolean canUseFastTrack = true;
PluralRules rules = getPluralRules(symbols.getULocale(), properties);
BeforeTargetAfterFormat format = new Format.BeforeTargetAfterFormat(rules);
TargetFormat target = new PositiveDecimalFormat(symbols, properties);
format.setTargetFormat(target);
// TODO: integer-only format?
if (MagnitudeMultiplier.useMagnitudeMultiplier(properties)) {
canUseFastTrack = false;
format.addBeforeFormat(MagnitudeMultiplier.getInstance(properties));
}
if (BigDecimalMultiplier.useMultiplier(properties)) {
canUseFastTrack = false;
format.addBeforeFormat(BigDecimalMultiplier.getInstance(properties));
}
if (MeasureFormat.useMeasureFormat(properties)) {
canUseFastTrack = false;
format.addBeforeFormat(MeasureFormat.getInstance(symbols, properties));
}
if (CurrencyFormat.useCurrency(properties)) {
canUseFastTrack = false;
if (CompactDecimalFormat.useCompactDecimalFormat(properties)) {
format.addBeforeFormat(CompactDecimalFormat.getInstance(symbols, properties));
} else if (ScientificFormat.useScientificNotation(properties)) {
// TODO: Should the currency rounder or scientific rounder be used in this case?
// For now, default to using the scientific rounder.
format.addBeforeFormat(PositiveNegativeAffixFormat.getInstance(symbols, properties));
format.addBeforeFormat(ScientificFormat.getInstance(symbols, properties));
} else {
format.addBeforeFormat(CurrencyFormat.getCurrencyRounder(symbols, properties));
format.addBeforeFormat(CurrencyFormat.getCurrencyModifier(symbols, properties));
}
} else {
if (CompactDecimalFormat.useCompactDecimalFormat(properties)) {
canUseFastTrack = false;
format.addBeforeFormat(CompactDecimalFormat.getInstance(symbols, properties));
} else if (ScientificFormat.useScientificNotation(properties)) {
canUseFastTrack = false;
format.addBeforeFormat(PositiveNegativeAffixFormat.getInstance(symbols, properties));
format.addBeforeFormat(ScientificFormat.getInstance(symbols, properties));
} else {
format.addBeforeFormat(PositiveNegativeAffixFormat.getInstance(symbols, properties));
format.addBeforeFormat(RoundingFormat.getDefaultOrNoRounder(properties));
}
}
if (PaddingFormat.usePadding(properties)) {
canUseFastTrack = false;
format.addAfterFormat(PaddingFormat.getInstance(properties));
}
if (canUseFastTrack) {
return new Format.PositiveNegativeRounderTargetFormat(
PositiveNegativeAffixFormat.getInstance(symbols, properties),
RoundingFormat.getDefaultOrNoRounder(properties),
target);
} else {
return format;
}
}
public static String staticFormat(FormatQuantity input, Properties properties) {
return staticFormat(input, properties, getSymbols());
}
public static String staticFormat(FormatQuantity input, Properties properties, Locale locale) {
return staticFormat(input, properties, getSymbols(locale));
}
public static String staticFormat(FormatQuantity input, Properties properties, ULocale uLocale) {
return staticFormat(input, properties, getSymbols(uLocale));
}
public static String staticFormat(FormatQuantity input, String pattern) {
return staticFormat(input, getProperties(pattern), getSymbols());
}
public static String staticFormat(FormatQuantity input, String pattern, Locale locale) {
return staticFormat(input, getProperties(pattern), getSymbols(locale));
}
public static String staticFormat(FormatQuantity input, String pattern, ULocale uLocale) {
return staticFormat(input, getProperties(pattern), getSymbols(uLocale));
}
public static String staticFormat(
FormatQuantity input, String pattern, DecimalFormatSymbols symbols) {
return staticFormat(input, getProperties(pattern), symbols);
}
public static String staticFormat(
FormatQuantity input, Properties properties, DecimalFormatSymbols symbols) {
PluralRules rules = null;
ModifierHolder mods = Format.threadLocalModifierHolder.get().clear();
NumberStringBuilder sb = Format.threadLocalStringBuilder.get().clear();
int length = 0;
// Pre-processing
if (!input.isNaN()) {
if (MagnitudeMultiplier.useMagnitudeMultiplier(properties)) {
MagnitudeMultiplier.getInstance(properties).before(input, mods, rules);
}
if (BigDecimalMultiplier.useMultiplier(properties)) {
BigDecimalMultiplier.getInstance(properties).before(input, mods, rules);
}
if (MeasureFormat.useMeasureFormat(properties)) {
rules = (rules != null) ? rules : getPluralRules(symbols.getULocale(), properties);
MeasureFormat.getInstance(symbols, properties).before(input, mods, rules);
}
if (CompactDecimalFormat.useCompactDecimalFormat(properties)) {
rules = (rules != null) ? rules : getPluralRules(symbols.getULocale(), properties);
CompactDecimalFormat.apply(input, mods, rules, symbols, properties);
} else if (CurrencyFormat.useCurrency(properties)) {
rules = (rules != null) ? rules : getPluralRules(symbols.getULocale(), properties);
CurrencyFormat.getCurrencyRounder(symbols, properties).before(input, mods, rules);
CurrencyFormat.getCurrencyModifier(symbols, properties).before(input, mods, rules);
} else if (ScientificFormat.useScientificNotation(properties)) {
// TODO: Is it possible to combine significant digits with currency?
PositiveNegativeAffixFormat.getInstance(symbols, properties).before(input, mods, rules);
ScientificFormat.getInstance(symbols, properties).before(input, mods, rules);
} else {
PositiveNegativeAffixFormat.apply(input, mods, symbols, properties);
RoundingFormat.getDefaultOrNoRounder(properties).before(input, mods, rules);
}
}
// Primary format step
length += new PositiveDecimalFormat(symbols, properties).target(input, sb, 0);
length += mods.applyStrong(sb, 0, length);
// Post-processing
if (PaddingFormat.usePadding(properties)) {
length += PaddingFormat.getInstance(properties).after(mods, sb, 0, length);
}
length += mods.applyAll(sb, 0, length);
return sb.toString();
}
private static final ThreadLocal<Map<ULocale, DecimalFormatSymbols>> threadLocalSymbolsCache =
new ThreadLocal<Map<ULocale, DecimalFormatSymbols>>() {
@Override
protected Map<ULocale, DecimalFormatSymbols> initialValue() {
return new HashMap<ULocale, DecimalFormatSymbols>();
}
};
private static DecimalFormatSymbols getSymbols() {
ULocale uLocale = ULocale.getDefault();
return getSymbols(uLocale);
}
private static DecimalFormatSymbols getSymbols(Locale locale) {
ULocale uLocale = ULocale.forLocale(locale);
return getSymbols(uLocale);
}
private static DecimalFormatSymbols getSymbols(ULocale uLocale) {
if (uLocale == null) uLocale = ULocale.getDefault();
DecimalFormatSymbols symbols = threadLocalSymbolsCache.get().get(uLocale);
if (symbols == null) {
symbols = DecimalFormatSymbols.getInstance(uLocale);
threadLocalSymbolsCache.get().put(uLocale, symbols);
}
return symbols;
}
private static final ThreadLocal<Map<String, Properties>> threadLocalPropertiesCache =
new ThreadLocal<Map<String, Properties>>() {
@Override
protected Map<String, Properties> initialValue() {
return new HashMap<String, Properties>();
}
};
private static Properties getProperties(String pattern) {
if (pattern == null) pattern = "#";
Properties properties = threadLocalPropertiesCache.get().get(pattern);
if (properties == null) {
properties = PatternString.parseToProperties(pattern);
threadLocalPropertiesCache.get().put(pattern.intern(), properties);
}
return properties;
}
private static final ThreadLocal<Map<ULocale, PluralRules>> threadLocalRulesCache =
new ThreadLocal<Map<ULocale, PluralRules>>() {
@Override
protected Map<ULocale, PluralRules> initialValue() {
return new HashMap<ULocale, PluralRules>();
}
};
private static PluralRules getPluralRules(ULocale uLocale, Properties properties) {
if (properties.getPluralRules() != null) {
return properties.getPluralRules();
}
// Backwards compatibility: CurrencyPluralInfo wraps its own copy of PluralRules
if (properties.getCurrencyPluralInfo() != null) {
return properties.getCurrencyPluralInfo().getPluralRules();
}
if (uLocale == null) uLocale = ULocale.getDefault();
PluralRules rules = threadLocalRulesCache.get().get(uLocale);
if (rules == null) {
rules = PluralRules.forLocale(uLocale);
threadLocalRulesCache.get().put(uLocale, rules);
}
return rules;
}
}

View file

@ -1,15 +0,0 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number;
/**
* This is a small interface I made to assist with converting from a formatter pipeline object to a
* pattern string. It allows classes to "export" themselves to a property bag, which in turn can be
* passed to {@link PatternString#propertiesToString(Properties)} to generate the pattern string.
*
* <p>Depending on the new API we expose, this process might not be necessary if we persist the
* property bag in the current DecimalFormat shim.
*/
public interface Exportable {
public void export(Properties properties);
}

View file

@ -1,277 +0,0 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number;
import java.text.AttributedCharacterIterator;
import java.text.FieldPosition;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
import com.ibm.icu.text.PluralRules;
// TODO: Get a better name for this base class.
public abstract class Format {
protected static final ThreadLocal<NumberStringBuilder> threadLocalStringBuilder =
new ThreadLocal<NumberStringBuilder>() {
@Override
protected NumberStringBuilder initialValue() {
return new NumberStringBuilder();
}
};
protected static final ThreadLocal<ModifierHolder> threadLocalModifierHolder =
new ThreadLocal<ModifierHolder>() {
@Override
protected ModifierHolder initialValue() {
return new ModifierHolder();
}
};
public String format(FormatQuantity... inputs) {
// Setup
Deque<FormatQuantity> inputDeque = new ArrayDeque<FormatQuantity>();
inputDeque.addAll(Arrays.asList(inputs));
ModifierHolder modDeque = threadLocalModifierHolder.get().clear();
NumberStringBuilder sb = threadLocalStringBuilder.get().clear();
// Primary "recursion" step, calling the implementation's process method
int length = process(inputDeque, modDeque, sb, 0);
// Resolve remaining affixes
modDeque.applyAll(sb, 0, length);
return sb.toString();
}
/** A Format that works on only one number. */
public abstract static class SingularFormat extends Format implements Exportable {
public String format(FormatQuantity input) {
NumberStringBuilder sb = formatToStringBuilder(input);
return sb.toString();
}
public void format(FormatQuantity input, StringBuffer output) {
NumberStringBuilder sb = formatToStringBuilder(input);
output.append(sb);
}
public String format(FormatQuantity input, FieldPosition fp) {
NumberStringBuilder sb = formatToStringBuilder(input);
sb.populateFieldPosition(fp, 0);
return sb.toString();
}
public void format(FormatQuantity input, StringBuffer output, FieldPosition fp) {
NumberStringBuilder sb = formatToStringBuilder(input);
sb.populateFieldPosition(fp, output.length());
output.append(sb);
}
public AttributedCharacterIterator formatToCharacterIterator(FormatQuantity input) {
NumberStringBuilder sb = formatToStringBuilder(input);
return sb.getIterator();
}
private NumberStringBuilder formatToStringBuilder(FormatQuantity input) {
// Setup
ModifierHolder modDeque = threadLocalModifierHolder.get().clear();
NumberStringBuilder sb = threadLocalStringBuilder.get().clear();
// Primary "recursion" step, calling the implementation's process method
int length = process(input, modDeque, sb, 0);
// Resolve remaining affixes
length += modDeque.applyAll(sb, 0, length);
return sb;
}
@Override
public int process(
Deque<FormatQuantity> input,
ModifierHolder mods,
NumberStringBuilder string,
int startIndex) {
return process(input.removeFirst(), mods, string, startIndex);
}
public abstract int process(
FormatQuantity input, ModifierHolder mods, NumberStringBuilder string, int startIndex);
}
public static class BeforeTargetAfterFormat extends SingularFormat {
// The formatters are kept as individual fields to avoid extra object creation overhead.
private BeforeFormat before1 = null;
private BeforeFormat before2 = null;
private BeforeFormat before3 = null;
private TargetFormat target = null;
private AfterFormat after1 = null;
private AfterFormat after2 = null;
private AfterFormat after3 = null;
private final PluralRules rules;
public BeforeTargetAfterFormat(PluralRules rules) {
this.rules = rules;
}
public void addBeforeFormat(BeforeFormat before) {
if (before1 == null) {
before1 = before;
} else if (before2 == null) {
before2 = before;
} else if (before3 == null) {
before3 = before;
} else {
throw new IllegalArgumentException("Only three BeforeFormats are allowed at a time");
}
}
public void setTargetFormat(TargetFormat target) {
this.target = target;
}
public void addAfterFormat(AfterFormat after) {
if (after1 == null) {
after1 = after;
} else if (after2 == null) {
after2 = after;
} else if (after3 == null) {
after3 = after;
} else {
throw new IllegalArgumentException("Only three AfterFormats are allowed at a time");
}
}
@Override
public String format(FormatQuantity input) {
ModifierHolder mods = threadLocalModifierHolder.get().clear();
NumberStringBuilder sb = threadLocalStringBuilder.get().clear();
int length = process(input, mods, sb, 0);
mods.applyAll(sb, 0, length);
return sb.toString();
}
@Override
public int process(
FormatQuantity input, ModifierHolder mods, NumberStringBuilder string, int startIndex) {
// Special case: modifiers are skipped for NaN
int length = 0;
if (!input.isNaN()) {
if (before1 != null) {
before1.before(input, mods, rules);
}
if (before2 != null) {
before2.before(input, mods, rules);
}
if (before3 != null) {
before3.before(input, mods, rules);
}
}
length = target.target(input, string, startIndex);
length += mods.applyStrong(string, startIndex, startIndex + length);
if (after1 != null) {
length += after1.after(mods, string, startIndex, startIndex + length);
}
if (after2 != null) {
length += after2.after(mods, string, startIndex, startIndex + length);
}
if (after3 != null) {
length += after3.after(mods, string, startIndex, startIndex + length);
}
return length;
}
@Override
public void export(Properties properties) {
if (before1 != null) {
before1.export(properties);
}
if (before2 != null) {
before2.export(properties);
}
if (before3 != null) {
before3.export(properties);
}
target.export(properties);
if (after1 != null) {
after1.export(properties);
}
if (after2 != null) {
after2.export(properties);
}
if (after3 != null) {
after3.export(properties);
}
}
}
public static class PositiveNegativeRounderTargetFormat extends SingularFormat {
private final Modifier.PositiveNegativeModifier positiveNegative;
private final Rounder rounder;
private final TargetFormat target;
public PositiveNegativeRounderTargetFormat(
Modifier.PositiveNegativeModifier positiveNegative, Rounder rounder, TargetFormat target) {
this.positiveNegative = positiveNegative;
this.rounder = rounder;
this.target = target;
}
@Override
public String format(FormatQuantity input) {
NumberStringBuilder sb = threadLocalStringBuilder.get().clear();
process(input, null, sb, 0);
return sb.toString();
}
@Override
public int process(
FormatQuantity input, ModifierHolder mods, NumberStringBuilder string, int startIndex) {
// Special case: modifiers are skipped for NaN
Modifier mod = null;
rounder.apply(input);
if (!input.isNaN() && positiveNegative != null) {
mod = positiveNegative.getModifier(input.isNegative());
}
int length = target.target(input, string, startIndex);
if (mod != null) {
length += mod.apply(string, 0, length);
}
return length;
}
@Override
public void export(Properties properties) {
rounder.export(properties);
positiveNegative.export(properties);
target.export(properties);
}
}
public abstract static class BeforeFormat implements Exportable {
protected abstract void before(FormatQuantity input, ModifierHolder mods);
@SuppressWarnings("unused")
public void before(FormatQuantity input, ModifierHolder mods, PluralRules rules) {
before(input, mods);
}
}
public static interface TargetFormat extends Exportable {
public abstract int target(FormatQuantity input, NumberStringBuilder string, int startIndex);
}
public static interface AfterFormat extends Exportable {
public abstract int after(
ModifierHolder mods, NumberStringBuilder string, int leftIndex, int rightIndex);
}
// Instead of Dequeue<BigDecimal>, it could be Deque<Quantity> where
// we control the API of Quantity
public abstract int process(
Deque<FormatQuantity> inputs,
ModifierHolder outputMods,
NumberStringBuilder outputString,
int startIndex);
}

View file

@ -1,52 +0,0 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number;
import java.math.BigDecimal;
import java.math.BigInteger;
/** @author sffc */
public class FormatQuantitySelector {
public static FormatQuantityBCD from(int input) {
return new FormatQuantity4(input);
}
public static FormatQuantityBCD from(long input) {
return new FormatQuantity4(input);
}
public static FormatQuantityBCD from(double input) {
return new FormatQuantity4(input);
}
public static FormatQuantityBCD from(BigInteger input) {
return new FormatQuantity4(input);
}
public static FormatQuantityBCD from(BigDecimal input) {
return new FormatQuantity4(input);
}
public static FormatQuantityBCD from(com.ibm.icu.math.BigDecimal input) {
return from(input.toBigDecimal());
}
public static FormatQuantityBCD from(Number number) {
if (number instanceof Long) {
return from(number.longValue());
} else if (number instanceof Integer) {
return from(number.intValue());
} else if (number instanceof Double) {
return from(number.doubleValue());
} else if (number instanceof BigInteger) {
return from((BigInteger) number);
} else if (number instanceof BigDecimal) {
return from((BigDecimal) number);
} else if (number instanceof com.ibm.icu.math.BigDecimal) {
return from((com.ibm.icu.math.BigDecimal) number);
} else {
throw new IllegalArgumentException(
"Number is of an unsupported type: " + number.getClass().getName());
}
}
}

View file

@ -0,0 +1,155 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number;
import java.util.EnumMap;
import java.util.Map;
import com.ibm.icu.impl.CurrencyData;
import com.ibm.icu.impl.ICUData;
import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.SimpleFormatterImpl;
import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.impl.UResource;
import com.ibm.icu.number.NumberFormatter.UnitWidth;
import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.text.PluralRules;
import com.ibm.icu.util.Currency;
import com.ibm.icu.util.ICUException;
import com.ibm.icu.util.MeasureUnit;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.UResourceBundle;
public class LongNameHandler implements MicroPropsGenerator {
//////////////////////////
/// BEGIN DATA LOADING ///
//////////////////////////
private static final class PluralTableSink extends UResource.Sink {
Map<StandardPlural, String> output;
public PluralTableSink(Map<StandardPlural, String> output) {
this.output = output;
}
@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)) {
continue;
}
String formatString = value.getString();
output.put(plural, formatString);
}
}
}
private static void getMeasureData(ULocale locale, MeasureUnit unit, UnitWidth width,
Map<StandardPlural, String> output) {
PluralTableSink sink = new PluralTableSink(output);
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("/");
key.append(unit.getType());
key.append("/");
key.append(unit.getSubtype());
resource.getAllItemsWithFallback(key.toString(), sink);
}
private static void getCurrencyLongNameData(ULocale locale, Currency currency, Map<StandardPlural, String> output) {
// 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());
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);
// SimpleModifier mod = new SimpleModifier(compiled, Field.CURRENCY, false);
output.put(plural, simpleFormat);
}
}
////////////////////////
/// END DATA LOADING ///
////////////////////////
private final Map<StandardPlural, SimpleModifier> modifiers;
private final PluralRules rules;
private final MicroPropsGenerator parent;
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,
MicroPropsGenerator parent) {
Map<StandardPlural, String> simpleFormats = new EnumMap<StandardPlural, String>(StandardPlural.class);
getCurrencyLongNameData(locale, currency, simpleFormats);
// TODO(ICU4J): Reduce the number of object creations here?
Map<StandardPlural, SimpleModifier> modifiers = new EnumMap<StandardPlural, SimpleModifier>(
StandardPlural.class);
simpleFormatsToModifiers(simpleFormats, null, modifiers);
return new LongNameHandler(modifiers, rules, parent);
}
public static LongNameHandler forMeasureUnit(ULocale locale, MeasureUnit unit, UnitWidth width, PluralRules rules,
MicroPropsGenerator parent) {
Map<StandardPlural, String> simpleFormats = new EnumMap<StandardPlural, String>(StandardPlural.class);
getMeasureData(locale, unit, width, simpleFormats);
// TODO: What field to use for units?
// TODO(ICU4J): Reduce the number of object creations here?
Map<StandardPlural, SimpleModifier> modifiers = new EnumMap<StandardPlural, SimpleModifier>(
StandardPlural.class);
simpleFormatsToModifiers(simpleFormats, null, modifiers);
return new LongNameHandler(modifiers, rules, parent);
}
private static void simpleFormatsToModifiers(Map<StandardPlural, 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 compiled = SimpleFormatterImpl.compileToStringMinMaxArguments(simpleFormat, sb, 1, 1);
output.put(plural, new SimpleModifier(compiled, null, false));
}
}
@Override
public MicroProps processQuantity(DecimalQuantity quantity) {
MicroProps micros = parent.processQuantity(quantity);
// TODO: Avoid the copy here?
DecimalQuantity copy = quantity.createCopy();
micros.rounding.apply(copy);
micros.modOuter = modifiers.get(copy.getStandardPlural(rules));
return micros;
}
}

View file

@ -0,0 +1,104 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number;
import java.util.Objects;
import com.ibm.icu.number.Grouper;
import com.ibm.icu.number.IntegerWidth;
import com.ibm.icu.number.Notation;
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.text.PluralRules;
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;
/**
* 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 Objects.hash(
notation,
unit,
rounder,
grouper,
padder,
integerWidth,
symbols,
unitWidth,
sign,
decimal,
affixProvider,
multiplier,
rules,
loc);
}
@Override
public boolean equals(Object _other) {
MacroProps other = (MacroProps) _other;
return Objects.equals(notation, other.notation)
&& Objects.equals(unit, other.unit)
&& Objects.equals(rounder, other.rounder)
&& Objects.equals(grouper, other.grouper)
&& Objects.equals(padder, other.padder)
&& Objects.equals(integerWidth, other.integerWidth)
&& Objects.equals(symbols, other.symbols)
&& Objects.equals(unitWidth, other.unitWidth)
&& Objects.equals(sign, other.sign)
&& Objects.equals(decimal, other.decimal)
&& Objects.equals(affixProvider, other.affixProvider)
&& Objects.equals(multiplier, other.multiplier)
&& Objects.equals(rules, other.rules)
&& Objects.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

@ -0,0 +1,62 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number;
import com.ibm.icu.number.Grouper;
import com.ibm.icu.number.IntegerWidth;
import com.ibm.icu.number.Rounder;
import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay;
import com.ibm.icu.number.NumberFormatter.SignDisplay;
import com.ibm.icu.text.DecimalFormatSymbols;
public class MicroProps implements Cloneable, MicroPropsGenerator {
// Populated globally:
public SignDisplay sign;
public DecimalFormatSymbols symbols;
public Padder padding;
public DecimalSeparatorDisplay decimal;
public IntegerWidth integerWidth;
// Populated by notation/unit:
public Modifier modOuter;
public Modifier modMiddle;
public Modifier modInner;
public Rounder rounding;
public Grouper grouping;
public boolean useCurrency;
// Internal fields:
private final boolean immutable;
private volatile boolean exhausted;
/**
* @param immutable
* Whether this MicroProps should behave as an immutable after construction with respect to the quantity
* chain.
*/
public MicroProps(boolean immutable) {
this.immutable = immutable;
}
@Override
public MicroProps processQuantity(DecimalQuantity quantity) {
if (immutable) {
return (MicroProps) this.clone();
} else if (exhausted) {
// Safety check
throw new AssertionError("Cannot re-use a mutable MicroProps in the quantity chain");
} else {
exhausted = true;
return this;
}
}
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
}

View file

@ -0,0 +1,52 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
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.
*
* <p>
* 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.
*
* <p>
* A class implementing MicroPropsGenerator looks something like this:
*
* <pre>
* class Foo implements MicroPropsGenerator {
* private final MicroPropsGenerator parent;
*
* public Foo(MicroPropsGenerator parent) {
* this.parent = parent;
* }
*
* &#64;Override
* public MicroProps processQuantity(DecimalQuantity quantity) {
* MicroProps micros = this.parent.processQuantity(quantity);
* // Perform manipulations on micros and/or quantity
* return micros;
* }
* }
* </pre>
*
* @author sffc
*
*/
public interface MicroPropsGenerator {
/**
* Considers the given {@link DecimalQuantity}, optionally mutates it, and returns a {@link MicroProps}.
*
* @param quantity
* The quantity for consideration and optional mutation.
* @return A MicroProps instance resolved for the quantity.
*/
public MicroProps processQuantity(DecimalQuantity quantity);
}

View file

@ -0,0 +1,13 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number;
/**
* @author sffc
*
*/
public interface MicroPropsMutator<T> {
public void mutateMicros(MicroProps micros, T value);
}

View file

@ -2,127 +2,49 @@
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number;
import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.impl.number.modifiers.ConstantAffixModifier;
import com.ibm.icu.impl.number.modifiers.GeneralPluralModifier;
import com.ibm.icu.impl.number.modifiers.PositiveNegativeAffixModifier;
import com.ibm.icu.impl.number.modifiers.SimpleModifier;
/**
* A Modifier is an immutable 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.
*
* @see PositiveNegativeAffixModifier
* @see ConstantAffixModifier
* @see GeneralPluralModifier
* @see SimpleModifier
* A Modifier is usually immutable, except in cases such as {@link MutablePatternModifier}, which are mutable for performance
* reasons.
*/
public interface Modifier {
/**
* Apply this Modifier to the string builder.
*
* @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.
* @param rightIndex The right index of the string within the string builder. Equal to length-1
* 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);
/**
* The number of characters that {@link #apply} would add to the string builder.
*
* @return The number of characters (UTF-16 code units) that would be added to a string builder.
*/
public int length();
/**
* 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.
*/
public boolean isStrong();
/**
* Gets the prefix string associated with this modifier, defined as the string that will be
* inserted at leftIndex when {@link #apply} is called.
*
* @return The prefix string. Will not be null.
*/
public String getPrefix();
/**
* Gets the prefix string associated with this modifier, defined as the string that will be
* inserted at rightIndex when {@link #apply} is called.
*
* @return The suffix string. Will not be null.
*/
public String getSuffix();
/**
* An interface for a modifier that contains both a positive and a negative form. Note that a
* class implementing {@link PositiveNegativeModifier} is not necessarily a {@link Modifier}
* itself. Rather, it returns a {@link Modifier} when {@link #getModifier} is called.
*/
public static interface PositiveNegativeModifier extends Exportable {
/**
* Converts this {@link PositiveNegativeModifier} to a {@link Modifier} given the negative sign.
* Apply this Modifier to the string builder.
*
* @param isNegative true if the negative form of this modifier should be used; false if the
* positive form should be used.
* @return A Modifier corresponding to the negative sign.
* @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.
* @param rightIndex
* 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 Modifier getModifier(boolean isNegative);
}
public int apply(NumberStringBuilder output, int leftIndex, int rightIndex);
/**
* An interface for a modifier that contains both a positive and a negative form for all six
* standard plurals. Note that a class implementing {@link PositiveNegativePluralModifier} is not
* necessarily a {@link Modifier} itself. Rather, it returns a {@link Modifier} when {@link
* #getModifier} is called.
*/
public static interface PositiveNegativePluralModifier extends Exportable {
/**
* Converts this {@link PositiveNegativePluralModifier} to a {@link Modifier} given the negative
* sign and the standard plural.
* Gets the length of the prefix. This information can be used in combination with {@link #apply} to extract the
* prefix and suffix strings.
*
* @param plural The StandardPlural to use.
* @param isNegative true if the negative form of this modifier should be used; false if the
* positive form should be used.
* @return A Modifier corresponding to the negative sign.
* @return The number of characters (UTF-16 code units) in the prefix.
*/
public Modifier getModifier(StandardPlural plural, boolean isNegative);
}
public int getPrefixLength();
/**
* An interface for a modifier that is represented internally by a prefix string and a suffix
* string.
*/
public static interface AffixModifier extends Modifier {}
/**
* Returns the number of code points in the modifier, prefix plus suffix.
*/
public int getCodePointCount();
/**
* A starter implementation with defaults for some of the basic methods.
*
* <p>Implements {@link PositiveNegativeModifier} only so that instances of this class can be used when
* a {@link PositiveNegativeModifier} is required.
*/
public abstract static class BaseModifier extends Format.BeforeFormat
implements Modifier, PositiveNegativeModifier {
@Override
public void before(FormatQuantity input, ModifierHolder mods) {
mods.add(this);
}
@Override
public Modifier getModifier(boolean isNegative) {
return this;
}
}
/**
* 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.
*/
public boolean isStrong();
}

View file

@ -1,106 +0,0 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number;
import java.util.ArrayDeque;
public class ModifierHolder {
private ArrayDeque<Modifier> mods = new ArrayDeque<Modifier>();
// Using five separate fields instead of the ArrayDeque saves about 10ns at the expense of
// worse code.
// TODO: Decide which implementation to use.
// private Modifier mod1 = null;
// private Modifier mod2 = null;
// private Modifier mod3 = null;
// private Modifier mod4 = null;
// private Modifier mod5 = null;
public ModifierHolder clear() {
// mod1 = null;
// mod2 = null;
// mod3 = null;
// mod4 = null;
// mod5 = null;
mods.clear();
return this;
}
public void add(Modifier modifier) {
// if (mod1 == null) {
// mod1 = modifier;
// } else if (mod2 == null) {
// mod2 = modifier;
// } else if (mod3 == null) {
// mod3 = modifier;
// } else if (mod4 == null) {
// mod4 = modifier;
// } else if (mod5 == null) {
// mod5 = modifier;
// } else {
// throw new IndexOutOfBoundsException();
// }
if (modifier != null) mods.addFirst(modifier);
}
public Modifier peekLast() {
return mods.peekLast();
}
public Modifier removeLast() {
return mods.removeLast();
}
public int applyAll(NumberStringBuilder string, int leftIndex, int rightIndex) {
int addedLength = 0;
// if (mod5 != null) {
// addedLength += mod5.apply(string, leftIndex, rightIndex + addedLength);
// mod5 = null;
// }
// if (mod4 != null) {
// addedLength += mod4.apply(string, leftIndex, rightIndex + addedLength);
// mod4 = null;
// }
// if (mod3 != null) {
// addedLength += mod3.apply(string, leftIndex, rightIndex + addedLength);
// mod3 = null;
// }
// if (mod2 != null) {
// addedLength += mod2.apply(string, leftIndex, rightIndex + addedLength);
// mod2 = null;
// }
// if (mod1 != null) {
// addedLength += mod1.apply(string, leftIndex, rightIndex + addedLength);
// mod1 = null;
// }
while (!mods.isEmpty()) {
Modifier mod = mods.removeFirst();
addedLength += mod.apply(string, leftIndex, rightIndex + addedLength);
}
return addedLength;
}
public int applyStrong(NumberStringBuilder string, int leftIndex, int rightIndex) {
int addedLength = 0;
while (!mods.isEmpty() && mods.peekFirst().isStrong()) {
Modifier mod = mods.removeFirst();
addedLength += mod.apply(string, leftIndex, rightIndex + addedLength);
}
return addedLength;
}
public int totalLength() {
int length = 0;
// if (mod1 != null) length += mod1.length();
// if (mod2 != null) length += mod2.length();
// if (mod3 != null) length += mod3.length();
// if (mod4 != null) length += mod4.length();
// if (mod5 != null) length += mod5.length();
for (Modifier mod : mods) {
if (mod == null) continue;
length += mod.length();
}
return length;
}
}

View file

@ -0,0 +1,43 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number;
import java.math.BigDecimal;
public class MultiplierImpl implements MicroPropsGenerator, Cloneable {
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);
}
return micros;
}
}

View file

@ -0,0 +1,10 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number;
/**
* An interface used by compact notation and scientific notation to choose a multiplier while rounding.
*/
public interface MultiplierProducer {
int getMultiplier(int magnitude);
}

View file

@ -0,0 +1,424 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number;
import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.impl.number.AffixUtils.SymbolProvider;
import com.ibm.icu.number.NumberFormatter.SignDisplay;
import com.ibm.icu.number.NumberFormatter.UnitWidth;
import com.ibm.icu.text.DecimalFormatSymbols;
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}.
*
* <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.
*
* <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.
*
* <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.
*/
public class MutablePatternModifier implements Modifier, SymbolProvider, CharSequence, MicroPropsGenerator {
// Modifier details
final boolean isStrong;
// Pattern details
AffixPatternProvider patternInfo;
SignDisplay signDisplay;
boolean perMilleReplacesPercent;
// Symbol details
DecimalFormatSymbols symbols;
UnitWidth unitWidth;
Currency currency;
PluralRules rules;
// Number details
boolean isNegative;
StandardPlural plural;
// QuantityChain details
MicroPropsGenerator parent;
// Transient CharSequence fields
boolean inCharSequenceMode;
int flags;
int length;
boolean prependSign;
boolean plusReplacesMinusSign;
/**
* @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.
*/
public MutablePatternModifier(boolean isStrong) {
this.isStrong = isStrong;
}
/**
* Sets a reference to the parsed decimal format pattern, usually obtained from
* {@link PatternStringParser#parseToPatternInfo(String)}, but any implementation of {@link AffixPatternProvider} is
* accepted.
*/
public void setPatternInfo(AffixPatternProvider patternInfo) {
this.patternInfo = patternInfo;
}
/**
* Sets attributes that imply changes to the literal interpretation of the pattern string affixes.
*
* @param signDisplay
* Whether to force a plus sign on positive numbers.
* @param perMille
* Whether to substitute the percent sign in the pattern with a permille sign.
*/
public void setPatternAttributes(SignDisplay signDisplay, boolean perMille) {
this.signDisplay = signDisplay;
this.perMilleReplacesPercent = perMille;
}
/**
* Sets locale-specific details that affect the symbols substituted into the pattern string affixes.
*
* @param symbols
* The desired instance of DecimalFormatSymbols.
* @param currency
* The currency to be used when substituting currency values into the affixes.
* @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()}.
*/
public void setSymbols(DecimalFormatSymbols symbols, Currency currency, UnitWidth unitWidth, PluralRules rules) {
assert (rules != null) == needsPlurals();
this.symbols = symbols;
this.currency = currency;
this.unitWidth = unitWidth;
this.rules = rules;
}
/**
* Sets attributes of the current number being processed.
*
* @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()}).
*/
public void setNumberProperties(boolean isNegative, StandardPlural plural) {
assert (plural != null) == needsPlurals();
this.isNegative = isNegative;
this.plural = plural;
}
/**
* 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.
*
* <p>
* The resulting modifier cannot be used in a QuantityChain.
*
* @return An immutable that supports both positive and negative numbers.
*/
public ImmutablePatternModifier createImmutable() {
return createImmutableAndChain(null);
}
/**
* 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.
* @return An immutable that supports both positive and negative numbers.
*/
public ImmutablePatternModifier createImmutableAndChain(MicroPropsGenerator parent) {
NumberStringBuilder a = new NumberStringBuilder();
NumberStringBuilder b = new NumberStringBuilder();
if (needsPlurals()) {
// Slower path when we require the plural keyword.
ParameterizedModifier pm = new ParameterizedModifier();
for (StandardPlural plural : StandardPlural.VALUES) {
setNumberProperties(false, plural);
pm.setModifier(false, plural, createConstantModifier(a, b));
setNumberProperties(true, plural);
pm.setModifier(true, plural, createConstantModifier(a, b));
}
pm.freeze();
return new ImmutablePatternModifier(pm, rules, parent);
} else {
// Faster path when plural keyword is not needed.
setNumberProperties(false, null);
Modifier positive = createConstantModifier(a, b);
setNumberProperties(true, null);
Modifier negative = createConstantModifier(a, b);
ParameterizedModifier pm = new ParameterizedModifier(positive, negative);
return new ImmutablePatternModifier(pm, null, parent);
}
}
/**
* 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.
* @param b
* Another working NumberStringBuilder object.
* @return The constant modifier object.
*/
private ConstantMultiFieldModifier createConstantModifier(NumberStringBuilder a, NumberStringBuilder b) {
insertPrefix(a.clear(), 0);
insertSuffix(b.clear(), 0);
if (patternInfo.hasCurrencySign()) {
return new CurrencySpacingEnabledModifier(a, b, isStrong, symbols);
} else {
return new ConstantMultiFieldModifier(a, b, isStrong);
}
}
public static class ImmutablePatternModifier implements MicroPropsGenerator {
final ParameterizedModifier pm;
final PluralRules rules;
final MicroPropsGenerator parent;
ImmutablePatternModifier(ParameterizedModifier pm, PluralRules rules, MicroPropsGenerator parent) {
this.pm = pm;
this.rules = rules;
this.parent = parent;
}
@Override
public MicroProps processQuantity(DecimalQuantity quantity) {
MicroProps micros = parent.processQuantity(quantity);
applyToMicros(micros, quantity);
return micros;
}
public void applyToMicros(MicroProps micros, DecimalQuantity quantity) {
if (rules == null) {
micros.modMiddle = pm.getModifier(quantity.isNegative());
} else {
// TODO: Fix this. Avoid the copy.
DecimalQuantity copy = quantity.createCopy();
copy.roundToInfinity();
StandardPlural plural = copy.getStandardPlural(rules);
micros.modMiddle = pm.getModifier(quantity.isNegative(), plural);
}
}
}
/** Used by the unsafe code path. */
public MicroPropsGenerator addToChain(MicroPropsGenerator parent) {
this.parent = parent;
return this;
}
@Override
public MicroProps processQuantity(DecimalQuantity fq) {
MicroProps micros = parent.processQuantity(fq);
if (needsPlurals()) {
// TODO: Fix this. Avoid the copy.
DecimalQuantity copy = fq.createCopy();
micros.rounding.apply(copy);
setNumberProperties(fq.isNegative(), copy.getStandardPlural(rules));
} else {
setNumberProperties(fq.isNegative(), null);
}
micros.modMiddle = this;
return micros;
}
@Override
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);
return prefixLen + suffixLen;
}
@Override
public int getPrefixLength() {
// Enter and exit CharSequence Mode to get the length.
enterCharSequenceMode(true);
int result = AffixUtils.unescapedCodePointCount(this, this); // prefix length
exitCharSequenceMode();
return result;
}
@Override
public int getCodePointCount() {
// Enter and exit CharSequence Mode to get the length.
enterCharSequenceMode(true);
int result = AffixUtils.unescapedCodePointCount(this, this); // prefix length
exitCharSequenceMode();
enterCharSequenceMode(false);
result += AffixUtils.unescapedCodePointCount(this, this); // suffix length
exitCharSequenceMode();
return result;
}
@Override
public boolean isStrong() {
return isStrong;
}
private int insertPrefix(NumberStringBuilder sb, int position) {
enterCharSequenceMode(true);
int length = AffixUtils.unescape(this, sb, position, this);
exitCharSequenceMode();
return length;
}
private int insertSuffix(NumberStringBuilder sb, int position) {
enterCharSequenceMode(false);
int length = AffixUtils.unescape(this, sb, position, this);
exitCharSequenceMode();
return length;
}
/**
* Returns the string that substitutes a given symbol type in a pattern.
*/
@Override
public CharSequence getSymbol(int type) {
switch (type) {
case AffixUtils.TYPE_MINUS_SIGN:
return symbols.getMinusSignString();
case AffixUtils.TYPE_PLUS_SIGN:
return symbols.getPlusSignString();
case AffixUtils.TYPE_PERCENT:
return symbols.getPercentString();
case AffixUtils.TYPE_PERMILLE:
return symbols.getPerMillString();
case AffixUtils.TYPE_CURRENCY_SINGLE:
// UnitWidth ISO or HIDDEN overrides the singular currency symbol.
if (unitWidth == UnitWidth.ISO_CODE) {
return currency.getCurrencyCode();
} else if (unitWidth == UnitWidth.HIDDEN) {
return "";
} else {
return currency.getName(symbols.getULocale(), Currency.SYMBOL_NAME, null);
}
case AffixUtils.TYPE_CURRENCY_DOUBLE:
return currency.getCurrencyCode();
case AffixUtils.TYPE_CURRENCY_TRIPLE:
// NOTE: This is the code path only for patterns containing "¤¤¤".
// 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);
case AffixUtils.TYPE_CURRENCY_QUAD:
return "\uFFFD";
case AffixUtils.TYPE_CURRENCY_QUINT:
return "\uFFFD";
default:
throw new AssertionError();
}
}
/** This method contains the heart of the logic for rendering LDML affix strings. */
private void enterCharSequenceMode(boolean isPrefix) {
assert !inCharSequenceMode;
inCharSequenceMode = true;
// Should the output render '+' where '-' would normally appear in the pattern?
plusReplacesMinusSign = !isNegative
&& (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.)
boolean useNegativeAffixPattern = patternInfo.hasNegativeSubpattern()
&& (isNegative || (patternInfo.negativeHasMinusSign() && plusReplacesMinusSign));
// Resolve the flags for the affix pattern.
flags = 0;
if (useNegativeAffixPattern) {
flags |= AffixPatternProvider.Flags.NEGATIVE_SUBPATTERN;
}
if (isPrefix) {
flags |= AffixPatternProvider.Flags.PREFIX;
}
if (plural != null) {
assert plural.ordinal() == (AffixPatternProvider.Flags.PLURAL_MASK & plural.ordinal());
flags |= plural.ordinal();
}
// Should we prepend a sign to the pattern?
if (!isPrefix || useNegativeAffixPattern) {
prependSign = false;
} else if (isNegative) {
prependSign = signDisplay != SignDisplay.NEVER;
} else {
prependSign = plusReplacesMinusSign;
}
// Finally, compute the length of the affix pattern.
length = patternInfo.length(flags) + (prependSign ? 1 : 0);
}
private void exitCharSequenceMode() {
assert inCharSequenceMode;
inCharSequenceMode = false;
}
@Override
public int length() {
assert inCharSequenceMode;
return length;
}
@Override
public char charAt(int index) {
assert inCharSequenceMode;
char candidate;
if (prependSign && index == 0) {
candidate = '-';
} else if (prependSign) {
candidate = patternInfo.charAt(flags, index - 1);
} else {
candidate = patternInfo.charAt(flags, index);
}
if (plusReplacesMinusSign && candidate == '-') {
return '+';
}
if (perMilleReplacesPercent && candidate == '%') {
return '‰';
}
return candidate;
}
@Override
public CharSequence subSequence(int start, int end) {
// Never called by AffixUtils
throw new AssertionError();
}
}

View file

@ -12,400 +12,491 @@ import java.util.Map;
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:
*
* <ol>
* <li>Efficient prepend as well as append.
* <li>Keeps tracks of Fields in an efficient manner.
* <li>String operations are fast-pathed to code point operations when possible.
* </ol>
*/
public class NumberStringBuilder implements CharSequence {
private char[] chars;
private Field[] fields;
private int zero;
private int length;
public NumberStringBuilder() {
this(40);
}
/** A constant, empty NumberStringBuilder. Do NOT call mutative operations on this. */
public static final NumberStringBuilder EMPTY = new NumberStringBuilder();
public NumberStringBuilder(int capacity) {
chars = new char[capacity];
fields = new Field[capacity];
zero = capacity / 2;
length = 0;
}
private char[] chars;
private Field[] fields;
private int zero;
private int length;
public NumberStringBuilder(NumberStringBuilder source) {
this(source.chars.length);
zero = source.zero;
length = source.length;
System.arraycopy(source.chars, zero, chars, zero, length);
System.arraycopy(source.fields, zero, fields, zero, length);
}
@Override
public int length() {
return length;
}
@Override
public char charAt(int index) {
if (index < 0 || index > length) {
throw new IndexOutOfBoundsException();
}
return chars[zero + index];
}
/**
* Appends the specified codePoint to the end of the string.
*
* @return The number of chars added: 1 if the code point is in the BMP, or 2 otherwise.
*/
public int appendCodePoint(int codePoint, Field field) {
return insertCodePoint(length, codePoint, field);
}
/**
* Inserts the specified codePoint at the specified index in the string.
*
* @return The number of chars added: 1 if the code point is in the BMP, or 2 otherwise.
*/
public int insertCodePoint(int index, int codePoint, Field field) {
int count = Character.charCount(codePoint);
int position = prepareForInsert(index, count);
Character.toChars(codePoint, chars, position);
fields[position] = field;
if (count == 2) fields[position + 1] = field;
return count;
}
/**
* Appends the specified CharSequence to the end of the string.
*
* @return The number of chars added, which is the length of CharSequence.
*/
public int append(CharSequence sequence, Field field) {
return insert(length, sequence, field);
}
/**
* Inserts the specified CharSequence at the specified index in the string.
*
* @return The number of chars added, which is the length of CharSequence.
*/
public int insert(int index, CharSequence sequence, Field field) {
if (sequence.length() == 0) {
// Nothing to insert.
return 0;
} else if (sequence.length() == 1) {
// Fast path: on a single-char string, using insertCodePoint below is 70% faster than the
// CharSequence method: 12.2 ns versus 41.9 ns for five operations on my Linux x86-64.
return insertCodePoint(index, sequence.charAt(0), field);
} else {
return insert(index, sequence, 0, sequence.length(), field);
}
}
/**
* 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.
*/
public int insert(int index, CharSequence sequence, int start, int end, Field field) {
int count = end - start;
int position = prepareForInsert(index, count);
for (int i = 0; i < count; i++) {
chars[position + i] = sequence.charAt(start + i);
fields[position + i] = field;
}
return count;
}
/**
* 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.
*/
public int append(char[] chars, Field[] fields) {
return insert(length, chars, fields);
}
/**
* 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.
*/
public int insert(int index, char[] chars, Field[] fields) {
assert fields == null || chars.length == fields.length;
int count = chars.length;
if (count == 0) return 0; // nothing to insert
int position = prepareForInsert(index, count);
for (int i = 0; i < count; i++) {
this.chars[position + i] = chars[i];
this.fields[position + i] = fields == null ? null : fields[i];
}
return count;
}
/**
* Appends the contents of another {@link NumberStringBuilder} to the end of this instance.
*
* @return The number of chars added, which is the length of the other {@link
* NumberStringBuilder}.
*/
public int append(NumberStringBuilder other) {
return insert(length, other);
}
/**
* Inserts the contents of another {@link NumberStringBuilder} into this instance at the given
* index.
*
* @return The number of chars added, which is the length of the other {@link
* NumberStringBuilder}.
*/
public int insert(int index, NumberStringBuilder other) {
if (this == other) {
throw new IllegalArgumentException("Cannot call insert/append on myself");
}
int count = other.length;
if (count == 0) return 0; // nothing to insert
int position = prepareForInsert(index, count);
for (int i = 0; i < count; i++) {
this.chars[position + i] = other.chars[other.zero + i];
this.fields[position + i] = other.fields[other.zero + i];
}
return count;
}
/**
* Shifts around existing data if necessary to make room for new characters.
*
* @param index The location in the string where the operation is to take place.
* @param count The number of chars (UTF-16 code units) to be inserted at that location.
* @return The position in the char array to insert the chars.
*/
private int prepareForInsert(int index, int count) {
if (index == 0 && zero - count >= 0) {
// Append to start
zero -= count;
length += count;
return zero;
} else if (index == length && zero + length + count < chars.length) {
// Append to end
length += count;
return zero + length - count;
} else {
// Move chars around and/or allocate more space
return prepareForInsertHelper(index, count);
}
}
private int prepareForInsertHelper(int index, int count) {
// Keeping this code out of prepareForInsert() increases the speed of append operations.
if (length + count > chars.length) {
char[] newChars = new char[(length + count) * 2];
Field[] newFields = new Field[(length + count) * 2];
int newZero = newChars.length / 2 - (length + count) / 2;
System.arraycopy(chars, zero, newChars, newZero, index);
System.arraycopy(chars, zero + index, newChars, newZero + index + count, length - index);
System.arraycopy(fields, zero, newFields, newZero, index);
System.arraycopy(fields, zero + index, newFields, newZero + index + count, length - index);
chars = newChars;
fields = newFields;
zero = newZero;
length += count;
} else {
int newZero = chars.length / 2 - (length + count) / 2;
System.arraycopy(chars, zero, chars, newZero, length);
System.arraycopy(chars, newZero + index, chars, newZero + index + count, length - index);
System.arraycopy(fields, zero, fields, newZero, length);
System.arraycopy(fields, newZero + index, fields, newZero + index + count, length - index);
zero = newZero;
length += count;
}
return zero + index;
}
@Override
public CharSequence subSequence(int start, int end) {
if (start < 0 || end > length || end < start) {
throw new IndexOutOfBoundsException();
}
NumberStringBuilder other = new NumberStringBuilder(this);
other.zero = zero + start;
other.length = end - start;
return other;
}
/**
* Returns the string represented by the characters in this string builder.
*
* <p>For a string intended be used for debugging, use {@link #toDebugString}.
*/
@Override
public String toString() {
return new String(chars, zero, length);
}
private static final Map<Field, Character> fieldToDebugChar = new HashMap<Field, Character>();
static {
fieldToDebugChar.put(NumberFormat.Field.SIGN, '-');
fieldToDebugChar.put(NumberFormat.Field.INTEGER, 'i');
fieldToDebugChar.put(NumberFormat.Field.FRACTION, 'f');
fieldToDebugChar.put(NumberFormat.Field.EXPONENT, 'e');
fieldToDebugChar.put(NumberFormat.Field.EXPONENT_SIGN, '+');
fieldToDebugChar.put(NumberFormat.Field.EXPONENT_SYMBOL, 'E');
fieldToDebugChar.put(NumberFormat.Field.DECIMAL_SEPARATOR, '.');
fieldToDebugChar.put(NumberFormat.Field.GROUPING_SEPARATOR, ',');
fieldToDebugChar.put(NumberFormat.Field.PERCENT, '%');
fieldToDebugChar.put(NumberFormat.Field.PERMILLE, '‰');
fieldToDebugChar.put(NumberFormat.Field.CURRENCY, '$');
}
/**
* 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;"
*
* @return A string for debugging purposes.
*/
public String toDebugString() {
StringBuilder sb = new StringBuilder();
sb.append("<NumberStringBuilder [");
sb.append(this.toString());
sb.append("] [");
for (int i = zero; i < zero + length; i++) {
if (fields[i] == null) {
sb.append('n');
} else {
sb.append(fieldToDebugChar.get(fields[i]));
}
}
sb.append("]>");
return sb.toString();
}
/** @return A new array containing the contents of this string builder. */
public char[] toCharArray() {
return Arrays.copyOfRange(chars, zero, zero + length);
}
/** @return A new array containing the field values of this string builder. */
public Field[] toFieldArray() {
return Arrays.copyOfRange(fields, zero, zero + length);
}
/**
* @return Whether the contents and field values of this string builder are equal to the given
* chars and fields.
* @see #toCharArray
* @see #toFieldArray
*/
public boolean contentEquals(char[] chars, Field[] fields) {
if (chars.length != length) return false;
if (fields.length != length) return false;
for (int i = 0; i < length; i++) {
if (this.chars[zero + i] != chars[i]) return false;
if (this.fields[zero + i] != fields[i]) return false;
}
return true;
}
/**
* @param other The instance to compare.
* @return Whether the contents of this instance is currently equal to the given instance.
*/
public boolean contentEquals(NumberStringBuilder other) {
if (length != other.length) return false;
for (int i = 0; i < length; i++) {
if (chars[zero + i] != other.chars[other.zero + i]) return false;
if (fields[zero + i] != other.fields[other.zero + i]) return false;
}
return true;
}
/**
* Populates the given {@link FieldPosition} based on this string builder.
*
* @param fp The FieldPosition to populate.
* @param offset An offset to add to the field position index; can be zero.
*/
public void populateFieldPosition(FieldPosition fp, int offset) {
java.text.Format.Field rawField = fp.getFieldAttribute();
if (rawField == null) {
// Backwards compatibility: read from fp.getField()
if (fp.getField() == NumberFormat.INTEGER_FIELD) {
rawField = NumberFormat.Field.INTEGER;
} else if (fp.getField() == NumberFormat.FRACTION_FIELD) {
rawField = NumberFormat.Field.FRACTION;
} else {
// No field is set
return;
}
public NumberStringBuilder() {
this(40);
}
if (!(rawField instanceof com.ibm.icu.text.NumberFormat.Field)) {
throw new IllegalArgumentException(
"You must pass an instance of com.ibm.icu.text.NumberFormat.Field as your FieldPosition attribute. You passed: "
+ rawField.getClass().toString());
}
/* com.ibm.icu.text.NumberFormat. */ Field field = (Field) rawField;
boolean seenStart = false;
int fractionStart = -1;
for (int i = zero; i <= zero + length; i++) {
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)
continue;
fp.setEndIndex(i - zero + offset);
break;
} else if (!seenStart && field == _field) {
fp.setBeginIndex(i - zero + offset);
seenStart = true;
}
if (_field == NumberFormat.Field.INTEGER || _field == NumberFormat.Field.DECIMAL_SEPARATOR) {
fractionStart = i - zero + 1;
}
public NumberStringBuilder(int capacity) {
chars = new char[capacity];
fields = new Field[capacity];
zero = capacity / 2;
length = 0;
}
// Backwards compatibility: FRACTION needs to start after INTEGER if empty
if (field == NumberFormat.Field.FRACTION && !seenStart) {
fp.setBeginIndex(fractionStart);
fp.setEndIndex(fractionStart);
public NumberStringBuilder(NumberStringBuilder source) {
copyFrom(source);
}
}
public AttributedCharacterIterator getIterator() {
AttributedString as = new AttributedString(toString());
Field current = null;
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) {
// Special case: GROUPING_SEPARATOR counts as an INTEGER.
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);
public void copyFrom(NumberStringBuilder source) {
chars = Arrays.copyOf(source.chars, source.chars.length);
fields = Arrays.copyOf(source.fields, source.fields.length);
zero = source.zero;
length = source.length;
}
@Override
public int length() {
return length;
}
public int codePointCount() {
return Character.codePointCount(this, 0, length());
}
@Override
public char charAt(int index) {
assert index >= 0;
assert index < length;
return chars[zero + index];
}
public Field fieldAt(int index) {
assert index >= 0;
assert index < length;
return fields[zero + index];
}
public int getFirstCodePoint() {
if (length == 0) {
return -1;
}
current = field;
currentStart = i;
}
return Character.codePointAt(chars, zero, zero + length);
}
if (current != null) {
as.addAttribute(current, current, currentStart, length);
}
return as.getIterator();
}
public NumberStringBuilder clear() {
zero = chars.length / 2;
length = 0;
return this;
}
public int getLastCodePoint() {
if (length == 0) {
return -1;
}
return Character.codePointBefore(chars, zero + length, zero);
}
public int codePointAt(int index) {
return Character.codePointAt(chars, zero + index, zero + length);
}
public int codePointBefore(int index) {
return Character.codePointBefore(chars, zero + index, zero);
}
public NumberStringBuilder clear() {
zero = getCapacity() / 2;
length = 0;
return this;
}
/**
* Appends the specified codePoint to the end of the string.
*
* @return The number of chars added: 1 if the code point is in the BMP, or 2 otherwise.
*/
public int appendCodePoint(int codePoint, Field field) {
return insertCodePoint(length, codePoint, field);
}
/**
* Inserts the specified codePoint at the specified index in the string.
*
* @return The number of chars added: 1 if the code point is in the BMP, or 2 otherwise.
*/
public int insertCodePoint(int index, int codePoint, Field field) {
int count = Character.charCount(codePoint);
int position = prepareForInsert(index, count);
Character.toChars(codePoint, chars, position);
fields[position] = field;
if (count == 2)
fields[position + 1] = field;
return count;
}
/**
* Appends the specified CharSequence to the end of the string.
*
* @return The number of chars added, which is the length of CharSequence.
*/
public int append(CharSequence sequence, Field field) {
return insert(length, sequence, field);
}
/**
* Inserts the specified CharSequence at the specified index in the string.
*
* @return The number of chars added, which is the length of CharSequence.
*/
public int insert(int index, CharSequence sequence, Field field) {
if (sequence.length() == 0) {
// Nothing to insert.
return 0;
} else if (sequence.length() == 1) {
// Fast path: on a single-char string, using insertCodePoint below is 70% faster than the
// CharSequence method: 12.2 ns versus 41.9 ns for five operations on my Linux x86-64.
return insertCodePoint(index, sequence.charAt(0), field);
} else {
return insert(index, sequence, 0, sequence.length(), field);
}
}
/**
* 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.
*/
public int insert(int index, CharSequence sequence, int start, int end, Field field) {
int count = end - start;
int position = prepareForInsert(index, count);
for (int i = 0; i < count; i++) {
chars[position + i] = sequence.charAt(start + i);
fields[position + i] = field;
}
return count;
}
/**
* 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.
*/
public int append(char[] chars, Field[] fields) {
return insert(length, chars, fields);
}
/**
* 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.
*/
public int insert(int index, char[] chars, Field[] fields) {
assert fields == null || chars.length == fields.length;
int count = chars.length;
if (count == 0)
return 0; // nothing to insert
int position = prepareForInsert(index, count);
for (int i = 0; i < count; i++) {
this.chars[position + i] = chars[i];
this.fields[position + i] = fields == null ? null : fields[i];
}
return count;
}
/**
* Appends the contents of another {@link NumberStringBuilder} to the end of this instance.
*
* @return The number of chars added, which is the length of the other {@link NumberStringBuilder}.
*/
public int append(NumberStringBuilder other) {
return insert(length, other);
}
/**
* Inserts the contents of another {@link NumberStringBuilder} into this instance at the given index.
*
* @return The number of chars added, which is the length of the other {@link NumberStringBuilder}.
*/
public int insert(int index, NumberStringBuilder other) {
if (this == other) {
throw new IllegalArgumentException("Cannot call insert/append on myself");
}
int count = other.length;
if (count == 0) {
// Nothing to insert.
return 0;
}
int position = prepareForInsert(index, count);
for (int i = 0; i < count; i++) {
this.chars[position + i] = other.charAt(i);
this.fields[position + i] = other.fieldAt(i);
}
return count;
}
/**
* Shifts around existing data if necessary to make room for new characters.
*
* @param index
* The location in the string where the operation is to take place.
* @param count
* The number of chars (UTF-16 code units) to be inserted at that location.
* @return The position in the char array to insert the chars.
*/
private int prepareForInsert(int index, int count) {
if (index == 0 && zero - count >= 0) {
// Append to start
zero -= count;
length += count;
return zero;
} else if (index == length && zero + length + count < getCapacity()) {
// Append to end
length += count;
return zero + length - count;
} else {
// Move chars around and/or allocate more space
return prepareForInsertHelper(index, count);
}
}
private int prepareForInsertHelper(int index, int count) {
// Java note: Keeping this code out of prepareForInsert() increases the speed of append operations.
int oldCapacity = getCapacity();
int oldZero = zero;
char[] oldChars = chars;
Field[] oldFields = fields;
if (length + count > oldCapacity) {
int newCapacity = (length + count) * 2;
int newZero = newCapacity / 2 - (length + count) / 2;
char[] newChars = new char[newCapacity];
Field[] newFields = new Field[newCapacity];
// 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(oldFields, oldZero, newFields, newZero, index);
System.arraycopy(oldFields, oldZero + index, newFields, newZero + index + count, length - index);
chars = newChars;
fields = newFields;
zero = newZero;
length += count;
} else {
int newZero = oldCapacity / 2 - (length + count) / 2;
// 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(oldFields, oldZero, oldFields, newZero, length);
System.arraycopy(oldFields, newZero + index, oldFields, newZero + index + count, length - index);
zero = newZero;
length += count;
}
return zero + index;
}
private int getCapacity() {
return chars.length;
}
@Override
public CharSequence subSequence(int start, int end) {
if (start < 0 || end > length || end < start) {
throw new IndexOutOfBoundsException();
}
NumberStringBuilder other = new NumberStringBuilder(this);
other.zero = zero + start;
other.length = end - start;
return other;
}
/**
* Returns the string represented by the characters in this string builder.
*
* <p>
* For a string intended be used for debugging, use {@link #toDebugString}.
*/
@Override
public String toString() {
return new String(chars, zero, length);
}
private static final Map<Field, Character> fieldToDebugChar = new HashMap<Field, Character>();
static {
fieldToDebugChar.put(NumberFormat.Field.SIGN, '-');
fieldToDebugChar.put(NumberFormat.Field.INTEGER, 'i');
fieldToDebugChar.put(NumberFormat.Field.FRACTION, 'f');
fieldToDebugChar.put(NumberFormat.Field.EXPONENT, 'e');
fieldToDebugChar.put(NumberFormat.Field.EXPONENT_SIGN, '+');
fieldToDebugChar.put(NumberFormat.Field.EXPONENT_SYMBOL, 'E');
fieldToDebugChar.put(NumberFormat.Field.DECIMAL_SEPARATOR, '.');
fieldToDebugChar.put(NumberFormat.Field.GROUPING_SEPARATOR, ',');
fieldToDebugChar.put(NumberFormat.Field.PERCENT, '%');
fieldToDebugChar.put(NumberFormat.Field.PERMILLE, '‰');
fieldToDebugChar.put(NumberFormat.Field.CURRENCY, '$');
}
/**
* 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;"
*
* @return A string for debugging purposes.
*/
public String toDebugString() {
StringBuilder sb = new StringBuilder();
sb.append("<NumberStringBuilder [");
sb.append(this.toString());
sb.append("] [");
for (int i = zero; i < zero + length; i++) {
if (fields[i] == null) {
sb.append('n');
} else {
sb.append(fieldToDebugChar.get(fields[i]));
}
}
sb.append("]>");
return sb.toString();
}
/** @return A new array containing the contents of this string builder. */
public char[] toCharArray() {
return Arrays.copyOfRange(chars, zero, zero + length);
}
/** @return A new array containing the field values of this string builder. */
public Field[] toFieldArray() {
return Arrays.copyOfRange(fields, zero, zero + length);
}
/**
* @return Whether the contents and field values of this string builder are equal to the given chars and fields.
* @see #toCharArray
* @see #toFieldArray
*/
public boolean contentEquals(char[] chars, Field[] fields) {
if (chars.length != length)
return false;
if (fields.length != length)
return false;
for (int i = 0; i < length; i++) {
if (this.chars[zero + i] != chars[i])
return false;
if (this.fields[zero + i] != fields[i])
return false;
}
return true;
}
/**
* @param other
* The instance to compare.
* @return Whether the contents of this instance is currently equal to the given instance.
*/
public boolean contentEquals(NumberStringBuilder other) {
if (length != other.length)
return false;
for (int i = 0; i < length; i++) {
if (charAt(i) != other.charAt(i) || fieldAt(i) != other.fieldAt(i)) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
throw new UnsupportedOperationException("Don't call #hashCode() or #equals() on a mutable.");
}
@Override
public boolean equals(Object other) {
throw new UnsupportedOperationException("Don't call #hashCode() or #equals() on a mutable.");
}
/**
* Populates the given {@link FieldPosition} based on this string builder.
*
* @param fp
* The FieldPosition to populate.
* @param offset
* An offset to add to the field position index; can be zero.
*/
public void populateFieldPosition(FieldPosition fp, int offset) {
java.text.Format.Field rawField = fp.getFieldAttribute();
if (rawField == null) {
// Backwards compatibility: read from fp.getField()
if (fp.getField() == NumberFormat.INTEGER_FIELD) {
rawField = NumberFormat.Field.INTEGER;
} else if (fp.getField() == NumberFormat.FRACTION_FIELD) {
rawField = NumberFormat.Field.FRACTION;
} else {
// No field is set
return;
}
}
if (!(rawField instanceof com.ibm.icu.text.NumberFormat.Field)) {
throw new IllegalArgumentException(
"You must pass an instance of com.ibm.icu.text.NumberFormat.Field as your FieldPosition attribute. You passed: "
+ rawField.getClass().toString());
}
/* com.ibm.icu.text.NumberFormat. */ Field field = (Field) rawField;
boolean seenStart = false;
int fractionStart = -1;
for (int i = zero; i <= zero + length; i++) {
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) {
continue;
}
fp.setEndIndex(i - zero + offset);
break;
} else if (!seenStart && field == _field) {
fp.setBeginIndex(i - zero + offset);
seenStart = true;
}
if (_field == NumberFormat.Field.INTEGER || _field == NumberFormat.Field.DECIMAL_SEPARATOR) {
fractionStart = i - zero + 1;
}
}
// Backwards compatibility: FRACTION needs to start after INTEGER if empty
if (field == NumberFormat.Field.FRACTION && !seenStart) {
fp.setBeginIndex(fractionStart + offset);
fp.setEndIndex(fractionStart + offset);
}
}
public AttributedCharacterIterator getIterator() {
AttributedString as = new AttributedString(toString());
Field current = null;
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) {
// Special case: GROUPING_SEPARATOR counts as an INTEGER.
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);
}
current = field;
currentStart = i;
}
}
if (current != null) {
as.addAttribute(current, current, currentStart, length);
}
return as.getIterator();
}
}

View file

@ -1,281 +0,0 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number;
import com.ibm.icu.impl.number.Modifier.AffixModifier;
import com.ibm.icu.impl.number.formatters.CompactDecimalFormat;
import com.ibm.icu.impl.number.formatters.PositiveNegativeAffixFormat;
import com.ibm.icu.impl.number.formatters.PositiveNegativeAffixFormat.IProperties;
import com.ibm.icu.impl.number.modifiers.ConstantAffixModifier;
import com.ibm.icu.impl.number.modifiers.ConstantMultiFieldModifier;
import com.ibm.icu.text.DecimalFormatSymbols;
import com.ibm.icu.text.NumberFormat.Field;
/**
* A class to convert from a bag of prefix/suffix properties into a positive and negative {@link
* Modifier}. This is a standard implementation used by {@link PositiveNegativeAffixFormat}, {@link
* CompactDecimalFormat}, {@link Parse}, and others.
*
* <p>This class is is intended to be an efficient generator for instances of Modifier by a single
* thread during construction of a formatter or during static formatting. It uses internal caching
* to avoid creating new Modifier objects when possible. It is NOT THREAD SAFE and NOT IMMUTABLE.
*
* <p>The thread-local instance of this class provided by {@link #getThreadLocalInstance} should be
* used in most cases instead of constructing a new instance of the object.
*
* <p>This class also handles the logic of assigning positive signs, negative signs, and currency
* signs according to the LDML specification.
*/
public class PNAffixGenerator {
public static class Result {
public AffixModifier positive = null;
public AffixModifier negative = null;
}
protected static final ThreadLocal<PNAffixGenerator> threadLocalInstance =
new ThreadLocal<PNAffixGenerator>() {
@Override
protected PNAffixGenerator initialValue() {
return new PNAffixGenerator();
}
};
public static PNAffixGenerator getThreadLocalInstance() {
return threadLocalInstance.get();
}
// These instances are used internally and cached to avoid object creation. The resultInstance
// also serves as a 1-element cache to avoid creating objects when subsequent calls have
// identical prefixes and suffixes. This happens, for example, when consuming CDF data.
private Result resultInstance = new Result();
private NumberStringBuilder sb1 = new NumberStringBuilder();
private NumberStringBuilder sb2 = new NumberStringBuilder();
private NumberStringBuilder sb3 = new NumberStringBuilder();
private NumberStringBuilder sb4 = new NumberStringBuilder();
private NumberStringBuilder sb5 = new NumberStringBuilder();
private NumberStringBuilder sb6 = new NumberStringBuilder();
/**
* Generates modifiers using default currency symbols.
*
* @param symbols The symbols to interpolate for minus, plus, percent, permille, and currency.
* @param properties The bag of properties to convert.
* @return The positive and negative {@link Modifier}.
*/
public Result getModifiers(
DecimalFormatSymbols symbols, PositiveNegativeAffixFormat.IProperties properties) {
// If this method is used, the user doesn't care about currencies. Default the currency symbols
// to the information we can get from the DecimalFormatSymbols instance.
return getModifiers(
symbols,
symbols.getCurrencySymbol(),
symbols.getInternationalCurrencySymbol(),
symbols.getCurrencySymbol(),
properties);
}
/**
* Generates modifiers using the specified currency symbol for all three lengths of currency
* placeholders in the pattern string.
*
* @param symbols The symbols to interpolate for minus, plus, percent, and permille.
* @param currencySymbol The currency symbol.
* @param properties The bag of properties to convert.
* @return The positive and negative {@link Modifier}.
*/
public Result getModifiers(
DecimalFormatSymbols symbols,
String currencySymbol,
PositiveNegativeAffixFormat.IProperties properties) {
// If this method is used, the user doesn't cares about currencies but doesn't care about
// supporting all three sizes of currency placeholders. Use the one provided string for all
// three sizes of placeholders.
return getModifiers(symbols, currencySymbol, currencySymbol, currencySymbol, properties);
}
/**
* Generates modifiers using the three specified strings to replace the three lengths of currency
* placeholders: "¤", "¤¤", and "¤¤¤".
*
* @param symbols The symbols to interpolate for minus, plus, percent, and permille.
* @param curr1 The string to replace "¤".
* @param curr2 The string to replace "¤¤".
* @param curr3 The string to replace "¤¤¤".
* @param properties The bag of properties to convert.
* @return The positive and negative {@link Modifier}.
*/
public Result getModifiers(
DecimalFormatSymbols symbols,
String curr1,
String curr2,
String curr3,
PositiveNegativeAffixFormat.IProperties properties) {
// Use a different code path for handling affixes with "always show plus sign"
if (properties.getSignAlwaysShown()) {
return getModifiersWithPlusSign(symbols, curr1, curr2, curr3, properties);
}
String ppp = properties.getPositivePrefixPattern();
String psp = properties.getPositiveSuffixPattern();
String npp = properties.getNegativePrefixPattern();
String nsp = properties.getNegativeSuffixPattern();
// Set sb1/sb2 to the positive prefix/suffix.
sb1.clear();
sb2.clear();
AffixPatternUtils.unescape(ppp, symbols, curr1, curr2, curr3, null, sb1);
AffixPatternUtils.unescape(psp, symbols, curr1, curr2, curr3, null, sb2);
setPositiveResult(sb1, sb2, properties);
// Set sb1/sb2 to the negative prefix/suffix.
if (npp == null && nsp == null) {
// Negative prefix defaults to positive prefix prepended with the minus sign.
// Negative suffix defaults to positive suffix.
sb1.insert(0, symbols.getMinusSignString(), Field.SIGN);
} else {
sb1.clear();
sb2.clear();
AffixPatternUtils.unescape(npp, symbols, curr1, curr2, curr3, null, sb1);
AffixPatternUtils.unescape(nsp, symbols, curr1, curr2, curr3, null, sb2);
}
setNegativeResult(sb1, sb2, properties);
return resultInstance;
}
private Result getModifiersWithPlusSign(
DecimalFormatSymbols symbols,
String curr1,
String curr2,
String curr3,
IProperties properties) {
String ppp = properties.getPositivePrefixPattern();
String psp = properties.getPositiveSuffixPattern();
String npp = properties.getNegativePrefixPattern();
String nsp = properties.getNegativeSuffixPattern();
// There are three cases, listed below with their expected outcomes.
// TODO: Should we handle the cases when the positive subpattern has a '+' already?
//
// 1) No negative subpattern.
// Positive => Positive subpattern prepended with '+'
// Negative => Positive subpattern prepended with '-'
// 2) Negative subpattern does not have '-'.
// Positive => Positive subpattern prepended with '+'
// Negative => Negative subpattern
// 3) Negative subpattern has '-'.
// Positive => Negative subpattern with '+' substituted for '-'
// Negative => Negative subpattern
if (npp != null || nsp != null) {
// Case 2 or Case 3
sb1.clear();
sb2.clear();
sb3.clear();
sb4.clear();
AffixPatternUtils.unescape(npp, symbols, curr1, curr2, curr3, null, sb1);
AffixPatternUtils.unescape(nsp, symbols, curr1, curr2, curr3, null, sb2);
AffixPatternUtils.unescape(
npp, symbols, curr1, curr2, curr3, symbols.getPlusSignString(), sb3);
AffixPatternUtils.unescape(
nsp, symbols, curr1, curr2, curr3, symbols.getPlusSignString(), sb4);
if (!charSequenceEquals(sb1, sb3) || !charSequenceEquals(sb2, sb4)) {
// Case 3. The plus sign substitution was successful.
setPositiveResult(sb3, sb4, properties);
setNegativeResult(sb1, sb2, properties);
return resultInstance;
} else {
// Case 2. There was no minus sign. Set the negative result and fall through.
setNegativeResult(sb1, sb2, properties);
}
}
// Case 1 or 2. Set sb1/sb2 to the positive prefix/suffix.
sb1.clear();
sb2.clear();
AffixPatternUtils.unescape(ppp, symbols, curr1, curr2, curr3, null, sb1);
AffixPatternUtils.unescape(psp, symbols, curr1, curr2, curr3, null, sb2);
if (npp == null && nsp == null) {
// Case 1. Compute the negative result from the positive subpattern.
sb3.clear();
sb3.append(symbols.getMinusSignString(), Field.SIGN);
sb3.append(sb1);
setNegativeResult(sb3, sb2, properties);
}
// Case 1 or 2. Prepend a '+' sign to the positive prefix.
sb1.insert(0, symbols.getPlusSignString(), Field.SIGN);
setPositiveResult(sb1, sb2, properties);
return resultInstance;
}
private void setPositiveResult(
NumberStringBuilder prefix, NumberStringBuilder suffix, IProperties properties) {
// Override with custom affixes. We need to put these into NumberStringBuilders so that they
// have the same datatype as the incoming prefix and suffix (important when testing for field
// equality in contentEquals).
// TODO: It is a little inefficient that we copy String -> NumberStringBuilder -> Modifier.
// Consider re-working the logic so that fewer copies are required.
String _prefix = properties.getPositivePrefix();
String _suffix = properties.getPositiveSuffix();
if (_prefix != null) {
prefix = sb5.clear();
prefix.append(_prefix, null);
}
if (_suffix != null) {
suffix = sb6.clear();
suffix.append(_suffix, null);
}
if (prefix.length() == 0 && suffix.length() == 0) {
resultInstance.positive = ConstantAffixModifier.EMPTY;
return;
}
if (resultInstance.positive != null
&& (resultInstance.positive instanceof ConstantMultiFieldModifier)
&& ((ConstantMultiFieldModifier) resultInstance.positive).contentEquals(prefix, suffix)) {
// Use the cached modifier
return;
}
resultInstance.positive = new ConstantMultiFieldModifier(prefix, suffix, false);
}
private void setNegativeResult(
NumberStringBuilder prefix, NumberStringBuilder suffix, IProperties properties) {
String _prefix = properties.getNegativePrefix();
String _suffix = properties.getNegativeSuffix();
if (_prefix != null) {
prefix = sb5.clear();
prefix.append(_prefix, null);
}
if (_suffix != null) {
suffix = sb6.clear();
suffix.append(_suffix, null);
}
if (prefix.length() == 0 && suffix.length() == 0) {
resultInstance.negative = ConstantAffixModifier.EMPTY;
return;
}
if (resultInstance.negative != null
&& (resultInstance.negative instanceof ConstantMultiFieldModifier)
&& ((ConstantMultiFieldModifier) resultInstance.negative).contentEquals(prefix, suffix)) {
// Use the cached modifier
return;
}
resultInstance.negative = new ConstantMultiFieldModifier(prefix, suffix, false);
}
/** A null-safe equals method for CharSequences. */
private static boolean charSequenceEquals(CharSequence a, CharSequence b) {
if (a == b) return true;
if (a == null || b == null) return false;
if (a.length() != b.length()) return false;
for (int i = 0; i < a.length(); i++) {
if (a.charAt(i) != b.charAt(i)) return false;
}
return true;
}
}

View file

@ -0,0 +1,113 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number;
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;
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
}
}
}
/* like package-private */ public static final Padder NONE = new Padder(null, -1, null);
String paddingString;
int targetWidth;
PadPosition position;
public Padder(String paddingString, int targetWidth, PadPosition position) {
// TODO: Add a few default instances
this.paddingString = (paddingString == null) ? FALLBACK_PADDING_STRING : paddingString;
this.targetWidth = targetWidth;
this.position = (position == null) ? PadPosition.BEFORE_PREFIX : position;
}
public static Padder none() {
return NONE;
}
public static Padder codePoints(int cp, int targetWidth, PadPosition position) {
// TODO: Validate the code point
if (targetWidth >= 0) {
String paddingString = String.valueOf(Character.toChars(cp));
return new Padder(paddingString, targetWidth, position);
} else {
throw new IllegalArgumentException("Padding width must not be negative");
}
}
public boolean isValid() {
return targetWidth > 0;
}
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
int length = 0;
if (requiredPadding <= 0) {
// Padding is not required.
length += mod1.apply(string, leftIndex, rightIndex);
length += mod2.apply(string, leftIndex, rightIndex + length);
return length;
}
if (position == PadPosition.AFTER_PREFIX) {
length += addPaddingHelper(paddingString, requiredPadding, string, leftIndex);
} else if (position == PadPosition.BEFORE_SUFFIX) {
length += addPaddingHelper(paddingString, requiredPadding, string, rightIndex + length);
}
length += mod1.apply(string, leftIndex, rightIndex + length);
length += mod2.apply(string, leftIndex, rightIndex + length);
if (position == PadPosition.BEFORE_PREFIX) {
length += addPaddingHelper(paddingString, requiredPadding, string, leftIndex);
} else if (position == PadPosition.AFTER_SUFFIX) {
length += addPaddingHelper(paddingString, requiredPadding, string, rightIndex + length);
}
return length;
}
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.
string.insert(index, paddingString, null);
}
return paddingString.length() * requiredPadding;
}
}

View file

@ -0,0 +1,68 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
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.
*/
public class ParameterizedModifier {
private final Modifier positive;
private final Modifier negative;
final Modifier[] mods;
boolean frozen;
/**
* This constructor populates the ParameterizedModifier with a single positive and negative form.
*
* <p>
* If this constructor is used, a plural form CANNOT be passed to {@link #getModifier}.
*/
public ParameterizedModifier(Modifier positive, Modifier negative) {
this.positive = positive;
this.negative = negative;
this.mods = null;
this.frozen = true;
}
/**
* 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}.
*/
public ParameterizedModifier() {
this.positive = null;
this.negative = null;
this.mods = new Modifier[2 * StandardPlural.COUNT];
this.frozen = false;
}
public void setModifier(boolean isNegative, StandardPlural plural, Modifier mod) {
assert !frozen;
mods[getModIndex(isNegative, plural)] = mod;
}
public void freeze() {
frozen = true;
}
public Modifier getModifier(boolean isNegative) {
assert frozen;
assert mods == null;
return isNegative ? negative : positive;
}
public Modifier getModifier(boolean isNegative, StandardPlural plural) {
assert frozen;
assert positive == null;
return mods[getModIndex(isNegative, plural)];
}
private static int getModIndex(boolean isNegative, StandardPlural plural) {
return plural.ordinal() * 2 + (isNegative ? 1 : 0);
}
}

View file

@ -13,13 +13,6 @@ import java.util.concurrent.ConcurrentHashMap;
import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.impl.TextTrieMap;
import com.ibm.icu.impl.number.formatters.BigDecimalMultiplier;
import com.ibm.icu.impl.number.formatters.CurrencyFormat;
import com.ibm.icu.impl.number.formatters.MagnitudeMultiplier;
import com.ibm.icu.impl.number.formatters.PaddingFormat;
import com.ibm.icu.impl.number.formatters.PositiveDecimalFormat;
import com.ibm.icu.impl.number.formatters.PositiveNegativeAffixFormat;
import com.ibm.icu.impl.number.formatters.ScientificFormat;
import com.ibm.icu.lang.UCharacter;
import com.ibm.icu.text.CurrencyPluralInfo;
import com.ibm.icu.text.DecimalFormatSymbols;
@ -95,139 +88,6 @@ public class Parse {
FAST,
}
/** The set of properties required for {@link Parse}. Accepts a {@link Properties} object. */
public static interface IProperties
extends PositiveNegativeAffixFormat.IProperties,
PaddingFormat.IProperties,
CurrencyFormat.ICurrencyProperties,
BigDecimalMultiplier.IProperties,
MagnitudeMultiplier.IProperties,
PositiveDecimalFormat.IProperties,
ScientificFormat.IProperties {
boolean DEFAULT_PARSE_INTEGER_ONLY = false;
/** @see #setParseIntegerOnly */
public boolean getParseIntegerOnly();
/**
* 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
* @return The property bag, for chaining.
*/
public IProperties setParseIntegerOnly(boolean parseIntegerOnly);
boolean DEFAULT_PARSE_NO_EXPONENT = false;
/** @see #setParseNoExponent */
public boolean getParseNoExponent();
/**
* Whether to ignore the exponential part of numbers. For example, parses "123E4" to "123"
* instead of "1230000".
*
* @param parseIgnoreExponent true to ignore exponents; false to parse them.
* @return The property bag, for chaining.
*/
public IProperties setParseNoExponent(boolean parseIgnoreExponent);
boolean DEFAULT_DECIMAL_PATTERN_MATCH_REQUIRED = false;
/** @see #setDecimalPatternMatchRequired */
public boolean getDecimalPatternMatchRequired();
/**
* 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
* @return The property bag, for chaining.
*/
public IProperties setDecimalPatternMatchRequired(boolean decimalPatternMatchRequired);
ParseMode DEFAULT_PARSE_MODE = null;
/** @see #setParseMode */
public ParseMode getParseMode();
/**
* 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}.
* @return The property bag, for chaining.
*/
public IProperties setParseMode(ParseMode parseMode);
boolean DEFAULT_PARSE_TO_BIG_DECIMAL = false;
/** @see #setParseToBigDecimal */
public boolean getParseToBigDecimal();
/**
* 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.
* @return The property bag, for chaining.
*/
public IProperties setParseToBigDecimal(boolean parseToBigDecimal);
boolean DEFAULT_PARSE_CASE_SENSITIVE = false;
/** @see #setParseCaseSensitive */
public boolean getParseCaseSensitive();
/**
* 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.
*
* @param parseCaseSensitive true to be case-sensitive when parsing; false to allow any case.
* @return The property bag, for chaining.
*/
public IProperties setParseCaseSensitive(boolean parseCaseSensitive);
GroupingMode DEFAULT_PARSE_GROUPING_MODE = null;
/** @see #setParseGroupingMode */
public GroupingMode getParseGroupingMode();
/**
* 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.
*
* <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).
*
* <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.
*
* @param parseGroupingMode The {@link GroupingMode} to use; either DEFAULT or RESTRICTED.
* @return The property bag, for chaining.
*/
public IProperties setParseGroupingMode(GroupingMode parseGroupingMode);
}
/**
* An enum containing the choices for strategy in parsing when choosing between grouping and
* decimal separators.
@ -253,7 +113,7 @@ public class Parse {
}
/**
* @see Parse#parse(String, ParsePosition, ParseMode, boolean, boolean, IProperties,
* @see Parse#parse(String, ParsePosition, ParseMode, boolean, boolean, DecimalFormatProperties,
* DecimalFormatSymbols)
*/
private static enum StateName {
@ -343,7 +203,7 @@ public class Parse {
int score;
// Numerical value:
FormatQuantity4 fq = new FormatQuantity4();
DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD();
int numDigits;
int trailingZeros;
int exponent;
@ -540,7 +400,7 @@ public class Parse {
*
* @return The Number. Never null.
*/
Number toNumber(IProperties properties) {
Number toNumber(DecimalFormatProperties properties) {
// Check for NaN, infinity, and -0.0
if (sawNaN) {
return Double.NaN;
@ -610,7 +470,7 @@ public class Parse {
*
* @return The CurrencyAmount. Never null.
*/
public CurrencyAmount toCurrencyAmount(IProperties properties) {
public CurrencyAmount toCurrencyAmount(DecimalFormatProperties properties) {
assert isoCode != null;
Number number = toNumber(properties);
Currency currency = Currency.getInstance(isoCode);
@ -635,7 +495,7 @@ public class Parse {
sb.append("{");
sb.append(currentAffixPattern);
sb.append(":");
sb.append(AffixPatternUtils.getOffset(currentStepwiseParserTag) - 1);
sb.append(AffixUtils.getOffset(currentStepwiseParserTag) - 1);
sb.append("}");
}
sb.append(" ");
@ -679,7 +539,7 @@ public class Parse {
int prevLength;
// Properties and Symbols memory:
IProperties properties;
DecimalFormatProperties properties;
DecimalFormatSymbols symbols;
ParseMode mode;
boolean caseSensitive;
@ -782,6 +642,29 @@ public class Parse {
assert i >= 0 && i < length;
return items[i];
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("<ParseState mode:");
sb.append(mode);
sb.append(" caseSensitive:");
sb.append(caseSensitive);
sb.append(" parseCurrency:");
sb.append(parseCurrency);
sb.append(" groupingMode:");
sb.append(groupingMode);
sb.append(" decimalCps:");
sb.append((char) decimalCp1);
sb.append((char) decimalCp2);
sb.append(" groupingCps:");
sb.append((char) groupingCp1);
sb.append((char) groupingCp2);
sb.append(" affixes:");
sb.append(affixHolders);
sb.append(">");
return sb.toString();
}
}
/**
@ -797,7 +680,7 @@ public class Parse {
static final AffixHolder EMPTY_POSITIVE = new AffixHolder("", "", true, false);
static final AffixHolder EMPTY_NEGATIVE = new AffixHolder("", "", true, true);
static void addToState(ParserState state, IProperties properties) {
static void addToState(ParserState state, DecimalFormatProperties properties) {
AffixHolder pp = fromPropertiesPositivePattern(properties);
AffixHolder np = fromPropertiesNegativePattern(properties);
AffixHolder ps = fromPropertiesPositiveString(properties);
@ -808,21 +691,21 @@ public class Parse {
if (ns != null) state.affixHolders.add(ns);
}
static AffixHolder fromPropertiesPositivePattern(IProperties properties) {
static AffixHolder fromPropertiesPositivePattern(DecimalFormatProperties properties) {
String ppp = properties.getPositivePrefixPattern();
String psp = properties.getPositiveSuffixPattern();
if (properties.getSignAlwaysShown()) {
// TODO: This logic is somewhat duplicated from PNAffixGenerator.
// TODO: This logic is somewhat duplicated from MurkyModifier.
boolean foundSign = false;
String npp = properties.getNegativePrefixPattern();
String nsp = properties.getNegativeSuffixPattern();
if (AffixPatternUtils.containsType(npp, AffixPatternUtils.TYPE_MINUS_SIGN)) {
if (AffixUtils.containsType(npp, AffixUtils.TYPE_MINUS_SIGN)) {
foundSign = true;
ppp = AffixPatternUtils.replaceType(npp, AffixPatternUtils.TYPE_MINUS_SIGN, '+');
ppp = AffixUtils.replaceType(npp, AffixUtils.TYPE_MINUS_SIGN, '+');
}
if (AffixPatternUtils.containsType(nsp, AffixPatternUtils.TYPE_MINUS_SIGN)) {
if (AffixUtils.containsType(nsp, AffixUtils.TYPE_MINUS_SIGN)) {
foundSign = true;
psp = AffixPatternUtils.replaceType(nsp, AffixPatternUtils.TYPE_MINUS_SIGN, '+');
psp = AffixUtils.replaceType(nsp, AffixUtils.TYPE_MINUS_SIGN, '+');
}
if (!foundSign) {
ppp = "+" + ppp;
@ -831,7 +714,7 @@ public class Parse {
return getInstance(ppp, psp, false, false);
}
static AffixHolder fromPropertiesNegativePattern(IProperties properties) {
static AffixHolder fromPropertiesNegativePattern(DecimalFormatProperties properties) {
String npp = properties.getNegativePrefixPattern();
String nsp = properties.getNegativeSuffixPattern();
if (npp == null && nsp == null) {
@ -846,14 +729,14 @@ public class Parse {
return getInstance(npp, nsp, false, true);
}
static AffixHolder fromPropertiesPositiveString(IProperties properties) {
static AffixHolder fromPropertiesPositiveString(DecimalFormatProperties properties) {
String pp = properties.getPositivePrefix();
String ps = properties.getPositiveSuffix();
if (pp == null && ps == null) return null;
return getInstance(pp, ps, true, false);
}
static AffixHolder fromPropertiesNegativeString(IProperties properties) {
static AffixHolder fromPropertiesNegativeString(DecimalFormatProperties properties) {
String np = properties.getNegativePrefix();
String ns = properties.getNegativeSuffix();
if (np == null && ns == null) return null;
@ -943,18 +826,18 @@ public class Parse {
}
}
private static final ThreadLocal<Properties> threadLocalProperties =
new ThreadLocal<Properties>() {
private static final ThreadLocal<DecimalFormatProperties> threadLocalProperties =
new ThreadLocal<DecimalFormatProperties>() {
@Override
protected Properties initialValue() {
return new Properties();
protected DecimalFormatProperties initialValue() {
return new DecimalFormatProperties();
}
};
private void addPattern(String pattern) {
Properties properties = threadLocalProperties.get();
DecimalFormatProperties properties = threadLocalProperties.get();
try {
PatternString.parseToExistingProperties(pattern, properties);
PatternStringParser.parseToExistingProperties(pattern, properties);
} catch (IllegalArgumentException e) {
// This should only happen if there is a bug in CLDR data. Fail silently.
}
@ -1029,7 +912,7 @@ public class Parse {
0xFE63, 0xFE63, 0xFF0D, 0xFF0D)
.freeze();
public static Number parse(String input, IProperties properties, DecimalFormatSymbols symbols) {
public static Number parse(String input, DecimalFormatProperties properties, DecimalFormatSymbols symbols) {
ParsePosition ppos = threadLocalParsePosition.get();
ppos.setIndex(0);
return parse(input, ppos, properties, symbols);
@ -1057,19 +940,19 @@ public class Parse {
public static Number parse(
CharSequence input,
ParsePosition ppos,
IProperties properties,
DecimalFormatProperties properties,
DecimalFormatSymbols symbols) {
StateItem best = _parse(input, ppos, false, properties, symbols);
return (best == null) ? null : best.toNumber(properties);
}
public static CurrencyAmount parseCurrency(
String input, IProperties properties, DecimalFormatSymbols symbols) throws ParseException {
String input, DecimalFormatProperties properties, DecimalFormatSymbols symbols) throws ParseException {
return parseCurrency(input, null, properties, symbols);
}
public static CurrencyAmount parseCurrency(
CharSequence input, ParsePosition ppos, IProperties properties, DecimalFormatSymbols symbols)
CharSequence input, ParsePosition ppos, DecimalFormatProperties properties, DecimalFormatSymbols symbols)
throws ParseException {
if (ppos == null) {
ppos = threadLocalParsePosition.get();
@ -1084,7 +967,7 @@ public class Parse {
CharSequence input,
ParsePosition ppos,
boolean parseCurrency,
IProperties properties,
DecimalFormatProperties properties,
DecimalFormatSymbols symbols) {
if (input == null || ppos == null || properties == null || symbols == null) {
@ -1128,7 +1011,7 @@ public class Parse {
if (DEBUGGING) {
System.out.println("Parsing: " + input);
System.out.println(properties);
System.out.println(state.affixHolders);
System.out.println(state);
}
// Start walking through the string, one codepoint at a time. Backtracking is not allowed. This
@ -1458,7 +1341,9 @@ public class Parse {
// Optionally require that the presence of a decimal point matches the pattern.
if (properties.getDecimalPatternMatchRequired()
&& item.sawDecimalPoint != PositiveDecimalFormat.allowsDecimalPoint(properties)) {
&& item.sawDecimalPoint
!= (properties.getDecimalSeparatorAlwaysShown()
|| properties.getMaximumFractionDigits() != 0)) {
if (DEBUGGING) System.out.println("-> rejected due to decimal point violation");
continue;
}
@ -1831,7 +1716,7 @@ public class Parse {
added = acceptString(cp, nextName, null, state, item, str, 0, false);
} else {
added =
acceptAffixPattern(cp, nextName, state, item, str, AffixPatternUtils.nextToken(0, str));
acceptAffixPattern(cp, nextName, state, item, str, AffixUtils.nextToken(0, str));
}
// Record state in the added entries
for (int i = Long.numberOfTrailingZeros(added); (1L << i) <= added; i++) {
@ -2014,27 +1899,27 @@ public class Parse {
if (typeOrCp < 0) {
// Symbol
switch (typeOrCp) {
case AffixPatternUtils.TYPE_MINUS_SIGN:
case AffixUtils.TYPE_MINUS_SIGN:
resolvedMinusSign = true;
break;
case AffixPatternUtils.TYPE_PLUS_SIGN:
case AffixUtils.TYPE_PLUS_SIGN:
resolvedPlusSign = true;
break;
case AffixPatternUtils.TYPE_PERCENT:
case AffixUtils.TYPE_PERCENT:
resolvedStr = state.symbols.getPercentString();
if (resolvedStr.length() != 1 || resolvedStr.charAt(0) != '%') {
resolvedCp = '%'; // accept ASCII percent as well as locale percent
}
break;
case AffixPatternUtils.TYPE_PERMILLE:
case AffixUtils.TYPE_PERMILLE:
resolvedStr = state.symbols.getPerMillString();
if (resolvedStr.length() != 1 || resolvedStr.charAt(0) != '‰') {
resolvedCp = '‰'; // accept ASCII permille as well as locale permille
}
break;
case AffixPatternUtils.TYPE_CURRENCY_SINGLE:
case AffixPatternUtils.TYPE_CURRENCY_DOUBLE:
case AffixPatternUtils.TYPE_CURRENCY_TRIPLE:
case AffixUtils.TYPE_CURRENCY_SINGLE:
case AffixUtils.TYPE_CURRENCY_DOUBLE:
case AffixUtils.TYPE_CURRENCY_TRIPLE:
resolvedCurrency = true;
break;
default:
@ -2209,7 +2094,7 @@ public class Parse {
int typeOrCp =
isString
? Character.codePointAt(str, (int) offsetOrTag)
: AffixPatternUtils.getTypeOrCp(offsetOrTag);
: AffixUtils.getTypeOrCp(offsetOrTag);
if (isIgnorable(typeOrCp, state)) {
// Look for the next nonignorable code point
@ -2222,7 +2107,7 @@ public class Parse {
nextOffsetOrTag =
isString
? nextOffsetOrTag + Character.charCount(nextTypeOrCp)
: AffixPatternUtils.nextToken(nextOffsetOrTag, str);
: AffixUtils.nextToken(nextOffsetOrTag, str);
if (firstOffsetOrTag == 0L) firstOffsetOrTag = nextOffsetOrTag;
if (isString ? nextOffsetOrTag >= str.length() : nextOffsetOrTag < 0) {
// Integer.MIN_VALUE is an invalid value for either a type or a cp;
@ -2233,7 +2118,7 @@ public class Parse {
nextTypeOrCp =
isString
? Character.codePointAt(str, (int) nextOffsetOrTag)
: AffixPatternUtils.getTypeOrCp(nextOffsetOrTag);
: AffixUtils.getTypeOrCp(nextOffsetOrTag);
if (!isIgnorable(nextTypeOrCp, state)) break;
}
@ -2292,7 +2177,7 @@ public class Parse {
nextOffsetOrTag =
isString
? nextOffsetOrTag + Character.charCount(nextTypeOrCp)
: AffixPatternUtils.nextToken(nextOffsetOrTag, str);
: AffixUtils.nextToken(nextOffsetOrTag, str);
if (firstOffsetOrTag == 0L) firstOffsetOrTag = nextOffsetOrTag;
if (isString ? nextOffsetOrTag >= str.length() : nextOffsetOrTag < 0) {
nextTypeOrCp = -1;
@ -2301,7 +2186,7 @@ public class Parse {
nextTypeOrCp =
isString
? Character.codePointAt(str, (int) nextOffsetOrTag)
: AffixPatternUtils.getTypeOrCp(nextOffsetOrTag);
: AffixUtils.getTypeOrCp(nextOffsetOrTag);
if (!isIgnorable(nextTypeOrCp, state)) break;
}

Some files were not shown because too many files have changed in this diff Show more