mirror of
https://github.com/unicode-org/icu.git
synced 2025-04-14 09:21:03 +00:00
ICU-13836 Represent suppressed exponent for better plural support
This commit is contained in:
parent
6a0e9ce0cd
commit
ee2d1e7b9b
12 changed files with 480 additions and 26 deletions
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -95,6 +95,7 @@ public final class DecimalQuantity_64BitBCD extends DecimalQuantity_AbstractBCD
|
|||
isApproximate = false;
|
||||
origDouble = 0;
|
||||
origDelta = 0;
|
||||
exponent = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -112,6 +112,7 @@ public final class DecimalQuantity_ByteArrayBCD extends DecimalQuantity_Abstract
|
|||
isApproximate = false;
|
||||
origDouble = 0;
|
||||
origDelta = 0;
|
||||
exponent = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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, "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;
|
||||
/**
|
||||
|
|
|
@ -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, "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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Add table
Reference in a new issue