From acd53b0c181d291eb994148cc88d89a1fc4e2a9d Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Thu, 22 Jun 2017 23:26:48 +0000 Subject: [PATCH] ICU-13129 Changing default Parse MathContext to 34 digits instead of 16 digits. X-SVN-Rev: 40194 --- .../numberformattestspecification.txt | 3 +- .../src/com/ibm/icu/impl/number/Parse.java | 2 +- .../ibm/icu/impl/number/RoundingUtils.java | 12 ++--- .../src/com/ibm/icu/text/DecimalFormat.java | 13 +++-- .../data/numberformattestspecification.txt | 3 +- .../icu/dev/test/format/NumberFormatTest.java | 47 +++++++++++++++++++ 6 files changed, 64 insertions(+), 16 deletions(-) diff --git a/icu4c/source/test/testdata/numberformattestspecification.txt b/icu4c/source/test/testdata/numberformattestspecification.txt index 9fb934914b1..5200bc83d6f 100644 --- a/icu4c/source/test/testdata/numberformattestspecification.txt +++ b/icu4c/source/test/testdata/numberformattestspecification.txt @@ -1588,8 +1588,7 @@ set locale en set pattern 0% begin parse output breaks -// S uses a MathContext of 16 digits when dividing by 100. -9223372036854775807% 92233720368547758.07 S +9223372036854775807% 92233720368547758.07 diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Parse.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Parse.java index b4be09621d1..68b1c0cbffd 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Parse.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Parse.java @@ -583,7 +583,7 @@ public class Parse { // We need to use a math context in order to prevent non-terminating decimal expansions. // This is only used when dividing by the multiplier. - MathContext mc = RoundingUtils.getMathContextOr16Digits(properties); + MathContext mc = RoundingUtils.getMathContextOr34Digits(properties); // Construct the output number. // This is the only step during fast-mode parsing that incurs object creations. diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/RoundingUtils.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/RoundingUtils.java index 3994eb25737..80ff29e3cc7 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/RoundingUtils.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/RoundingUtils.java @@ -117,13 +117,13 @@ public class RoundingUtils { private static final MathContext[] MATH_CONTEXT_BY_ROUNDING_MODE_UNLIMITED = new MathContext[RoundingMode.values().length]; - private static final MathContext[] MATH_CONTEXT_BY_ROUNDING_MODE_16_DIGITS = + private static final MathContext[] MATH_CONTEXT_BY_ROUNDING_MODE_34_DIGITS = new MathContext[RoundingMode.values().length]; static { - for (int i = 0; i < MATH_CONTEXT_BY_ROUNDING_MODE_16_DIGITS.length; i++) { + for (int i = 0; i < MATH_CONTEXT_BY_ROUNDING_MODE_34_DIGITS.length; i++) { MATH_CONTEXT_BY_ROUNDING_MODE_UNLIMITED[i] = new MathContext(0, RoundingMode.valueOf(i)); - MATH_CONTEXT_BY_ROUNDING_MODE_16_DIGITS[i] = new MathContext(16); + MATH_CONTEXT_BY_ROUNDING_MODE_34_DIGITS[i] = new MathContext(34); } } @@ -147,18 +147,18 @@ public class RoundingUtils { /** * Gets the user-specified math context out of the property bag. If there is none, falls back to a - * math context with 16 digits of precision (the 64-bit IEEE 754R default) and the user-specified + * math context with 34 digits of precision (the 128-bit IEEE 754R default) and the user-specified * rounding mode, which defaults to HALF_EVEN (the IEEE 754R default). * * @param properties The property bag. * @return A {@link MathContext}. Never null. */ - public static MathContext getMathContextOr16Digits(IBasicRoundingProperties properties) { + public static MathContext getMathContextOr34Digits(IBasicRoundingProperties properties) { MathContext mathContext = properties.getMathContext(); if (mathContext == null) { RoundingMode roundingMode = properties.getRoundingMode(); if (roundingMode == null) roundingMode = RoundingMode.HALF_EVEN; - mathContext = MATH_CONTEXT_BY_ROUNDING_MODE_16_DIGITS[roundingMode.ordinal()]; + mathContext = MATH_CONTEXT_BY_ROUNDING_MODE_34_DIGITS[roundingMode.ordinal()]; } return mathContext; } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java index 705afa195bc..ad3698f5f00 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java @@ -1241,11 +1241,14 @@ public class DecimalFormat extends NumberFormat { /** * {@icu} Rounding and Digit Limits: Sets the {@link java.math.MathContext} used * to round numbers. A "math context" encodes both a rounding mode and a number of significant - * digits. - * - *

