summaryrefslogtreecommitdiffstats
path: root/harmony-tests
diff options
context:
space:
mode:
authorNeil Fuller <nfuller@google.com>2014-09-26 11:55:58 +0100
committerNeil Fuller <nfuller@google.com>2014-09-26 16:53:34 +0100
commit4e92b6265acb1d12109e311819dd64b27ec85df5 (patch)
tree07147102983f6cb93efe13b7914420a4a0ca2c0a /harmony-tests
parent37fcc48b63f95775d0304bf0bd3b9d7ff30e2232 (diff)
downloadlibcore-4e92b6265acb1d12109e311819dd64b27ec85df5.zip
libcore-4e92b6265acb1d12109e311819dd64b27ec85df5.tar.gz
libcore-4e92b6265acb1d12109e311819dd64b27ec85df5.tar.bz2
Rewrite tests and add tests that demonstrate a bug
The bug: DecimalFormat.format() behavior is slightly lossy around 15 decimal digits even without any digit constraints. This change isolates the test failures that result from this bug to 2 test cases: test_formatDouble_bug17656132 test_formatDouble_roundingProblemCases Example of the bug: Double: 999999999999999.9 is represented as an IEEE 754 value which is exactly decimal 999999999999999.875 When format(999999999999999.9) is called on a DecimalFormat with large MaxIntegerDigit and MaxFractionDigit.... Correct answer: "999999999999999.9" Actual answer: "1000000000000000" By contrast Double.toString() prints 9.999999999999999E14 for Android and the RI (correctly). The DecimalFormat is printing to 16 decimal digits: The inclusion of the 16th digit implies slightly more precision than IEEE 754 provides (~15.9 decimal digits for most of the representable range). However, the use of 16 decimal digits for outputting IEEE 754 appears consistent with Double.toString() and elsewhere. Before printing, DecimalFormat appears to be rounding to 15 decimal digits internally (or something similar). Parsing "1000000000000000" produces a different double representation than the original double (one that is closer to 1000000000000000 than 999999999999999.9). This is the bug - we just lost information. We should be able to round-trip a double if there is no rounding since every double is representable with decimal and we have sufficient digits available to represent it (close enough) in decimal. Additional tests have been added to demonstrate the bug and also demonstrate the (correct) formatting behavior when the formatter is rounding. test_formatDouble_maxFractionDigits: rounding at 1, 10, 14 digits after the dp. test_formatDouble_roundingTo15Digits: rounding at 15 total digits overall test_formatDouble_bug17656132: Demonstrates the bug concisely. The test changes: test_formatDouble_wideRange(): implicitly assumed that the any loss of accuracy when a decimal string was turned into a double would be undone when format() was called, and it would always arrive back at the original string. The test has been re-written here to use BigDecimal rather than Double.parseDouble(), and to compare two doubles rather than original string with the output from format(). The test was previously failing with the RI for 1E23: the closest representable double to 1E23 is exactly 99999999999999991611392. The value produces "99999999999999990000000" when formatted with the RI and not "100000000000000000000000". On Android it was passing for 1E23 because of bug 17656132 rounding back to the original decimal value. This test was previously failing on Android with 1E-309 because below 1E-308 IEEE 754 starts losing precision and the closest representable value is not close to the original string. The test now isn't affected if the double being tested is not close to the original decimal; it passes providing the can be round tripped. test_formatDouble_roundingProblemCases: Re-written like the _wideRange test but continues to demonstrate the bug due to the test values (intentionally) chosen. Bug: 17656132 Change-Id: I7d81e38bd1f9dbfd1e1b2caa60a6bb16b871b925
Diffstat (limited to 'harmony-tests')
-rw-r--r--harmony-tests/src/test/java/org/apache/harmony/tests/java/text/DecimalFormatTest.java279
1 files changed, 221 insertions, 58 deletions
diff --git a/harmony-tests/src/test/java/org/apache/harmony/tests/java/text/DecimalFormatTest.java b/harmony-tests/src/test/java/org/apache/harmony/tests/java/text/DecimalFormatTest.java
index ad96256..e0e04e8 100644
--- a/harmony-tests/src/test/java/org/apache/harmony/tests/java/text/DecimalFormatTest.java
+++ b/harmony-tests/src/test/java/org/apache/harmony/tests/java/text/DecimalFormatTest.java
@@ -1465,78 +1465,241 @@ public class DecimalFormatTest extends TestCase {
formatTester.throwFailures();
}
- public void test_formatDouble_wideRange() {
+ // Demonstrates that fraction digit rounding occurs as expected with 1, 10 and 14 fraction
+ // digits, using numbers that are well within the precision of IEEE 754.
+ public void test_formatDouble_maxFractionDigits() {
final DecimalFormatSymbols dfs = new DecimalFormatSymbols(Locale.US);
DecimalFormat format = new DecimalFormat("#0.#", dfs);
+ format.setGroupingUsed(false);
format.setMaximumIntegerDigits(400);
- format.setMaximumFractionDigits(400);
-
- for (int i = 0; i < 309; i++) {
- String tval = "1";
- for (int j = 0; j < i; j++) {
- tval += "0";
- }
- double d = Double.parseDouble(tval);
- String result = format.format(d);
- assertEquals(i + ") e:" + tval + " r:" + result, tval, result);
- }
- for (int i = 0; i < 322; i++) {
- String tval = "0.";
- for (int j = 0; j < i; j++) {
- tval += "0";
- }
- tval += "1";
- double d = Double.parseDouble(tval);
- String result = format.format(d);
- assertEquals(i + ") e:" + tval + " r:" + result, tval, result);
+ format.setMaximumFractionDigits(1);
+
+ assertEquals("1", format.format(0.99));
+ assertEquals("1", format.format(0.95));
+ assertEquals("0.9", format.format(0.94));
+ assertEquals("0.9", format.format(0.90));
+
+ assertEquals("0.2", format.format(0.19));
+ assertEquals("0.2", format.format(0.15));
+ assertEquals("0.1", format.format(0.14));
+ assertEquals("0.1", format.format(0.10));
+
+ format.setMaximumFractionDigits(10);
+ assertEquals("1", format.format(0.99999999999));
+ assertEquals("1", format.format(0.99999999995));
+ assertEquals("0.9999999999", format.format(0.99999999994));
+ assertEquals("0.9999999999", format.format(0.99999999990));
+
+ assertEquals("0.1111111112", format.format(0.11111111119));
+ assertEquals("0.1111111112", format.format(0.11111111115));
+ assertEquals("0.1111111111", format.format(0.11111111114));
+ assertEquals("0.1111111111", format.format(0.11111111110));
+
+ format.setMaximumFractionDigits(14);
+ assertEquals("1", format.format(0.999999999999999));
+ assertEquals("1", format.format(0.999999999999995));
+ assertEquals("0.99999999999999", format.format(0.999999999999994));
+ assertEquals("0.99999999999999", format.format(0.999999999999990));
+
+ assertEquals("0.11111111111112", format.format(0.111111111111119));
+ assertEquals("0.11111111111112", format.format(0.111111111111115));
+ assertEquals("0.11111111111111", format.format(0.111111111111114));
+ assertEquals("0.11111111111111", format.format(0.111111111111110));
+ }
+
+ // This demonstrates rounding at the 15th decimal digit. By setting the maximum fraction digits
+ // we force rounding at a point just below the full IEEE 754 precision. IEEE 754 should be
+ // precise to just above 15 decimal digits.
+ // df.format() with no limits always emits up to 16 decimal digits, slightly above what IEEE 754
+ // can store precisely.
+ public void test_formatDouble_roundingTo15Digits() throws Exception {
+ final DecimalFormatSymbols dfs = new DecimalFormatSymbols(Locale.US);
+ DecimalFormat df = new DecimalFormat("#.#", dfs);
+ df.setMaximumIntegerDigits(400);
+ df.setGroupingUsed(false);
+
+ df.setMaximumFractionDigits(0);
+ assertEquals("1000000000000000", df.format(999999999999999.9));
+ df.setMaximumFractionDigits(1);
+ assertEquals("100000000000000", df.format(99999999999999.99));
+ df.setMaximumFractionDigits(2);
+ assertEquals("10000000000000", df.format(9999999999999.999));
+ df.setMaximumFractionDigits(3);
+ assertEquals("1000000000000", df.format(999999999999.9999));
+ df.setMaximumFractionDigits(4);
+ assertEquals("100000000000", df.format(99999999999.99999));
+ df.setMaximumFractionDigits(5);
+ assertEquals("10000000000", df.format(9999999999.999999));
+ df.setMaximumFractionDigits(6);
+ assertEquals("1000000000", df.format(999999999.9999999));
+ df.setMaximumFractionDigits(7);
+ assertEquals("100000000", df.format(99999999.99999999));
+ df.setMaximumFractionDigits(8);
+ assertEquals("10000000", df.format(9999999.999999999));
+ df.setMaximumFractionDigits(9);
+ assertEquals("1000000", df.format(999999.9999999999));
+ df.setMaximumFractionDigits(10);
+ assertEquals("100000", df.format(99999.99999999999));
+ df.setMaximumFractionDigits(11);
+ assertEquals("10000", df.format(9999.999999999999));
+ df.setMaximumFractionDigits(12);
+ assertEquals("1000", df.format(999.9999999999999));
+ df.setMaximumFractionDigits(13);
+ assertEquals("100", df.format(99.99999999999999));
+ df.setMaximumFractionDigits(14);
+ assertEquals("10", df.format(9.999999999999999));
+ df.setMaximumFractionDigits(15);
+ assertEquals("1", df.format(0.9999999999999999));
+ }
+
+ // This checks formatting over most of the representable IEEE 754 range using a formatter that
+ // should be performing no lossy rounding.
+ // It checks that the formatted double can be parsed to get the original double.
+ // IEEE 754 can represent values from (decimal) ~2.22507E−308 to ~1.79769E308 to full precision.
+ // Close to zero it can go down to 4.94066E-324 with a loss of precision.
+ // At the extremes of the double range this test will not be testing doubles that exactly
+ // represent powers of 10. The test is only interested in whether the doubles closest
+ // to powers of 10 that can be represented can each be turned into a string and read back again
+ // to arrive at the original double.
+ public void test_formatDouble_wideRange() throws Exception {
+ for (int i = -324; i < 309; i++) {
+ // Generate a decimal number we are interested in: 1 * 10^i
+ String stringForm = "1e" + i;
+ BigDecimal bigDecimal = new BigDecimal(stringForm);
+
+ // Obtain the nearest double representation of the decimal number.
+ // This is lossy because going from BigDecimal -> double is inexact, but we should
+ // arrive at a number that is as close as possible to the decimal value. We assume that
+ // BigDecimal is capable of this, but it is not critical to this test if it gets it a
+ // little wrong, though it may mean we are testing a double value different from the one
+ // we thought we were.
+ double d = bigDecimal.doubleValue();
+
+ assertDecimalFormatIsLossless(d);
}
}
- public void test_formatDouble_varyingScaleProblemCases() throws Exception {
+ // This test is a regression test for http://b/17656132.
+ // It checks hand-picked values can be formatted and parsed to get the original double.
+ // The formatter as configured should perform no rounding.
+ public void test_formatDouble_roundingProblemCases() throws Exception {
+ // Most of the double literals below are not exactly representable in IEEE 754 but
+ // it should not matter to this test.
+ assertDecimalFormatIsLossless(999999999999999.9);
+ assertDecimalFormatIsLossless(99999999999999.99);
+ assertDecimalFormatIsLossless(9999999999999.999);
+ assertDecimalFormatIsLossless(999999999999.9999);
+ assertDecimalFormatIsLossless(99999999999.99999);
+ assertDecimalFormatIsLossless(9999999999.999999);
+ assertDecimalFormatIsLossless(999999999.9999999);
+ assertDecimalFormatIsLossless(99999999.99999999);
+ assertDecimalFormatIsLossless(9999999.999999999);
+ assertDecimalFormatIsLossless(999999.9999999999);
+ assertDecimalFormatIsLossless(99999.99999999999);
+ assertDecimalFormatIsLossless(9999.999999999999);
+ assertDecimalFormatIsLossless(999.9999999999999);
+ assertDecimalFormatIsLossless(99.99999999999999);
+ assertDecimalFormatIsLossless(9.999999999999999);
+ assertDecimalFormatIsLossless(0.9999999999999999);
+ }
+
+ // This test checks hand-picked values can be formatted and parsed to get the original double.
+ // The formatter as configured should perform no rounding.
+ // These numbers are not affected by http://b/17656132.
+ public void test_formatDouble_varyingScale() throws Exception {
+ // Most of the double literals below are not exactly representable in IEEE 754 but
+ // it should not matter to this test.
+
+ assertDecimalFormatIsLossless(999999999999999.);
+
+ assertDecimalFormatIsLossless(123456789012345.);
+ assertDecimalFormatIsLossless(12345678901234.5);
+ assertDecimalFormatIsLossless(1234567890123.25);
+ assertDecimalFormatIsLossless(999999999999.375);
+ assertDecimalFormatIsLossless(99999999999.0625);
+ assertDecimalFormatIsLossless(9999999999.03125);
+ assertDecimalFormatIsLossless(999999999.015625);
+ assertDecimalFormatIsLossless(99999999.0078125);
+ assertDecimalFormatIsLossless(9999999.00390625);
+ assertDecimalFormatIsLossless(999999.001953125);
+ assertDecimalFormatIsLossless(9999.00048828125);
+ assertDecimalFormatIsLossless(999.000244140625);
+ assertDecimalFormatIsLossless(99.0001220703125);
+ assertDecimalFormatIsLossless(9.00006103515625);
+ assertDecimalFormatIsLossless(0.000030517578125);
+ }
+
+ private static void assertDecimalFormatIsLossless(double d) throws Exception {
final DecimalFormatSymbols dfs = new DecimalFormatSymbols(Locale.US);
DecimalFormat format = new DecimalFormat("#0.#", dfs);
+ format.setGroupingUsed(false);
format.setMaximumIntegerDigits(400);
format.setMaximumFractionDigits(400);
- assertEquals("999999999999999", format.format(999999999999999.));
- assertEquals("999999999999999.9", format.format(999999999999999.9));
- assertEquals("99999999999999.98", format.format(99999999999999.99));
- assertEquals("9999999999999.998", format.format(9999999999999.999));
- assertEquals("999999999999.9999", format.format(999999999999.9999));
- assertEquals("99999999999.99998", format.format(99999999999.99999));
- assertEquals("9999999999.999998", format.format(9999999999.999999));
- assertEquals("999999999.9999999", format.format(999999999.9999999));
- assertEquals("99999999.99999999", format.format(99999999.99999999));
- assertEquals("9999999.999999998", format.format(9999999.999999999));
- assertEquals("99999.99999999999", format.format(99999.99999999999));
- assertEquals("9999.999999999998", format.format(9999.999999999999));
- assertEquals("999.9999999999999", format.format(999.9999999999999));
- assertEquals("99.99999999999999", format.format(99.99999999999999));
- assertEquals("9.999999999999998", format.format(9.999999999999999));
- assertEquals("0.9999999999999999", format.format(.9999999999999999));
+ // Every floating point binary can be represented exactly in decimal if you have enough
+ // digits. This shows the value actually being tested.
+ String testId = "decimalValue: " + new BigDecimal(d);
+
+ // As a sanity check we try out parseDouble() with the string generated by
+ // Double.toString(). Strictly speaking Double.toString() is probably not guaranteed to be
+ // lossless, but in reality it probably is, or at least is close enough.
+ assertDoubleEqual(
+ testId + " failed parseDouble(toString()) sanity check",
+ d, Double.parseDouble(Double.toString(d)));
+
+ // Format the number: If this is lossy it is a problem. We are trying to check that it
+ // doesn't lose any unnecessary precision.
+ String result = format.format(d);
+
+ // Here we use Double.parseDouble() which should able to parse a number we know was
+ // representable as a double into the original double. If parseDouble() is not implemented
+ // correctly the test is invalid.
+ double doubleParsed = Double.parseDouble(result);
+ assertDoubleEqual(testId + " (format() produced " + result + ")",
+ d, doubleParsed);
+
+ // For completeness we try to parse using the formatter too. If this fails but the format
+ // above didn't it may be a problem with parse(), or with format() that we didn't spot.
+ assertDoubleEqual(testId + " failed parse(format()) check",
+ d, format.parse(result).doubleValue());
}
- public void test_formatDouble_varyingScale() throws Exception {
- final DecimalFormatSymbols dfs = new DecimalFormatSymbols(Locale.US);
- DecimalFormat format = new DecimalFormat("#0.#", dfs);
- format.setMaximumIntegerDigits(400);
- format.setMaximumFractionDigits(400);
+ private static void assertDoubleEqual(String message, double d, double doubleParsed) {
+ assertEquals(message,
+ createPrintableDouble(d),createPrintableDouble(doubleParsed));
+ }
- assertEquals("123456789012345", format.format(123456789012345.));
- assertEquals("12345678901234.5", format.format(12345678901234.5));
- assertEquals("1234567890123.25", format.format(1234567890123.25));
- assertEquals("999999999999.375", format.format(999999999999.375));
- assertEquals("99999999999.0625", format.format(99999999999.0625));
- assertEquals("9999999999.03125", format.format(9999999999.03125));
- assertEquals("999999999.015625", format.format(999999999.015625));
- assertEquals("99999999.0078125", format.format(99999999.0078125));
- assertEquals("9999999.00390625", format.format(9999999.00390625));
- assertEquals("999999.001953125", format.format(999999.001953125));
- assertEquals("9999.00048828125", format.format(9999.00048828125));
- assertEquals("999.000244140625", format.format(999.000244140625));
- assertEquals("99.0001220703125", format.format(99.0001220703125));
- assertEquals("9.00006103515625", format.format(9.00006103515625));
- assertEquals("0.000030517578125", format.format(0.000030517578125));
+ private static String createPrintableDouble(double d) {
+ return Double.toString(d) + "(" + Long.toHexString(Double.doubleToRawLongBits(d)) + ")";
+ }
+
+ // Concise demonstration of http://b/17656132 using hard-coded expected values.
+ public void test_formatDouble_bug17656132() {
+ final DecimalFormatSymbols dfs = new DecimalFormatSymbols(Locale.US);
+ DecimalFormat df = new DecimalFormat("#0.#", dfs);
+ df.setGroupingUsed(false);
+ df.setMaximumIntegerDigits(400);
+ df.setMaximumFractionDigits(400);
+
+ // The expected values below come from the RI and are correct 16 decimal digit
+ // representations of the formatted value. Android does something different.
+ // The decimal value given in each comment is the actual double value as represented by
+ // IEEE 754. See new BigDecimal(double).
+
+ // double: 999999999999999.9 is decimal 999999999999999.875
+ assertEquals("999999999999999.9", df.format(999999999999999.9));
+ // double: 99999999999999.98 is decimal 99999999999999.984375
+ assertEquals("99999999999999.98", df.format(99999999999999.98));
+ // double 9999999999999.998 is decimal 9999999999999.998046875
+ assertEquals("9999999999999.998", df.format(9999999999999.998));
+ // double 999999999999.9999 is decimal 999999999999.9998779296875
+ assertEquals("999999999999.9999", df.format(999999999999.9999));
+ // double 99999999999.99998 is decimal 99999999999.9999847412109375
+ assertEquals("99999999999.99998", df.format(99999999999.99998));
+ // double 9999999999.999998 is decimal 9999999999.9999980926513671875
+ assertEquals("9999999999.999998", df.format(9999999999.999998));
+ // double 1E23 is decimal 99999999999999991611392
+ assertEquals("9999999999999999", df.format(1E23));
}
public void test_getDecimalFormatSymbols() {