ICU-9541 Re-integrate branch changes into trunk.

X-SVN-Rev: 32375
This commit is contained in:
Travis Keep 2012-09-11 23:54:53 +00:00
parent 1dff0b56ee
commit 237e167bce
4 changed files with 480 additions and 133 deletions

View File

@ -6,6 +6,9 @@
*/
package com.ibm.icu.text;
import java.util.HashMap;
import java.util.Map;
import com.ibm.icu.impl.ICUCache;
import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.SimpleCache;
@ -19,30 +22,42 @@ import com.ibm.icu.util.UResourceBundle;
*/
class CompactDecimalDataCache {
private static final int MAX_DIGITS = 15;
private final ICUCache<ULocale, Data> cache = new SimpleCache<ULocale, Data>();
static final String OTHER = "other";
/**
* We can specify prefixes or suffixes for values with up to 15 digits,
* less than 10^15.
*/
static final int MAX_DIGITS = 15;
private final ICUCache<ULocale, DataBundle> cache =
new SimpleCache<ULocale, DataBundle>();
/**
* Data contains the compact decimal data for a particular locale. Data consists
* of three arrays. The index of each array corresponds to log10 of the number
* being formatted, so when formatting 12,345, the 4th index of the arrays should
* be used. Divisors contain the number to divide by before doing formatting.
* In the case of english, <code>divisors[4]</code> is 1000. So to format
* 12,345, divide by 1000 to get 12. prefix and suffix contain the prefix and
* suffix to use, for english, <code>suffix[4]</code> is "K" So ultimately,
* 12,345 is formatted as 12K.
* of one array and two hashmaps. The index of the divisors array as well
* as the arrays stored in the values of the two hashmaps correspond
* to log10 of the number being formatted, so when formatting 12,345, the 4th
* index of the arrays should be used. Divisors contain the number to divide
* by before doing formatting. In the case of english, <code>divisors[4]</code>
* is 1000. So to format 12,345, divide by 1000 to get 12. Then use
* PluralRules with the current locale to figure out which of the 6 plural variants
* 12 matches: "zero", "one", "two", "few", "many", or "other." Prefixes and
* suffixes are maps whose key is the plural variant and whose values are
* arrays of strings with indexes corresponding to log10 of the original number.
* these arrays contain the prefix or suffix to use.
*
* Each array in data is 15 in length, and every index is filled.
*
* @author Travis Keep
* *
*
*/
static class Data {
long[] divisors;
String[] prefixes;
String[] suffixes;
Map<String, String[]> prefixes;
Map<String, String[]> suffixes;
Data(long[] divisors, String[] prefixes, String[] suffixes) {
Data(long[] divisors, Map<String, String[]> prefixes, Map<String, String[]> suffixes) {
this.divisors = divisors;
this.prefixes = prefixes;
this.suffixes = suffixes;
@ -50,10 +65,29 @@ class CompactDecimalDataCache {
}
/**
* Fetch data for a particular locale.
* DataBundle contains compact decimal data for all the styles in a particular
* locale. Currently available styles are short and long.
*
* @author Travis Keep
*/
Data get(ULocale locale) {
Data result = cache.get(locale);
static class DataBundle {
Data shortData;
Data longData;
DataBundle(Data shortData, Data longData) {
this.shortData = shortData;
this.longData = longData;
}
}
/**
* Fetch data for a particular locale. Clients must not modify any part
* of the returned data. Portions of returned data may be shared so modifying
* it will have unpredictable results.
*/
DataBundle get(ULocale locale) {
DataBundle result = cache.get(locale);
if (result == null) {
result = load(locale);
cache.put(locale, result);
@ -61,16 +95,54 @@ class CompactDecimalDataCache {
return result;
}
private static Data load(ULocale ulocale) {
/**
* Loads the "patternsShort" and "patternsLong" data for a particular locale.
* We assume that "patternsShort" data can be found for any locale. If we can't
* find it we throw an exception. However, we allow "patternsLong" data to be
* missing for a locale. In this case, we assume that the "patternsLong" data
* is identical to the "paternsShort" data.
* @param ulocale the locale for which we are loading the data.
* @return The returned data, never null.
*/
private static DataBundle load(ULocale ulocale) {
NumberingSystem ns = NumberingSystem.getInstance(ulocale);
ICUResourceBundle r = (ICUResourceBundle)UResourceBundle.
getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, ulocale);
r = r.getWithFallback("NumberElements/" + ns.getName() + "/patternsShort/decimalFormat");
String numberingSystemName = ns.getName();
Data shortData = loadWithStyle(r, numberingSystemName, ulocale, "patternsShort", false);
Data longData = loadWithStyle(r, numberingSystemName, ulocale, "patternsLong", true);
if (longData == null) {
longData = shortData;
}
return new DataBundle(shortData, longData);
}
/**
* Loads the data
* @param r the main resource bundle.
* @param numberingSystemName The namespace name.
* @param allowNullResult If true, returns null if no data can be found
* for particular locale and style. If false, throws a runtime exception
* if data cannot be found.
* @return The loaded data or possibly null if allowNullResult is true.
*/
private static Data loadWithStyle(
ICUResourceBundle r, String numberingSystemName, ULocale locale, String style,
boolean allowNullResult) {
String resourcePath =
"NumberElements/" + numberingSystemName + "/" + style + "/decimalFormat";
if (allowNullResult) {
r = r.findWithFallback(resourcePath);
} else {
r = r.getWithFallback(resourcePath);
}
int size = r.getSize();
Data result = new Data(
new long[MAX_DIGITS], new String[MAX_DIGITS], new String[MAX_DIGITS]);
new long[MAX_DIGITS],
new HashMap<String, String[]>(),
new HashMap<String, String[]>());
for (int i = 0; i < size; i++) {
populateData((ICUResourceBundle) r.get(i), result);
populateData(r.get(i), locale, style, result);
}
fillInMissing(result);
return result;
@ -78,36 +150,114 @@ class CompactDecimalDataCache {
/**
* Populates Data object with data for a particular divisor from resource bundle.
* @param divisorData represents the rules for numbers of a particular size.
* This may look like:
* <pre>
* 10000{
* few{"00K"}
* many{"00K"}
* one{"00 xnb"}
* other{"00 xnb"}
* }
* </pre>
* @param locale the locale
* @param style the style
* @param result rule stored here.
*
*/
private static void populateData(ICUResourceBundle divisorData, Data result) {
long divisor = Long.parseLong(divisorData.getKey());
int thisIndex = (int) Math.log10(divisor);
private static void populateData(
UResourceBundle divisorData, ULocale locale, String style, Data result) {
// This value will always be some even pwoer of 10. e.g 10000.
long magnitude = Long.parseLong(divisorData.getKey());
int thisIndex = (int) Math.log10(magnitude);
// Silently ignore divisors that are too big.
if (thisIndex >= MAX_DIGITS) {
return;
}
ICUResourceBundle other = (ICUResourceBundle) divisorData.get("other");
int numZeros = populatePrefixSuffix(other.getString(), thisIndex, result);
int size = divisorData.getSize();
// keep track of how many zeros are used in the plural variants.
// For "00K" this would be 2. This number must be the same for all
// plural variants. If they differ, we throw a runtime exception as
// such an anomaly is unrecoverable. We expect at least one zero.
int numZeros = 0;
// Keep track if this block defines "other" variant. If a block
// fails to define the "other" variant, we must immediately throw
// an exception as it is assumed that "other" variants are always
// defined.
boolean otherVariantDefined = false;
// Loop over all the plural variants. e.g one, other.
for (int i = 0; i < size; i++) {
UResourceBundle pluralVariantData = divisorData.get(i);
String pluralVariant = pluralVariantData.getKey();
String template = pluralVariantData.getString();
if (pluralVariant.equals(OTHER)) {
otherVariantDefined = true;
}
int nz = populatePrefixSuffix(
pluralVariant, thisIndex, template, locale, style, result);
if (nz != numZeros) {
if (numZeros != 0) {
throw new IllegalArgumentException(
"Plural variant '" + pluralVariant + "' template '" +
template + "' for 10^" + thisIndex +
" has wrong number of zeros in " + localeAndStyle(locale, style));
}
numZeros = nz;
}
}
if (!otherVariantDefined) {
throw new IllegalArgumentException(
"No 'other' plural variant defined for 10^" + thisIndex +
"in " +localeAndStyle(locale, style));
}
// We craft our divisor such that when we divide by it, we get a
// number with the same number of digits as zeros found in the
// plural variant templates. If our magnitude is 10000 and we have
// two 0's in our plural variants, then we want a divisor of 1000.
// Note that if we have 43560 which is of same magnitude as 10000.
// When we divide by 1000 we a quotient which rounds to 44 (2 digits)
long divisor = magnitude;
for (int i = 1; i < numZeros; i++) {
divisor /= 10;
}
result.divisors[thisIndex] = divisor;
}
/**
* Extracts the prefix and suffix from the template and places them in the
* Data object.
* @param template the number template, e.g 000K
* @param idx the index to store the extracted prefix and suffix
* @param result Data object modified in-place here.
* Populates prefix and suffix information for a particular plural variant
* and index (log10 value).
* @param pluralVariant e.g "one", "other"
* @param idx the index (log10 value of the number) 0 <= idx < MAX_DIGITS
* @param template e.g "00K"
* @param locale the locale
* @param style the style
* @param result Extracted prefix and suffix stored here.
* @return number of zeros found before any decimal point in template.
*/
private static int populatePrefixSuffix(String template, int idx, Data result) {
private static int populatePrefixSuffix(
String pluralVariant, int idx, String template, ULocale locale, String style,
Data result) {
int firstIdx = template.indexOf("0");
int lastIdx = template.lastIndexOf("0");
result.prefixes[idx] = template.substring(0, firstIdx);
result.suffixes[idx] = template.substring(lastIdx + 1);
if (firstIdx == -1) {
throw new IllegalArgumentException(
"Expect at least one zero in template '" + template +
"' for variant '" +pluralVariant + "' for 10^" + idx +
" in " + localeAndStyle(locale, style));
}
savePrefixOrSuffix(
template.substring(0, firstIdx), pluralVariant, idx, result.prefixes);
savePrefixOrSuffix(
template.substring(lastIdx + 1), pluralVariant, idx, result.suffixes);
// Calculate number of zeros before decimal point.
int i = firstIdx + 1;
while (i <= lastIdx && template.charAt(i) == '0') {
@ -116,33 +266,97 @@ class CompactDecimalDataCache {
return i - firstIdx;
}
/**
* Returns locale and style. Used to form useful messages in thrown
* exceptions.
* @param locale the locale
* @param style the style
*/
private static String localeAndStyle(ULocale locale, String style) {
return "locale '" + locale + "' style '" + style + "'";
}
/**
* After reading information from resource bundle into a Data object, there
* is no guarantee that every index of the arrays will be filled.
*
* This function walks through the arrays filling in indexes with missing
* data from the previous index. If the first indexes are missing data,
* they are assumed to have no prefixes or suffixes and a divisor of 1.
* We assume an index has missing data if the corresponding element in the
* prefixes array is null.
* is guarantee that it is complete.
*
* This method fixes any incomplete data it finds within <code>result</code>.
* It looks at each log10 value applying the two rules.
* <p>
* If no prefix is defined for the "other" variant, use the divisor, prefixes and
* suffixes for all defined variants from the previous log10. For log10 = 0,
* use all empty prefixes and suffixes and a divisor of 1.
* </p><p>
* Otherwise, examine each plural variant defined for the given log10 value.
* If it has no prefix and suffix for a particular variant, use the one from the
* "other" variant.
* </p>
*
* @param result this instance is fixed in-place.
*/
private static void fillInMissing(Data result) {
// Initially we assume that previous divisor is 1 with no prefix or suffix.
long lastDivisor = 1L;
String lastPrefix = "";
String lastSuffix = "";
for (int i = 0; i < result.divisors.length; i++) {
if (result.prefixes[i] == null) {
if (result.prefixes.get(OTHER)[i] == null) {
result.divisors[i] = lastDivisor;
result.prefixes[i] = lastPrefix;
result.suffixes[i] = lastSuffix;
copyFromPreviousIndex(i, result.prefixes);
copyFromPreviousIndex(i, result.suffixes);
} else {
lastDivisor = result.divisors[i];
lastPrefix = result.prefixes[i];
lastSuffix = result.suffixes[i];
propagateOtherToMissing(i, result.prefixes);
propagateOtherToMissing(i, result.suffixes);
}
}
}
private static void propagateOtherToMissing(
int idx, Map<String, String[]> prefixesOrSuffixes) {
String otherVariantValue = prefixesOrSuffixes.get(OTHER)[idx];
for (String[] byBase : prefixesOrSuffixes.values()) {
if (byBase[idx] == null) {
byBase[idx] = otherVariantValue;
}
}
}
private static void copyFromPreviousIndex(int idx, Map<String, String[]> prefixesOrSuffixes) {
for (String[] byBase : prefixesOrSuffixes.values()) {
if (idx == 0) {
byBase[idx] = "";
} 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);
if (byBase == null) {
byBase = new String[MAX_DIGITS];
prefixesOrSuffixes.put(pluralVariant, byBase);
}
byBase[idx] = value;
}
/**
* Fetches a prefix or suffix given a plural variant and log10 value. If it
* can't find the given variant, it falls back to "other".
* @param prefixOrSuffix the prefix or suffix map
* @param variant the plural variant
* @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);
if (byBase == null) {
byBase = prefixOrSuffix.get(CompactDecimalDataCache.OTHER);
}
return byBase[base];
}
}

View File

@ -19,6 +19,7 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import com.ibm.icu.text.CompactDecimalDataCache.Data;
import com.ibm.icu.util.ULocale;
/**
@ -45,17 +46,20 @@ import com.ibm.icu.util.ULocale;
* @provisional This API might change or be removed in a future release.
*/
public class CompactDecimalFormat extends DecimalFormat {
private static final long serialVersionUID = 4716293295276629682L;
private static final int MINIMUM_ARRAY_LENGTH = 15;
private static final int POSITIVE_PREFIX = 0, POSITIVE_SUFFIX = 1, AFFIX_SIZE = 2;
private static final CompactDecimalDataCache cache = new CompactDecimalDataCache();
private final String[] prefix;
private final String[] suffix;
private final Map<String, String[]> prefix;
private final Map<String, String[]> suffix;
private final long[] divisor;
private final String[] currencyAffixes;
// null if created internally using explicit prefixes and suffixes.
private final PluralRules pluralRules;
/**
* The public mechanism is NumberFormat.getCompactDecimalInstance().
*
@ -66,16 +70,16 @@ public class CompactDecimalFormat extends DecimalFormat {
*/
CompactDecimalFormat(ULocale locale, CompactStyle style) {
DecimalFormat format = (DecimalFormat) NumberFormat.getInstance(locale);
CompactDecimalDataCache.Data data = cache.get(locale);
CompactDecimalDataCache.Data data = getData(locale, style);
this.prefix = data.prefixes;
this.suffix = data.suffixes;
this.divisor = data.divisors;
// TODO fix to consider plural form when choosing a prefix or suffix.
applyPattern(format.toPattern());
setDecimalFormatSymbols(format.getDecimalFormatSymbols());
setMaximumSignificantDigits(2); // default significant digits
setSignificantDigitsUsed(true);
setGroupingUsed(false);
this.pluralRules = PluralRules.forLocale(locale);
DecimalFormat currencyFormat = (DecimalFormat) NumberFormat.getCurrencyInstance(locale);
currencyAffixes = new String[AFFIX_SIZE];
@ -108,8 +112,8 @@ public class CompactDecimalFormat extends DecimalFormat {
*/
public CompactDecimalFormat(String pattern, DecimalFormatSymbols formatSymbols, String[] prefix, String[] suffix,
long[] divisor, Collection<String> debugCreationErrors, CompactStyle style, String[] currencyAffixes) {
if (prefix.length < MINIMUM_ARRAY_LENGTH) {
recordError(debugCreationErrors, "Must have at least " + MINIMUM_ARRAY_LENGTH + " prefix items.");
if (prefix.length < CompactDecimalDataCache.MAX_DIGITS) {
recordError(debugCreationErrors, "Must have at least " + CompactDecimalDataCache.MAX_DIGITS + " prefix items.");
}
if (prefix.length != suffix.length || prefix.length != divisor.length) {
recordError(debugCreationErrors, "Prefix, suffix, and divisor arrays must have the same length.");
@ -148,8 +152,8 @@ public class CompactDecimalFormat extends DecimalFormat {
oldDivisor = divisor[i];
}
this.prefix = prefix.clone();
this.suffix = suffix.clone();
this.prefix = otherPluralVariant(prefix);
this.suffix = otherPluralVariant(suffix);
this.divisor = divisor.clone();
applyPattern(pattern);
setDecimalFormatSymbols(formatSymbols);
@ -157,6 +161,7 @@ public class CompactDecimalFormat extends DecimalFormat {
setSignificantDigitsUsed(true);
setGroupingUsed(false);
this.currencyAffixes = currencyAffixes.clone();
this.pluralRules = null;
}
/**
@ -169,12 +174,19 @@ public class CompactDecimalFormat extends DecimalFormat {
if (number < 0.0d) {
throw new UnsupportedOperationException("CompactDecimalFormat doesn't handle negative numbers yet.");
}
int integerCount = number <= 1.0d ? 0 : (int) Math.log10(number);
int base = integerCount > 14 ? 14 : integerCount;
// 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.
number = adjustNumberAsInFormatting(number);
int base = number <= 1.0d ? 0 : (int) Math.log10(number);
if (base >= CompactDecimalDataCache.MAX_DIGITS) {
base = CompactDecimalDataCache.MAX_DIGITS - 1;
}
number = number / divisor[base];
setPositivePrefix(prefix[base]);
setPositiveSuffix(suffix[base]);
String pluralVariant = getPluralForm(number);
setPositivePrefix(CompactDecimalDataCache.getPrefixOrSuffix(prefix, pluralVariant, base));
setPositiveSuffix(CompactDecimalDataCache.getPrefixOrSuffix(suffix, pluralVariant, base));
setCurrency(null);
return super.format(number, toAppendTo, pos);
}
@ -248,4 +260,37 @@ 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());
return result;
}
private String getPluralForm(double number) {
if (pluralRules == null) {
return CompactDecimalDataCache.OTHER;
}
return pluralRules.select(number);
}
/**
* Gets the data for a particular locale and style. If style is unrecognized,
* we just return data for CompactStyle.SHORT.
* @param locale The locale.
* @param style The style.
* @return The data which must not be modified.
*/
private Data getData(ULocale locale, CompactStyle style) {
CompactDecimalDataCache.DataBundle bundle = cache.get(locale);
switch (style) {
case SHORT:
return bundle.shortData;
case LONG:
return bundle.longData;
default:
return bundle.shortData;
}
}
}

View File

@ -770,10 +770,52 @@ public class DecimalFormat extends NumberFormat {
* {@inheritDoc}
* @stable ICU 2.0
*/
@Override
public StringBuffer format(double number, StringBuffer result, FieldPosition fieldPosition) {
return format(number, result, fieldPosition, false);
}
// See if number is negative.
// usage: isNegative(multiply(numberToBeFormatted));
private boolean isNegative(double number) {
// Detecting whether a double is negative is easy with the exception of the value
// -0.0. This is a double which has a zero mantissa (and exponent), but a negative
// sign bit. It is semantically distinct from a zero with a positive sign bit, and
// this distinction is important to certain kinds of computations. However, it's a
// little tricky to detect, since (-0.0 == 0.0) and !(-0.0 < 0.0). How then, you
// may ask, does it behave distinctly from +0.0? Well, 1/(-0.0) ==
// -Infinity. Proper detection of -0.0 is needed to deal with the issues raised by
// bugs 4106658, 4106667, and 4147706. Liu 7/6/98.
return (number < 0.0) || (number == 0.0 && 1 / number < 0.0);
}
// Rounds the number and strips of the negative sign.
// usage: round(multiply(numberToBeFormatted))
private double round(double number) {
boolean isNegative = isNegative(number);
if (isNegative)
number = -number;
// Apply rounding after multiplier
if (roundingDouble > 0.0) {
// number = roundingDouble
// * round(number / roundingDouble, roundingMode, isNegative);
return round(
number, roundingDouble, roundingDoubleReciprocal, roundingMode,
isNegative);
}
return number;
}
// Multiplies given number by multipler (if there is one) returning the new
// number. If there is no multiplier, returns the number passed in unchanged.
private double multiply(double number) {
if (multiplier != 1) {
return number * multiplier;
}
return number;
}
// [Spark/CDL] The actual method to format number. If boolean value
// parseAttr == true, then attribute information will be recorded.
private StringBuffer format(double number, StringBuffer result, FieldPosition fieldPosition,
@ -805,31 +847,11 @@ public class DecimalFormat extends NumberFormat {
return result;
}
// Do this BEFORE checking to see if value is infinite or negative!
if (multiplier != 1)
number *= multiplier;
// Detecting whether a double is negative is easy with the exception of the value
// -0.0. This is a double which has a zero mantissa (and exponent), but a negative
// sign bit. It is semantically distinct from a zero with a positive sign bit, and
// this distinction is important to certain kinds of computations. However, it's a
// little tricky to detect, since (-0.0 == 0.0) and !(-0.0 < 0.0). How then, you
// may ask, does it behave distinctly from +0.0? Well, 1/(-0.0) ==
// -Infinity. Proper detection of -0.0 is needed to deal with the issues raised by
// bugs 4106658, 4106667, and 4147706. Liu 7/6/98.
boolean isNegative = (number < 0.0) || (number == 0.0 && 1 / number < 0.0);
if (isNegative)
number = -number;
// Apply rounding after multiplier
if (roundingDouble > 0.0) {
// number = roundingDouble
// * round(number / roundingDouble, roundingMode, isNegative);
double newNumber = round(number, roundingDouble, roundingDoubleReciprocal, roundingMode,
isNegative);
number = newNumber;
}
// Do this BEFORE checking to see if value is negative or infinite and
// before rounding.
number = multiply(number);
boolean isNegative = isNegative(number);
number = round(number);
if (Double.isInfinite(number)) {
int prefixLen = appendAffix(result, isNegative, true, parseAttr);
@ -867,6 +889,32 @@ public class DecimalFormat extends NumberFormat {
}
}
/**
* This is a special function used by the CompactDecimalFormat subclass.
* It completes only the rounding portion of the formatting and returns
* the resulting double. CompactDecimalFormat uses the result to compute
* the plural form to use.
*
* @param number The number to format.
* @return The number rounded to the correct number of significant digits
* with negative sign stripped off.
* @internal
* @deprecated
*/
@Deprecated
double adjustNumberAsInFormatting(double number) {
if (Double.isNaN(number)) {
return number;
}
number = round(multiply(number));
if (Double.isInfinite(number)) {
return number;
}
DigitList dl = new DigitList();
dl.set(number, precision(false), false);
return dl.getDouble();
}
/**
* 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
@ -966,6 +1014,7 @@ public class DecimalFormat extends NumberFormat {
* @stable ICU 2.0
*/
// [Spark/CDL] Delegate to format_long_StringBuffer_FieldPosition_boolean
@Override
public StringBuffer format(long number, StringBuffer result, FieldPosition fieldPosition) {
return format(number, result, fieldPosition, false);
}
@ -1020,6 +1069,7 @@ public class DecimalFormat extends NumberFormat {
*
* @stable ICU 2.0
*/
@Override
public StringBuffer format(BigInteger number, StringBuffer result,
FieldPosition fieldPosition) {
return format(number, result, fieldPosition, false);
@ -1051,6 +1101,7 @@ public class DecimalFormat extends NumberFormat {
*
* @stable ICU 2.0
*/
@Override
public StringBuffer format(java.math.BigDecimal number, StringBuffer result,
FieldPosition fieldPosition) {
return format(number, result, fieldPosition, false);
@ -1080,6 +1131,7 @@ public class DecimalFormat extends NumberFormat {
*
* @stable ICU 2.0
*/
@Override
public StringBuffer format(BigDecimal number, StringBuffer result,
FieldPosition fieldPosition) {
// This method is just a copy of the corresponding java.math.BigDecimal method
@ -1211,7 +1263,7 @@ public class DecimalFormat extends NumberFormat {
int i;
char [] digits = symbols.getDigitsLocal();
char grouping = currencySignCount > 0 ? symbols.getMonetaryGroupingSeparator() :
symbols.getGroupingSeparator();
char decimal = currencySignCount > 0 ? symbols.getMonetaryDecimalSeparator() :
@ -1638,6 +1690,7 @@ public class DecimalFormat extends NumberFormat {
* <code>null</code> if the parse failed
* @stable ICU 2.0
*/
@Override
public Number parse(String text, ParsePosition parsePosition) {
return (Number) parse(text, parsePosition, null);
}
@ -1657,6 +1710,7 @@ public class DecimalFormat extends NumberFormat {
* @draft ICU 49
* @provisional This API might change or be removed in a future release.
*/
@Override
public CurrencyAmount parseCurrency(CharSequence text, ParsePosition pos) {
Currency[] currency = new Currency[1];
return (CurrencyAmount) parse(text.toString(), pos, currency);
@ -2180,7 +2234,7 @@ public class DecimalFormat extends NumberFormat {
break;
}
}
if (digit == 0) {
@ -2457,7 +2511,7 @@ public class DecimalFormat extends NumberFormat {
return true;
}
// Utility method used to count the number of codepoints
// Utility method used to count the number of codepoints
private int countCodePoints(String str,int start, int end) {
int count = 0;
int index = start;
@ -3069,6 +3123,7 @@ public class DecimalFormat extends NumberFormat {
* @see java.math.BigDecimal
* @stable ICU 2.0
*/
@Override
public int getRoundingMode() {
return roundingMode;
}
@ -3086,6 +3141,7 @@ public class DecimalFormat extends NumberFormat {
* @see java.math.BigDecimal
* @stable ICU 2.0
*/
@Override
public void setRoundingMode(int roundingMode) {
if (roundingMode < BigDecimal.ROUND_UP || roundingMode > BigDecimal.ROUND_UNNECESSARY) {
throw new IllegalArgumentException("Invalid rounding mode: " + roundingMode);
@ -3094,7 +3150,7 @@ public class DecimalFormat extends NumberFormat {
this.roundingMode = roundingMode;
if (getRoundingIncrement() == null) {
setRoundingIncrement(Math.pow(10.0, (double) -getMaximumFractionDigits()));
setRoundingIncrement(Math.pow(10.0, -getMaximumFractionDigits()));
}
}
@ -3501,6 +3557,7 @@ public class DecimalFormat extends NumberFormat {
* Overrides clone.
* @stable ICU 2.0
*/
@Override
public Object clone() {
try {
DecimalFormat other = (DecimalFormat) super.clone();
@ -3524,6 +3581,7 @@ public class DecimalFormat extends NumberFormat {
* Overrides equals.
* @stable ICU 2.0
*/
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
@ -3604,6 +3662,7 @@ public class DecimalFormat extends NumberFormat {
* Overrides hashCode.
* @stable ICU 2.0
*/
@Override
public int hashCode() {
return super.hashCode() * 37 + positivePrefix.hashCode();
// just enough fields for a reasonable distribution
@ -3896,6 +3955,7 @@ public class DecimalFormat extends NumberFormat {
*
* @stable ICU 3.6
*/
@Override
public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
if (!(obj instanceof Number))
throw new IllegalArgumentException();
@ -4742,6 +4802,7 @@ public class DecimalFormat extends NumberFormat {
* @see NumberFormat#setMaximumIntegerDigits
* @stable ICU 2.0
*/
@Override
public void setMaximumIntegerDigits(int newValue) {
super.setMaximumIntegerDigits(Math.min(newValue, DOUBLE_INTEGER_DIGITS));
}
@ -4753,6 +4814,7 @@ public class DecimalFormat extends NumberFormat {
* @see NumberFormat#setMinimumIntegerDigits
* @stable ICU 2.0
*/
@Override
public void setMinimumIntegerDigits(int newValue) {
super.setMinimumIntegerDigits(Math.min(newValue, DOUBLE_INTEGER_DIGITS));
}
@ -4853,6 +4915,7 @@ public class DecimalFormat extends NumberFormat {
* @param theCurrency new currency object to use. Must not be null.
* @stable ICU 2.2
*/
@Override
public void setCurrency(Currency theCurrency) {
// If we are a currency format, then modify our affixes to
// encode the currency symbol for the given currency in our
@ -4890,6 +4953,8 @@ public class DecimalFormat extends NumberFormat {
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
@Override
protected Currency getEffectiveCurrency() {
Currency c = getCurrency();
if (c == null) {
@ -4905,6 +4970,7 @@ public class DecimalFormat extends NumberFormat {
* @see NumberFormat#setMaximumFractionDigits
* @stable ICU 2.0
*/
@Override
public void setMaximumFractionDigits(int newValue) {
super.setMaximumFractionDigits(Math.min(newValue, DOUBLE_FRACTION_DIGITS));
}
@ -4916,6 +4982,7 @@ public class DecimalFormat extends NumberFormat {
* @see NumberFormat#setMinimumFractionDigits
* @stable ICU 2.0
*/
@Override
public void setMinimumFractionDigits(int newValue) {
super.setMinimumFractionDigits(Math.min(newValue, DOUBLE_FRACTION_DIGITS));
}
@ -5514,7 +5581,7 @@ public class DecimalFormat extends NumberFormat {
private String posPrefixPatternForCurrency = null;
// positive suffix pattern
private String posSuffixPatternForCurrency = null;
private int patternType;
private final int patternType;
public AffixForCurrency(String negPrefix, String negSuffix, String posPrefix,
String posSuffix, int type) {

View File

@ -37,35 +37,57 @@ public class CompactDecimalFormatTest extends TestFmwk {
{123456789012345f, "120T"},
{12345678901234567890f, "12000000T"},
};
Object[][] SerbianTestData = {
{1234f, "1200"},
{12345f, "12\u00a0\u0445\u0438\u0459"},
{123456f, "120\u00a0\u0445\u0438\u0459"},
{1234567f, "1,2\u00a0\u043c\u0438\u043b"},
{12345678f, "12\u00a0\u043c\u0438\u043b"},
{123456789f, "120\u00a0\u043c\u0438\u043b"},
{1234567890f, "1,2\u00a0\u043c\u043b\u0440\u0434"},
{12345678901f, "12\u00a0\u043c\u043b\u0440\u0434"},
{123456789012f, "120\u00a0\u043c\u043b\u0440\u0434"},
{1234567890123f, "1,2\u00a0\u0431\u0438\u043b"},
{12345678901234f, "12\u00a0\u0431\u0438\u043b"},
{123456789012345f, "120\u00a0\u0431\u0438\u043b"},
Object[][] SerbianTestDataShort = {
{1234, "1200"},
{12345, "12K"},
{20789, "21\u00a0хиљ"},
{123456, "120\u00a0хиљ"},
{1234567, "1,2\u00a0мил"},
{12345678, "12\u00a0мил"},
{123456789, "120\u00a0мил"},
{1234567890, "1,2\u00a0млрд"},
{12345678901f, "12\u00a0млрд"},
{123456789012f, "120\u00a0млрд"},
{1234567890123f, "1,2\u00a0бил"},
{12345678901234f, "12\u00a0бил"},
{123456789012345f, "120\u00a0бил"},
{1234567890123456f, "1200\u00a0бил"},
};
Object[][] JapaneseTestData = {
{1234f, "1.2\u5343"},
{12345f, "1.2\u4E07"},
{123456f, "12\u4E07"},
{1234567f, "120\u4E07"},
{12345678f, "1200\u4E07"},
{123456789f, "1.2\u5104"},
{1234567890f, "12\u5104"},
{12345678901f, "120\u5104"},
{123456789012f, "1200\u5104"},
{1234567890123f, "1.2\u5146"},
{12345678901234f, "12\u5146"},
{123456789012345f, "120\u5146"},
Object[][] SerbianTestDataLong = {
{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万"},
{123456f, "12万"},
{1234567f, "120万"},
{12345678f, "1200万"},
{123456789f, "1.2億"},
{1234567890f, "12億"},
{12345678901f, "120億"},
{123456789012f, "1200億"},
{1234567890123f, "1.2兆"},
{12345678901234f, "12兆"},
{123456789012345f, "120兆"},
};
Object[][] SwahiliTestData = {
@ -83,29 +105,28 @@ public class CompactDecimalFormatTest extends TestFmwk {
{12345678901234567890f, "T12000000"},
};
public void TestEnglish() {
checkLocale(ULocale.ENGLISH, EnglishTestData);
}
public void TestSerbian() {
checkLocale(ULocale.forLanguageTag("sr"), SerbianTestData);
public void TestEnglishShort() {
checkLocale(ULocale.ENGLISH, CompactStyle.SHORT, EnglishTestData);
}
public void TestJapanese() {
checkLocale(ULocale.JAPANESE, JapaneseTestData);
public void TestSerbianShort() {
checkLocale(ULocale.forLanguageTag("sr"), CompactStyle.SHORT, SerbianTestDataShort);
}
public void TestJapaneseGermany() {
// check fallback.
checkLocale(ULocale.forLanguageTag("ja-DE"), JapaneseTestData);
public void TestSerbianLong() {
checkLocale(ULocale.forLanguageTag("sr"), CompactStyle.LONG, SerbianTestDataLong);
}
public void TestSwahili() {
checkLocale(ULocale.forLanguageTag("sw"), SwahiliTestData);
public void TestJapaneseShort() {
checkLocale(ULocale.JAPANESE, CompactStyle.SHORT, JapaneseTestData);
}
public void checkLocale(ULocale locale, Object[][] testData) {
CompactDecimalFormat cdf = NumberFormat.getCompactDecimalInstance(locale, CompactStyle.SHORT);
public void TestSwahiliShort() {
checkLocale(ULocale.forLanguageTag("sw"), CompactStyle.SHORT, SwahiliTestData);
}
public void checkLocale(ULocale locale, CompactStyle style, Object[][] testData) {
CompactDecimalFormat cdf = NumberFormat.getCompactDecimalInstance(locale, style);
for (Object[] row : testData) {
assertEquals(locale + " (" + locale.getDisplayName(locale) + ")", row[1], cdf.format(row[0]));
}