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; package com.ibm.icu.text;
import java.util.HashMap;
import java.util.Map;
import com.ibm.icu.impl.ICUCache; import com.ibm.icu.impl.ICUCache;
import com.ibm.icu.impl.ICUResourceBundle; import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.SimpleCache; import com.ibm.icu.impl.SimpleCache;
@ -19,30 +22,42 @@ import com.ibm.icu.util.UResourceBundle;
*/ */
class CompactDecimalDataCache { class CompactDecimalDataCache {
private static final int MAX_DIGITS = 15; static final String OTHER = "other";
private final ICUCache<ULocale, Data> cache = new SimpleCache<ULocale, Data>();
/**
* 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 * 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 * of one array and two hashmaps. The index of the divisors array as well
* being formatted, so when formatting 12,345, the 4th index of the arrays should * as the arrays stored in the values of the two hashmaps correspond
* be used. Divisors contain the number to divide by before doing formatting. * to log10 of the number being formatted, so when formatting 12,345, the 4th
* In the case of english, <code>divisors[4]</code> is 1000. So to format * index of the arrays should be used. Divisors contain the number to divide
* 12,345, divide by 1000 to get 12. prefix and suffix contain the prefix and * by before doing formatting. In the case of english, <code>divisors[4]</code>
* suffix to use, for english, <code>suffix[4]</code> is "K" So ultimately, * is 1000. So to format 12,345, divide by 1000 to get 12. Then use
* 12,345 is formatted as 12K. * 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. * Each array in data is 15 in length, and every index is filled.
* *
* @author Travis Keep * @author Travis Keep
* * *
*/ */
static class Data { static class Data {
long[] divisors; long[] divisors;
String[] prefixes; Map<String, String[]> prefixes;
String[] suffixes; 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.divisors = divisors;
this.prefixes = prefixes; this.prefixes = prefixes;
this.suffixes = suffixes; 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) { static class DataBundle {
Data result = cache.get(locale); 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) { if (result == null) {
result = load(locale); result = load(locale);
cache.put(locale, result); cache.put(locale, result);
@ -61,16 +95,54 @@ class CompactDecimalDataCache {
return result; 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); NumberingSystem ns = NumberingSystem.getInstance(ulocale);
ICUResourceBundle r = (ICUResourceBundle)UResourceBundle. ICUResourceBundle r = (ICUResourceBundle)UResourceBundle.
getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, ulocale); 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(); int size = r.getSize();
Data result = new Data( 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++) { for (int i = 0; i < size; i++) {
populateData((ICUResourceBundle) r.get(i), result); populateData(r.get(i), locale, style, result);
} }
fillInMissing(result); fillInMissing(result);
return result; return result;
@ -78,36 +150,114 @@ class CompactDecimalDataCache {
/** /**
* Populates Data object with data for a particular divisor from resource bundle. * 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) { private static void populateData(
long divisor = Long.parseLong(divisorData.getKey()); UResourceBundle divisorData, ULocale locale, String style, Data result) {
int thisIndex = (int) Math.log10(divisor); // 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. // Silently ignore divisors that are too big.
if (thisIndex >= MAX_DIGITS) { if (thisIndex >= MAX_DIGITS) {
return; 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++) { for (int i = 1; i < numZeros; i++) {
divisor /= 10; divisor /= 10;
} }
result.divisors[thisIndex] = divisor; result.divisors[thisIndex] = divisor;
} }
/** /**
* Extracts the prefix and suffix from the template and places them in the * Populates prefix and suffix information for a particular plural variant
* Data object. * and index (log10 value).
* @param template the number template, e.g 000K * @param pluralVariant e.g "one", "other"
* @param idx the index to store the extracted prefix and suffix * @param idx the index (log10 value of the number) 0 <= idx < MAX_DIGITS
* @param result Data object modified in-place here. * @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. * @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 firstIdx = template.indexOf("0");
int lastIdx = template.lastIndexOf("0"); int lastIdx = template.lastIndexOf("0");
result.prefixes[idx] = template.substring(0, firstIdx); if (firstIdx == -1) {
result.suffixes[idx] = template.substring(lastIdx + 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. // Calculate number of zeros before decimal point.
int i = firstIdx + 1; int i = firstIdx + 1;
while (i <= lastIdx && template.charAt(i) == '0') { while (i <= lastIdx && template.charAt(i) == '0') {
@ -116,33 +266,97 @@ class CompactDecimalDataCache {
return i - firstIdx; 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 * After reading information from resource bundle into a Data object, there
* is no guarantee that every index of the arrays will be filled. * is guarantee that it is complete.
* *
* This function walks through the arrays filling in indexes with missing * This method fixes any incomplete data it finds within <code>result</code>.
* data from the previous index. If the first indexes are missing data, * It looks at each log10 value applying the two rules.
* they are assumed to have no prefixes or suffixes and a divisor of 1. * <p>
* We assume an index has missing data if the corresponding element in the * If no prefix is defined for the "other" variant, use the divisor, prefixes and
* prefixes array is null. * 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. * @param result this instance is fixed in-place.
*/ */
private static void fillInMissing(Data result) { private static void fillInMissing(Data result) {
// Initially we assume that previous divisor is 1 with no prefix or suffix. // Initially we assume that previous divisor is 1 with no prefix or suffix.
long lastDivisor = 1L; long lastDivisor = 1L;
String lastPrefix = "";
String lastSuffix = "";
for (int i = 0; i < result.divisors.length; i++) { 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.divisors[i] = lastDivisor;
result.prefixes[i] = lastPrefix; copyFromPreviousIndex(i, result.prefixes);
result.suffixes[i] = lastSuffix; copyFromPreviousIndex(i, result.suffixes);
} else { } else {
lastDivisor = result.divisors[i]; lastDivisor = result.divisors[i];
lastPrefix = result.prefixes[i]; propagateOtherToMissing(i, result.prefixes);
lastSuffix = result.suffixes[i]; 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.HashMap;
import java.util.Map; import java.util.Map;
import com.ibm.icu.text.CompactDecimalDataCache.Data;
import com.ibm.icu.util.ULocale; 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. * @provisional This API might change or be removed in a future release.
*/ */
public class CompactDecimalFormat extends DecimalFormat { public class CompactDecimalFormat extends DecimalFormat {
private static final long serialVersionUID = 4716293295276629682L; 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 int POSITIVE_PREFIX = 0, POSITIVE_SUFFIX = 1, AFFIX_SIZE = 2;
private static final CompactDecimalDataCache cache = new CompactDecimalDataCache(); private static final CompactDecimalDataCache cache = new CompactDecimalDataCache();
private final String[] prefix; private final Map<String, String[]> prefix;
private final String[] suffix; private final Map<String, String[]> suffix;
private final long[] divisor; private final long[] divisor;
private final String[] currencyAffixes; private final String[] currencyAffixes;
// null if created internally using explicit prefixes and suffixes.
private final PluralRules pluralRules;
/** /**
* The public mechanism is NumberFormat.getCompactDecimalInstance(). * The public mechanism is NumberFormat.getCompactDecimalInstance().
* *
@ -66,16 +70,16 @@ public class CompactDecimalFormat extends DecimalFormat {
*/ */
CompactDecimalFormat(ULocale locale, CompactStyle style) { CompactDecimalFormat(ULocale locale, CompactStyle style) {
DecimalFormat format = (DecimalFormat) NumberFormat.getInstance(locale); DecimalFormat format = (DecimalFormat) NumberFormat.getInstance(locale);
CompactDecimalDataCache.Data data = cache.get(locale); CompactDecimalDataCache.Data data = getData(locale, style);
this.prefix = data.prefixes; this.prefix = data.prefixes;
this.suffix = data.suffixes; this.suffix = data.suffixes;
this.divisor = data.divisors; this.divisor = data.divisors;
// TODO fix to consider plural form when choosing a prefix or suffix.
applyPattern(format.toPattern()); applyPattern(format.toPattern());
setDecimalFormatSymbols(format.getDecimalFormatSymbols()); setDecimalFormatSymbols(format.getDecimalFormatSymbols());
setMaximumSignificantDigits(2); // default significant digits setMaximumSignificantDigits(2); // default significant digits
setSignificantDigitsUsed(true); setSignificantDigitsUsed(true);
setGroupingUsed(false); setGroupingUsed(false);
this.pluralRules = PluralRules.forLocale(locale);
DecimalFormat currencyFormat = (DecimalFormat) NumberFormat.getCurrencyInstance(locale); DecimalFormat currencyFormat = (DecimalFormat) NumberFormat.getCurrencyInstance(locale);
currencyAffixes = new String[AFFIX_SIZE]; currencyAffixes = new String[AFFIX_SIZE];
@ -108,8 +112,8 @@ public class CompactDecimalFormat extends DecimalFormat {
*/ */
public CompactDecimalFormat(String pattern, DecimalFormatSymbols formatSymbols, String[] prefix, String[] suffix, public CompactDecimalFormat(String pattern, DecimalFormatSymbols formatSymbols, String[] prefix, String[] suffix,
long[] divisor, Collection<String> debugCreationErrors, CompactStyle style, String[] currencyAffixes) { long[] divisor, Collection<String> debugCreationErrors, CompactStyle style, String[] currencyAffixes) {
if (prefix.length < MINIMUM_ARRAY_LENGTH) { if (prefix.length < CompactDecimalDataCache.MAX_DIGITS) {
recordError(debugCreationErrors, "Must have at least " + MINIMUM_ARRAY_LENGTH + " prefix items."); recordError(debugCreationErrors, "Must have at least " + CompactDecimalDataCache.MAX_DIGITS + " prefix items.");
} }
if (prefix.length != suffix.length || prefix.length != divisor.length) { if (prefix.length != suffix.length || prefix.length != divisor.length) {
recordError(debugCreationErrors, "Prefix, suffix, and divisor arrays must have the same 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]; oldDivisor = divisor[i];
} }
this.prefix = prefix.clone(); this.prefix = otherPluralVariant(prefix);
this.suffix = suffix.clone(); this.suffix = otherPluralVariant(suffix);
this.divisor = divisor.clone(); this.divisor = divisor.clone();
applyPattern(pattern); applyPattern(pattern);
setDecimalFormatSymbols(formatSymbols); setDecimalFormatSymbols(formatSymbols);
@ -157,6 +161,7 @@ public class CompactDecimalFormat extends DecimalFormat {
setSignificantDigitsUsed(true); setSignificantDigitsUsed(true);
setGroupingUsed(false); setGroupingUsed(false);
this.currencyAffixes = currencyAffixes.clone(); this.currencyAffixes = currencyAffixes.clone();
this.pluralRules = null;
} }
/** /**
@ -169,12 +174,19 @@ public class CompactDecimalFormat extends DecimalFormat {
if (number < 0.0d) { if (number < 0.0d) {
throw new UnsupportedOperationException("CompactDecimalFormat doesn't handle negative numbers yet."); throw new UnsupportedOperationException("CompactDecimalFormat doesn't handle negative numbers yet.");
} }
int integerCount = number <= 1.0d ? 0 : (int) Math.log10(number); // We do this here so that the prefix or suffix we choose is always consistent
int base = integerCount > 14 ? 14 : integerCount; // 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]; number = number / divisor[base];
setPositivePrefix(prefix[base]); String pluralVariant = getPluralForm(number);
setPositiveSuffix(suffix[base]); setPositivePrefix(CompactDecimalDataCache.getPrefixOrSuffix(prefix, pluralVariant, base));
setPositiveSuffix(CompactDecimalDataCache.getPrefixOrSuffix(suffix, pluralVariant, base));
setCurrency(null); setCurrency(null);
return super.format(number, toAppendTo, pos); return super.format(number, toAppendTo, pos);
} }
@ -248,4 +260,37 @@ public class CompactDecimalFormat extends DecimalFormat {
} }
creationErrors.add(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());
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} * {@inheritDoc}
* @stable ICU 2.0 * @stable ICU 2.0
*/ */
@Override
public StringBuffer format(double number, StringBuffer result, FieldPosition fieldPosition) { public StringBuffer format(double number, StringBuffer result, FieldPosition fieldPosition) {
return format(number, result, fieldPosition, false); 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 // [Spark/CDL] The actual method to format number. If boolean value
// parseAttr == true, then attribute information will be recorded. // parseAttr == true, then attribute information will be recorded.
private StringBuffer format(double number, StringBuffer result, FieldPosition fieldPosition, private StringBuffer format(double number, StringBuffer result, FieldPosition fieldPosition,
@ -805,31 +847,11 @@ public class DecimalFormat extends NumberFormat {
return result; return result;
} }
// Do this BEFORE checking to see if value is infinite or negative! // Do this BEFORE checking to see if value is negative or infinite and
if (multiplier != 1) // before rounding.
number *= multiplier; number = multiply(number);
boolean isNegative = isNegative(number);
number = round(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.
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;
}
if (Double.isInfinite(number)) { if (Double.isInfinite(number)) {
int prefixLen = appendAffix(result, isNegative, true, parseAttr); 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, * 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 * 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 * @stable ICU 2.0
*/ */
// [Spark/CDL] Delegate to format_long_StringBuffer_FieldPosition_boolean // [Spark/CDL] Delegate to format_long_StringBuffer_FieldPosition_boolean
@Override
public StringBuffer format(long number, StringBuffer result, FieldPosition fieldPosition) { public StringBuffer format(long number, StringBuffer result, FieldPosition fieldPosition) {
return format(number, result, fieldPosition, false); return format(number, result, fieldPosition, false);
} }
@ -1020,6 +1069,7 @@ public class DecimalFormat extends NumberFormat {
* *
* @stable ICU 2.0 * @stable ICU 2.0
*/ */
@Override
public StringBuffer format(BigInteger number, StringBuffer result, public StringBuffer format(BigInteger number, StringBuffer result,
FieldPosition fieldPosition) { FieldPosition fieldPosition) {
return format(number, result, fieldPosition, false); return format(number, result, fieldPosition, false);
@ -1051,6 +1101,7 @@ public class DecimalFormat extends NumberFormat {
* *
* @stable ICU 2.0 * @stable ICU 2.0
*/ */
@Override
public StringBuffer format(java.math.BigDecimal number, StringBuffer result, public StringBuffer format(java.math.BigDecimal number, StringBuffer result,
FieldPosition fieldPosition) { FieldPosition fieldPosition) {
return format(number, result, fieldPosition, false); return format(number, result, fieldPosition, false);
@ -1080,6 +1131,7 @@ public class DecimalFormat extends NumberFormat {
* *
* @stable ICU 2.0 * @stable ICU 2.0
*/ */
@Override
public StringBuffer format(BigDecimal number, StringBuffer result, public StringBuffer format(BigDecimal number, StringBuffer result,
FieldPosition fieldPosition) { FieldPosition fieldPosition) {
// This method is just a copy of the corresponding java.math.BigDecimal method // This method is just a copy of the corresponding java.math.BigDecimal method
@ -1211,7 +1263,7 @@ public class DecimalFormat extends NumberFormat {
int i; int i;
char [] digits = symbols.getDigitsLocal(); char [] digits = symbols.getDigitsLocal();
char grouping = currencySignCount > 0 ? symbols.getMonetaryGroupingSeparator() : char grouping = currencySignCount > 0 ? symbols.getMonetaryGroupingSeparator() :
symbols.getGroupingSeparator(); symbols.getGroupingSeparator();
char decimal = currencySignCount > 0 ? symbols.getMonetaryDecimalSeparator() : char decimal = currencySignCount > 0 ? symbols.getMonetaryDecimalSeparator() :
@ -1638,6 +1690,7 @@ public class DecimalFormat extends NumberFormat {
* <code>null</code> if the parse failed * <code>null</code> if the parse failed
* @stable ICU 2.0 * @stable ICU 2.0
*/ */
@Override
public Number parse(String text, ParsePosition parsePosition) { public Number parse(String text, ParsePosition parsePosition) {
return (Number) parse(text, parsePosition, null); return (Number) parse(text, parsePosition, null);
} }
@ -1657,6 +1710,7 @@ public class DecimalFormat extends NumberFormat {
* @draft ICU 49 * @draft ICU 49
* @provisional This API might change or be removed in a future release. * @provisional This API might change or be removed in a future release.
*/ */
@Override
public CurrencyAmount parseCurrency(CharSequence text, ParsePosition pos) { public CurrencyAmount parseCurrency(CharSequence text, ParsePosition pos) {
Currency[] currency = new Currency[1]; Currency[] currency = new Currency[1];
return (CurrencyAmount) parse(text.toString(), pos, currency); return (CurrencyAmount) parse(text.toString(), pos, currency);
@ -2180,7 +2234,7 @@ public class DecimalFormat extends NumberFormat {
break; break;
} }
} }
if (digit == 0) { if (digit == 0) {
@ -2457,7 +2511,7 @@ public class DecimalFormat extends NumberFormat {
return true; 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) { private int countCodePoints(String str,int start, int end) {
int count = 0; int count = 0;
int index = start; int index = start;
@ -3069,6 +3123,7 @@ public class DecimalFormat extends NumberFormat {
* @see java.math.BigDecimal * @see java.math.BigDecimal
* @stable ICU 2.0 * @stable ICU 2.0
*/ */
@Override
public int getRoundingMode() { public int getRoundingMode() {
return roundingMode; return roundingMode;
} }
@ -3086,6 +3141,7 @@ public class DecimalFormat extends NumberFormat {
* @see java.math.BigDecimal * @see java.math.BigDecimal
* @stable ICU 2.0 * @stable ICU 2.0
*/ */
@Override
public void setRoundingMode(int roundingMode) { public void setRoundingMode(int roundingMode) {
if (roundingMode < BigDecimal.ROUND_UP || roundingMode > BigDecimal.ROUND_UNNECESSARY) { if (roundingMode < BigDecimal.ROUND_UP || roundingMode > BigDecimal.ROUND_UNNECESSARY) {
throw new IllegalArgumentException("Invalid rounding mode: " + roundingMode); throw new IllegalArgumentException("Invalid rounding mode: " + roundingMode);
@ -3094,7 +3150,7 @@ public class DecimalFormat extends NumberFormat {
this.roundingMode = roundingMode; this.roundingMode = roundingMode;
if (getRoundingIncrement() == null) { 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. * Overrides clone.
* @stable ICU 2.0 * @stable ICU 2.0
*/ */
@Override
public Object clone() { public Object clone() {
try { try {
DecimalFormat other = (DecimalFormat) super.clone(); DecimalFormat other = (DecimalFormat) super.clone();
@ -3524,6 +3581,7 @@ public class DecimalFormat extends NumberFormat {
* Overrides equals. * Overrides equals.
* @stable ICU 2.0 * @stable ICU 2.0
*/ */
@Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (obj == null) if (obj == null)
return false; return false;
@ -3604,6 +3662,7 @@ public class DecimalFormat extends NumberFormat {
* Overrides hashCode. * Overrides hashCode.
* @stable ICU 2.0 * @stable ICU 2.0
*/ */
@Override
public int hashCode() { public int hashCode() {
return super.hashCode() * 37 + positivePrefix.hashCode(); return super.hashCode() * 37 + positivePrefix.hashCode();
// just enough fields for a reasonable distribution // just enough fields for a reasonable distribution
@ -3896,6 +3955,7 @@ public class DecimalFormat extends NumberFormat {
* *
* @stable ICU 3.6 * @stable ICU 3.6
*/ */
@Override
public AttributedCharacterIterator formatToCharacterIterator(Object obj) { public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
if (!(obj instanceof Number)) if (!(obj instanceof Number))
throw new IllegalArgumentException(); throw new IllegalArgumentException();
@ -4742,6 +4802,7 @@ public class DecimalFormat extends NumberFormat {
* @see NumberFormat#setMaximumIntegerDigits * @see NumberFormat#setMaximumIntegerDigits
* @stable ICU 2.0 * @stable ICU 2.0
*/ */
@Override
public void setMaximumIntegerDigits(int newValue) { public void setMaximumIntegerDigits(int newValue) {
super.setMaximumIntegerDigits(Math.min(newValue, DOUBLE_INTEGER_DIGITS)); super.setMaximumIntegerDigits(Math.min(newValue, DOUBLE_INTEGER_DIGITS));
} }
@ -4753,6 +4814,7 @@ public class DecimalFormat extends NumberFormat {
* @see NumberFormat#setMinimumIntegerDigits * @see NumberFormat#setMinimumIntegerDigits
* @stable ICU 2.0 * @stable ICU 2.0
*/ */
@Override
public void setMinimumIntegerDigits(int newValue) { public void setMinimumIntegerDigits(int newValue) {
super.setMinimumIntegerDigits(Math.min(newValue, DOUBLE_INTEGER_DIGITS)); 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. * @param theCurrency new currency object to use. Must not be null.
* @stable ICU 2.2 * @stable ICU 2.2
*/ */
@Override
public void setCurrency(Currency theCurrency) { public void setCurrency(Currency theCurrency) {
// If we are a currency format, then modify our affixes to // If we are a currency format, then modify our affixes to
// encode the currency symbol for the given currency in our // encode the currency symbol for the given currency in our
@ -4890,6 +4953,8 @@ public class DecimalFormat extends NumberFormat {
* @internal * @internal
* @deprecated This API is ICU internal only. * @deprecated This API is ICU internal only.
*/ */
@Deprecated
@Override
protected Currency getEffectiveCurrency() { protected Currency getEffectiveCurrency() {
Currency c = getCurrency(); Currency c = getCurrency();
if (c == null) { if (c == null) {
@ -4905,6 +4970,7 @@ public class DecimalFormat extends NumberFormat {
* @see NumberFormat#setMaximumFractionDigits * @see NumberFormat#setMaximumFractionDigits
* @stable ICU 2.0 * @stable ICU 2.0
*/ */
@Override
public void setMaximumFractionDigits(int newValue) { public void setMaximumFractionDigits(int newValue) {
super.setMaximumFractionDigits(Math.min(newValue, DOUBLE_FRACTION_DIGITS)); super.setMaximumFractionDigits(Math.min(newValue, DOUBLE_FRACTION_DIGITS));
} }
@ -4916,6 +4982,7 @@ public class DecimalFormat extends NumberFormat {
* @see NumberFormat#setMinimumFractionDigits * @see NumberFormat#setMinimumFractionDigits
* @stable ICU 2.0 * @stable ICU 2.0
*/ */
@Override
public void setMinimumFractionDigits(int newValue) { public void setMinimumFractionDigits(int newValue) {
super.setMinimumFractionDigits(Math.min(newValue, DOUBLE_FRACTION_DIGITS)); super.setMinimumFractionDigits(Math.min(newValue, DOUBLE_FRACTION_DIGITS));
} }
@ -5514,7 +5581,7 @@ public class DecimalFormat extends NumberFormat {
private String posPrefixPatternForCurrency = null; private String posPrefixPatternForCurrency = null;
// positive suffix pattern // positive suffix pattern
private String posSuffixPatternForCurrency = null; private String posSuffixPatternForCurrency = null;
private int patternType; private final int patternType;
public AffixForCurrency(String negPrefix, String negSuffix, String posPrefix, public AffixForCurrency(String negPrefix, String negSuffix, String posPrefix,
String posSuffix, int type) { String posSuffix, int type) {

View File

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