ICU-20418 Number skeletons: implement star wildcard; user guide fixes
See #1060
This commit is contained in:
parent
0d26f83f83
commit
ac4540f8a4
@ -95,14 +95,15 @@ Start with the stem `scientific` or `engineering`. Those stems take the
|
||||
following optional options:
|
||||
|
||||
- `/sign-xxx` sets the sign display option for the exponent; see [Sign](#sign).
|
||||
- `/+ee` sets exponent digits to "at least 2"; use `/+eee` for at least 3 digits, etc.
|
||||
- `/*ee` sets exponent digits to "at least 2"; use `/*eee` for at least 3 digits, etc.
|
||||
- ***Prior to ICU 67***, use `/+ee` instead of `/*ee`.
|
||||
|
||||
For example, all of the following skeletons are valid:
|
||||
|
||||
- `scientific`
|
||||
- `scientific/sign-always`
|
||||
- `scientific/+ee`
|
||||
- `scientific/+ee/sign-always`
|
||||
- `scientific/*ee`
|
||||
- `scientific/*ee/sign-always`
|
||||
|
||||
#### Scientific and Engineering Notation: Concise Form
|
||||
|
||||
@ -111,14 +112,14 @@ The following are examples of concise form:
|
||||
| Concise Skeleton | Equivalent Long-Form Skeleton |
|
||||
|---|---|
|
||||
| `E0` | `scientific` |
|
||||
| `E00` | `scientific/+ee` |
|
||||
| `EE+0` | `engineering/sign-always` |
|
||||
| `E00` | `scientific/*ee` |
|
||||
| `EE+!0` | `engineering/sign-always` |
|
||||
| `E+?00` | `scientific/sign-except-zero/+ee` |
|
||||
|
||||
More precisely:
|
||||
|
||||
1. Start with `E` for scientific or `EE` for engineering.
|
||||
2. Allow either `+` or `+?` as a concise sign display option.
|
||||
2. Allow either `+!` or `+?` as a concise sign display option.
|
||||
3. Expect one or more `0`s. If more than one, set minimum integer digits.
|
||||
|
||||
### Unit
|
||||
@ -192,13 +193,13 @@ The following are examples of fraction-precision stems:
|
||||
| Stem | Explanation | Equivalent C++ Code |
|
||||
|---|---|---|
|
||||
| `.00` | Exactly 2 fraction digits | `Precision::fixedFraction(2) ` |
|
||||
| `.00+` | At least 2 fraction digits | `Precision::minFraction(2)` |
|
||||
| `.00*` | At least 2 fraction digits | `Precision::minFraction(2)` |
|
||||
| `.##` | At most 2 fraction digits | `Precision::maxFraction(2) ` |
|
||||
| `.0#` | Between 1 and 2 fraction digits | `Precision::minMaxFraction(1, 2)` |
|
||||
|
||||
More precisely, the fraction precision stem starts with `.`, then contains
|
||||
zero or more `0` symbols, which implies the minimum fraction digits. Then it
|
||||
contains either a `+`, for unlimited maximum fraction digits, or zero or more
|
||||
contains either a `*`, for unlimited maximum fraction digits, or zero or more
|
||||
`#` symbols, which implies the minimum fraction digits when added to the `0`
|
||||
symbols.
|
||||
|
||||
@ -211,11 +212,11 @@ examples:
|
||||
|
||||
| Skeleton | Explanation | Equivalent C++ Code |
|
||||
|---|---|---|
|
||||
| `.##/@@@+` | At most 2 fraction digits, but guarantee <br/> at least 3 significant digits | `Precision::maxFraction(2)` <br/> `.withMinDigits(3)` |
|
||||
| `.##/@@@*` | At most 2 fraction digits, but guarantee <br/> at least 3 significant digits | `Precision::maxFraction(2)` <br/> `.withMinDigits(3)` |
|
||||
| `.00/@##` | Exactly 2 fraction digits, but do not <br/> display more than 3 significant digits | `Precision::fixedFraction(2)` <br/> `.withMaxDigits(3)` |
|
||||
|
||||
Precisely, the option starts with one or more `@` symbols. Then it contains
|
||||
either a `+`, for `::withMinDigits`, or one or more `#` symbols, for
|
||||
either a `*`, for `::withMinDigits`, or one or more `#` symbols, for
|
||||
`::withMaxDigits`. If a `#` symbol is present, there must be only one `@`
|
||||
symbol.
|
||||
|
||||
@ -226,16 +227,22 @@ The following are examples of stems for significant figures:
|
||||
| Stem | Explanation | Equivalent C++ Code|
|
||||
|---|---|---|
|
||||
| `@@@` | Exactly 3 significant digits | `Precision::fixedSignificantDigits(3)` |
|
||||
| `@@@+` | At least 3 significant digits | `Precision::minSignificantDigits(3)` |
|
||||
| `@@@*` | At least 3 significant digits | `Precision::minSignificantDigits(3)` |
|
||||
| `@##` | At most 3 significant digits | `Precision::maxSignificantDigits(3)` |
|
||||
| `@@#` | Between 2 and 3 significant digits | `...::minMaxSignificantDigits(2, 3)` |
|
||||
|
||||
The precise syntax is very similar to fraction precision. The blueprint stem
|
||||
starts with one or more `@` symbols, which implies the minimum significant
|
||||
digits. Then it contains either a `+`, for unlimited maximum significant
|
||||
digits. Then it contains either a `*`, for unlimited maximum significant
|
||||
digits, or zero or more `#` symbols, which implies the minimum significant
|
||||
digits when added to the `@` symbols.
|
||||
|
||||
#### Wildcard Character
|
||||
|
||||
***Prior to ICU 67***, the symbol `+` was used for unlimited precision, instead
|
||||
of `*` (for example, `.00+`). For backwards compatibility, either `+` or `*` is
|
||||
accepted. This applies for both fraction digits and significant digits.
|
||||
|
||||
### Rounding Mode
|
||||
|
||||
The rounding mode can be specified by the following stems:
|
||||
@ -259,21 +266,23 @@ integer digits):
|
||||
|
||||
| Long Form | Concise Form | Explanation | Equivalent C++ Code |
|
||||
|---|---|---|---|
|
||||
| `integer-width/+000` | `000` | At least 3 <br/> integer digits | `IntegerWidth::zeroFillTo(3)` |
|
||||
| `integer-width/*000` | `000` | At least 3 <br/> integer digits | `IntegerWidth::zeroFillTo(3)` |
|
||||
| `integer-width/##0` | - | Between 1 and 3 <br/> integer digits | `IntegerWidth::zeroFillTo(1)` <br/> `.truncateAt(3)`
|
||||
| `integer-width/00` | - | Exactly 2 <br/> integer digits | `IntegerWidth::zeroFillTo(2)` <br/> `.truncateAt(2)` |
|
||||
| `integer-width/+` | - | Zero or more <br/> integer digits | `IntegerWidth::zeroFillTo(0) `
|
||||
| `integer-width/*` | - | Zero or more <br/> integer digits | `IntegerWidth::zeroFillTo(0) `
|
||||
|
||||
The long-form option starts with either a single `+` symbol, signaling no limit
|
||||
The long-form option starts with either a single `*` symbol, signaling no limit
|
||||
on the number of integer digits (no *truncateAt*), or zero or more `#` symbols.
|
||||
It should then be followed by zero or more `0` symbols, indicating the minimum
|
||||
integer digits (the argument to *zeroFillTo*). If there is no `+` symbol, the
|
||||
integer digits (the argument to *zeroFillTo*). If there is no `*` symbol, the
|
||||
maximum integer digits (the argument to *truncateAt*) is the number of `#`
|
||||
symbols plus the number of `0` symbols.
|
||||
|
||||
The concise skeleton is simply one or more `0` characters. This supports
|
||||
minimum integer digits but not maximum integer digits.
|
||||
|
||||
***Prior to ICU 67***, use the symbol `+` instead of `*`.
|
||||
|
||||
### Scale
|
||||
|
||||
To specify the scale, use the following stem and option:
|
||||
@ -303,7 +312,7 @@ The grouping strategy can be specified by the following stems:
|
||||
- `group-min2` or `,?` (concise)
|
||||
- `group-auto` (or omit since this is the default)
|
||||
- `group-on-aligned` or `,!` (concise)
|
||||
- `group-thousands` or `,=` (concise)
|
||||
- `group-thousands` (no concise equivalent)
|
||||
|
||||
For more details, see
|
||||
[UNumberGroupingStrategy](http://icu-project.org/apiref/icu4c/unumberformatter_8h.html).
|
||||
|
@ -897,7 +897,7 @@ void GeneratorHelpers::generateSkeleton(const MacroProps& macros, UnicodeString&
|
||||
|
||||
bool blueprint_helpers::parseExponentWidthOption(const StringSegment& segment, MacroProps& macros,
|
||||
UErrorCode&) {
|
||||
if (segment.charAt(0) != u'+') {
|
||||
if (!isWildcardChar(segment.charAt(0))) {
|
||||
return false;
|
||||
}
|
||||
int32_t offset = 1;
|
||||
@ -919,7 +919,7 @@ bool blueprint_helpers::parseExponentWidthOption(const StringSegment& segment, M
|
||||
|
||||
void
|
||||
blueprint_helpers::generateExponentWidthOption(int32_t minExponentDigits, UnicodeString& sb, UErrorCode&) {
|
||||
sb.append(u'+');
|
||||
sb.append(kWildcardChar);
|
||||
appendMultiple(sb, u'e', minExponentDigits);
|
||||
}
|
||||
|
||||
@ -1071,7 +1071,7 @@ void blueprint_helpers::parseFractionStem(const StringSegment& segment, MacroPro
|
||||
}
|
||||
}
|
||||
if (offset < segment.length()) {
|
||||
if (segment.charAt(offset) == u'+') {
|
||||
if (isWildcardChar(segment.charAt(offset))) {
|
||||
maxFrac = -1;
|
||||
offset++;
|
||||
} else {
|
||||
@ -1113,7 +1113,7 @@ blueprint_helpers::generateFractionStem(int32_t minFrac, int32_t maxFrac, Unicod
|
||||
sb.append(u'.');
|
||||
appendMultiple(sb, u'0', minFrac);
|
||||
if (maxFrac == -1) {
|
||||
sb.append(u'+');
|
||||
sb.append(kWildcardChar);
|
||||
} else {
|
||||
appendMultiple(sb, u'#', maxFrac - minFrac);
|
||||
}
|
||||
@ -1133,7 +1133,7 @@ blueprint_helpers::parseDigitsStem(const StringSegment& segment, MacroProps& mac
|
||||
}
|
||||
}
|
||||
if (offset < segment.length()) {
|
||||
if (segment.charAt(offset) == u'+') {
|
||||
if (isWildcardChar(segment.charAt(offset))) {
|
||||
maxSig = -1;
|
||||
offset++;
|
||||
} else {
|
||||
@ -1166,7 +1166,7 @@ void
|
||||
blueprint_helpers::generateDigitsStem(int32_t minSig, int32_t maxSig, UnicodeString& sb, UErrorCode&) {
|
||||
appendMultiple(sb, u'@', minSig);
|
||||
if (maxSig == -1) {
|
||||
sb.append(u'+');
|
||||
sb.append(kWildcardChar);
|
||||
} else {
|
||||
appendMultiple(sb, u'#', maxSig - minSig);
|
||||
}
|
||||
@ -1262,7 +1262,7 @@ bool blueprint_helpers::parseFracSigOption(const StringSegment& segment, MacroPr
|
||||
// Invalid: @, @@, @@@
|
||||
// Invalid: @@#, @@##, @@@#
|
||||
if (offset < segment.length()) {
|
||||
if (segment.charAt(offset) == u'+') {
|
||||
if (isWildcardChar(segment.charAt(offset))) {
|
||||
maxSig = -1;
|
||||
offset++;
|
||||
} else if (minSig > 1) {
|
||||
@ -1351,7 +1351,7 @@ void blueprint_helpers::parseIntegerWidthOption(const StringSegment& segment, Ma
|
||||
int32_t offset = 0;
|
||||
int32_t minInt = 0;
|
||||
int32_t maxInt;
|
||||
if (segment.charAt(0) == u'+') {
|
||||
if (isWildcardChar(segment.charAt(0))) {
|
||||
maxInt = -1;
|
||||
offset++;
|
||||
} else {
|
||||
@ -1392,7 +1392,7 @@ void blueprint_helpers::parseIntegerWidthOption(const StringSegment& segment, Ma
|
||||
void blueprint_helpers::generateIntegerWidthOption(int32_t minInt, int32_t maxInt, UnicodeString& sb,
|
||||
UErrorCode&) {
|
||||
if (maxInt == -1) {
|
||||
sb.append(u'+');
|
||||
sb.append(kWildcardChar);
|
||||
} else {
|
||||
appendMultiple(sb, u'#', maxInt - minInt);
|
||||
}
|
||||
|
@ -118,6 +118,17 @@ enum StemEnum {
|
||||
STEM_SCALE,
|
||||
};
|
||||
|
||||
/** Default wildcard char, accepted on input and printed in output */
|
||||
constexpr char16_t kWildcardChar = u'*';
|
||||
|
||||
/** Alternative wildcard char, accept on input but not printed in output */
|
||||
constexpr char16_t kAltWildcardChar = u'+';
|
||||
|
||||
/** Checks whether the char is a wildcard on input */
|
||||
inline bool isWildcardChar(char16_t c) {
|
||||
return c == kWildcardChar || c == kAltWildcardChar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a NumberFormatter corresponding to the given skeleton string.
|
||||
*
|
||||
|
@ -257,6 +257,7 @@ class NumberSkeletonTest : public IntlTest {
|
||||
void stemsRequiringOption();
|
||||
void defaultTokens();
|
||||
void flexibleSeparators();
|
||||
void wildcardCharacters();
|
||||
|
||||
void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0);
|
||||
|
||||
|
@ -209,7 +209,7 @@ void NumberFormatterApiTest::notationScientific() {
|
||||
|
||||
assertFormatDescending(
|
||||
u"Scientific min exponent digits",
|
||||
u"scientific/+ee",
|
||||
u"scientific/*ee",
|
||||
u"E00",
|
||||
NumberFormatter::with().notation(Notation::scientific().withMinExponentDigits(2)),
|
||||
Locale::getEnglish(),
|
||||
@ -1039,7 +1039,7 @@ void NumberFormatterApiTest::roundingFraction() {
|
||||
|
||||
assertFormatDescending(
|
||||
u"Min Fraction",
|
||||
u".0+",
|
||||
u".0*",
|
||||
u".0+",
|
||||
NumberFormatter::with().precision(Precision::minFraction(1)),
|
||||
Locale::getEnglish(),
|
||||
@ -1116,7 +1116,7 @@ void NumberFormatterApiTest::roundingFigures() {
|
||||
|
||||
assertFormatSingle(
|
||||
u"Min Significant",
|
||||
u"@@+",
|
||||
u"@@*",
|
||||
u"@@+",
|
||||
NumberFormatter::with().precision(Precision::minSignificantDigits(2)),
|
||||
Locale::getEnglish(),
|
||||
@ -1153,7 +1153,7 @@ void NumberFormatterApiTest::roundingFigures() {
|
||||
|
||||
assertFormatSingle(
|
||||
u"Fixed Significant on zero with zero integer width",
|
||||
u"@ integer-width/+",
|
||||
u"@ integer-width/*",
|
||||
u"@ integer-width/+",
|
||||
NumberFormatter::with().precision(Precision::fixedSignificantDigits(1))
|
||||
.integerWidth(IntegerWidth::zeroFillTo(0)),
|
||||
@ -1181,7 +1181,7 @@ void NumberFormatterApiTest::roundingFractionFigures() {
|
||||
|
||||
assertFormatDescending(
|
||||
u"FracSig minMaxFrac minSig",
|
||||
u".0#/@@@+",
|
||||
u".0#/@@@*",
|
||||
u".0#/@@@+",
|
||||
NumberFormatter::with().precision(Precision::minMaxFraction(1, 2).withMinDigits(3)),
|
||||
Locale::getEnglish(),
|
||||
@ -1229,7 +1229,7 @@ void NumberFormatterApiTest::roundingFractionFigures() {
|
||||
|
||||
assertFormatSingle(
|
||||
u"FracSig with trailing zeros A",
|
||||
u".00/@@@+",
|
||||
u".00/@@@*",
|
||||
u".00/@@@+",
|
||||
NumberFormatter::with().precision(Precision::fixedFraction(2).withMinDigits(3)),
|
||||
Locale::getEnglish(),
|
||||
@ -1238,7 +1238,7 @@ void NumberFormatterApiTest::roundingFractionFigures() {
|
||||
|
||||
assertFormatSingle(
|
||||
u"FracSig with trailing zeros B",
|
||||
u".00/@@@+",
|
||||
u".00/@@@*",
|
||||
u".00/@@@+",
|
||||
NumberFormatter::with().precision(Precision::fixedFraction(2).withMinDigits(3)),
|
||||
Locale::getEnglish(),
|
||||
@ -1807,7 +1807,7 @@ void NumberFormatterApiTest::integerWidth() {
|
||||
|
||||
assertFormatDescending(
|
||||
u"Integer Width Zero Fill 0",
|
||||
u"integer-width/+",
|
||||
u"integer-width/*",
|
||||
u"integer-width/+",
|
||||
NumberFormatter::with().integerWidth(IntegerWidth::zeroFillTo(0)),
|
||||
Locale::getEnglish(),
|
||||
|
@ -29,6 +29,7 @@ void NumberSkeletonTest::runIndexedTest(int32_t index, UBool exec, const char*&
|
||||
TESTCASE_AUTO(stemsRequiringOption);
|
||||
TESTCASE_AUTO(defaultTokens);
|
||||
TESTCASE_AUTO(flexibleSeparators);
|
||||
TESTCASE_AUTO(wildcardCharacters);
|
||||
TESTCASE_AUTO_END;
|
||||
}
|
||||
|
||||
@ -41,26 +42,35 @@ void NumberSkeletonTest::validTokens() {
|
||||
u"precision-integer",
|
||||
u"precision-unlimited",
|
||||
u"@@@##",
|
||||
u"@@*",
|
||||
u"@@+",
|
||||
u".000##",
|
||||
u".00*",
|
||||
u".00+",
|
||||
u".",
|
||||
u".*",
|
||||
u".+",
|
||||
u".######",
|
||||
u".00/@@*",
|
||||
u".00/@@+",
|
||||
u".00/@##",
|
||||
u"precision-increment/3.14",
|
||||
u"precision-currency-standard",
|
||||
u"precision-integer rounding-mode-half-up",
|
||||
u".00# rounding-mode-ceiling",
|
||||
u".00/@@* rounding-mode-floor",
|
||||
u".00/@@+ rounding-mode-floor",
|
||||
u"scientific",
|
||||
u"scientific/*ee",
|
||||
u"scientific/+ee",
|
||||
u"scientific/sign-always",
|
||||
u"scientific/*ee/sign-always",
|
||||
u"scientific/+ee/sign-always",
|
||||
u"scientific/sign-always/*ee",
|
||||
u"scientific/sign-always/+ee",
|
||||
u"scientific/sign-except-zero",
|
||||
u"engineering",
|
||||
u"engineering/*eee",
|
||||
u"engineering/+eee",
|
||||
u"compact-short",
|
||||
u"compact-long",
|
||||
@ -81,6 +91,7 @@ void NumberSkeletonTest::validTokens() {
|
||||
u"group-thousands",
|
||||
u"integer-width/00",
|
||||
u"integer-width/#0",
|
||||
u"integer-width/*00",
|
||||
u"integer-width/+00",
|
||||
u"sign-always",
|
||||
u"sign-auto",
|
||||
@ -136,16 +147,22 @@ void NumberSkeletonTest::invalidTokens() {
|
||||
static const char16_t* cases[] = {
|
||||
u".00x",
|
||||
u".00##0",
|
||||
u".##*",
|
||||
u".00##*",
|
||||
u".0#*",
|
||||
u"@#*",
|
||||
u".##+",
|
||||
u".00##+",
|
||||
u".0#+",
|
||||
u"@#+",
|
||||
u"@@x",
|
||||
u"@@##0",
|
||||
u"@#+",
|
||||
u".00/@",
|
||||
u".00/@@",
|
||||
u".00/@@x",
|
||||
u".00/@@#",
|
||||
u".00/@@#*",
|
||||
u".00/floor/@@*", // wrong order
|
||||
u".00/@@#+",
|
||||
u".00/floor/@@+", // wrong order
|
||||
u"precision-increment/français", // non-invariant characters for C++
|
||||
@ -161,6 +178,10 @@ void NumberSkeletonTest::invalidTokens() {
|
||||
u"currency/ççç", // three characters but not ASCII
|
||||
u"measure-unit/foo",
|
||||
u"integer-width/xxx",
|
||||
u"integer-width/0*",
|
||||
u"integer-width/*0#",
|
||||
u"integer-width/*#",
|
||||
u"integer-width/*#0",
|
||||
u"integer-width/0+",
|
||||
u"integer-width/+0#",
|
||||
u"integer-width/+#",
|
||||
@ -177,6 +198,7 @@ void NumberSkeletonTest::invalidTokens() {
|
||||
u"EEE",
|
||||
u"EEE0",
|
||||
u"001",
|
||||
u"00*",
|
||||
u"00+",
|
||||
};
|
||||
|
||||
@ -304,6 +326,32 @@ void NumberSkeletonTest::flexibleSeparators() {
|
||||
}
|
||||
}
|
||||
|
||||
void NumberSkeletonTest::wildcardCharacters() {
|
||||
IcuTestErrorCode status(*this, "wildcardCharacters");
|
||||
|
||||
struct TestCase {
|
||||
const char16_t* star;
|
||||
const char16_t* plus;
|
||||
} cases[] = {
|
||||
{ u".00*", u".00+" },
|
||||
{ u"@@*", u"@@+" },
|
||||
{ u".00/@@*", u".00/@@+" },
|
||||
{ u"scientific/*ee", u"scientific/+ee" },
|
||||
{ u"integer-width/*00", u"integer-width/+00" },
|
||||
};
|
||||
|
||||
for (const auto& cas : cases) {
|
||||
UnicodeString star(cas.star);
|
||||
UnicodeString plus(cas.plus);
|
||||
status.setScope(star);
|
||||
|
||||
UnicodeString normalized = NumberFormatter::forSkeleton(plus, status)
|
||||
.toSkeleton(status);
|
||||
assertEquals("Plus should normalize to star", star, normalized);
|
||||
status.errIfFailureAndReset();
|
||||
}
|
||||
}
|
||||
|
||||
// In C++, there is no distinguishing between "invalid", "unknown", and "unexpected" tokens.
|
||||
void NumberSkeletonTest::expectedErrorSkeleton(const char16_t** cases, int32_t casesLen) {
|
||||
for (int32_t i = 0; i < casesLen; i++) {
|
||||
|
@ -122,6 +122,17 @@ class NumberSkeletonImpl {
|
||||
STEM_SCALE,
|
||||
};
|
||||
|
||||
/** Default wildcard char, accepted on input and printed in output */
|
||||
static final char WILDCARD_CHAR = '*';
|
||||
|
||||
/** Alternative wildcard char, accept on input but not printed in output */
|
||||
static final char ALT_WILDCARD_CHAR = '+';
|
||||
|
||||
/** Checks whether the char is a wildcard on input */
|
||||
static boolean isWildcardChar(char c) {
|
||||
return c == WILDCARD_CHAR || c == ALT_WILDCARD_CHAR;
|
||||
}
|
||||
|
||||
/** For mapping from ordinal back to StemEnum in Java. */
|
||||
static final StemEnum[] STEM_ENUM_VALUES = StemEnum.values();
|
||||
|
||||
@ -924,7 +935,7 @@ class NumberSkeletonImpl {
|
||||
|
||||
/** @return Whether we successfully found and parsed an exponent width option. */
|
||||
private static boolean parseExponentWidthOption(StringSegment segment, MacroProps macros) {
|
||||
if (segment.charAt(0) != '+') {
|
||||
if (!isWildcardChar(segment.charAt(0))) {
|
||||
return false;
|
||||
}
|
||||
int offset = 1;
|
||||
@ -945,7 +956,7 @@ class NumberSkeletonImpl {
|
||||
}
|
||||
|
||||
private static void generateExponentWidthOption(int minExponentDigits, StringBuilder sb) {
|
||||
sb.append('+');
|
||||
sb.append(WILDCARD_CHAR);
|
||||
appendMultiple(sb, 'e', minExponentDigits);
|
||||
}
|
||||
|
||||
@ -1044,7 +1055,7 @@ class NumberSkeletonImpl {
|
||||
}
|
||||
}
|
||||
if (offset < segment.length()) {
|
||||
if (segment.charAt(offset) == '+') {
|
||||
if (isWildcardChar(segment.charAt(offset))) {
|
||||
maxFrac = -1;
|
||||
offset++;
|
||||
} else {
|
||||
@ -1083,7 +1094,7 @@ class NumberSkeletonImpl {
|
||||
sb.append('.');
|
||||
appendMultiple(sb, '0', minFrac);
|
||||
if (maxFrac == -1) {
|
||||
sb.append('+');
|
||||
sb.append(WILDCARD_CHAR);
|
||||
} else {
|
||||
appendMultiple(sb, '#', maxFrac - minFrac);
|
||||
}
|
||||
@ -1102,7 +1113,7 @@ class NumberSkeletonImpl {
|
||||
}
|
||||
}
|
||||
if (offset < segment.length()) {
|
||||
if (segment.charAt(offset) == '+') {
|
||||
if (isWildcardChar(segment.charAt(offset))) {
|
||||
maxSig = -1;
|
||||
offset++;
|
||||
} else {
|
||||
@ -1132,7 +1143,7 @@ class NumberSkeletonImpl {
|
||||
private static void generateDigitsStem(int minSig, int maxSig, StringBuilder sb) {
|
||||
appendMultiple(sb, '@', minSig);
|
||||
if (maxSig == -1) {
|
||||
sb.append('+');
|
||||
sb.append(WILDCARD_CHAR);
|
||||
} else {
|
||||
appendMultiple(sb, '#', maxSig - minSig);
|
||||
}
|
||||
@ -1224,7 +1235,7 @@ class NumberSkeletonImpl {
|
||||
// Invalid: @, @@, @@@
|
||||
// Invalid: @@#, @@##, @@@#
|
||||
if (offset < segment.length()) {
|
||||
if (segment.charAt(offset) == '+') {
|
||||
if (isWildcardChar(segment.charAt(offset))) {
|
||||
maxSig = -1;
|
||||
offset++;
|
||||
} else if (minSig > 1) {
|
||||
@ -1278,7 +1289,7 @@ class NumberSkeletonImpl {
|
||||
int offset = 0;
|
||||
int minInt = 0;
|
||||
int maxInt;
|
||||
if (segment.charAt(0) == '+') {
|
||||
if (isWildcardChar(segment.charAt(0))) {
|
||||
maxInt = -1;
|
||||
offset++;
|
||||
} else {
|
||||
@ -1316,7 +1327,7 @@ class NumberSkeletonImpl {
|
||||
|
||||
private static void generateIntegerWidthOption(int minInt, int maxInt, StringBuilder sb) {
|
||||
if (maxInt == -1) {
|
||||
sb.append('+');
|
||||
sb.append(WILDCARD_CHAR);
|
||||
} else {
|
||||
appendMultiple(sb, '#', maxInt - minInt);
|
||||
}
|
||||
|
@ -165,7 +165,7 @@ public class NumberFormatterApiTest {
|
||||
|
||||
assertFormatDescending(
|
||||
"Scientific min exponent digits",
|
||||
"scientific/+ee",
|
||||
"scientific/*ee",
|
||||
"E00",
|
||||
NumberFormatter.with().notation(Notation.scientific().withMinExponentDigits(2)),
|
||||
ULocale.ENGLISH,
|
||||
@ -966,7 +966,7 @@ public class NumberFormatterApiTest {
|
||||
|
||||
assertFormatDescending(
|
||||
"Min Fraction",
|
||||
".0+",
|
||||
".0*",
|
||||
".0+",
|
||||
NumberFormatter.with().precision(Precision.minFraction(1)),
|
||||
ULocale.ENGLISH,
|
||||
@ -1044,7 +1044,7 @@ public class NumberFormatterApiTest {
|
||||
|
||||
assertFormatSingle(
|
||||
"Min Significant",
|
||||
"@@+",
|
||||
"@@*",
|
||||
"@@+",
|
||||
NumberFormatter.with().precision(Precision.minSignificantDigits(2)),
|
||||
ULocale.ENGLISH,
|
||||
@ -1071,7 +1071,7 @@ public class NumberFormatterApiTest {
|
||||
|
||||
assertFormatSingle(
|
||||
"Fixed Significant on zero with zero integer width",
|
||||
"@ integer-width/+",
|
||||
"@ integer-width/*",
|
||||
"@ integer-width/+",
|
||||
NumberFormatter.with().precision(Precision.fixedSignificantDigits(1)).integerWidth(IntegerWidth.zeroFillTo(0)),
|
||||
ULocale.ENGLISH,
|
||||
@ -1108,7 +1108,7 @@ public class NumberFormatterApiTest {
|
||||
|
||||
assertFormatDescending(
|
||||
"FracSig minMaxFrac minSig",
|
||||
".0#/@@@+",
|
||||
".0#/@@@*",
|
||||
".0#/@@@+",
|
||||
NumberFormatter.with().precision(Precision.minMaxFraction(1, 2).withMinDigits(3)),
|
||||
ULocale.ENGLISH,
|
||||
@ -1172,7 +1172,7 @@ public class NumberFormatterApiTest {
|
||||
|
||||
assertFormatSingle(
|
||||
"FracSig with trailing zeros A",
|
||||
".00/@@@+",
|
||||
".00/@@@*",
|
||||
".00/@@@+",
|
||||
NumberFormatter.with().precision(Precision.fixedFraction(2).withMinDigits(3)),
|
||||
ULocale.ENGLISH,
|
||||
@ -1181,7 +1181,7 @@ public class NumberFormatterApiTest {
|
||||
|
||||
assertFormatSingle(
|
||||
"FracSig with trailing zeros B",
|
||||
".00/@@@+",
|
||||
".00/@@@*",
|
||||
".00/@@@+",
|
||||
NumberFormatter.with().precision(Precision.fixedFraction(2).withMinDigits(3)),
|
||||
ULocale.ENGLISH,
|
||||
@ -1726,7 +1726,7 @@ public class NumberFormatterApiTest {
|
||||
|
||||
assertFormatDescending(
|
||||
"Integer Width Zero Fill 0",
|
||||
"integer-width/+",
|
||||
"integer-width/*",
|
||||
"integer-width/+",
|
||||
NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(0)),
|
||||
ULocale.ENGLISH,
|
||||
|
@ -28,26 +28,35 @@ public class NumberSkeletonTest {
|
||||
"precision-integer",
|
||||
"precision-unlimited",
|
||||
"@@@##",
|
||||
"@@*",
|
||||
"@@+",
|
||||
".000##",
|
||||
".00*",
|
||||
".00+",
|
||||
".",
|
||||
".*",
|
||||
".+",
|
||||
".######",
|
||||
".00/@@*",
|
||||
".00/@@+",
|
||||
".00/@##",
|
||||
"precision-increment/3.14",
|
||||
"precision-currency-standard",
|
||||
"precision-integer rounding-mode-half-up",
|
||||
".00# rounding-mode-ceiling",
|
||||
".00/@@* rounding-mode-floor",
|
||||
".00/@@+ rounding-mode-floor",
|
||||
"scientific",
|
||||
"scientific/*ee",
|
||||
"scientific/+ee",
|
||||
"scientific/sign-always",
|
||||
"scientific/*ee/sign-always",
|
||||
"scientific/+ee/sign-always",
|
||||
"scientific/sign-always/*ee",
|
||||
"scientific/sign-always/+ee",
|
||||
"scientific/sign-except-zero",
|
||||
"engineering",
|
||||
"engineering/*eee",
|
||||
"engineering/+eee",
|
||||
"compact-short",
|
||||
"compact-long",
|
||||
@ -68,6 +77,7 @@ public class NumberSkeletonTest {
|
||||
"group-thousands",
|
||||
"integer-width/00",
|
||||
"integer-width/#0",
|
||||
"integer-width/*00",
|
||||
"integer-width/+00",
|
||||
"sign-always",
|
||||
"sign-auto",
|
||||
@ -122,16 +132,22 @@ public class NumberSkeletonTest {
|
||||
String[] cases = {
|
||||
".00x",
|
||||
".00##0",
|
||||
".##*",
|
||||
".00##*",
|
||||
".0#*",
|
||||
"@#*",
|
||||
".##+",
|
||||
".00##+",
|
||||
".0#+",
|
||||
"@#+",
|
||||
"@@x",
|
||||
"@@##0",
|
||||
"@#+",
|
||||
".00/@",
|
||||
".00/@@",
|
||||
".00/@@x",
|
||||
".00/@@#",
|
||||
".00/@@#*",
|
||||
".00/floor/@@*", // wrong order
|
||||
".00/@@#+",
|
||||
".00/floor/@@+", // wrong order
|
||||
"precision-increment/français", // non-invariant characters for C++
|
||||
@ -147,6 +163,10 @@ public class NumberSkeletonTest {
|
||||
"currency/ççç", // three characters but not ASCII
|
||||
"measure-unit/foo",
|
||||
"integer-width/xxx",
|
||||
"integer-width/0*",
|
||||
"integer-width/*0#",
|
||||
"integer-width/*#",
|
||||
"integer-width/*#0",
|
||||
"integer-width/0+",
|
||||
"integer-width/+0#",
|
||||
"integer-width/+#",
|
||||
@ -163,6 +183,7 @@ public class NumberSkeletonTest {
|
||||
"EEE",
|
||||
"EEE0",
|
||||
"001",
|
||||
"00*",
|
||||
"00+",
|
||||
};
|
||||
|
||||
@ -296,6 +317,26 @@ public class NumberSkeletonTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void wildcardCharacters() {
|
||||
String[][] cases = {
|
||||
{ ".00*", ".00+" },
|
||||
{ "@@*", "@@+" },
|
||||
{ ".00/@@*", ".00/@@+" },
|
||||
{ "scientific/*ee", "scientific/+ee" },
|
||||
{ "integer-width/*00", "integer-width/+00" },
|
||||
};
|
||||
|
||||
for (String[] cas : cases) {
|
||||
String star = cas[0];
|
||||
String plus = cas[1];
|
||||
|
||||
String normalized = NumberFormatter.forSkeleton(plus)
|
||||
.toSkeleton();
|
||||
assertEquals("Plus should normalize to star", star, normalized);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void roundingModeNames() {
|
||||
for (RoundingMode mode : RoundingMode.values()) {
|
||||
|
Loading…
Reference in New Issue
Block a user