blob: f8d961d0e98764ea816413bbfe983d20fbfad04c [file] [log] [blame]
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
package com.ibm.icu.dev.test.number;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.math.RoundingMode;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import com.ibm.icu.dev.impl.number.DecimalQuantity_64BitBCD;
import com.ibm.icu.dev.impl.number.DecimalQuantity_ByteArrayBCD;
import com.ibm.icu.dev.impl.number.DecimalQuantity_SimpleStorage;
import com.ibm.icu.dev.test.TestFmwk;
import com.ibm.icu.impl.FormattedStringBuilder;
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)
public class DecimalQuantityTest extends TestFmwk {
@Ignore
@Test
public void testBehavior() throws ParseException {
// Make a list of several formatters to test the behavior of DecimalQuantity.
List<LocalizedNumberFormatter> formats = new ArrayList<>();
DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(ULocale.ENGLISH);
DecimalFormatProperties properties = new DecimalFormatProperties();
formats.add(
NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH));
properties = new DecimalFormatProperties().setMinimumSignificantDigits(3)
.setMaximumSignificantDigits(3).setCompactStyle(CompactStyle.LONG);
formats.add(
NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH));
properties = new DecimalFormatProperties().setMinimumExponentDigits(1).setMaximumIntegerDigits(3)
.setMaximumFractionDigits(1);
formats.add(
NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH));
properties = new DecimalFormatProperties().setRoundingIncrement(new BigDecimal("0.5"));
formats.add(
NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH));
String[] cases = {
"1.0",
"2.01",
"1234.56",
"3000.0",
"0.00026418",
"0.01789261",
"468160.0",
"999000.0",
"999900.0",
"999990.0",
"0.0",
"12345678901.0",
"-5193.48", };
String[] hardCases = {
"9999999999999900.0",
"789000000000000000000000.0",
"789123123567853156372158.0",
"987654321987654321987654321987654321987654311987654321.0", };
String[] doubleCases = {
"512.0000000000017",
"4095.9999999999977",
"4095.999999999998",
"4095.9999999999986",
"4095.999999999999",
"4095.9999999999995",
"4096.000000000001",
"4096.000000000002",
"4096.000000000003",
"4096.000000000004",
"4096.000000000005",
"4096.0000000000055",
"4096.000000000006",
"4096.000000000007", };
int i = 0;
for (String str : cases) {
testDecimalQuantity(i++, str, formats, 0);
}
i = 0;
for (String str : hardCases) {
testDecimalQuantity(i++, str, formats, 1);
}
i = 0;
for (String str : doubleCases) {
testDecimalQuantity(i++, str, formats, 2);
}
}
static void testDecimalQuantity(
int t,
String str,
List<LocalizedNumberFormatter> formats,
int mode) {
if (mode == 2) {
assertEquals("Double is not valid", Double.toString(Double.parseDouble(str)), str);
}
List<DecimalQuantity> qs = new ArrayList<>();
BigDecimal d = new BigDecimal(str);
qs.add(new DecimalQuantity_SimpleStorage(d));
if (mode == 0)
qs.add(new DecimalQuantity_64BitBCD(d));
qs.add(new DecimalQuantity_ByteArrayBCD(d));
qs.add(new DecimalQuantity_DualStorageBCD(d));
if (new BigDecimal(Double.toString(d.doubleValue())).compareTo(d) == 0) {
double dv = d.doubleValue();
qs.add(new DecimalQuantity_SimpleStorage(dv));
if (mode == 0)
qs.add(new DecimalQuantity_64BitBCD(dv));
qs.add(new DecimalQuantity_ByteArrayBCD(dv));
qs.add(new DecimalQuantity_DualStorageBCD(dv));
}
if (new BigDecimal(Long.toString(d.longValue())).compareTo(d) == 0) {
double lv = d.longValue();
qs.add(new DecimalQuantity_SimpleStorage(lv));
if (mode == 0)
qs.add(new DecimalQuantity_64BitBCD(lv));
qs.add(new DecimalQuantity_ByteArrayBCD(lv));
qs.add(new DecimalQuantity_DualStorageBCD(lv));
}
testDecimalQuantityExpectedOutput(qs.get(0), str);
if (qs.size() == 1) {
return;
}
for (int i = 1; i < qs.size(); i++) {
DecimalQuantity q0 = qs.get(0);
DecimalQuantity q1 = qs.get(i);
testDecimalQuantityExpectedOutput(q1, str);
testDecimalQuantityRounding(q0, q1);
testDecimalQuantityRoundingInterval(q0, q1);
testDecimalQuantityMath(q0, q1);
testDecimalQuantityWithFormats(q0, q1, formats);
}
}
private static void testDecimalQuantityExpectedOutput(DecimalQuantity rq, String expected) {
DecimalQuantity q0 = rq.createCopy();
// Force an accurate double
q0.roundToInfinity();
q0.setMinInteger(1);
q0.setMinFraction(1);
String actual = q0.toPlainString();
assertEquals("Unexpected output from simple string conversion (" + q0 + ")", expected, actual);
}
private static final MathContext MATH_CONTEXT_HALF_EVEN = new MathContext(0, RoundingMode.HALF_EVEN);
private static final MathContext MATH_CONTEXT_CEILING = new MathContext(0, RoundingMode.CEILING);
@SuppressWarnings("unused")
private static final MathContext MATH_CONTEXT_FLOOR = new MathContext(0, RoundingMode.FLOOR);
private static final MathContext MATH_CONTEXT_PRECISION = new MathContext(3, RoundingMode.HALF_UP);
private static void testDecimalQuantityRounding(DecimalQuantity rq0, DecimalQuantity rq1) {
DecimalQuantity q0 = rq0.createCopy();
DecimalQuantity q1 = rq1.createCopy();
q0.roundToMagnitude(-1, MATH_CONTEXT_HALF_EVEN);
q1.roundToMagnitude(-1, MATH_CONTEXT_HALF_EVEN);
testDecimalQuantityBehavior(q0, q1);
q0 = rq0.createCopy();
q1 = rq1.createCopy();
q0.roundToMagnitude(-1, MATH_CONTEXT_CEILING);
q1.roundToMagnitude(-1, MATH_CONTEXT_CEILING);
testDecimalQuantityBehavior(q0, q1);
q0 = rq0.createCopy();
q1 = rq1.createCopy();
q0.roundToMagnitude(-1, MATH_CONTEXT_PRECISION);
q1.roundToMagnitude(-1, MATH_CONTEXT_PRECISION);
testDecimalQuantityBehavior(q0, q1);
}
private static void testDecimalQuantityRoundingInterval(DecimalQuantity rq0, DecimalQuantity rq1) {
DecimalQuantity q0 = rq0.createCopy();
DecimalQuantity q1 = rq1.createCopy();
q0.roundToIncrement(new BigDecimal("0.05"), MATH_CONTEXT_HALF_EVEN);
q1.roundToIncrement(new BigDecimal("0.05"), MATH_CONTEXT_HALF_EVEN);
testDecimalQuantityBehavior(q0, q1);
q0 = rq0.createCopy();
q1 = rq1.createCopy();
q0.roundToIncrement(new BigDecimal("0.05"), MATH_CONTEXT_CEILING);
q1.roundToIncrement(new BigDecimal("0.05"), MATH_CONTEXT_CEILING);
testDecimalQuantityBehavior(q0, q1);
}
private static void testDecimalQuantityMath(DecimalQuantity rq0, DecimalQuantity rq1) {
DecimalQuantity q0 = rq0.createCopy();
DecimalQuantity q1 = rq1.createCopy();
q0.adjustMagnitude(-3);
q1.adjustMagnitude(-3);
testDecimalQuantityBehavior(q0, q1);
q0 = rq0.createCopy();
q1 = rq1.createCopy();
q0.multiplyBy(new BigDecimal("3.14159"));
q1.multiplyBy(new BigDecimal("3.14159"));
testDecimalQuantityBehavior(q0, q1);
}
private static void testDecimalQuantityWithFormats(
DecimalQuantity rq0,
DecimalQuantity rq1,
List<LocalizedNumberFormatter> formats) {
for (LocalizedNumberFormatter format : formats) {
DecimalQuantity q0 = rq0.createCopy();
DecimalQuantity q1 = rq1.createCopy();
FormattedStringBuilder nsb1 = new FormattedStringBuilder();
FormattedStringBuilder nsb2 = new FormattedStringBuilder();
format.formatImpl(q0, nsb1);
format.formatImpl(q1, nsb2);
String s1 = nsb1.toString();
String s2 = nsb2.toString();
assertEquals("Different output from formatter (" + q0 + ", " + q1 + ")", s1, s2);
}
}
private static void testDecimalQuantityBehavior(DecimalQuantity rq0, DecimalQuantity rq1) {
DecimalQuantity q0 = rq0.createCopy();
DecimalQuantity q1 = rq1.createCopy();
assertEquals("Different sign (" + q0 + ", " + q1 + ")", q0.isNegative(), q1.isNegative());
assertEquals("Different fingerprint (" + q0 + ", " + q1 + ")",
q0.getPositionFingerprint(),
q1.getPositionFingerprint());
assertDoubleEquals("Different double values (" + q0 + ", " + q1 + ")",
q0.toDouble(),
q1.toDouble());
assertBigDecimalEquals("Different BigDecimal values (" + q0 + ", " + q1 + ")",
q0.toBigDecimal(),
q1.toBigDecimal());
assertEquals("Different long values (" + q0 + ", " + q1 + ")",
q0.toLong(true),
q1.toLong(true));
q0.roundToInfinity();
q1.roundToInfinity();
assertEquals("Different lower display magnitude",
q0.getLowerDisplayMagnitude(),
q1.getLowerDisplayMagnitude());
assertEquals("Different upper display magnitude",
q0.getUpperDisplayMagnitude(),
q1.getUpperDisplayMagnitude());
for (int m = q0.getUpperDisplayMagnitude(); m >= q0.getLowerDisplayMagnitude(); m--) {
assertEquals("Different digit at magnitude " + m + " (" + q0 + ", " + q1 + ")",
q0.getDigit(m),
q1.getDigit(m));
}
if (rq0 instanceof DecimalQuantity_DualStorageBCD) {
String message = ((DecimalQuantity_DualStorageBCD) rq0).checkHealth();
if (message != null)
errln(message);
}
if (rq1 instanceof DecimalQuantity_DualStorageBCD) {
String message = ((DecimalQuantity_DualStorageBCD) rq1).checkHealth();
if (message != null)
errln(message);
}
}
@Test
public void testSwitchStorage() {
DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD();
fq.setToLong(1234123412341234L);
assertFalse("Should not be using byte array", fq.isUsingBytes());
assertEquals("Failed on initialize", "1.234123412341234E+15", fq.toScientificString());
assertNull("Failed health check", fq.checkHealth());
// Long -> Bytes
fq.appendDigit((byte) 5, 0, true);
assertTrue("Should be using byte array", fq.isUsingBytes());
assertEquals("Failed on multiply", "1.2341234123412345E+16", fq.toScientificString());
assertNull("Failed health check", fq.checkHealth());
// Bytes -> Long
fq.roundToMagnitude(5, MATH_CONTEXT_HALF_EVEN);
assertFalse("Should not be using byte array", fq.isUsingBytes());
assertEquals("Failed on round", "1.23412341234E+16", fq.toScientificString());
assertNull("Failed health check", fq.checkHealth());
// Bytes with popFromLeft
fq.setToBigDecimal(new BigDecimal("999999999999999999"));
assertToStringAndHealth(fq, "<DecimalQuantity 0:0 bytes 999999999999999999E0>");
fq.applyMaxInteger(17);
assertToStringAndHealth(fq, "<DecimalQuantity 0:0 bytes 99999999999999999E0>");
fq.applyMaxInteger(16);
assertToStringAndHealth(fq, "<DecimalQuantity 0:0 long 9999999999999999E0>");
fq.applyMaxInteger(15);
assertToStringAndHealth(fq, "<DecimalQuantity 0:0 long 999999999999999E0>");
}
@Test
public void testAppend() {
DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD();
fq.appendDigit((byte) 1, 0, true);
assertEquals("Failed on append", "1E+0", fq.toScientificString());
assertNull("Failed health check", fq.checkHealth());
fq.appendDigit((byte) 2, 0, true);
assertEquals("Failed on append", "1.2E+1", fq.toScientificString());
assertNull("Failed health check", fq.checkHealth());
fq.appendDigit((byte) 3, 1, true);
assertEquals("Failed on append", "1.203E+3", fq.toScientificString());
assertNull("Failed health check", fq.checkHealth());
fq.appendDigit((byte) 0, 1, true);
assertEquals("Failed on append", "1.203E+5", fq.toScientificString());
assertNull("Failed health check", fq.checkHealth());
fq.appendDigit((byte) 4, 0, true);
assertEquals("Failed on append", "1.203004E+6", fq.toScientificString());
assertNull("Failed health check", fq.checkHealth());
fq.appendDigit((byte) 0, 0, true);
assertEquals("Failed on append", "1.203004E+7", fq.toScientificString());
assertNull("Failed health check", fq.checkHealth());
fq.appendDigit((byte) 5, 0, false);
assertEquals("Failed on append", "1.20300405E+7", fq.toScientificString());
assertNull("Failed health check", fq.checkHealth());
fq.appendDigit((byte) 6, 0, false);
assertEquals("Failed on append", "1.203004056E+7", fq.toScientificString());
assertNull("Failed health check", fq.checkHealth());
fq.appendDigit((byte) 7, 3, false);
assertEquals("Failed on append", "1.2030040560007E+7", fq.toScientificString());
assertNull("Failed health check", fq.checkHealth());
StringBuilder baseExpected = new StringBuilder("1.2030040560007");
for (int i = 0; i < 10; i++) {
fq.appendDigit((byte) 8, 0, false);
baseExpected.append('8');
StringBuilder expected = new StringBuilder(baseExpected);
expected.append("E+7");
assertEquals("Failed on append", expected.toString(), fq.toScientificString());
assertNull("Failed health check", fq.checkHealth());
}
fq.appendDigit((byte) 9, 2, false);
baseExpected.append("009");
StringBuilder expected = new StringBuilder(baseExpected);
expected.append("E+7");
assertEquals("Failed on append", expected.toString(), fq.toScientificString());
assertNull("Failed health check", fq.checkHealth());
}
@Test
public void testUseApproximateDoubleWhenAble() {
Object[][] cases = {
{ 1.2345678, 1, MATH_CONTEXT_HALF_EVEN, false },
{ 1.2345678, 7, MATH_CONTEXT_HALF_EVEN, false },
{ 1.2345678, 12, MATH_CONTEXT_HALF_EVEN, false },
{ 1.2345678, 13, MATH_CONTEXT_HALF_EVEN, true },
{ 1.235, 1, MATH_CONTEXT_HALF_EVEN, false },
{ 1.235, 2, MATH_CONTEXT_HALF_EVEN, true },
{ 1.235, 3, MATH_CONTEXT_HALF_EVEN, false },
{ 1.000000000000001, 0, MATH_CONTEXT_HALF_EVEN, false },
{ 1.000000000000001, 0, MATH_CONTEXT_CEILING, true },
{ 1.235, 1, MATH_CONTEXT_CEILING, false },
{ 1.235, 2, MATH_CONTEXT_CEILING, false },
{ 1.235, 3, MATH_CONTEXT_CEILING, true } };
for (Object[] cas : cases) {
double d = (Double) cas[0];
int maxFrac = (Integer) cas[1];
MathContext mc = (MathContext) cas[2];
boolean usesExact = (Boolean) cas[3];
DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD(d);
assertTrue("Should be using approximate double", !fq.explicitExactDouble);
fq.roundToMagnitude(-maxFrac, mc);
assertEquals(
"Using approximate double after rounding: " + d + " maxFrac=" + maxFrac + " " + mc,
usesExact,
fq.explicitExactDouble);
}
}
@Test
public void testDecimalQuantityBehaviorStandalone() {
DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD();
assertToStringAndHealth(fq, "<DecimalQuantity 0:0 long 0E0>");
fq.setToInt(51423);
assertToStringAndHealth(fq, "<DecimalQuantity 0:0 long 51423E0>");
fq.adjustMagnitude(-3);
assertToStringAndHealth(fq, "<DecimalQuantity 0:0 long 51423E-3>");
fq.setToLong(90909090909000L);
assertToStringAndHealth(fq, "<DecimalQuantity 0:0 long 90909090909E3>");
fq.setMinInteger(2);
fq.applyMaxInteger(5);
assertToStringAndHealth(fq, "<DecimalQuantity 2:0 long 9E3>");
fq.setMinFraction(3);
assertToStringAndHealth(fq, "<DecimalQuantity 2:-3 long 9E3>");
fq.setToDouble(987.654321);
assertToStringAndHealth(fq, "<DecimalQuantity 2:-3 long 987654321E-6>");
fq.roundToInfinity();
assertToStringAndHealth(fq, "<DecimalQuantity 2:-3 long 987654321E-6>");
fq.roundToIncrement(new BigDecimal("0.005"), MATH_CONTEXT_HALF_EVEN);
assertToStringAndHealth(fq, "<DecimalQuantity 2:-3 long 987655E-3>");
fq.roundToMagnitude(-2, MATH_CONTEXT_HALF_EVEN);
assertToStringAndHealth(fq, "<DecimalQuantity 2:-3 long 98766E-2>");
}
@Test
public void testFitsInLong() {
DecimalQuantity_DualStorageBCD quantity = new DecimalQuantity_DualStorageBCD();
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.setToBigInteger(new BigInteger("9223372036854775808"));
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"));
assertFalse("A large 10^19 number with trailing zeros should not fit", quantity.fitsInLong());
quantity.setToBigInteger(new BigInteger("10000000000000000000"));
assertFalse("10^20 should not fit", quantity.fitsInLong());
}
@Test
public void testHardDoubleConversion() {
// This test is somewhat duplicated from previous tests, but it is needed
// for ICU4C compatibility.
Object[][] cases = {
{ 512.0000000000017, "512.0000000000017" },
{ 4095.9999999999977, "4095.9999999999977" },
{ 4095.999999999998, "4095.999999999998" },
{ 4095.9999999999986, "4095.9999999999986" },
{ 4095.999999999999, "4095.999999999999" },
{ 4095.9999999999995, "4095.9999999999995" },
{ 4096.000000000001, "4096.000000000001" },
{ 4096.000000000002, "4096.000000000002" },
{ 4096.000000000003, "4096.000000000003" },
{ 4096.000000000004, "4096.000000000004" },
{ 4096.000000000005, "4096.000000000005" },
{ 4096.0000000000055, "4096.0000000000055" },
{ 4096.000000000006, "4096.000000000006" },
{ 4096.000000000007, "4096.000000000007" } };
for (Object[] cas : cases) {
double input = (Double) cas[0];
String expectedOutput = (String) cas[1];
DecimalQuantity q = new DecimalQuantity_DualStorageBCD(input);
q.roundToInfinity();
String actualOutput = q.toPlainString();
assertEquals("", expectedOutput, actualOutput);
}
}
@Test
public void testToDouble() {
Object[][] cases = new Object[][] {
{ "0", 0.0 },
{ "514.23", 514.23 },
{ "-3.142E-271", -3.142e-271 }
};
for (Object[] cas : cases) {
String input = (String) cas[0];
double expected = (Double) cas[1];
DecimalQuantity q = new DecimalQuantity_DualStorageBCD();
q.setToBigDecimal(new BigDecimal(input));
double actual = q.toDouble();
assertEquals("Doubles should exactly equal", expected, actual);
}
}
@Test
public void testMaxDigits() {
DecimalQuantity_DualStorageBCD dq = new DecimalQuantity_DualStorageBCD(876.543);
dq.roundToInfinity();
dq.setMinInteger(0);
dq.applyMaxInteger(2);
dq.setMinFraction(0);
dq.roundToMagnitude(-2, RoundingUtils.mathContextUnlimited(RoundingMode.FLOOR));
assertEquals("Should trim, toPlainString", "76.54", dq.toPlainString());
assertEquals("Should trim, toScientificString", "7.654E+1", dq.toScientificString());
assertEquals("Should trim, toLong", 76, dq.toLong(true));
assertEquals("Should trim, toFractionLong", 54, dq.toFractionLong(false));
assertEquals("Should trim, toDouble", 76.54, dq.toDouble());
assertEquals("Should trim, toBigDecimal", new BigDecimal("76.54"), dq.toBigDecimal());
}
@Test
public void testNickelRounding() {
Object[][] cases = new Object[][] {
{1.000, -2, RoundingMode.HALF_EVEN, "1"},
{1.001, -2, RoundingMode.HALF_EVEN, "1"},
{1.010, -2, RoundingMode.HALF_EVEN, "1"},
{1.020, -2, RoundingMode.HALF_EVEN, "1"},
{1.024, -2, RoundingMode.HALF_EVEN, "1"},
{1.025, -2, RoundingMode.HALF_EVEN, "1"},
{1.025, -2, RoundingMode.HALF_DOWN, "1"},
{1.025, -2, RoundingMode.HALF_UP, "1.05"},
{1.026, -2, RoundingMode.HALF_EVEN, "1.05"},
{1.030, -2, RoundingMode.HALF_EVEN, "1.05"},
{1.040, -2, RoundingMode.HALF_EVEN, "1.05"},
{1.050, -2, RoundingMode.HALF_EVEN, "1.05"},
{1.060, -2, RoundingMode.HALF_EVEN, "1.05"},
{1.070, -2, RoundingMode.HALF_EVEN, "1.05"},
{1.074, -2, RoundingMode.HALF_EVEN, "1.05"},
{1.075, -2, RoundingMode.HALF_DOWN, "1.05"},
{1.075, -2, RoundingMode.HALF_UP, "1.1"},
{1.075, -2, RoundingMode.HALF_EVEN, "1.1"},
{1.076, -2, RoundingMode.HALF_EVEN, "1.1"},
{1.080, -2, RoundingMode.HALF_EVEN, "1.1"},
{1.090, -2, RoundingMode.HALF_EVEN, "1.1"},
{1.099, -2, RoundingMode.HALF_EVEN, "1.1"},
{1.999, -2, RoundingMode.HALF_EVEN, "2"},
{2.25, -1, RoundingMode.HALF_EVEN, "2"},
{2.25, -1, RoundingMode.HALF_UP, "2.5"},
{2.75, -1, RoundingMode.HALF_DOWN, "2.5"},
{2.75, -1, RoundingMode.HALF_EVEN, "3"},
{3.00, -1, RoundingMode.CEILING, "3"},
{3.25, -1, RoundingMode.CEILING, "3.5"},
{3.50, -1, RoundingMode.CEILING, "3.5"},
{3.75, -1, RoundingMode.CEILING, "4"},
{4.00, -1, RoundingMode.FLOOR, "4"},
{4.25, -1, RoundingMode.FLOOR, "4"},
{4.50, -1, RoundingMode.FLOOR, "4.5"},
{4.75, -1, RoundingMode.FLOOR, "4.5"},
{5.00, -1, RoundingMode.UP, "5"},
{5.25, -1, RoundingMode.UP, "5.5"},
{5.50, -1, RoundingMode.UP, "5.5"},
{5.75, -1, RoundingMode.UP, "6"},
{6.00, -1, RoundingMode.DOWN, "6"},
{6.25, -1, RoundingMode.DOWN, "6"},
{6.50, -1, RoundingMode.DOWN, "6.5"},
{6.75, -1, RoundingMode.DOWN, "6.5"},
{7.00, -1, RoundingMode.UNNECESSARY, "7"},
{7.50, -1, RoundingMode.UNNECESSARY, "7.5"},
};
for (Object[] cas : cases) {
double input = (Double) cas[0];
int magnitude = (Integer) cas[1];
RoundingMode roundingMode = (RoundingMode) cas[2];
String expected = (String) cas[3];
String message = input + " @ " + magnitude + " / " + roundingMode;
for (int i=0; i<2; i++) {
DecimalQuantity dq;
if (i == 0) {
dq = new DecimalQuantity_DualStorageBCD(input);
} else {
dq = new DecimalQuantity_SimpleStorage(input);
}
dq.roundToNickel(magnitude, RoundingUtils.mathContextUnlimited(roundingMode));
String actual = dq.toPlainString();
assertEquals(message, expected, actual);
}
}
try {
DecimalQuantity_DualStorageBCD dq = new DecimalQuantity_DualStorageBCD(7.1);
dq.roundToNickel(-1, RoundingUtils.mathContextUnlimited(RoundingMode.UNNECESSARY));
fail("Expected ArithmeticException");
} catch (ArithmeticException expected) {
// pass
}
}
@Test
public void testScientificAndCompactSuppressedExponent() {
ULocale locale = new ULocale("fr-FR");
Object[][] casesData = {
// unlocalized formatter skeleton, input, string output, long output,
// double output, BigDecimal output, plain string,
// suppressed scientific exponent, suppressed compact exponent
{"", 123456789, "123 456 789", 123456789L, 123456789.0, new BigDecimal("123456789"), "123456789", 0, 0},
{"compact-long", 123456789, "123 millions", 123000000L, 123000000.0, new BigDecimal("123000000"), "123000000", 6, 6},
{"compact-short", 123456789, "123 M", 123000000L, 123000000.0, new BigDecimal("123000000"), "123000000", 6, 6},
{"scientific", 123456789, "1,234568E8", 123456800L, 123456800.0, new BigDecimal("123456800"), "123456800", 8, 8},
{"", 1234567, "1 234 567", 1234567L, 1234567.0, new BigDecimal("1234567"), "1234567", 0, 0},
{"compact-long", 1234567, "1,2 million", 1200000L, 1200000.0, new BigDecimal("1200000"), "1200000", 6, 6},
{"compact-short", 1234567, "1,2 M", 1200000L, 1200000.0, new BigDecimal("1200000"), "1200000", 6, 6},
{"scientific", 1234567, "1,234567E6", 1234567L, 1234567.0, new BigDecimal("1234567"), "1234567", 6, 6},
{"", 123456, "123 456", 123456L, 123456.0, new BigDecimal("123456"), "123456", 0, 0},
{"compact-long", 123456, "123 mille", 123000L, 123000.0, new BigDecimal("123000"), "123000", 3, 3},
{"compact-short", 123456, "123 k", 123000L, 123000.0, new BigDecimal("123000"), "123000", 3, 3},
{"scientific", 123456, "1,23456E5", 123456L, 123456.0, new BigDecimal("123456"), "123456", 5, 5},
{"", 123, "123", 123L, 123.0, new BigDecimal("123"), "123", 0, 0},
{"compact-long", 123, "123", 123L, 123.0, new BigDecimal("123"), "123", 0, 0},
{"compact-short", 123, "123", 123L, 123.0, new BigDecimal("123"), "123", 0, 0},
{"scientific", 123, "1,23E2", 123L, 123.0, new BigDecimal("123"), "123", 2, 2},
{"", 1.2, "1,2", 1L, 1.2, new BigDecimal("1.2"), "1.2", 0, 0},
{"compact-long", 1.2, "1,2", 1L, 1.2, new BigDecimal("1.2"), "1.2", 0, 0},
{"compact-short", 1.2, "1,2", 1L, 1.2, new BigDecimal("1.2"), "1.2", 0, 0},
{"scientific", 1.2, "1,2E0", 1L, 1.2, new BigDecimal("1.2"), "1.2", 0, 0},
{"", 0.12, "0,12", 0L, 0.12, new BigDecimal("0.12"), "0.12", 0, 0},
{"compact-long", 0.12, "0,12", 0L, 0.12, new BigDecimal("0.12"), "0.12", 0, 0},
{"compact-short", 0.12, "0,12", 0L, 0.12, new BigDecimal("0.12"), "0.12", 0, 0},
{"scientific", 0.12, "1,2E-1", 0L, 0.12, new BigDecimal("0.12"), "0.12", -1, -1},
{"", 0.012, "0,012", 0L, 0.012, new BigDecimal("0.012"), "0.012", 0, 0},
{"compact-long", 0.012, "0,012", 0L, 0.012, new BigDecimal("0.012"), "0.012", 0, 0},
{"compact-short", 0.012, "0,012", 0L, 0.012, new BigDecimal("0.012"), "0.012", 0, 0},
{"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, "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, "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},
};
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 expectedSuppressedScientificExponent = (int) caseDatum[7];
int expectedSuppressedCompactExponent = (int) caseDatum[8];
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 actualSuppressedScientificExponent = dq.getExponent();
int actualSuppressedCompactExponent = dq.getExponent();
assertEquals(
String.format("formatted number %s toString: %f", skeleton, input),
expectedString,
actualString);
assertEquals(
String.format("formatted number %s toLong: %f", skeleton, input),
expectedLong,
actualLong);
assertDoubleEquals(
String.format("formatted number %s toDouble: %f", skeleton, input),
expectedDouble,
actualDouble);
assertBigDecimalEquals(
String.format("formatted number %s toBigDecimal: %f", skeleton, input),
expectedBigDecimal,
actualBigDecimal);
assertEquals(
String.format("formatted number %s toPlainString: %f", skeleton, input),
expectedPlainString,
actualPlainString);
assertEquals(
String.format("formatted number %s suppressed scientific exponent: %f", skeleton, input),
expectedSuppressedScientificExponent,
actualSuppressedScientificExponent);
assertEquals(
String.format("formatted number %s suppressed compact exponent: %f", skeleton, input),
expectedSuppressedCompactExponent,
actualSuppressedCompactExponent);
// test the actual computed values of the plural operands
double expectedNOperand = expectedDouble;
double expectedIOperand = expectedLong;
double expectedEOperand = expectedSuppressedScientificExponent;
double expectedCOperand = expectedSuppressedCompactExponent;
double actualNOperand = dq.getPluralOperand(Operand.n);
double actualIOperand = dq.getPluralOperand(Operand.i);
double actualEOperand = dq.getPluralOperand(Operand.e);
double actualCOperand = dq.getPluralOperand(Operand.c);
assertEquals(
String.format("formatted number %s toString: %s", skeleton, input),
expectedString,
actualString);
assertDoubleEquals(
String.format("formatted number %s n operand: %f", skeleton, input),
expectedNOperand,
actualNOperand);
assertDoubleEquals(
String.format("formatted number %s i operand: %f", skeleton, input),
expectedIOperand,
actualIOperand);
assertDoubleEquals(
String.format("formatted number %s e operand: %f", skeleton, input),
expectedEOperand,
actualEOperand);
assertDoubleEquals(
String.format("formatted number %s c operand: %f", skeleton, input),
expectedCOperand,
actualCOperand);
}
}
@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;
double expectedCOperand = 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);
double actualCOperand = dq.getPluralOperand(Operand.c);
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);
assertDoubleEquals(
String.format("compact decimal fraction c operand: %f", inputVal),
expectedCOperand,
actualCOperand);
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,
// compact c operand
{123456789, "123 millions", 123000000.0, 123000000.0, 6.0, 6.0},
{1234567, "1,2 million", 1200000.0, 1200000.0, 6.0, 6.0},
{123456, "123 mille", 123000.0, 123000.0, 3.0, 3.0},
{123, "123", 123.0, 123.0, 0.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];
double expectedCOperand = (double) caseDatum[5];
FormattedNumber fnCompactScaled = compactScaled.format(input);
DecimalQuantity_DualStorageBCD dqCompactScaled =
(DecimalQuantity_DualStorageBCD) fnCompactScaled.getFixedDecimal();
double compactScaledCOperand = dqCompactScaled.getPluralOperand(Operand.c);
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);
double compactCOperand = dqCompact.getPluralOperand(Operand.c);
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);
assertDoubleEquals(
String.format("compact decimal %d, c operand vs. expected", input),
expectedCOperand,
compactCOperand);
// 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, c operand for compact vs. compact scaled", input),
compactCOperand + 3,
compactScaledCOperand);
}
}
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 = doubleEquals(d1, d2);
handleAssert(equal, message, d1, d2, null, false);
}
static void assertBigDecimalEquals(String message, String d1, BigDecimal d2) {
assertBigDecimalEquals(message, new BigDecimal(d1), d2);
}
static void assertBigDecimalEquals(String message, BigDecimal d1, BigDecimal d2) {
boolean equal = d1.compareTo(d2) == 0;
handleAssert(equal, message, d1, d2, null, false);
}
static void assertToStringAndHealth(DecimalQuantity_DualStorageBCD fq, String expected) {
String actual = fq.toString();
assertEquals("DecimalQuantity toString", expected, actual);
String health = fq.checkHealth();
assertNull("DecimalQuantity health", health);
}
}