} // namespace
// NOTE: patterns and multipliers both get zero-initialized.
-CompactData::CompactData() : patterns(), multipliers(), largestMagnitude(0), isEmpty(TRUE) {
+CompactData::CompactData() : patterns(), multipliers(), largestMagnitude(0), isEmpty(true) {
}
void CompactData::populate(const Locale &locale, const char *nsName, CompactStyle compactStyle,
return multipliers[magnitude];
}
-const UChar *CompactData::getPattern(int32_t magnitude, StandardPlural::Form plural) const {
+const UChar *CompactData::getPattern(
+ int32_t magnitude,
+ const PluralRules *rules,
+ const DecimalQuantity &dq) const {
if (magnitude < 0) {
return nullptr;
}
if (magnitude > largestMagnitude) {
magnitude = largestMagnitude;
}
- const UChar *patternString = patterns[getIndex(magnitude, plural)];
+ const UChar *patternString = nullptr;
+ if (dq.hasIntegerValue()) {
+ int64_t i = dq.toLong(true);
+ if (i == 0) {
+ patternString = patterns[getIndex(magnitude, StandardPlural::Form::EQ_0)];
+ } else if (i == 1) {
+ patternString = patterns[getIndex(magnitude, StandardPlural::Form::EQ_1)];
+ }
+ if (patternString != nullptr) {
+ return patternString;
+ }
+ }
+ StandardPlural::Form plural = utils::getStandardPlural(rules, dq);
+ patternString = patterns[getIndex(magnitude, plural)];
if (patternString == nullptr && plural != StandardPlural::OTHER) {
// Fall back to "other" plural variant
patternString = patterns[getIndex(magnitude, StandardPlural::OTHER)];
ResourceTable pluralVariantsTable = value.getTable(status);
if (U_FAILURE(status)) { return; }
for (int i4 = 0; pluralVariantsTable.getKeyAndValue(i4, key, value); ++i4) {
-
- if (uprv_strcmp(key, "0") == 0 || uprv_strcmp(key, "1") == 0) {
- // TODO(ICU-21258): Handle this case. For now, skip.
- continue;
- }
-
// 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);
magnitude -= multiplier;
}
- StandardPlural::Form plural = utils::getStandardPlural(rules, quantity);
- const UChar *patternString = data.getPattern(magnitude, plural);
+ const UChar *patternString = data.getPattern(magnitude, rules, quantity);
if (patternString == nullptr) {
// Use the default (non-compact) modifier.
// No need to take any action.
int32_t getMultiplier(int32_t magnitude) const U_OVERRIDE;
- const UChar *getPattern(int32_t magnitude, StandardPlural::Form plural) const;
+ const UChar *getPattern(
+ int32_t magnitude,
+ const PluralRules *rules,
+ const DecimalQuantity &dq) const;
void getUniquePatterns(UVector &output, UErrorCode &status) const;
U_NAMESPACE_BEGIN
static const char *gKeywords[StandardPlural::COUNT] = {
- "zero", "one", "two", "few", "many", "other"
+ "zero", "one", "two", "few", "many", "other", "=0", "=1"
};
const char *StandardPlural::getKeyword(Form p) {
return ZERO;
}
break;
+ case '=':
+ if (uprv_strcmp(keyword, "0") == 0) {
+ return EQ_0;
+ } else if (uprv_strcmp(keyword, "1") == 0) {
+ return EQ_1;
+ }
+ break;
+ // Also allow "0" and "1"
+ case '0':
+ if (*keyword == 0) {
+ return EQ_0;
+ }
+ break;
+ case '1':
+ if (*keyword == 0) {
+ return EQ_1;
+ }
+ break;
default:
break;
}
return -1;
}
-static const UChar gZero[] = { 0x7A, 0x65, 0x72, 0x6F };
-static const UChar gOne[] = { 0x6F, 0x6E, 0x65 };
-static const UChar gTwo[] = { 0x74, 0x77, 0x6F };
-static const UChar gFew[] = { 0x66, 0x65, 0x77 };
-static const UChar gMany[] = { 0x6D, 0x61, 0x6E, 0x79 };
-static const UChar gOther[] = { 0x6F, 0x74, 0x68, 0x65, 0x72 };
+static const UChar gZero[] = u"zero";
+static const UChar gOne[] = u"one";
+static const UChar gTwo[] = u"two";
+static const UChar gFew[] = u"few";
+static const UChar gMany[] = u"many";
+static const UChar gOther[] = u"other";
+static const UChar gEq0[] = u"=0";
+static const UChar gEq1[] = u"=1";
int32_t StandardPlural::indexOrNegativeFromString(const UnicodeString &keyword) {
switch (keyword.length()) {
+ case 1:
+ if (keyword.charAt(0) == '0') {
+ return EQ_0;
+ } else if (keyword.charAt(0) == '1') {
+ return EQ_1;
+ }
+ break;
+ case 2:
+ if (keyword.compare(gEq0, 2) == 0) {
+ return EQ_0;
+ } else if (keyword.compare(gEq1, 2) == 0) {
+ return EQ_1;
+ }
+ break;
case 3:
if (keyword.compare(gOne, 3) == 0) {
return ONE;
FEW,
MANY,
OTHER,
+ EQ_0,
+ EQ_1,
COUNT
};
void testConvertToAccurateDouble();
void testUseApproximateDoubleWhenAble();
void testHardDoubleConversion();
+ void testFitsInLong();
void testToDouble();
void testMaxDigits();
void testNickelRounding();
1e7,
u"1000\u842C");
- if (!logKnownIssue("21258", "StandardPlural cannot handle keywords 1, 0")) {
- assertFormatSingle(
- u"Compact with plural form =1 (ICU-21258)",
- u"compact-long",
- u"K",
- NumberFormatter::with().notation(Notation::compactLong()),
- Locale("fr-FR"),
- 1e3,
- u"mille");
- }
+ assertFormatSingle(
+ u"Compact with plural form =1 (ICU-21258)",
+ u"compact-long",
+ u"KK",
+ NumberFormatter::with().notation(Notation::compactLong()),
+ Locale("fr-FR"),
+ 1e3,
+ u"mille");
assertFormatSingle(
u"Compact Infinity",
}
TESTCASE_AUTO(testUseApproximateDoubleWhenAble);
TESTCASE_AUTO(testHardDoubleConversion);
+ TESTCASE_AUTO(testFitsInLong);
TESTCASE_AUTO(testToDouble);
TESTCASE_AUTO(testMaxDigits);
TESTCASE_AUTO(testNickelRounding);
}
}
+void DecimalQuantityTest::testFitsInLong() {
+ IcuTestErrorCode status(*this, "testFitsInLong");
+ DecimalQuantity quantity;
+ quantity.setToInt(0);
+ assertTrue("Zero should fit", quantity.fitsInLong());
+ quantity.setToInt(42);
+ assertTrue("Small int should fit", quantity.fitsInLong());
+ quantity.setToDouble(0.1);
+ assertFalse("Fraction should not fit", quantity.fitsInLong());
+ quantity.setToDouble(42.1);
+ assertFalse("Fraction should not fit", quantity.fitsInLong());
+ quantity.setToLong(1000000);
+ assertTrue("Large low-precision int should fit", quantity.fitsInLong());
+ quantity.setToLong(1000000000000000000L);
+ assertTrue("10^19 should fit", quantity.fitsInLong());
+ quantity.setToLong(1234567890123456789L);
+ assertTrue("A number between 10^19 and max long should fit", quantity.fitsInLong());
+ quantity.setToLong(1234567890000000000L);
+ assertTrue("A number with trailing zeros less than max long should fit", quantity.fitsInLong());
+ quantity.setToLong(9223372026854775808L);
+ assertTrue("A number less than max long but with similar digits should fit",
+ quantity.fitsInLong());
+ quantity.setToLong(9223372036854775806L);
+ assertTrue("One less than max long should fit", quantity.fitsInLong());
+ quantity.setToLong(9223372036854775807L);
+ assertTrue("Max long should fit", quantity.fitsInLong());
+ assertEquals("Max long should equal toLong", 9223372036854775807L, quantity.toLong(false));
+ quantity.setToDecNumber("9223372036854775808", status);
+ assertFalse("One greater than max long should not fit", quantity.fitsInLong());
+ assertEquals("toLong(true) should truncate", 223372036854775808L, quantity.toLong(true));
+ quantity.setToDecNumber("9223372046854775806", status);
+ assertFalse("A number between max long and 10^20 should not fit", quantity.fitsInLong());
+ quantity.setToDecNumber("9223372046800000000", status);
+ assertFalse("A large 10^19 number with trailing zeros should not fit", quantity.fitsInLong());
+ quantity.setToDecNumber("10000000000000000000", status);
+ assertFalse("10^20 should not fit", quantity.fitsInLong());
+}
+
void DecimalQuantityTest::testToDouble() {
IcuTestErrorCode status(*this, "testToDouble");
static const struct TestCase {
{u"scientific", 0.012, u"1,2E-2", 0L, 0.012, u"0.012", -2, -2},
{u"", 999.9, u"999,9", 999L, 999.9, u"999.9", 0, 0},
- {u"compact-long", 999.9, u"1 millier", 1000L, 1000.0, u"1000", 3, 3},
+ {u"compact-long", 999.9, u"mille", 1000L, 1000.0, u"1000", 3, 3},
{u"compact-short", 999.9, u"1 k", 1000L, 1000.0, u"1000", 3, 3},
{u"scientific", 999.9, u"9,999E2", 999L, 999.9, u"999.9", 2, 2},
{u"", 1000.0, u"1 000", 1000L, 1000.0, u"1000", 0, 0},
- {u"compact-long", 1000.0, u"1 millier", 1000L, 1000.0, u"1000", 3, 3},
+ {u"compact-long", 1000.0, u"mille", 1000L, 1000.0, u"1000", 3, 3},
{u"compact-short", 1000.0, u"1 k", 1000L, 1000.0, u"1000", 3, 3},
{u"scientific", 1000.0, u"1E3", 1000L, 1000.0, u"1000", 3, 3},
};
TWO("two"),
FEW("few"),
MANY("many"),
- OTHER("other");
+ OTHER("other"),
+ EQ_0("=0"),
+ EQ_1("=1");
/**
* Numeric index of OTHER, same as OTHER.ordinal().
*/
public static final StandardPlural orNullFromString(CharSequence keyword) {
switch (keyword.length()) {
+ case 1:
+ if (keyword.charAt(0) == '0') {
+ return EQ_0;
+ } else if (keyword.charAt(0) == '1') {
+ return EQ_1;
+ }
+ break;
+ case 2:
+ if ("=0".contentEquals(keyword)) {
+ return EQ_0;
+ } else if ("=1".contentEquals(keyword)) {
+ return EQ_1;
+ }
+ break;
case 3:
if ("one".contentEquals(keyword)) {
return ONE;
import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.impl.UResource;
+import com.ibm.icu.text.PluralRules;
import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
import com.ibm.icu.util.ICUException;
import com.ibm.icu.util.ULocale;
byte magnitude = (byte) (magnitudeEntry.getKey().length() - 1);
for (Map.Entry<String, String> pluralEntry : magnitudeEntry.getValue().entrySet()) {
String pluralString = pluralEntry.getKey().toString();
- if ("0".equals(pluralString) || "1".equals(pluralString)) {
- // TODO(ICU-21258): Handle this case. For now, skip.
- continue;
- }
StandardPlural plural = StandardPlural.fromString(pluralString);
String patternString = pluralEntry.getValue().toString();
patterns[getIndex(magnitude, plural)] = patternString;
return multipliers[magnitude];
}
- public String getPattern(int magnitude, StandardPlural plural) {
+ public String getPattern(int magnitude, PluralRules rules, DecimalQuantity dq) {
if (magnitude < 0) {
return null;
}
if (magnitude > largestMagnitude) {
magnitude = largestMagnitude;
}
- String patternString = patterns[getIndex(magnitude, plural)];
+ String patternString = null;
+ if (dq.isHasIntegerValue()) {
+ long i = dq.toLong(true);
+ if (i == 0) {
+ patternString = patterns[getIndex(magnitude, StandardPlural.EQ_0)];
+ } else if (i == 1) {
+ patternString = patterns[getIndex(magnitude, StandardPlural.EQ_1)];
+ }
+ if (patternString != null) {
+ return patternString;
+ }
+ }
+ StandardPlural plural = dq.getStandardPlural(rules);
+ patternString = patterns[getIndex(magnitude, plural)];
if (patternString == null && plural != StandardPlural.OTHER) {
// Fall back to "other" plural variant
patternString = patterns[getIndex(magnitude, StandardPlural.OTHER)];
// Iterate over the plural variants ("one", "other", etc)
UResource.Table pluralVariantsTable = value.getTable();
for (int i4 = 0; pluralVariantsTable.getKeyAndValue(i4, key, value); ++i4) {
-
- if ("0".equals(key.toString()) || "1".equals(key.toString())) {
- // TODO(ICU-21258): Handle this case. For now, skip.
- continue;
- }
-
// 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());
public BigDecimal toBigDecimal();
+ /**
+ * Returns a long approximating the decimal quantity. A long can only represent the
+ * integral part of the number. Note: this method incorporates the value of
+ * {@code getExponent} (for cases such as compact notation) to return the proper long
+ * value represented by the result.
+ *
+ * @param truncateIfOverflow if false and the number does NOT fit, fails with an error.
+ * See comment about call site guards in DecimalQuantity_AbstractBCD.java
+ * @return A 64-bit integer representation of the internal number.
+ */
+ public long toLong(boolean truncateIfOverflow);
+
public void setToBigDecimal(BigDecimal input);
public int maxRepresentableDigits();
exponent = exponent + delta;
}
+ @Override
+ public boolean isHasIntegerValue() {
+ return scale >= 0;
+ }
+
@Override
public StandardPlural getStandardPlural(PluralRules rules) {
if (rules == null) {
scale -= fracLength;
}
- /**
- * Returns a long approximating the internal BCD. A long can only represent the integral part of the
- * number. Note: this method incorporates the value of {@code exponent}
- * (for cases such as compact notation) to return the proper long value
- * represented by the result.
- *
- * @param truncateIfOverflow if false and the number does NOT fit, fails with an assertion error.
- * @return A 64-bit integer representation of the internal BCD.
- */
+ @Override
public long toLong(boolean truncateIfOverflow) {
// NOTE: Call sites should be guarded by fitsInLong(), like this:
// if (dq.fitsInLong()) { /* use dq.toLong() */ } else { /* use some fallback */ }
magnitude -= multiplier;
}
- StandardPlural plural = quantity.getStandardPlural(rules);
- String patternString = data.getPattern(magnitude, plural);
+ String patternString = data.getPattern(magnitude, rules, quantity);
if (patternString == null) {
// Use the default (non-compact) modifier.
// No need to take any action.
*/
@Deprecated
public boolean isInfinite();
+
+ /**
+ * Whether the number has no nonzero fraction digits.
+ * @internal CLDR
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
+ public boolean isHasIntegerValue();
}
/**
* @deprecated This API is ICU internal only.
*/
@Deprecated
+ @Override
public boolean isHasIntegerValue() {
return hasIntegerValue;
}
primary = -1;
}
+ @Override
+ public long toLong(boolean truncateIfOverflow) {
+ BigDecimal temp = toBigDecimal().setScale(0, RoundingMode.FLOOR);
+ if (truncateIfOverflow) {
+ return temp.longValue();
+ } else {
+ return temp.longValueExact();
+ }
+ }
+
@Override
public void setMinInteger(int minInt) {
// Graceful failures for bogus input
public void adjustExponent(int delta) {
origPrimaryScale = origPrimaryScale + delta;
}
+
+ @Override
+ public boolean isHasIntegerValue() {
+ return scaleBigDecimal(toBigDecimal()) >= 0;
+ }
}
q0.toBigDecimal(),
q1.toBigDecimal());
+ assertEquals("Different long values (" + q0 + ", " + q1 + ")",
+ q0.toLong(true),
+ q1.toLong(true));
+
q0.roundToInfinity();
q1.roundToInfinity();
assertTrue("One less than max long should fit", quantity.fitsInLong());
quantity.setToLong(9223372036854775807L);
assertTrue("Max long should fit", quantity.fitsInLong());
+ assertEquals("Max long should equal toLong", 9223372036854775807L, quantity.toLong(false));
quantity.setToBigInteger(new BigInteger("9223372036854775808"));
- assertFalse("One greater than max long long should not fit", quantity.fitsInLong());
+ assertFalse("One greater than max long should not fit", quantity.fitsInLong());
+ assertEquals("toLong(true) should truncate", 223372036854775808L, quantity.toLong(true));
+ try {
+ quantity.toLong(false);
+ fail("One greater than max long is not convertible to long");
+ } catch (ArithmeticException | AssertionError e) {
+ // expected
+ }
quantity.setToBigInteger(new BigInteger("9223372046854775806"));
assertFalse("A number between max long and 10^20 should not fit", quantity.fitsInLong());
quantity.setToBigInteger(new BigInteger("9223372046800000000"));
{"scientific", 0.012, "1,2E-2", 0L, 0.012, new BigDecimal("0.012"), "0.012", -2, -2},
{"", 999.9, "999,9", 999L, 999.9, new BigDecimal("999.9"), "999.9", 0, 0},
- {"compact-long", 999.9, "1 millier", 1000L, 1000.0, new BigDecimal("1000"), "1000", 3, 3},
+ {"compact-long", 999.9, "mille", 1000L, 1000.0, new BigDecimal("1000"), "1000", 3, 3},
{"compact-short", 999.9, "1 k", 1000L, 1000.0, new BigDecimal("1000"), "1000", 3, 3},
{"scientific", 999.9, "9,999E2", 999L, 999.9, new BigDecimal("999.9"), "999.9", 2, 2},
{"", 1000.0, "1 000", 1000L, 1000.0, new BigDecimal("1000"), "1000", 0, 0},
- {"compact-long", 1000.0, "1 millier", 1000L, 1000.0, new BigDecimal("1000"), "1000", 3, 3},
+ {"compact-long", 1000.0, "mille", 1000L, 1000.0, new BigDecimal("1000"), "1000", 3, 3},
{"compact-short", 1000.0, "1 k", 1000L, 1000.0, new BigDecimal("1000"), "1000", 3, 3},
{"scientific", 1000.0, "1E3", 1000L, 1000.0, new BigDecimal("1000"), "1000", 3, 3},
};
1e7,
"1000\u842C");
- if (!logKnownIssue("21258", "StandardPlural cannot handle keywords 1, 0")) {
- assertFormatSingle(
- "Compact with plural form =1 (ICU-21258)",
- "compact-long",
- "K",
- NumberFormatter.with().notation(Notation.compactLong()),
- ULocale.FRANCE,
- 1e3,
- "mille");
- }
+ assertFormatSingle(
+ "Compact with plural form =1 (ICU-21258)",
+ "compact-long",
+ "KK",
+ NumberFormatter.with().notation(Notation.compactLong()),
+ ULocale.FRANCE,
+ 1e3,
+ "mille");
assertFormatSingle(
"Compact Infinity",