]> granicus.if.org Git - icu/commitdiff
ICU-9554 JAVA CompactDecimalFormat to support negative numbers
authorTravis Keep <keep94@gmail.com>
Mon, 1 Oct 2012 22:24:57 +0000 (22:24 +0000)
committerTravis Keep <keep94@gmail.com>
Mon, 1 Oct 2012 22:24:57 +0000 (22:24 +0000)
X-SVN-Rev: 32479

icu4j/main/classes/core/src/com/ibm/icu/text/CompactDecimalDataCache.java
icu4j/main/classes/core/src/com/ibm/icu/text/CompactDecimalFormat.java
icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/CompactDecimalFormatTest.java

index 6fdb42ab7e5e3d43b413a662f1163d586d109e51..0b22aa4dfed04dafb1cbd803030dd7074f5f82de 100644 (file)
@@ -56,13 +56,11 @@ class CompactDecimalDataCache {
      */
     static class Data {
         long[] divisors;
-        Map<String, String[]> prefixes;
-        Map<String, String[]> suffixes;
+        Map<String, DecimalFormat.Unit[]> units;
 
-        Data(long[] divisors, Map<String, String[]> prefixes, Map<String, String[]> suffixes) {
+        Data(long[] divisors, Map<String, DecimalFormat.Unit[]> units) {
             this.divisors = divisors;
-            this.prefixes = prefixes;
-            this.suffixes = suffixes;
+            this.units = units;
         }
     }
 
@@ -160,8 +158,7 @@ class CompactDecimalDataCache {
         int size = r.getSize();
         Data result = new Data(
                 new long[MAX_DIGITS],
-                new HashMap<String, String[]>(),
-                new HashMap<String, String[]>());
+                new HashMap<String, DecimalFormat.Unit[]>());
         for (int i = 0; i < size; i++) {
             populateData(r.get(i), locale, style, result);
         }
@@ -276,11 +273,8 @@ class CompactDecimalDataCache {
         }
         String prefix = fixQuotes(template.substring(0, firstIdx));
         String suffix = fixQuotes(template.substring(lastIdx + 1));
-        savePrefixOrSuffix(
-                prefix, pluralVariant, idx, result.prefixes);
-        savePrefixOrSuffix(
-                suffix, pluralVariant, idx, result.suffixes);
-        
+        saveUnit(new DecimalFormat.Unit(prefix, suffix), pluralVariant, idx, result.units);
+
         // If there is effectively no prefix or suffix, ignore the actual
         // number of 0's and act as if the number of 0's matches the size
         // of the number
@@ -358,47 +352,45 @@ class CompactDecimalDataCache {
         // Initially we assume that previous divisor is 1 with no prefix or suffix.
         long lastDivisor = 1L;
         for (int i = 0; i < result.divisors.length; i++) {
-            if (result.prefixes.get(OTHER)[i] == null) {
+            if (result.units.get(OTHER)[i] == null) {
                 result.divisors[i] = lastDivisor;
-                copyFromPreviousIndex(i, result.prefixes);
-                copyFromPreviousIndex(i, result.suffixes);
+                copyFromPreviousIndex(i, result.units);
             } else {
                 lastDivisor = result.divisors[i];
-                propagateOtherToMissing(i, result.prefixes);
-                propagateOtherToMissing(i, result.suffixes);
+                propagateOtherToMissing(i, result.units);
             }
         }
     }
 
     private static void propagateOtherToMissing(
-            int idx, Map<String, String[]> prefixesOrSuffixes) {
-        String otherVariantValue = prefixesOrSuffixes.get(OTHER)[idx];
-        for (String[] byBase : prefixesOrSuffixes.values()) {
+            int idx, Map<String, DecimalFormat.Unit[]> units) {
+        DecimalFormat.Unit otherVariantValue = units.get(OTHER)[idx];
+        for (DecimalFormat.Unit[] byBase : units.values()) {
             if (byBase[idx] == null) {
                 byBase[idx] = otherVariantValue;
             }
         }
     }
 
-    private static void copyFromPreviousIndex(int idx, Map<String, String[]> prefixesOrSuffixes) {
-        for (String[] byBase : prefixesOrSuffixes.values()) {
+    private static void copyFromPreviousIndex(int idx, Map<String, DecimalFormat.Unit[]> units) {
+        for (DecimalFormat.Unit[] byBase : units.values()) {
             if (idx == 0) {
-                byBase[idx] = "";
+                byBase[idx] = DecimalFormat.NULL_UNIT;
             } else {
                 byBase[idx] = byBase[idx - 1];
             }
         }
     }
 
-    private static void savePrefixOrSuffix(
-            String value, String pluralVariant, int idx,
-            Map<String, String[]> prefixesOrSuffixes) {
-        String[] byBase = prefixesOrSuffixes.get(pluralVariant);
+    private static void saveUnit(
+            DecimalFormat.Unit unit, String pluralVariant, int idx,
+            Map<String, DecimalFormat.Unit[]> units) {
+        DecimalFormat.Unit[] byBase = units.get(pluralVariant);
         if (byBase == null) {
-            byBase = new String[MAX_DIGITS];
-            prefixesOrSuffixes.put(pluralVariant, byBase);
+            byBase = new DecimalFormat.Unit[MAX_DIGITS];
+            units.put(pluralVariant, byBase);
         }
-        byBase[idx] = value;
+        byBase[idx] = unit;
 
     }
 
@@ -410,11 +402,11 @@ class CompactDecimalDataCache {
      * @param base log10 value. 0 <= base < MAX_DIGITS.
      * @return the prefix or suffix.
      */
-    static String getPrefixOrSuffix(
-            Map<String, String[]> prefixOrSuffix, String variant, int base) {
-        String[] byBase = prefixOrSuffix.get(variant);
+    static DecimalFormat.Unit getUnit(
+            Map<String, DecimalFormat.Unit[]> units, String variant, int base) {
+        DecimalFormat.Unit[] byBase = units.get(variant);
         if (byBase == null) {
-            byBase = prefixOrSuffix.get(CompactDecimalDataCache.OTHER);
+            byBase = units.get(CompactDecimalDataCache.OTHER);
         }
         return byBase[base];
     }
index 44581af01069b26888ab83999cc89cf3dcff065a..daa3f9dd9dce94444c1b49e2e591918c2f319736 100644 (file)
@@ -54,8 +54,7 @@ public class CompactDecimalFormat extends DecimalFormat {
     private static final int POSITIVE_PREFIX = 0, POSITIVE_SUFFIX = 1, AFFIX_SIZE = 2;
     private static final CompactDecimalDataCache cache = new CompactDecimalDataCache();
 
-    private final Map<String, String[]> prefix;
-    private final Map<String, String[]> suffix;
+    private final Map<String, DecimalFormat.Unit[]> units;
     private final long[] divisor;
     private final String[] currencyAffixes;
 
@@ -119,8 +118,7 @@ public class CompactDecimalFormat extends DecimalFormat {
     CompactDecimalFormat(ULocale locale, CompactStyle style) {
         DecimalFormat format = (DecimalFormat) NumberFormat.getInstance(locale);
         CompactDecimalDataCache.Data data = getData(locale, style);
-        this.prefix = data.prefixes;
-        this.suffix = data.suffixes;
+        this.units = data.units;
         this.divisor = data.divisors;
         applyPattern(format.toPattern());
         setDecimalFormatSymbols(format.getDecimalFormatSymbols());
@@ -133,6 +131,7 @@ public class CompactDecimalFormat extends DecimalFormat {
         currencyAffixes = new String[AFFIX_SIZE];
         currencyAffixes[CompactDecimalFormat.POSITIVE_PREFIX] = currencyFormat.getPositivePrefix();
         currencyAffixes[CompactDecimalFormat.POSITIVE_SUFFIX] = currencyFormat.getPositiveSuffix();
+        setCurrency(null);
         // TODO fix to get right symbol for the count
     }
 
@@ -200,8 +199,7 @@ public class CompactDecimalFormat extends DecimalFormat {
             oldDivisor = divisor[i];
         }
 
-        this.prefix = otherPluralVariant(prefix);
-        this.suffix = otherPluralVariant(suffix);
+        this.units = otherPluralVariant(prefix, suffix);
         this.divisor = divisor.clone();
         applyPattern(pattern);
         setDecimalFormatSymbols(formatSymbols);
@@ -210,6 +208,7 @@ public class CompactDecimalFormat extends DecimalFormat {
         setGroupingUsed(false);
         this.currencyAffixes = currencyAffixes.clone();
         this.pluralRules = null;
+        setCurrency(null);
     }
 
     /**
@@ -219,8 +218,12 @@ public class CompactDecimalFormat extends DecimalFormat {
      */
     @Override
     public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) {
-        number = configure(number);
-        return super.format(number, toAppendTo, pos);
+        Amount amount = toAmount(number);
+        Unit unit = amount.getUnit();
+        unit.writePrefix(toAppendTo);
+        super.format(amount.getQty(), toAppendTo, pos);
+        unit.writeSuffix(toAppendTo);
+        return toAppendTo;
     }
 
     /**
@@ -234,8 +237,8 @@ public class CompactDecimalFormat extends DecimalFormat {
             throw new IllegalArgumentException();
         }
         Number number = (Number) obj;
-        double newNumber = configure(number.doubleValue());
-        return super.formatToCharacterIterator(newNumber);
+        Amount amount = toAmount(number.doubleValue());
+        return super.formatToCharacterIterator(amount.getQty(), amount.getUnit());
     }
 
     /**
@@ -300,12 +303,11 @@ public class CompactDecimalFormat extends DecimalFormat {
 
     /* INTERNALS */
 
-    private double configure(double number) {
-        if (number < 0.0d) {
-            throw new UnsupportedOperationException("CompactDecimalFormat doesn't handle negative numbers yet.");
-        }
+
+    private Amount toAmount(double number) {
         // We do this here so that the prefix or suffix we choose is always consistent
         // with the rounding we do. This way, 999999 -> 1M instead of 1000K.
+        boolean negative = isNumberNegative(number);
         number = adjustNumberAsInFormatting(number);
         int base = number <= 1.0d ? 0 : (int) Math.log10(number);
         if (base >= CompactDecimalDataCache.MAX_DIGITS) {
@@ -313,10 +315,13 @@ public class CompactDecimalFormat extends DecimalFormat {
         }
         number /= divisor[base];
         String pluralVariant = getPluralForm(number);
-        setPositivePrefix(CompactDecimalDataCache.getPrefixOrSuffix(prefix, pluralVariant, base));
-        setPositiveSuffix(CompactDecimalDataCache.getPrefixOrSuffix(suffix, pluralVariant, base));
-        setCurrency(null);
-        return number;
+        if (negative) {
+            number = -number;
+        }
+        return new Amount(
+                number,
+                CompactDecimalDataCache.getUnit(units, pluralVariant, base));
+
     }
 
     private void recordError(Collection<String> creationErrors, String errorMessage) {
@@ -326,9 +331,13 @@ public class CompactDecimalFormat extends DecimalFormat {
         creationErrors.add(errorMessage);
     }
 
-    private Map<String, String[]> otherPluralVariant(String[] prefixOrSuffix) {
-        Map<String, String[]> result = new HashMap<String, String[]>();
-        result.put(CompactDecimalDataCache.OTHER, prefixOrSuffix.clone());
+    private Map<String, DecimalFormat.Unit[]> otherPluralVariant(String[] prefix, String[] suffix) {
+        Map<String, DecimalFormat.Unit[]> result = new HashMap<String, DecimalFormat.Unit[]>();
+        DecimalFormat.Unit[] units = new DecimalFormat.Unit[prefix.length];
+        for (int i = 0; i < units.length; i++) {
+            units[i] = new DecimalFormat.Unit(prefix[i], suffix[i]);
+        }
+        result.put(CompactDecimalDataCache.OTHER, units);
         return result;
     }
 
@@ -358,4 +367,21 @@ public class CompactDecimalFormat extends DecimalFormat {
         }
     }
 
+    private static class Amount {
+        private final double qty;
+        private final Unit unit;
+
+        public Amount(double qty, Unit unit) {
+            this.qty = qty;
+            this.unit = unit;
+        }
+
+        public double getQty() {
+            return qty;
+        }
+
+        public Unit getUnit() {
+            return unit;
+        }
+    }
 }
index 0f93d1768b8a70cbefb01b9da5faa20b60deba8d..1e70a3dc23c68d31f2abc4bb578eb169533e5d41 100644 (file)
@@ -915,6 +915,23 @@ public class DecimalFormat extends NumberFormat {
         return dl.getDouble();
     }
 
+    /**
+      * This is a special function used by the CompactDecimalFormat subclass
+      * to determine if the number to be formatted is negative.
+      *
+      * @param number The number to format.
+      * @return True if number is negative.
+      * @internal
+      * @deprecated
+      */
+     @Deprecated
+     boolean isNumberNegative(double number) {
+         if (Double.isNaN(number)) {
+             return false;
+         }
+         return isNegative(multiply(number));
+     }
+
     /**
      * Round a double value to the nearest multiple of the given rounding increment,
      * according to the given mode. This is equivalent to rounding value/roundingInc to
@@ -3957,24 +3974,30 @@ public class DecimalFormat extends NumberFormat {
      */
     @Override
     public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
+      return formatToCharacterIterator(obj, NULL_UNIT);
+    }
+
+    // TODO: implement
+    protected AttributedCharacterIterator formatToCharacterIterator(Object obj, Unit unit) {
         if (!(obj instanceof Number))
             throw new IllegalArgumentException();
         Number number = (Number) obj;
-        StringBuffer text = null;
+        StringBuffer text = new StringBuffer();
+        unit.writePrefix(text);
         attributes.clear();
         if (obj instanceof BigInteger) {
-            text = format((BigInteger) number, new StringBuffer(), new FieldPosition(0), true);
+            format((BigInteger) number, text, new FieldPosition(0), true);
         } else if (obj instanceof java.math.BigDecimal) {
-            text = format((java.math.BigDecimal) number, new StringBuffer(), new FieldPosition(0)
+            format((java.math.BigDecimal) number, text, new FieldPosition(0)
                           , true);
         } else if (obj instanceof Double) {
-            text = format(number.doubleValue(), new StringBuffer(), new FieldPosition(0), true);
+            format(number.doubleValue(), text, new FieldPosition(0), true);
         } else if (obj instanceof Integer || obj instanceof Long) {
-            text = format(number.longValue(), new StringBuffer(), new FieldPosition(0), true);
+            format(number.longValue(), text, new FieldPosition(0), true);
         } else {
             throw new IllegalArgumentException();
         }
-
+        unit.writeSuffix(text);
         AttributedString as = new AttributedString(text.toString());
 
         // add NumberFormat field attributes to the AttributedString
@@ -5624,6 +5647,33 @@ public class DecimalFormat extends NumberFormat {
 
     // Information needed for DecimalFormat to format/parse currency plural.
     private CurrencyPluralInfo currencyPluralInfo = null;
+
+    /**
+     * Unit is an immutable class for the textual representation of a unit, in
+     * particular its prefix and suffix.
+     *
+     * @author rocketman
+     *
+     */
+    static class Unit {
+        private final String prefix;
+        private final String suffix;
+
+        public Unit(String prefix, String suffix) {
+            this.prefix = prefix;
+            this.suffix = suffix;
+        }
+
+        public void writeSuffix(StringBuffer toAppendTo) {
+            toAppendTo.append(suffix);
+        }
+
+        public void writePrefix(StringBuffer toAppendTo) {
+            toAppendTo.append(prefix);
+        }
+    }
+
+    static final Unit NULL_UNIT = new Unit("", "");
 }
 
 // eof
index 2eff0758efdcb9eda9ea0778f03c6a9f81598ad0..1384fac5ccc7e7b8e5d0977190b155cd0ad5c7fb 100644 (file)
@@ -8,6 +8,7 @@ package com.ibm.icu.dev.test.format;
 
 import java.text.AttributedCharacterIterator;
 import java.text.CharacterIterator;
+import java.text.FieldPosition;
 
 import com.ibm.icu.dev.test.TestFmwk;
 import com.ibm.icu.text.CompactDecimalFormat;
@@ -78,6 +79,26 @@ public class CompactDecimalFormatTest extends TestFmwk {
             {1234567890123456f, "1200 трилиона"},
     };
 
+    Object[][] SerbianTestDataLongNegative = {
+            {-1234, "-1,2 хиљада"},
+            {-12345, "-12 хиљада"},
+            {-21789, "-22 хиљаде"},
+            {-123456, "-120 хиљада"},
+            {-999999, "-1 милион"},
+            {-1234567, "-1,2 милиона"},
+            {-12345678, "-12 милиона"},
+            {-123456789, "-120 милиона"},
+            {-1234567890, "-1,2 милијарди"},
+            {-12345678901f, "-12 милијарди"},
+            {-20890123456f, "-21 милијарда"},
+            {-21890123456f, "-22 милијарде"},
+            {-123456789012f, "-120 милијарди"},
+            {-1234567890123f, "-1,2 трилиона"},
+            {-12345678901234f, "-12 трилиона"},
+            {-123456789012345f, "-120 трилиона"},
+            {-1234567890123456f, "-1200 трилиона"},
+    };
+
    Object[][] JapaneseTestData = {
             {1234f, "1.2千"},
             {12345f, "1.2万"},
@@ -131,14 +152,32 @@ public class CompactDecimalFormatTest extends TestFmwk {
             {5184, "5200"},
     };
 
+    Object[][] SwahiliTestDataNegative = {
+            {-1234f, "elfu\u00a0-1.2"},
+            {-12345f, "elfu\u00a0-12"},
+            {-123456f, "laki-1.2"},
+            {-1234567f, "M-1.2"},
+            {-12345678f, "M-12"},
+            {-123456789f, "M-120"},
+            {-1234567890f, "B-1.2"},
+            {-12345678901f, "B-12"},
+            {-123456789012f, "B-120"},
+            {-1234567890123f, "T-1.2"},
+            {-12345678901234f, "T-12"},
+            {-12345678901234567890f, "T-12000000"},
+    };
+
+    // TODO: Write a test for negative numbers in arabic.
+
     public void TestCharacterIterator() {
         CompactDecimalFormat cdf =
-                CompactDecimalFormat.getInstance(ULocale.ENGLISH, CompactStyle.SHORT);
-        AttributedCharacterIterator iter = cdf.formatToCharacterIterator(12346);
-        assertEquals("CharacterIterator", "12K", iterToString(iter));
-        iter = cdf.formatToCharacterIterator(12346);
-        assertEquals("Attributes", iter.getAttribute(NumberFormat.Field.INTEGER), NumberFormat.Field.INTEGER);
-        assertEquals("Attributes", 0, iter.getRunStart());
+            CompactDecimalFormat.getInstance(ULocale.forLanguageTag("sw"), CompactStyle.SHORT);
+        AttributedCharacterIterator iter = cdf.formatToCharacterIterator(1234567);
+        assertEquals("CharacterIterator", "M1.2", iterToString(iter));
+        iter = cdf.formatToCharacterIterator(1234567);
+        iter.setIndex(1);
+        assertEquals("Attributes", NumberFormat.Field.INTEGER, iter.getAttribute(NumberFormat.Field.INTEGER));
+        assertEquals("Attributes", 1, iter.getRunStart());
         assertEquals("Attributes", 2, iter.getRunLimit());
     }
 
@@ -150,7 +189,7 @@ public class CompactDecimalFormatTest extends TestFmwk {
         NumberFormat cdf =
                 CompactDecimalFormat.getInstance(
                         ULocale.forLanguageTag("ar"), CompactStyle.LONG);
-        assertEquals("Arabic Long", "٥٫٣ ألف", cdf.format(5300));
+        assertEquals("Arabic Long", "\u0665\u066B\u0663- \u0623\u0644\u0641", cdf.format(-5300));
     }
     
     public void TestCsShort() {
@@ -174,6 +213,10 @@ public class CompactDecimalFormatTest extends TestFmwk {
         checkLocale(ULocale.forLanguageTag("sr"), CompactStyle.LONG, SerbianTestDataLong);
     }
 
+    public void TestSerbianLongNegative() {
+        checkLocale(ULocale.forLanguageTag("sr"), CompactStyle.LONG, SerbianTestDataLongNegative);
+    }
+
     public void TestJapaneseShort() {
          checkLocale(ULocale.JAPANESE, CompactStyle.SHORT, JapaneseTestData);
     }
@@ -182,6 +225,21 @@ public class CompactDecimalFormatTest extends TestFmwk {
         checkLocale(ULocale.forLanguageTag("sw"), CompactStyle.SHORT, SwahiliTestData);
     }
 
+    public void TestSwahiliShortNegative() {
+        checkLocale(ULocale.forLanguageTag("sw"), CompactStyle.SHORT, SwahiliTestDataNegative);
+    }
+
+    public void TestFieldPosition() {
+        CompactDecimalFormat cdf = CompactDecimalFormat.getInstance(
+                ULocale.forLanguageTag("sw"), CompactStyle.SHORT);
+        FieldPosition fp = new FieldPosition(0);
+        StringBuffer sb = new StringBuffer();
+        cdf.format(1234567f, sb, fp);
+        assertEquals("fp string", "M1.2", sb.toString());
+        assertEquals("fp start", 1, fp.getBeginIndex());
+        assertEquals("fp end", 2, fp.getEndIndex());
+    }
+
     public void checkLocale(ULocale locale, CompactStyle style, Object[][] testData) {
         CompactDecimalFormat cdf = CompactDecimalFormat.getInstance(locale, style);
         for (Object[] row : testData) {