From 0c9a9cf832f03a48e81cc4a5ba6516dad80fc8ca Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Tue, 26 Sep 2017 08:51:34 +0000 Subject: [PATCH] ICU-13177 Renaming files and ICU4C syncing. X-SVN-Rev: 40459 --- .../impl/number}/AffixPatternProvider.java | 2 +- .../ibm/icu/impl/number}/CompactData.java | 8 +- .../ConstantAffixModifier.java | 8 +- .../ConstantMultiFieldModifier.java | 4 +- .../CurrencySpacingEnabledModifier.java | 3 +- .../impl/number}/CustomSymbolCurrency.java | 2 +- .../impl/number/DecimalFormatProperties.java | 3 +- .../number/DecimalQuantity_AbstractBCD.java | 22 +- .../DecimalQuantity_DualStorageBCD.java | 5 + .../ibm/icu/impl/number}/LongNameHandler.java | 193 +++--- .../ibm/icu/impl/number}/MacroProps.java | 19 +- .../ibm/icu/impl/number}/MicroProps.java | 18 +- .../icu/impl/number}/MicroPropsGenerator.java | 4 +- .../icu/impl/number}/MicroPropsMutator.java | 2 +- .../src/com/ibm/icu/impl/number/Modifier.java | 2 - .../ibm/icu/impl/number}/MultiplierImpl.java | 4 +- .../icu/impl/number}/MultiplierProducer.java | 2 +- .../impl/number}/MutablePatternModifier.java | 21 +- .../ibm/icu/impl/number}/Padder.java | 27 +- .../impl/number/ParameterizedModifier.java | 68 ++ .../icu/impl/number/PatternStringParser.java | 3 +- .../icu/impl/number/PatternStringUtils.java | 4 +- .../ibm/icu/impl/number/RoundingUtils.java | 11 + .../{modifiers => }/SimpleModifier.java | 4 +- .../ibm/icu/number}/CompactNotation.java | 33 +- .../ibm/icu/number}/CurrencyRounder.java | 31 +- .../ibm/icu/number}/FormattedNumber.java | 5 +- .../ibm/icu/number}/FractionRounder.java | 36 +- .../ibm/icu/number}/Grouper.java | 8 +- .../ibm/icu/number}/IntegerWidth.java | 16 +- .../icu/number}/LocalizedNumberFormatter.java | 16 +- .../core/src/com/ibm/icu/number/Notation.java | 183 ++++++ .../com/ibm/icu/number/NumberFormatter.java | 260 ++++++++ .../ibm/icu/number}/NumberFormatterImpl.java | 230 ++++--- .../icu/number}/NumberFormatterSettings.java | 22 +- .../ibm/icu/number}/NumberPropertyMapper.java | 27 +- .../ibm/icu/number}/Rounder.java | 300 ++++++--- .../ibm/icu/number}/ScientificNotation.java | 64 +- .../ibm/icu/number}/SimpleNotation.java | 2 +- .../number}/UnlocalizedNumberFormatter.java | 2 +- .../src/com/ibm/icu/text/DecimalFormat.java | 9 +- .../src/com/ibm/icu/text/NumberFormat.java | 19 +- .../classes/core/src/newapi/Notation.java | 40 -- .../core/src/newapi/NumberFormatter.java | 62 -- .../core/src/newapi/SkeletonBuilder.java | 586 ------------------ .../classes/core/src/newapi/impl/demo.java | 64 -- .../data/numberformattestspecification.txt | 19 - .../format/NumberFormatDataDrivenTest.java | 7 +- .../icu/dev/test/format/NumberFormatTest.java | 4 +- .../dev/test/number/DecimalQuantityTest.java | 5 +- .../ibm/icu/dev/test/number/ModifierTest.java | 8 +- .../number/MutablePatternModifierTest.java | 9 +- .../dev/test/number/NumberFormatterTest.java | 418 +++++++++---- .../icu/dev/test/number/PropertiesTest.java | 3 +- .../impl/number/DecimalQuantity_64BitBCD.java | 5 + .../number/DecimalQuantity_ByteArrayBCD.java | 5 + .../number/DecimalQuantity_SimpleStorage.java | 0 57 files changed, 1520 insertions(+), 1417 deletions(-) rename icu4j/main/classes/core/src/{newapi/impl => com/ibm/icu/impl/number}/AffixPatternProvider.java (95%) rename icu4j/main/classes/core/src/{newapi/impl => com/ibm/icu/impl/number}/CompactData.java (99%) rename icu4j/main/classes/core/src/com/ibm/icu/impl/number/{modifiers => }/ConstantAffixModifier.java (90%) rename icu4j/main/classes/core/src/com/ibm/icu/impl/number/{modifiers => }/ConstantMultiFieldModifier.java (94%) rename icu4j/main/classes/core/src/com/ibm/icu/impl/number/{modifiers => }/CurrencySpacingEnabledModifier.java (98%) rename icu4j/main/classes/core/src/{newapi/impl => com/ibm/icu/impl/number}/CustomSymbolCurrency.java (98%) rename icu4j/main/classes/core/src/{newapi/impl => com/ibm/icu/impl/number}/LongNameHandler.java (53%) rename icu4j/main/classes/core/src/{newapi/impl => com/ibm/icu/impl/number}/MacroProps.java (89%) rename icu4j/main/classes/core/src/{newapi/impl => com/ibm/icu/impl/number}/MicroProps.java (82%) rename icu4j/main/classes/core/src/{newapi/impl => com/ibm/icu/impl/number}/MicroPropsGenerator.java (96%) rename icu4j/main/classes/core/src/{newapi/impl => com/ibm/icu/impl/number}/MicroPropsMutator.java (88%) rename icu4j/main/classes/core/src/{newapi/impl => com/ibm/icu/impl/number}/MultiplierImpl.java (94%) rename icu4j/main/classes/core/src/{newapi/impl => com/ibm/icu/impl/number}/MultiplierProducer.java (85%) rename icu4j/main/classes/core/src/{newapi/impl => com/ibm/icu/impl/number}/MutablePatternModifier.java (96%) rename icu4j/main/classes/core/src/{newapi/impl => com/ibm/icu/impl/number}/Padder.java (81%) create mode 100644 icu4j/main/classes/core/src/com/ibm/icu/impl/number/ParameterizedModifier.java rename icu4j/main/classes/core/src/com/ibm/icu/impl/number/{modifiers => }/SimpleModifier.java (96%) rename icu4j/main/classes/core/src/{newapi => com/ibm/icu/number}/CompactNotation.java (85%) rename icu4j/main/classes/core/src/{newapi => com/ibm/icu/number}/CurrencyRounder.java (52%) rename icu4j/main/classes/core/src/{newapi => com/ibm/icu/number}/FormattedNumber.java (98%) rename icu4j/main/classes/core/src/{newapi => com/ibm/icu/number}/FractionRounder.java (56%) rename icu4j/main/classes/core/src/{newapi => com/ibm/icu/number}/Grouper.java (94%) rename icu4j/main/classes/core/src/{newapi => com/ibm/icu/number}/IntegerWidth.java (57%) rename icu4j/main/classes/core/src/{newapi => com/ibm/icu/number}/LocalizedNumberFormatter.java (89%) create mode 100644 icu4j/main/classes/core/src/com/ibm/icu/number/Notation.java create mode 100644 icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatter.java rename icu4j/main/classes/core/src/{newapi => com/ibm/icu/number}/NumberFormatterImpl.java (67%) rename icu4j/main/classes/core/src/{newapi => com/ibm/icu/number}/NumberFormatterSettings.java (97%) rename icu4j/main/classes/core/src/{newapi => com/ibm/icu/number}/NumberPropertyMapper.java (96%) rename icu4j/main/classes/core/src/{newapi => com/ibm/icu/number}/Rounder.java (53%) rename icu4j/main/classes/core/src/{newapi => com/ibm/icu/number}/ScientificNotation.java (80%) rename icu4j/main/classes/core/src/{newapi => com/ibm/icu/number}/SimpleNotation.java (88%) rename icu4j/main/classes/core/src/{newapi => com/ibm/icu/number}/UnlocalizedNumberFormatter.java (97%) delete mode 100644 icu4j/main/classes/core/src/newapi/Notation.java delete mode 100644 icu4j/main/classes/core/src/newapi/NumberFormatter.java delete mode 100644 icu4j/main/classes/core/src/newapi/SkeletonBuilder.java delete mode 100644 icu4j/main/classes/core/src/newapi/impl/demo.java rename icu4j/main/{classes => tests}/core/src/com/ibm/icu/impl/number/DecimalQuantity_64BitBCD.java (97%) rename icu4j/main/{classes => tests}/core/src/com/ibm/icu/impl/number/DecimalQuantity_ByteArrayBCD.java (98%) rename icu4j/main/{classes => tests}/core/src/com/ibm/icu/impl/number/DecimalQuantity_SimpleStorage.java (100%) diff --git a/icu4j/main/classes/core/src/newapi/impl/AffixPatternProvider.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/AffixPatternProvider.java similarity index 95% rename from icu4j/main/classes/core/src/newapi/impl/AffixPatternProvider.java rename to icu4j/main/classes/core/src/com/ibm/icu/impl/number/AffixPatternProvider.java index 87554b7f771..0cf547e341e 100644 --- a/icu4j/main/classes/core/src/newapi/impl/AffixPatternProvider.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/AffixPatternProvider.java @@ -1,6 +1,6 @@ // © 2017 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License -package newapi.impl; +package com.ibm.icu.impl.number; public interface AffixPatternProvider { public static final class Flags { diff --git a/icu4j/main/classes/core/src/newapi/impl/CompactData.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CompactData.java similarity index 99% rename from icu4j/main/classes/core/src/newapi/impl/CompactData.java rename to icu4j/main/classes/core/src/com/ibm/icu/impl/number/CompactData.java index 5090f7091bb..ed524b3ab4e 100644 --- a/icu4j/main/classes/core/src/newapi/impl/CompactData.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CompactData.java @@ -1,6 +1,6 @@ // © 2017 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License -package newapi.impl; +package com.ibm.icu.impl.number; import java.util.Arrays; import java.util.Map; @@ -15,10 +15,12 @@ import com.ibm.icu.util.ICUException; import com.ibm.icu.util.ULocale; import com.ibm.icu.util.UResourceBundle; -import newapi.CompactNotation.CompactType; - public class CompactData implements MultiplierProducer { + public enum CompactType { + DECIMAL, CURRENCY + } + // A dummy object used when a "0" compact decimal entry is encountered. This is necessary // in order to prevent falling back to root. Object equality ("==") is intended. private static final String USE_FALLBACK = ""; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/modifiers/ConstantAffixModifier.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/ConstantAffixModifier.java similarity index 90% rename from icu4j/main/classes/core/src/com/ibm/icu/impl/number/modifiers/ConstantAffixModifier.java rename to icu4j/main/classes/core/src/com/ibm/icu/impl/number/ConstantAffixModifier.java index e629712cd02..53f79096fe5 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/modifiers/ConstantAffixModifier.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/ConstantAffixModifier.java @@ -1,13 +1,7 @@ // © 2017 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License -package com.ibm.icu.impl.number.modifiers; +package com.ibm.icu.impl.number; -import com.ibm.icu.impl.number.Modifier; - -// TODO: This class is currently unused, but it might be useful for something in the future. -// Should probably be moved to a different package. - -import com.ibm.icu.impl.number.NumberStringBuilder; import com.ibm.icu.text.NumberFormat.Field; /** diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/modifiers/ConstantMultiFieldModifier.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/ConstantMultiFieldModifier.java similarity index 94% rename from icu4j/main/classes/core/src/com/ibm/icu/impl/number/modifiers/ConstantMultiFieldModifier.java rename to icu4j/main/classes/core/src/com/ibm/icu/impl/number/ConstantMultiFieldModifier.java index b07422407ea..c6c38e07b9e 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/modifiers/ConstantMultiFieldModifier.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/ConstantMultiFieldModifier.java @@ -1,9 +1,7 @@ // © 2017 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License -package com.ibm.icu.impl.number.modifiers; +package com.ibm.icu.impl.number; -import com.ibm.icu.impl.number.Modifier; -import com.ibm.icu.impl.number.NumberStringBuilder; import com.ibm.icu.text.NumberFormat.Field; /** diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/modifiers/CurrencySpacingEnabledModifier.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CurrencySpacingEnabledModifier.java similarity index 98% rename from icu4j/main/classes/core/src/com/ibm/icu/impl/number/modifiers/CurrencySpacingEnabledModifier.java rename to icu4j/main/classes/core/src/com/ibm/icu/impl/number/CurrencySpacingEnabledModifier.java index d9318849a62..5aff0f36304 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/modifiers/CurrencySpacingEnabledModifier.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CurrencySpacingEnabledModifier.java @@ -1,8 +1,7 @@ // © 2017 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License -package com.ibm.icu.impl.number.modifiers; +package com.ibm.icu.impl.number; -import com.ibm.icu.impl.number.NumberStringBuilder; import com.ibm.icu.text.DecimalFormatSymbols; import com.ibm.icu.text.NumberFormat; import com.ibm.icu.text.UnicodeSet; diff --git a/icu4j/main/classes/core/src/newapi/impl/CustomSymbolCurrency.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CustomSymbolCurrency.java similarity index 98% rename from icu4j/main/classes/core/src/newapi/impl/CustomSymbolCurrency.java rename to icu4j/main/classes/core/src/com/ibm/icu/impl/number/CustomSymbolCurrency.java index 664694bd354..cfbf3f91dad 100644 --- a/icu4j/main/classes/core/src/newapi/impl/CustomSymbolCurrency.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CustomSymbolCurrency.java @@ -1,6 +1,6 @@ // © 2017 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License -package newapi.impl; +package com.ibm.icu.impl.number; import com.ibm.icu.text.DecimalFormatSymbols; import com.ibm.icu.util.Currency; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalFormatProperties.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalFormatProperties.java index 2ee66346c67..52535762e69 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalFormatProperties.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalFormatProperties.java @@ -15,6 +15,7 @@ import java.text.ParsePosition; import java.util.ArrayList; import java.util.Map; +import com.ibm.icu.impl.number.Padder.PadPosition; import com.ibm.icu.impl.number.Parse.GroupingMode; import com.ibm.icu.impl.number.Parse.ParseMode; import com.ibm.icu.text.CompactDecimalFormat.CompactStyle; @@ -23,8 +24,6 @@ import com.ibm.icu.text.PluralRules; import com.ibm.icu.util.Currency; import com.ibm.icu.util.Currency.CurrencyUsage; -import newapi.impl.Padder.PadPosition; - public class DecimalFormatProperties implements Cloneable, Serializable { private static final DecimalFormatProperties DEFAULT = new DecimalFormatProperties(); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java index 3be79a22d13..b442cf1f3ee 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java @@ -310,19 +310,6 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { return precision == 0; } - @Override - public DecimalQuantity createCopy() { - if (this instanceof DecimalQuantity_64BitBCD) { - return new DecimalQuantity_64BitBCD((DecimalQuantity_64BitBCD) this); - } else if (this instanceof DecimalQuantity_ByteArrayBCD) { - return new DecimalQuantity_ByteArrayBCD((DecimalQuantity_ByteArrayBCD) this); - } else if (this instanceof DecimalQuantity_DualStorageBCD) { - return new DecimalQuantity_DualStorageBCD((DecimalQuantity_DualStorageBCD) this); - } else { - throw new IllegalArgumentException("Don't know how to copy " + this.getClass()); - } - } - public void setToInt(int n) { setBcdToZero(); flags = 0; @@ -423,6 +410,11 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { * to digits. Since double arithmetic is inexact, the resulting digits may not be accurate. */ private void _setToDoubleFast(double n) { + isApproximate = true; + origDouble = n; + origDelta = 0; + + // NOTE: Unlike ICU4C, doubles are always IEEE 754 doubles. long ieeeBits = Double.doubleToLongBits(n); int exponent = (int) ((ieeeBits & 0x7ff0000000000000L) >> 52) - 0x3ff; @@ -432,10 +424,6 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { return; } - isApproximate = true; - origDouble = n; - origDelta = 0; - // 3.3219... is log2(10) int fracLength = (int) ((52 - exponent) / 3.32192809489); if (fracLength >= 0) { diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java index 7774d2ac93f..e127d6fa4c7 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java @@ -76,6 +76,11 @@ public final class DecimalQuantity_DualStorageBCD extends DecimalQuantity_Abstra } } + @Override + public DecimalQuantity createCopy() { + return new DecimalQuantity_DualStorageBCD(this); + } + @Override protected byte getDigitPos(int position) { if (usingBytes) { diff --git a/icu4j/main/classes/core/src/newapi/impl/LongNameHandler.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java similarity index 53% rename from icu4j/main/classes/core/src/newapi/impl/LongNameHandler.java rename to icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java index cbb67f14d5e..2ed9f4d63cd 100644 --- a/icu4j/main/classes/core/src/newapi/impl/LongNameHandler.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java @@ -1,6 +1,6 @@ // © 2017 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License -package newapi.impl; +package com.ibm.icu.impl.number; import java.util.EnumMap; import java.util.Map; @@ -11,9 +11,8 @@ import com.ibm.icu.impl.ICUResourceBundle; import com.ibm.icu.impl.SimpleFormatterImpl; import com.ibm.icu.impl.StandardPlural; import com.ibm.icu.impl.UResource; -import com.ibm.icu.impl.number.DecimalQuantity; -import com.ibm.icu.impl.number.modifiers.SimpleModifier; -import com.ibm.icu.text.NumberFormat.Field; +import com.ibm.icu.number.NumberFormatter.UnitWidth; +import com.ibm.icu.text.NumberFormat; import com.ibm.icu.text.PluralRules; import com.ibm.icu.util.Currency; import com.ibm.icu.util.ICUException; @@ -21,97 +20,17 @@ import com.ibm.icu.util.MeasureUnit; import com.ibm.icu.util.ULocale; import com.ibm.icu.util.UResourceBundle; -import newapi.NumberFormatter.UnitWidth; - public class LongNameHandler implements MicroPropsGenerator { - private final Map modifiers; - private PluralRules rules; - private MicroPropsGenerator parent; + ////////////////////////// + /// BEGIN DATA LOADING /// + ////////////////////////// - private LongNameHandler(Map modifiers) { - this.modifiers = modifiers; - } - - /** For use by the "safe" code path */ - private LongNameHandler(LongNameHandler other) { - this.modifiers = other.modifiers; - } - - public static LongNameHandler forCurrencyLongNames(ULocale loc, Currency currency) { - Map data = CurrencyData.provider.getInstance(loc, true).getUnitPatterns(); - Map result = new EnumMap(StandardPlural.class); - StringBuilder sb = new StringBuilder(); - for (Map.Entry e : data.entrySet()) { - String pluralKeyword = e.getKey(); - StandardPlural plural = StandardPlural.fromString(e.getKey()); - String longName = currency.getName(loc, Currency.PLURAL_LONG_NAME, pluralKeyword, null); - String simpleFormat = e.getValue(); - // Example pattern from data: "{0} {1}" - // Example output after find-and-replace: "{0} US dollars" - simpleFormat = simpleFormat.replace("{1}", longName); - String compiled = SimpleFormatterImpl.compileToStringMinMaxArguments(simpleFormat, sb, 1, 1); - SimpleModifier mod = new SimpleModifier(compiled, Field.CURRENCY, false); - result.put(plural, mod); - } - return new LongNameHandler(result); - } - - public static LongNameHandler forMeasureUnit(ULocale loc, MeasureUnit unit, UnitWidth width) { - Map simpleFormats = getMeasureData(loc, unit, width); - Map result = new EnumMap(StandardPlural.class); - StringBuilder sb = new StringBuilder(); - for (StandardPlural plural : StandardPlural.VALUES) { - String simpleFormat = simpleFormats.get(plural); - if (simpleFormat == null) { - simpleFormat = simpleFormats.get(StandardPlural.OTHER); - } - if (simpleFormat == null) { - // There should always be data in the "other" plural variant. - throw new ICUException("Could not find data in 'other' plural variant for unit " + unit); - } - String compiled = SimpleFormatterImpl.compileToStringMinMaxArguments(simpleFormat, sb, 1, 1); - // TODO: What field to use for units? - SimpleModifier mod = new SimpleModifier(compiled, null, false); - result.put(plural, mod); - } - return new LongNameHandler(result); - } - - /** - * Applies locale data and inserts a long-name handler into the quantity chain. - * - * @param rules - * The PluralRules instance to reference. - * @param parent - * The old head of the quantity chain. - * @return The new head of the quantity chain. - */ - public MicroPropsGenerator withLocaleData(PluralRules rules, MicroPropsGenerator parent) { - this.rules = rules; - this.parent = parent; - return this; - } - - @Override - public MicroProps processQuantity(DecimalQuantity quantity) { - MicroProps micros = parent.processQuantity(quantity); - // TODO: Avoid the copy here? - DecimalQuantity copy = quantity.createCopy(); - micros.rounding.apply(copy); - micros.modOuter = modifiers.get(copy.getStandardPlural(rules)); - return micros; - } - - /////////////////////////////////////// - /// BEGIN MEASURE UNIT DATA LOADING /// - /////////////////////////////////////// - - private static final class MeasureUnitSink extends UResource.Sink { + private static final class PluralTableSink extends UResource.Sink { Map output; - public MeasureUnitSink(Map output) { + public PluralTableSink(Map output) { this.output = output; } @@ -132,9 +51,11 @@ public class LongNameHandler implements MicroPropsGenerator { } } - private static Map getMeasureData(ULocale locale, MeasureUnit unit, UnitWidth width) { - ICUResourceBundle resource = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_UNIT_BASE_NAME, - locale); + private static void getMeasureData(ULocale locale, MeasureUnit unit, UnitWidth width, + Map output) { + PluralTableSink sink = new PluralTableSink(output); + ICUResourceBundle resource; + resource = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_UNIT_BASE_NAME, locale); StringBuilder key = new StringBuilder(); key.append("units"); if (width == UnitWidth.NARROW) { @@ -146,13 +67,89 @@ public class LongNameHandler implements MicroPropsGenerator { key.append(unit.getType()); key.append("/"); key.append(unit.getSubtype()); - Map output = new EnumMap(StandardPlural.class); - MeasureUnitSink sink = new MeasureUnitSink(output); resource.getAllItemsWithFallback(key.toString(), sink); - return output; } - ///////////////////////////////////// - /// END MEASURE UNIT DATA LOADING /// - ///////////////////////////////////// + private static void getCurrencyLongNameData(ULocale locale, Currency currency, Map output) { + // In ICU4J, this method gets a CurrencyData from CurrencyData.provider. + // TODO(ICU4J): Implement this without going through CurrencyData, like in ICU4C? + Map data = CurrencyData.provider.getInstance(locale, true).getUnitPatterns(); + for (Map.Entry e : data.entrySet()) { + String pluralKeyword = e.getKey(); + StandardPlural plural = StandardPlural.fromString(e.getKey()); + String longName = currency.getName(locale, Currency.PLURAL_LONG_NAME, pluralKeyword, null); + String simpleFormat = e.getValue(); + // Example pattern from data: "{0} {1}" + // Example output after find-and-replace: "{0} US dollars" + simpleFormat = simpleFormat.replace("{1}", longName); + // String compiled = SimpleFormatterImpl.compileToStringMinMaxArguments(simpleFormat, sb, 1, 1); + // SimpleModifier mod = new SimpleModifier(compiled, Field.CURRENCY, false); + output.put(plural, simpleFormat); + } + } + + //////////////////////// + /// END DATA LOADING /// + //////////////////////// + + private final Map modifiers; + private final PluralRules rules; + private final MicroPropsGenerator parent; + + private LongNameHandler(Map modifiers, PluralRules rules, + MicroPropsGenerator parent) { + this.modifiers = modifiers; + this.rules = rules; + this.parent = parent; + } + + public static LongNameHandler forCurrencyLongNames(ULocale locale, Currency currency, PluralRules rules, + MicroPropsGenerator parent) { + Map simpleFormats = new EnumMap(StandardPlural.class); + getCurrencyLongNameData(locale, currency, simpleFormats); + // TODO(ICU4J): Reduce the number of object creations here? + Map modifiers = new EnumMap( + StandardPlural.class); + simpleFormatsToModifiers(simpleFormats, null, modifiers); + return new LongNameHandler(modifiers, rules, parent); + } + + public static LongNameHandler forMeasureUnit(ULocale locale, MeasureUnit unit, UnitWidth width, PluralRules rules, + MicroPropsGenerator parent) { + Map simpleFormats = new EnumMap(StandardPlural.class); + getMeasureData(locale, unit, width, simpleFormats); + // TODO: What field to use for units? + // TODO(ICU4J): Reduce the number of object creations here? + Map modifiers = new EnumMap( + StandardPlural.class); + simpleFormatsToModifiers(simpleFormats, null, modifiers); + return new LongNameHandler(modifiers, rules, parent); + } + + private static void simpleFormatsToModifiers(Map simpleFormats, NumberFormat.Field field, + Map output) { + StringBuilder sb = new StringBuilder(); + for (StandardPlural plural : StandardPlural.VALUES) { + String simpleFormat = simpleFormats.get(plural); + if (simpleFormat == null) { + simpleFormat = simpleFormats.get(StandardPlural.OTHER); + } + if (simpleFormat == null) { + // There should always be data in the "other" plural variant. + throw new ICUException("Could not find data in 'other' plural variant with field " + field); + } + String compiled = SimpleFormatterImpl.compileToStringMinMaxArguments(simpleFormat, sb, 1, 1); + output.put(plural, new SimpleModifier(compiled, null, false)); + } + } + + @Override + public MicroProps processQuantity(DecimalQuantity quantity) { + MicroProps micros = parent.processQuantity(quantity); + // TODO: Avoid the copy here? + DecimalQuantity copy = quantity.createCopy(); + micros.rounding.apply(copy); + micros.modOuter = modifiers.get(copy.getStandardPlural(rules)); + return micros; + } } diff --git a/icu4j/main/classes/core/src/newapi/impl/MacroProps.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MacroProps.java similarity index 89% rename from icu4j/main/classes/core/src/newapi/impl/MacroProps.java rename to icu4j/main/classes/core/src/com/ibm/icu/impl/number/MacroProps.java index 8146daffdf5..f07b1782f39 100644 --- a/icu4j/main/classes/core/src/newapi/impl/MacroProps.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MacroProps.java @@ -1,21 +1,20 @@ // © 2017 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License -package newapi.impl; +package com.ibm.icu.impl.number; import java.util.Objects; +import com.ibm.icu.number.Grouper; +import com.ibm.icu.number.IntegerWidth; +import com.ibm.icu.number.Notation; +import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay; +import com.ibm.icu.number.NumberFormatter.SignDisplay; +import com.ibm.icu.number.NumberFormatter.UnitWidth; +import com.ibm.icu.number.Rounder; import com.ibm.icu.text.PluralRules; import com.ibm.icu.util.MeasureUnit; import com.ibm.icu.util.ULocale; -import newapi.Grouper; -import newapi.IntegerWidth; -import newapi.Notation; -import newapi.NumberFormatter.DecimalMarkDisplay; -import newapi.NumberFormatter.SignDisplay; -import newapi.NumberFormatter.UnitWidth; -import newapi.Rounder; - public class MacroProps implements Cloneable { public Notation notation; public MeasureUnit unit; @@ -26,7 +25,7 @@ public class MacroProps implements Cloneable { public Object symbols; public UnitWidth unitWidth; public SignDisplay sign; - public DecimalMarkDisplay decimal; + public DecimalSeparatorDisplay decimal; public AffixPatternProvider affixProvider; // not in API; for JDK compatibility mode only public MultiplierImpl multiplier; // not in API; for JDK compatibility mode only public PluralRules rules; // not in API; could be made public in the future diff --git a/icu4j/main/classes/core/src/newapi/impl/MicroProps.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MicroProps.java similarity index 82% rename from icu4j/main/classes/core/src/newapi/impl/MicroProps.java rename to icu4j/main/classes/core/src/com/ibm/icu/impl/number/MicroProps.java index 03f52da2d63..2c15dfc87ed 100644 --- a/icu4j/main/classes/core/src/newapi/impl/MicroProps.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MicroProps.java @@ -1,23 +1,20 @@ // © 2017 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License -package newapi.impl; +package com.ibm.icu.impl.number; -import com.ibm.icu.impl.number.DecimalQuantity; -import com.ibm.icu.impl.number.Modifier; +import com.ibm.icu.number.Grouper; +import com.ibm.icu.number.IntegerWidth; +import com.ibm.icu.number.Rounder; +import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay; +import com.ibm.icu.number.NumberFormatter.SignDisplay; import com.ibm.icu.text.DecimalFormatSymbols; -import newapi.Grouper; -import newapi.IntegerWidth; -import newapi.NumberFormatter.DecimalMarkDisplay; -import newapi.NumberFormatter.SignDisplay; -import newapi.Rounder; - public class MicroProps implements Cloneable, MicroPropsGenerator { // Populated globally: public SignDisplay sign; public DecimalFormatSymbols symbols; public Padder padding; - public DecimalMarkDisplay decimal; + public DecimalSeparatorDisplay decimal; public IntegerWidth integerWidth; // Populated by notation/unit: @@ -26,7 +23,6 @@ public class MicroProps implements Cloneable, MicroPropsGenerator { public Modifier modInner; public Rounder rounding; public Grouper grouping; - public int multiplier; public boolean useCurrency; // Internal fields: diff --git a/icu4j/main/classes/core/src/newapi/impl/MicroPropsGenerator.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MicroPropsGenerator.java similarity index 96% rename from icu4j/main/classes/core/src/newapi/impl/MicroPropsGenerator.java rename to icu4j/main/classes/core/src/com/ibm/icu/impl/number/MicroPropsGenerator.java index 72620e7f5ae..c633ce7ec70 100644 --- a/icu4j/main/classes/core/src/newapi/impl/MicroPropsGenerator.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MicroPropsGenerator.java @@ -1,8 +1,6 @@ // © 2017 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License -package newapi.impl; - -import com.ibm.icu.impl.number.DecimalQuantity; +package com.ibm.icu.impl.number; /** * This interface is used when all number formatting settings, including the locale, are known, except for the quantity diff --git a/icu4j/main/classes/core/src/newapi/impl/MicroPropsMutator.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MicroPropsMutator.java similarity index 88% rename from icu4j/main/classes/core/src/newapi/impl/MicroPropsMutator.java rename to icu4j/main/classes/core/src/com/ibm/icu/impl/number/MicroPropsMutator.java index 3c01a3b8636..962f8d133f5 100644 --- a/icu4j/main/classes/core/src/newapi/impl/MicroPropsMutator.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MicroPropsMutator.java @@ -1,6 +1,6 @@ // © 2017 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License -package newapi.impl; +package com.ibm.icu.impl.number; /** * @author sffc diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Modifier.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Modifier.java index bff553636c8..b37e4d740fa 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Modifier.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Modifier.java @@ -2,8 +2,6 @@ // License & terms of use: http://www.unicode.org/copyright.html#License package com.ibm.icu.impl.number; -import newapi.impl.MutablePatternModifier; - /** * A Modifier is an object that can be passed through the formatting pipeline until it is finally applied to the string * builder. A Modifier usually contains a prefix and a suffix that are applied, but it could contain something else, diff --git a/icu4j/main/classes/core/src/newapi/impl/MultiplierImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MultiplierImpl.java similarity index 94% rename from icu4j/main/classes/core/src/newapi/impl/MultiplierImpl.java rename to icu4j/main/classes/core/src/com/ibm/icu/impl/number/MultiplierImpl.java index 1ef44e2b887..ca1b8ad59e1 100644 --- a/icu4j/main/classes/core/src/newapi/impl/MultiplierImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MultiplierImpl.java @@ -1,11 +1,9 @@ // © 2017 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License -package newapi.impl; +package com.ibm.icu.impl.number; import java.math.BigDecimal; -import com.ibm.icu.impl.number.DecimalQuantity; - public class MultiplierImpl implements MicroPropsGenerator, Cloneable { final int magnitudeMultiplier; final BigDecimal bigDecimalMultiplier; diff --git a/icu4j/main/classes/core/src/newapi/impl/MultiplierProducer.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MultiplierProducer.java similarity index 85% rename from icu4j/main/classes/core/src/newapi/impl/MultiplierProducer.java rename to icu4j/main/classes/core/src/com/ibm/icu/impl/number/MultiplierProducer.java index 814ceaabf25..2e7b96def7f 100644 --- a/icu4j/main/classes/core/src/newapi/impl/MultiplierProducer.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MultiplierProducer.java @@ -1,6 +1,6 @@ // © 2017 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License -package newapi.impl; +package com.ibm.icu.impl.number; public interface MultiplierProducer { int getMultiplier(int magnitude); diff --git a/icu4j/main/classes/core/src/newapi/impl/MutablePatternModifier.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MutablePatternModifier.java similarity index 96% rename from icu4j/main/classes/core/src/newapi/impl/MutablePatternModifier.java rename to icu4j/main/classes/core/src/com/ibm/icu/impl/number/MutablePatternModifier.java index 497dddbc787..ad61dd01965 100644 --- a/icu4j/main/classes/core/src/newapi/impl/MutablePatternModifier.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MutablePatternModifier.java @@ -1,24 +1,15 @@ // © 2017 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License -package newapi.impl; +package com.ibm.icu.impl.number; import com.ibm.icu.impl.StandardPlural; -import com.ibm.icu.impl.number.AffixUtils; import com.ibm.icu.impl.number.AffixUtils.SymbolProvider; -import com.ibm.icu.impl.number.DecimalQuantity; -import com.ibm.icu.impl.number.Modifier; -import com.ibm.icu.impl.number.NumberStringBuilder; -import com.ibm.icu.impl.number.ParameterizedModifier; -import com.ibm.icu.impl.number.PatternStringParser; -import com.ibm.icu.impl.number.modifiers.ConstantMultiFieldModifier; -import com.ibm.icu.impl.number.modifiers.CurrencySpacingEnabledModifier; +import com.ibm.icu.number.NumberFormatter.SignDisplay; +import com.ibm.icu.number.NumberFormatter.UnitWidth; import com.ibm.icu.text.DecimalFormatSymbols; import com.ibm.icu.text.PluralRules; import com.ibm.icu.util.Currency; -import newapi.NumberFormatter.SignDisplay; -import newapi.NumberFormatter.UnitWidth; - /** * This class is a {@link Modifier} that wraps a decimal format pattern. It applies the pattern's affixes in * {@link Modifier#apply}. @@ -37,8 +28,6 @@ import newapi.NumberFormatter.UnitWidth; * it from multiple threads! Instead, you can obtain a safe, immutable decimal format pattern modifier by calling * {@link MutablePatternModifier#createImmutable}, in effect treating this instance as a builder for the immutable * variant. - * - * FIXME: Make this package-private */ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSequence, MicroPropsGenerator { @@ -331,9 +320,11 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq case AffixUtils.TYPE_PERMILLE: return symbols.getPerMillString(); case AffixUtils.TYPE_CURRENCY_SINGLE: - // UnitWidth ISO overrides the singular currency symbol. + // UnitWidth ISO or HIDDEN overrides the singular currency symbol. if (unitWidth == UnitWidth.ISO_CODE) { return currency.getCurrencyCode(); + } else if (unitWidth == UnitWidth.HIDDEN) { + return ""; } else { return currency.getName(symbols.getULocale(), Currency.SYMBOL_NAME, null); } diff --git a/icu4j/main/classes/core/src/newapi/impl/Padder.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Padder.java similarity index 81% rename from icu4j/main/classes/core/src/newapi/impl/Padder.java rename to icu4j/main/classes/core/src/com/ibm/icu/impl/number/Padder.java index 6180fd58ddb..c1be7034d4c 100644 --- a/icu4j/main/classes/core/src/newapi/impl/Padder.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Padder.java @@ -1,9 +1,6 @@ // © 2017 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License -package newapi.impl; - -import com.ibm.icu.impl.number.Modifier; -import com.ibm.icu.impl.number.NumberStringBuilder; +package com.ibm.icu.impl.number; public class Padder { public static final String FALLBACK_PADDING_STRING = "\u0020"; // i.e. a space @@ -45,7 +42,7 @@ public class Padder { } } - private static final Padder NONE = new Padder(null, -1, null); + /* like package-private */ public static final Padder NONE = new Padder(null, -1, null); String paddingString; int targetWidth; @@ -53,7 +50,7 @@ public class Padder { public Padder(String paddingString, int targetWidth, PadPosition position) { // TODO: Add a few default instances - this.paddingString = (paddingString == null) ? " " : paddingString; + this.paddingString = (paddingString == null) ? FALLBACK_PADDING_STRING : paddingString; this.targetWidth = targetWidth; this.position = (position == null) ? PadPosition.BEFORE_PREFIX : position; } @@ -102,24 +99,6 @@ public class Padder { length += addPaddingHelper(paddingString, requiredPadding, string, rightIndex + length); } - // The length might not be exactly right due to currency spacing. - // Make an adjustment if needed. - while (string.codePointCount() < targetWidth) { - int insertIndex = mod1.getPrefixLength() + mod2.getPrefixLength(); - switch (position) { - case AFTER_PREFIX: - insertIndex += leftIndex; - break; - case BEFORE_SUFFIX: - insertIndex += rightIndex; - break; - default: - // Should not happen since currency spacing is always on the inside. - throw new AssertionError(); - } - addPaddingHelper(paddingString, requiredPadding, string, insertIndex); - } - return length; } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/ParameterizedModifier.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/ParameterizedModifier.java new file mode 100644 index 00000000000..5a7191af67c --- /dev/null +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/ParameterizedModifier.java @@ -0,0 +1,68 @@ +// © 2017 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html#License +package com.ibm.icu.impl.number; + +import com.ibm.icu.impl.StandardPlural; + +/** + * A ParameterizedModifier by itself is NOT a Modifier. Rather, it wraps a data structure containing two or more + * Modifiers and returns the modifier appropriate for the current situation. + */ +public class ParameterizedModifier { + private final Modifier positive; + private final Modifier negative; + final Modifier[] mods; + boolean frozen; + + /** + * This constructor populates the ParameterizedModifier with a single positive and negative form. + * + *

