From 0b24b6a633aa7895b1e5cbd5e5bcf08c04bc73d9 Mon Sep 17 00:00:00 2001 From: George Rhoten Date: Fri, 13 Jan 2017 19:06:54 +0000 Subject: [PATCH] ICU-12874 Don't truncate small numbers with CompactDecimalFormat style rules represented in RBNF X-SVN-Rev: 39557 --- .../src/com/ibm/icu/text/NFSubstitution.java | 36 +++++++++++--- .../com/ibm/icu/dev/test/format/RbnfTest.java | 47 +++++++++++++++++-- 2 files changed, 72 insertions(+), 11 deletions(-) diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/NFSubstitution.java b/icu4j/main/classes/core/src/com/ibm/icu/text/NFSubstitution.java index 057069d3d50..efcdfed0762 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/NFSubstitution.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/NFSubstitution.java @@ -278,6 +278,8 @@ abstract class NFSubstitution { // formatting //----------------------------------------------------------------------- + private static final long MAX_INT64_IN_DOUBLE = 0x1FFFFFFFFFFFFFL; + /** * Performs a mathematical operation on the number, formats it using * either ruleSet or decimalFormat, and inserts the result into @@ -289,15 +291,37 @@ abstract class NFSubstitution { * position to determine exactly where to insert the new text) */ public void doSubstitution(long number, StringBuilder toInsertInto, int position, int recursionCount) { - // perform a transformation on the number that is dependent - // on the type of substitution this is, then just call its - // rule set's format() method to format the result - long numberToFormat = transformNumber(number); - if (ruleSet != null) { + // Perform a transformation on the number that is dependent + // on the type of substitution this is, then just call its + // rule set's format() method to format the result + long numberToFormat = transformNumber(number); + ruleSet.format(numberToFormat, toInsertInto, position + pos, recursionCount); } else { - toInsertInto.insert(position + pos, numberFormat.format(numberToFormat)); + if (number <= MAX_INT64_IN_DOUBLE) { + // or perform the transformation on the number (preserving + // the result's fractional part if the formatter it set + // to show it), then use that formatter's format() method + // to format the result + double numberToFormat = transformNumber((double) number); + if (numberFormat.getMaximumFractionDigits() == 0) { + numberToFormat = Math.floor(numberToFormat); + } + + toInsertInto.insert(position + pos, numberFormat.format(numberToFormat)); + } + else { + // We have gone beyond double precision. Something has to give. + // We're favoring accuracy of the large number over potential rules + // that round like a CompactDecimalFormat, which is not a common use case. + // + // Perform a transformation on the number that is dependent + // on the type of substitution this is, then just call its + // rule set's format() method to format the result + long numberToFormat = transformNumber(number); + toInsertInto.insert(position + pos, numberFormat.format(numberToFormat)); + } } } diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/RbnfTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/RbnfTest.java index 6f62a95481b..c82ab7c95e1 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/RbnfTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/RbnfTest.java @@ -948,9 +948,9 @@ public class RbnfTest extends TestFmwk { @Test public void TestRuleSetDisplayName() { - /** + /* * Spellout rules for U.K. English. - * I borrow the rule sets for TestRuleSetDisplayName() + * This was borrowed from the rule sets for TestRuleSetDisplayName() */ final String ukEnglish = "%simplified:\n" @@ -1309,13 +1309,13 @@ public class RbnfTest extends TestFmwk { rbnf.getRuleSetDisplayName("", new ULocale("en_US")); errln("RuleBasedNumberFormat.getRuleSetDisplayName(String ruleSetName, ULocale loc) " + "was suppose to have an exception."); - } catch(Exception e){} + } catch(Exception ignored){} try{ rbnf.getRuleSetDisplayName("dummy", new ULocale("en_US")); errln("RuleBasedNumberFormat.getRuleSetDisplayName(String ruleSetName, ULocale loc) " + "was suppose to have an exception."); - } catch(Exception e){} + } catch(Exception ignored){} } /* Test the method @@ -1651,6 +1651,10 @@ public class RbnfTest extends TestFmwk { RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat(ULocale.US, RuleBasedNumberFormat.SPELLOUT); String[][] enTestFullData = { + {"-9007199254740991", "minus nine quadrillion seven trillion one hundred ninety-nine billion two hundred fifty-four million seven hundred forty thousand nine hundred ninety-one"}, // Maximum precision in both a double and a long + {"9007199254740991", "nine quadrillion seven trillion one hundred ninety-nine billion two hundred fifty-four million seven hundred forty thousand nine hundred ninety-one"}, // Maximum precision in both a double and a long + {"-9007199254740992", "minus nine quadrillion seven trillion one hundred ninety-nine billion two hundred fifty-four million seven hundred forty thousand nine hundred ninety-two"}, // Only precisely contained in a long + {"9007199254740992", "nine quadrillion seven trillion one hundred ninety-nine billion two hundred fifty-four million seven hundred forty thousand nine hundred ninety-two"}, // Only precisely contained in a long {"9999999999999998", "nine quadrillion nine hundred ninety-nine trillion nine hundred ninety-nine billion nine hundred ninety-nine million nine hundred ninety-nine thousand nine hundred ninety-eight"}, {"9999999999999999", "nine quadrillion nine hundred ninety-nine trillion nine hundred ninety-nine billion nine hundred ninety-nine million nine hundred ninety-nine thousand nine hundred ninety-nine"}, {"999999999999999999", "nine hundred ninety-nine quadrillion nine hundred ninety-nine trillion nine hundred ninety-nine billion nine hundred ninety-nine million nine hundred ninety-nine thousand nine hundred ninety-nine"}, @@ -1664,7 +1668,40 @@ public class RbnfTest extends TestFmwk { {"9223372036854775000", "9,223,372,036,854,775,000"}, // Below 64-bit precision {"9223372036854775806", "9,223,372,036,854,775,806"}, // Maximum 64-bit precision - 1 {"9223372036854775807", "9,223,372,036,854,775,807"}, // Maximum 64-bit precision - {"9223372036854775808", "9,223,372,036,854,775,808"}, // We've gone beyond 64-bit precision + {"9223372036854775808", "9,223,372,036,854,775,808"}, // We've gone beyond 64-bit precision. This can only be represented with BigDecimal. + }; + doTest(rbnf, enTestFullData, false); + } + + @Test + public void testCompactDecimalFormatStyle() { + // This is not a common use case, but we're testing it anyway. + final String numberPattern = "=###0.#####=;" + + "1000: <###0.00< K;" + + "1000000: <###0.00< M;" + + "1000000000: <###0.00< B;" + + "1000000000000: <###0.00< T;" + + "1000000000000000: <###0.00< Q;"; + RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat(numberPattern, ULocale.US); + + String[][] enTestFullData = { + {"1000", "1.00 K"}, + {"1234", "1.23 K"}, + {"999994", "999.99 K"}, + {"999995", "1000.00 K"}, + {"1000000", "1.00 M"}, + {"1200000", "1.20 M"}, + {"1200000000", "1.20 B"}, + {"1200000000000", "1.20 T"}, + {"1200000000000000", "1.20 Q"}, + {"4503599627370495", "4.50 Q"}, + {"4503599627370496", "4.50 Q"}, + {"8990000000000000", "8.99 Q"}, + {"9008000000000000", "9.00 Q"}, // Number doesn't precisely fit into a double + {"9456000000000000", "9.00 Q"}, // Number doesn't precisely fit into a double + {"10000000000000000", "10.00 Q"}, // Number doesn't precisely fit into a double + {"9223372036854775807", "9223.00 Q"}, // Maximum 64-bit precision + {"9223372036854775808", "9,223,372,036,854,775,808"}, // We've gone beyond 64-bit precision. This can only be represented with BigDecimal. }; doTest(rbnf, enTestFullData, false); }