From 8ea876aadb5a1f7718f9662611381fbc834a1e97 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Tue, 3 Apr 2018 04:38:16 +0000 Subject: [PATCH] ICU-13678 Cleaning up multiplier implementation and adding public API. X-SVN-Rev: 41188 --- icu4c/source/i18n/Makefile.in | 8 +- icu4c/source/i18n/number_decimalquantity.cpp | 10 +- icu4c/source/i18n/number_decimalquantity.h | 2 +- icu4c/source/i18n/number_fluent.cpp | 14 ++ icu4c/source/i18n/number_mapper.cpp | 6 +- icu4c/source/i18n/number_multiplier.cpp | 72 ++++++-- icu4c/source/i18n/number_multiplier.h | 20 +- icu4c/source/i18n/number_utils.h | 2 +- icu4c/source/i18n/numparse_impl.cpp | 12 +- icu4c/source/i18n/numparse_impl.h | 2 + icu4c/source/i18n/numparse_validators.h | 20 ++ icu4c/source/i18n/unicode/numberformatter.h | 171 ++++++++++++++---- icu4c/source/test/intltest/numbertest.h | 1 + icu4c/source/test/intltest/numbertest_api.cpp | 78 ++++++++ .../com/ibm/icu/impl/number/MacroProps.java | 3 +- .../impl/number/MultiplierFormatHandler.java | 25 +++ .../ibm/icu/impl/number/MultiplierImpl.java | 43 ----- .../ibm/icu/impl/number/RoundingUtils.java | 21 +++ .../impl/number/parse/MultiplierHandler.java | 39 ---- .../number/parse/MultiplierParseHandler.java | 30 +++ .../impl/number/parse/NumberParserImpl.java | 7 +- .../src/com/ibm/icu/number/Multiplier.java | 171 ++++++++++++++++++ .../ibm/icu/number/NumberFormatterImpl.java | 3 +- .../icu/number/NumberFormatterSettings.java | 42 ++++- .../ibm/icu/number/NumberPropertyMapper.java | 7 +- .../ibm/icu/number/NumberSkeletonImpl.java | 3 +- .../core/src/com/ibm/icu/number/Rounder.java | 5 +- .../test/number/NumberFormatterApiTest.java | 79 ++++++++ 28 files changed, 716 insertions(+), 180 deletions(-) create mode 100644 icu4j/main/classes/core/src/com/ibm/icu/impl/number/MultiplierFormatHandler.java delete mode 100644 icu4j/main/classes/core/src/com/ibm/icu/impl/number/MultiplierImpl.java delete mode 100644 icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/MultiplierHandler.java create mode 100644 icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/MultiplierParseHandler.java create mode 100644 icu4j/main/classes/core/src/com/ibm/icu/number/Multiplier.java diff --git a/icu4c/source/i18n/Makefile.in b/icu4c/source/i18n/Makefile.in index eaa5d0683ee..44236b50474 100644 --- a/icu4c/source/i18n/Makefile.in +++ b/icu4c/source/i18n/Makefile.in @@ -103,13 +103,13 @@ number_decimfmtprops.o number_fluent.o number_formatimpl.o number_grouping.o \ number_integerwidth.o number_longnames.o number_modifiers.o number_notation.o \ number_padding.o number_patternmodifier.o number_patternstring.o \ number_rounding.o number_scientific.o number_stringbuilder.o \ +number_mapper.o number_multiplier.o number_currencysymbols.o number_skeletons.o number_capi.o \ double-conversion.o double-conversion-bignum-dtoa.o double-conversion-bignum.o \ double-conversion-cached-powers.o double-conversion-diy-fp.o \ double-conversion-fast-dtoa.o double-conversion-strtod.o \ -numparse_stringsegment.o numparse_unisets.o numparse_parsednumber.o \ -numparse_impl.o numparse_symbols.o numparse_decimal.o numparse_scientific.o \ -numparse_currency.o numparse_affixes.o numparse_compositions.o numparse_validators.o \ -number_mapper.o number_multiplier.o number_currencysymbols.o number_skeletons.o number_capi.o \ +numparse_stringsegment.o numparse_unisets.o numparse_parsednumber.o numparse_impl.o \ +numparse_symbols.o numparse_decimal.o numparse_scientific.o numparse_currency.o \ +numparse_affixes.o numparse_compositions.o numparse_validators.o \ ## Header files to install diff --git a/icu4c/source/i18n/number_decimalquantity.cpp b/icu4c/source/i18n/number_decimalquantity.cpp index 40ad848ff4e..ca91c839a69 100644 --- a/icu4c/source/i18n/number_decimalquantity.cpp +++ b/icu4c/source/i18n/number_decimalquantity.cpp @@ -198,10 +198,18 @@ void DecimalQuantity::roundToIncrement(double roundingIncrement, RoundingMode ro roundToMagnitude(-maxFrac, roundingMode, status); } -void DecimalQuantity::multiplyBy(int32_t multiplicand) { +void DecimalQuantity::multiplyBy(double multiplicand) { if (isInfinite() || isZero() || isNaN()) { return; } + // Cover a few simple cases... + if (multiplicand == 1) { + return; + } else if (multiplicand == -1) { + negate(); + return; + } + // Do math for all other cases... // TODO: Should we convert to decNumber instead? double temp = toDouble(); temp *= multiplicand; diff --git a/icu4c/source/i18n/number_decimalquantity.h b/icu4c/source/i18n/number_decimalquantity.h index b205778e19a..4d8bb270d7a 100644 --- a/icu4c/source/i18n/number_decimalquantity.h +++ b/icu4c/source/i18n/number_decimalquantity.h @@ -93,7 +93,7 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { * * @param multiplicand The value by which to multiply. */ - void multiplyBy(int32_t multiplicand); + void multiplyBy(double multiplicand); /** Flips the sign from positive to negative and back. */ void negate(); diff --git a/icu4c/source/i18n/number_fluent.cpp b/icu4c/source/i18n/number_fluent.cpp index c5feb5e4380..81cf90a8471 100644 --- a/icu4c/source/i18n/number_fluent.cpp +++ b/icu4c/source/i18n/number_fluent.cpp @@ -233,6 +233,20 @@ Derived NumberFormatterSettings::decimal(const UNumberDecimalSeparatorD return move; } +template +Derived NumberFormatterSettings::multiplier(const Multiplier& multiplier) const& { + Derived copy(*this); + copy.fMacros.multiplier = multiplier; + return copy; +} + +template +Derived NumberFormatterSettings::multiplier(const Multiplier& multiplier)&& { + Derived move(std::move(*this)); + move.fMacros.multiplier = multiplier; + return move; +} + template Derived NumberFormatterSettings::padding(const Padder& padder) const& { Derived copy(*this); diff --git a/icu4c/source/i18n/number_mapper.cpp b/icu4c/source/i18n/number_mapper.cpp index 84774011011..9acf60382b2 100644 --- a/icu4c/source/i18n/number_mapper.cpp +++ b/icu4c/source/i18n/number_mapper.cpp @@ -257,11 +257,7 @@ MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& propert // MULTIPLIERS // ///////////////// - if (properties.magnitudeMultiplier != 0) { - macros.multiplier = Multiplier::magnitude(properties.magnitudeMultiplier); - } else if (properties.multiplier != 1) { - macros.multiplier = Multiplier::integer(properties.multiplier); - } + macros.multiplier = multiplierFromProperties(properties); ////////////////////// // PROPERTY EXPORTS // diff --git a/icu4c/source/i18n/number_multiplier.cpp b/icu4c/source/i18n/number_multiplier.cpp index ca445ba4dda..85fbd9e6ac0 100644 --- a/icu4c/source/i18n/number_multiplier.cpp +++ b/icu4c/source/i18n/number_multiplier.cpp @@ -11,37 +11,77 @@ #include "number_types.h" #include "number_multiplier.h" +#include "numparse_validators.h" using namespace icu; using namespace icu::number; using namespace icu::number::impl; +using namespace icu::numparse::impl; -Multiplier::Multiplier(int32_t magnitudeMultiplier, int32_t multiplier) - : magnitudeMultiplier(magnitudeMultiplier), multiplier(multiplier) {} +Multiplier::Multiplier(int32_t magnitude, double arbitrary) + : fMagnitude(magnitude), fArbitrary(arbitrary) {} -Multiplier Multiplier::magnitude(int32_t magnitudeMultiplier) { - return {magnitudeMultiplier, 1}; +Multiplier Multiplier::none() { + return {0, 1}; } -Multiplier Multiplier::integer(int32_t multiplier) { - return {0, multiplier}; +Multiplier Multiplier::powerOfTen(int32_t power) { + return {power, 1}; } - -void MultiplierChain::setAndChain(const Multiplier& multiplier, const MicroPropsGenerator* parent) { - this->multiplier = multiplier; - this->parent = parent; +Multiplier Multiplier::arbitraryDecimal(StringPiece multiplicand) { + // TODO: Fix this hack + UErrorCode localError = U_ZERO_ERROR; + DecimalQuantity dq; + dq.setToDecNumber(multiplicand, localError); + return {0, dq.toDouble()}; } -void -MultiplierChain::processQuantity(DecimalQuantity& quantity, MicroProps& micros, UErrorCode& status) const { - parent->processQuantity(quantity, micros, status); - quantity.adjustMagnitude(multiplier.magnitudeMultiplier); - if (multiplier.multiplier != 1) { - quantity.multiplyBy(multiplier.multiplier); +Multiplier Multiplier::arbitraryDouble(double multiplicand) { + return {0, multiplicand}; +} + +void Multiplier::applyTo(impl::DecimalQuantity& quantity) const { + quantity.adjustMagnitude(fMagnitude); + quantity.multiplyBy(fArbitrary); +} + +void Multiplier::applyReciprocalTo(impl::DecimalQuantity& quantity) const { + quantity.adjustMagnitude(-fMagnitude); + if (fArbitrary != 0) { + quantity.multiplyBy(1 / fArbitrary); } } +void +MultiplierFormatHandler::setAndChain(const Multiplier& multiplier, const MicroPropsGenerator* parent) { + this->multiplier = multiplier; + this->parent = parent; +} + +void MultiplierFormatHandler::processQuantity(DecimalQuantity& quantity, MicroProps& micros, + UErrorCode& status) const { + parent->processQuantity(quantity, micros, status); + multiplier.applyTo(quantity); +} + + +// NOTE: MultiplierParseHandler is declared in the header numparse_validators.h +MultiplierParseHandler::MultiplierParseHandler(::icu::number::Multiplier multiplier) + : fMultiplier(multiplier) {} + +void MultiplierParseHandler::postProcess(ParsedNumber& result) const { + if (!result.quantity.bogus) { + fMultiplier.applyReciprocalTo(result.quantity); + // NOTE: It is okay if the multiplier was negative. + } +} + +UnicodeString MultiplierParseHandler::toString() const { + return u""; +} + + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/number_multiplier.h b/icu4c/source/i18n/number_multiplier.h index 93d103dd84a..6baa6865916 100644 --- a/icu4c/source/i18n/number_multiplier.h +++ b/icu4c/source/i18n/number_multiplier.h @@ -8,14 +8,18 @@ #define __SOURCE_NUMBER_MULTIPLIER_H__ #include "numparse_types.h" +#include "number_decimfmtprops.h" U_NAMESPACE_BEGIN namespace number { namespace impl { -class MultiplierChain : public MicroPropsGenerator, public UMemory { +/** + * Wraps a {@link Multiplier} for use in the number formatting pipeline. + */ +class MultiplierFormatHandler : public MicroPropsGenerator, public UMemory { public: - void setAndChain(const Multiplier& other, const MicroPropsGenerator* parent); + void setAndChain(const Multiplier& multiplier, const MicroPropsGenerator* parent); void processQuantity(DecimalQuantity& quantity, MicroProps& micros, UErrorCode& status) const U_OVERRIDE; @@ -26,6 +30,18 @@ class MultiplierChain : public MicroPropsGenerator, public UMemory { }; +/** Gets a Multiplier from a DecimalFormatProperties. In Java, defined in RoundingUtils.java */ +static inline Multiplier multiplierFromProperties(const DecimalFormatProperties& properties) { + if (properties.magnitudeMultiplier != 0) { + return Multiplier::powerOfTen(properties.magnitudeMultiplier); + } else if (properties.multiplier != 1) { + return Multiplier::arbitraryDouble(properties.multiplier); + } else { + return Multiplier::none(); + } +} + + } // namespace impl } // namespace number U_NAMESPACE_END diff --git a/icu4c/source/i18n/number_utils.h b/icu4c/source/i18n/number_utils.h index 8f74692cc61..23c6fcf7ec8 100644 --- a/icu4c/source/i18n/number_utils.h +++ b/icu4c/source/i18n/number_utils.h @@ -75,7 +75,7 @@ struct MicroProps : public MicroPropsGenerator { ScientificModifier scientificModifier; EmptyModifier emptyWeakModifier{false}; EmptyModifier emptyStrongModifier{true}; - MultiplierChain multiplier; + MultiplierFormatHandler multiplier; } helpers; diff --git a/icu4c/source/i18n/numparse_impl.cpp b/icu4c/source/i18n/numparse_impl.cpp index 89db7001a34..c917aad1764 100644 --- a/icu4c/source/i18n/numparse_impl.cpp +++ b/icu4c/source/i18n/numparse_impl.cpp @@ -180,14 +180,10 @@ NumberParserImpl::createParserFromProperties(const number::impl::DecimalFormatPr properties.decimalSeparatorAlwaysShown || properties.maximumFractionDigits != 0; parser->addMatcher(parser->fLocalValidators.decimalSeparator = {patternHasDecimalSeparator}); } - - // TODO: MULTIPLIER -// if (properties.getMultiplier() != null) { -// // We need to use a math context in order to prevent non-terminating decimal expansions. -// // This is only used when dividing by the multiplier. -// parser.addMatcher(new MultiplierHandler(properties.getMultiplier(), -// RoundingUtils.getMathContextOr34Digits(properties))); -// } + // NOTE: Don't look at magnitude multiplier here. That is performed when percent sign is seen. + if (properties.multiplier != 1) { + parser->addMatcher(parser->fLocalValidators.multiplier = {multiplierFromProperties(properties)}); + } parser->freeze(); return parser.orphan(); diff --git a/icu4c/source/i18n/numparse_impl.h b/icu4c/source/i18n/numparse_impl.h index 308a2ffcf81..748b9415ecb 100644 --- a/icu4c/source/i18n/numparse_impl.h +++ b/icu4c/source/i18n/numparse_impl.h @@ -17,6 +17,7 @@ #include "number_decimfmtprops.h" #include "unicode/localpointer.h" #include "numparse_validators.h" +#include "number_multiplier.h" U_NAMESPACE_BEGIN namespace numparse { namespace impl { @@ -78,6 +79,7 @@ class NumberParserImpl : public MutableMatcherCollection { RequireDecimalSeparatorValidator decimalSeparator; RequireExponentValidator exponent; RequireNumberValidator number; + MultiplierParseHandler multiplier; } fLocalValidators; explicit NumberParserImpl(parse_flags_t parseFlags); diff --git a/icu4c/source/i18n/numparse_validators.h b/icu4c/source/i18n/numparse_validators.h index 817ec9cb8d6..d3bc63aceb3 100644 --- a/icu4c/source/i18n/numparse_validators.h +++ b/icu4c/source/i18n/numparse_validators.h @@ -77,6 +77,26 @@ class RequireNumberValidator : public ValidationMatcher, public UMemory { }; +/** + * Wraps a {@link Multiplier} for use in the number parsing pipeline. + * + * NOTE: Implemented in number_multiplier.cpp + */ +class MultiplierParseHandler : public ValidationMatcher, public UMemory { + public: + MultiplierParseHandler() = default; // leaves instance in valid but undefined state + + MultiplierParseHandler(::icu::number::Multiplier multiplier); + + void postProcess(ParsedNumber& result) const U_OVERRIDE; + + UnicodeString toString() const U_OVERRIDE; + + private: + ::icu::number::Multiplier fMultiplier; +}; + + } // namespace impl } // namespace numparse U_NAMESPACE_END diff --git a/icu4c/source/i18n/unicode/numberformatter.h b/icu4c/source/i18n/unicode/numberformatter.h index 51ead000cf4..54698d382ea 100644 --- a/icu4c/source/i18n/unicode/numberformatter.h +++ b/icu4c/source/i18n/unicode/numberformatter.h @@ -86,6 +86,7 @@ namespace impl { // Forward declarations: class NumberParserImpl; +class MultiplierParseHandler; } } @@ -142,7 +143,7 @@ class NumberStringBuilder; class AffixPatternProvider; class NumberPropertyMapper; struct DecimalFormatProperties; -class MultiplierChain; +class MultiplierFormatHandler; class CurrencySymbols; class GeneratorHelpers; @@ -895,7 +896,6 @@ class U_I18N_API IntegerWidth : public UMemory { * The minimum number of places before the decimal separator. * @return An IntegerWidth for chaining or passing to the NumberFormatter integerWidth() setter. * @draft ICU 60 - * @see NumberFormatter */ static IntegerWidth zeroFillTo(int32_t minInt); @@ -909,7 +909,6 @@ class U_I18N_API IntegerWidth : public UMemory { * truncation. * @return An IntegerWidth for passing to the NumberFormatter integerWidth() setter. * @draft ICU 60 - * @see NumberFormatter */ IntegerWidth truncateAt(int32_t maxInt); @@ -966,6 +965,94 @@ class U_I18N_API IntegerWidth : public UMemory { friend class impl::GeneratorHelpers; }; +/** + * A class that defines a quantity by which a number should be multiplied when formatting. + * + *

