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
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;
*
* @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();
return move;
}
+template<typename Derived>
+Derived NumberFormatterSettings<Derived>::multiplier(const Multiplier& multiplier) const& {
+ Derived copy(*this);
+ copy.fMacros.multiplier = multiplier;
+ return copy;
+}
+
+template<typename Derived>
+Derived NumberFormatterSettings<Derived>::multiplier(const Multiplier& multiplier)&& {
+ Derived move(std::move(*this));
+ move.fMacros.multiplier = multiplier;
+ return move;
+}
+
template<typename Derived>
Derived NumberFormatterSettings<Derived>::padding(const Padder& padder) const& {
Derived copy(*this);
// 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 //
#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};
+}
+
+Multiplier Multiplier::arbitraryDecimal(StringPiece multiplicand) {
+ // TODO: Fix this hack
+ UErrorCode localError = U_ZERO_ERROR;
+ DecimalQuantity dq;
+ dq.setToDecNumber(multiplicand, localError);
+ return {0, dq.toDouble()};
+}
+
+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 MultiplierChain::setAndChain(const Multiplier& multiplier, const MicroPropsGenerator* parent) {
+void
+MultiplierFormatHandler::setAndChain(const Multiplier& multiplier, const MicroPropsGenerator* parent) {
this->multiplier = multiplier;
this->parent = parent;
}
-void
-MultiplierChain::processQuantity(DecimalQuantity& quantity, MicroProps& micros, UErrorCode& status) const {
+void MultiplierFormatHandler::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.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"<Multiplier>";
+}
+
#endif /* #if !UCONFIG_NO_FORMATTING */
#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;
};
+/** 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
ScientificModifier scientificModifier;
EmptyModifier emptyWeakModifier{false};
EmptyModifier emptyStrongModifier{true};
- MultiplierChain multiplier;
+ MultiplierFormatHandler multiplier;
} helpers;
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();
#include "number_decimfmtprops.h"
#include "unicode/localpointer.h"
#include "numparse_validators.h"
+#include "number_multiplier.h"
U_NAMESPACE_BEGIN namespace numparse {
namespace impl {
RequireDecimalSeparatorValidator decimalSeparator;
RequireExponentValidator exponent;
RequireNumberValidator number;
+ MultiplierParseHandler multiplier;
} fLocalValidators;
explicit NumberParserImpl(parse_flags_t parseFlags);
};
+/**
+ * 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
// Forward declarations:
class NumberParserImpl;
+class MultiplierParseHandler;
}
}
class AffixPatternProvider;
class NumberPropertyMapper;
struct DecimalFormatProperties;
-class MultiplierChain;
+class MultiplierFormatHandler;
class CurrencySymbols;
class GeneratorHelpers;
* 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);
* truncation.
* @return An IntegerWidth for passing to the NumberFormatter integerWidth() setter.
* @draft ICU 60
- * @see NumberFormatter
*/
IntegerWidth truncateAt(int32_t maxInt);
friend class impl::GeneratorHelpers;
};
+/**
+ * A class that defines a quantity by which a number should be multiplied when formatting.
+ *
+ * <p>
+ * 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:
+ *
+ * <pre>
+ * NumberFormatter::with().unit(NoUnit::percent()).multiplier(Multiplier::powerOfTen(2))
+ * </pre>
+ *
+ * @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
};
// 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 */
/** @internal */
UNumberDecimalSeparatorDisplay decimal = UNUM_DECIMAL_SEPARATOR_COUNT;
- Multiplier multiplier; // = Multiplier(); (bogus)
+ /** @internal */
+ Multiplier multiplier; // = Multiplier(); (benign value)
+ /** @internal */
AffixPatternProvider* affixProvider = nullptr; // no ownership
/** @internal */
* @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:
+ *
+ * <ul>
+ * <li>Multiply by 100: useful for percentages.
+ * <li>Multiply by an arbitrary value: useful for unit conversions.
+ * </ul>
+ *
+ * <p>
+ * Pass an element from a {@link Multiplier} factory method to this setter. For example:
+ *
+ * <pre>
+ * NumberFormatter::with().multiplier(Multiplier::powerOfTen(2))
+ * </pre>
+ *
+ * <p>
+ * 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
/**
//void symbolsOverride();
void sign();
void decimal();
+ void multiplier();
void locale();
void formatTypes();
void errors();
//TESTCASE_AUTO(symbolsOverride);
TESTCASE_AUTO(sign);
TESTCASE_AUTO(decimal);
+ TESTCASE_AUTO(multiplier);
TESTCASE_AUTO(locale);
TESTCASE_AUTO(formatTypes);
TESTCASE_AUTO(errors);
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;
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;
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;
--- /dev/null
+// © 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;
+ }
+}
+++ /dev/null
-// © 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;
- }
-}
import java.math.MathContext;
import java.math.RoundingMode;
+import com.ibm.icu.number.Multiplier;
+
/** @author sffc */
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
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;
+ }
+ }
}
+++ /dev/null
-// © 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 "<MultiplierHandler " + multiplier + ">";
- }
-}
--- /dev/null
+// © 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 "<MultiplierHandler " + multiplier + ">";
+ }
+}
|| 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();
--- /dev/null
+// © 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.
+ *
+ * <p>
+ * 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:
+ * <p>
+ *
+ * <pre>
+ * NumberFormatter.with().unit(NoUnit.PERCENT).multiplier(Multiplier.powerOfTen(2))
+ * </pre>
+ *
+ * @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.
+ * <p>
+ * 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.
+ * <p>
+ * 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);
+ }
+ }
+
+}
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;
// Multiplier (compatibility mode value).
if (macros.multiplier != null) {
- chain = macros.multiplier.copyAndChain(chain);
+ chain = new MultiplierFormatHandler(macros.multiplier, chain);
}
// Rounding strategy
static final int KEY_UNIT_WIDTH = 9;
static final int KEY_SIGN = 10;
static final int KEY_DECIMAL = 11;
- static final int KEY_THRESHOLD = 12;
- static final int KEY_PER_UNIT = 13;
- static final int KEY_MAX = 14;
+ static final int KEY_MULTIPLIER = 12;
+ static final int KEY_THRESHOLD = 13;
+ static final int KEY_PER_UNIT = 14;
+ static final int KEY_MAX = 15;
final NumberFormatterSettings<?> parent;
final int key;
return create(KEY_DECIMAL, style);
}
+ /**
+ * Sets a multiplier to be used to scale the number by an arbitrary amount before formatting. Most
+ * common values:
+ *
+ * <ul>
+ * <li>Multiply by 100: useful for percentages.
+ * <li>Multiply by an arbitrary value: useful for unit conversions.
+ * </ul>
+ *
+ * <p>
+ * Pass an element from a {@link Multiplier} factory method to this setter. For example:
+ *
+ * <pre>
+ * NumberFormatter.with().multiplier(Multiplier.powerOfTen(2))
+ * </pre>
+ *
+ * <p>
+ * 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.
*
macros.decimal = (DecimalSeparatorDisplay) current.value;
}
break;
+ case KEY_MULTIPLIER:
+ if (macros.multiplier == null) {
+ macros.multiplier = (Multiplier) current.value;
+ }
+ break;
case KEY_THRESHOLD:
if (macros.threshold == null) {
macros.threshold = (Long) current.value;
import com.ibm.icu.impl.number.DecimalFormatProperties;
import com.ibm.icu.impl.number.Grouper;
import com.ibm.icu.impl.number.MacroProps;
-import com.ibm.icu.impl.number.MultiplierImpl;
import com.ibm.icu.impl.number.Padder;
import com.ibm.icu.impl.number.PatternStringParser;
import com.ibm.icu.impl.number.PropertiesAffixPatternProvider;
// MULTIPLIERS //
/////////////////
- if (properties.getMagnitudeMultiplier() != 0) {
- macros.multiplier = new MultiplierImpl(properties.getMagnitudeMultiplier());
- } else if (properties.getMultiplier() != null) {
- macros.multiplier = new MultiplierImpl(properties.getMultiplier());
- }
+ macros.multiplier = RoundingUtils.multiplierFromProperties(properties);
//////////////////////
// PROPERTY EXPORTS //
import com.ibm.icu.impl.SoftCache;
import com.ibm.icu.impl.StringSegment;
import com.ibm.icu.impl.number.MacroProps;
+import com.ibm.icu.impl.number.RoundingUtils;
import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay;
import com.ibm.icu.number.NumberFormatter.GroupingStrategy;
import com.ibm.icu.number.NumberFormatter.SignDisplay;
}
// Generate the options
- if (macros.rounder.mathContext != Rounder.DEFAULT_MATH_CONTEXT) {
+ if (macros.rounder.mathContext != RoundingUtils.DEFAULT_MATH_CONTEXT_UNLIMITED) {
sb.append('/');
BlueprintHelpers.generateRoundingModeOption(macros.rounder.mathContext.getRoundingMode(),
sb);
/* package-private final */ MathContext mathContext;
- /* package-private */ static final MathContext DEFAULT_MATH_CONTEXT = RoundingUtils
- .mathContextUnlimited(RoundingUtils.DEFAULT_ROUNDING_MODE);
-
/* package-private */ Rounder() {
- mathContext = DEFAULT_MATH_CONTEXT;
+ mathContext = RoundingUtils.DEFAULT_MATH_CONTEXT_UNLIMITED;
}
/**
import com.ibm.icu.number.FractionRounder;
import com.ibm.icu.number.IntegerWidth;
import com.ibm.icu.number.LocalizedNumberFormatter;
+import com.ibm.icu.number.Multiplier;
import com.ibm.icu.number.Notation;
import com.ibm.icu.number.NumberFormatter;
import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay;
"0.");
}
+ @Test
+ public void multiplier() {
+ assertFormatDescending(
+ "Multiplier None",
+ null,
+ NumberFormatter.with().multiplier(Multiplier.none()),
+ ULocale.ENGLISH,
+ "87,650",
+ "8,765",
+ "876.5",
+ "87.65",
+ "8.765",
+ "0.8765",
+ "0.08765",
+ "0.008765",
+ "0");
+
+ assertFormatDescending(
+ "Multiplier Power of Ten",
+ null,
+ NumberFormatter.with().multiplier(Multiplier.powerOfTen(6)),
+ ULocale.ENGLISH,
+ "87,650,000,000",
+ "8,765,000,000",
+ "876,500,000",
+ "87,650,000",
+ "8,765,000",
+ "876,500",
+ "87,650",
+ "8,765",
+ "0");
+
+ assertFormatDescending(
+ "Multiplier Arbitrary Double",
+ null,
+ NumberFormatter.with().multiplier(Multiplier.arbitrary(5.2)),
+ ULocale.ENGLISH,
+ "455,780",
+ "45,578",
+ "4,557.8",
+ "455.78",
+ "45.578",
+ "4.5578",
+ "0.45578",
+ "0.045578",
+ "0");
+
+ assertFormatDescending(
+ "Multiplier Arbitrary BigDecimal",
+ null,
+ NumberFormatter.with().multiplier(Multiplier.arbitrary(new BigDecimal("5.2"))),
+ ULocale.ENGLISH,
+ "455,780",
+ "45,578",
+ "4,557.8",
+ "455.78",
+ "45.578",
+ "4.5578",
+ "0.45578",
+ "0.045578",
+ "0");
+
+ assertFormatDescending(
+ "Multiplier Zero",
+ null,
+ NumberFormatter.with().multiplier(Multiplier.arbitrary(0)),
+ ULocale.ENGLISH,
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0");
+ }
+
@Test
public void locale() {
// Coverage for the locale setters.