| // © 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()); |
| |
| 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()); |
| quantity.setToBigInteger(new BigInteger("9223372036854775808")); |
| assertFalse("One greater than max long long should not fit", quantity.fitsInLong()); |
| 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 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 = 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); |
| } |
| } |