ICU-13836 Represent suppressed exponent for better plural support

This commit is contained in:
Elango Cheran 2019-11-26 15:42:24 -08:00 committed by Elango
parent 6a0e9ce0cd
commit ee2d1e7b9b
12 changed files with 480 additions and 26 deletions

View file

@ -122,6 +122,26 @@ public interface DecimalQuantity extends PluralRules.IFixedDecimal {
*/
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.

View file

@ -86,6 +86,12 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
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 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
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;
}
@ -216,6 +223,16 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
}
}
@Override
public int getExponent() {
return exponent;
}
@Override
public void adjustExponent(int delta) {
exponent = exponent + delta;
}
@Override
public StandardPlural getStandardPlural(PluralRules rules) {
if (rules == null) {
@ -246,6 +263,8 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
return fractionCount();
case w:
return fractionCountWithoutTrailingZeros();
case e:
return getExponent();
default:
return Math.abs(toDouble());
}
@ -291,11 +310,11 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
}
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 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
/**
* 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 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
// 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 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
* 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 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
if (isZeroish()) {
return true;
}
if (scale < 0) {
if (exponent + scale < 0) {
return false;
}
int magnitude = getMagnitude();
@ -991,22 +1015,40 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
@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 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
}
}
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 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
/**
* 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();

View file

@ -180,6 +180,7 @@ public final class DecimalQuantity_DualStorageBCD extends DecimalQuantity_Abstra
isApproximate = false;
origDouble = 0;
origDelta = 0;
exponent = 0;
}
@Override
@ -254,11 +255,11 @@ public final class DecimalQuantity_DualStorageBCD extends DecimalQuantity_Abstra
}
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();

View file

@ -128,11 +128,12 @@ public class CompactNotation extends Notation {
// 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 @@ public class CompactNotation extends Notation {
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;

View file

@ -195,6 +195,11 @@ public class ScientificNotation extends Notation implements Cloneable {
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;

View file

@ -464,6 +464,16 @@ public class PluralRules implements Serializable {
@Deprecated
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.
*
@ -539,6 +549,8 @@ public class PluralRules implements Serializable {
private final int baseFactor;
final int suppressedExponent;
/**
* @internal CLDR
* @deprecated This API is ICU internal only.
@ -620,6 +632,15 @@ public class PluralRules implements Serializable {
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 @@ public class PluralRules implements Serializable {
? 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 @@ public class PluralRules implements Serializable {
case t: return decimalDigitsWithoutTrailingZeros;
case v: return visibleDecimalDigitCount;
case w: return visibleDecimalDigitCountWithoutTrailingZeros;
case e: return suppressedExponent;
default: return source;
}
}

View file

@ -95,6 +95,7 @@ public final class DecimalQuantity_64BitBCD extends DecimalQuantity_AbstractBCD
isApproximate = false;
origDouble = 0;
origDelta = 0;
exponent = 0;
}
@Override

View file

@ -112,6 +112,7 @@ public final class DecimalQuantity_ByteArrayBCD extends DecimalQuantity_Abstract
isApproximate = false;
origDouble = 0;
origDelta = 0;
exponent = 0;
}
@Override

View file

@ -95,6 +95,8 @@ public class DecimalQuantity_SimpleStorage implements DecimalQuantity {
1000000000000000000L
};
private int origPrimaryScale;
@Override
public int maxRepresentableDigits() {
return Integer.MAX_VALUE;
@ -110,6 +112,7 @@ public class DecimalQuantity_SimpleStorage implements DecimalQuantity {
primaryScale = 0;
primaryPrecision = computePrecision(primary);
fallback = null;
origPrimaryScale = primaryScale;
}
/**
@ -189,6 +192,8 @@ public class DecimalQuantity_SimpleStorage implements DecimalQuantity {
primary = -1;
fallback = new BigDecimal(temp);
}
origPrimaryScale = primaryScale;
}
static final double LOG_2_OF_TEN = 3.32192809489;
@ -279,6 +284,7 @@ public class DecimalQuantity_SimpleStorage implements DecimalQuantity {
primaryPrecision = _other.primaryPrecision;
fallback = _other.fallback;
flags = _other.flags;
origPrimaryScale = _other.origPrimaryScale;
}
@Override
@ -916,4 +922,14 @@ public class DecimalQuantity_SimpleStorage implements DecimalQuantity {
.setFractionDigits((int) getPluralOperand(Operand.v), (long) getPluralOperand(Operand.f));
}
}
@Override
public int getExponent() {
return origPrimaryScale;
}
@Override
public void adjustExponent(int delta) {
origPrimaryScale = origPrimaryScale + delta;
}
}

View file

@ -42,6 +42,7 @@ import com.ibm.icu.dev.util.CollectionUtilities;
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 @@ public class PluralRulesTest extends TestFmwk {
}
}
@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, "1000000", "many"},
{"compact-long", 1000000, "1 million", "many"},
{"", 1000001, "1000001", "other"},
{"compact-long", 1000001, "1 million", "many"},
{"", 120000, "1200000", "other"},
{"compact-long", 1200000, "1,2 millions", "many"},
{"", 1200001, "1200001", "other"},
{"compact-long", 1200001, "1,2 millions", "many"},
{"", 2000000, "2000000", "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;
/**

View file

@ -24,10 +24,15 @@ import com.ibm.icu.impl.number.DecimalFormatProperties;
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 @@ public class DecimalQuantityTest extends TestFmwk {
}
}
@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, "123456789", 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, "1234567", 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, "123456", 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, "1000", 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);
}

View file

@ -349,6 +349,15 @@ public class NumberFormatterApiTest {
1000000,
"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",