mirror of
https://github.com/unicode-org/icu.git
synced 2025-04-14 01:11:02 +00:00
ICU-13177 Merging NumberFormatter to trunk for ICU 60
X-SVN-Rev: 40492
This commit is contained in:
commit
131f416755
181 changed files with 25480 additions and 8577 deletions
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
42
icu4c/source/i18n/nounit.cpp
Normal file
42
icu4c/source/i18n/nounit.cpp
Normal 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
|
400
icu4c/source/i18n/number_affixutils.cpp
Normal file
400
icu4c/source/i18n/number_affixutils.cpp
Normal 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 */
|
219
icu4c/source/i18n/number_affixutils.h
Normal file
219
icu4c/source/i18n/number_affixutils.h
Normal 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 */
|
320
icu4c/source/i18n/number_compact.cpp
Normal file
320
icu4c/source/i18n/number_compact.cpp
Normal 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 µs,
|
||||
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 */
|
89
icu4c/source/i18n/number_compact.h
Normal file
89
icu4c/source/i18n/number_compact.h
Normal 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 µs, 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 */
|
1014
icu4c/source/i18n/number_decimalquantity.cpp
Normal file
1014
icu4c/source/i18n/number_decimalquantity.cpp
Normal file
File diff suppressed because it is too large
Load diff
436
icu4c/source/i18n/number_decimalquantity.h
Normal file
436
icu4c/source/i18n/number_decimalquantity.h
Normal 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 */
|
98
icu4c/source/i18n/number_decimfmtprops.cpp
Normal file
98
icu4c/source/i18n/number_decimfmtprops.cpp
Normal 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 */
|
92
icu4c/source/i18n/number_decimfmtprops.h
Normal file
92
icu4c/source/i18n/number_decimfmtprops.h
Normal 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 */
|
322
icu4c/source/i18n/number_fluent.cpp
Normal file
322
icu4c/source/i18n/number_fluent.cpp
Normal 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 ¬ation) 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 ¯os, 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 */
|
460
icu4c/source/i18n/number_formatimpl.cpp
Normal file
460
icu4c/source/i18n/number_formatimpl.cpp
Normal 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 ¯os, UErrorCode &status) {
|
||||
return new NumberFormatterImpl(macros, true, status);
|
||||
}
|
||||
|
||||
void NumberFormatterImpl::applyStatic(const MacroProps ¯os, 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 ¯os, bool safe, UErrorCode &status) {
|
||||
fMicroPropsGenerator = macrosToMicroGenerator(macros, safe, status);
|
||||
}
|
||||
|
||||
//////////
|
||||
|
||||
const MicroPropsGenerator *
|
||||
NumberFormatterImpl::macrosToMicroGenerator(const MacroProps ¯os, 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(¯os.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 µs, 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 µs, 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 µs, 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 µs, 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 */
|
123
icu4c/source/i18n/number_formatimpl.h
Normal file
123
icu4c/source/i18n/number_formatimpl.h
Normal 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 ¯os, UErrorCode &status);
|
||||
|
||||
/**
|
||||
* Builds and evaluates an "unsafe" MicroPropsGenerator, which is cheaper but can be used only once.
|
||||
*/
|
||||
static void
|
||||
applyStatic(const MacroProps ¯os, 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 ¯os, 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 ¯os, 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 µs, DecimalQuantity &quantity, NumberStringBuilder &string,
|
||||
UErrorCode &status);
|
||||
|
||||
static int32_t
|
||||
writeNumber(const MicroProps µs, DecimalQuantity &quantity, NumberStringBuilder &string,
|
||||
UErrorCode &status);
|
||||
|
||||
static int32_t
|
||||
writeIntegerDigits(const MicroProps µs, DecimalQuantity &quantity, NumberStringBuilder &string,
|
||||
UErrorCode &status);
|
||||
|
||||
static int32_t
|
||||
writeFractionDigits(const MicroProps µs, DecimalQuantity &quantity, NumberStringBuilder &string,
|
||||
UErrorCode &status);
|
||||
};
|
||||
|
||||
} // namespace impl
|
||||
} // namespace number
|
||||
U_NAMESPACE_END
|
||||
|
||||
|
||||
#endif //__NUMBER_FORMATIMPL_H__
|
||||
|
||||
#endif /* #if !UCONFIG_NO_FORMATTING */
|
51
icu4c/source/i18n/number_grouping.cpp
Normal file
51
icu4c/source/i18n/number_grouping.cpp
Normal 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 */
|
45
icu4c/source/i18n/number_integerwidth.cpp
Normal file
45
icu4c/source/i18n/number_integerwidth.cpp
Normal 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 */
|
162
icu4c/source/i18n/number_longnames.cpp
Normal file
162
icu4c/source/i18n/number_longnames.cpp
Normal 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 ¤cy, 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 ¤cy,
|
||||
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 µs,
|
||||
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 */
|
46
icu4c/source/i18n/number_longnames.h
Normal file
46
icu4c/source/i18n/number_longnames.h
Normal 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 ¤cy, 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 µs, 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 */
|
299
icu4c/source/i18n/number_modifiers.cpp
Normal file
299
icu4c/source/i18n/number_modifiers.cpp
Normal 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 */
|
252
icu4c/source/i18n/number_modifiers.h
Normal file
252
icu4c/source/i18n/number_modifiers.h
Normal 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 */
|
72
icu4c/source/i18n/number_notation.cpp
Normal file
72
icu4c/source/i18n/number_notation.cpp
Normal 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 */
|
80
icu4c/source/i18n/number_padding.cpp
Normal file
80
icu4c/source/i18n/number_padding.cpp
Normal 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 */
|
348
icu4c/source/i18n/number_patternmodifier.cpp
Normal file
348
icu4c/source/i18n/number_patternmodifier.cpp
Normal 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 ¤cy,
|
||||
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 µs,
|
||||
UErrorCode &status) const {
|
||||
parent->processQuantity(quantity, micros, status);
|
||||
applyToMicros(micros, quantity);
|
||||
}
|
||||
|
||||
void ImmutablePatternModifier::applyToMicros(MicroProps µs, 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 µs,
|
||||
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 */
|
237
icu4c/source/i18n/number_patternmodifier.h
Normal file
237
icu4c/source/i18n/number_patternmodifier.h
Normal 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 µs, UErrorCode &status) const override;
|
||||
|
||||
void applyToMicros(MicroProps µs, 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 ¤cy, 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 µs, 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 */
|
836
icu4c/source/i18n/number_patternstring.cpp
Normal file
836
icu4c/source/i18n/number_patternstring.cpp
Normal 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 */
|
260
icu4c/source/i18n/number_patternstring.h
Normal file
260
icu4c/source/i18n/number_patternstring.h
Normal 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 */
|
344
icu4c/source/i18n/number_rounding.cpp
Normal file
344
icu4c/source/i18n/number_rounding.cpp
Normal 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 ¤cy, 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 ¤cy) 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 ¤cy, 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 */
|
139
icu4c/source/i18n/number_roundingutils.h
Normal file
139
icu4c/source/i18n/number_roundingutils.h
Normal 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 */
|
134
icu4c/source/i18n/number_scientific.cpp
Normal file
134
icu4c/source/i18n/number_scientific.cpp
Normal 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 µs,
|
||||
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 */
|
60
icu4c/source/i18n/number_scientific.h
Normal file
60
icu4c/source/i18n/number_scientific.h
Normal 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 µs, 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 */
|
457
icu4c/source/i18n/number_stringbuilder.cpp
Normal file
457
icu4c/source/i18n/number_stringbuilder.cpp
Normal 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 */
|
133
icu4c/source/i18n/number_stringbuilder.h
Normal file
133
icu4c/source/i18n/number_stringbuilder.h
Normal 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 */
|
285
icu4c/source/i18n/number_types.h
Normal file
285
icu4c/source/i18n/number_types.h
Normal 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 */
|
128
icu4c/source/i18n/number_utils.h
Normal file
128
icu4c/source/i18n/number_utils.h
Normal 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 µs, UErrorCode &status) const override {
|
||||
(void)status;
|
||||
if (this == µs) {
|
||||
// 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 */
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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:
|
||||
|
|
110
icu4c/source/i18n/unicode/nounit.h
Normal file
110
icu4c/source/i18n/unicode/nounit.h
Normal 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
|
||||
//
|
1968
icu4c/source/i18n/unicode/numberformatter.h
Normal file
1968
icu4c/source/i18n/unicode/numberformatter.h
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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) {
|
||||
|
|
201
icu4c/source/test/intltest/numbertest.h
Normal file
201
icu4c/source/test/intltest/numbertest.h
Normal 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 */
|
246
icu4c/source/test/intltest/numbertest_affixutils.cpp
Normal file
246
icu4c/source/test/intltest/numbertest_affixutils.cpp
Normal 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 */
|
1497
icu4c/source/test/intltest/numbertest_api.cpp
Normal file
1497
icu4c/source/test/intltest/numbertest_api.cpp
Normal file
File diff suppressed because it is too large
Load diff
265
icu4c/source/test/intltest/numbertest_decimalquantity.cpp
Normal file
265
icu4c/source/test/intltest/numbertest_decimalquantity.cpp
Normal 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 */
|
178
icu4c/source/test/intltest/numbertest_modifiers.cpp
Normal file
178
icu4c/source/test/intltest/numbertest_modifiers.cpp
Normal 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 */
|
124
icu4c/source/test/intltest/numbertest_patternmodifier.cpp
Normal file
124
icu4c/source/test/intltest/numbertest_patternmodifier.cpp
Normal 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(µs1);
|
||||
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(µs3);
|
||||
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 */
|
92
icu4c/source/test/intltest/numbertest_patternstring.cpp
Normal file
92
icu4c/source/test/intltest/numbertest_patternstring.cpp
Normal 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 */
|
235
icu4c/source/test/intltest/numbertest_stringbuilder.cpp
Normal file
235
icu4c/source/test/intltest/numbertest_stringbuilder.cpp
Normal 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 */
|
|
@ -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){
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
* }
|
||||
*
|
||||
* @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);
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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
|
||||
* "<NumberStringBuilder [-123.45] [-iii.ff]>"
|
||||
*
|
||||
* @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 "<NumberStringBuilder
|
||||
* [-123.45] [-iii.ff]>"
|
||||
*
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
113
icu4j/main/classes/core/src/com/ibm/icu/impl/number/Padder.java
Normal file
113
icu4j/main/classes/core/src/com/ibm/icu/impl/number/Padder.java
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
Loading…
Add table
Reference in a new issue