This method is provided for users who require their output to conform to a standard math - * context. Most users should call {@link #setRoundingMode} and/or {@link - * #setMaximumSignificantDigits} instead of this method. + * digits. Most users should call {@link #setRoundingMode} and/or {@link + * #setMaximumSignificantDigits} instead of this method. + * + *

When formatting, since no division is ever performed, the default MathContext is unlimited + * significant digits. However, when division occurs during parsing to correct for percentages and + * multipliers, a MathContext of 34 digits, the IEEE 754R Decimal128 standard, is used by default. + * If you require more than 34 digits when parsing, you can set a custom MathContext using this + * method. * * @param mathContext The MathContext to use when rounding numbers. * @see java.math.MathContext diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt b/icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt index 9fb934914b1..5200bc83d6f 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt @@ -1588,8 +1588,7 @@ set locale en set pattern 0% begin parse output breaks -// S uses a MathContext of 16 digits when dividing by 100. -9223372036854775807% 92233720368547758.07 S +9223372036854775807% 92233720368547758.07 diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java index b6902196383..3031c89344d 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java @@ -4796,6 +4796,53 @@ public class NumberFormatTest extends TestFmwk { } } + @Test + public void TestSetMathContext() throws ParseException { + java.math.MathContext fourDigits = new java.math.MathContext(4); + java.math.MathContext unlimitedCeiling = new java.math.MathContext(0, RoundingMode.CEILING); + + // Test rounding + DecimalFormat df = new DecimalFormat(); + assertEquals("Default format", "9,876.543", df.format(9876.5432)); + df.setMathContext(fourDigits); + assertEquals("Format with fourDigits", "9,877", df.format(9876.5432)); + df.setMathContext(unlimitedCeiling); + assertEquals("Format with unlimitedCeiling", "9,876.544", df.format(9876.5432)); + + // Test multiplication + df = new DecimalFormat("0.000%"); + assertEquals("Default multiplication", "12.001%", df.format(0.120011)); + df.setMathContext(fourDigits); + assertEquals("Multiplication with fourDigits", "12.000%", df.format(0.120011)); + df.setMathContext(unlimitedCeiling); + assertEquals("Multiplication with unlimitedCeiling", "12.002%", df.format(0.120011)); + + // Test simple division + df = new DecimalFormat("0%"); + assertEquals("Default division", 0.12001, df.parse("12.001%").doubleValue()); + df.setMathContext(fourDigits); + assertEquals("Division with fourDigits", 0.12, df.parse("12.001%").doubleValue()); + df.setMathContext(unlimitedCeiling); + assertEquals("Division with unlimitedCeiling", 0.12001, df.parse("12.001%").doubleValue()); + + // Test extreme division + df = new DecimalFormat(); + df.setMultiplier(1000000007); // prime number + String hugeNumberString = "9876543212345678987654321234567898765432123456789"; // 49 digits + BigInteger huge34Digits = new BigInteger("9876543143209876985185182338271622000000"); + BigInteger huge4Digits = new BigInteger("9877000000000000000000000000000000000000"); + assertEquals("Default extreme division", huge34Digits, df.parse(hugeNumberString)); + df.setMathContext(fourDigits); + assertEquals("Extreme division with fourDigits", huge4Digits, df.parse(hugeNumberString)); + df.setMathContext(unlimitedCeiling); + try { + df.parse(hugeNumberString); + fail("Extreme division with unlimitedCeiling should throw ArithmeticException"); + } catch (ArithmeticException e) { + // expected + } + } + @Test public void Test10436() { DecimalFormat df = (DecimalFormat) NumberFormat.getInstance(Locale.ENGLISH); -- 2.40.0