ICU-13836 Represent suppressed exponent for better plural support
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity.java
index 7d30c58..9d6fc34 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity.java
@@ -123,6 +123,26 @@
public int getMagnitude() throws ArithmeticException;
/**
+ * @return The value of the (suppressed) exponent after the number has been
+ * put into a notation with exponents (ex: compact, scientific). Ex: given
+ * the number 1000 as "1K" / "1E3", the return value will be 3 (positive).
+ */
+ public int getExponent();
+
+ /**
+ * Adjusts the value for the (suppressed) exponent stored when using
+ * notation with exponents (ex: compact, scientific).
+ *
+ * <p>Adjusting the exponent is decoupled from {@link #adjustMagnitude} in
+ * order to allow flexibility for {@link StandardPlural} to be selected in
+ * formatting (ex: for compact notation) either with or without the exponent
+ * applied in the value of the number.
+ * @param delta
+ * The value to adjust the exponent by.
+ */
+ public void adjustExponent(int delta);
+
+ /**
* @return Whether the value represented by this {@link DecimalQuantity} is
* zero, infinity, or NaN.
*/
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java
index 09c8893..32635e5 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java
@@ -86,6 +86,12 @@
protected int lReqPos = 0;
protected int rReqPos = 0;
+ /**
+ * The value of the (suppressed) exponent after the number has been put into
+ * a notation with exponents (ex: compact, scientific).
+ */
+ protected int exponent = 0;
+
@Override
public void copyFrom(DecimalQuantity _other) {
copyBcdFrom(_other);
@@ -98,13 +104,14 @@
origDouble = other.origDouble;
origDelta = other.origDelta;
isApproximate = other.isApproximate;
+ exponent = other.exponent;
}
public DecimalQuantity_AbstractBCD clear() {
lReqPos = 0;
rReqPos = 0;
flags = 0;
- setBcdToZero(); // sets scale, precision, hasDouble, origDouble, origDelta, and BCD data
+ setBcdToZero(); // sets scale, precision, hasDouble, origDouble, origDelta, exponent, and BCD data
return this;
}
@@ -217,6 +224,16 @@
}
@Override
+ public int getExponent() {
+ return exponent;
+ }
+
+ @Override
+ public void adjustExponent(int delta) {
+ exponent = exponent + delta;
+ }
+
+ @Override
public StandardPlural getStandardPlural(PluralRules rules) {
if (rules == null) {
// Fail gracefully if the user didn't provide a PluralRules
@@ -246,6 +263,8 @@
return fractionCount();
case w:
return fractionCountWithoutTrailingZeros();
+ case e:
+ return getExponent();
default:
return Math.abs(toDouble());
}
@@ -291,11 +310,11 @@
}
private int fractionCount() {
- return -getLowerDisplayMagnitude();
+ return Math.max(0, -getLowerDisplayMagnitude() - exponent);
}
private int fractionCountWithoutTrailingZeros() {
- return Math.max(-scale, 0);
+ return Math.max(-scale - exponent, 0);
}
@Override
@@ -577,7 +596,9 @@
/**
* Returns a long approximating the internal BCD. A long can only represent the integral part of the
- * number.
+ * 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.
@@ -588,12 +609,12 @@
// Fallback behavior upon truncateIfOverflow is to truncate at 17 digits.
assert(truncateIfOverflow || fitsInLong());
long result = 0L;
- int upperMagnitude = scale + precision - 1;
+ int upperMagnitude = exponent + scale + precision - 1;
if (truncateIfOverflow) {
upperMagnitude = Math.min(upperMagnitude, 17);
}
for (int magnitude = upperMagnitude; magnitude >= 0; magnitude--) {
- result = result * 10 + getDigitPos(magnitude - scale);
+ result = result * 10 + getDigitPos(magnitude - scale - exponent);
}
if (isNegative()) {
result = -result;
@@ -605,10 +626,13 @@
* This returns a long representing the fraction digits of the number, as required by PluralRules.
* For example, if we represent the number "1.20" (including optional and required digits), then this
* function returns "20" if includeTrailingZeros is true or "2" if false.
+ * 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.
*/
public long toFractionLong(boolean includeTrailingZeros) {
long result = 0L;
- int magnitude = -1;
+ int magnitude = -1 - exponent;
int lowerMagnitude = scale;
if (includeTrailingZeros) {
lowerMagnitude = Math.min(lowerMagnitude, rReqPos);
@@ -638,7 +662,7 @@
if (isZeroish()) {
return true;
}
- if (scale < 0) {
+ if (exponent + scale < 0) {
return false;
}
int magnitude = getMagnitude();
@@ -991,22 +1015,40 @@
@Override
public String toPlainString() {
- // NOTE: This logic is duplicated between here and DecimalQuantity_SimpleStorage.
StringBuilder sb = new StringBuilder();
- if (isNegative()) {
- sb.append('-');
- }
- if (precision == 0 || getMagnitude() < 0) {
- sb.append('0');
- }
- for (int m = getUpperDisplayMagnitude(); m >= getLowerDisplayMagnitude(); m--) {
- sb.append((char) ('0' + getDigit(m)));
- if (m == 0)
- sb.append('.');
- }
+ toPlainString(sb);
return sb.toString();
}
+ public void toPlainString(StringBuilder result) {
+ assert(!isApproximate);
+ if (isNegative()) {
+ result.append('-');
+ }
+ if (precision == 0) {
+ result.append('0');
+ return;
+ }
+
+ int upper = scale + precision + exponent - 1;
+ int lower = scale + exponent;
+ if (upper < lReqPos - 1) {
+ upper = lReqPos - 1;
+ }
+ if (lower > rReqPos) {
+ lower = rReqPos;
+ }
+
+ int p = upper;
+ for (; p >= 0; p--) {
+ result.append((char) ('0' + getDigitPos(p - scale - exponent)));
+ }
+ result.append('.');
+ for(; p >= lower; p--) {
+ result.append((char) ('0' + getDigitPos(p - scale - exponent)));
+ }
+ }
+
public String toScientificString() {
StringBuilder sb = new StringBuilder();
toScientificString(sb);
@@ -1035,7 +1077,7 @@
}
}
result.append('E');
- int _scale = upperPos + scale;
+ int _scale = upperPos + scale + exponent;
if (_scale == Integer.MIN_VALUE) {
result.append("-2147483648");
return;
@@ -1146,7 +1188,7 @@
/**
* Sets the internal representation to zero. Clears any values stored in scale, precision, hasDouble,
- * origDouble, origDelta, and BCD data.
+ * origDouble, origDelta, exponent, and BCD data.
*/
protected abstract void setBcdToZero();
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java
index 47f0d97..05612af 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java
@@ -180,6 +180,7 @@
isApproximate = false;
origDouble = 0;
origDelta = 0;
+ exponent = 0;
}
@Override
@@ -254,11 +255,11 @@
}
BigDecimal result = BigDecimal.valueOf(tempLong);
// Test that the new scale fits inside the BigDecimal
- long newScale = result.scale() + scale;
+ long newScale = result.scale() + scale + exponent;
if (newScale <= Integer.MIN_VALUE) {
result = BigDecimal.ZERO;
} else {
- result = result.scaleByPowerOfTen(scale);
+ result = result.scaleByPowerOfTen(scale + exponent);
}
if (isNegative()) {
result = result.negate();
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/CompactNotation.java b/icu4j/main/classes/core/src/com/ibm/icu/number/CompactNotation.java
index a4dad2d..f2bffe7 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/number/CompactNotation.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/number/CompactNotation.java
@@ -128,11 +128,12 @@
// Treat zero, NaN, and infinity as if they had magnitude 0
int magnitude;
+ int multiplier = 0;
if (quantity.isZeroish()) {
magnitude = 0;
micros.rounder.apply(quantity);
} else {
- int multiplier = micros.rounder.chooseMultiplierAndApply(quantity, data);
+ multiplier = micros.rounder.chooseMultiplierAndApply(quantity, data);
magnitude = quantity.isZeroish() ? 0 : quantity.getMagnitude();
magnitude -= multiplier;
}
@@ -156,6 +157,11 @@
micros.modMiddle = unsafePatternModifier;
}
+ // Change the exponent only after we select appropriate plural form
+ // for formatting purposes so that we preserve expected formatted
+ // string behavior.
+ quantity.adjustExponent(-1 * multiplier);
+
// We already performed rounding. Do not perform it again.
micros.rounder = null;
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/ScientificNotation.java b/icu4j/main/classes/core/src/com/ibm/icu/number/ScientificNotation.java
index e751688..cf1ac78 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/number/ScientificNotation.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/number/ScientificNotation.java
@@ -195,6 +195,11 @@
micros.modInner = this;
}
+ // Change the exponent only after we select appropriate plural form
+ // for formatting purposes so that we preserve expected formatted
+ // string behavior.
+ quantity.adjustExponent(exponent);
+
// We already performed rounding. Do not perform it again.
micros.rounder = null;
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/PluralRules.java b/icu4j/main/classes/core/src/com/ibm/icu/text/PluralRules.java
index a9bfa46..ec6944f 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/text/PluralRules.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/text/PluralRules.java
@@ -465,6 +465,16 @@
w,
/**
+ * Suppressed exponent for compact notation (exponent needed in
+ * scientific notation with compact notation to approximate i).
+ *
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
+ e,
+
+ /**
* 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.
@@ -539,6 +549,8 @@
private final int baseFactor;
+ final int suppressedExponent;
+
/**
* @internal CLDR
* @deprecated This API is ICU internal only.
@@ -620,6 +632,15 @@
return baseFactor;
}
+ /**
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
+ public int getSuppressedExponent() {
+ return suppressedExponent;
+ }
+
static final long MAX = (long)1E18;
/**
@@ -640,6 +661,7 @@
? MAX
: (long)n;
hasIntegerValue = source == integerValue;
+ suppressedExponent = 0;
// check values. TODO make into unit test.
//
// long visiblePower = (int) Math.pow(10, v);
@@ -797,6 +819,7 @@
case t: return decimalDigitsWithoutTrailingZeros;
case v: return visibleDecimalDigitCount;
case w: return visibleDecimalDigitCountWithoutTrailingZeros;
+ case e: return suppressedExponent;
default: return source;
}
}
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_64BitBCD.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_64BitBCD.java
index ea84162..22f6e20 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_64BitBCD.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_64BitBCD.java
@@ -95,6 +95,7 @@
isApproximate = false;
origDouble = 0;
origDelta = 0;
+ exponent = 0;
}
@Override
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_ByteArrayBCD.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_ByteArrayBCD.java
index de87080..92b40b0 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_ByteArrayBCD.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_ByteArrayBCD.java
@@ -112,6 +112,7 @@
isApproximate = false;
origDouble = 0;
origDelta = 0;
+ exponent = 0;
}
@Override
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_SimpleStorage.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_SimpleStorage.java
index cb1bc57..24d0434 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_SimpleStorage.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_SimpleStorage.java
@@ -95,6 +95,8 @@
1000000000000000000L
};
+ private int origPrimaryScale;
+
@Override
public int maxRepresentableDigits() {
return Integer.MAX_VALUE;
@@ -110,6 +112,7 @@
primaryScale = 0;
primaryPrecision = computePrecision(primary);
fallback = null;
+ origPrimaryScale = primaryScale;
}
/**
@@ -189,6 +192,8 @@
primary = -1;
fallback = new BigDecimal(temp);
}
+
+ origPrimaryScale = primaryScale;
}
static final double LOG_2_OF_TEN = 3.32192809489;
@@ -279,6 +284,7 @@
primaryPrecision = _other.primaryPrecision;
fallback = _other.fallback;
flags = _other.flags;
+ origPrimaryScale = _other.origPrimaryScale;
}
@Override
@@ -916,4 +922,14 @@
.setFractionDigits((int) getPluralOperand(Operand.v), (long) getPluralOperand(Operand.f));
}
}
+
+ @Override
+ public int getExponent() {
+ return origPrimaryScale;
+ }
+
+ @Override
+ public void adjustExponent(int delta) {
+ origPrimaryScale = origPrimaryScale + delta;
+ }
}
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/PluralRulesTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/PluralRulesTest.java
index 2379470..b67000e 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/PluralRulesTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/PluralRulesTest.java
@@ -42,6 +42,7 @@
import com.ibm.icu.impl.Relation;
import com.ibm.icu.impl.Utility;
import com.ibm.icu.number.FormattedNumber;
+import com.ibm.icu.number.LocalizedNumberFormatter;
import com.ibm.icu.number.NumberFormatter;
import com.ibm.icu.number.Precision;
import com.ibm.icu.number.UnlocalizedNumberFormatter;
@@ -929,6 +930,66 @@
}
}
+
+
+ @Test
+ public void testCompactDecimalPluralKeyword() {
+ PluralRules rules = PluralRules.createRules("one: i = 0,1 @integer 0, 1 @decimal 0.0~1.5; many: e = 0 and i % 1000000 = 0 and v = 0 or " +
+ "e != 0 .. 5; other: @integer 2~17, 100, 1000, 10000, 100000, 1000000, @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …");
+ ULocale locale = new ULocale("fr-FR");
+
+ Object[][] casesData = {
+ // unlocalized formatter skeleton, input, string output, plural rule keyword
+ {"", 0, "0", "one"},
+ {"compact-long", 0, "0", "one"},
+
+ {"", 1, "1", "one"},
+ {"compact-long", 1, "1", "one"},
+
+ {"", 2, "2", "other"},
+ {"compact-long", 2, "2", "other"},
+
+ {"", 1000000, "1 000 000", "many"},
+ {"compact-long", 1000000, "1 million", "many"},
+
+ {"", 1000001, "1 000 001", "other"},
+ {"compact-long", 1000001, "1 million", "many"},
+
+ {"", 120000, "1 200 000", "other"},
+ {"compact-long", 1200000, "1,2 millions", "many"},
+
+ {"", 1200001, "1 200 001", "other"},
+ {"compact-long", 1200001, "1,2 millions", "many"},
+
+ {"", 2000000, "2 000 000", "many"},
+ {"compact-long", 2000000, "2 millions", "many"},
+ };
+
+ for (Object[] caseDatum : casesData) {
+ String skeleton = (String) caseDatum[0];
+ int input = (int) caseDatum[1];
+ String expectedString = (String) caseDatum[2];
+ String expectPluralRuleKeyword = (String) caseDatum[3];
+
+ String actualPluralRuleKeyword =
+ getPluralKeyword(rules, locale, input, skeleton);
+
+ assertEquals(
+ String.format("PluralRules select %s: %d", skeleton, input),
+ expectPluralRuleKeyword,
+ actualPluralRuleKeyword);
+ }
+ }
+
+ private String getPluralKeyword(PluralRules rules, ULocale locale, double number, String skeleton) {
+ LocalizedNumberFormatter formatter =
+ NumberFormatter.forSkeleton(skeleton)
+ .locale(locale);
+ FormattedNumber fn = formatter.format(number);
+ String pluralKeyword = rules.select(fn);
+ return pluralKeyword;
+ }
+
enum StandardPluralCategories {
zero, one, two, few, many, other;
/**
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java
index 471682a..82a0b4f 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java
@@ -24,10 +24,15 @@
import com.ibm.icu.impl.number.DecimalQuantity;
import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD;
import com.ibm.icu.impl.number.RoundingUtils;
+import com.ibm.icu.number.FormattedNumber;
import com.ibm.icu.number.LocalizedNumberFormatter;
+import com.ibm.icu.number.Notation;
import com.ibm.icu.number.NumberFormatter;
+import com.ibm.icu.number.Precision;
+import com.ibm.icu.number.Scale;
import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
import com.ibm.icu.text.DecimalFormatSymbols;
+import com.ibm.icu.text.PluralRules.Operand;
import com.ibm.icu.util.ULocale;
@RunWith(JUnit4.class)
@@ -603,8 +608,272 @@
}
}
+ @Test
+ public void testCompactDecimalSuppressedExponent() {
+ ULocale locale = new ULocale("fr-FR");
+
+ Object[][] casesData = {
+ // unlocalized formatter skeleton, input, string output, long output, double output, BigDecimal output, plain string, suppressed exponent
+ {"", 123456789, "123 456 789", 123456789L, 123456789.0, new BigDecimal("123456789"), "123456789.", 0},
+ {"compact-long", 123456789, "123 millions", 123000000L, 123000000.0, new BigDecimal("123000000"), "123000000.", 6},
+ {"compact-short", 123456789, "123 M", 123000000L, 123000000.0, new BigDecimal("123000000"), "123000000.", 6},
+ {"scientific", 123456789, "1,234568E8", 123456800L, 123456800.0, new BigDecimal("123456800"), "123456800.", 8},
+
+ {"", 1234567, "1 234 567", 1234567L, 1234567.0, new BigDecimal("1234567"), "1234567.", 0},
+ {"compact-long", 1234567, "1,2 million", 1200000L, 1200000.0, new BigDecimal("1200000"), "1200000.", 6},
+ {"compact-short", 1234567, "1,2 M", 1200000L, 1200000.0, new BigDecimal("1200000"), "1200000.", 6},
+ {"scientific", 1234567, "1,234567E6", 1234567L, 1234567.0, new BigDecimal("1234567"), "1234567.", 6},
+
+ {"", 123456, "123 456", 123456L, 123456.0, new BigDecimal("123456"), "123456.", 0},
+ {"compact-long", 123456, "123 mille", 123000L, 123000.0, new BigDecimal("123000"), "123000.", 3},
+ {"compact-short", 123456, "123 k", 123000L, 123000.0, new BigDecimal("123000"), "123000.", 3},
+ {"scientific", 123456, "1,23456E5", 123456L, 123456.0, new BigDecimal("123456"), "123456.", 5},
+
+ {"", 123, "123", 123L, 123.0, new BigDecimal("123"), "123.", 0},
+ {"compact-long", 123, "123", 123L, 123.0, new BigDecimal("123"), "123.", 0},
+ {"compact-short", 123, "123", 123L, 123.0, new BigDecimal("123"), "123.", 0},
+ {"scientific", 123, "1,23E2", 123L, 123.0, new BigDecimal("123"), "123.", 2},
+
+ {"", 1.2, "1,2", 1L, 1.2, new BigDecimal("1.2"), "1.2", 0},
+ {"compact-long", 1.2, "1,2", 1L, 1.2, new BigDecimal("1.2"), "1.2", 0},
+ {"compact-short", 1.2, "1,2", 1L, 1.2, new BigDecimal("1.2"), "1.2", 0},
+ {"scientific", 1.2, "1,2E0", 1L, 1.2, new BigDecimal("1.2"), "1.2", 0},
+
+ {"", 0.12, "0,12", 0L, 0.12, new BigDecimal("0.12"), "0.12", 0},
+ {"compact-long", 0.12, "0,12", 0L, 0.12, new BigDecimal("0.12"), "0.12", 0},
+ {"compact-short", 0.12, "0,12", 0L, 0.12, new BigDecimal("0.12"), "0.12", 0},
+ {"scientific", 0.12, "1,2E-1", 0L, 0.12, new BigDecimal("0.12"), "0.12", -1},
+
+ {"", 0.012, "0,012", 0L, 0.012, new BigDecimal("0.012"), "0.012", 0},
+ {"compact-long", 0.012, "0,012", 0L, 0.012, new BigDecimal("0.012"), "0.012", 0},
+ {"compact-short", 0.012, "0,012", 0L, 0.012, new BigDecimal("0.012"), "0.012", 0},
+ {"scientific", 0.012, "1,2E-2", 0L, 0.012, new BigDecimal("0.012"), "0.012", -2},
+
+ {"", 999.9, "999,9", 999L, 999.9, new BigDecimal("999.9"), "999.9", 0},
+ {"compact-long", 999.9, "1 millier", 1000L, 1000.0, new BigDecimal("1000"), "1000.", 3},
+ {"compact-short", 999.9, "1 k", 1000L, 1000.0, new BigDecimal("1000"), "1000.", 3},
+ {"scientific", 999.9, "9,999E2", 999L, 999.9, new BigDecimal("999.9"), "999.9", 2},
+
+ {"", 1000.0, "1 000", 1000L, 1000.0, new BigDecimal("1000"), "1000.", 0},
+ {"compact-long", 1000.0, "1 millier", 1000L, 1000.0, new BigDecimal("1000"), "1000.", 3},
+ {"compact-short", 1000.0, "1 k", 1000L, 1000.0, new BigDecimal("1000"), "1000.", 3},
+ {"scientific", 1000.0, "1E3", 1000L, 1000.0, new BigDecimal("1000"), "1000.", 3},
+ };
+
+ for (Object[] caseDatum : casesData) {
+ // test the helper methods used to compute plural operand values
+
+ String skeleton = (String) caseDatum[0];
+ LocalizedNumberFormatter formatter =
+ NumberFormatter.forSkeleton(skeleton)
+ .locale(locale);
+ double input = ((Number) caseDatum[1]).doubleValue();
+ String expectedString = (String) caseDatum[2];
+ long expectedLong = (long) caseDatum[3];
+ double expectedDouble = (double) caseDatum[4];
+ BigDecimal expectedBigDecimal = (BigDecimal) caseDatum[5];
+ String expectedPlainString = (String) caseDatum[6];
+ int expectedSuppressedExponent = (int) caseDatum[7];
+
+ FormattedNumber fn = formatter.format(input);
+ DecimalQuantity_DualStorageBCD dq = (DecimalQuantity_DualStorageBCD)
+ fn.getFixedDecimal();
+ String actualString = fn.toString();
+ long actualLong = dq.toLong(true);
+ double actualDouble = dq.toDouble();
+ BigDecimal actualBigDecimal = dq.toBigDecimal();
+ String actualPlainString = dq.toPlainString();
+ int actualSuppressedExponent = dq.getExponent();
+
+ assertEquals(
+ String.format("formatted number %s toString: %f", skeleton, input),
+ expectedString,
+ actualString);
+ assertEquals(
+ String.format("compact decimal %s toLong: %f", skeleton, input),
+ expectedLong,
+ actualLong);
+ assertDoubleEquals(
+ String.format("compact decimal %s toDouble: %f", skeleton, input),
+ expectedDouble,
+ actualDouble);
+ assertBigDecimalEquals(
+ String.format("compact decimal %s toBigDecimal: %f", skeleton, input),
+ expectedBigDecimal,
+ actualBigDecimal);
+ assertEquals(
+ String.format("formatted number %s toPlainString: %f", skeleton, input),
+ expectedPlainString,
+ actualPlainString);
+ assertEquals(
+ String.format("compact decimal %s suppressed exponent: %f", skeleton, input),
+ expectedSuppressedExponent,
+ actualSuppressedExponent);
+
+ // test the actual computed values of the plural operands
+
+ double expectedNOperand = expectedDouble;
+ double expectedIOperand = expectedLong;
+ double expectedEOperand = expectedSuppressedExponent;
+ double actualNOperand = dq.getPluralOperand(Operand.n);
+ double actualIOperand = dq.getPluralOperand(Operand.i);
+ double actualEOperand = dq.getPluralOperand(Operand.e);
+
+ assertEquals(
+ String.format("formatted number %s toString: %s", skeleton, input),
+ expectedString,
+ actualString);
+ assertDoubleEquals(
+ String.format("compact decimal %s n operand: %f", skeleton, input),
+ expectedNOperand,
+ actualNOperand);
+ assertDoubleEquals(
+ String.format("compact decimal %s i operand: %f", skeleton, input),
+ expectedIOperand,
+ actualIOperand);
+ assertDoubleEquals(
+ String.format("compact decimal %s e operand: %f", skeleton, input),
+ expectedEOperand,
+ actualEOperand);
+ }
+ }
+
+
+ @Test
+ public void testCompactNotationFractionPluralOperands() {
+ ULocale locale = new ULocale("fr-FR");
+ LocalizedNumberFormatter formatter =
+ NumberFormatter.withLocale(locale)
+ .notation(Notation.compactLong())
+ .precision(Precision.fixedFraction(5))
+ .scale(Scale.powerOfTen(-1));
+ double formatterInput = 12345;
+ double inputVal = 1234.5;
+ FormattedNumber fn = formatter.format(formatterInput);
+ DecimalQuantity_DualStorageBCD dq = (DecimalQuantity_DualStorageBCD)
+ fn.getFixedDecimal();
+
+ double expectedNOperand = 1234.5;
+ double expectedIOperand = 1234;
+ double expectedFOperand = 50;
+ double expectedTOperand = 5;
+ double expectedVOperand = 2;
+ double expectedWOperand = 1;
+ double expectedEOperand = 3;
+ String expectedString = "1,23450 millier";
+ double actualNOperand = dq.getPluralOperand(Operand.n);
+ double actualIOperand = dq.getPluralOperand(Operand.i);
+ double actualFOperand = dq.getPluralOperand(Operand.f);
+ double actualTOperand = dq.getPluralOperand(Operand.t);
+ double actualVOperand = dq.getPluralOperand(Operand.v);
+ double actualWOperand = dq.getPluralOperand(Operand.w);
+ double actualEOperand = dq.getPluralOperand(Operand.e);
+ String actualString = fn.toString();
+
+ assertDoubleEquals(
+ String.format("compact decimal fraction n operand: %f", inputVal),
+ expectedNOperand,
+ actualNOperand);
+ assertDoubleEquals(
+ String.format("compact decimal fraction i operand: %f", inputVal),
+ expectedIOperand,
+ actualIOperand);
+ assertDoubleEquals(
+ String.format("compact decimal fraction f operand: %f", inputVal),
+ expectedFOperand,
+ actualFOperand);
+ assertDoubleEquals(
+ String.format("compact decimal fraction t operand: %f", inputVal),
+ expectedTOperand,
+ actualTOperand);
+ assertDoubleEquals(
+ String.format("compact decimal fraction v operand: %f", inputVal),
+ expectedVOperand,
+ actualVOperand);
+ assertDoubleEquals(
+ String.format("compact decimal fraction w operand: %f", inputVal),
+ expectedWOperand,
+ actualWOperand);
+ assertDoubleEquals(
+ String.format("compact decimal fraction e operand: %f", inputVal),
+ expectedEOperand,
+ actualEOperand);
+ assertEquals(
+ String.format("compact decimal fraction toString: %f", inputVal),
+ expectedString,
+ actualString);
+ }
+
+ @Test
+ public void testSuppressedExponentUnchangedByInitialScaling() {
+ ULocale locale = new ULocale("fr-FR");
+ LocalizedNumberFormatter withLocale = NumberFormatter.withLocale(locale);
+ LocalizedNumberFormatter compactLong =
+ withLocale.notation(Notation.compactLong());
+ LocalizedNumberFormatter compactScaled =
+ compactLong.scale(Scale.powerOfTen(3));
+
+ Object[][] casesData = {
+ // input, compact long string output,
+ // compact n operand, compact i operand, compact e operand
+ {123456789, "123 millions", 123000000.0, 123000000.0, 6.0},
+ {1234567, "1,2 million", 1200000.0, 1200000.0, 6.0},
+ {123456, "123 mille", 123000.0, 123000.0, 3.0},
+ {123, "123", 123.0, 123.0, 0.0},
+ };
+
+ for (Object[] caseDatum : casesData) {
+ int input = (int) caseDatum[0];
+ String expectedString = (String) caseDatum[1];
+ double expectedNOperand = (double) caseDatum[2];
+ double expectedIOperand = (double) caseDatum[3];
+ double expectedEOperand = (double) caseDatum[4];
+
+ FormattedNumber fnCompactScaled = compactScaled.format(input);
+ DecimalQuantity_DualStorageBCD dqCompactScaled =
+ (DecimalQuantity_DualStorageBCD) fnCompactScaled.getFixedDecimal();
+ double compactScaledEOperand = dqCompactScaled.getPluralOperand(Operand.e);
+
+ FormattedNumber fnCompact = compactLong.format(input);
+ DecimalQuantity_DualStorageBCD dqCompact =
+ (DecimalQuantity_DualStorageBCD) fnCompact.getFixedDecimal();
+ String actualString = fnCompact.toString();
+ double compactNOperand = dqCompact.getPluralOperand(Operand.n);
+ double compactIOperand = dqCompact.getPluralOperand(Operand.i);
+ double compactEOperand = dqCompact.getPluralOperand(Operand.e);
+ assertEquals(
+ String.format("formatted number compactLong toString: %s", input),
+ expectedString,
+ actualString);
+ assertDoubleEquals(
+ String.format("compact decimal %d, n operand vs. expected", input),
+ expectedNOperand,
+ compactNOperand);
+ assertDoubleEquals(
+ String.format("compact decimal %d, i operand vs. expected", input),
+ expectedIOperand,
+ compactIOperand);
+ assertDoubleEquals(
+ String.format("compact decimal %d, e operand vs. expected", input),
+ expectedEOperand,
+ compactEOperand);
+
+ // By scaling by 10^3 in a locale that has words / compact notation
+ // based on powers of 10^3, we guarantee that the suppressed
+ // exponent will differ by 3.
+ assertDoubleEquals(
+ String.format("decimal %d, e operand for compact vs. compact scaled", input),
+ compactEOperand + 3,
+ compactScaledEOperand);
+ }
+ }
+
+ static boolean doubleEquals(double d1, double d2) {
+ return (Math.abs(d1 - d2) < 1e-6) || (Math.abs((d1 - d2) / d1) < 1e-6);
+ }
+
static void assertDoubleEquals(String message, double d1, double d2) {
- boolean equal = (Math.abs(d1 - d2) < 1e-6) || (Math.abs((d1 - d2) / d1) < 1e-6);
+ boolean equal = doubleEquals(d1, d2);
handleAssert(equal, message, d1, d2, null, false);
}
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java
index 4ba49dc..47000e6 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java
@@ -350,6 +350,15 @@
"1 millón");
assertFormatSingle(
+ "Compact Plural One with rounding",
+ "compact-long precision-integer",
+ "KK precision-integer",
+ NumberFormatter.with().notation(Notation.compactLong()).precision(Precision.integer()),
+ ULocale.forLanguageTag("es"),
+ 1222222,
+ "1 millón");
+
+ assertFormatSingle(
"Compact Plural Other",
"compact-long",
"KK",