import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.impl.TextTrieMap;
-import com.ibm.icu.impl.number.Parse.ParseMode;
import com.ibm.icu.impl.number.formatters.BigDecimalMultiplier;
import com.ibm.icu.impl.number.formatters.CurrencyFormat;
import com.ibm.icu.impl.number.formatters.MagnitudeMultiplier;
import com.ibm.icu.impl.number.formatters.PaddingFormat;
import com.ibm.icu.impl.number.formatters.PositiveDecimalFormat;
import com.ibm.icu.impl.number.formatters.PositiveNegativeAffixFormat;
+import com.ibm.icu.impl.number.formatters.ScientificFormat;
import com.ibm.icu.lang.UCharacter;
import com.ibm.icu.text.CurrencyPluralInfo;
import com.ibm.icu.text.DecimalFormatSymbols;
CurrencyFormat.ICurrencyProperties,
BigDecimalMultiplier.IProperties,
MagnitudeMultiplier.IProperties,
- PositiveDecimalFormat.IProperties {
+ PositiveDecimalFormat.IProperties,
+ ScientificFormat.IProperties {
boolean DEFAULT_PARSE_INTEGER_ONLY = false;
}
/**
- * @see #parse(String, ParsePosition, ParseMode, boolean, boolean, IProperties,
+ * @see Parse#parse(String, ParsePosition, ParseMode, boolean, boolean, IProperties,
* DecimalFormatSymbols)
*/
private static enum StateName {
boolean sawPrefix;
boolean sawSuffix;
boolean sawDecimalPoint;
+ boolean sawExponentDigit;
// Data for intermediate parsing steps:
StateName returnTo1;
// For string literals:
CharSequence currentString;
int currentOffset;
+ boolean currentTrailing;
// For affix patterns:
CharSequence currentAffixPattern;
long currentStepwiseParserTag;
sawPrefix = false;
sawSuffix = false;
sawDecimalPoint = false;
+ sawExponentDigit = false;
// Data for intermediate parsing steps:
returnTo1 = null;
returnTo2 = null;
currentString = null;
currentOffset = 0;
+ currentTrailing = false;
currentAffixPattern = null;
currentStepwiseParserTag = 0L;
currentCurrencyTrieState = null;
sawPrefix = other.sawPrefix;
sawSuffix = other.sawSuffix;
sawDecimalPoint = other.sawDecimalPoint;
+ sawExponentDigit = other.sawExponentDigit;
// Data for intermediate parsing steps:
returnTo1 = other.returnTo1;
returnTo2 = other.returnTo2;
currentString = other.currentString;
currentOffset = other.currentOffset;
+ currentTrailing = other.currentTrailing;
currentAffixPattern = other.currentAffixPattern;
currentStepwiseParserTag = other.currentStepwiseParserTag;
currentCurrencyTrieState = other.currentCurrencyTrieState;
*/
void appendDigit(byte digit, DigitType type) {
if (type == DigitType.EXPONENT) {
+ sawExponentDigit = true;
int newExponent = exponent * 10 + digit;
if (newExponent < exponent) {
// overflow
int groupingCp2;
SeparatorType decimalType1;
SeparatorType decimalType2;
+ // TODO(sffc): Remove this field if it is not necessary.
+ @SuppressWarnings("unused")
SeparatorType groupingType1;
+ // TODO(sffc): Remove this field if it is not necessary.
+ @SuppressWarnings("unused")
SeparatorType groupingType2;
TextTrieMap<Byte> digitTrie;
Set<AffixHolder> affixHolders = new HashSet<AffixHolder>();
continue;
}
+ // Check for scientific notation.
+ if (properties.getMinimumExponentDigits() > 0 && !item.sawExponentDigit) {
+ if (DEBUGGING) System.out.println("-> reject due to lack of exponent");
+ continue;
+ }
+
// Check that grouping sizes are valid.
int grouping1 = properties.getGroupingSize();
int grouping2 = properties.getSecondaryGroupingSize();
private static void acceptNan(int cp, StateName nextName, ParserState state, StateItem item) {
CharSequence nan = state.symbols.getNaN();
- long added = acceptString(cp, nextName, null, state, item, nan, 0);
+ long added = acceptString(cp, nextName, null, state, item, nan, 0, false);
// Set state in the items that were added by the function call
for (int i = Long.numberOfTrailingZeros(added); (1L << i) <= added; i++) {
private static void acceptInfinity(
int cp, StateName nextName, ParserState state, StateItem item) {
CharSequence inf = state.symbols.getInfinity();
- long added = acceptString(cp, nextName, null, state, item, inf, 0);
+ long added = acceptString(cp, nextName, null, state, item, inf, 0, false);
// Set state in the items that were added by the function call
for (int i = Long.numberOfTrailingZeros(added); (1L << i) <= added; i++) {
private static void acceptExponentSeparator(
int cp, StateName nextName, ParserState state, StateItem item) {
CharSequence exp = state.symbols.getExponentSeparator();
- acceptString(cp, nextName, null, state, item, exp, 0);
+ acceptString(cp, nextName, null, state, item, exp, 0, true);
}
private static void acceptPrefix(int cp, StateName nextName, ParserState state, StateItem item) {
if (holder == null) return;
String str = prefix ? holder.p : holder.s;
if (holder.strings) {
- long added = acceptString(cp, nextName, null, state, item, str, 0);
+ long added = acceptString(cp, nextName, null, state, item, str, 0, false);
// At most one item can be added upon consuming a string.
if (added != 0) {
int i = state.lastInsertedIndex();
private static void acceptStringOffset(int cp, ParserState state, StateItem item) {
acceptString(
- cp, item.returnTo1, item.returnTo2, state, item, item.currentString, item.currentOffset);
+ cp,
+ item.returnTo1,
+ item.returnTo2,
+ state,
+ item,
+ item.currentString,
+ item.currentOffset,
+ item.currentTrailing);
}
/**
* @param returnTo1 The state to return to after reaching the end of the string.
* @param returnTo2 The state to save in <code>returnTo1</code> after reaching the end of the
* string. Set to null if returning to the main state loop.
+ * @param trailing true if this string should be ignored for the purposes of recording trailing
+ * code points; false if it trailing count should be reset after reading the string.
* @param state The current {@link ParserState}
* @param item The current {@link StateItem}
* @param str The string against which to check for a match.
ParserState state,
StateItem item,
CharSequence str,
- int offset) {
+ int offset,
+ boolean trailing) {
if (str == null || str.length() == 0) return 0L;
// Fast path for fast mode
next.returnTo2 = returnTo2;
next.currentString = str;
next.currentOffset = offset;
+ next.currentTrailing = trailing;
} else {
// We've reached the end of the string.
next.name = returnTo1;
- next.trailingCount = 0;
+ if (!trailing) next.trailingCount = 0;
next.returnTo1 = returnTo2;
next.returnTo2 = null;
}
resolvedPlusSign = true;
break;
case AffixPatternUtils.TYPE_PERCENT:
+ resolvedCp = '%'; // accept ASCII percent as well as locale percent
resolvedStr = state.symbols.getPercentString();
break;
case AffixPatternUtils.TYPE_PERMILLE:
+ resolvedCp = '‰'; // accept ASCII permille as well as locale permille
resolvedStr = state.symbols.getPerMillString();
break;
case AffixPatternUtils.TYPE_CURRENCY_SINGLE:
// String symbol
if (hasNext) {
added |=
- acceptString(cp, StateName.INSIDE_AFFIX_PATTERN, returnTo, state, item, resolvedStr, 0);
+ acceptString(
+ cp, StateName.INSIDE_AFFIX_PATTERN, returnTo, state, item, resolvedStr, 0, false);
} else {
- added |= acceptString(cp, returnTo, null, state, item, resolvedStr, 0);
+ added |= acceptString(cp, returnTo, null, state, item, resolvedStr, 0, false);
}
}
if (resolvedCurrency) {
str1 = state.symbols.getCurrencySymbol();
str2 = state.symbols.getInternationalCurrencySymbol();
}
- added |= acceptString(cp, returnTo1, returnTo2, state, item, str1, 0);
- added |= acceptString(cp, returnTo1, returnTo2, state, item, str2, 0);
+ added |= acceptString(cp, returnTo1, returnTo2, state, item, str1, 0, false);
+ added |= acceptString(cp, returnTo1, returnTo2, state, item, str2, 0, false);
for (int i = Long.numberOfTrailingZeros(added); (1L << i) <= added; i++) {
if (((1L << i) & added) != 0) {
state.getItem(i).sawCurrency = true;
int exponentDigits = 0;
boolean hasPercentSign = false;
boolean hasPerMilleSign = false;
- boolean hasCurrencySign = false;
StringBuilder padding = new StringBuilder();
StringBuilder prefix = new StringBuilder();
break;
case '¤':
- result.hasCurrencySign = true;
+ // no need to record that we saw it
break;
}
consumeLiteral(state, destination);
int count = ois.readInt();
// 2) Read each field by its name and value
- for (int i=0; i<count; i++) {
+ for (int i = 0; i < count; i++) {
String name = (String) ois.readObject();
Object value = ois.readObject();
public String toString() {
StringBuilder result = new StringBuilder();
result.append("<Properties");
+ toStringBare(result);
+ result.append(">");
+ return result.toString();
+ }
+
+ /**
+ * Appends a string containing properties that differ from the default, but without being
+ * surrounded by <Properties>.
+ */
+ public void toStringBare(StringBuilder result) {
Field[] fields = Properties.class.getDeclaredFields();
for (Field field : fields) {
Object myValue, defaultValue;
result.append(" " + field.getName() + ":" + myValue);
}
}
- result.append(">");
- return result.toString();
}
/**
input.setIntegerFractionLength(minInt, maxInt, minFrac, maxFrac);
}
- private static final ThreadLocal<Properties> threadLocalProperties =
- new ThreadLocal<Properties>() {
- @Override
- protected Properties initialValue() {
- return new Properties();
- }
- };
-
- /**
- * Gets a thread-local property bag that can be used to deliver properties to a constructor.
- * Rounders themselves are guaranteed to not internally use a copy of this property bag.
- *
- * @return A clean, thread-local property bag.
- */
- public static Properties getThreadLocalProperties() {
- return threadLocalProperties.get().clear();
- }
-
@Override
public void before(FormatQuantity input, ModifierHolder mods) {
apply(input);
import java.util.HashMap;
import java.util.Map;
+import java.util.MissingResourceException;
import com.ibm.icu.impl.ICUData;
import com.ibm.icu.impl.ICUResourceBundle;
return new CompactDecimalFormat(symbols, properties);
}
+ private static final int DEFAULT_MIN_SIG = 1;
+ private static final int DEFAULT_MAX_SIG = 2;
+ private static final SignificantDigitsMode DEFAULT_SIG_MODE =
+ SignificantDigitsMode.OVERRIDE_MAXIMUM_FRACTION;
+
+ private static final ThreadLocal<Properties> threadLocalProperties =
+ new ThreadLocal<Properties>() {
+ @Override
+ protected Properties initialValue() {
+ return new Properties();
+ }
+ };
+
private static Rounder getRounder(IProperties properties) {
// Use rounding settings if they were specified, or else use the default CDF rounder.
- Rounder rounder = RoundingFormat.getDefaultOrNull(properties);
+ // TODO: Detecting and overriding significant digits here is a bit of a hack, since detection
+ // is also performed in the "RoundingFormat.getDefaultOrNull" method.
+ // It would be more elegant to call some sort of "fallback" copy method.
+ Rounder rounder = null;
+ if (!SignificantDigitsRounder.useSignificantDigits(properties)) {
+ rounder = RoundingFormat.getDefaultOrNull(properties);
+ }
if (rounder == null) {
- rounder =
- SignificantDigitsRounder.getInstance(
- SignificantDigitsRounder.getThreadLocalProperties()
- .setMinimumSignificantDigits(1)
- .setMaximumSignificantDigits(2)
- .setSignificantDigitsMode(SignificantDigitsMode.OVERRIDE_MAXIMUM_FRACTION));
+ int _minSig = properties.getMinimumSignificantDigits();
+ int _maxSig = properties.getMaximumSignificantDigits();
+ SignificantDigitsMode _mode = properties.getSignificantDigitsMode();
+ Properties rprops = threadLocalProperties.get().clear();
+ // Settings needing possible override:
+ rprops.setMinimumSignificantDigits(_minSig > 0 ? _minSig : DEFAULT_MIN_SIG);
+ rprops.setMaximumSignificantDigits(_maxSig > 0 ? _maxSig : DEFAULT_MAX_SIG);
+ rprops.setSignificantDigitsMode(_mode != null ? _mode : DEFAULT_SIG_MODE);
+ // TODO: Should copyFrom() be used instead? It requires a cast.
+ // Settings to copy verbatim:
+ rprops.setRoundingMode(properties.getRoundingMode());
+ rprops.setMinimumFractionDigits(properties.getMinimumFractionDigits());
+ rprops.setMaximumFractionDigits(properties.getMaximumFractionDigits());
+ rprops.setMinimumIntegerDigits(properties.getMinimumIntegerDigits());
+ rprops.setMaximumIntegerDigits(properties.getMaximumIntegerDigits());
+ rounder = SignificantDigitsRounder.getInstance(rprops);
}
return rounder;
}
ULocale ulocale = symbols.getULocale();
CompactDecimalDataSink sink = new CompactDecimalDataSink(data, symbols, fingerprint);
String nsName = NumberingSystem.getInstance(ulocale).getName();
- ICUResourceBundle r =
+ ICUResourceBundle rb =
(ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, ulocale);
- r.getAllItemsWithFallback("NumberElements/" + nsName, sink);
+ internalPopulateData(nsName, rb, sink, data);
+ if (data.isEmpty() && fingerprint.compactStyle == CompactStyle.LONG) {
+ // No long data is available; load short data instead
+ sink.compactStyle = CompactStyle.SHORT;
+ internalPopulateData(nsName, rb, sink, data);
+ }
+ threadLocalDataCache.get().put(fingerprint, data);
+ return data;
+ }
+
+ private static void internalPopulateData(
+ String nsName, ICUResourceBundle rb, CompactDecimalDataSink sink, CompactDecimalData data) {
+ try {
+ rb.getAllItemsWithFallback("NumberElements/" + nsName, sink);
+ } catch (MissingResourceException e) {
+ // Fall back to latn
+ }
if (data.isEmpty() && !nsName.equals("latn")) {
- r.getAllItemsWithFallback("NumberElements/latn", sink);
+ rb.getAllItemsWithFallback("NumberElements/latn", sink);
}
if (sink.exception != null) {
throw sink.exception;
}
- threadLocalDataCache.get().put(fingerprint, data);
- return data;
}
private static PositiveNegativeModifier getDefaultMod(
currencySymbol = CurrencyFormat.getCurrencySymbol(symbols, properties);
} else {
compactType = CompactType.DECIMAL;
- currencySymbol = symbols.getCurrencySymbol(); // fallback; should remain unused
+ currencySymbol = ""; // fallback; should remain unused
}
compactStyle = properties.getCompactStyle();
uloc = symbols.getULocale();
private static final class CompactDecimalDataSink extends UResource.Sink {
- final CompactDecimalData data;
- final DecimalFormatSymbols symbols;
- final CompactStyle compactStyle;
- final CompactType compactType;
- final String currencySymbol;
- final PNAffixGenerator pnag;
+ CompactDecimalData data;
+ DecimalFormatSymbols symbols;
+ CompactStyle compactStyle;
+ CompactType compactType;
+ String currencySymbol;
+ PNAffixGenerator pnag;
IllegalArgumentException exception;
/*
import com.ibm.icu.impl.number.modifiers.GeneralPluralModifier;
import com.ibm.icu.impl.number.rounders.IncrementRounder;
import com.ibm.icu.impl.number.rounders.MagnitudeRounder;
+import com.ibm.icu.impl.number.rounders.SignificantDigitsRounder;
import com.ibm.icu.text.CurrencyPluralInfo;
import com.ibm.icu.text.DecimalFormatSymbols;
import com.ibm.icu.util.Currency;
};
public static Rounder getCurrencyRounder(DecimalFormatSymbols symbols, IProperties properties) {
+ if (SignificantDigitsRounder.useSignificantDigits(properties)) {
+ return SignificantDigitsRounder.getInstance(properties);
+ }
Properties cprops = threadLocalProperties.get().clear();
populateCurrencyRounderProperties(cprops, symbols, properties);
if (cprops.getRoundingIncrement() != null) {
}
public static boolean allowsDecimalPoint(IProperties properties) {
- return properties.getDecimalSeparatorAlwaysShown() || properties.getMaximumFractionDigits() != 0;
+ return properties.getDecimalSeparatorAlwaysShown()
+ || properties.getMaximumFractionDigits() != 0;
}
// Properties
private final boolean alwaysShowDecimal;
- private final int groupingSize;
+ private final int primaryGroupingSize;
private final int secondaryGroupingSize;
private final int minimumGroupingDigits;
private final int codePointZero;
public PositiveDecimalFormat(DecimalFormatSymbols symbols, IProperties properties) {
- groupingSize =
- (properties.getGroupingSize() < 0)
- ? properties.getSecondaryGroupingSize()
- : properties.getGroupingSize();
- secondaryGroupingSize =
- (properties.getSecondaryGroupingSize() < 0)
- ? properties.getGroupingSize()
- : properties.getSecondaryGroupingSize();
+ int _primary = properties.getGroupingSize();
+ int _secondary = properties.getSecondaryGroupingSize();
+ primaryGroupingSize = _primary > 0 ? _primary : _secondary > 0 ? _secondary : 0;
+ secondaryGroupingSize = _secondary > 0 ? _secondary : primaryGroupingSize;
minimumGroupingDigits = properties.getMinimumGroupingDigits();
alwaysShowDecimal = properties.getDecimalSeparatorAlwaysShown();
// Add the decimal point
if (input.getLowerDisplayMagnitude() < 0 || alwaysShowDecimal) {
- length += string.insert(startIndex + length, decimalSeparator, NumberFormat.Field.DECIMAL_SEPARATOR);
+ length +=
+ string.insert(
+ startIndex + length, decimalSeparator, NumberFormat.Field.DECIMAL_SEPARATOR);
}
// Add the fraction digits
int integerCount = input.getUpperDisplayMagnitude() + 1;
for (int i = 0; i < integerCount; i++) {
// Add grouping separator
- if (groupingSize > 0 && i == groupingSize && integerCount - i >= minimumGroupingDigits) {
- length += string.insert(startIndex, groupingSeparator, NumberFormat.Field.GROUPING_SEPARATOR);
+ if (primaryGroupingSize > 0
+ && i == primaryGroupingSize
+ && integerCount - i >= minimumGroupingDigits) {
+ length +=
+ string.insert(startIndex, groupingSeparator, NumberFormat.Field.GROUPING_SEPARATOR);
} else if (secondaryGroupingSize > 0
- && i > groupingSize
- && (i - groupingSize) % secondaryGroupingSize == 0) {
- length += string.insert(startIndex, groupingSeparator, NumberFormat.Field.GROUPING_SEPARATOR);
+ && i > primaryGroupingSize
+ && (i - primaryGroupingSize) % secondaryGroupingSize == 0) {
+ length +=
+ string.insert(startIndex, groupingSeparator, NumberFormat.Field.GROUPING_SEPARATOR);
}
// Get and append the next digit value
@Override
public void export(Properties properties) {
+ // For backwards compatibility, export 0 as secondary grouping if primary and secondary are the same
+ int effectiveSecondaryGroupingSize =
+ secondaryGroupingSize == primaryGroupingSize ? 0 : secondaryGroupingSize;
+
properties.setDecimalSeparatorAlwaysShown(alwaysShowDecimal);
- properties.setGroupingSize(groupingSize);
- properties.setSecondaryGroupingSize(secondaryGroupingSize);
+ properties.setGroupingSize(primaryGroupingSize);
+ properties.setSecondaryGroupingSize(effectiveSecondaryGroupingSize);
properties.setMinimumGroupingDigits(minimumGroupingDigits);
}
}
package com.ibm.icu.text;
-import java.io.IOException;
-import java.io.NotSerializableException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.text.AttributedCharacterIterator;
-import java.text.FieldPosition;
import java.text.ParsePosition;
import java.util.Locale;
-import com.ibm.icu.impl.number.FormatQuantity4;
import com.ibm.icu.impl.number.Properties;
+import com.ibm.icu.util.CurrencyAmount;
import com.ibm.icu.util.ULocale;
/**
*/
public class CompactDecimalFormat extends DecimalFormat {
- private static final long serialVersionUID = 4716293295276629682L;
+ private static final long serialVersionUID = 4716293295276629682L;
/**
* Style parameter for CompactDecimalFormat.
}
/**
- * {@inheritDoc}
- *
- * @stable ICU 49
- */
- @Override
- public boolean equals(Object obj) {
- return super.equals(obj);
- }
-
- /**
- * {@inheritDoc}
- *
- * @stable ICU 49
- */
- @Override
- public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) {
- FormatQuantity4 fq = new FormatQuantity4(number);
- formatter.format(fq, toAppendTo, pos);
- fq.populateUFieldPosition(pos);
- return toAppendTo;
- }
-
- /**
- * {@inheritDoc}
- *
- * @stable ICU 50
- */
- @Override
- public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
- if (!(obj instanceof Number)) throw new IllegalArgumentException();
- Number number = (Number) obj;
- FormatQuantity4 fq = new FormatQuantity4(number);
- AttributedCharacterIterator result = formatter.formatToCharacterIterator(fq);
- return result;
- }
-
- /**
- * {@inheritDoc}
- *
- * @stable ICU 49
- */
- @Override
- public StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition pos) {
- FormatQuantity4 fq = new FormatQuantity4(number);
- formatter.format(fq, toAppendTo, pos);
- fq.populateUFieldPosition(pos);
- return toAppendTo;
- }
-
- /**
- * {@inheritDoc}
- *
- * @stable ICU 49
- */
- @Override
- public StringBuffer format(BigInteger number, StringBuffer toAppendTo, FieldPosition pos) {
- FormatQuantity4 fq = new FormatQuantity4(number);
- formatter.format(fq, toAppendTo, pos);
- fq.populateUFieldPosition(pos);
- return toAppendTo;
- }
-
- /**
- * {@inheritDoc}
- *
- * @stable ICU 49
- */
- @Override
- public StringBuffer format(BigDecimal number, StringBuffer toAppendTo, FieldPosition pos) {
- FormatQuantity4 fq = new FormatQuantity4(number);
- formatter.format(fq, toAppendTo, pos);
- fq.populateUFieldPosition(pos);
- return toAppendTo;
- }
-
- /**
- * {@inheritDoc}
+ * Parsing is currently unsupported, and throws an UnsupportedOperationException.
*
* @stable ICU 49
*/
@Override
- public StringBuffer format(
- com.ibm.icu.math.BigDecimal number, StringBuffer toAppendTo, FieldPosition pos) {
- FormatQuantity4 fq = new FormatQuantity4(number.toBigDecimal());
- formatter.format(fq, toAppendTo, pos);
- fq.populateUFieldPosition(pos);
- return toAppendTo;
+ public Number parse(String text, ParsePosition parsePosition) {
+ throw new UnsupportedOperationException();
}
-// /**
-// * {@inheritDoc}
-// *
-// * @internal ICU 57 technology preview
-// * @deprecated This API might change or be removed in a future release.
-// */
-// @Override
-// @Deprecated
-// public StringBuffer format(CurrencyAmount currAmt, StringBuffer toAppendTo, FieldPosition pos) {
-// // TODO(sffc)
-// throw new UnsupportedOperationException();
-// }
-
/**
* Parsing is currently unsupported, and throws an UnsupportedOperationException.
*
* @stable ICU 49
*/
@Override
- public Number parse(String text, ParsePosition parsePosition) {
+ public CurrencyAmount parseCurrency(CharSequence text, ParsePosition parsePosition) {
throw new UnsupportedOperationException();
}
-
- // DISALLOW Serialization, at least while draft
-
- private void writeObject(ObjectOutputStream out) throws IOException {
- throw new NotSerializableException();
- }
-
- private void readObject(ObjectInputStream in) throws IOException {
- throw new NotSerializableException();
- }
}
// FORMAT AND PARSE APIS //
//=====================================================================================//
- /** @stable ICU 2.0 */
+ /**
+ * {@inheritDoc}
+ *
+ * @stable ICU 2.0
+ */
@Override
public StringBuffer format(double number, StringBuffer result, FieldPosition fieldPosition) {
FormatQuantity4 fq = new FormatQuantity4(number);
return result;
}
- /** @stable ICU 2.0 */
+ /**
+ * {@inheritDoc}
+ *
+ * @stable ICU 2.0
+ */
@Override
public StringBuffer format(long number, StringBuffer result, FieldPosition fieldPosition) {
FormatQuantity4 fq = new FormatQuantity4(number);
return result;
}
- /** @stable ICU 2.0 */
+ /**
+ * {@inheritDoc}
+ *
+ * @stable ICU 2.0
+ */
@Override
public StringBuffer format(BigInteger number, StringBuffer result, FieldPosition fieldPosition) {
FormatQuantity4 fq = new FormatQuantity4(number);
return result;
}
- /** @stable ICU 2.0 */
+ /**
+ * {@inheritDoc}
+ *
+ * @stable ICU 2.0
+ */
@Override
public StringBuffer format(
java.math.BigDecimal number, StringBuffer result, FieldPosition fieldPosition) {
return result;
}
- /** @stable ICU 2.0 */
+ /**
+ * {@inheritDoc}
+ *
+ * @stable ICU 2.0
+ */
@Override
public StringBuffer format(BigDecimal number, StringBuffer result, FieldPosition fieldPosition) {
FormatQuantity4 fq = new FormatQuantity4(number.toBigDecimal());
return result;
}
- /** @stable ICU 3.6 */
+ /**
+ * {@inheritDoc}
+ *
+ * @stable ICU 3.6
+ */
@Override
public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
if (!(obj instanceof Number)) throw new IllegalArgumentException();
return result;
}
- protected static final ThreadLocal<Properties> threadLocalCurrencyProperties =
+ private static final ThreadLocal<Properties> threadLocalCurrencyProperties =
new ThreadLocal<Properties>() {
@Override
protected Properties initialValue() {
}
};
+ /**
+ * {@inheritDoc}
+ *
+ * @stable ICU 3.0
+ */
@Override
public StringBuffer format(CurrencyAmount currAmt, StringBuffer toAppendTo, FieldPosition pos) {
// TODO: This is ugly (although not as ugly as it was in ICU 58).
// Currency should be a free parameter, not in property bag. Fix in ICU 60.
Properties cprops = threadLocalCurrencyProperties.get();
+ SingularFormat fmt = null;
synchronized (this) {
- cprops.copyFrom(properties);
+ // Use the pre-compiled formatter if possible. Otherwise, copy the properties
+ // and build our own formatter.
+ // TODO: Consider using a static format path here.
+ if (currAmt.getCurrency().equals(properties.getCurrency())) {
+ fmt = formatter;
+ } else {
+ cprops.copyFrom(properties);
+ }
+ }
+ if (fmt == null) {
+ cprops.setCurrency(currAmt.getCurrency());
+ fmt = Endpoint.fromBTA(cprops, symbols);
}
- cprops.setCurrency(currAmt.getCurrency());
FormatQuantity4 fq = new FormatQuantity4(currAmt.getNumber());
- // TODO: Use a static format path here
- SingularFormat fmt = Endpoint.fromBTA(cprops, symbols);
fmt.format(fq, toAppendTo, pos);
fq.populateUFieldPosition(pos);
return toAppendTo;
}
- /** @stable ICU 2.0 */
+ /**
+ * {@inheritDoc}
+ *
+ * @stable ICU 2.0
+ */
@Override
public Number parse(String text, ParsePosition parsePosition) {
// Backwards compatibility: use currency parse mode if this is a currency instance
return result;
}
- /** @stable ICU 49 */
+ /**
+ * {@inheritDoc}
+ *
+ * @stable ICU 49
+ */
@Override
public CurrencyAmount parseCurrency(CharSequence text, ParsePosition parsePosition) {
try {
return properties.equals(other.properties) && symbols.equals(other.symbols);
}
- /** @stable ICU 2.0 */
+ /**
+ * {@inheritDoc}
+ *
+ * @stable ICU 2.0
+ */
@Override
public synchronized int hashCode() {
return properties.hashCode();
}
};
+ /**
+ * Returns the default value of toString() with extra DecimalFormat-specific information appended
+ * to the end of the string. This extra information is intended for debugging purposes, and the
+ * format is not guaranteed to be stable.
+ *
+ * @stable ICU 2.0
+ */
@Override
- public synchronized String toString() {
- return "<DecimalFormat " + symbols.toString() + " " + properties.toString() + ">";
+ public String toString() {
+ StringBuilder result = new StringBuilder();
+ result.append(getClass().getName());
+ result.append("@");
+ result.append(Integer.toHexString(hashCode()));
+ result.append(" { symbols@");
+ result.append(Integer.toHexString(symbols.hashCode()));
+ synchronized (this) {
+ properties.toStringBare(result);
+ }
+ result.append(" }");
+ return result.toString();
}
/**
refreshFormatter();
}
+ /**
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
public static interface PropertySetter {
+ /**
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
public void set(Properties props);
}
// ===== End of factory stuff =====
/**
- * Overrides hashCode.
+ * {@inheritDoc}
+ *
* @stable ICU 2.0
*/
@Override
*/
@Deprecated
public static enum Operand {
- /** The double value of the entire number. */
+ /**
+ * The double value of the entire number.
+ *
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
n,
- /** The integer value, with the fraction digits truncated off. */
+
+ /**
+ * The integer value, with the fraction digits truncated off.
+ *
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
i,
- /** All visible fraction digits as an integer, including trailing zeros. */
+
+ /**
+ * All visible fraction digits as an integer, including trailing zeros.
+ *
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
f,
- /** Visible fraction digits, not including trailing zeros. */
+
+ /**
+ * Visible fraction digits as an integer, not including trailing zeros.
+ *
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
t,
- /** Number of visible fraction digits. */
+
+ /**
+ * Number of visible fraction digits.
+ *
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
v,
+
+ /**
+ * Number of visible fraction digits, not including trailing zeros.
+ *
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
w,
- /* deprecated */
+
+ /**
+ * THIS OPERAND IS DEPRECATED AND HAS BEEN REMOVED FROM THE SPEC.
+ *
+ * <p>Returns the integer value, but will fail if the number has fraction digits.
+ * That is, using "j" instead of "i" is like implicitly adding "v is 0".
+ *
+ * <p>For example, "j is 3" is equivalent to "i is 3 and v is 0": it matches
+ * "3" but not "3.1" or "3.0".
+ *
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
j;
}
*/
@Deprecated
public static interface IFixedDecimal {
+ /**
+ * Returns the value corresponding to the specified operand (n, i, f, t, v, or w).
+ * If the operand is 'n', returns a double; otherwise, returns an integer.
+ *
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
public double getPluralOperand(Operand operand);
+
+ /**
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
public boolean isNaN();
+
+ /**
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
public boolean isInfinite();
}
}
/**
+ * {@inheritDoc}
+ *
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public double getPluralOperand(Operand operand) {
switch(operand) {
- default: return source;
+ case n: return source;
case i: return integerValue;
case f: return decimalDigits;
case t: return decimalDigitsWithoutTrailingZeros;
case v: return visibleDecimalDigitCount;
case w: return visibleDecimalDigitCountWithoutTrailingZeros;
+ default: return source;
}
}
throw new NotSerializableException();
}
- /* (non-Javadoc)
- * @see com.ibm.icu.text.PluralRules.IFixedDecimal#isNaN()
+ /**
+ * {@inheritDoc}
+ *
+ * @internal
+ * @deprecated This API is ICU internal only.
*/
+ @Deprecated
@Override
public boolean isNaN() {
return Double.isNaN(source);
}
- /* (non-Javadoc)
- * @see com.ibm.icu.text.PluralRules.IFixedDecimal#isInfinite()
+ /**
+ * {@inheritDoc}
+ *
+ * @internal
+ * @deprecated This API is ICU internal only.
*/
+ @Deprecated
@Override
public boolean isInfinite() {
return Double.isInfinite(source);
private String isoCode;
private String currencyString;
+ /**
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
public CurrencyStringInfo(String isoCode, String currencyString) {
this.isoCode = isoCode;
this.currencyString = currencyString;
}
+ /**
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
public String getISOCode() {
return isoCode;
}
+ /**
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
@SuppressWarnings("unused")
public String getCurrencyString() {
return currencyString;
// JDK parses as -1945
(1,945d1) fail K
+test parse strict scientific
+set locale en
+set pattern #E0
+set lenient 0
+begin
+parse output breaks
+123 fail JK
+123E1 1230
+123E0 123
+123E fail JK
+
test parse strict without prefix/suffix
set locale en
set pattern #
}
}
+ @Test
+ public void TestLongShortFallback() {
+ // smn, dz have long but not short
+ // es_US, es_GT, es_419, ee have short but not long
+ ULocale[] locs = new ULocale[] {
+ new ULocale("smn"),
+ new ULocale("es_US"),
+ new ULocale("es_GT"),
+ new ULocale("es_419"),
+ new ULocale("ee"),
+ };
+ double number = 12345.0;
+ // These expected values are the same in both ICU 58 and 59.
+ String[][] expectedShortLong = new String[][] {
+ { "12K", "12 tuhháát" },
+ { "12k", "12 mil" },
+ { "12k", "12 mil" },
+ { "12k", "12 mil" },
+ { "12K", "12K" },
+ };
+
+ for (int i=0; i<locs.length; i++) {
+ ULocale loc = locs[i];
+ String expectedShort = expectedShortLong[i][0];
+ String expectedLong = expectedShortLong[i][1];
+ CompactDecimalFormat cdfShort = CompactDecimalFormat.getInstance(loc, CompactStyle.SHORT);
+ CompactDecimalFormat cdfLong = CompactDecimalFormat.getInstance(loc, CompactStyle.LONG);
+ String actualShort = cdfShort.format(number);
+ String actualLong = cdfLong.format(number);
+ assertEquals("Short, locale " + loc, expectedShort, actualShort);
+ assertEquals("Long, locale " + loc, expectedLong, actualLong);
+ }
+ }
+
+ @Test
+ public void TestLocales() {
+ // Run a CDF over all locales to make sure there are no unexpected exceptions.
+ ULocale[] locs = ULocale.getAvailableLocales();
+ for (ULocale loc : locs) {
+ CompactDecimalFormat cdfShort = CompactDecimalFormat.getInstance(loc, CompactStyle.SHORT);
+ CompactDecimalFormat cdfLong = CompactDecimalFormat.getInstance(loc, CompactStyle.LONG);
+ for (double d = 12345.0; d > 0.01; d /= 10) {
+ String s1 = cdfShort.format(d);
+ String s2 = cdfLong.format(d);
+ assertNotNull("Short " + loc, s1);
+ assertNotNull("Long " + loc, s2);
+ assertNotEquals("Short " + loc, 0, s1.length());
+ assertNotEquals("Long " + loc, 0, s2.length());
+ }
+ }
+ }
+
+ @Test
+ public void TestDigitDisplay() {
+ CompactDecimalFormat cdf = CompactDecimalFormat.getInstance(ULocale.US, CompactStyle.SHORT);
+ cdf.setMinimumSignificantDigits(2);
+ String actual = cdf.format(70123.45678);
+ assertEquals("Should not display any extra fraction digits", "70K", actual);
+ }
+
@Test
public void TestBug12422() {
CompactDecimalFormat cdf;
NumberFormat fmt = NumberFormat.getInstance(new ULocale("en"));
fmt.setMinimumIntegerDigits(10);
FieldPosition pos = new FieldPosition(NumberFormat.Field.GROUPING_SEPARATOR);
- fmt.format(1234, new StringBuffer(), pos);
+ StringBuffer sb = new StringBuffer();
+ fmt.format(1234567, sb, pos);
+ assertEquals("Should have multiple grouping separators", "0,001,234,567", sb.toString());
assertEquals("FieldPosition should report the first occurence", 1, pos.getBeginIndex());
assertEquals("FieldPosition should report the first occurence", 2, pos.getEndIndex());
}
assertNotEquals("df2 != df1", df2, df1);
}
+ @Test
+ public void Test13055() {
+ DecimalFormat df = (DecimalFormat) NumberFormat.getPercentInstance();
+ df.setMaximumFractionDigits(0);
+ df.setRoundingMode(BigDecimal.ROUND_HALF_EVEN);
+ assertEquals("Should round percent toward even number", "216%", df.format(2.155));
+ }
+
+ @Test
+ public void Test13056() {
+ DecimalFormat df = new DecimalFormat("#,##0");
+ assertEquals("Primary grouping should return 3", 3, df.getGroupingSize());
+ assertEquals("Secondary grouping should return 0", 0, df.getSecondaryGroupingSize());
+ df.setSecondaryGroupingSize(3);
+ assertEquals("Primary grouping should still return 3", 3, df.getGroupingSize());
+ assertEquals("Secondary grouping should still return 0", 0, df.getSecondaryGroupingSize());
+ df.setGroupingSize(4);
+ assertEquals("Primary grouping should return 4", 4, df.getGroupingSize());
+ assertEquals("Secondary should remember explicit setting and return 3", 3, df.getSecondaryGroupingSize());
+ }
+
@Test
public void testPercentZero() {
DecimalFormat df = (DecimalFormat) NumberFormat.getPercentInstance();
assertEquals("Rounding mode ordinal from java.math.RoundingMode should be the same", df1, df2);
}
+ @Test
+ public void testCurrencySignificantDigits() {
+ ULocale locale = new ULocale("en-US");
+ DecimalFormat df = (DecimalFormat) NumberFormat.getCurrencyInstance(locale);
+ df.setMaximumSignificantDigits(2);
+ String result = df.format(1234);
+ assertEquals("Currency rounding should obey significant digits", "$1,200", result);
+ }
+
+ @Test
+ public void testParseStrictScientific() {
+ // See ticket #13057
+ DecimalFormat df = (DecimalFormat) NumberFormat.getScientificInstance();
+ df.setParseStrict(true);
+ ParsePosition ppos = new ParsePosition(0);
+ Number result0 = df.parse("123E4", ppos);
+ assertEquals("Should accept number with exponent", 1230000L, result0);
+ assertEquals("Should consume the whole number", 5, ppos.getIndex());
+ ppos.setIndex(0);
+ result0 = df.parse("123", ppos);
+ assertNull("Should reject number without exponent", result0);
+ ppos.setIndex(0);
+ CurrencyAmount result1 = df.parseCurrency("USD123", ppos);
+ assertNull("Should reject currency without exponent", result1);
+ }
+
+ @Test
+ public void testParseLenientScientific() {
+ DecimalFormat df = (DecimalFormat) NumberFormat.getScientificInstance();
+ ParsePosition ppos = new ParsePosition(0);
+ Number result0 = df.parse("123E", ppos);
+ assertEquals("Should parse the number in lenient mode", 123L, result0);
+ assertEquals("Should stop before the E", 3, ppos.getIndex());
+ DecimalFormatSymbols dfs = df.getDecimalFormatSymbols();
+ dfs.setExponentSeparator("EE");
+ df.setDecimalFormatSymbols(dfs);
+ ppos.setIndex(0);
+ result0 = df.parse("123EE", ppos);
+ assertEquals("Should parse the number in lenient mode", 123L, result0);
+ assertEquals("Should stop before the EE", 3, ppos.getIndex());
+ }
+
+ @Test
+ public void testParseAcceptAsciiPercentPermilleFallback() {
+ ULocale loc = new ULocale("ar");
+ DecimalFormat df = (DecimalFormat) NumberFormat.getPercentInstance(loc);
+ ParsePosition ppos = new ParsePosition(0);
+ Number result = df.parse("42%", ppos);
+ assertEquals("Should parse as 0.42 even in ar", new BigDecimal("0.42"), result);
+ assertEquals("Should consume the entire string even in ar", 3, ppos.getIndex());
+ // TODO: Is there a better way to make a localized permille formatter?
+ df.applyPattern(df.toPattern().replace("%", "‰"));
+ ppos.setIndex(0);
+ result = df.parse("42‰", ppos);
+ assertEquals("Should parse as 0.042 even in ar", new BigDecimal("0.042"), result);
+ assertEquals("Should consume the entire string even in ar", 3, ppos.getIndex());
+ }
+
+ @Test
+ public void testParseSubtraction() {
+ // TODO: Is this a case we need to support? It prevents us from automatically parsing
+ // minus signs that appear after the number, like in "12-" vs "-12".
+ DecimalFormat df = new DecimalFormat();
+ String str = "12 - 5";
+ ParsePosition ppos = new ParsePosition(0);
+ Number n1 = df.parse(str, ppos);
+ Number n2 = df.parse(str, ppos);
+ assertEquals("Should parse 12 and -5", 7, n1.intValue() + n2.intValue());
+ }
+
+ @Test
+ public void testMultiCodePointPaddingInPattern() {
+ DecimalFormat df = new DecimalFormat("a*'நி'###0b");
+ String result = df.format(12);
+ assertEquals("Multi-codepoint padding should not be split", "aநிநி12b", result);
+ df = new DecimalFormat("a*😁###0b");
+ result = df.format(12);
+ assertEquals("Single-codepoint padding should not be split", "a😁😁12b", result);
+ df = new DecimalFormat("a*''###0b");
+ result = df.format(12);
+ assertEquals("Quote should be escapable in padding syntax", "a''12b", result);
+ }
+
@Test
public void testSignificantDigitsMode() {
String[][] allExpected = {