+ * If this constructor is used, a plural form CANNOT be passed to {@link #getModifier}. + */ + public ParameterizedModifier(Modifier positive, Modifier negative) { + this.positive = positive; + this.negative = negative; + this.mods = null; + this.frozen = true; + } + + /** + * This constructor prepares the ParameterizedModifier to be populated with a positive and negative Modifier for + * multiple plural forms. + * + *

+ * If this constructor is used, a plural form MUST be passed to {@link #getModifier}. + */ + public ParameterizedModifier() { + this.positive = null; + this.negative = null; + this.mods = new Modifier[2 * StandardPlural.COUNT]; + this.frozen = false; + } + + public void setModifier(boolean isNegative, StandardPlural plural, Modifier mod) { + assert !frozen; + mods[getModIndex(isNegative, plural)] = mod; + } + + public void freeze() { + frozen = true; + } + + public Modifier getModifier(boolean isNegative) { + assert frozen; + assert mods == null; + return isNegative ? negative : positive; + } + + public Modifier getModifier(boolean isNegative, StandardPlural plural) { + assert frozen; + assert positive == null; + return mods[getModIndex(isNegative, plural)]; + } + + private static int getModIndex(boolean isNegative, StandardPlural plural) { + return plural.ordinal() * 2 + (isNegative ? 1 : 0); + } +} diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/PatternStringParser.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/PatternStringParser.java index 853ed1dd568..50336bc09b7 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/PatternStringParser.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/PatternStringParser.java @@ -2,8 +2,7 @@ // License & terms of use: http://www.unicode.org/copyright.html#License package com.ibm.icu.impl.number; -import newapi.impl.AffixPatternProvider; -import newapi.impl.Padder.PadPosition; +import com.ibm.icu.impl.number.Padder.PadPosition; /** Implements a recursive descent parser for decimal format patterns. */ public class PatternStringParser { diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/PatternStringUtils.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/PatternStringUtils.java index cfd23d0d03b..4bed35f1f56 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/PatternStringUtils.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/PatternStringUtils.java @@ -4,11 +4,9 @@ package com.ibm.icu.impl.number; import java.math.BigDecimal; +import com.ibm.icu.impl.number.Padder.PadPosition; import com.ibm.icu.text.DecimalFormatSymbols; -import newapi.impl.Padder; -import newapi.impl.Padder.PadPosition; - /** * Assorted utilities relating to decimal formatting pattern strings. */ diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/RoundingUtils.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/RoundingUtils.java index e07c6e8c1a7..0c97552c487 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/RoundingUtils.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/RoundingUtils.java @@ -13,6 +13,17 @@ public class RoundingUtils { public static final int SECTION_MIDPOINT = 2; public static final int SECTION_UPPER = 3; + /** + * The default rounding mode. + */ + public static final RoundingMode DEFAULT_ROUNDING_MODE = RoundingMode.HALF_EVEN; + + /** + * The maximum number of fraction places, integer numerals, or significant digits. + * TODO: This does not feel like the best home for this value. + */ + public static final int MAX_INT_FRAC_SIG = 100; + /** * Converts a rounding mode and metadata about the quantity being rounded to a boolean determining * whether the value should be rounded toward infinity or toward zero. diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/modifiers/SimpleModifier.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/SimpleModifier.java similarity index 96% rename from icu4j/main/classes/core/src/com/ibm/icu/impl/number/modifiers/SimpleModifier.java rename to icu4j/main/classes/core/src/com/ibm/icu/impl/number/SimpleModifier.java index a318a4fe2c2..6de0284e6f2 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/modifiers/SimpleModifier.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/SimpleModifier.java @@ -1,10 +1,8 @@ // © 2017 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License -package com.ibm.icu.impl.number.modifiers; +package com.ibm.icu.impl.number; import com.ibm.icu.impl.SimpleFormatterImpl; -import com.ibm.icu.impl.number.Modifier; -import com.ibm.icu.impl.number.NumberStringBuilder; import com.ibm.icu.text.NumberFormat.Field; /** diff --git a/icu4j/main/classes/core/src/newapi/CompactNotation.java b/icu4j/main/classes/core/src/com/ibm/icu/number/CompactNotation.java similarity index 85% rename from icu4j/main/classes/core/src/newapi/CompactNotation.java rename to icu4j/main/classes/core/src/com/ibm/icu/number/CompactNotation.java index 6f2415aae71..bc5375eb483 100644 --- a/icu4j/main/classes/core/src/newapi/CompactNotation.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/CompactNotation.java @@ -1,6 +1,6 @@ // © 2017 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License -package newapi; +package com.ibm.icu.number; import java.util.HashMap; import java.util.HashSet; @@ -8,34 +8,41 @@ import java.util.Map; import java.util.Set; import com.ibm.icu.impl.StandardPlural; +import com.ibm.icu.impl.number.CompactData; +import com.ibm.icu.impl.number.CompactData.CompactType; import com.ibm.icu.impl.number.DecimalQuantity; +import com.ibm.icu.impl.number.MicroProps; +import com.ibm.icu.impl.number.MicroPropsGenerator; +import com.ibm.icu.impl.number.MutablePatternModifier; +import com.ibm.icu.impl.number.MutablePatternModifier.ImmutablePatternModifier; import com.ibm.icu.impl.number.PatternStringParser; import com.ibm.icu.impl.number.PatternStringParser.ParsedPatternInfo; import com.ibm.icu.text.CompactDecimalFormat.CompactStyle; import com.ibm.icu.text.PluralRules; import com.ibm.icu.util.ULocale; -import newapi.impl.CompactData; -import newapi.impl.MicroProps; -import newapi.impl.MicroPropsGenerator; -import newapi.impl.MutablePatternModifier; -import newapi.impl.MutablePatternModifier.ImmutablePatternModifier; +/** + * A class that defines the scientific notation style to be used when formatting numbers in NumberFormatter. + * + *

+ * This class exposes no public functionality. To create a CompactNotation, use one of the factory methods in + * {@link Notation}. + * + * @draft ICU 60 + * @see NumberFormatter + */ public class CompactNotation extends Notation { final CompactStyle compactStyle; final Map> compactCustomData; - public enum CompactType { - DECIMAL, CURRENCY - } - - public CompactNotation(CompactStyle compactStyle) { + /* package-private */ CompactNotation(CompactStyle compactStyle) { compactCustomData = null; this.compactStyle = compactStyle; } - public CompactNotation(Map> compactCustomData) { + /* package-private */ CompactNotation(Map> compactCustomData) { compactStyle = null; this.compactCustomData = compactCustomData; } @@ -118,7 +125,7 @@ public class CompactNotation extends Notation { // No need to take any action. } else if (precomputedMods != null) { // Safe code path. - // Java uses a hash set here for O(1) lookup. C++ uses a linear search. + // Java uses a hash set here for O(1) lookup. C++ uses a linear search. CompactModInfo info = precomputedMods.get(patternString); info.mod.applyToMicros(micros, quantity); numDigits = info.numDigits; diff --git a/icu4j/main/classes/core/src/newapi/CurrencyRounder.java b/icu4j/main/classes/core/src/com/ibm/icu/number/CurrencyRounder.java similarity index 52% rename from icu4j/main/classes/core/src/newapi/CurrencyRounder.java rename to icu4j/main/classes/core/src/com/ibm/icu/number/CurrencyRounder.java index 0749dcc4080..041494fa45f 100644 --- a/icu4j/main/classes/core/src/newapi/CurrencyRounder.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/CurrencyRounder.java @@ -1,28 +1,31 @@ // © 2017 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License -package newapi; +package com.ibm.icu.number; import com.ibm.icu.util.Currency; -import com.ibm.icu.util.Currency.CurrencyUsage; -import com.ibm.icu.util.CurrencyAmount; -import com.ibm.icu.util.Measure; -import com.ibm.icu.util.MeasureUnit; -/** A rounding strategy parameterized by a currency. */ +/** + * A class that defines a rounding strategy parameterized by a currency to be used when formatting numbers in + * NumberFormatter. + * + *

+ * To create a CurrencyRounder, use one of the factory methods on Rounder. + * + * @draft ICU 60 + * @see NumberFormatter + */ public abstract class CurrencyRounder extends Rounder { /* package-private */ CurrencyRounder() { } /** - * Associates a {@link com.ibm.icu.util.Currency} with this rounding strategy. Only applies to rounding strategies - * returned from {@link #currency(CurrencyUsage)}. + * Associates a currency with this rounding strategy. * *

- * Calling this method is not required, because the currency specified in - * {@link NumberFormatterSettings#unit(MeasureUnit)} or via a {@link CurrencyAmount} passed into - * {@link LocalizedNumberFormatter#format(Measure)} is automatically applied to currency rounding strategies. - * However, this method enables you to override that automatic association. + * Calling this method is not required, because the currency specified in unit() or via a + * CurrencyAmount passed into format(Measure) is automatically applied to currency rounding strategies. However, + * this method enables you to override that automatic association. * *

* This method also enables numbers to be formatted using currency rounding rules without explicitly using a @@ -30,7 +33,9 @@ public abstract class CurrencyRounder extends Rounder { * * @param currency * The currency to associate with this rounding strategy. - * @return An immutable object for chaining. + * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. + * @draft ICU 60 + * @see NumberFormatter */ public Rounder withCurrency(Currency currency) { if (currency != null) { diff --git a/icu4j/main/classes/core/src/newapi/FormattedNumber.java b/icu4j/main/classes/core/src/com/ibm/icu/number/FormattedNumber.java similarity index 98% rename from icu4j/main/classes/core/src/newapi/FormattedNumber.java rename to icu4j/main/classes/core/src/com/ibm/icu/number/FormattedNumber.java index 682f88cb655..69762031366 100644 --- a/icu4j/main/classes/core/src/newapi/FormattedNumber.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/FormattedNumber.java @@ -1,6 +1,6 @@ // © 2017 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License -package newapi; +package com.ibm.icu.number; import java.io.IOException; import java.math.BigDecimal; @@ -9,12 +9,11 @@ import java.text.FieldPosition; import java.util.Arrays; import com.ibm.icu.impl.number.DecimalQuantity; +import com.ibm.icu.impl.number.MicroProps; import com.ibm.icu.impl.number.NumberStringBuilder; import com.ibm.icu.text.PluralRules.IFixedDecimal; import com.ibm.icu.util.ICUUncheckedIOException; -import newapi.impl.MicroProps; - public class FormattedNumber { NumberStringBuilder nsb; DecimalQuantity fq; diff --git a/icu4j/main/classes/core/src/newapi/FractionRounder.java b/icu4j/main/classes/core/src/com/ibm/icu/number/FractionRounder.java similarity index 56% rename from icu4j/main/classes/core/src/newapi/FractionRounder.java rename to icu4j/main/classes/core/src/com/ibm/icu/number/FractionRounder.java index 3d2a5f7eac2..a8ff7fc3a4f 100644 --- a/icu4j/main/classes/core/src/newapi/FractionRounder.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/FractionRounder.java @@ -1,10 +1,18 @@ // © 2017 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License -package newapi; +package com.ibm.icu.number; + +import com.ibm.icu.impl.number.RoundingUtils; /** - * A rounding strategy based on a minimum and/or maximum number of fraction digits. Allows for a minimum or maximum - * number of significant digits to be specified. + * A class that defines a rounding strategy based on a number of fraction places and optionally significant digits to be + * used when formatting numbers in NumberFormatter. + * + *

+ * To create a FractionRounder, use one of the factory methods on Rounder. + * + * @draft ICU 60 + * @see NumberFormatter */ public abstract class FractionRounder extends Rounder { @@ -12,7 +20,7 @@ public abstract class FractionRounder extends Rounder { } /** - * Ensures that no less than this number of significant figures are retained when rounding according to fraction + * Ensure that no less than this number of significant digits are retained when rounding according to fraction * rules. * *

@@ -24,18 +32,21 @@ public abstract class FractionRounder extends Rounder { * * @param minSignificantDigits * The number of significant figures to guarantee. - * @return An immutable object for chaining. + * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. + * @draft ICU 60 + * @see NumberFormatter */ public Rounder withMinDigits(int minSignificantDigits) { - if (minSignificantDigits > 0 && minSignificantDigits <= MAX_VALUE) { + if (minSignificantDigits > 0 && minSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) { return constructFractionSignificant(this, minSignificantDigits, -1); } else { - throw new IllegalArgumentException("Significant digits must be between 0 and " + MAX_VALUE); + throw new IllegalArgumentException( + "Significant digits must be between 0 and " + RoundingUtils.MAX_INT_FRAC_SIG); } } /** - * Ensures that no more than this number of significant figures are retained when rounding according to fraction + * Ensure that no more than this number of significant digits are retained when rounding according to fraction * rules. * *

@@ -48,13 +59,16 @@ public abstract class FractionRounder extends Rounder { * * @param maxSignificantDigits * Round the number to no more than this number of significant figures. - * @return An immutable object for chaining. + * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. + * @draft ICU 60 + * @see NumberFormatter */ public Rounder withMaxDigits(int maxSignificantDigits) { - if (maxSignificantDigits > 0 && maxSignificantDigits <= MAX_VALUE) { + if (maxSignificantDigits > 0 && maxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) { return constructFractionSignificant(this, -1, maxSignificantDigits); } else { - throw new IllegalArgumentException("Significant digits must be between 0 and " + MAX_VALUE); + throw new IllegalArgumentException( + "Significant digits must be between 0 and " + RoundingUtils.MAX_INT_FRAC_SIG); } } } \ No newline at end of file diff --git a/icu4j/main/classes/core/src/newapi/Grouper.java b/icu4j/main/classes/core/src/com/ibm/icu/number/Grouper.java similarity index 94% rename from icu4j/main/classes/core/src/newapi/Grouper.java rename to icu4j/main/classes/core/src/com/ibm/icu/number/Grouper.java index 296dbc3cb24..e5b030be036 100644 --- a/icu4j/main/classes/core/src/newapi/Grouper.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/Grouper.java @@ -1,6 +1,6 @@ // © 2017 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License -package newapi; +package com.ibm.icu.number; import com.ibm.icu.impl.number.DecimalQuantity; import com.ibm.icu.impl.number.PatternStringParser.ParsedPatternInfo; @@ -31,7 +31,7 @@ public class Grouper { return DEFAULTS; } - public static Grouper min2() { + public static Grouper minTwoDigits() { return MIN2; } @@ -64,10 +64,6 @@ public class Grouper { } } - static Grouper normalizeType(Grouper grouping, ParsedPatternInfo patternInfo) { - return grouping.withLocaleData(patternInfo); - } - Grouper withLocaleData(ParsedPatternInfo patternInfo) { if (grouping1 != -2) { return this; diff --git a/icu4j/main/classes/core/src/newapi/IntegerWidth.java b/icu4j/main/classes/core/src/com/ibm/icu/number/IntegerWidth.java similarity index 57% rename from icu4j/main/classes/core/src/newapi/IntegerWidth.java rename to icu4j/main/classes/core/src/com/ibm/icu/number/IntegerWidth.java index e092ec08615..e1fcb9c87ee 100644 --- a/icu4j/main/classes/core/src/newapi/IntegerWidth.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/IntegerWidth.java @@ -1,11 +1,13 @@ // © 2017 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License -package newapi; +package com.ibm.icu.number; + +import com.ibm.icu.impl.number.RoundingUtils; @SuppressWarnings("unused") public class IntegerWidth { - private static final IntegerWidth DEFAULT = new IntegerWidth(1, -1); + /* package-private */ static final IntegerWidth DEFAULT = new IntegerWidth(1, -1); final int minInt; final int maxInt; @@ -18,22 +20,24 @@ public class IntegerWidth { public static IntegerWidth zeroFillTo(int minInt) { if (minInt == 1) { return DEFAULT; - } else if (minInt >= 0 && minInt < Rounder.MAX_VALUE) { + } else if (minInt >= 0 && minInt < RoundingUtils.MAX_INT_FRAC_SIG) { return new IntegerWidth(minInt, -1); } else { - throw new IllegalArgumentException("Integer digits must be between 0 and " + Rounder.MAX_VALUE); + throw new IllegalArgumentException( + "Integer digits must be between 0 and " + RoundingUtils.MAX_INT_FRAC_SIG); } } public IntegerWidth truncateAt(int maxInt) { if (maxInt == this.maxInt) { return this; - } else if (maxInt >= 0 && maxInt < Rounder.MAX_VALUE) { + } else if (maxInt >= 0 && maxInt < RoundingUtils.MAX_INT_FRAC_SIG) { return new IntegerWidth(minInt, maxInt); } else if (maxInt == -1) { return new IntegerWidth(minInt, maxInt); } else { - throw new IllegalArgumentException("Integer digits must be between 0 and " + Rounder.MAX_VALUE); + throw new IllegalArgumentException( + "Integer digits must be between 0 and " + RoundingUtils.MAX_INT_FRAC_SIG); } } } \ No newline at end of file diff --git a/icu4j/main/classes/core/src/newapi/LocalizedNumberFormatter.java b/icu4j/main/classes/core/src/com/ibm/icu/number/LocalizedNumberFormatter.java similarity index 89% rename from icu4j/main/classes/core/src/newapi/LocalizedNumberFormatter.java rename to icu4j/main/classes/core/src/com/ibm/icu/number/LocalizedNumberFormatter.java index b71f1a815bb..d17ec7e2554 100644 --- a/icu4j/main/classes/core/src/newapi/LocalizedNumberFormatter.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/LocalizedNumberFormatter.java @@ -1,19 +1,18 @@ // © 2017 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License -package newapi; +package com.ibm.icu.number; import java.util.Objects; import java.util.concurrent.atomic.AtomicLongFieldUpdater; import com.ibm.icu.impl.number.DecimalQuantity; import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD; +import com.ibm.icu.impl.number.MacroProps; +import com.ibm.icu.impl.number.MicroProps; import com.ibm.icu.impl.number.NumberStringBuilder; import com.ibm.icu.util.Measure; import com.ibm.icu.util.MeasureUnit; -import newapi.impl.MacroProps; -import newapi.impl.MicroProps; - public class LocalizedNumberFormatter extends NumberFormatterSettings { static final AtomicLongFieldUpdater callCount = AtomicLongFieldUpdater @@ -60,6 +59,9 @@ public class LocalizedNumberFormatter extends NumberFormatterSettings + * This function is very hot, being called in every call to the number formatting pipeline. + * * @param fq * The quantity to be formatted. * @return The formatted number result. @@ -70,9 +72,11 @@ public class LocalizedNumberFormatter extends NumberFormatterSettings + * Example outputs in en-US when printing 8.765E4 through 8.765E-3: + * + *

+     * 8.765E4
+     * 8.765E3
+     * 8.765E2
+     * 8.765E1
+     * 8.765E0
+     * 8.765E-1
+     * 8.765E-2
+     * 8.765E-3
+     * 0E0
+     * 
+ * + * @return A ScientificNotation for chaining or passing to the NumberFormatter notation() setter. + * @draft ICU 60 + * @see NumberFormatter + */ + public static ScientificNotation scientific() { + return SCIENTIFIC; + } + + /** + * Print the number using engineering notation, a variant of scientific notation in which the exponent must be + * divisible by 3. + * + *

+ * Example outputs in en-US when printing 8.765E4 through 8.765E-3: + * + *

+     * 87.65E3
+     * 8.765E3
+     * 876.5E0
+     * 87.65E0
+     * 8.765E0
+     * 876.5E-3
+     * 87.65E-3
+     * 8.765E-3
+     * 0E0
+     * 
+ * + * @return A ScientificNotation for chaining or passing to the NumberFormatter notation() setter. + * @draft ICU 60 + * @see NumberFormatter + */ + public static ScientificNotation engineering() { + return ENGINEERING; + } + + /** + * Print the number using short-form compact notation. + * + *

+ * Compact notation, defined in Unicode Technical Standard #35 Part 3 Section 2.4.1, prints numbers with + * localized prefixes or suffixes corresponding to different powers of ten. Compact notation is similar to + * engineering notation in how it scales numbers. + * + *

+ * Compact notation is ideal for displaying large numbers (over ~1000) to humans while at the same time minimizing + * screen real estate. + * + *

+ * In short form, the powers of ten are abbreviated. In en-US, the abbreviations are "K" for thousands, "M" + * for millions, "B" for billions, and "T" for trillions. Example outputs in en-US when printing 8.765E7 + * through 8.765E0: + * + *

+     * 88M
+     * 8.8M
+     * 876K
+     * 88K
+     * 8.8K
+     * 876
+     * 88
+     * 8.8
+     * 
+ * + *

+ * When compact notation is specified without an explicit rounding strategy, numbers are rounded off to the closest + * integer after scaling the number by the corresponding power of 10, but with a digit shown after the decimal + * separator if there is only one digit before the decimal separator. The default compact notation rounding strategy + * is equivalent to: + * + *

+     * Rounder.integer().withMinDigits(2)
+     * 
+ * + * @return A CompactNotation for passing to the NumberFormatter notation() setter. + * @draft ICU 60 + * @see NumberFormatter + */ + public static CompactNotation compactShort() { + return COMPACT_SHORT; + } + + /** + * Print the number using long-form compact notation. For more information on compact notation, see + * {@link #compactShort}. + * + *

+ * In long form, the powers of ten are spelled out fully. Example outputs in en-US when printing 8.765E7 + * through 8.765E0: + * + *

+     * 88 million
+     * 8.8 million
+     * 876 thousand
+     * 88 thousand
+     * 8.8 thousand
+     * 876
+     * 88
+     * 8.8
+     * 
+ * + * @return A CompactNotation for passing to the NumberFormatter notation() setter. + * @draft ICU 60 + * @see NumberFormatter + */ + public static CompactNotation compactLong() { + return COMPACT_LONG; + } + + /** + * Print the number using simple notation without any scaling by powers of ten. This is the default behavior. + * + *

+ * Since this is the default behavior, this method needs to be called only when it is necessary to override a + * previous setting. + * + *

+ * Example outputs in en-US when printing 8.765E7 through 8.765E0: + * + *

+     * 87,650,000
+     * 8,765,000
+     * 876,500
+     * 87,650
+     * 8,765
+     * 876.5
+     * 87.65
+     * 8.765
+     * 
+ * + * @return A SimpleNotation for passing to the NumberFormatter notation() setter. + * @draft ICU 60 + * @see NumberFormatter + */ + public static SimpleNotation simple() { + return SIMPLE; + } +} \ No newline at end of file diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatter.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatter.java new file mode 100644 index 00000000000..c0c3faa0d23 --- /dev/null +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatter.java @@ -0,0 +1,260 @@ +// © 2017 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html#License +package com.ibm.icu.number; + +import java.util.Locale; + +import com.ibm.icu.impl.number.DecimalFormatProperties; +import com.ibm.icu.impl.number.MacroProps; +import com.ibm.icu.text.DecimalFormatSymbols; +import com.ibm.icu.util.ULocale; + +public final class NumberFormatter { + + private static final UnlocalizedNumberFormatter BASE = new UnlocalizedNumberFormatter(); + + /** + * An enum declaring how to render units, including currencies. Example outputs when formatting 123 USD and 123 + * meters in en-CA: + * + *

+ *

    + *
  • NARROW*: "$123.00" and "123 m" + *
  • SHORT: "US$ 123.00" and "123 m" + *
  • FULL_NAME: "123.00 US dollars" and "123 meters" + *
  • ISO_CODE: "USD 123.00" and undefined behavior + *
  • HIDDEN: "123.00" and "123" + *
+ * + *

+ * * The narrow format for currencies is not currently supported; this is a known issue that will be fixed in a + * future version. See #11666 for more information. + * + *

+ * This enum is similar to {@link com.ibm.icu.text.MeasureFormat.FormatWidth}. + * + * @draft ICU 60 + * @see NumberFormatter + */ + public static enum UnitWidth { + /** + * Print an abbreviated version of the unit name. Similar to SHORT, but always use the shortest available + * abbreviation or symbol. This option can be used when the context hints at the identity of the unit. For more + * information on the difference between NARROW and SHORT, see SHORT. + * + *

+ * In CLDR, this option corresponds to the "Narrow" format for measure units and the "¤¤¤¤¤" placeholder for + * currencies. + * + * @draft ICU 60 + * @see NumberFormatter + */ + NARROW, + + /** + * Print an abbreviated version of the unit name. Similar to NARROW, but use a slightly wider abbreviation or + * symbol when there may be ambiguity. This is the default behavior. + * + *

+ * For example, in es-US, the SHORT form for Fahrenheit is "{0} °F", but the NARROW form is "{0}°", + * since Fahrenheit is the customary unit for temperature in that locale. + * + *

+ * In CLDR, this option corresponds to the "Short" format for measure units and the "¤" placeholder for + * currencies. + * + * @draft ICU 60 + * @see NumberFormatter + */ + SHORT, + + /** + * Print the full name of the unit, without any abbreviations. + * + *

+ * In CLDR, this option corresponds to the default format for measure units and the "¤¤¤" placeholder for + * currencies. + * + * @draft ICU 60 + * @see NumberFormatter + */ + FULL_NAME, + + /** + * Use the three-digit ISO XXX code in place of the symbol for displaying currencies. The behavior of this + * option is currently undefined for use with measure units. + * + *

+ * In CLDR, this option corresponds to the "¤¤" placeholder for currencies. + * + * @draft ICU 60 + * @see NumberFormatter + */ + ISO_CODE, + + /** + * Format the number according to the specified unit, but do not display the unit. For currencies, apply + * monetary symbols and formats as with SHORT, but omit the currency symbol. For measure units, the behavior is + * equivalent to not specifying the unit at all. + * + * @draft ICU 60 + * @see NumberFormatter + */ + HIDDEN, + } + + /** + * An enum declaring how to denote positive and negative numbers. Example outputs when formatting 123 and -123 in + * en-US: + * + *

+ *

    + *
  • AUTO: "123" and "-123" + *
  • ALWAYS: "+123" and "-123" + *
  • NEVER: "123" and "123" + *
  • ACCOUNTING: "$123" and "($123)" + *
  • ACCOUNTING_ALWAYS: "+$123" and "($123)" + *
+ * + *

+ * The exact format, including the position and the code point of the sign, differ by locale. + * + * @draft ICU 60 + * @see NumberFormatter + */ + public static enum SignDisplay { + /** + * Show the minus sign on negative numbers, and do not show the sign on positive numbers. This is the default + * behavior. + * + * @draft ICU 60 + * @see NumberFormatter + */ + AUTO, + + /** + * Show the minus sign on negative numbers and the plus sign on positive numbers. + * + * @draft ICU 60 + * @see NumberFormatter + */ + ALWAYS, + + /** + * Do not show the sign on positive or negative numbers. + * + * @draft ICU 60 + * @see NumberFormatter + */ + NEVER, + + /** + * Use the locale-dependent accounting format on negative numbers, and do not show the sign on positive numbers. + * + *

+ * The accounting format is defined in CLDR and varies by locale; in many Western locales, the format is a pair + * of parentheses around the number. + * + *

+ * Note: Since CLDR defines the accounting format in the monetary context only, this option falls back to the + * AUTO sign display strategy when formatting without a currency unit. This limitation may be lifted in the + * future. + * + * @draft ICU 60 + * @see NumberFormatter + */ + ACCOUNTING, + + /** + * Use the locale-dependent accounting format on negative numbers, and show the plus sign on positive numbers. + * For more information on the accounting format, see the ACCOUNTING sign display strategy. + * + * @draft ICU 60 + * @see NumberFormatter + */ + ACCOUNTING_ALWAYS, + } + + /** + * An enum declaring how to render the decimal separator. Example outputs when formatting 1 and 1.1 in + * en-US: + * + *

+ *

    + *
  • AUTO: "1" and "1.1" + *
  • ALWAYS: "1." and "1.1" + *
+ */ + public static enum DecimalSeparatorDisplay { + /** + * Show the decimal separator when there are one or more digits to display after the separator, and do not show + * it otherwise. This is the default behavior. + * + * @draft ICU 60 + * @see NumberFormatter + */ + AUTO, + + /** + * Always show the decimal separator, even if there are no digits to display after the separator. + * + * @draft ICU 60 + * @see NumberFormatter + */ + ALWAYS, + } + + /** + * Use a default threshold of 3. This means that the third time .format() is called, the data structures get built + * using the "safe" code path. The first two calls to .format() will trigger the unsafe code path. + */ + static final long DEFAULT_THRESHOLD = 3; + + /** + * Call this method at the beginning of a NumberFormatter fluent chain in which the locale is not currently known at + * the call site. + * + * @return An {@link UnlocalizedNumberFormatter}, to be used for chaining. + * @draft ICU 60 + */ + public static UnlocalizedNumberFormatter with() { + return BASE; + } + + /** + * Call this method at the beginning of a NumberFormatter fluent chain in which the locale is known at the call + * site. + * + * @param locale + * The locale from which to load formats and symbols for number formatting. + * @return A {@link LocalizedNumberFormatter}, to be used for chaining. + * @draft ICU 60 + */ + public static LocalizedNumberFormatter withLocale(Locale locale) { + return BASE.locale(locale); + } + + /** + * Call this method at the beginning of a NumberFormatter fluent chain in which the locale is known at the call + * site. + * + * @param locale + * The locale from which to load formats and symbols for number formatting. + * @return A {@link LocalizedNumberFormatter}, to be used for chaining. + * @draft ICU 60 + */ + public static LocalizedNumberFormatter withLocale(ULocale locale) { + return BASE.locale(locale); + } + + /** + * @internal + * @deprecated ICU 60 This API is ICU internal only. + */ + @Deprecated + public static UnlocalizedNumberFormatter fromDecimalFormat(DecimalFormatProperties properties, + DecimalFormatSymbols symbols, DecimalFormatProperties exportedProperties) { + MacroProps macros = NumberPropertyMapper.oldToNew(properties, symbols, exportedProperties); + return NumberFormatter.with().macros(macros); + } +} diff --git a/icu4j/main/classes/core/src/newapi/NumberFormatterImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java similarity index 67% rename from icu4j/main/classes/core/src/newapi/NumberFormatterImpl.java rename to icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java index 0e7fb71e862..74b49426027 100644 --- a/icu4j/main/classes/core/src/newapi/NumberFormatterImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java @@ -1,31 +1,28 @@ // © 2017 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License -package newapi; +package com.ibm.icu.number; +import com.ibm.icu.impl.number.CompactData.CompactType; +import com.ibm.icu.impl.number.ConstantAffixModifier; import com.ibm.icu.impl.number.DecimalQuantity; +import com.ibm.icu.impl.number.LongNameHandler; +import com.ibm.icu.impl.number.MacroProps; +import com.ibm.icu.impl.number.MicroProps; +import com.ibm.icu.impl.number.MicroPropsGenerator; +import com.ibm.icu.impl.number.MutablePatternModifier; import com.ibm.icu.impl.number.NumberStringBuilder; +import com.ibm.icu.impl.number.Padder; import com.ibm.icu.impl.number.PatternStringParser; import com.ibm.icu.impl.number.PatternStringParser.ParsedPatternInfo; -import com.ibm.icu.impl.number.modifiers.ConstantAffixModifier; +import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay; +import com.ibm.icu.number.NumberFormatter.SignDisplay; +import com.ibm.icu.number.NumberFormatter.UnitWidth; import com.ibm.icu.text.DecimalFormatSymbols; import com.ibm.icu.text.NumberFormat; import com.ibm.icu.text.NumberingSystem; import com.ibm.icu.text.PluralRules; import com.ibm.icu.util.Currency; -import com.ibm.icu.util.Currency.CurrencyUsage; -import com.ibm.icu.util.NoUnit; -import com.ibm.icu.util.ULocale; - -import newapi.CompactNotation.CompactType; -import newapi.NumberFormatter.DecimalMarkDisplay; -import newapi.NumberFormatter.SignDisplay; -import newapi.NumberFormatter.UnitWidth; -import newapi.impl.LongNameHandler; -import newapi.impl.MacroProps; -import newapi.impl.MicroProps; -import newapi.impl.MicroPropsGenerator; -import newapi.impl.MutablePatternModifier; -import newapi.impl.Padder; +import com.ibm.icu.util.MeasureUnit; /** * This is the "brain" of the number formatting pipeline. It ties all the pieces together, taking in a MacroProps and a @@ -67,6 +64,25 @@ class NumberFormatterImpl { ////////// + private static boolean unitIsCurrency(MeasureUnit unit) { + // TODO: Check using "instanceof" operator instead? + return unit != null && "currency".equals(unit.getType()); + } + + private static boolean unitIsNoUnit(MeasureUnit unit) { + // NOTE: In ICU4C, units cannot be null, and the default unit is a NoUnit. + // In ICU4J, return TRUE for a null unit from this method. + return unit == null || "none".equals(unit.getType()); + } + + private static boolean unitIsPercent(MeasureUnit unit) { + return unit != null && "percent".equals(unit.getSubtype()); + } + + private static boolean unitIsPermille(MeasureUnit unit) { + return unit != null && "permille".equals(unit.getSubtype()); + } + /** * Synthesizes the MacroProps into a MicroPropsGenerator. All information, including the locale, is encoded into the * MicroPropsGenerator, except for the quantity itself, which is left abstract and must be provided to the returned @@ -81,119 +97,122 @@ class NumberFormatterImpl { * object is more expensive. */ private static MicroPropsGenerator macrosToMicroGenerator(MacroProps macros, boolean safe) { - - String innerPattern = null; - LongNameHandler longNames = null; - Rounder defaultRounder = Rounder.unlimited(); - Currency currency = DEFAULT_CURRENCY; - UnitWidth unitWidth = (macros.unitWidth == null) ? UnitWidth.SHORT : macros.unitWidth; - boolean perMille = false; - PluralRules rules = macros.rules; - - // FIXME - String nsName = NumberingSystem.getInstance(macros.loc).getName(); - MicroProps micros = new MicroProps(safe); MicroPropsGenerator chain = micros; - // Copy over the simple settings - micros.sign = macros.sign == null ? SignDisplay.AUTO : macros.sign; - micros.decimal = macros.decimal == null ? DecimalMarkDisplay.AUTO : macros.decimal; - micros.multiplier = 0; - micros.integerWidth = macros.integerWidth == null ? IntegerWidth.zeroFillTo(1) : macros.integerWidth; - - if (macros.unit == null || macros.unit == NoUnit.BASE) { - // No units; default format - innerPattern = NumberFormat.getPatternForStyle(macros.loc, NumberFormat.NUMBERSTYLE); - } else if (macros.unit == NoUnit.PERCENT) { - // Percent - innerPattern = NumberFormat.getPatternForStyle(macros.loc, NumberFormat.PERCENTSTYLE); - micros.multiplier += 2; - } else if (macros.unit == NoUnit.PERMILLE) { - // Permille - innerPattern = NumberFormat.getPatternForStyle(macros.loc, NumberFormat.PERCENTSTYLE); - micros.multiplier += 3; - perMille = true; - } else if (macros.unit instanceof Currency && macros.unitWidth != UnitWidth.FULL_NAME) { - // Narrow, short, or ISO currency. - // TODO: Although ACCOUNTING and ACCOUNTING_ALWAYS are only supported in currencies right now, - // the API contract allows us to add support to other units. - if (macros.sign == SignDisplay.ACCOUNTING || macros.sign == SignDisplay.ACCOUNTING_ALWAYS) { - innerPattern = NumberFormat.getPatternForStyle(macros.loc, NumberFormat.ACCOUNTINGCURRENCYSTYLE); - } else { - innerPattern = NumberFormat.getPatternForStyle(macros.loc, NumberFormat.CURRENCYSTYLE); - } - defaultRounder = Rounder.currency(CurrencyUsage.STANDARD); - currency = (Currency) macros.unit; - micros.useCurrency = true; - } else if (macros.unit instanceof Currency) { - // Currency long name - innerPattern = NumberFormat.getPatternForStyle(macros.loc, NumberFormat.NUMBERSTYLE); - longNames = LongNameHandler.forCurrencyLongNames(macros.loc, (Currency) macros.unit); - defaultRounder = Rounder.currency(CurrencyUsage.STANDARD); - currency = (Currency) macros.unit; - micros.useCurrency = true; - } else { - // MeasureUnit - innerPattern = NumberFormat.getPatternForStyle(macros.loc, NumberFormat.NUMBERSTYLE); - longNames = LongNameHandler.forMeasureUnit(macros.loc, macros.unit, unitWidth); - } - - // Parse the pattern, which is used for grouping and affixes only. - ParsedPatternInfo patternInfo = PatternStringParser.parseToPatternInfo(innerPattern); - - // Symbols - // NOTE: C++ has a special class, SymbolsWrapper, in MacroProps. Java has all the resolution logic here - // directly. - if (macros.symbols == null) { - micros.symbols = DecimalFormatSymbols.getInstance(macros.loc); - } else if (macros.symbols instanceof DecimalFormatSymbols) { - micros.symbols = (DecimalFormatSymbols) macros.symbols; - } else if (macros.symbols instanceof NumberingSystem) { - // TODO: Do this more efficiently. Will require modifying DecimalFormatSymbols. - NumberingSystem ns = (NumberingSystem) macros.symbols; - ULocale temp = macros.loc.setKeywordValue("numbers", ns.getName()); - micros.symbols = DecimalFormatSymbols.getInstance(temp); - } else { - throw new AssertionError(); - } - // TODO: Normalize the currency (accept symbols from DecimalFormatSymbols)? // currency = CustomSymbolCurrency.resolve(currency, input.loc, micros.symbols); + // Pre-compute a few values for efficiency. + boolean isCurrency = unitIsCurrency(macros.unit); + boolean isNoUnit = unitIsNoUnit(macros.unit); + boolean isPercent = isNoUnit && unitIsPercent(macros.unit); + boolean isPermille = isNoUnit && unitIsPermille(macros.unit); + boolean isCldrUnit = !isCurrency && !isNoUnit; + boolean isAccounting = macros.sign == SignDisplay.ACCOUNTING || macros.sign == SignDisplay.ACCOUNTING_ALWAYS; + Currency currency = isCurrency ? (Currency) macros.unit : DEFAULT_CURRENCY; + UnitWidth unitWidth = UnitWidth.SHORT; + if (macros.unitWidth != null) { + unitWidth = macros.unitWidth; + } + PluralRules rules = macros.rules; + + // Select the numbering system. + NumberingSystem ns; + if (macros.symbols instanceof NumberingSystem) { + ns = (NumberingSystem) macros.symbols; + } else { + // TODO: Is there a way to avoid creating the NumberingSystem object? + ns = NumberingSystem.getInstance(macros.loc); + } + String nsName = ns.getName(); + + // Load and parse the pattern string. It is used for grouping sizes and affixes only. + int patternStyle; + if (isPercent || isPermille) { + patternStyle = NumberFormat.PERCENTSTYLE; + } else if (!isCurrency || unitWidth == UnitWidth.FULL_NAME) { + patternStyle = NumberFormat.NUMBERSTYLE; + } else if (isAccounting) { + // NOTE: Although ACCOUNTING and ACCOUNTING_ALWAYS are only supported in currencies right now, + // the API contract allows us to add support to other units in the future. + patternStyle = NumberFormat.ACCOUNTINGCURRENCYSTYLE; + } else { + patternStyle = NumberFormat.CURRENCYSTYLE; + } + String pattern = NumberFormat.getPatternForStyleAndNumberingSystem(macros.loc, nsName, patternStyle); + ParsedPatternInfo patternInfo = PatternStringParser.parseToPatternInfo(pattern); + + ///////////////////////////////////////////////////////////////////////////////////// + /// START POPULATING THE DEFAULT MICROPROPS AND BUILDING THE MICROPROPS GENERATOR /// + ///////////////////////////////////////////////////////////////////////////////////// + + // Symbols + if (macros.symbols instanceof DecimalFormatSymbols) { + micros.symbols = (DecimalFormatSymbols) macros.symbols; + } else { + micros.symbols = DecimalFormatSymbols.forNumberingSystem(macros.loc, ns); + } + // Multiplier (compatibility mode value). - // An int magnitude multiplier is used when not in compatibility mode to - // reduce object creations. if (macros.multiplier != null) { chain = macros.multiplier.copyAndChain(chain); } // Rounding strategy if (macros.rounder != null) { - micros.rounding = Rounder.normalizeType(macros.rounder, currency); + micros.rounding = macros.rounder; } else if (macros.notation instanceof CompactNotation) { micros.rounding = Rounder.COMPACT_STRATEGY; + } else if (isCurrency) { + micros.rounding = Rounder.MONETARY_STANDARD; } else { - micros.rounding = Rounder.normalizeType(defaultRounder, currency); + micros.rounding = Rounder.MAX_FRAC_6; } + micros.rounding = micros.rounding.withLocaleData(currency); // Grouping strategy if (macros.grouper != null) { - micros.grouping = Grouper.normalizeType(macros.grouper, patternInfo); + micros.grouping = macros.grouper; } else if (macros.notation instanceof CompactNotation) { // Compact notation uses minGrouping by default since ICU 59 - micros.grouping = Grouper.normalizeType(Grouper.min2(), patternInfo); + micros.grouping = Grouper.minTwoDigits(); } else { - micros.grouping = Grouper.normalizeType(Grouper.defaults(), patternInfo); + micros.grouping = Grouper.defaults(); } + micros.grouping = micros.grouping.withLocaleData(patternInfo); // Padding strategy if (macros.padder != null) { micros.padding = macros.padder; } else { - micros.padding = Padder.none(); + micros.padding = Padder.NONE; } + // Integer width + if (macros.integerWidth != null) { + micros.integerWidth = macros.integerWidth; + } else { + micros.integerWidth = IntegerWidth.DEFAULT; + } + + // Sign display + if (macros.sign != null) { + micros.sign = macros.sign; + } else { + micros.sign = SignDisplay.AUTO; + } + + // Decimal mark display + if (macros.decimal != null) { + micros.decimal = macros.decimal; + } else { + micros.decimal = DecimalSeparatorDisplay.AUTO; + } + + // Use monetary separator symbols + micros.useCurrency = isCurrency; + // Inner modifier (scientific notation) if (macros.notation instanceof ScientificNotation) { chain = ((ScientificNotation) macros.notation).withLocaleData(micros.symbols, safe, chain); @@ -206,7 +225,7 @@ class NumberFormatterImpl { // The default middle modifier is weak (thus the false argument). MutablePatternModifier patternMod = new MutablePatternModifier(false); patternMod.setPatternInfo((macros.affixProvider != null) ? macros.affixProvider : patternInfo); - patternMod.setPatternAttributes(micros.sign, perMille); + patternMod.setPatternAttributes(micros.sign, isPermille); if (patternMod.needsPlurals()) { if (rules == null) { // Lazily create PluralRules @@ -223,12 +242,18 @@ class NumberFormatterImpl { } // Outer modifier (CLDR units and currency long names) - if (longNames != null) { + if (isCldrUnit) { if (rules == null) { // Lazily create PluralRules rules = PluralRules.forLocale(macros.loc); } - chain = longNames.withLocaleData(rules, chain); + chain = LongNameHandler.forMeasureUnit(macros.loc, macros.unit, unitWidth, rules, chain); + } else if (isCurrency && unitWidth == UnitWidth.FULL_NAME) { + if (rules == null) { + // Lazily create PluralRules + rules = PluralRules.forLocale(macros.loc); + } + chain = LongNameHandler.forCurrencyLongNames(macros.loc, currency, rules, chain); } else { // No outer modifier required micros.modOuter = ConstantAffixModifier.EMPTY; @@ -265,7 +290,6 @@ class NumberFormatterImpl { * The output string. Will be mutated. */ private static void microsToString(MicroProps micros, DecimalQuantity quantity, NumberStringBuilder string) { - quantity.adjustMagnitude(micros.multiplier); micros.rounding.apply(quantity); if (micros.integerWidth.maxInt == -1) { quantity.setIntegerLength(micros.integerWidth.minInt, Integer.MAX_VALUE); @@ -298,7 +322,7 @@ class NumberFormatterImpl { length += writeIntegerDigits(micros, quantity, string); // Add the decimal point - if (quantity.getLowerDisplayMagnitude() < 0 || micros.decimal == DecimalMarkDisplay.ALWAYS) { + if (quantity.getLowerDisplayMagnitude() < 0 || micros.decimal == DecimalSeparatorDisplay.ALWAYS) { length += string.insert(length, micros.useCurrency ? micros.symbols.getMonetaryDecimalSeparatorString() : micros.symbols.getDecimalSeparatorString(), NumberFormat.Field.DECIMAL_SEPARATOR); } diff --git a/icu4j/main/classes/core/src/newapi/NumberFormatterSettings.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java similarity index 97% rename from icu4j/main/classes/core/src/newapi/NumberFormatterSettings.java rename to icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java index 694448b345c..1646204c895 100644 --- a/icu4j/main/classes/core/src/newapi/NumberFormatterSettings.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java @@ -1,7 +1,12 @@ // © 2017 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License -package newapi; +package com.ibm.icu.number; +import com.ibm.icu.impl.number.MacroProps; +import com.ibm.icu.impl.number.Padder; +import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay; +import com.ibm.icu.number.NumberFormatter.SignDisplay; +import com.ibm.icu.number.NumberFormatter.UnitWidth; import com.ibm.icu.text.DecimalFormatSymbols; import com.ibm.icu.text.MeasureFormat.FormatWidth; import com.ibm.icu.text.NumberingSystem; @@ -11,12 +16,6 @@ import com.ibm.icu.util.MeasureUnit; import com.ibm.icu.util.NoUnit; import com.ibm.icu.util.ULocale; -import newapi.NumberFormatter.DecimalMarkDisplay; -import newapi.NumberFormatter.SignDisplay; -import newapi.NumberFormatter.UnitWidth; -import newapi.impl.MacroProps; -import newapi.impl.Padder; - /** * An abstract base class for specifying settings related to number formatting. This class is implemented by * {@link UnlocalizedNumberFormatter} and {@link LocalizedNumberFormatter}. @@ -342,7 +341,7 @@ public abstract class NumberFormatterSettings * This class, as well as NumberFormatterImpl, could go into the impl package, but they depend on too many @@ -198,8 +197,8 @@ final class NumberPropertyMapper { // DECIMAL MARK ALWAYS SHOWN // /////////////////////////////// - macros.decimal = properties.getDecimalSeparatorAlwaysShown() ? DecimalMarkDisplay.ALWAYS - : DecimalMarkDisplay.AUTO; + macros.decimal = properties.getDecimalSeparatorAlwaysShown() ? DecimalSeparatorDisplay.ALWAYS + : DecimalSeparatorDisplay.AUTO; /////////////////////// // SIGN ALWAYS SHOWN // diff --git a/icu4j/main/classes/core/src/newapi/Rounder.java b/icu4j/main/classes/core/src/com/ibm/icu/number/Rounder.java similarity index 53% rename from icu4j/main/classes/core/src/newapi/Rounder.java rename to icu4j/main/classes/core/src/com/ibm/icu/number/Rounder.java index a21776fbd91..0c52e2f12ae 100644 --- a/icu4j/main/classes/core/src/newapi/Rounder.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/Rounder.java @@ -1,39 +1,50 @@ // © 2017 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License -package newapi; +package com.ibm.icu.number; import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; import com.ibm.icu.impl.number.DecimalQuantity; +import com.ibm.icu.impl.number.MultiplierProducer; import com.ibm.icu.impl.number.RoundingUtils; import com.ibm.icu.util.Currency; import com.ibm.icu.util.Currency.CurrencyUsage; -import newapi.impl.MultiplierProducer; - +/** + * A class that defines the rounding strategy to be used when formatting numbers in NumberFormatter. + * + *

+ * To create a Rounder, use one of the factory methods. + * + * @draft ICU 60 + * @see NumberFormatter + */ public abstract class Rounder implements Cloneable { - // FIXME - /** @internal */ - public static final int MAX_VALUE = 100; - /* package-private final */ MathContext mathContext; /* package-private */ Rounder() { - mathContext = RoundingUtils.mathContextUnlimited(RoundingMode.HALF_EVEN); + mathContext = RoundingUtils.mathContextUnlimited(RoundingUtils.DEFAULT_ROUNDING_MODE); } /** * Show all available digits to full precision. * *

- * NOTE: If you are formatting doubles and you know that the number of fraction places or - * significant digits is bounded, consider using {@link #maxFraction} or {@link #maxDigits} instead to maximize - * performance. + * NOTE: When formatting a double, this method, along with {@link #minFraction} and + * {@link #minDigits}, will trigger complex algorithm similar to Dragon4 to determine the low-order digits + * and the number of digits to display based on the value of the double. If the number of fraction places or + * significant digits can be bounded, consider using {@link #maxFraction} or {@link #maxDigits} instead to maximize + * performance. For more information, read the following blog post. * - * @return A rounding strategy for {@link NumberFormatterSettings#rounding}. + *

+ * http://www.serpentine.com/blog/2011/06/29/here-be-dragons-advances-in-problems-you-didnt-even-know-you-had/ + * + * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. + * @draft ICU 60 + * @see NumberFormatter */ public static Rounder unlimited() { return constructInfinite(); @@ -42,18 +53,20 @@ public abstract class Rounder implements Cloneable { /** * Show numbers rounded if necessary to the nearest integer. * - * @return A rounding strategy for {@link NumberFormatterSettings#rounding}. + * @return A FractionRounder for chaining or passing to the NumberFormatter rounding() setter. + * @draft ICU 60 + * @see NumberFormatter */ public static FractionRounder integer() { return constructFraction(0, 0); } /** - * Show numbers rounded if necessary to a certain number of fraction places (digits after the decimal mark). - * Additionally, pad with zeros to ensure that this number digits are always shown. + * Show numbers rounded if necessary to a certain number of fraction places (numerals after the decimal separator). + * Additionally, pad with zeros to ensure that this number of places are always shown. * *

- * Example output with minMaxFractionDigits = 3: + * Example output with minMaxFractionPlaces = 3: * *

* 87,650.000
@@ -69,106 +82,196 @@ public abstract class Rounder implements Cloneable { *

* This method is equivalent to {@link #minMaxFraction} with both arguments equal. * - * @param minMaxFractionDigits - * The minimum and maximum number of digits to display after the decimal mark (rounding if too long or - * padding with zeros if too short). - * @return A rounding strategy for {@link NumberFormatterSettings#rounding}. + * @param minMaxFractionPlaces + * The minimum and maximum number of numerals to display after the decimal separator (rounding if too + * long or padding with zeros if too short). + * @return A FractionRounder for chaining or passing to the NumberFormatter rounding() setter. + * @draft ICU 60 + * @see NumberFormatter */ - public static FractionRounder fixedFraction(int minMaxFractionDigits) { - if (minMaxFractionDigits >= 0 && minMaxFractionDigits <= MAX_VALUE) { - return constructFraction(minMaxFractionDigits, minMaxFractionDigits); + public static FractionRounder fixedFraction(int minMaxFractionPlaces) { + if (minMaxFractionPlaces >= 0 && minMaxFractionPlaces <= RoundingUtils.MAX_INT_FRAC_SIG) { + return constructFraction(minMaxFractionPlaces, minMaxFractionPlaces); } else { - throw new IllegalArgumentException("Fraction length must be between 0 and " + MAX_VALUE); + throw new IllegalArgumentException( + "Fraction length must be between 0 and " + RoundingUtils.MAX_INT_FRAC_SIG); } } /** - * Always show a certain number of digits after the decimal mark, padding with zeros if necessary. Do not perform - * rounding (display numbers to their full precision). + * Always show at least a certain number of fraction places after the decimal separator, padding with zeros if + * necessary. Do not perform rounding (display numbers to their full precision). * *

- * NOTE: If you are formatting doubles and you know that the number of fraction places is - * bounded, consider using {@link #fixedFraction} or {@link #minMaxFraction} instead to maximize performance. + * NOTE: If you are formatting doubles, see the performance note in {@link #unlimited}. * - * @param minFractionDigits - * The minimum number of digits to display after the decimal mark (padding with zeros if necessary). - * @return A rounding strategy for {@link NumberFormatterSettings#rounding}. + * @param minFractionPlaces + * The minimum number of numerals to display after the decimal separator (padding with zeros if + * necessary). + * @return A FractionRounder for chaining or passing to the NumberFormatter rounding() setter. + * @draft ICU 60 + * @see NumberFormatter */ - public static FractionRounder minFraction(int minFractionDigits) { - if (minFractionDigits >= 0 && minFractionDigits < MAX_VALUE) { - return constructFraction(minFractionDigits, -1); + public static FractionRounder minFraction(int minFractionPlaces) { + if (minFractionPlaces >= 0 && minFractionPlaces < RoundingUtils.MAX_INT_FRAC_SIG) { + return constructFraction(minFractionPlaces, -1); } else { - throw new IllegalArgumentException("Fraction length must be between 0 and " + MAX_VALUE); + throw new IllegalArgumentException( + "Fraction length must be between 0 and " + RoundingUtils.MAX_INT_FRAC_SIG); } } /** - * Show numbers rounded if necessary to a certain number of fraction places (digits after the decimal mark). Unlike - * the other fraction rounding strategies, this strategy does not pad zeros to the end of the number. + * Show numbers rounded if necessary to a certain number of fraction places (numerals after the decimal separator). + * Unlike the other fraction rounding strategies, this strategy does not pad zeros to the end of the + * number. * - * @param maxFractionDigits - * The maximum number of digits to display after the decimal mark (rounding if necessary). - * @return A rounding strategy for {@link NumberFormatterSettings#rounding}. + * @param maxFractionPlaces + * The maximum number of numerals to display after the decimal mark (rounding if necessary). + * @return A FractionRounder for chaining or passing to the NumberFormatter rounding() setter. + * @draft ICU 60 + * @see NumberFormatter */ - public static FractionRounder maxFraction(int maxFractionDigits) { - if (maxFractionDigits >= 0 && maxFractionDigits < MAX_VALUE) { - return constructFraction(0, maxFractionDigits); + public static FractionRounder maxFraction(int maxFractionPlaces) { + if (maxFractionPlaces >= 0 && maxFractionPlaces < RoundingUtils.MAX_INT_FRAC_SIG) { + return constructFraction(0, maxFractionPlaces); } else { - throw new IllegalArgumentException("Fraction length must be between 0 and " + MAX_VALUE); + throw new IllegalArgumentException( + "Fraction length must be between 0 and " + RoundingUtils.MAX_INT_FRAC_SIG); } } /** - * Show numbers rounded if necessary to a certain number of fraction places (digits after the decimal mark); in - * addition, always show a certain number of digits after the decimal mark, padding with zeros if necessary. + * Show numbers rounded if necessary to a certain number of fraction places (numerals after the decimal separator); + * in addition, always show at least a certain number of places after the decimal separator, padding with zeros if + * necessary. * - * @param minFractionDigits - * The minimum number of digits to display after the decimal mark (padding with zeros if necessary). - * @param maxFractionDigits - * The maximum number of digits to display after the decimal mark (rounding if necessary). - * @return A rounding strategy for {@link NumberFormatterSettings#rounding}. + * @param minFractionPlaces + * The minimum number of numerals to display after the decimal separator (padding with zeros if + * necessary). + * @param maxFractionPlaces + * The maximum number of numerals to display after the decimal separator (rounding if necessary). + * @return A FractionRounder for chaining or passing to the NumberFormatter rounding() setter. + * @draft ICU 60 + * @see NumberFormatter */ - public static FractionRounder minMaxFraction(int minFractionDigits, int maxFractionDigits) { - if (minFractionDigits >= 0 && maxFractionDigits <= MAX_VALUE && minFractionDigits <= maxFractionDigits) { - return constructFraction(minFractionDigits, maxFractionDigits); + public static FractionRounder minMaxFraction(int minFractionPlaces, int maxFractionPlaces) { + if (minFractionPlaces >= 0 && maxFractionPlaces <= RoundingUtils.MAX_INT_FRAC_SIG + && minFractionPlaces <= maxFractionPlaces) { + return constructFraction(minFractionPlaces, maxFractionPlaces); } else { - throw new IllegalArgumentException("Fraction length must be between 0 and " + MAX_VALUE); + throw new IllegalArgumentException( + "Fraction length must be between 0 and " + RoundingUtils.MAX_INT_FRAC_SIG); } } + /** + * Show numbers rounded if necessary to a certain number of significant digits or significant figures. Additionally, + * pad with zeros to ensure that this number of significant digits/figures are always shown. + * + *

+ * This method is equivalent to {@link #minMaxDigits} with both arguments equal. + * + * @param minMaxSignificantDigits + * The minimum and maximum number of significant digits to display (rounding if too long or padding with + * zeros if too short). + * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. + * @draft ICU 60 + * @see NumberFormatter + */ public static Rounder fixedDigits(int minMaxSignificantDigits) { - if (minMaxSignificantDigits > 0 && minMaxSignificantDigits <= MAX_VALUE) { + if (minMaxSignificantDigits > 0 && minMaxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) { return constructSignificant(minMaxSignificantDigits, minMaxSignificantDigits); } else { - throw new IllegalArgumentException("Significant digits must be between 0 and " + MAX_VALUE); + throw new IllegalArgumentException( + "Significant digits must be between 0 and " + RoundingUtils.MAX_INT_FRAC_SIG); } } + /** + * Always show at least a certain number of significant digits/figures, padding with zeros if necessary. Do not + * perform rounding (display numbers to their full precision). + * + *

+ * NOTE: If you are formatting doubles, see the performance note in {@link #unlimited}. + * + * @param minSignificantDigits + * The minimum number of significant digits to display (padding with zeros if too short). + * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. + * @draft ICU 60 + * @see NumberFormatter + */ public static Rounder minDigits(int minSignificantDigits) { - if (minSignificantDigits > 0 && minSignificantDigits <= MAX_VALUE) { + if (minSignificantDigits > 0 && minSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) { return constructSignificant(minSignificantDigits, -1); } else { - throw new IllegalArgumentException("Significant digits must be between 0 and " + MAX_VALUE); + throw new IllegalArgumentException( + "Significant digits must be between 0 and " + RoundingUtils.MAX_INT_FRAC_SIG); } } + /** + * Show numbers rounded if necessary to a certain number of significant digits/figures. + * + * @param maxSignificantDigits + * The maximum number of significant digits to display (rounding if too long). + * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. + * @draft ICU 60 + * @see NumberFormatter + */ public static Rounder maxDigits(int maxSignificantDigits) { - if (maxSignificantDigits > 0 && maxSignificantDigits <= MAX_VALUE) { + if (maxSignificantDigits > 0 && maxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) { return constructSignificant(0, maxSignificantDigits); } else { - throw new IllegalArgumentException("Significant digits must be between 0 and " + MAX_VALUE); + throw new IllegalArgumentException( + "Significant digits must be between 0 and " + RoundingUtils.MAX_INT_FRAC_SIG); } } + /** + * Show numbers rounded if necessary to a certain number of significant digits/figures; in addition, always show at + * least a certain number of significant digits, padding with zeros if necessary. + * + * @param minSignificantDigits + * The minimum number of significant digits to display (padding with zeros if necessary). + * @param maxSignificantDigits + * The maximum number of significant digits to display (rounding if necessary). + * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. + * @draft ICU 60 + * @see NumberFormatter + */ public static Rounder minMaxDigits(int minSignificantDigits, int maxSignificantDigits) { - if (minSignificantDigits > 0 && maxSignificantDigits <= MAX_VALUE + if (minSignificantDigits > 0 && maxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG && minSignificantDigits <= maxSignificantDigits) { return constructSignificant(minSignificantDigits, maxSignificantDigits); } else { - throw new IllegalArgumentException("Significant digits must be between 0 and " + MAX_VALUE); + throw new IllegalArgumentException( + "Significant digits must be between 0 and " + RoundingUtils.MAX_INT_FRAC_SIG); } } + /** + * Show numbers rounded if necessary to the closest multiple of a certain rounding increment. For example, if the + * rounding increment is 0.5, then round 1.2 to 1 and round 1.3 to 1.5. + * + *

+ * In order to ensure that numbers are padded to the appropriate number of fraction places, set the scale on the + * rounding increment BigDecimal. For example, to round to the nearest 0.5 and always display 2 numerals after the + * decimal separator (to display 1.2 as "1.00" and 1.3 as "1.50"), you can run: + * + *

+     * Rounder.increment(new BigDecimal("0.50"))
+     * 
+ * + *

+ * For more information on the scale of Java BigDecimal, see {@link java.math.BigDecimal#scale()}. + * + * @param roundingIncrement + * The increment to which to round numbers. + * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. + * @draft ICU 60 + * @see NumberFormatter + */ public static Rounder increment(BigDecimal roundingIncrement) { if (roundingIncrement != null && roundingIncrement.compareTo(BigDecimal.ZERO) > 0) { return constructIncrement(roundingIncrement); @@ -177,6 +280,23 @@ public abstract class Rounder implements Cloneable { } } + /** + * Show numbers rounded and padded according to the rules for the currency unit. The most common rounding settings + * for currencies include Rounder.fixedFraction(2), Rounder.integer(), and + * Rounder.increment(0.05) for cash transactions ("nickel rounding"). + * + *

+ * The exact rounding details will be resolved at runtime based on the currency unit specified in the + * NumberFormatter chain. To round according to the rules for one currency while displaying the symbol for another + * currency, the withCurrency() method can be called on the return value of this method. + * + * @param currencyUsage + * Either STANDARD (for digital transactions) or CASH (for transactions where the rounding increment may + * be limited by the available denominations of cash or coins). + * @return A CurrencyRounder for chaining or passing to the NumberFormatter rounding() setter. + * @draft ICU 60 + * @see NumberFormatter + */ public static CurrencyRounder currency(CurrencyUsage currencyUsage) { if (currencyUsage != null) { return constructCurrency(currencyUsage); @@ -186,15 +306,14 @@ public abstract class Rounder implements Cloneable { } /** - * Sets the {@link java.math.RoundingMode} to use when picking the direction to round (up or down). - * - *

- * Common values include {@link RoundingMode#HALF_EVEN}, {@link RoundingMode#HALF_UP}, and - * {@link RoundingMode#CEILING}. The default is HALF_EVEN. + * Sets the {@link java.math.RoundingMode} to use when picking the direction to round (up or down). Common values + * include HALF_EVEN, HALF_UP, and FLOOR. The default is HALF_EVEN. * * @param roundingMode * The RoundingMode to use. - * @return An immutable object for chaining. + * @return A Rounder for chaining. + * @draft ICU 60 + * @see NumberFormatter */ public Rounder withMode(RoundingMode roundingMode) { return withMode(RoundingUtils.mathContextUnlimited(roundingMode)); @@ -216,6 +335,11 @@ public abstract class Rounder implements Cloneable { return other; } + /** + * @internal + * @deprecated This API is ICU internal only. + */ + @Deprecated @Override public Object clone() { try { @@ -237,23 +361,24 @@ public abstract class Rounder implements Cloneable { // PACKAGE-PRIVATE APIS // ////////////////////////// - private static final InfiniteRounderImpl NONE = new InfiniteRounderImpl(); + static final InfiniteRounderImpl NONE = new InfiniteRounderImpl(); - private static final FractionRounderImpl FIXED_FRAC_0 = new FractionRounderImpl(0, 0); - private static final FractionRounderImpl FIXED_FRAC_2 = new FractionRounderImpl(2, 2); + static final FractionRounderImpl FIXED_FRAC_0 = new FractionRounderImpl(0, 0); + static final FractionRounderImpl FIXED_FRAC_2 = new FractionRounderImpl(2, 2); + static final FractionRounderImpl MAX_FRAC_6 = new FractionRounderImpl(0, 6); - private static final SignificantRounderImpl FIXED_SIG_2 = new SignificantRounderImpl(2, 2); - private static final SignificantRounderImpl FIXED_SIG_3 = new SignificantRounderImpl(3, 3); - private static final SignificantRounderImpl RANGE_SIG_2_3 = new SignificantRounderImpl(2, 3); + static final SignificantRounderImpl FIXED_SIG_2 = new SignificantRounderImpl(2, 2); + static final SignificantRounderImpl FIXED_SIG_3 = new SignificantRounderImpl(3, 3); + static final SignificantRounderImpl RANGE_SIG_2_3 = new SignificantRounderImpl(2, 3); - /* package-private */ static final FracSigRounderImpl COMPACT_STRATEGY = new FracSigRounderImpl(0, 0, 2, -1); + static final FracSigRounderImpl COMPACT_STRATEGY = new FracSigRounderImpl(0, 0, 2, -1); - private static final IncrementRounderImpl NICKEL = new IncrementRounderImpl(BigDecimal.valueOf(0.5)); + static final IncrementRounderImpl NICKEL = new IncrementRounderImpl(BigDecimal.valueOf(0.05)); - private static final CurrencyRounderImpl MONETARY_STANDARD = new CurrencyRounderImpl(CurrencyUsage.STANDARD); - private static final CurrencyRounderImpl MONETARY_CASH = new CurrencyRounderImpl(CurrencyUsage.CASH); + static final CurrencyRounderImpl MONETARY_STANDARD = new CurrencyRounderImpl(CurrencyUsage.STANDARD); + static final CurrencyRounderImpl MONETARY_CASH = new CurrencyRounderImpl(CurrencyUsage.CASH); - private static final PassThroughRounderImpl PASS_THROUGH = new PassThroughRounderImpl(); + static final PassThroughRounderImpl PASS_THROUGH = new PassThroughRounderImpl(); static Rounder constructInfinite() { return NONE; @@ -264,6 +389,8 @@ public abstract class Rounder implements Cloneable { return FIXED_FRAC_0; } else if (minFrac == 2 && maxFrac == 2) { return FIXED_FRAC_2; + } else if (minFrac == 0 && maxFrac == 6) { + return MAX_FRAC_6; } else { return new FractionRounderImpl(minFrac, maxFrac); } @@ -293,7 +420,8 @@ public abstract class Rounder implements Cloneable { } static Rounder constructIncrement(BigDecimal increment) { - if (increment.compareTo(NICKEL.increment) == 0) { + // NOTE: .equals() is what we want, not .compareTo() + if (increment.equals(NICKEL.increment)) { return NICKEL; } else { return new IncrementRounderImpl(increment); @@ -331,17 +459,15 @@ public abstract class Rounder implements Cloneable { * Returns a valid working Rounder. If the Rounder is a CurrencyRounder, applies the given currency. Otherwise, * simply passes through the argument. * - * @param rounder - * The input object. * @param currency * A currency object to use in case the input object needs it. * @return A Rounder object ready for use. */ - static Rounder normalizeType(Rounder rounder, Currency currency) { - if (rounder instanceof CurrencyRounder) { - return ((CurrencyRounder) rounder).withCurrency(currency); + Rounder withLocaleData(Currency currency) { + if (this instanceof CurrencyRounder) { + return ((CurrencyRounder) this).withCurrency(currency); } else { - return rounder; + return this; } } diff --git a/icu4j/main/classes/core/src/newapi/ScientificNotation.java b/icu4j/main/classes/core/src/com/ibm/icu/number/ScientificNotation.java similarity index 80% rename from icu4j/main/classes/core/src/newapi/ScientificNotation.java rename to icu4j/main/classes/core/src/com/ibm/icu/number/ScientificNotation.java index e31ec8af73f..4fd1d3aadde 100644 --- a/icu4j/main/classes/core/src/newapi/ScientificNotation.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/ScientificNotation.java @@ -1,20 +1,28 @@ // © 2017 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License -package newapi; +package com.ibm.icu.number; import com.ibm.icu.impl.number.DecimalQuantity; +import com.ibm.icu.impl.number.MicroProps; +import com.ibm.icu.impl.number.MicroPropsGenerator; import com.ibm.icu.impl.number.Modifier; +import com.ibm.icu.impl.number.MultiplierProducer; import com.ibm.icu.impl.number.NumberStringBuilder; +import com.ibm.icu.impl.number.RoundingUtils; +import com.ibm.icu.number.NumberFormatter.SignDisplay; +import com.ibm.icu.number.Rounder.SignificantRounderImpl; import com.ibm.icu.text.DecimalFormatSymbols; import com.ibm.icu.text.NumberFormat; -import newapi.NumberFormatter.SignDisplay; -import newapi.Rounder.SignificantRounderImpl; -import newapi.impl.MicroProps; -import newapi.impl.MicroPropsGenerator; -import newapi.impl.MultiplierProducer; - -@SuppressWarnings("unused") +/** + * A class that defines the scientific notation style to be used when formatting numbers in NumberFormatter. + * + *

+ * To create a ScientificNotation, use one of the factory methods in {@link Notation}. + * + * @draft ICU 60 + * @see NumberFormatter + */ public class ScientificNotation extends Notation implements Cloneable { int engineeringInterval; @@ -30,22 +38,56 @@ public class ScientificNotation extends Notation implements Cloneable { this.exponentSignDisplay = exponentSignDisplay; } + /** + * Sets the minimum number of digits to show in the exponent of scientific notation, padding with zeros if + * necessary. Useful for fixed-width display. + * + *

+ * For example, with minExponentDigits=2, the number 123 will be printed as "1.23E02" in en-US instead of + * the default "1.23E2". + * + * @param minExponentDigits + * The minimum number of digits to show in the exponent. + * @return A ScientificNotation, for chaining. + * @draft ICU 60 + * @see NumberFormatter + */ public ScientificNotation withMinExponentDigits(int minExponentDigits) { - if (minExponentDigits >= 0 && minExponentDigits < Rounder.MAX_VALUE) { + if (minExponentDigits >= 0 && minExponentDigits < RoundingUtils.MAX_INT_FRAC_SIG) { ScientificNotation other = (ScientificNotation) this.clone(); other.minExponentDigits = minExponentDigits; return other; } else { - throw new IllegalArgumentException("Integer digits must be between 0 and " + Rounder.MAX_VALUE); + throw new IllegalArgumentException( + "Integer digits must be between 0 and " + RoundingUtils.MAX_INT_FRAC_SIG); } } + /** + * Sets whether to show the sign on positive and negative exponents in scientific notation. The default is AUTO, + * showing the minus sign but not the plus sign. + * + *

+ * For example, with exponentSignDisplay=ALWAYS, the number 123 will be printed as "1.23E+2" in en-US + * instead of the default "1.23E2". + * + * @param exponentSignDisplay + * The strategy for displaying the sign in the exponent. + * @return A ScientificNotation, for chaining. + * @draft ICU 60 + * @see NumberFormatter + */ public ScientificNotation withExponentSignDisplay(SignDisplay exponentSignDisplay) { ScientificNotation other = (ScientificNotation) this.clone(); other.exponentSignDisplay = exponentSignDisplay; return other; } + /** + * @internal + * @deprecated This API is ICU internal only. + */ + @Deprecated @Override public Object clone() { try { @@ -183,7 +225,7 @@ public class ScientificNotation extends Notation implements Cloneable { i += output.insert(i, symbols.getExponentSeparator(), NumberFormat.Field.EXPONENT_SYMBOL); if (exponent < 0 && notation.exponentSignDisplay != SignDisplay.NEVER) { i += output.insert(i, symbols.getMinusSignString(), NumberFormat.Field.EXPONENT_SIGN); - } else if (notation.exponentSignDisplay == SignDisplay.ALWAYS) { + } else if (exponent >= 0 && notation.exponentSignDisplay == SignDisplay.ALWAYS) { i += output.insert(i, symbols.getPlusSignString(), NumberFormat.Field.EXPONENT_SIGN); } // Append the exponent digits (using a simple inline algorithm) diff --git a/icu4j/main/classes/core/src/newapi/SimpleNotation.java b/icu4j/main/classes/core/src/com/ibm/icu/number/SimpleNotation.java similarity index 88% rename from icu4j/main/classes/core/src/newapi/SimpleNotation.java rename to icu4j/main/classes/core/src/com/ibm/icu/number/SimpleNotation.java index 8c72cb427f1..08db0ca0bb8 100644 --- a/icu4j/main/classes/core/src/newapi/SimpleNotation.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/SimpleNotation.java @@ -1,6 +1,6 @@ // © 2017 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License -package newapi; +package com.ibm.icu.number; public class SimpleNotation extends Notation { /* package-private */ SimpleNotation() { diff --git a/icu4j/main/classes/core/src/newapi/UnlocalizedNumberFormatter.java b/icu4j/main/classes/core/src/com/ibm/icu/number/UnlocalizedNumberFormatter.java similarity index 97% rename from icu4j/main/classes/core/src/newapi/UnlocalizedNumberFormatter.java rename to icu4j/main/classes/core/src/com/ibm/icu/number/UnlocalizedNumberFormatter.java index fcfa7f99930..0afab0faa5e 100644 --- a/icu4j/main/classes/core/src/newapi/UnlocalizedNumberFormatter.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/UnlocalizedNumberFormatter.java @@ -1,6 +1,6 @@ // © 2017 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License -package newapi; +package com.ibm.icu.number; import java.util.Locale; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java index 63e20a93f02..1ee62d28568 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java @@ -15,12 +15,16 @@ import java.text.ParsePosition; import com.ibm.icu.impl.number.AffixUtils; import com.ibm.icu.impl.number.DecimalFormatProperties; +import com.ibm.icu.impl.number.Padder.PadPosition; import com.ibm.icu.impl.number.Parse; import com.ibm.icu.impl.number.PatternStringParser; import com.ibm.icu.impl.number.PatternStringUtils; import com.ibm.icu.lang.UCharacter; import com.ibm.icu.math.BigDecimal; import com.ibm.icu.math.MathContext; +import com.ibm.icu.number.FormattedNumber; +import com.ibm.icu.number.LocalizedNumberFormatter; +import com.ibm.icu.number.NumberFormatter; import com.ibm.icu.text.PluralRules.IFixedDecimal; import com.ibm.icu.util.Currency; import com.ibm.icu.util.Currency.CurrencyUsage; @@ -28,11 +32,6 @@ import com.ibm.icu.util.CurrencyAmount; import com.ibm.icu.util.ULocale; import com.ibm.icu.util.ULocale.Category; -import newapi.FormattedNumber; -import newapi.LocalizedNumberFormatter; -import newapi.NumberFormatter; -import newapi.impl.Padder.PadPosition; - /** * {@icuenhanced java.text.DecimalFormat}.{@icu _usage_} DecimalFormat is the primary * concrete subclass of {@link NumberFormat}. It has a variety of features designed to make it diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/NumberFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/NumberFormat.java index 35216fbce77..dab77744d63 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/NumberFormat.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/NumberFormat.java @@ -1490,6 +1490,22 @@ public abstract class NumberFormat extends UFormat { */ @Deprecated public static String getPatternForStyle(ULocale forLocale, int choice) { + NumberingSystem ns = NumberingSystem.getInstance(forLocale); + String nsName = ns.getName(); + return getPatternForStyleAndNumberingSystem(forLocale, nsName, choice); + } + + /** + * Returns the pattern for the provided locale, numbering system, and choice. + * @param forLocale the locale of the data. + * @param nsName The name of the numbering system, like "latn". + * @param choice the pattern format. + * @return the pattern + * @internal + * @deprecated This API is ICU internal only. + */ + @Deprecated + public static String getPatternForStyleAndNumberingSystem(ULocale forLocale, String nsName, int choice) { /* for ISOCURRENCYSTYLE and PLURALCURRENCYSTYLE, * the pattern is the same as the pattern of CURRENCYSTYLE * but by replacing the single currency sign with @@ -1529,10 +1545,9 @@ public abstract class NumberFormat extends UFormat { ICUResourceBundle rb = (ICUResourceBundle)UResourceBundle. getBundleInstance(ICUData.ICU_BASE_NAME, forLocale); - NumberingSystem ns = NumberingSystem.getInstance(forLocale); String result = rb.findStringWithFallback( - "NumberElements/" + ns.getName() + "/patterns/" + patternKey); + "NumberElements/" + nsName + "/patterns/" + patternKey); if (result == null) { result = rb.getStringWithFallback("NumberElements/latn/patterns/" + patternKey); } diff --git a/icu4j/main/classes/core/src/newapi/Notation.java b/icu4j/main/classes/core/src/newapi/Notation.java deleted file mode 100644 index 0487baad666..00000000000 --- a/icu4j/main/classes/core/src/newapi/Notation.java +++ /dev/null @@ -1,40 +0,0 @@ -// © 2017 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html#License -package newapi; - -import com.ibm.icu.text.CompactDecimalFormat.CompactStyle; - -import newapi.NumberFormatter.SignDisplay; - -public class Notation { - - // FIXME: Support engineering intervals other than 3? - private static final ScientificNotation SCIENTIFIC = new ScientificNotation(1, false, 1, SignDisplay.AUTO); - private static final ScientificNotation ENGINEERING = new ScientificNotation(3, false, 1, SignDisplay.AUTO); - private static final CompactNotation COMPACT_SHORT = new CompactNotation(CompactStyle.SHORT); - private static final CompactNotation COMPACT_LONG = new CompactNotation(CompactStyle.LONG); - private static final SimpleNotation SIMPLE = new SimpleNotation(); - - /* package-private */ Notation() { - } - - public static ScientificNotation scientific() { - return SCIENTIFIC; - } - - public static ScientificNotation engineering() { - return ENGINEERING; - } - - public static CompactNotation compactShort() { - return COMPACT_SHORT; - } - - public static CompactNotation compactLong() { - return COMPACT_LONG; - } - - public static SimpleNotation simple() { - return SIMPLE; - } -} \ No newline at end of file diff --git a/icu4j/main/classes/core/src/newapi/NumberFormatter.java b/icu4j/main/classes/core/src/newapi/NumberFormatter.java deleted file mode 100644 index dfa3f97cc49..00000000000 --- a/icu4j/main/classes/core/src/newapi/NumberFormatter.java +++ /dev/null @@ -1,62 +0,0 @@ -// © 2017 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html#License -package newapi; - -import java.util.Locale; - -import com.ibm.icu.impl.number.DecimalFormatProperties; -import com.ibm.icu.text.DecimalFormatSymbols; -import com.ibm.icu.util.ULocale; - -import newapi.impl.MacroProps; - -public final class NumberFormatter { - - private static final UnlocalizedNumberFormatter BASE = new UnlocalizedNumberFormatter(); - - public static enum UnitWidth { - NARROW, // ¤¤¤¤¤ or narrow measure unit - SHORT, // ¤ or short measure unit (DEFAULT) - ISO_CODE, // ¤¤; undefined for measure unit - FULL_NAME, // ¤¤¤ or wide unit - HIDDEN, // no unit is displayed, but other unit effects are obeyed (like currency rounding) - // TODO: For hidden, what to do if currency symbol appears in the middle, as in Portugal ? - } - - public static enum DecimalMarkDisplay { - AUTO, ALWAYS, - } - - public static enum SignDisplay { - AUTO, ALWAYS, NEVER, ACCOUNTING, ACCOUNTING_ALWAYS, - } - - /** - * Use a default threshold of 3. This means that the third time .format() is called, the data structures get built - * using the "safe" code path. The first two calls to .format() will trigger the unsafe code path. - */ - static final long DEFAULT_THRESHOLD = 3; - - public static UnlocalizedNumberFormatter with() { - return BASE; - } - - public static LocalizedNumberFormatter withLocale(Locale locale) { - return BASE.locale(locale); - } - - public static LocalizedNumberFormatter withLocale(ULocale locale) { - return BASE.locale(locale); - } - - /** - * @internal - * @deprecated ICU 60 This API is ICU internal only. - */ - @Deprecated - public static UnlocalizedNumberFormatter fromDecimalFormat(DecimalFormatProperties properties, - DecimalFormatSymbols symbols, DecimalFormatProperties exportedProperties) { - MacroProps macros = NumberPropertyMapper.oldToNew(properties, symbols, exportedProperties); - return NumberFormatter.with().macros(macros); - } -} diff --git a/icu4j/main/classes/core/src/newapi/SkeletonBuilder.java b/icu4j/main/classes/core/src/newapi/SkeletonBuilder.java deleted file mode 100644 index b44385b16b2..00000000000 --- a/icu4j/main/classes/core/src/newapi/SkeletonBuilder.java +++ /dev/null @@ -1,586 +0,0 @@ -// © 2017 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html#License -package newapi; - -import java.math.BigDecimal; -import java.math.MathContext; -import java.math.RoundingMode; - -import com.ibm.icu.text.CompactDecimalFormat.CompactStyle; -import com.ibm.icu.text.DecimalFormatSymbols; -import com.ibm.icu.text.NumberingSystem; -import com.ibm.icu.util.Currency; -import com.ibm.icu.util.Currency.CurrencyUsage; -import com.ibm.icu.util.MeasureUnit; -import com.ibm.icu.util.NoUnit; - -import newapi.NumberFormatter.DecimalMarkDisplay; -import newapi.NumberFormatter.SignDisplay; -import newapi.NumberFormatter.UnitWidth; -import newapi.Rounder.CurrencyRounderImpl; -import newapi.Rounder.FracSigRounderImpl; -import newapi.Rounder.FractionRounderImpl; -import newapi.Rounder.IncrementRounderImpl; -import newapi.Rounder.InfiniteRounderImpl; -import newapi.Rounder.SignificantRounderImpl; -import newapi.impl.MacroProps; - -final class SkeletonBuilder { - - public static String macrosToSkeleton(MacroProps macros) { - // Print out the values in their canonical order. - StringBuilder sb = new StringBuilder(); - if (macros.notation != null) { - // sb.append("notation="); - notationToSkeleton(macros.notation, sb); - sb.append(' '); - } - if (macros.unit != null) { - // sb.append("unit="); - unitToSkeleton(macros.unit, sb); - sb.append(' '); - } - if (macros.rounder != null) { - // sb.append("rounding="); - rounderToSkeleton(macros.rounder, sb); - sb.append(' '); - } - if (macros.grouper != null) { - sb.append("grouping="); - grouperToSkeleton(macros.grouper, sb); - sb.append(' '); - } -// if (macros.padder != null) { -// sb.append("padding="); -// paddingToSkeleton(macros.padder, sb); -// sb.append(' '); -// } - if (macros.integerWidth != null) { - sb.append("integer-width="); - integerWidthToSkeleton(macros.integerWidth, sb); - sb.append(' '); - } - if (macros.symbols != null) { - sb.append("symbols="); - symbolsToSkeleton(macros.symbols, sb); - sb.append(' '); - } - if (macros.unitWidth != null) { - sb.append("unit-width="); - unitWidthToSkeleton(macros.unitWidth, sb); - sb.append(' '); - } - if (macros.sign != null) { - sb.append("sign="); - signToSkeleton(macros.sign, sb); - sb.append(' '); - } - if (macros.decimal != null) { - sb.append("decimal="); - decimalToSkeleton(macros.decimal, sb); - sb.append(' '); - } - if (sb.length() > 0) { - // Remove the trailing space - sb.setLength(sb.length() - 1); - } - return sb.toString(); - } - - public static MacroProps skeletonToMacros(String skeleton) { - MacroProps macros = new MacroProps(); - for (int offset = 0; offset < skeleton.length();) { - char c = skeleton.charAt(offset); - switch (c) { - case ' ': - offset++; - break; - case 'E': - case 'C': - case 'I': - offset += skeletonToNotation(skeleton, offset, macros); - break; - case '%': - case 'B': - case '$': - case 'U': - offset += skeletonToUnit(skeleton, offset, macros); - break; - case 'F': - case 'S': - case 'M': - case 'G': - case 'Y': - offset += skeletonToRounding(skeleton, offset, macros); - break; - default: - if (skeleton.regionMatches(offset, "notation=", 0, 9)) { - offset += 9; - offset += skeletonToNotation(skeleton, offset, macros); - } else if (skeleton.regionMatches(offset, "unit=", 0, 5)) { - offset += 5; - offset += skeletonToUnit(skeleton, offset, macros); - } else if (skeleton.regionMatches(offset, "rounding=", 0, 9)) { - offset += 9; - offset += skeletonToRounding(skeleton, offset, macros); - } else if (skeleton.regionMatches(offset, "grouping=", 0, 9)) { - offset += 9; - offset += skeletonToGrouping(skeleton, offset, macros); -// } else if (skeleton.regionMatches(offset, "padding=", 0, 9)) { -// offset += 8; -// offset += skeletonToPadding(skeleton, offset, macros); - } else if (skeleton.regionMatches(offset, "integer-width=", 0, 9)) { - offset += 14; - offset += skeletonToIntegerWidth(skeleton, offset, macros); - } else if (skeleton.regionMatches(offset, "symbols=", 0, 9)) { - offset += 8; - offset += skeletonToSymbols(skeleton, offset, macros); - } else if (skeleton.regionMatches(offset, "unit-width=", 0, 9)) { - offset += 11; - offset += skeletonToUnitWidth(skeleton, offset, macros); - } else if (skeleton.regionMatches(offset, "sign=", 0, 9)) { - offset += 5; - offset += skeletonToSign(skeleton, offset, macros); - } else if (skeleton.regionMatches(offset, "decimal=", 0, 9)) { - offset += 8; - offset += skeletonToDecimal(skeleton, offset, macros); - } else { - throw new IllegalArgumentException( - "Unexpected token at offset " + offset + " in skeleton string: " + c); - } - } - } - return macros; - } - - private static void notationToSkeleton(Notation value, StringBuilder sb) { - if (value instanceof ScientificNotation) { - ScientificNotation notation = (ScientificNotation) value; - sb.append('E'); - if (notation.engineeringInterval != 1) { - sb.append(notation.engineeringInterval); - } - if (notation.exponentSignDisplay == SignDisplay.ALWAYS) { - sb.append('+'); - } else if (notation.exponentSignDisplay == SignDisplay.NEVER) { - sb.append('!'); - } else { - assert notation.exponentSignDisplay == SignDisplay.AUTO; - } - if (notation.minExponentDigits != 1) { - for (int i = 0; i < notation.minExponentDigits; i++) { - sb.append('0'); - } - } - } else if (value instanceof CompactNotation) { - CompactNotation notation = (CompactNotation) value; - if (notation.compactStyle == CompactStyle.SHORT) { - sb.append('C'); - } else { - // FIXME: CCC or CCCC instead? - sb.append("CC"); - } - } else { - assert value instanceof SimpleNotation; - sb.append('I'); - } - } - - private static int skeletonToNotation(String skeleton, int offset, MacroProps output) { - int originalOffset = offset; - char c0 = skeleton.charAt(offset++); - Notation result = null; - if (c0 == 'E') { - int engineering = 1; - SignDisplay sign = SignDisplay.AUTO; - int minExponentDigits = 0; - char c = safeCharAt(skeleton, offset++); - if (c >= '1' && c <= '9') { - engineering = c - '0'; - c = safeCharAt(skeleton, offset++); - } - if (c == '+') { - sign = SignDisplay.ALWAYS; - c = safeCharAt(skeleton, offset++); - } - if (c == '!') { - sign = SignDisplay.NEVER; - c = safeCharAt(skeleton, offset++); - } - while (c == '0') { - minExponentDigits++; - c = safeCharAt(skeleton, offset++); - } - minExponentDigits = Math.max(1, minExponentDigits); - result = new ScientificNotation(engineering, false, minExponentDigits, sign); - } else if (c0 == 'C') { - char c = safeCharAt(skeleton, offset++); - if (c == 'C') { - result = Notation.compactLong(); - } else { - result = Notation.compactShort(); - } - } else if (c0 == 'I') { - result = Notation.simple(); - } - output.notation = result; - return offset - originalOffset; - } - - private static void unitToSkeleton(MeasureUnit value, StringBuilder sb) { - if (value.getType().equals("none")) { - if (value.getSubtype().equals("percent")) { - sb.append('%'); - } else if (value.getSubtype().equals("permille")) { - sb.append("%%"); - } else { - assert value.getSubtype().equals("base"); - sb.append('B'); - } - } else if (value.getType().equals("currency")) { - sb.append('$'); - sb.append(value.getSubtype()); - } else { - sb.append("U:"); - sb.append(value.getType()); - sb.append(':'); - sb.append(value.getSubtype()); - } - } - - private static int skeletonToUnit(String skeleton, int offset, MacroProps output) { - int originalOffset = offset; - char c0 = skeleton.charAt(offset++); - MeasureUnit result = null; - if (c0 == '%') { - char c = safeCharAt(skeleton, offset++); - if (c == '%') { - result = NoUnit.PERCENT; - } else { - result = NoUnit.PERMILLE; - } - } else if (c0 == 'B') { - result = NoUnit.BASE; - } else if (c0 == '$') { - String currencyCode = skeleton.substring(offset, offset + 3); - offset += 3; - result = Currency.getInstance(currencyCode); - } else if (c0 == 'U') { - StringBuilder sb = new StringBuilder(); - offset += consumeUntil(skeleton, offset, ':', sb); - String type = sb.toString(); - sb.setLength(0); - offset += consumeUntil(skeleton, offset, ' ', sb); - String subtype = sb.toString(); - for (MeasureUnit candidate : MeasureUnit.getAvailable(type)) { - if (candidate.getSubtype().equals(subtype)) { - result = candidate; - break; - } - } - } - output.unit = result; - return offset - originalOffset; - } - - private static void rounderToSkeleton(Rounder value, StringBuilder sb) { - if (!(value instanceof Rounder)) { - // FIXME: Throw an exception here instead? - return; - } - MathContext mathContext; - if (value instanceof FractionRounderImpl) { - FractionRounderImpl rounder = (FractionRounderImpl) value; - sb.append('F'); - minMaxToSkeletonHelper(rounder.minFrac, rounder.maxFrac, sb); - mathContext = rounder.mathContext; - } else if (value instanceof SignificantRounderImpl) { - SignificantRounderImpl rounder = (SignificantRounderImpl) value; - sb.append('S'); - minMaxToSkeletonHelper(rounder.minSig, rounder.maxSig, sb); - mathContext = rounder.mathContext; - } else if (value instanceof FracSigRounderImpl) { - FracSigRounderImpl rounder = (FracSigRounderImpl) value; - sb.append('F'); - minMaxToSkeletonHelper(rounder.minFrac, rounder.maxFrac, sb); - if (rounder.minSig != -1) { - sb.append('>'); - sb.append(rounder.minSig); - } else { - sb.append('<'); - sb.append(rounder.maxSig); - } - mathContext = rounder.mathContext; - } else if (value instanceof IncrementRounderImpl) { - IncrementRounderImpl rounder = (IncrementRounderImpl) value; - sb.append('M'); - sb.append(rounder.increment.toString()); - mathContext = rounder.mathContext; - } else if (value instanceof CurrencyRounderImpl) { - CurrencyRounderImpl rounder = (CurrencyRounderImpl) value; - sb.append('G'); - sb.append(rounder.usage.name()); - mathContext = rounder.mathContext; - } else { - InfiniteRounderImpl rounder = (InfiniteRounderImpl) value; - sb.append('Y'); - mathContext = rounder.mathContext; - } - // RoundingMode - RoundingMode roundingMode = mathContext.getRoundingMode(); - if (roundingMode != RoundingMode.HALF_EVEN) { - sb.append(';'); - sb.append(roundingMode.name()); - } - } - - private static void minMaxToSkeletonHelper(int minFrac, int maxFrac, StringBuilder sb) { - if (minFrac == maxFrac) { - sb.append(minFrac); - } else { - boolean showMaxFrac = (maxFrac >= 0 && maxFrac < Integer.MAX_VALUE); - if (minFrac > 0 || !showMaxFrac) { - sb.append(minFrac); - } - sb.append('-'); - if (showMaxFrac) { - sb.append(maxFrac); - } - } - } - - private static int skeletonToRounding(String skeleton, int offset, MacroProps output) { - int originalOffset = offset; - char c0 = skeleton.charAt(offset++); - Rounder result = null; - if (c0 == 'F') { - int[] minMax = new int[2]; - offset += skeletonToMinMaxHelper(skeleton, offset, minMax); - FractionRounder temp = Rounder.constructFraction(minMax[0], minMax[1]); - char c1 = skeleton.charAt(offset++); - if (c1 == '<') { - char c2 = skeleton.charAt(offset++); - result = temp.withMaxDigits(c2 - '0'); - } else if (c1 == '>') { - char c2 = skeleton.charAt(offset++); - result = temp.withMinDigits(c2 - '0'); - } else { - result = temp; - } - } else if (c0 == 'S') { - int[] minMax = new int[2]; - offset += skeletonToMinMaxHelper(skeleton, offset, minMax); - result = Rounder.constructSignificant(minMax[0], minMax[1]); - } else if (c0 == 'M') { - StringBuilder sb = new StringBuilder(); - offset += consumeUntil(skeleton, offset, ' ', sb); - BigDecimal increment = new BigDecimal(sb.toString()); - result = Rounder.constructIncrement(increment); - } else if (c0 == 'G') { - StringBuilder sb = new StringBuilder(); - offset += consumeUntil(skeleton, offset, ' ', sb); - CurrencyUsage usage = Enum.valueOf(CurrencyUsage.class, sb.toString()); - result = Rounder.constructCurrency(usage); - } else if (c0 == 'Y') { - result = Rounder.constructInfinite(); - } - output.rounder = result; - return offset - originalOffset; - } - - private static int skeletonToMinMaxHelper(String skeleton, int offset, int[] output) { - int originalOffset = offset; - char c0 = safeCharAt(skeleton, offset++); - char c1 = safeCharAt(skeleton, offset++); - // TODO: This algorithm breaks if the number is more than 1 char wide. - if (c1 == '-') { - output[0] = c0 - '0'; - char c2 = safeCharAt(skeleton, offset++); - if (c2 == ' ') { - output[1] = Integer.MAX_VALUE; - } else { - output[1] = c2 - '0'; - } - } else if ('0' <= c1 && c1 <= '9') { - output[0] = 0; - output[1] = c1 - '0'; - } else { - offset--; - output[0] = c0 - '0'; - output[1] = c0 - '0'; - } - return offset - originalOffset; - } - - private static void grouperToSkeleton(Grouper value, StringBuilder sb) { - if (value.equals(Grouper.defaults())) { - sb.append("defaults"); - } else if (value.equals(Grouper.min2())) { - sb.append("min2"); - } else if (value.equals(Grouper.none())) { - sb.append("none"); - } else { - // Not supported in skeleton string - sb.append("defaults"); - } - } - - private static int skeletonToGrouping(String skeleton, int offset, MacroProps output) { - int originalOffset = offset; - char c0 = skeleton.charAt(offset++); - Grouper result = null; - StringBuilder sb = new StringBuilder(); - offset += consumeUntil(skeleton, --offset, ' ', sb); - String name = sb.toString(); - if (name.equals("defaults")) { - result = Grouper.defaults(); - } else if (name.equals("min2")) { - result = Grouper.min2(); - } else if (name.equals("none")) { - result = Grouper.none(); - } - output.grouper = result; - return offset - originalOffset; - } - -// private static void paddingToSkeleton(Padder value, StringBuilder sb) { -// PaddingImpl padding = (PaddingImpl) value; -// if (padding == Padder.NONE) { -// sb.append("NONE"); -// return; -// } -// sb.append(padding.targetWidth); -// sb.append(':'); -// sb.append(padding.position.name()); -// sb.append(':'); -// if (!padding.paddingString.equals(" ")) { -// sb.append(padding.paddingString); -// } -// } -// -// private static int skeletonToPadding(String skeleton, int offset, MacroProps output) { -// int originalOffset = offset; -// char c0 = skeleton.charAt(offset++); -// if (c0 == 'N') { -// offset += consumeUntil(skeleton, --offset, ' ', null); -// } else if ('0' <= c0 && c0 <= '9') { -// long intResult = consumeInt(skeleton, --offset); -// offset += intResult & 0xffffffff; -// int width = (int) (intResult >>> 32); -// char c1 = safeCharAt(skeleton, offset++); -// if (c1 != ':') { -// return offset - originalOffset - 1; -// } -// StringBuilder sb = new StringBuilder(); -// offset += consumeUntil(skeleton, offset, ':', sb); -// String padPositionString = sb.toString(); -// sb.setLength(0); -// offset += consumeUntil(skeleton, offset, ' ', sb); -// String string = (sb.length() == 0) ? " " : sb.toString(); -// PadPosition position = Enum.valueOf(PadPosition.class, padPositionString); -// output.padder = PaddingImpl.getInstance(string, width, position); -// } -// return offset - originalOffset; -// } - - private static void integerWidthToSkeleton(IntegerWidth value, StringBuilder sb) { - sb.append(value.minInt); - if (value.maxInt != value.minInt) { - sb.append('-'); - if (value.maxInt != -1) { - sb.append(value.maxInt); - } - } - } - - private static int skeletonToIntegerWidth(String skeleton, int offset, MacroProps output) { - int originalOffset = offset; - long intResult = consumeInt(skeleton, offset); - offset += intResult & 0xffffffff; - int minInt = (int) (intResult >>> 32); - char c1 = safeCharAt(skeleton, --offset); - int maxInt; - if (c1 == '-') { - intResult = consumeInt(skeleton, offset); - offset += intResult & 0xffffffff; - maxInt = (int) (intResult >>> 32); - } - } - - private static void symbolsToSkeleton(Object value, StringBuilder sb) { - if (value instanceof DecimalFormatSymbols) { - // TODO: Check to see if any of the symbols are not default? - sb.append("loc:"); - sb.append(((DecimalFormatSymbols) value).getULocale()); - } else { - sb.append("ns:"); - sb.append(((NumberingSystem) value).getName()); - } - } - - private static void unitWidthToSkeleton(UnitWidth value, StringBuilder sb) { - sb.append(value.name()); - } - - private static int skeletonToUnitWidth(String skeleton, int offset, MacroProps output) { - int originalOffset = offset; - StringBuilder sb = new StringBuilder(); - offset += consumeUntil(skeleton, offset, ' ', sb); - output.unitWidth = Enum.valueOf(UnitWidth.class, sb.toString()); - return offset - originalOffset; - } - - private static void signToSkeleton(SignDisplay value, StringBuilder sb) { - sb.append(value.name()); - } - - private static int skeletonToSign(String skeleton, int offset, MacroProps output) { - int originalOffset = offset; - StringBuilder sb = new StringBuilder(); - offset += consumeUntil(skeleton, offset, ' ', sb); - output.sign = Enum.valueOf(SignDisplay.class, sb.toString()); - return offset - originalOffset; - } - - private static void decimalToSkeleton(DecimalMarkDisplay value, StringBuilder sb) { - sb.append(value.name()); - } - - private static int skeletonToDecimal(String skeleton, int offset, MacroProps output) { - int originalOffset = offset; - StringBuilder sb = new StringBuilder(); - offset += consumeUntil(skeleton, offset, ' ', sb); - output.decimal = Enum.valueOf(DecimalMarkDisplay.class, sb.toString()); - return offset - originalOffset; - } - - private static char safeCharAt(String str, int offset) { - if (offset < str.length()) { - return str.charAt(offset); - } else { - return ' '; - } - } - - private static int consumeUntil(String skeleton, int offset, char brk, StringBuilder sb) { - int originalOffset = offset; - char c = safeCharAt(skeleton, offset++); - while (c != brk) { - if (sb != null) - sb.append(c); - c = safeCharAt(skeleton, offset++); - } - return offset - originalOffset; - } - - private static long consumeInt(String skeleton, int offset) { - int originalOffset = offset; - char c = safeCharAt(skeleton, offset++); - int result = 0; - while ('0' <= c && c <= '9') { - result = (result * 10) + (c - '0'); - c = safeCharAt(skeleton, offset++); - } - return (offset - originalOffset) | (((long) result) << 32); - } -} diff --git a/icu4j/main/classes/core/src/newapi/impl/demo.java b/icu4j/main/classes/core/src/newapi/impl/demo.java deleted file mode 100644 index 8deaa788a26..00000000000 --- a/icu4j/main/classes/core/src/newapi/impl/demo.java +++ /dev/null @@ -1,64 +0,0 @@ -// © 2017 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html#License -package newapi.impl; - -import java.math.RoundingMode; - -import com.ibm.icu.text.DecimalFormatSymbols; -import com.ibm.icu.text.NumberingSystem; -import com.ibm.icu.util.Currency; -import com.ibm.icu.util.Currency.CurrencyUsage; -import com.ibm.icu.util.MeasureUnit; -import com.ibm.icu.util.NoUnit; -import com.ibm.icu.util.ULocale; - -import newapi.Grouper; -import newapi.Notation; -import newapi.NumberFormatter; -import newapi.NumberFormatter.DecimalMarkDisplay; -import newapi.NumberFormatter.SignDisplay; -import newapi.NumberFormatter.UnitWidth; -import newapi.Rounder; -import newapi.UnlocalizedNumberFormatter; - -public class demo { - public static void main(String[] args) { - System.out.println(NumberingSystem.LATIN.getDescription()); - UnlocalizedNumberFormatter formatter = - NumberFormatter.with() - .notation(Notation.compactShort()) - .notation(Notation.scientific().withExponentSignDisplay(SignDisplay.ALWAYS)) - .notation(Notation.engineering().withMinExponentDigits(2)) - .notation(Notation.simple()) - .unit(Currency.getInstance("GBP")) - .unit(NoUnit.PERCENT) - .unit(MeasureUnit.CUBIC_METER) - .unitWidth(UnitWidth.SHORT) - // .rounding(Rounding.fixedSignificantDigits(3)) -// .rounding( -// (BigDecimal input) -> { -// return input.divide(new BigDecimal("0.02"), 0).multiply(new BigDecimal("0.02")); -// }) - .rounding(Rounder.fixedFraction(2).withMode(RoundingMode.HALF_UP)) - .rounding(Rounder.integer().withMode(RoundingMode.CEILING)) - .rounding(Rounder.currency(CurrencyUsage.STANDARD)) -// .grouping( -// (int position, BigDecimal number) -> { -// return (position % 3) == 0; -// }) - .grouping(Grouper.defaults()) - .grouping(Grouper.none()) - .grouping(Grouper.min2()) - // .padding(Padding.codePoints(' ', 8, PadPosition.AFTER_PREFIX)) - .sign(SignDisplay.ALWAYS) - .decimal(DecimalMarkDisplay.ALWAYS) - .symbols(DecimalFormatSymbols.getInstance(new ULocale("fr@digits=ascii"))) - .symbols(NumberingSystem.getInstanceByName("arab")) - .symbols(NumberingSystem.LATIN); - System.out.println(formatter.toSkeleton()); - System.out.println(formatter.locale(ULocale.ENGLISH).format(0.98381).toString()); - // .locale(Locale.ENGLISH) - // .format(123.45) - // .toString(); - } -} diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt b/icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt index 31955450cf6..404fe0ddedb 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt @@ -195,25 +195,6 @@ pattern format output breaks #E0 52413 5,2413E4 K 0E0 52413 5E4 -test scientific with grouping -set locale en -set pattern #,##0.000E0 -begin -format output breaks -// J throws an IllegalArgumentException when parsing the pattern. -// C sets error code to U_MALFORMED_EXPONENTIAL_PATTERN. -1 1.000E0 CJ -11 11.00E0 CJ -111 111.0E0 CJ -// K doesn't print the grouping separator ("1111E0") -1111 1,111E0 CJK -// K prints too many digits ("1.1111E4") -11111 1.111E4 CJK -111111 11.11E4 CJK -1111111 111.1E4 CJK -11111111 1,111E4 CJK -111111111 1.111E8 CJK - test percents set locale fr begin diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatDataDrivenTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatDataDrivenTest.java index 4b7ea8b21d2..0015673974f 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatDataDrivenTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatDataDrivenTest.java @@ -10,18 +10,17 @@ import org.junit.Test; import com.ibm.icu.dev.test.TestUtil; import com.ibm.icu.impl.number.DecimalFormatProperties; +import com.ibm.icu.impl.number.Padder.PadPosition; import com.ibm.icu.impl.number.Parse.ParseMode; import com.ibm.icu.impl.number.PatternStringParser; import com.ibm.icu.impl.number.PatternStringUtils; +import com.ibm.icu.number.LocalizedNumberFormatter; +import com.ibm.icu.number.NumberFormatter; import com.ibm.icu.text.DecimalFormatSymbols; import com.ibm.icu.text.DecimalFormat_ICU58; import com.ibm.icu.util.CurrencyAmount; import com.ibm.icu.util.ULocale; -import newapi.LocalizedNumberFormatter; -import newapi.NumberFormatter; -import newapi.impl.Padder.PadPosition; - public class NumberFormatDataDrivenTest { private static ULocale EN = new ULocale("en"); diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java index ff800d3b316..145ff9e896a 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java @@ -2750,7 +2750,9 @@ public class NumberFormatTest extends TestFmwk { @Test public void TestScientificWithGrouping() { - DecimalFormat df = new DecimalFormat("#,##0.000E0"); + // Grouping separators are not allowed in the pattern, but we can enable them via the API. + DecimalFormat df = new DecimalFormat("###0.000E0"); + df.setGroupingUsed(true); expect2(df, 123, "123.0E0"); expect2(df, 1234, "1,234E0"); expect2(df, 12340, "1.234E4"); diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java index b2485ed8a1c..7bda67cc998 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java @@ -20,13 +20,12 @@ import com.ibm.icu.impl.number.DecimalQuantity_64BitBCD; import com.ibm.icu.impl.number.DecimalQuantity_ByteArrayBCD; import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD; import com.ibm.icu.impl.number.DecimalQuantity_SimpleStorage; +import com.ibm.icu.number.LocalizedNumberFormatter; +import com.ibm.icu.number.NumberFormatter; import com.ibm.icu.text.CompactDecimalFormat.CompactStyle; import com.ibm.icu.text.DecimalFormatSymbols; import com.ibm.icu.util.ULocale; -import newapi.LocalizedNumberFormatter; -import newapi.NumberFormatter; - /** TODO: This is a temporary name for this class. Suggestions for a better name? */ public class DecimalQuantityTest extends TestFmwk { diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/ModifierTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/ModifierTest.java index 205660f27c7..6af4ea1c4a0 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/ModifierTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/ModifierTest.java @@ -8,12 +8,12 @@ import static org.junit.Assert.assertTrue; import org.junit.Test; import com.ibm.icu.impl.SimpleFormatterImpl; +import com.ibm.icu.impl.number.ConstantAffixModifier; +import com.ibm.icu.impl.number.ConstantMultiFieldModifier; +import com.ibm.icu.impl.number.CurrencySpacingEnabledModifier; import com.ibm.icu.impl.number.Modifier; import com.ibm.icu.impl.number.NumberStringBuilder; -import com.ibm.icu.impl.number.modifiers.ConstantAffixModifier; -import com.ibm.icu.impl.number.modifiers.ConstantMultiFieldModifier; -import com.ibm.icu.impl.number.modifiers.CurrencySpacingEnabledModifier; -import com.ibm.icu.impl.number.modifiers.SimpleModifier; +import com.ibm.icu.impl.number.SimpleModifier; import com.ibm.icu.text.DecimalFormatSymbols; import com.ibm.icu.text.NumberFormat; import com.ibm.icu.util.ULocale; diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/MutablePatternModifierTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/MutablePatternModifierTest.java index 115d8229dab..80f44ada5d4 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/MutablePatternModifierTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/MutablePatternModifierTest.java @@ -10,17 +10,16 @@ import org.junit.Test; import com.ibm.icu.impl.number.DecimalQuantity; import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD; +import com.ibm.icu.impl.number.MicroProps; +import com.ibm.icu.impl.number.MutablePatternModifier; import com.ibm.icu.impl.number.NumberStringBuilder; import com.ibm.icu.impl.number.PatternStringParser; +import com.ibm.icu.number.NumberFormatter.SignDisplay; +import com.ibm.icu.number.NumberFormatter.UnitWidth; import com.ibm.icu.text.DecimalFormatSymbols; import com.ibm.icu.util.Currency; import com.ibm.icu.util.ULocale; -import newapi.NumberFormatter.SignDisplay; -import newapi.NumberFormatter.UnitWidth; -import newapi.impl.MicroProps; -import newapi.impl.MutablePatternModifier; - public class MutablePatternModifierTest { @Test diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterTest.java index 8a6d196be4c..466399ff1a1 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterTest.java @@ -6,12 +6,26 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import java.math.BigDecimal; +import java.math.RoundingMode; import java.util.Locale; import org.junit.Ignore; import org.junit.Test; +import com.ibm.icu.impl.number.Padder; +import com.ibm.icu.impl.number.Padder.PadPosition; import com.ibm.icu.impl.number.PatternStringParser; +import com.ibm.icu.number.FormattedNumber; +import com.ibm.icu.number.Grouper; +import com.ibm.icu.number.IntegerWidth; +import com.ibm.icu.number.LocalizedNumberFormatter; +import com.ibm.icu.number.Notation; +import com.ibm.icu.number.NumberFormatter; +import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay; +import com.ibm.icu.number.NumberFormatter.SignDisplay; +import com.ibm.icu.number.NumberFormatter.UnitWidth; +import com.ibm.icu.number.Rounder; +import com.ibm.icu.number.UnlocalizedNumberFormatter; import com.ibm.icu.text.DecimalFormatSymbols; import com.ibm.icu.text.NumberingSystem; import com.ibm.icu.util.Currency; @@ -22,25 +36,12 @@ import com.ibm.icu.util.MeasureUnit; import com.ibm.icu.util.NoUnit; import com.ibm.icu.util.ULocale; -import newapi.FormattedNumber; -import newapi.Grouper; -import newapi.IntegerWidth; -import newapi.LocalizedNumberFormatter; -import newapi.Notation; -import newapi.NumberFormatter; -import newapi.NumberFormatter.DecimalMarkDisplay; -import newapi.NumberFormatter.SignDisplay; -import newapi.NumberFormatter.UnitWidth; -import newapi.Rounder; -import newapi.UnlocalizedNumberFormatter; -import newapi.impl.Padder; -import newapi.impl.Padder.PadPosition; - public class NumberFormatterTest { private static final Currency USD = Currency.getInstance("USD"); private static final Currency GBP = Currency.getInstance("GBP"); private static final Currency CZK = Currency.getInstance("CZK"); + private static final Currency CAD = Currency.getInstance("CAD"); @Test public void notationSimple() { @@ -59,6 +60,21 @@ public class NumberFormatterTest { "0.008765", "0"); + assertFormatDescendingBig( + "Big Simple", + "", + NumberFormatter.with().notation(Notation.simple()), + ULocale.ENGLISH, + "87,650,000", + "8,765,000", + "876,500", + "87,650", + "8,765", + "876.5", + "87.65", + "8.765", + "0"); + assertFormatSingle( "Basic with Negative Sign", "", @@ -141,34 +157,34 @@ public class NumberFormatterTest { @Test public void notationCompact() { - assertFormatDescending( + assertFormatDescendingBig( "Compact Short", "C", NumberFormatter.with().notation(Notation.compactShort()), ULocale.ENGLISH, + "88M", + "8.8M", + "876K", "88K", "8.8K", "876", "88", "8.8", - "0.88", - "0.088", - "0.0088", "0"); - assertFormatDescending( + assertFormatDescendingBig( "Compact Long", "CC", NumberFormatter.with().notation(Notation.compactLong()), ULocale.ENGLISH, + "88 million", + "8.8 million", + "876 thousand", "88 thousand", "8.8 thousand", "876", "88", "8.8", - "0.88", - "0.088", - "0.0088", "0"); assertFormatDescending( @@ -189,10 +205,7 @@ public class NumberFormatterTest { assertFormatDescending( "Compact Short with ISO Currency", "C $USD unit-width=ISO_CODE", - NumberFormatter.with() - .notation(Notation.compactShort()) - .unit(USD) - .unitWidth(UnitWidth.ISO_CODE), + NumberFormatter.with().notation(Notation.compactShort()).unit(USD).unitWidth(UnitWidth.ISO_CODE), ULocale.ENGLISH, "USD 88K", "USD 8.8K", @@ -207,10 +220,7 @@ public class NumberFormatterTest { assertFormatDescending( "Compact Short with Long Name Currency", "C $USD unit-width=FULL_NAME", - NumberFormatter.with() - .notation(Notation.compactShort()) - .unit(USD) - .unitWidth(UnitWidth.FULL_NAME), + NumberFormatter.with().notation(Notation.compactShort()).unit(USD).unitWidth(UnitWidth.FULL_NAME), ULocale.ENGLISH, "88K US dollars", "8.8K US dollars", @@ -244,10 +254,7 @@ public class NumberFormatterTest { assertFormatDescending( "Compact Long with ISO Currency", "CC $USD unit-width=ISO_CODE", - NumberFormatter.with() - .notation(Notation.compactLong()) - .unit(USD) - .unitWidth(UnitWidth.ISO_CODE), + NumberFormatter.with().notation(Notation.compactLong()).unit(USD).unitWidth(UnitWidth.ISO_CODE), ULocale.ENGLISH, "USD 88K", // should be something like "USD 88 thousand" "USD 8.8K", @@ -263,10 +270,7 @@ public class NumberFormatterTest { assertFormatDescending( "Compact Long with Long Name Currency", "CC $USD unit-width=FULL_NAME", - NumberFormatter.with() - .notation(Notation.compactLong()) - .unit(USD) - .unitWidth(UnitWidth.FULL_NAME), + NumberFormatter.with().notation(Notation.compactLong()).unit(USD).unitWidth(UnitWidth.FULL_NAME), ULocale.ENGLISH, "88 thousand US dollars", "8.8 thousand US dollars", @@ -442,6 +446,24 @@ public class NumberFormatterTest { ULocale.forLanguageTag("en-GB"), 5.43, "5.43 m²"); + + // es_US has "{0}°" for unitsNarrow/temperature/FAHRENHEIT. + // NOTE: This example is in the documentation. + assertFormatSingle( + "MeasureUnit Difference between Narrow and Short (Narrow Version)", + "", + NumberFormatter.with().unit(MeasureUnit.FAHRENHEIT).unitWidth(UnitWidth.NARROW), + ULocale.forLanguageTag("es-US"), + 5.43, + "5.43°"); + + assertFormatSingle( + "MeasureUnit Difference between Narrow and Short (Short Version)", + "", + NumberFormatter.with().unit(MeasureUnit.FAHRENHEIT).unitWidth(UnitWidth.SHORT), + ULocale.forLanguageTag("es-US"), + 5.43, + "5.43 °F"); } @Test @@ -491,6 +513,21 @@ public class NumberFormatterTest { "0.01 British pounds", "0.00 British pounds"); + assertFormatDescending( + "Currency Hidden", + "$GBP unit-width=HIDDEN", + NumberFormatter.with().unit(GBP).unitWidth(UnitWidth.HIDDEN), + ULocale.ENGLISH, + "87,650.00", + "8,765.00", + "876.50", + "87.65", + "8.76", + "0.88", + "0.09", + "0.01", + "0.00"); + assertFormatSingleMeasure( "Currency with CurrencyAmount Input", "", @@ -517,6 +554,25 @@ public class NumberFormatterTest { ULocale.ENGLISH, -9876543.21, "-£9,876,543.21"); + + // The full currency symbol is not shown in NARROW format. + // NOTE: This example is in the documentation. + // FIXME: Narrow Currency does not currently work; see #11666 + assertFormatSingle( + "Currency Difference between Narrow and Short (Narrow Version)", + "", + NumberFormatter.with().unit(USD).unitWidth(UnitWidth.NARROW), + ULocale.forLanguageTag("en-CA"), + 5.43, + "US$5.43"); + + assertFormatSingle( + "Currency Difference between Narrow and Short (Short Version)", + "", + NumberFormatter.with().unit(USD).unitWidth(UnitWidth.SHORT), + ULocale.forLanguageTag("en_CA"), + 5.43, + "US$ 5.43"); } @Test @@ -526,14 +582,14 @@ public class NumberFormatterTest { "%", NumberFormatter.with().unit(NoUnit.PERCENT), ULocale.ENGLISH, - "8,765,000%", - "876,500%", "87,650%", "8,765%", "876.5%", "87.65%", "8.765%", "0.8765%", + "0.08765%", + "0.008765%", "0%"); assertFormatDescending( @@ -541,14 +597,14 @@ public class NumberFormatterTest { "%%", NumberFormatter.with().unit(NoUnit.PERMILLE), ULocale.ENGLISH, - "87,650,000‰", - "8,765,000‰", - "876,500‰", "87,650‰", "8,765‰", "876.5‰", "87.65‰", "8.765‰", + "0.8765‰", + "0.08765‰", + "0.008765‰", "0‰"); assertFormatSingle( @@ -564,8 +620,8 @@ public class NumberFormatterTest { "%", NumberFormatter.with().unit(NoUnit.PERCENT), ULocale.ENGLISH, - -0.987654321, - "-98.7654321%"); + -98.7654321, + "-98.765432%"); } @Test @@ -792,6 +848,21 @@ public class NumberFormatterTest { "0.0", "0.0"); + assertFormatDescending( + "Increment with Min Fraction", + "M0.5", + NumberFormatter.with().rounding(Rounder.increment(new BigDecimal("0.50"))), + ULocale.ENGLISH, + "87,650.00", + "8,765.00", + "876.50", + "87.50", + "9.00", + "1.00", + "0.00", + "0.00", + "0.00"); + assertFormatDescending( "Currency Standard", "$CZK GSTANDARD", @@ -822,6 +893,21 @@ public class NumberFormatterTest { "CZK 0", "CZK 0"); + assertFormatDescending( + "Currency Cash with Nickel Rounding", + "$CAD GCASH", + NumberFormatter.with().rounding(Rounder.currency(CurrencyUsage.CASH)).unit(CAD), + ULocale.ENGLISH, + "CA$87,650.00", + "CA$8,765.00", + "CA$876.50", + "CA$87.65", + "CA$8.75", + "CA$0.90", + "CA$0.10", + "CA$0.00", + "CA$0.00"); + assertFormatDescending( "Currency not in top-level fluent chain", "F0", @@ -836,71 +922,100 @@ public class NumberFormatterTest { "0", "0", "0"); + + // NOTE: Other tests cover the behavior of the other rounding modes. + assertFormatDescending( + "Rounding Mode CEILING", + "", + NumberFormatter.with().rounding(Rounder.integer().withMode(RoundingMode.CEILING)), + ULocale.ENGLISH, + "87,650", + "8,765", + "877", + "88", + "9", + "1", + "1", + "1", + "0"); } @Test public void grouping() { - // NoUnit.PERMILLE multiplies all the number by 10^3 (good for testing grouping). - // Note that en-US is already performed in the unitPercent() function. - assertFormatDescending( - "Indic Grouping", - "%% grouping=defaults", - NumberFormatter.with().unit(NoUnit.PERMILLE).grouping(Grouper.defaults()), - new ULocale("en-IN"), - "8,76,50,000‰", - "87,65,000‰", - "8,76,500‰", - "87,650‰", - "8,765‰", - "876.5‰", - "87.65‰", - "8.765‰", - "0‰"); - - assertFormatDescending( - "Western Grouping, Min 2", - "%% grouping=min2", - NumberFormatter.with().unit(NoUnit.PERMILLE).grouping(Grouper.min2()), + assertFormatDescendingBig( + "Western Grouping", + "grouping=defaults", + NumberFormatter.with().grouping(Grouper.defaults()), ULocale.ENGLISH, - "87,650,000‰", - "8,765,000‰", - "876,500‰", - "87,650‰", - "8765‰", - "876.5‰", - "87.65‰", - "8.765‰", - "0‰"); + "87,650,000", + "8,765,000", + "876,500", + "87,650", + "8,765", + "876.5", + "87.65", + "8.765", + "0"); - assertFormatDescending( + assertFormatDescendingBig( + "Indic Grouping", + "grouping=defaults", + NumberFormatter.with().grouping(Grouper.defaults()), + new ULocale("en-IN"), + "8,76,50,000", + "87,65,000", + "8,76,500", + "87,650", + "8,765", + "876.5", + "87.65", + "8.765", + "0"); + + assertFormatDescendingBig( + "Western Grouping, Min 2", + "grouping=min2", + NumberFormatter.with().grouping(Grouper.minTwoDigits()), + ULocale.ENGLISH, + "87,650,000", + "8,765,000", + "876,500", + "87,650", + "8765", + "876.5", + "87.65", + "8.765", + "0"); + + assertFormatDescendingBig( "Indic Grouping, Min 2", - "%% grouping=min2", - NumberFormatter.with().unit(NoUnit.PERMILLE).grouping(Grouper.min2()), + "grouping=min2", + NumberFormatter.with().grouping(Grouper.minTwoDigits()), new ULocale("en-IN"), - "8,76,50,000‰", - "87,65,000‰", - "8,76,500‰", - "87,650‰", - "8765‰", - "876.5‰", - "87.65‰", - "8.765‰", - "0‰"); + "8,76,50,000", + "87,65,000", + "8,76,500", + "87,650", + "8765", + "876.5", + "87.65", + "8.765", + "0"); - assertFormatDescending( + assertFormatDescendingBig( "No Grouping", - "%% grouping=none", - NumberFormatter.with().unit(NoUnit.PERMILLE).grouping(Grouper.none()), + "grouping=none", + NumberFormatter.with().grouping(Grouper.none()), new ULocale("en-IN"), - "87650000‰", - "8765000‰", - "876500‰", - "87650‰", - "8765‰", - "876.5‰", - "87.65‰", - "8.765‰", - "0‰"); + "87650000", + "8765000", + "876500", + "87650", + "8765", + "876.5", + "87.65", + "8.765", + "0"); } @Test @@ -1004,7 +1119,7 @@ public class NumberFormatterTest { NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.BEFORE_SUFFIX)) .unit(NoUnit.PERCENT), ULocale.ENGLISH, - 0.8888, + 88.88, "88.88**%"); assertFormatSingle( @@ -1013,14 +1128,14 @@ public class NumberFormatterTest { NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.AFTER_SUFFIX)) .unit(NoUnit.PERCENT), ULocale.ENGLISH, - 0.8888, + 88.88, "88.88%**"); assertFormatSingle( "Currency Spacing with Zero Digit Padding Broken", "$GBP unit-width=ISO_CODE", NumberFormatter.with().padding(Padder.codePoints('0', 12, PadPosition.AFTER_PREFIX)).unit(GBP) - .unitWidth(UnitWidth.ISO_CODE), + .unitWidth(UnitWidth.ISO_CODE), ULocale.ENGLISH, 514.23, "GBP 000514.23"); // TODO: This is broken; it renders too wide (13 instead of 12). @@ -1135,15 +1250,15 @@ public class NumberFormatterTest { "$USD symbols=ns:latn", NumberFormatter.with().symbols(NumberingSystem.LATIN).unit(USD), new ULocale("ar"), - "87,650.00 US$", - "8,765.00 US$", - "876.50 US$", - "87.65 US$", - "8.76 US$", - "0.88 US$", - "0.09 US$", - "0.01 US$", - "0.00 US$"); + "US$ 87,650.00", + "US$ 8,765.00", + "US$ 876.50", + "US$ 87.65", + "US$ 8.76", + "US$ 0.88", + "US$ 0.09", + "US$ 0.01", + "US$ 0.00"); assertFormatDescending( "Math Numbering System with French Data", @@ -1176,6 +1291,40 @@ public class NumberFormatterTest { 12345.67, "\u1041\u1042,\u1043\u1044\u1045.\u1046\u1047"); + // NOTE: Locale ar puts ¤ after the number in NS arab but before the number in NS latn. + + assertFormatSingle( + "Currency symbol should precede number in ar with NS latn", + "", + NumberFormatter.with().symbols(NumberingSystem.LATIN).unit(USD), + new ULocale("ar"), + 12345.67, + "US$ 12,345.67"); + + assertFormatSingle( + "Currency symbol should precede number in ar@numbers=latn", + "", + NumberFormatter.with().unit(USD), + new ULocale("ar@numbers=latn"), + 12345.67, + "US$ 12,345.67"); + + assertFormatSingle( + "Currency symbol should follow number in ar with NS arab", + "", + NumberFormatter.with().unit(USD), + new ULocale("ar"), + 12345.67, + "١٢٬٣٤٥٫٦٧ US$"); + + assertFormatSingle( + "Currency symbol should follow number in ar@numbers=arab", + "", + NumberFormatter.with().unit(USD), + new ULocale("ar@numbers=arab"), + 12345.67, + "١٢٬٣٤٥٫٦٧ US$"); + DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(new ULocale("de-CH")); UnlocalizedNumberFormatter f = NumberFormatter.with().symbols(symbols); symbols.setGroupingSeparatorString("!"); @@ -1300,6 +1449,14 @@ public class NumberFormatterTest { ULocale.ENGLISH, -444444, "($444,444.00)"); + + assertFormatSingle( + "Sign Accounting Negative Hidden", + "$USD unit-width=HIDDEN sign=ACCOUNTING", + NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD).unitWidth(UnitWidth.HIDDEN), + ULocale.ENGLISH, + -444444, + "(444,444.00)"); } @Test @@ -1307,7 +1464,7 @@ public class NumberFormatterTest { assertFormatDescending( "Decimal Default", "decimal=AUTO", - NumberFormatter.with().decimal(DecimalMarkDisplay.AUTO), + NumberFormatter.with().decimal(DecimalSeparatorDisplay.AUTO), ULocale.ENGLISH, "87,650", "8,765", @@ -1322,7 +1479,7 @@ public class NumberFormatterTest { assertFormatDescending( "Decimal Always Shown", "decimal=ALWAYS", - NumberFormatter.with().decimal(DecimalMarkDisplay.ALWAYS), + NumberFormatter.with().decimal(DecimalSeparatorDisplay.ALWAYS), ULocale.ENGLISH, "87,650.", "8,765.", @@ -1394,17 +1551,38 @@ public class NumberFormatterTest { UnlocalizedNumberFormatter f, ULocale locale, String... expected) { - assert expected.length == 9; - assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton()); final double[] inputs = new double[] { 87650, 8765, 876.5, 87.65, 8.765, 0.8765, 0.08765, 0.008765, 0 }; + assertFormatDescending(message, skeleton, f, locale, inputs, expected); + } + + private static void assertFormatDescendingBig( + String message, + String skeleton, + UnlocalizedNumberFormatter f, + ULocale locale, + String... expected) { + final double[] inputs = new double[] { 87650000, 8765000, 876500, 87650, 8765, 876.5, 87.65, 8.765, 0 }; + assertFormatDescending(message, skeleton, f, locale, inputs, expected); + } + + private static void assertFormatDescending( + String message, + String skeleton, + UnlocalizedNumberFormatter f, + ULocale locale, + double[] inputs, + String... expected) { + assert expected.length == 9; + // TODO: Add a check for skeleton. + // assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton()); LocalizedNumberFormatter l1 = f.threshold(0L).locale(locale); // no self-regulation LocalizedNumberFormatter l2 = f.threshold(1L).locale(locale); // all self-regulation for (int i = 0; i < 9; i++) { double d = inputs[i]; String actual1 = l1.format(d).toString(); - assertEquals(message + ": L1: " + d, expected[i], actual1); + assertEquals(message + ": Unsafe Path: " + d, expected[i], actual1); String actual2 = l2.format(d).toString(); - assertEquals(message + ": L2: " + d, expected[i], actual2); + assertEquals(message + ": Safe Path: " + d, expected[i], actual2); } } @@ -1415,7 +1593,8 @@ public class NumberFormatterTest { ULocale locale, Number input, String expected) { - assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton()); + // TODO: Add a check for skeleton. + // assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton()); LocalizedNumberFormatter l1 = f.threshold(0L).locale(locale); // no self-regulation LocalizedNumberFormatter l2 = f.threshold(1L).locale(locale); // all self-regulation String actual1 = l1.format(input).toString(); @@ -1431,7 +1610,8 @@ public class NumberFormatterTest { ULocale locale, Measure input, String expected) { - assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton()); + // TODO: Add a check for skeleton. + // assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton()); LocalizedNumberFormatter l1 = f.threshold(0L).locale(locale); // no self-regulation LocalizedNumberFormatter l2 = f.threshold(1L).locale(locale); // all self-regulation String actual1 = l1.format(input).toString(); diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PropertiesTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PropertiesTest.java index 64e034925c5..e3a1a9b848b 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PropertiesTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PropertiesTest.java @@ -33,6 +33,7 @@ import com.ibm.icu.impl.number.DecimalFormatProperties; import com.ibm.icu.impl.number.Parse.GroupingMode; import com.ibm.icu.impl.number.Parse.ParseMode; import com.ibm.icu.impl.number.PatternStringParser; +import com.ibm.icu.impl.number.Padder.PadPosition; import com.ibm.icu.text.CompactDecimalFormat.CompactStyle; import com.ibm.icu.text.CurrencyPluralInfo; import com.ibm.icu.text.MeasureFormat.FormatWidth; @@ -42,8 +43,6 @@ import com.ibm.icu.util.Currency.CurrencyUsage; import com.ibm.icu.util.MeasureUnit; import com.ibm.icu.util.ULocale; -import newapi.impl.Padder.PadPosition; - public class PropertiesTest { @Test diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_64BitBCD.java b/icu4j/main/tests/core/src/com/ibm/icu/impl/number/DecimalQuantity_64BitBCD.java similarity index 97% rename from icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_64BitBCD.java rename to icu4j/main/tests/core/src/com/ibm/icu/impl/number/DecimalQuantity_64BitBCD.java index 4954444a54c..c968a0abc52 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_64BitBCD.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/impl/number/DecimalQuantity_64BitBCD.java @@ -45,6 +45,11 @@ public final class DecimalQuantity_64BitBCD extends DecimalQuantity_AbstractBCD copyFrom(other); } + @Override + public DecimalQuantity createCopy() { + return new DecimalQuantity_64BitBCD(this); + } + @Override protected byte getDigitPos(int position) { if (position < 0 || position >= 16) return 0; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_ByteArrayBCD.java b/icu4j/main/tests/core/src/com/ibm/icu/impl/number/DecimalQuantity_ByteArrayBCD.java similarity index 98% rename from icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_ByteArrayBCD.java rename to icu4j/main/tests/core/src/com/ibm/icu/impl/number/DecimalQuantity_ByteArrayBCD.java index e428c6810c5..f0198109bff 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_ByteArrayBCD.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/impl/number/DecimalQuantity_ByteArrayBCD.java @@ -45,6 +45,11 @@ public final class DecimalQuantity_ByteArrayBCD extends DecimalQuantity_Abstract copyFrom(other); } + @Override + public DecimalQuantity createCopy() { + return new DecimalQuantity_ByteArrayBCD(this); + } + @Override protected byte getDigitPos(int position) { if (position < 0 || position > precision) return 0; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_SimpleStorage.java b/icu4j/main/tests/core/src/com/ibm/icu/impl/number/DecimalQuantity_SimpleStorage.java similarity index 100% rename from icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_SimpleStorage.java rename to icu4j/main/tests/core/src/com/ibm/icu/impl/number/DecimalQuantity_SimpleStorage.java