*/
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;
}
}
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);
}
}
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
// 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;
}
* @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];
}
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;
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());
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
}
oldDivisor = divisor[i];
}
- this.prefix = otherPluralVariant(prefix);
- this.suffix = otherPluralVariant(suffix);
+ this.units = otherPluralVariant(prefix, suffix);
this.divisor = divisor.clone();
applyPattern(pattern);
setDecimalFormatSymbols(formatSymbols);
setGroupingUsed(false);
this.currencyAffixes = currencyAffixes.clone();
this.pluralRules = null;
+ setCurrency(null);
}
/**
*/
@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;
}
/**
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());
}
/**
/* 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) {
}
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) {
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;
}
}
}
+ 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;
+ }
+ }
}
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
*/
@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
// 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
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;
{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万"},
{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());
}
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() {
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);
}
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) {