ICU-13678 Cleaning up multiplier implementation and adding public API.

X-SVN-Rev: 41188
This commit is contained in:
Shane Carr 2018-04-03 04:38:16 +00:00
parent 921355c6f0
commit 8ea876aadb
28 changed files with 716 additions and 180 deletions

View File

@ -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

View File

@ -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;

View File

@ -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();

View File

@ -233,6 +233,20 @@ Derived NumberFormatterSettings<Derived>::decimal(const UNumberDecimalSeparatorD
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);

View File

@ -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 //

View File

@ -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"<Multiplier>";
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View File

@ -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

View File

@ -75,7 +75,7 @@ struct MicroProps : public MicroPropsGenerator {
ScientificModifier scientificModifier;
EmptyModifier emptyWeakModifier{false};
EmptyModifier emptyStrongModifier{true};
MultiplierChain multiplier;
MultiplierFormatHandler multiplier;
} helpers;

View File

@ -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();

View File

@ -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);

View File

@ -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

View File

@ -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.
*
* <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
@ -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:
*
* <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
/**

View File

@ -64,6 +64,7 @@ class NumberFormatterApiTest : public IntlTest {
//void symbolsOverride();
void sign();
void decimal();
void multiplier();
void locale();
void formatTypes();
void errors();

View File

@ -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;

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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 "<MultiplierHandler " + multiplier + ">";
}
}

View File

@ -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 "<MultiplierHandler " + multiplier + ">";
}
}

View File

@ -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();

View File

@ -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.
*
* <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);
}
}
}

View File

@ -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

View File

@ -39,9 +39,10 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
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;
@ -441,6 +442,36 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
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.
*
@ -568,6 +599,11 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
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;

View File

@ -11,7 +11,6 @@ import com.ibm.icu.impl.number.CustomSymbolCurrency;
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;
@ -299,11 +298,7 @@ final class NumberPropertyMapper {
// 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 //

View File

@ -11,6 +11,7 @@ import com.ibm.icu.impl.PatternProps;
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;
@ -1267,7 +1268,7 @@ class NumberSkeletonImpl {
}
// 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);

View File

@ -26,11 +26,8 @@ public abstract class Rounder implements Cloneable {
/* 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;
}
/**

View File

@ -29,6 +29,7 @@ import com.ibm.icu.number.CompactNotation;
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;
@ -1882,6 +1883,84 @@ public class NumberFormatterApiTest {
"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.