+ * To create a Multiplier, use one of the factory methods. + * + * @draft ICU 62 + */ +class U_I18N_API Multiplier : public UMemory { + public: + /** + * Do not change the value of numbers when formatting or parsing. + * + * @return A Multiplier to prevent any multiplication. + * @draft ICU 62 + */ + static Multiplier none(); + + /** + * Multiply numbers by 100 before formatting. Useful for combining with a percent unit: + * + *

+     * NumberFormatter::with().unit(NoUnit::percent()).multiplier(Multiplier::powerOfTen(2))
+     * 
+ * + * @return A Multiplier for passing to the setter in NumberFormatter. + * @draft ICU 62 + */ + static Multiplier powerOfTen(int32_t power); + + /** + * Multiply numbers by an arbitrary value before formatting. Useful for unit conversions. + * + * This method takes a string in a decimal number format with syntax + * as defined in the Decimal Arithmetic Specification, available at + * http://speleotrove.com/decimal + * + * Also see the version of this method that takes a double. + * + * @return A Multiplier for passing to the setter in NumberFormatter. + * @draft ICU 62 + */ + static Multiplier arbitraryDecimal(StringPiece multiplicand); + + /** + * Multiply numbers by an arbitrary value before formatting. Useful for unit conversions. + * + * This method takes a double; also see the version of this method that takes an exact decimal. + * + * @return A Multiplier for passing to the setter in NumberFormatter. + * @draft ICU 62 + */ + static Multiplier arbitraryDouble(double multiplicand); + + private: + int32_t fMagnitude; + double fArbitrary; + + Multiplier(int32_t magnitude, double arbitrary); + + Multiplier() : fMagnitude(0), fArbitrary(1) {} + + bool isValid() const { + return fMagnitude != 0 || fArbitrary != 1; + } + + void applyTo(impl::DecimalQuantity& quantity) const; + + void applyReciprocalTo(impl::DecimalQuantity& quantity) const; + + // To allow MacroProps/MicroProps to initialize empty instances: + friend struct impl::MacroProps; + friend struct impl::MicroProps; + + // To allow NumberFormatterImpl to access isBogus() and perform other operations: + friend class impl::NumberFormatterImpl; + + // To allow the helper class MultiplierFormatHandler access to private fields: + friend class impl::MultiplierFormatHandler; + + // To allow access to the skeleton generation code: + friend class impl::GeneratorHelpers; + + // To allow access to parsing code: + friend class ::icu::numparse::impl::NumberParserImpl; + friend class ::icu::numparse::impl::MultiplierParseHandler; +}; + namespace impl { // Do not enclose entire SymbolsWrapper with #ifndef U_HIDE_INTERNAL_API, needed for a protected field @@ -1208,41 +1295,6 @@ class U_I18N_API Padder : public UMemory { }; // Do not enclose entire MacroProps with #ifndef U_HIDE_INTERNAL_API, needed for a protected field -/** @internal */ -class U_I18N_API Multiplier : public UMemory { - public: - /** @internal */ - static Multiplier magnitude(int32_t magnitudeMultiplier); - - /** @internal */ - static Multiplier integer(int32_t multiplier); - - private: - int32_t magnitudeMultiplier; - int32_t multiplier; - - Multiplier(int32_t magnitudeMultiplier, int32_t multiplier); - - Multiplier() : magnitudeMultiplier(0), multiplier(1) {} - - bool isValid() const { - return magnitudeMultiplier != 0 || multiplier != 1; - } - - // To allow MacroProps/MicroProps to initialize empty instances: - friend struct MacroProps; - friend struct MicroProps; - - // To allow NumberFormatterImpl to access isBogus() and perform other operations: - friend class impl::NumberFormatterImpl; - - // To allow the helper class MultiplierChain access to private fields: - friend class impl::MultiplierChain; - - // To allow access to the skeleton generation code: - friend class impl::GeneratorHelpers; -}; - /** @internal */ struct U_I18N_API MacroProps : public UMemory { /** @internal */ @@ -1280,8 +1332,10 @@ struct U_I18N_API MacroProps : public UMemory { /** @internal */ UNumberDecimalSeparatorDisplay decimal = UNUM_DECIMAL_SEPARATOR_COUNT; - Multiplier multiplier; // = Multiplier(); (bogus) + /** @internal */ + Multiplier multiplier; // = Multiplier(); (benign value) + /** @internal */ AffixPatternProvider* affixProvider = nullptr; // no ownership /** @internal */ @@ -1835,11 +1889,48 @@ class U_I18N_API NumberFormatterSettings { * @param style * The decimal separator display strategy to use when rendering numbers. * @return The fluent chain. - * @see #sign + * @see #decimal * @draft ICU 62 */ Derived decimal(const UNumberDecimalSeparatorDisplay &style) &&; + /** + * Sets a multiplier to be used to scale the number by an arbitrary amount before formatting. Most + * common values: + * + *
    + *
  • Multiply by 100: useful for percentages. + *
  • Multiply by an arbitrary value: useful for unit conversions. + *
+ * + *

+ * Pass an element from a {@link Multiplier} factory method to this setter. For example: + * + *

+     * NumberFormatter::with().multiplier(Multiplier::powerOfTen(2))
+     * 
+ * + *

+ * The default is to not apply any multiplier. + * + * @param style + * The decimal separator display strategy to use when rendering numbers. + * @return The fluent chain + * @draft ICU 60 + */ + Derived multiplier(const Multiplier &style) const &; + + /** + * Overload of multiplier() for use on an rvalue reference. + * + * @param style + * The multiplier separator display strategy to use when rendering numbers. + * @return The fluent chain. + * @see #multiplier + * @draft ICU 62 + */ + Derived multiplier(const Multiplier &style) &&; + #ifndef U_HIDE_INTERNAL_API /** diff --git a/icu4c/source/test/intltest/numbertest.h b/icu4c/source/test/intltest/numbertest.h index e1a84aab1e3..2da93bcbb81 100644 --- a/icu4c/source/test/intltest/numbertest.h +++ b/icu4c/source/test/intltest/numbertest.h @@ -64,6 +64,7 @@ class NumberFormatterApiTest : public IntlTest { //void symbolsOverride(); void sign(); void decimal(); + void multiplier(); void locale(); void formatTypes(); void errors(); diff --git a/icu4c/source/test/intltest/numbertest_api.cpp b/icu4c/source/test/intltest/numbertest_api.cpp index a6d1a3553c6..bf650087886 100644 --- a/icu4c/source/test/intltest/numbertest_api.cpp +++ b/icu4c/source/test/intltest/numbertest_api.cpp @@ -77,6 +77,7 @@ void NumberFormatterApiTest::runIndexedTest(int32_t index, UBool exec, const cha //TESTCASE_AUTO(symbolsOverride); TESTCASE_AUTO(sign); TESTCASE_AUTO(decimal); + TESTCASE_AUTO(multiplier); TESTCASE_AUTO(locale); TESTCASE_AUTO(formatTypes); TESTCASE_AUTO(errors); @@ -1909,6 +1910,83 @@ void NumberFormatterApiTest::decimal() { u"0."); } +void NumberFormatterApiTest::multiplier() { + assertFormatDescending( + u"Multiplier None", + u"", + NumberFormatter::with().multiplier(Multiplier::none()), + Locale::getEnglish(), + u"87,650", + u"8,765", + u"876.5", + u"87.65", + u"8.765", + u"0.8765", + u"0.08765", + u"0.008765", + u"0"); + + assertFormatDescending( + u"Multiplier Power of Ten", + nullptr, + NumberFormatter::with().multiplier(Multiplier::powerOfTen(6)), + Locale::getEnglish(), + u"87,650,000,000", + u"8,765,000,000", + u"876,500,000", + u"87,650,000", + u"8,765,000", + u"876,500", + u"87,650", + u"8,765", + u"0"); + + assertFormatDescending( + u"Multiplier Arbitrary Double", + nullptr, + NumberFormatter::with().multiplier(Multiplier::arbitraryDouble(5.2)), + Locale::getEnglish(), + u"455,780", + u"45,578", + u"4,557.8", + u"455.78", + u"45.578", + u"4.5578", + u"0.45578", + u"0.045578", + u"0"); + + assertFormatDescending( + u"Multiplier Arbitrary BigDecimal", + nullptr, + NumberFormatter::with().multiplier(Multiplier::arbitraryDecimal({"5.2", -1})), + Locale::getEnglish(), + u"455,780", + u"45,578", + u"4,557.8", + u"455.78", + u"45.578", + u"4.5578", + u"0.45578", + u"0.045578", + u"0"); + + assertFormatDescending( + u"Multiplier Zero", + nullptr, + NumberFormatter::with().multiplier(Multiplier::arbitraryDouble(0)), + Locale::getEnglish(), + u"0", + u"0", + u"0", + u"0", + u"0", + u"0", + u"0", + u"0", + u"0"); +} + void NumberFormatterApiTest::locale() { // Coverage for the locale setters. UErrorCode status = U_ZERO_ERROR; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MacroProps.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MacroProps.java index fa0f7648ca7..47448c2bbde 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MacroProps.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MacroProps.java @@ -4,6 +4,7 @@ package com.ibm.icu.impl.number; import com.ibm.icu.impl.Utility; import com.ibm.icu.number.IntegerWidth; +import com.ibm.icu.number.Multiplier; import com.ibm.icu.number.Notation; import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay; import com.ibm.icu.number.NumberFormatter.SignDisplay; @@ -25,8 +26,8 @@ public class MacroProps implements Cloneable { public UnitWidth unitWidth; public SignDisplay sign; public DecimalSeparatorDisplay decimal; + public Multiplier multiplier; 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 public Long threshold; // not in API; controls internal self-regulation threshold public ULocale loc; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MultiplierFormatHandler.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MultiplierFormatHandler.java new file mode 100644 index 00000000000..fb7389a6311 --- /dev/null +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MultiplierFormatHandler.java @@ -0,0 +1,25 @@ +// © 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.number.Multiplier; + +/** + * Wraps a {@link Multiplier} for use in the number formatting pipeline. + */ +public class MultiplierFormatHandler implements MicroPropsGenerator { + final Multiplier multiplier; + final MicroPropsGenerator parent; + + public MultiplierFormatHandler(Multiplier multiplier, MicroPropsGenerator parent) { + this.multiplier = multiplier; + this.parent = parent; + } + + @Override + public MicroProps processQuantity(DecimalQuantity quantity) { + MicroProps micros = parent.processQuantity(quantity); + multiplier.applyTo(quantity); + return micros; + } +} diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MultiplierImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MultiplierImpl.java deleted file mode 100644 index dd495d1a795..00000000000 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MultiplierImpl.java +++ /dev/null @@ -1,43 +0,0 @@ -// © 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 java.math.BigDecimal; - -public class MultiplierImpl implements MicroPropsGenerator { - final int magnitudeMultiplier; - final BigDecimal bigDecimalMultiplier; - final MicroPropsGenerator parent; - - public MultiplierImpl(int magnitudeMultiplier) { - this.magnitudeMultiplier = magnitudeMultiplier; - this.bigDecimalMultiplier = null; - parent = null; - } - - public MultiplierImpl(BigDecimal bigDecimalMultiplier) { - this.magnitudeMultiplier = 0; - this.bigDecimalMultiplier = bigDecimalMultiplier; - parent = null; - } - - private MultiplierImpl(MultiplierImpl base, MicroPropsGenerator parent) { - this.magnitudeMultiplier = base.magnitudeMultiplier; - this.bigDecimalMultiplier = base.bigDecimalMultiplier; - this.parent = parent; - } - - public MicroPropsGenerator copyAndChain(MicroPropsGenerator parent) { - return new MultiplierImpl(this, parent); - } - - @Override - public MicroProps processQuantity(DecimalQuantity quantity) { - MicroProps micros = parent.processQuantity(quantity); - quantity.adjustMagnitude(magnitudeMultiplier); - if (bigDecimalMultiplier != null) { - quantity.multiplyBy(bigDecimalMultiplier); - } - return micros; - } -} 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 b9a3cdb6da1..7d9ca686c31 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 @@ -6,6 +6,8 @@ import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; +import com.ibm.icu.number.Multiplier; + /** @author sffc */ public class RoundingUtils { @@ -147,6 +149,14 @@ public class RoundingUtils { } } + /** The default MathContext, unlimited-precision version. */ + public static final MathContext DEFAULT_MATH_CONTEXT_UNLIMITED + = MATH_CONTEXT_BY_ROUNDING_MODE_UNLIMITED[DEFAULT_ROUNDING_MODE.ordinal()]; + + /** The default MathContext, 34-digit version. */ + public static final MathContext DEFAULT_MATH_CONTEXT_34_DIGITS + = MATH_CONTEXT_BY_ROUNDING_MODE_34_DIGITS[DEFAULT_ROUNDING_MODE.ordinal()]; + /** * Gets the user-specified math context out of the property bag. If there is none, falls back to a * math context with unlimited precision and the user-specified rounding mode, which defaults to @@ -198,4 +208,15 @@ public class RoundingUtils { public static MathContext mathContextUnlimited(RoundingMode roundingMode) { return MATH_CONTEXT_BY_ROUNDING_MODE_UNLIMITED[roundingMode.ordinal()]; } + + public static Multiplier multiplierFromProperties(DecimalFormatProperties properties) { + MathContext mc = getMathContextOr34Digits(properties); + if (properties.getMagnitudeMultiplier() != 0) { + return Multiplier.powerOfTen(properties.getMagnitudeMultiplier()).withMathContext(mc); + } else if (properties.getMultiplier() != null) { + return Multiplier.arbitrary(properties.getMultiplier()).withMathContext(mc); + } else { + return null; + } + } } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/MultiplierHandler.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/MultiplierHandler.java deleted file mode 100644 index e2c225084c4..00000000000 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/MultiplierHandler.java +++ /dev/null @@ -1,39 +0,0 @@ -// © 2017 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html#License -package com.ibm.icu.impl.number.parse; - -import java.math.BigDecimal; -import java.math.MathContext; - -/** - * @author sffc - * - */ -public class MultiplierHandler extends ValidationMatcher { - - private final BigDecimal multiplier; - private final MathContext mc; - private final boolean isNegative; - - public MultiplierHandler(BigDecimal multiplier, MathContext mc) { - this.multiplier = BigDecimal.ONE.divide(multiplier, mc).abs(); - this.mc = mc; - isNegative = multiplier.signum() < 0; - } - - @Override - public void postProcess(ParsedNumber result) { - if (result.quantity != null) { - result.quantity.multiplyBy(multiplier); - result.quantity.roundToMagnitude(result.quantity.getMagnitude() - mc.getPrecision(), mc); - if (isNegative) { - result.flags ^= ParsedNumber.FLAG_NEGATIVE; - } - } - } - - @Override - public String toString() { - return ""; - } -} diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/MultiplierParseHandler.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/MultiplierParseHandler.java new file mode 100644 index 00000000000..054cdf41c47 --- /dev/null +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/MultiplierParseHandler.java @@ -0,0 +1,30 @@ +// © 2017 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html#License +package com.ibm.icu.impl.number.parse; + +import com.ibm.icu.number.Multiplier; + +/** + * Wraps a {@link Multiplier} for use in the number parsing pipeline. + */ +public class MultiplierParseHandler extends ValidationMatcher { + + private final Multiplier multiplier; + + public MultiplierParseHandler(Multiplier multiplier) { + this.multiplier = multiplier; + } + + @Override + public void postProcess(ParsedNumber result) { + if (result.quantity != null) { + multiplier.applyReciprocalTo(result.quantity); + // NOTE: It is okay if the multiplier was negative. + } + } + + @Override + public String toString() { + return ""; + } +} diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java index 9283523275b..b5fac765261 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java @@ -230,11 +230,10 @@ public class NumberParserImpl { || properties.getMaximumFractionDigits() != 0; parser.addMatcher(RequireDecimalSeparatorValidator.getInstance(patternHasDecimalSeparator)); } + // NOTE: Don't look at magnitude multiplier here. That is performed when percent sign is seen. if (properties.getMultiplier() != null) { - // We need to use a math context in order to prevent non-terminating decimal expansions. - // This is only used when dividing by the multiplier. - parser.addMatcher(new MultiplierHandler(properties.getMultiplier(), - RoundingUtils.getMathContextOr34Digits(properties))); + parser.addMatcher( + new MultiplierParseHandler(RoundingUtils.multiplierFromProperties(properties))); } parser.freeze(); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/Multiplier.java b/icu4j/main/classes/core/src/com/ibm/icu/number/Multiplier.java new file mode 100644 index 00000000000..d048221f5ce --- /dev/null +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/Multiplier.java @@ -0,0 +1,171 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html#License +package com.ibm.icu.number; + +import java.math.BigDecimal; +import java.math.MathContext; + +import com.ibm.icu.impl.number.DecimalQuantity; +import com.ibm.icu.impl.number.RoundingUtils; + +/** + * A class that defines a quantity by which a number should be multiplied when formatting. + * + *

+ * To create a Multiplier, use one of the factory methods. + * + * @draft ICU 62 + * @provisional This API might change or be removed in a future release. + * @see NumberFormatter + */ +public class Multiplier { + + private static final Multiplier DEFAULT = new Multiplier(0, null); + private static final Multiplier HUNDRED = new Multiplier(2, null); + private static final Multiplier THOUSAND = new Multiplier(3, null); + + private static final BigDecimal BIG_DECIMAL_100 = BigDecimal.valueOf(100); + private static final BigDecimal BIG_DECIMAL_1000 = BigDecimal.valueOf(1000); + + final int magnitude; + final BigDecimal arbitrary; + final BigDecimal reciprocal; + final MathContext mc; + + private Multiplier(int magnitude, BigDecimal arbitrary) { + this(magnitude, arbitrary, RoundingUtils.DEFAULT_MATH_CONTEXT_34_DIGITS); + } + + private Multiplier(int magnitude, BigDecimal arbitrary, MathContext mc) { + this.magnitude = magnitude; + this.arbitrary = arbitrary; + this.mc = mc; + // We need to use a math context in order to prevent non-terminating decimal expansions. + // This is only used when dividing by the multiplier. + if (arbitrary != null && BigDecimal.ZERO.compareTo(arbitrary) != 0) { + this.reciprocal = BigDecimal.ONE.divide(arbitrary, mc); + } else { + this.reciprocal = null; + } + } + + /** + * Do not change the value of numbers when formatting or parsing. + * + * @return A Multiplier to prevent any multiplication. + * @draft ICU 62 + * @provisional This API might change or be removed in a future release. + * @see NumberFormatter + */ + public static Multiplier none() { + return DEFAULT; + } + + /** + * Multiply numbers by 100 before formatting. Useful for combining with a percent unit: + *

+ * + *

+     * NumberFormatter.with().unit(NoUnit.PERCENT).multiplier(Multiplier.powerOfTen(2))
+     * 
+ * + * @return A Multiplier for passing to the setter in NumberFormatter. + * @draft ICU 62 + * @provisional This API might change or be removed in a future release. + * @see NumberFormatter + */ + public static Multiplier powerOfTen(int power) { + if (power == 0) { + return DEFAULT; + } else if (power == 2) { + return HUNDRED; + } else if (power == 3) { + return THOUSAND; + } else { + return new Multiplier(power, null); + } + } + + /** + * Multiply numbers by an arbitrary value before formatting. Useful for unit conversions. + *

+ * This method takes a BigDecimal; also see the version that takes a double. + * + * @return A Multiplier for passing to the setter in NumberFormatter. + * @draft ICU 62 + * @provisional This API might change or be removed in a future release. + * @see NumberFormatter + */ + public static Multiplier arbitrary(BigDecimal multiplicand) { + if (multiplicand.compareTo(BigDecimal.ONE) == 0) { + return DEFAULT; + } else if (multiplicand.compareTo(BIG_DECIMAL_100) == 0) { + return HUNDRED; + } else if (multiplicand.compareTo(BIG_DECIMAL_1000) == 0) { + return THOUSAND; + } else { + return new Multiplier(0, multiplicand); + } + } + + /** + * Multiply numbers by an arbitrary value before formatting. Useful for unit conversions. + *

+ * This method takes a double; also see the version that takes a BigDecimal. + * + * @return A Multiplier for passing to the setter in NumberFormatter. + * @draft ICU 62 + * @provisional This API might change or be removed in a future release. + * @see NumberFormatter + */ + public static Multiplier arbitrary(double multiplicand) { + if (multiplicand == 1) { + return DEFAULT; + } else if (multiplicand == 100.0) { + return HUNDRED; + } else if (multiplicand == 1000.0) { + return THOUSAND; + } else { + return new Multiplier(0, BigDecimal.valueOf(multiplicand)); + } + } + + /** + * @internal + * @deprecated ICU 62 This API is ICU internal only. + */ + @Deprecated + public Multiplier withMathContext(MathContext mc) { + // TODO: Make this public? + if (this.mc.equals(mc)) { + return this; + } + return new Multiplier(magnitude, arbitrary, mc); + } + + /** + * @internal + * @deprecated ICU 62 This API is ICU internal only. + */ + @Deprecated + public void applyTo(DecimalQuantity quantity) { + quantity.adjustMagnitude(magnitude); + if (arbitrary != null) { + quantity.multiplyBy(arbitrary); + } + } + + /** + * @internal + * @deprecated ICU 62 This API is ICU internal only. + */ + @Deprecated + public void applyReciprocalTo(DecimalQuantity quantity) { + quantity.adjustMagnitude(-magnitude); + if (reciprocal != null) { + quantity.multiplyBy(reciprocal); + quantity.roundToMagnitude(quantity.getMagnitude() - mc.getPrecision(), mc); + } + } + +} diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java index e8d8d238b15..0ecca0e8138 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java @@ -12,6 +12,7 @@ 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.MultiplierFormatHandler; import com.ibm.icu.impl.number.MutablePatternModifier; import com.ibm.icu.impl.number.NumberStringBuilder; import com.ibm.icu.impl.number.Padder; @@ -183,7 +184,7 @@ class NumberFormatterImpl { // Multiplier (compatibility mode value). if (macros.multiplier != null) { - chain = macros.multiplier.copyAndChain(chain); + chain = new MultiplierFormatHandler(macros.multiplier, chain); } // Rounding strategy diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java index 88033d70a37..5fb86d5a10c 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java @@ -39,9 +39,10 @@ public abstract class NumberFormatterSettings parent; final int key; @@ -441,6 +442,36 @@ public abstract class NumberFormatterSettings + *

  • Multiply by 100: useful for percentages. + *
  • Multiply by an arbitrary value: useful for unit conversions. + * + * + *

    + * Pass an element from a {@link Multiplier} factory method to this setter. For example: + * + *

    +     * NumberFormatter.with().multiplier(Multiplier.powerOfTen(2))
    +     * 
    + * + *

    + * The default is to not apply any multiplier. + * + * @param multiplier + * An amount to be multiplied against numbers before formatting. + * @return The fluent chain + * @see Multiplier + * @draft ICU 62 + * @provisional This API might change or be removed in a future release. + */ + public T multiplier(Multiplier multiplier) { + return create(KEY_MULTIPLIER, multiplier); + } + /** * Internal method to set a starting macros. * @@ -568,6 +599,11 @@ public abstract class NumberFormatterSettings